$npx -y skills add gaia-react/gaia --skill file-tech-debtFile a new tech-debt GitHub issue for an out-of-scope code-review finding, building the dedup key, checking for an existing open or declined-closed match, and only if none exists, creating the issue with the right labels and touching the debt-count staleness sentinel. Trigger on
| 1 | # File a tech-debt issue |
| 2 | |
| 3 | This skill is the single source of truth for turning one out-of-scope finding (a real problem spotted while reviewing something else, and therefore not fixed in place) into a durable, deduplicated GitHub issue. It covers building the key, checking for a prior match, filing when there is none, and nudging the debt-count display to refresh. It does not decide *which* findings are out-of-scope, does not classify security-sensitivity, and does not fix anything, it only files. |
| 4 | |
| 5 | **Callers own their own bookkeeping around this recipe.** Some callers record their own disposition-ledger entry and gate their own downstream state on it after filing succeeds; others file and stop. That bookkeeping is caller-specific and lives in the caller, not here. Follow the steps below exactly as written; do not invent a bookkeeping record, a completion flag, or a run-tracking step of your own on top of them, that would duplicate (or fight with) whatever the caller already does. |
| 6 | |
| 7 | ## 1. Build the dedup key |
| 8 | |
| 9 | Every filed issue's body carries exactly one dedup-key line: a single HTML comment, byte-for-byte in this form: |
| 10 | |
| 11 | ``` |
| 12 | <!-- gaia-debt-key: v1 class=<finding_class> path=<repo-relative-posix-path> line=<integer> --> |
| 13 | ``` |
| 14 | |
| 15 | - `v1` is the schema version. Bump it only for a breaking change to the key's shape, not for routine use. |
| 16 | - `<finding_class>` is the finding's seeded class, or `holistic/unclassified` when the finding maps to no seeded class. |
| 17 | - `<path>` is a repo-relative POSIX path (forward slashes, never an absolute machine path). |
| 18 | - `<line>` is a plain integer. |
| 19 | |
| 20 | This line is what every later step (dedup, re-filing checks, any caller-side ledger) matches against, so build it first and keep it verbatim in the body you construct in step 4. |
| 21 | |
| 22 | ## 2. Check for an existing match (dedup) |
| 23 | |
| 24 | **Never rely on `gh`'s full-text search.** GitHub's search tokenizes on `/ : @`, so it cannot reliably match a key containing those characters. Query and match locally instead, and match on **the parsed `path=` and `line=` fields alone, ignoring `class=`**: a finding reclassified from `holistic/unclassified` to a seeded class (or the reverse) still carries the same `path=`+`line=` and must resolve to the same issue, not a new one. |
| 25 | |
| 26 | 1. `gh issue list --label tech-debt --state open --limit 1000 --json number,title,body`. For each issue's `gaia-debt-key` comment, parse out its `path=` and `line=` fields and compare them against the finding's own path and line: `path=` as a string, `line=` as a parsed integer, so `line=4` never matches `line=42`. Two keys equal on both fields are the same finding regardless of what `class=` either one carries. |
| 27 | 2. Also check `--state closed` with the same `--limit 1000`: the same path+line comparison on a closed issue that carries the `wontfix` label (or was closed as not-planned) means the finding was **declined**, not merely resolved. Do not re-file it. |
| 28 | 3. Keyless fallback for issues a human filed by hand (no machine key present): scan open `tech-debt` issue bodies for the bare `<path>:<line>` substring. Anchor the match so the line number is followed by a non-digit or end-of-string, otherwise `foo.ts:4` false-matches a sibling `foo.ts:42`. This is the same path+line identity as 1 and 2, sourced from a bare-text scan instead of a parsed key; a hit here suppresses re-filing even with no key line at all. |
| 29 | |
| 30 | On any match (1, 2, or 3), hand back to the caller the **matched issue's number**, its **open/closed state**, and, when the match came from a parsed key (1 or 2), that key's **existing verbatim inner key** (`v1 class=… path=… line=…`). This recipe records nothing itself; callers own their bookkeeping (see above). |
| 31 | |
| 32 | Accepted tradeoff: two genuinely distinct findings that land on the exact same `path:line` with different root-cause classes collapse to one issue under path+line dedup. This is the same residual risk the keyless `path:line` fallback already accepted; matching on path+line alone extends it to the machine-keyed case too. |
| 33 | |
| 34 | ## 3. Idempotency: skip if a match exists |
| 35 | |
| 36 | If step 2 found a matching open issue, or a declined-closed one, stop, do not file. The finding already has a disposition; re-filing would create a duplicate. For an open match, the caller records the matched issue's number and its existing inner key |