@hegemonart/get-design-done 1.30.0 → 1.30.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +103 -0
  4. package/README.de.md +2 -0
  5. package/README.fr.md +2 -0
  6. package/README.it.md +2 -0
  7. package/README.ja.md +2 -0
  8. package/README.ko.md +2 -0
  9. package/README.md +3 -1
  10. package/README.zh-CN.md +2 -0
  11. package/agents/design-authority-watcher.md +42 -1
  12. package/agents/design-integration-checker.md +1 -1
  13. package/agents/design-planner.md +1 -1
  14. package/agents/gdd-graph-refresh.md +90 -0
  15. package/bin/gdd-graph +261 -0
  16. package/connections/connections.md +10 -9
  17. package/connections/graphify.md +65 -54
  18. package/package.json +4 -2
  19. package/reference/capability-gap-stage-gate.md +7 -4
  20. package/reference/known-failure-modes.md +337 -1
  21. package/reference/model-tiers.md +2 -2
  22. package/reference/schemas/events.schema.json +61 -0
  23. package/reference/start-interview.md +1 -1
  24. package/scripts/detect-stale-refs.cjs +6 -0
  25. package/scripts/lib/apply-reflections/incubator-proposals.cjs +10 -3
  26. package/scripts/lib/authority-watcher/index.cjs +201 -0
  27. package/scripts/lib/failure-mode-matcher.cjs +460 -0
  28. package/scripts/lib/graph/atomic-write.mjs +68 -0
  29. package/scripts/lib/graph/build.mjs +124 -0
  30. package/scripts/lib/graph/diff.mjs +90 -0
  31. package/scripts/lib/graph/index.mjs +14 -0
  32. package/scripts/lib/graph/query.mjs +155 -0
  33. package/scripts/lib/graph/schema.json +69 -0
  34. package/scripts/lib/graph/schema.mjs +47 -0
  35. package/scripts/lib/graph/status.mjs +88 -0
  36. package/scripts/lib/graph/token-estimate.mjs +27 -0
  37. package/scripts/lib/graph/upsert.mjs +210 -0
  38. package/scripts/lib/{gsd-health-mirror → health-mirror}/index.cjs +1 -1
  39. package/scripts/lib/install/interactive.cjs +27 -2
  40. package/scripts/lib/reflector-capability-gap-aggregator.cjs +32 -0
  41. package/scripts/lib/reflector-kfm-proposer.cjs +468 -0
  42. package/scripts/mcp-servers/gdd-mcp/tools/gdd_health.ts +3 -3
  43. package/skills/apply-reflections/SKILL.md +4 -0
  44. package/skills/apply-reflections/apply-reflections-procedure.md +38 -4
  45. package/skills/connections/connections-onboarding.md +6 -6
  46. package/skills/graphify/SKILL.md +11 -10
  47. package/skills/scan/scan-procedure.md +9 -8
  48. package/agents/gdd-graphify-sync.md +0 -110
  49. /package/scripts/lib/{gsd-health-mirror → health-mirror}/index.d.cts +0 -0
@@ -9,7 +9,13 @@ gate but still requires consent (D-11).
9
9
 
10
10
  ## Schema
11
11
 
12
- Each entry is a single fenced ```yaml block with this flat key:value shape:
12
+ Each entry is a single fenced ```yaml block with this flat key:value shape.
13
+
14
+ ### Schema v1 (Phase 30, matcher-consumed) — original 6 fields
15
+
16
+ These six fields are consumed by `scripts/lib/issue-reporter/triage-matcher.cjs`
17
+ (`matchKnownFailure(errorContext)`). The matcher reads ONLY these fields and
18
+ ignores everything else gracefully (D-04 backward-compat).
13
19
 
14
20
  - `id` — stable identifier (kebab-case or `KFM-NNN` numeric). Required.
15
21
  - `pattern` — JavaScript regex string. Matched against
@@ -21,6 +27,39 @@ Each entry is a single fenced ```yaml block with this flat key:value shape:
21
27
  whitelist: 30-04 may *propose* `--report` at error time for this
22
28
  class. Defaults to `false`. Advisory; the matcher does not act on it.
23
29
 
30
+ ### Schema v2 (Phase 30.5 D-02) — additive fields
31
+
32
+ These five fields are required on every entry from Phase 30.5 onward.
33
+ They are NOT consumed by the Phase 30 matcher (D-04 backward-compat
34
+ invariant); they exist for human authors, retrospective harvesting, and
35
+ future tooling (e.g. the Phase 30.5-02 fuzzy matcher, the Phase 30.5-03
36
+ reflector incubator). Adding them does not change the matcher's
37
+ behaviour for the original 6 fields.
38
+
39
+ - `symptom` — string, 1–3 sentences. Plain-English description of what
40
+ the user sees when this failure mode hits. Required.
41
+ *Example:* `'Build fails with EUSAGE about a missing or stale lockfile after a package.json edit.'`
42
+ - `root_cause` — string, 1–2 sentences. Technical explanation of why
43
+ the failure happens. Required.
44
+ *Example:* `'npm ci enforces lockfile parity with package.json; manually editing one without the other breaks parity.'`
45
+ - `fix` — string (single line; multi-step encoded as `1) … 2) … 3) …`).
46
+ Step-by-step user-runnable remedy. The original `remedy` field stays
47
+ as the short matcher-consumed one-liner; `fix` is the fuller version
48
+ with prerequisites and verification steps. The two MAY differ. Required.
49
+ *Example:* `1) Run npm install once locally. 2) Stage the updated package-lock.json. 3) Commit and re-run npm ci.`
50
+ - `related_phases` — number[] (YAML flow style: `[12, 24]`). Phase numbers
51
+ this mode touches. Empty array `[]` is allowed when the mode is
52
+ cross-cutting and not tied to a specific phase. Required.
53
+ *Example:* `[12, 14.6, 24]`
54
+ - `first_observed_cycle` — string. Cycle slug like `cycle-2026-05`, or
55
+ `pre-30.5` for entries harvested from before this catalogue formalised
56
+ the schema. Required.
57
+ *Example:* `'cycle-2026-05'`
58
+
59
+ The Phase 30 matcher (`scripts/lib/issue-reporter/triage-matcher.cjs`)
60
+ consumes ONLY the original 6 fields per D-04. The 5 additive fields are
61
+ for human authors, retrospective harvesting, and future tooling.
62
+
24
63
  ## Matching policy
25
64
 
26
65
  - **First match wins.** Entries are evaluated in file order. The matcher
@@ -51,6 +90,11 @@ diagnosis: 'Permission denied writing to .design/ — the plugin cannot persist
51
90
  remedy: 'Run `chown -R "$USER" .design` (or recreate the directory as your normal user) and retry the command.'
52
91
  severity: medium
53
92
  propose_report: false
93
+ symptom: 'Commands that write into `.design/` (state snapshots, reflection drafts, issue drafts) fail with `EACCES: permission denied`. The plugin cannot persist its work-product directory.'
94
+ root_cause: 'The `.design/` directory was created by a different user (often root after a `sudo`-clone) or its permissions were tightened so the current user lacks write access.'
95
+ fix: '1) Identify the owner with `ls -ld .design` and `id -u`. 2) Run `sudo chown -R "$USER" .design` to reclaim ownership. 3) Alternatively, `rm -rf .design && mkdir .design` if the directory contains no state you need. 4) Re-run the command that failed.'
96
+ related_phases: [11, 22, 29]
97
+ first_observed_cycle: 'pre-30.5'
54
98
  ```
55
99
 
56
100
  ### KFM-002 — `gh` CLI not on PATH
@@ -66,6 +110,11 @@ diagnosis: 'GitHub CLI (`gh`) is not installed or not on PATH; the issue reporte
66
110
  remedy: 'Install gh from https://cli.github.com and run `gh auth login`, then retry. (Or use the clipboard fallback: the payload is already on disk under .design/issue-drafts/.)'
67
111
  severity: low
68
112
  propose_report: false
113
+ symptom: 'The `/gdd:report-issue` flow exits during the outbound submission step with `gh: command not found`, `spawn gh ENOENT`, or (on Windows) `''gh'' is not recognized as an internal or external command`.'
114
+ root_cause: 'Phase 30 D-05 routes the outbound submission through the user''s `gh` CLI; when `gh` is absent or off `PATH`, the spawn fails before any network call.'
115
+ fix: '1) Install `gh` from https://cli.github.com (`brew install gh`, `winget install GitHub.cli`, or distro package). 2) Run `gh auth login` and pick GitHub.com + your preferred protocol. 3) Verify with `gh auth status`. 4) Re-run `/gdd:report-issue`. As a fallback, the issue draft is already saved under `.design/issue-drafts/` — open it and file manually via the GitHub web UI.'
116
+ related_phases: [30]
117
+ first_observed_cycle: 'pre-30.5'
69
118
  ```
70
119
 
71
120
  ### KFM-003 — Node.js version mismatch
@@ -81,6 +130,11 @@ diagnosis: 'Active Node.js version is below the plugin''s required >=22; modern
81
130
  remedy: 'Upgrade Node to >=22 (e.g. `nvm install 22 && nvm use 22`) and rerun.'
82
131
  severity: high
83
132
  propose_report: false
133
+ symptom: 'npm warns `engine "node" is incompatible` / `Unsupported engine`, or Node throws `SyntaxError: Unexpected token "satisfies"`, or the test runner fails to recognise `--experimental-strip-types`.'
134
+ root_cause: 'The plugin declares `engines.node: ">=22"` in `package.json` and uses Node 22-only syntax (e.g. `satisfies` operator in TypeScript-stripped tests). Older Node runtimes cannot parse the source.'
135
+ fix: '1) Check active version with `node -v`. 2) If <22, run `nvm install 22 && nvm use 22` (Linux/macOS) or `nvm-windows install 22` / `winget install OpenJS.NodeJS`. 3) Reopen the shell or re-source `~/.nvmrc`. 4) Verify with `node -v` then re-run the command.'
136
+ related_phases: [12, 14.6, 24]
137
+ first_observed_cycle: 'pre-30.5'
84
138
  ```
85
139
 
86
140
  ### KFM-004 — Figma token missing
@@ -95,6 +149,11 @@ diagnosis: 'FIGMA_TOKEN environment variable is missing or invalid; Figma-depend
95
149
  remedy: 'Generate a personal access token at https://www.figma.com/developers/api#access-tokens and `export FIGMA_TOKEN=<token>` in your shell profile.'
96
150
  severity: medium
97
151
  propose_report: false
152
+ symptom: 'Figma-aware commands abort with `FIGMA_TOKEN not set`, return HTTP 401 from `api.figma.com`, or emit `figma: unauthorized`.'
153
+ root_cause: 'Figma flows authenticate via a personal access token in `FIGMA_TOKEN`. When the env var is unset, expired, or revoked, the API rejects every request.'
154
+ fix: '1) Visit https://www.figma.com/developers/api#access-tokens and generate a new token. 2) `export FIGMA_TOKEN=<token>` in `~/.zshrc` / `~/.bashrc` (or `$Env:FIGMA_TOKEN` in PowerShell). 3) Open a new shell and verify with `echo $FIGMA_TOKEN`. 4) Re-run the failing command.'
155
+ related_phases: [13.2, 18, 19.6]
156
+ first_observed_cycle: 'pre-30.5'
98
157
  ```
99
158
 
100
159
  ### KFM-005 — Git working tree dirty
@@ -110,6 +169,11 @@ diagnosis: 'Git working tree has uncommitted changes; the command requires a cle
110
169
  remedy: 'Commit, stash (`git stash -u`), or discard your local changes, then rerun the command.'
111
170
  severity: low
112
171
  propose_report: false
172
+ symptom: 'A phase-tooling command (e.g. `/gsd:execute-phase`, milestone closeout) aborts with `working tree is not clean`, `uncommitted changes`, or `Changes not staged for commit`.'
173
+ root_cause: 'Several GSD/GDD tooling commands assume a clean git checkpoint between cycles so commits remain atomic and easy to revert. A dirty working tree breaks that invariant.'
174
+ fix: '1) Run `git status` to see which files are dirty. 2) If the changes are wanted, stage them individually with `git add <path>` and `git commit -m "..."`. 3) If they''re scratch work, `git stash -u` (preserves them) or `git checkout -- <path>` (discards them). 4) Re-run the original command.'
175
+ related_phases: [22, 23.5, 25]
176
+ first_observed_cycle: 'pre-30.5'
113
177
  ```
114
178
 
115
179
  ### KFM-006 — `.planning/` directory missing
@@ -124,6 +188,11 @@ diagnosis: '.planning/ directory does not exist; the project has not been initia
124
188
  remedy: 'Run `/gsd:new-project` to bootstrap the planning structure, then retry.'
125
189
  severity: medium
126
190
  propose_report: false
191
+ symptom: 'A GSD/GDD command fails with `ENOENT: no such file or directory, open ''.planning/STATE.md''` or a similar `ENOENT` referencing the `.planning/` tree.'
192
+ root_cause: 'GSD/GDD commands read project state from `.planning/` (STATE.md, ROADMAP.md, REQUIREMENTS.md, phases/). The directory must be bootstrapped by `/gsd:new-project` before any other workflow command will work.'
193
+ fix: '1) Verify you''re in the project root with `pwd` / `ls`. 2) If `.planning/` is genuinely missing, run `/gsd:new-project` to scaffold it. 3) If you expected `.planning/` to exist (cloned repo), check whether it was excluded by `.gitignore` — `.planning/` is local-only in some projects. 4) Re-run the failing command.'
194
+ related_phases: [22, 23.5]
195
+ first_observed_cycle: 'pre-30.5'
127
196
  ```
128
197
 
129
198
  ### KFM-007 — `reference/registry.json` invalid JSON
@@ -138,6 +207,11 @@ diagnosis: 'reference/registry.json failed to parse as JSON — likely a trailin
138
207
  remedy: 'Open reference/registry.json in your editor; the JSON parser error message will pinpoint the line. Fix the syntax and retry.'
139
208
  severity: medium
140
209
  propose_report: false
210
+ symptom: 'A tool that loads the Phase 14.5 reference registry crashes with `SyntaxError: Unexpected token` or `JSON.parse` failure referencing `reference/registry.json`.'
211
+ root_cause: 'reference/registry.json is hand-edited as part of new-phase work; a trailing comma, unquoted key, or unbalanced brace from a recent edit breaks JSON.parse on the next consumer.'
212
+ fix: '1) Open `reference/registry.json` in your editor — the JSON parser error message includes the offending line/column. 2) Look for trailing commas (JSON forbids them), unquoted keys, or unbalanced braces/brackets. 3) Validate with `node -e "JSON.parse(require(''fs'').readFileSync(''reference/registry.json''))"` — silent exit means success. 4) Re-run the failing command.'
213
+ related_phases: [14.5, 25]
214
+ first_observed_cycle: 'pre-30.5'
141
215
  ```
142
216
 
143
217
  ### KFM-008 — MCP server unreachable
@@ -152,6 +226,11 @@ diagnosis: 'An MCP server (Figma, GDD-state, or GDD-tools) is not reachable; the
152
226
  remedy: 'Start the relevant MCP server (see scripts/mcp-servers/) and confirm `claude mcp list` shows it as connected.'
153
227
  severity: medium
154
228
  propose_report: true
229
+ symptom: 'A command depending on an MCP server fails with `MCP unreachable`, `ECONNREFUSED`, `mcp server not running`, or `connection refused ws://...`. Tool calls routed through MCP never reach their target.'
230
+ root_cause: 'The local MCP transport (WebSocket or stdio bridge) is not bound. The MCP server process is either not started, crashed silently, or is listening on a different port than the client expects.'
231
+ fix: '1) Run `claude mcp list` and check the status column for the failing server. 2) If it shows `disconnected`, start it: `scripts/mcp-servers/<name>.cjs` or the launcher script for that server. 3) Confirm the port matches the value in `mcp.json` or `.mcp.json`. 4) Re-run the failing command. If the server crashes on start, this is a maintainer report path (propose_report:true).'
232
+ related_phases: [27.7, 33.6]
233
+ first_observed_cycle: 'pre-30.5'
155
234
  ```
156
235
 
157
236
  ### KFM-009 — Plugin file accidentally deleted
@@ -168,6 +247,11 @@ diagnosis: 'A plugin file is missing — most often the result of a local `git c
168
247
  remedy: 'Reinstall the plugin: `npm install -g @hegemonart/get-design-done` (or pull the repo fresh in dev). If the file should exist, the error message gives its path.'
169
248
  severity: medium
170
249
  propose_report: true
250
+ symptom: 'A command errors out with `Cannot find module` referencing a path under `scripts/lib/`, `skills/.../SKILL.md`, or `reference/*.md`. From the user''s perspective, a file that should ship with the plugin is gone.'
251
+ root_cause: 'The file was removed locally — most often by an aggressive `git clean -fdx`, a worktree teardown that swept up tracked files, or an incomplete reinstall. The error path tells you exactly which file is missing.'
252
+ fix: '1) Note the missing path from the error. 2) `git status` to confirm it''s gone (not just renamed). 3) `git checkout HEAD -- <path>` to restore from the current commit. 4) If the file was never committed locally, run `npm install -g @hegemonart/get-design-done` (or pull the repo fresh in dev mode). 5) Re-run the failing command. (propose_report:true because a missing plugin file can also indicate an upstream packaging bug.)'
253
+ related_phases: [24, 25]
254
+ first_observed_cycle: 'pre-30.5'
171
255
  ```
172
256
 
173
257
  ### KFM-010 — Disk full / ENOSPC
@@ -182,4 +266,256 @@ diagnosis: 'Disk is full — no space left on the device the plugin is writing t
182
266
  remedy: 'Free space (e.g. clear `.design/cache/`, prune old worktrees, empty trash) and retry.'
183
267
  severity: high
184
268
  propose_report: false
269
+ symptom: 'A write operation fails with `ENOSPC`, `no space left on device`, or `disk full`. The error often surfaces from a seemingly unrelated path (cache write, log append, snapshot save).'
270
+ root_cause: 'The filesystem holding `.design/`, the repo, or the npm cache has run out of free blocks or inodes. Node''s `fs.writeFile` (and downstream tools) propagate the kernel''s `ENOSPC` directly.'
271
+ fix: '1) Check free space with `df -h` (Linux/macOS) or `Get-PSDrive` (PowerShell). 2) Clear local caches: `rm -rf .design/cache/`, `npm cache clean --force`, prune stale `git worktree list` entries. 3) Empty system trash / Recycle Bin. 4) If the issue is inodes (not blocks), check with `df -i` and clean small-file-heavy dirs (`node_modules`, build artifacts). 5) Re-run the command.'
272
+ related_phases: [22, 24, 29]
273
+ first_observed_cycle: 'pre-30.5'
274
+ ```
275
+
276
+ ### KFM-011 — `validate-skill-length` pre-commit hook fails
277
+
278
+ The local pre-commit hook `scripts/validate-skill-length.cjs` rejects
279
+ SKILL.md files exceeding the agentskills.io length budget. A commit is
280
+ blocked until the offending skill is trimmed or the `--fix` autoformatter
281
+ is run.
282
+
283
+ ```yaml
284
+ id: KFM-011
285
+ pattern: '(validate-skill-length|skill.*exceeds.*(line|character)|SKILL\.md.*(too long|over.*budget))'
286
+ diagnosis: 'A SKILL.md file exceeds the agentskills.io length budget; the validate-skill-length pre-commit hook is blocking the commit.'
287
+ remedy: 'Run `node scripts/validate-skill-length.cjs --fix` to auto-trim, then re-stage and commit.'
288
+ severity: low
289
+ propose_report: true
290
+ symptom: 'A `git commit` aborts with `validate-skill-length` warnings: one or more `SKILL.md` files exceed the per-skill line or character budget. The commit never lands.'
291
+ root_cause: 'The pre-commit hook enforces the agentskills.io length contract (Phase 28.5). When a recent edit pushes a skill over budget, the hook fails fast to keep skills shippable.'
292
+ fix: '1) Inspect the hook output — it names each offending file and its measured size. 2) Run `node scripts/validate-skill-length.cjs --fix` to autoformat any auto-trimmable bloat. 3) If the file still exceeds budget, manually shorten descriptions or move detail into a `reference/` sidecar. 4) `git add` the trimmed file and re-run `git commit`. This is on the propose_report whitelist because recurring trips often indicate the budget itself needs reconsideration.'
293
+ related_phases: [28.5, 28.6]
294
+ first_observed_cycle: 'cycle-2026-05'
295
+ ```
296
+
297
+ ### KFM-012 — `npm ci` lockfile drift
298
+
299
+ `npm ci` enforces strict parity between `package.json` and
300
+ `package-lock.json`. A manual edit to one without the other surfaces as
301
+ an `EUSAGE` error with the lockfile path called out.
302
+
303
+ ```yaml
304
+ id: KFM-012
305
+ pattern: '(npm error code EUSAGE|can only install (packages )?with an existing package-lock\.json|npm ci.*lockfile)'
306
+ diagnosis: 'package-lock.json is out of sync with package.json; npm ci refuses to install when the lockfile is missing or drifted.'
307
+ remedy: 'Run `npm install` once locally to regenerate the lockfile, commit the updated package-lock.json, then re-run `npm ci`.'
308
+ severity: medium
309
+ propose_report: false
310
+ symptom: 'CI or a clean install fails with `npm error code EUSAGE` and a message about `npm ci` only working with an existing, up-to-date `package-lock.json`.'
311
+ root_cause: 'A manual edit to `package.json` (e.g. bumping a version) without a follow-up `npm install` leaves the lockfile drifted. `npm ci` rejects drift to keep installs reproducible.'
312
+ fix: '1) Pull the latest main if needed. 2) Run `npm install` (NOT `npm ci`) to regenerate the lockfile from `package.json`. 3) Inspect the diff with `git diff package-lock.json` — should match the package.json change. 4) Stage and commit the updated lockfile. 5) Re-run `npm ci`.'
313
+ related_phases: [25]
314
+ first_observed_cycle: 'cycle-2026-05'
315
+ ```
316
+
317
+ ### KFM-013 — lychee transient SSL failure on whitelisted hosts
318
+
319
+ The link-check job (lychee) reports intermittent TLS errors on a small
320
+ set of well-known authority hosts (`heydonworks.com`,
321
+ `ryanmulligan.dev`, `adamwathan.me`). These are usually transient and
322
+ clear on re-run; persistent failures warrant an allowlist entry.
323
+
324
+ ```yaml
325
+ id: KFM-013
326
+ pattern: '(lychee.*\[ERROR\].*(heydonworks\.com|ryanmulligan\.dev|adamwathan\.me)|SSL.*handshake.*(heydonworks|ryanmulligan|adamwathan))'
327
+ diagnosis: 'lychee link-check hit a transient TLS/SSL failure on one of the whitelisted authority hosts; likely a flaky CDN handshake rather than a real broken link.'
328
+ remedy: 'Rerun the link-check job; if 3 consecutive runs fail, add the host to lychee allowlist in .lycheeignore or workflow config.'
329
+ severity: low
330
+ propose_report: false
331
+ symptom: 'The `link-check` workflow fails with `lychee [ERROR]` lines naming `heydonworks.com`, `ryanmulligan.dev`, or `adamwathan.me`, often with a TLS handshake or `SSL` error in the message.'
332
+ root_cause: 'These hosts sit behind CDNs that occasionally renegotiate TLS sessions mid-flight from CI runners. lychee surfaces the failure as a hard error even when the URL is reachable on retry.'
333
+ fix: '1) Open the failed Actions run and re-run only failed jobs. 2) If the same host fails three times in a row, edit `.lycheeignore` (or the equivalent allowlist in the workflow) and add the URL or hostname. 3) Re-run the workflow.'
334
+ related_phases: [13.2, 18]
335
+ first_observed_cycle: 'cycle-2026-05'
336
+ ```
337
+
338
+ ### KFM-014 — `gh pr merge` fast-forward warning (cosmetic)
339
+
340
+ GitHub's CLI surfaces a "Not possible to fast-forward" warning after a
341
+ successful server-side merge in some workflow combinations. The merge
342
+ already landed; the warning is purely cosmetic.
343
+
344
+ ```yaml
345
+ id: KFM-014
346
+ pattern: '(Not possible to fast-forward, do you want to continue|gh pr merge.*warning.*fast-forward)'
347
+ diagnosis: 'gh pr merge surfaced a fast-forward warning, but the server-side merge already succeeded; the local branch is just lagging.'
348
+ remedy: 'Verify the PR is merged on GitHub (gh pr view --json state); the warning is cosmetic and can be ignored. Pull main to sync your local branch.'
349
+ severity: low
350
+ propose_report: false
351
+ symptom: 'After `gh pr merge`, the CLI prints `Not possible to fast-forward, do you want to continue?` even though the PR shows as merged on github.com. The user is unsure whether the merge happened.'
352
+ root_cause: 'The local main branch has diverged from origin/main between the start of `gh pr merge` and its post-merge sync step. The server-side merge succeeded; the local refs just need a `git pull` to catch up.'
353
+ fix: '1) Confirm the merge with `gh pr view <number> --json state,mergedAt`. 2) If `state` is `MERGED`, the warning is cosmetic. 3) Run `git checkout main && git pull origin main` to sync local refs. 4) Continue with the next workflow step.'
354
+ related_phases: [25]
355
+ first_observed_cycle: 'cycle-2026-05'
356
+ ```
357
+
358
+ ### KFM-015 — CodeQL `js/incomplete-sanitization` false positive
359
+
360
+ CodeQL flags `js/incomplete-sanitization` on regex-based input that
361
+ operates on an enum-constrained value. The alert is a false positive in
362
+ that narrow case but cannot be silenced without an inline justification.
363
+
364
+ ```yaml
365
+ id: KFM-015
366
+ pattern: '(js/incomplete-sanitization|CodeQL.*incomplete[- ]sanitization)'
367
+ diagnosis: 'CodeQL flagged js/incomplete-sanitization on input that is already constrained to an enum/whitelist; common false-positive class.'
368
+ remedy: 'Add a CodeQL justification comment (`// codeql[js/incomplete-sanitization]`) explaining the enum constraint, then dismiss the alert with reason "false positive — input is enum-constrained".'
369
+ severity: low
370
+ propose_report: false
371
+ symptom: 'A CodeQL scan reports `js/incomplete-sanitization` on a line that sanitises user-supplied input which is already constrained to a small enum (e.g. `low|medium|high`).'
372
+ root_cause: 'CodeQL''s data-flow analysis cannot prove that an upstream check restricts the input to an enum, so it conservatively flags the regex-based normaliser as incomplete sanitization.'
373
+ fix: '1) Confirm via code inspection that the input is genuinely enum-constrained upstream (e.g. validated by a `SEVERITIES.has(x)` check). 2) Add an inline justification comment immediately above the flagged line: `// codeql[js/incomplete-sanitization] — input is enum-constrained by SEVERITIES.has() at line N`. 3) Open the alert in GitHub Security tab and dismiss with reason "False positive". 4) Push the justification comment so future scans also surface it.'
374
+ related_phases: [25, 27.5]
375
+ first_observed_cycle: 'cycle-2026-05'
376
+ ```
377
+
378
+ ### KFM-016 — `gitleaks` false positive on documentation examples
379
+
380
+ `gitleaks` flags literal example tokens (`ghp_…`, `sk-ant-…`) in
381
+ `reference/*.md` documentation. These are educational examples, not real
382
+ credentials, but the scanner cannot tell them apart.
383
+
384
+ ```yaml
385
+ id: KFM-016
386
+ pattern: '(gitleaks.*finding.*reference/|gitleaks.*(ghp_|sk-ant-).*reference)'
387
+ diagnosis: 'gitleaks flagged a documentation example as a real secret; the path under reference/ contains an illustrative token literal, not a live credential.'
388
+ remedy: 'Either add the file path to .gitleaks.toml allowlist OR rewrite the example to use a clearly-fake placeholder like `EXAMPLE_TOKEN_REDACTED`.'
389
+ severity: low
390
+ propose_report: false
391
+ symptom: 'A `gitleaks` scan in CI fails with a finding pointing at a `reference/*.md` file containing a string that pattern-matches a token shape (e.g. `ghp_…` for a GitHub PAT or `sk-ant-…` for an Anthropic key).'
392
+ root_cause: 'gitleaks rules are pattern-based and cannot distinguish an example literal in documentation from a real leaked credential. Docs that include token shapes for teaching purposes trip the scanner.'
393
+ fix: '1) Confirm via `git log -p <file>` that the token is example-only and was never live. 2) Edit `.gitleaks.toml` and add the file path under `[allowlist] paths`. OR rewrite the example to use an obviously-fake placeholder like `ghp_EXAMPLE_REDACTED`. 3) Push the change and re-run the scan. 4) If the leak was real, follow secret-rotation playbook instead.'
394
+ related_phases: [25]
395
+ first_observed_cycle: 'cycle-2026-05'
396
+ ```
397
+
398
+ ### KFM-017 — `release.yml` version mismatch with `plugin.json`
399
+
400
+ The Phase 25 release safeguard rejects a `workflow_dispatch` invocation
401
+ whose input version doesn't match the version declared in `plugin.json`.
402
+ This is the manifest-lockstep guard catching a forgotten bump.
403
+
404
+ ```yaml
405
+ id: KFM-017
406
+ pattern: '(release\.yml.*version (mismatch|does not match)|workflow_dispatch.*plugin\.json.*version)'
407
+ diagnosis: 'release.yml refused the workflow_dispatch because input version disagrees with plugin.json; the lockstep version guard is intentionally blocking.'
408
+ remedy: 'Either correct the dispatch input to match plugin.json, or bump plugin.json (and the other 5 manifests) to the target version and re-dispatch.'
409
+ severity: medium
410
+ propose_report: false
411
+ symptom: 'A manual `workflow_dispatch` of `release.yml` fails immediately with an error about input version not matching `plugin.json`. The release does not proceed.'
412
+ root_cause: 'Phase 25 added a manifest-lockstep guard to `release.yml`: the dispatch input version MUST match `plugin.json` exactly. The guard catches the case where someone tries to release a version without first bumping all 6 manifests.'
413
+ fix: '1) Read the current `plugin.json` version. 2) If the dispatch input was a typo, re-dispatch with the correct version. 3) If you genuinely intended a new version, run the 6-manifest lockstep bump first: `plugin.json`, `package.json`, `package-lock.json`, `README.md`, `CHANGELOG.md`, the SDK version constant. 4) Commit the bumps, then re-dispatch `release.yml`.'
414
+ related_phases: [25]
415
+ first_observed_cycle: 'cycle-2026-05'
416
+ ```
417
+
418
+ ### KFM-018 — `npm publish` 404 after `NPM_TOKEN` rotation
419
+
420
+ `npm publish` returns `404 Not Found` from the registry when the
421
+ `NPM_TOKEN` secret has been rotated upstream but the GitHub repo secret
422
+ still holds the old value. (Common after npm''s May 2026 Mini
423
+ Shai-Hulud forced rotation.)
424
+
425
+ ```yaml
426
+ id: KFM-018
427
+ pattern: '(npm error 404|Not Found - PUT https://registry\.npmjs\.org/|npm publish.*401.*registry\.npmjs)'
428
+ diagnosis: 'npm publish failed with 404/401 against the registry; the most common cause in 2026 is a rotated NPM_TOKEN that the repo secret has not caught up to.'
429
+ remedy: 'Regenerate token at npmjs.com, update NPM_TOKEN repo secret in GitHub Settings, retry the release workflow.'
430
+ severity: medium
431
+ propose_report: false
432
+ symptom: 'A release run''s `npm publish` step fails with `npm error 404 Not Found - PUT https://registry.npmjs.org/<package>` or a 401 on the same URL. No version is published.'
433
+ root_cause: 'Either the `NPM_TOKEN` secret in the GitHub repo settings was rotated upstream (npmjs.com revoked the old token, common during the May 2026 Mini Shai-Hulud incident), or the token never had publish scope for the package.'
434
+ fix: '1) Log into npmjs.com → Access Tokens. 2) Generate a new Automation token with publish scope for the package. 3) In GitHub: Settings → Secrets → update `NPM_TOKEN` with the new value. 4) Re-dispatch the release workflow. 5) If the failure persists, verify the package name in `package.json` matches what npm expects and that 2FA settings allow automation tokens.'
435
+ related_phases: [25]
436
+ first_observed_cycle: 'cycle-2026-05'
437
+ ```
438
+
439
+ ### KFM-019 — macOS symlinked tmpdir comparison failure
440
+
441
+ Tests that compare a tmpdir-derived path against an expected absolute
442
+ path fail on macOS because `/var/folders/...` is a symlink to
443
+ `/private/var/folders/...`. The string comparison sees mismatched
444
+ prefixes; `fs.realpathSync` resolves them.
445
+
446
+ ```yaml
447
+ id: KFM-019
448
+ pattern: '(/var/folders/.*\/private/var/folders|AssertionError.*expected.*\/private\/var|tmpdir.*symlink.*realpath)'
449
+ diagnosis: 'macOS tmpdir is a symlink (/var/folders/ -> /private/var/folders/); a string equality test against tmpdir path fails because one side is resolved and the other isn''t.'
450
+ remedy: 'Wrap tmpdir reads in fs.realpathSync before comparing; or normalize both sides to realpath at assertion time.'
451
+ severity: low
452
+ propose_report: true
453
+ symptom: 'A unit test that uses `os.tmpdir()` fails on macOS with an assertion showing `/private/var/folders/...` on one side and `/var/folders/...` on the other. Same test passes on Linux.'
454
+ root_cause: 'macOS''s `/var/folders` is a symlink to `/private/var/folders`. `os.tmpdir()` returns the unresolved form, but some Node APIs (especially after a `process.chdir` into the tmpdir) return the resolved form. String equality fails on the prefix.'
455
+ fix: '1) In test setup, wrap the tmpdir path: `const TMP = fs.realpathSync(os.tmpdir())`. 2) Use `TMP` consistently throughout the test. 3) For any assertion that receives a path from a Node API after `chdir`, also pass it through `fs.realpathSync`. 4) Re-run the test. propose_report:true because this is recurring developer-experience friction that warrants a tracked tooling issue.'
456
+ related_phases: [12, 14.6]
457
+ first_observed_cycle: 'cycle-2026-05'
458
+ ```
459
+
460
+ ### KFM-020 — Windows CRLF vs LF byte-comparison mismatch
461
+
462
+ Tests that byte-compare file contents fail on Windows because
463
+ `git show HEAD:<file>` returns LF line endings while
464
+ `fs.readFileSync` after checkout returns CRLF (when `core.autocrlf` is
465
+ `true`). Normalisation or `.gitattributes` resolves it.
466
+
467
+ ```yaml
468
+ id: KFM-020
469
+ pattern: '(core\.autocrlf|CRLF.*(mismatch|conversion)|AssertionError.*(CRLF|line endings|eol)|git.*autocrlf.*true)'
470
+ diagnosis: 'Windows checkout converted LF to CRLF; byte-comparison between `git show HEAD:` (LF) and the working tree (CRLF) fails.'
471
+ remedy: 'Either set .gitattributes `* text=auto eol=lf` for the affected files, or normalize both sides with `.replace(/\\r\\n/g,"\\n")` before comparison.'
472
+ severity: medium
473
+ propose_report: true
474
+ symptom: 'A test that byte-compares file contents passes on Linux/macOS but fails on Windows with an `AssertionError` showing `\\r\\n` on one side and `\\n` on the other.'
475
+ root_cause: 'Windows git has `core.autocrlf=true` by default; on checkout, LF in the repo is converted to CRLF in the working tree. `git show HEAD:<file>` returns the LF-form, but `fs.readFileSync` after a normal checkout returns CRLF. Byte equality fails.'
476
+ fix: '1) Decide whether the file should be EOL-normalised: if yes, add `<glob> text=auto eol=lf` to `.gitattributes`, run `git add --renormalize .`, commit. 2) Or normalise both sides at compare time: `expected.replace(/\\r\\n/g,"\\n") === actual.replace(/\\r\\n/g,"\\n")`. 3) For test-fixture binary-ish files, also consider `<glob> -text` to disable EOL handling entirely. 4) Re-run the Windows test. propose_report:true because Windows-specific test failures are easy to miss in Linux-only CI.'
477
+ related_phases: [12, 14.6, 24]
478
+ first_observed_cycle: 'cycle-2026-05'
479
+ ```
480
+
481
+ ### KFM-021 — Skill name contains colon (agentskills.io slug regex)
482
+
483
+ Skills named with a colon (e.g. `get-design-done:foo`) fail the
484
+ agentskills.io spec validator. The spec reserves colons for namespace
485
+ delimiters at the registry level; on-disk skill folders must use
486
+ hyphens.
487
+
488
+ ```yaml
489
+ id: KFM-021
490
+ pattern: '(agentskills\.io.*(slug|name).*invalid|skill name.*contains colon|SKILL.*invalid.*slug)'
491
+ diagnosis: 'Skill folder name contains a colon, which the agentskills.io spec reserves for registry-level namespacing; on-disk skill slugs must be hyphen-separated.'
492
+ remedy: 'Rename the skill folder to use a hyphen (e.g. `get-design-done:foo` -> `get-design-done-foo`) and update any cross-references.'
493
+ severity: medium
494
+ propose_report: false
495
+ symptom: 'A skill-validation step (or the publishing flow) rejects a skill folder with an error like `skill name "get-design-done:foo" is not a valid agentskills.io slug` or `slug must match /^[a-z0-9][a-z0-9-]*$/`.'
496
+ root_cause: 'The agentskills.io spec slug regex `/^[a-z0-9][a-z0-9-]*$/` excludes colons; colons are reserved at the registry level for namespacing. On-disk skill folders must use hyphens only.'
497
+ fix: '1) Rename the folder: `git mv skills/get-design-done:foo skills/get-design-done-foo`. 2) Update any cross-references in `plugin.json`, `README.md`, and other skills'' frontmatter `requires:` arrays. 3) Search-and-replace the old slug with the new one across `reference/` and `commands/`. 4) Re-run the validation.'
498
+ related_phases: [28.5, 28.6]
499
+ first_observed_cycle: 'cycle-2026-05'
500
+ ```
501
+
502
+ ### KFM-022 — Dependabot alert on transitive optional peer not in resolved tree
503
+
504
+ Dependabot opens an alert for a vulnerable transitive dependency that is
505
+ declared as an optional or peer dep upstream and is NOT actually
506
+ installed (`npm ls <dep>` shows nothing). The alert is dismissible with
507
+ "not vulnerable — not in resolved tree".
508
+
509
+ ```yaml
510
+ id: KFM-022
511
+ pattern: '(Dependabot.*alert.*(transitive|optional|peer)|npm ls.*empty.*Dependabot)'
512
+ diagnosis: 'Dependabot opened an alert on a transitive optional/peer dependency that is not actually present in the resolved npm tree; the alert is real for some users but not for this install.'
513
+ remedy: 'Dismiss alert with reason "not vulnerable — transitive optional peer, not in resolved tree"; verify with `npm ls <dep>` (empty output confirms).'
514
+ severity: low
515
+ propose_report: false
516
+ symptom: 'A new Dependabot alert names a deeply-nested dependency the project does not directly use. Running `npm ls <dep>` from the project root returns empty output (no path to the package).'
517
+ root_cause: 'Dependabot reads `package-lock.json` and flags any package whose version matches a known CVE. Optional/peer transitive deps that npm did NOT install (because the host platform or peer mismatch ruled them out) still appear in the lockfile metadata, so Dependabot flags them even though they''re absent from the resolved tree.'
518
+ fix: '1) Verify absence: `npm ls <dep>` from the project root — empty output confirms the package is not installed. 2) Open the alert in GitHub Security tab → Dismiss → reason "Not vulnerable" with note "transitive optional peer; npm ls returns empty; not in resolved tree". 3) Optionally pin or update the parent dependency in a follow-up to remove the lockfile reference entirely. 4) Re-run any scan that depends on Dependabot state.'
519
+ related_phases: [25, 27.5]
520
+ first_observed_cycle: 'cycle-2026-05'
185
521
  ```
@@ -13,7 +13,7 @@ Phase 10.1 (OPT-06) locked the initial per-agent tier assignment. Phase 11's `de
13
13
  Pick `haiku` when the agent is:
14
14
  - Applying a fixed scoring rubric (`design-verifier` runs five deterministic passes with numeric category scores).
15
15
  - Producing a boolean + rationale answer (`design-plan-checker`, `design-context-checker`, `design-integration-checker` all return "gaps found" + structured list).
16
- - Performing a read-only state sync (`gdd-graphify-sync` mirrors graph state — no reasoning density beyond schema matching).
16
+ - Performing a read-only state sync (`gdd-graph-refresh` mirrors graph state — no reasoning density beyond schema matching).
17
17
  - Running at high frequency where cost compounds (checkers run on every `/gdd:verify` pass — cost multiplies with iterations).
18
18
 
19
19
  Haiku's ~20x price advantage over Opus per 1M tokens (see `reference/model-prices.md`) makes it correct for deterministic-rubric work where the marginal quality gain of larger models is negligible.
@@ -49,7 +49,7 @@ Opus cost (~5x Sonnet, ~25x Haiku) is justified only when a single wrong decisio
49
49
  | design-plan-checker | Checker | haiku | Checks the plan against a fixed schema — boolean + gap-list output. |
50
50
  | design-context-checker | Checker | haiku | Checks context completeness against a schema — boolean + gap-list output. |
51
51
  | design-integration-checker | Checker | haiku | Checks cross-artifact references — deterministic link-integrity work. |
52
- | gdd-graphify-sync | Sync agent | haiku | Mirrors graph state to the graph store — no reasoning density required. |
52
+ | gdd-graph-refresh | Refresh agent | haiku | Rebuilds graph at .design/graph/graph.json from intel slices — no reasoning density required. |
53
53
  | a11y-mapper | Mapper | sonnet | Open-ended a11y pattern recognition across many files; Sonnet's breadth matters. |
54
54
  | component-taxonomy-mapper | Mapper | sonnet | Classifies components by role — requires nuance Haiku lacks, not enough to warrant Opus. |
55
55
  | design-auditor | Worker | sonnet | Emits structured findings from code inspection; Sonnet balances depth with cost. |
@@ -131,6 +131,56 @@
131
131
  }
132
132
  },
133
133
  "description": "Phase 29 D-02 capability_gap payload — exactly 7 fields, additionalProperties: false. Validated when the envelope's type === 'capability_gap' via the allOf[0] conditional."
134
+ },
135
+ "KfmCandidatePayload": {
136
+ "type": "object",
137
+ "additionalProperties": false,
138
+ "required": [
139
+ "event_id",
140
+ "source",
141
+ "article_url",
142
+ "article_title",
143
+ "suggested_symptom",
144
+ "suggested_pattern_hint",
145
+ "raw_excerpt"
146
+ ],
147
+ "properties": {
148
+ "event_id": {
149
+ "type": "string",
150
+ "minLength": 1,
151
+ "description": "Stable identifier for this kfm-candidate event. Used by Plan 30.5-03 reflector to de-dupe re-emissions of the same authority-watcher article hit."
152
+ },
153
+ "source": {
154
+ "type": "string",
155
+ "const": "authority_watcher",
156
+ "description": "Phase 30.5-03 D-06 — the kfm-candidate event class is emitted EXCLUSIVELY by the authority-watcher pipeline. No other producer is authorised."
157
+ },
158
+ "article_url": {
159
+ "type": "string",
160
+ "format": "uri",
161
+ "description": "Permalink to the source article. Logged into the incubator draft body so the user can audit provenance."
162
+ },
163
+ "article_title": {
164
+ "type": "string",
165
+ "minLength": 1,
166
+ "description": "Article title verbatim — feeds the symptom-derivation heuristic + the draft origin header."
167
+ },
168
+ "suggested_symptom": {
169
+ "type": "string",
170
+ "minLength": 1,
171
+ "description": "Reflector-consumed symptom string. Derived from the article H1/H2 + title; capped to a single line."
172
+ },
173
+ "suggested_pattern_hint": {
174
+ "type": "string",
175
+ "description": "Best-effort regex fragment proposed by the watcher heuristic. Empty string when the watcher cannot infer a pattern; the user fills `pattern` via the apply-reflections edit action."
176
+ },
177
+ "raw_excerpt": {
178
+ "type": "string",
179
+ "maxLength": 500,
180
+ "description": "Up-to-500-char excerpt of the article body. Truncated with an ellipsis if longer. Hard cap is enforced by the schema; the watcher truncates before emission."
181
+ }
182
+ },
183
+ "description": "Phase 30.5-03 D-06 kfm-candidate payload — 7 fields, additionalProperties: false. Validated when the envelope's type === 'kfm-candidate' via the allOf[1] conditional."
134
184
  }
135
185
  },
136
186
  "allOf": [
@@ -144,6 +194,17 @@
144
194
  "payload": { "$ref": "#/definitions/CapabilityGapPayload" }
145
195
  }
146
196
  }
197
+ },
198
+ {
199
+ "if": {
200
+ "properties": { "type": { "const": "kfm-candidate" } },
201
+ "required": ["type"]
202
+ },
203
+ "then": {
204
+ "properties": {
205
+ "payload": { "$ref": "#/definitions/KfmCandidatePayload" }
206
+ }
207
+ }
147
208
  }
148
209
  ]
149
210
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Purpose:** collect the minimum signal needed to steer the findings engine without slowing first-run completion past 30 seconds of interview wall-clock. Autodetectable dimensions collapse to a one-key confirmation; genuinely non-derivable dimensions are asked explicitly.
4
4
 
5
- **Hard constraint:** v1.14.7 ships this fixed question set. Do not branch, re-order, or insert new questions without an explicit `/gsd-discuss-phase` override captured in a future DISCUSSION.md.
5
+ **Hard constraint:** v1.14.7 ships this fixed question set. Do not branch, re-order, or insert new questions without an explicit `/gdd:discuss` override captured in a future DISCUSSION.md.
6
6
 
7
7
  ---
8
8
 
@@ -19,6 +19,12 @@ const DEPRECATIONS_PATH = path.join(REPO_ROOT, 'reference/DEPRECATIONS.md');
19
19
  // would over-fire. Cover those cases via targeted review rather than grep.
20
20
  const PATTERNS = [
21
21
  { name: '/design: namespace (replaced by /gdd:)', regex: /\/design:[a-z-]+/g },
22
+ // Phase 30.6: runtime dispatch to upstream gsd-tools.cjs is now disallowed.
23
+ // Native CLI lives at bin/gdd-graph (graph ops) and is invoked as `node bin/gdd-graph <sub>`.
24
+ // The pattern is precise — only the explicit bash dispatch is flagged. Prose mentions
25
+ // of "gsd-tools" or "get-shit-done" in attribution contexts (NOTICE, CHANGELOG, README)
26
+ // are NOT matched because they don't include the leading `node "$HOME/.claude/...` token.
27
+ { name: 'gsd-tools.cjs runtime dispatch (use bin/gdd-graph instead)', regex: /node\s+["']?\$HOME\/\.claude\/get-shit-done\/bin\/gsd-tools\.cjs/g },
22
28
  ];
23
29
 
24
30
  const EXCLUDE_DIRS = new Set([
@@ -57,9 +57,16 @@ function warn(msg) {
57
57
  }
58
58
 
59
59
  function quoteArg(s) {
60
- // Cross-platform quote: wrap in double quotes and escape embedded ones.
61
- // Sufficient for tmpdir paths (no real shell metachars expected).
62
- return `"${String(s).replace(/"/g, '\\"')}"`;
60
+ // Cross-platform quote: wrap in double quotes and escape embedded
61
+ // backslashes FIRST (so the escapes themselves don't become escaped
62
+ // separators), then escape embedded double quotes. Closes Code
63
+ // Scanning #23 (js/incomplete-sanitization). Order matters: if we
64
+ // escape quotes before backslashes, the inserted `\"` would then have
65
+ // its `\` doubled by the backslash pass, producing `\\\"` instead of
66
+ // the intended `\"`.
67
+ // Sufficient for tmpdir paths (no real shell metachars expected),
68
+ // but now safe for paths containing backslash-quote sequences too.
69
+ return `"${String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
63
70
  }
64
71
 
65
72
  // --- discoverIncubatorDrafts ---