@hegemonart/get-design-done 1.57.1 → 1.57.3

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 (237) hide show
  1. package/.claude-plugin/marketplace.json +26 -41
  2. package/.claude-plugin/plugin.json +23 -48
  3. package/CHANGELOG.md +139 -0
  4. package/README.md +166 -511
  5. package/SKILL.md +4 -6
  6. package/agents/README.md +33 -36
  7. package/agents/a11y-mapper.md +3 -3
  8. package/agents/component-benchmark-harvester.md +6 -6
  9. package/agents/component-benchmark-synthesizer.md +3 -3
  10. package/agents/compose-executor.md +3 -3
  11. package/agents/cost-forecaster.md +2 -2
  12. package/agents/design-auditor.md +7 -7
  13. package/agents/design-authority-watcher.md +15 -15
  14. package/agents/design-context-builder.md +4 -4
  15. package/agents/design-context-checker-gate.md +1 -1
  16. package/agents/design-discussant.md +2 -2
  17. package/agents/design-doc-writer.md +1 -1
  18. package/agents/design-executor.md +2 -2
  19. package/agents/design-figma-writer.md +2 -2
  20. package/agents/design-fixer.md +7 -7
  21. package/agents/design-integration-checker-gate.md +1 -1
  22. package/agents/design-integration-checker.md +1 -1
  23. package/agents/design-paper-writer.md +3 -3
  24. package/agents/design-pencil-writer.md +1 -1
  25. package/agents/design-planner.md +21 -0
  26. package/agents/design-reflector.md +39 -39
  27. package/agents/design-research-synthesizer.md +1 -0
  28. package/agents/design-start-writer.md +1 -1
  29. package/agents/design-update-checker.md +5 -5
  30. package/agents/design-verifier-gate.md +1 -1
  31. package/agents/design-verifier.md +52 -48
  32. package/agents/ds-generator.md +2 -2
  33. package/agents/ds-migration-planner.md +4 -4
  34. package/agents/email-executor.md +9 -9
  35. package/agents/experiment-result-ingester.md +3 -3
  36. package/agents/flutter-executor.md +5 -5
  37. package/agents/gdd-graph-refresh.md +3 -3
  38. package/agents/gdd-intel-updater.md +2 -2
  39. package/agents/motion-mapper.md +2 -2
  40. package/agents/motion-verifier.md +4 -4
  41. package/agents/pdf-executor.md +8 -8
  42. package/agents/perf-analyzer.md +17 -17
  43. package/agents/pr-commenter.md +9 -9
  44. package/agents/prototype-gate.md +2 -2
  45. package/agents/quality-gate-runner.md +1 -1
  46. package/agents/rollout-coordinator.md +3 -3
  47. package/agents/swift-executor.md +4 -4
  48. package/agents/ticket-sync-agent.md +6 -6
  49. package/agents/user-research-synthesizer.md +2 -2
  50. package/connections/connections.md +44 -45
  51. package/connections/cursor.md +72 -0
  52. package/connections/preview.md +3 -3
  53. package/hooks/first-run-nudge.cjs +171 -0
  54. package/hooks/gdd-intel-trigger.js +243 -0
  55. package/hooks/gdd-mcp-circuit-breaker.js +62 -7
  56. package/hooks/gdd-precompact-snapshot.js +50 -29
  57. package/hooks/gdd-protected-paths.js +150 -18
  58. package/hooks/gdd-risk-gate.js +93 -1
  59. package/hooks/gdd-sessionstart-recap.js +59 -24
  60. package/hooks/hooks.json +13 -4
  61. package/hooks/inject-using-gdd.cjs +188 -0
  62. package/hooks/update-check.cjs +511 -0
  63. package/package.json +9 -3
  64. package/reference/STATE-TEMPLATE.md +10 -13
  65. package/reference/audit-scoring.md +1 -1
  66. package/reference/cache-tier-doctrine.md +46 -0
  67. package/reference/config-schema.md +9 -9
  68. package/reference/i18n.md +1 -1
  69. package/reference/intel-schema.md +37 -2
  70. package/reference/meta-rules.md +4 -4
  71. package/reference/model-tiers.md +2 -2
  72. package/reference/registry.json +101 -94
  73. package/reference/runtime-models.md +11 -1
  74. package/reference/shared-preamble.md +13 -14
  75. package/reference/skill-graph.md +22 -3
  76. package/scripts/bootstrap.cjs +373 -0
  77. package/scripts/injection-patterns.cjs +58 -0
  78. package/scripts/lib/apply-reflections/incubator-proposals.cjs +57 -26
  79. package/scripts/lib/install/converters/codex-plugin.cjs +5 -2
  80. package/scripts/lib/install/converters/cursor.cjs +20 -0
  81. package/scripts/lib/issue-reporter/report-flow.cjs +1 -1
  82. package/scripts/lib/manifest/skills.json +75 -28
  83. package/scripts/lib/state/query-surface.cjs +67 -9
  84. package/scripts/lib/state/state-store.cjs +68 -26
  85. package/scripts/lib/worktree-resolve.cjs +4 -16
  86. package/sdk/cli/commands/stage.ts +17 -0
  87. package/sdk/cli/index.js +14 -0
  88. package/skills/README.md +46 -0
  89. package/skills/bootstrap-ds/SKILL.md +1 -1
  90. package/skills/cache-manager/SKILL.md +3 -3
  91. package/skills/cache-manager/cache-policy.md +1 -1
  92. package/skills/compare/SKILL.md +1 -1
  93. package/skills/design/SKILL.md +19 -0
  94. package/skills/explore/SKILL.md +11 -0
  95. package/skills/figma-write/SKILL.md +13 -2
  96. package/skills/new-cycle/SKILL.md +1 -1
  97. package/skills/paper-write/SKILL.md +54 -0
  98. package/skills/peer-cli-customize/SKILL.md +0 -1
  99. package/skills/peers/SKILL.md +1 -1
  100. package/skills/pencil-write/SKILL.md +54 -0
  101. package/skills/reflect/procedures/capability-gap-scan.md +0 -1
  102. package/skills/report-issue/SKILL.md +2 -2
  103. package/skills/report-issue/report-issue-procedure.md +0 -1
  104. package/skills/router/SKILL.md +2 -2
  105. package/skills/synthesize/SKILL.md +1 -1
  106. package/skills/turn-closeout/SKILL.md +1 -1
  107. package/skills/verify/verify-procedure.md +10 -11
  108. package/skills/warm-cache/SKILL.md +1 -1
  109. package/dist/claude-code/.claude/skills/add-backlog/SKILL.md +0 -48
  110. package/dist/claude-code/.claude/skills/analyze-dependencies/SKILL.md +0 -95
  111. package/dist/claude-code/.claude/skills/apply-reflections/SKILL.md +0 -109
  112. package/dist/claude-code/.claude/skills/apply-reflections/apply-reflections-procedure.md +0 -170
  113. package/dist/claude-code/.claude/skills/audit/SKILL.md +0 -79
  114. package/dist/claude-code/.claude/skills/bandit-status/SKILL.md +0 -94
  115. package/dist/claude-code/.claude/skills/benchmark/SKILL.md +0 -65
  116. package/dist/claude-code/.claude/skills/bootstrap-ds/SKILL.md +0 -43
  117. package/dist/claude-code/.claude/skills/brief/SKILL.md +0 -145
  118. package/dist/claude-code/.claude/skills/budget/SKILL.md +0 -45
  119. package/dist/claude-code/.claude/skills/cache-manager/SKILL.md +0 -66
  120. package/dist/claude-code/.claude/skills/cache-manager/cache-policy.md +0 -126
  121. package/dist/claude-code/.claude/skills/check-update/SKILL.md +0 -98
  122. package/dist/claude-code/.claude/skills/compare/SKILL.md +0 -82
  123. package/dist/claude-code/.claude/skills/compare/compare-rubric.md +0 -171
  124. package/dist/claude-code/.claude/skills/complete-cycle/SKILL.md +0 -81
  125. package/dist/claude-code/.claude/skills/connections/SKILL.md +0 -71
  126. package/dist/claude-code/.claude/skills/connections/connections-onboarding.md +0 -608
  127. package/dist/claude-code/.claude/skills/context/SKILL.md +0 -137
  128. package/dist/claude-code/.claude/skills/continue/SKILL.md +0 -24
  129. package/dist/claude-code/.claude/skills/darkmode/SKILL.md +0 -76
  130. package/dist/claude-code/.claude/skills/darkmode/darkmode-audit-procedure.md +0 -258
  131. package/dist/claude-code/.claude/skills/debug/SKILL.md +0 -41
  132. package/dist/claude-code/.claude/skills/debug/debug-feedback-loops.md +0 -119
  133. package/dist/claude-code/.claude/skills/design/SKILL.md +0 -99
  134. package/dist/claude-code/.claude/skills/design/design-procedure.md +0 -304
  135. package/dist/claude-code/.claude/skills/discover/SKILL.md +0 -78
  136. package/dist/claude-code/.claude/skills/discover/discover-procedure.md +0 -222
  137. package/dist/claude-code/.claude/skills/discuss/SKILL.md +0 -96
  138. package/dist/claude-code/.claude/skills/do/SKILL.md +0 -45
  139. package/dist/claude-code/.claude/skills/explore/SKILL.md +0 -107
  140. package/dist/claude-code/.claude/skills/explore/explore-procedure.md +0 -267
  141. package/dist/claude-code/.claude/skills/export/SKILL.md +0 -30
  142. package/dist/claude-code/.claude/skills/extract-learnings/SKILL.md +0 -114
  143. package/dist/claude-code/.claude/skills/fast/SKILL.md +0 -91
  144. package/dist/claude-code/.claude/skills/figma-extract/SKILL.md +0 -64
  145. package/dist/claude-code/.claude/skills/figma-write/SKILL.md +0 -39
  146. package/dist/claude-code/.claude/skills/graphify/SKILL.md +0 -49
  147. package/dist/claude-code/.claude/skills/health/SKILL.md +0 -99
  148. package/dist/claude-code/.claude/skills/health/health-mcp-detection.md +0 -44
  149. package/dist/claude-code/.claude/skills/health/health-skill-length-report.md +0 -69
  150. package/dist/claude-code/.claude/skills/help/SKILL.md +0 -87
  151. package/dist/claude-code/.claude/skills/instinct/SKILL.md +0 -111
  152. package/dist/claude-code/.claude/skills/list-assumptions/SKILL.md +0 -61
  153. package/dist/claude-code/.claude/skills/list-pins/SKILL.md +0 -27
  154. package/dist/claude-code/.claude/skills/live/SKILL.md +0 -98
  155. package/dist/claude-code/.claude/skills/locale/SKILL.md +0 -51
  156. package/dist/claude-code/.claude/skills/map/SKILL.md +0 -89
  157. package/dist/claude-code/.claude/skills/migrate/SKILL.md +0 -70
  158. package/dist/claude-code/.claude/skills/migrate-context/SKILL.md +0 -123
  159. package/dist/claude-code/.claude/skills/new-addendum/SKILL.md +0 -81
  160. package/dist/claude-code/.claude/skills/new-cycle/SKILL.md +0 -37
  161. package/dist/claude-code/.claude/skills/new-cycle/milestone-completeness-rubric.md +0 -87
  162. package/dist/claude-code/.claude/skills/new-project/SKILL.md +0 -53
  163. package/dist/claude-code/.claude/skills/new-skill/SKILL.md +0 -90
  164. package/dist/claude-code/.claude/skills/next/SKILL.md +0 -68
  165. package/dist/claude-code/.claude/skills/note/SKILL.md +0 -48
  166. package/dist/claude-code/.claude/skills/openrouter-status/SKILL.md +0 -86
  167. package/dist/claude-code/.claude/skills/optimize/SKILL.md +0 -97
  168. package/dist/claude-code/.claude/skills/override/SKILL.md +0 -86
  169. package/dist/claude-code/.claude/skills/pause/SKILL.md +0 -77
  170. package/dist/claude-code/.claude/skills/peer-cli-add/SKILL.md +0 -88
  171. package/dist/claude-code/.claude/skills/peer-cli-add/peer-cli-protocol.md +0 -161
  172. package/dist/claude-code/.claude/skills/peer-cli-customize/SKILL.md +0 -90
  173. package/dist/claude-code/.claude/skills/peers/SKILL.md +0 -96
  174. package/dist/claude-code/.claude/skills/pin/SKILL.md +0 -37
  175. package/dist/claude-code/.claude/skills/plan/SKILL.md +0 -105
  176. package/dist/claude-code/.claude/skills/plan/plan-procedure.md +0 -278
  177. package/dist/claude-code/.claude/skills/plant-seed/SKILL.md +0 -48
  178. package/dist/claude-code/.claude/skills/pr-branch/SKILL.md +0 -32
  179. package/dist/claude-code/.claude/skills/progress/SKILL.md +0 -107
  180. package/dist/claude-code/.claude/skills/quality-gate/SKILL.md +0 -90
  181. package/dist/claude-code/.claude/skills/quality-gate/threat-modeling.md +0 -101
  182. package/dist/claude-code/.claude/skills/quick/SKILL.md +0 -44
  183. package/dist/claude-code/.claude/skills/reapply-patches/SKILL.md +0 -32
  184. package/dist/claude-code/.claude/skills/recall/SKILL.md +0 -75
  185. package/dist/claude-code/.claude/skills/reflect/SKILL.md +0 -85
  186. package/dist/claude-code/.claude/skills/reflect/procedures/capability-gap-scan.md +0 -120
  187. package/dist/claude-code/.claude/skills/report-issue/SKILL.md +0 -53
  188. package/dist/claude-code/.claude/skills/report-issue/report-issue-procedure.md +0 -120
  189. package/dist/claude-code/.claude/skills/resume/SKILL.md +0 -93
  190. package/dist/claude-code/.claude/skills/review-backlog/SKILL.md +0 -46
  191. package/dist/claude-code/.claude/skills/review-decisions/SKILL.md +0 -42
  192. package/dist/claude-code/.claude/skills/roi/SKILL.md +0 -54
  193. package/dist/claude-code/.claude/skills/rollout-status/SKILL.md +0 -35
  194. package/dist/claude-code/.claude/skills/router/SKILL.md +0 -89
  195. package/dist/claude-code/.claude/skills/router/capability-gap-emitter.md +0 -65
  196. package/dist/claude-code/.claude/skills/router/router-pick-emitter.md +0 -78
  197. package/dist/claude-code/.claude/skills/router/router-rules.md +0 -84
  198. package/dist/claude-code/.claude/skills/scan/SKILL.md +0 -92
  199. package/dist/claude-code/.claude/skills/scan/scan-procedure.md +0 -732
  200. package/dist/claude-code/.claude/skills/settings/SKILL.md +0 -87
  201. package/dist/claude-code/.claude/skills/ship/SKILL.md +0 -48
  202. package/dist/claude-code/.claude/skills/sketch/SKILL.md +0 -78
  203. package/dist/claude-code/.claude/skills/sketch-wrap-up/SKILL.md +0 -92
  204. package/dist/claude-code/.claude/skills/skill-manifest/SKILL.md +0 -79
  205. package/dist/claude-code/.claude/skills/spike/SKILL.md +0 -67
  206. package/dist/claude-code/.claude/skills/spike-wrap-up/SKILL.md +0 -86
  207. package/dist/claude-code/.claude/skills/start/SKILL.md +0 -67
  208. package/dist/claude-code/.claude/skills/start/start-procedure.md +0 -115
  209. package/dist/claude-code/.claude/skills/state/SKILL.md +0 -106
  210. package/dist/claude-code/.claude/skills/stats/SKILL.md +0 -51
  211. package/dist/claude-code/.claude/skills/style/SKILL.md +0 -71
  212. package/dist/claude-code/.claude/skills/style/style-doc-procedure.md +0 -150
  213. package/dist/claude-code/.claude/skills/synthesize/SKILL.md +0 -94
  214. package/dist/claude-code/.claude/skills/timeline/SKILL.md +0 -66
  215. package/dist/claude-code/.claude/skills/todo/SKILL.md +0 -64
  216. package/dist/claude-code/.claude/skills/turn-closeout/SKILL.md +0 -95
  217. package/dist/claude-code/.claude/skills/undo/SKILL.md +0 -31
  218. package/dist/claude-code/.claude/skills/unlock-decision/SKILL.md +0 -54
  219. package/dist/claude-code/.claude/skills/unpin/SKILL.md +0 -31
  220. package/dist/claude-code/.claude/skills/update/SKILL.md +0 -56
  221. package/dist/claude-code/.claude/skills/using-gdd/SKILL.md +0 -78
  222. package/dist/claude-code/.claude/skills/verify/SKILL.md +0 -113
  223. package/dist/claude-code/.claude/skills/verify/verify-procedure.md +0 -512
  224. package/dist/claude-code/.claude/skills/warm-cache/SKILL.md +0 -81
  225. package/dist/claude-code/.claude/skills/watch-authorities/SKILL.md +0 -82
  226. package/dist/claude-code/.claude/skills/zoom-out/SKILL.md +0 -26
  227. package/hooks/first-run-nudge.sh +0 -82
  228. package/hooks/inject-using-gdd.sh +0 -72
  229. package/hooks/run-hook.cmd +0 -35
  230. package/hooks/update-check.sh +0 -251
  231. package/scripts/lib/audit-aggregator/index.cjs +0 -219
  232. package/scripts/lib/hedge-ensemble.cjs +0 -217
  233. package/skills/discover/SKILL.md +0 -78
  234. package/skills/discover/discover-procedure.md +0 -222
  235. package/skills/new-cycle/milestone-completeness-rubric.md +0 -87
  236. package/skills/scan/SKILL.md +0 -92
  237. package/skills/scan/scan-procedure.md +0 -732
@@ -0,0 +1,72 @@
1
+ # Cursor — Runtime Install Notes
2
+
3
+ This file documents the get-design-done install path on Cursor and one known limitation.
4
+
5
+ Cursor is one of the 14 first-class runtimes for `gdd install` (see `reference/runtimes.md` for the full set). It uses the `multi-artifact` install kind: skills land as `~/.cursor/skills/<gdd-name>/SKILL.md` after passing through `scripts/lib/install/converters/cursor.cjs`.
6
+
7
+ ## Install
8
+
9
+ ```
10
+ gdd install --runtime cursor # global → ~/.cursor/skills/
11
+ gdd install --runtime cursor --local # repo-local → .cursor/skills/
12
+ ```
13
+
14
+ Each installed skill is name-prefixed with `gdd-` (e.g. `gdd-explore`, `gdd-discover`) so it cannot collide with user-authored Cursor skills.
15
+
16
+ ## Converter behavior
17
+
18
+ `converters/cursor.cjs#convert` is a pure string→string transform that:
19
+
20
+ - Normalizes the frontmatter `name:` field to `gdd-<skill>` (no double-prefix).
21
+ - Rewrites mixed-shape slash references (`gdd-x`, `/gdd:x`) to a consistent `/gdd-<name>` shape.
22
+ - Passes the Claude tool vocabulary through unchanged (Read / Write / Bash / Edit / Grep / Glob — Cursor accepts these).
23
+ - Injects a 1-line HTML adapter header recording auto-generation.
24
+
25
+ The output is byte-stable per source — re-running `gdd install --runtime cursor` is a no-op when nothing has changed.
26
+
27
+ ## Known limitation — sibling .md files are NOT installed
28
+
29
+ Some skills ship procedure detail in sibling `.md` files next to `SKILL.md`. For example:
30
+
31
+ ```
32
+ skills/discover/
33
+ ├── SKILL.md ← installed
34
+ └── discover-procedure.md ← NOT installed (sibling)
35
+ ```
36
+
37
+ `SKILL.md` references the sibling via a relative link (e.g. `./discover-procedure.md`). On Cursor, that link resolves to nothing.
38
+
39
+ This is a systemic limitation of the current `multi-artifact` install pipeline (`scripts/lib/install/runtime-artifact-layout.cjs#skillsKind`), not a Cursor-specific bug. It affects every runtime that uses `skillsKind`: claude global, cursor, codex, copilot, antigravity, windsurf, augment, trae, qwen, codebuddy. Skills with sibling files currently affected:
40
+
41
+ - `apply-reflections/apply-reflections-procedure.md`
42
+ - `cache-manager/cache-policy.md`
43
+ - `compare/compare-rubric.md`
44
+ - `connections/connections-onboarding.md`
45
+ - `darkmode/darkmode-audit-procedure.md`
46
+ - `debug/debug-feedback-loops.md`
47
+ - `design/design-procedure.md`
48
+ - `discover/discover-procedure.md`
49
+ - `explore/explore-procedure.md`
50
+ - `health/health-mcp-detection.md`
51
+ - `health/health-skill-length-report.md`
52
+ - `peer-cli-add/peer-cli-protocol.md`
53
+ - `plan/plan-procedure.md`
54
+ - `quality-gate/threat-modeling.md`
55
+ - `report-issue/report-issue-procedure.md`
56
+ - `router/capability-gap-emitter.md`
57
+ - `router/router-pick-emitter.md`
58
+ - `router/router-rules.md`
59
+ - `scan/scan-procedure.md`
60
+ - `start/start-procedure.md`
61
+ - `style/style-doc-procedure.md`
62
+ - `verify/verify-procedure.md`
63
+
64
+ ### Workarounds until a fix lands
65
+
66
+ 1. **Use the cursor-marketplace distribution channel** — `scripts/lib/install/converters/cursor-marketplace.cjs` copies the entire `skills/` tree byte-for-byte (siblings included). Install through the marketplace plugin rather than per-skill if you need the full procedure detail.
67
+ 2. **Clone the repo locally** — Cursor can read sibling files directly from the source checkout if you point it at the repo tree.
68
+ 3. **Inline-grep the source** — for one-off questions, read the source `SKILL.md` directly; siblings are short and self-contained.
69
+
70
+ ### Fix scope (tracked as follow-up beyond batch H6)
71
+
72
+ Properly enumerating siblings requires extending the StagedArtifact contract to emit multiple files per skill (one SKILL.md + N siblings), updating `computeDestPath` for non-SKILL.md paths inside a skill subdir, the foreign-file detection in `installer.cjs#detectMultiArtifactInstalled`, and the uninstall enumeration in `uninstallMultiArtifact`. See the `KNOWN LIMITATION` block at the top of `scripts/lib/install/converters/cursor.cjs` for the technical detail.
@@ -107,10 +107,10 @@ When preview is `not_configured` or `unavailable`, stages degrade gracefully —
107
107
 
108
108
  **verify stage (`skills/verify/SKILL.md` + `agents/design-verifier.md`):**
109
109
 
110
- - Skip Phase 4B screenshot evidence loop entirely
110
+ - Skip Stage 4B screenshot evidence loop entirely
111
111
  - Keep existing `? VISUAL` static analysis path for H-02, H-06, H-07 heuristics
112
- - Mark all Phase 4B checks: `[SKIPPED — browser not available]`
113
- - Design-verifier continues to Phase 5 gap analysis with partial scores
112
+ - Mark all Stage 4B checks: `[SKIPPED — browser not available]`
113
+ - Design-verifier continues to Stage 5 gap analysis with partial scores
114
114
 
115
115
  **compare stage (`skills/compare/SKILL.md`):**
116
116
 
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * get-design-done — first-run nudge (Phase 14.7)
6
+ *
7
+ * Port of hooks/first-run-nudge.sh to pure Node CommonJS (Phase 28.x).
8
+ * SessionStart hook. Silent-on-failure by policy: exits 0 on every error path.
9
+ * Prints exactly one restrained line pointing at /gdd:start when all gates
10
+ * pass, and nothing otherwise.
11
+ *
12
+ * Non-obvious behavior preserved:
13
+ * - Logger is silent unless GDD_NUDGE_DEBUG=1 (matches bash `${VAR:-0}`).
14
+ * - HOME falls back to USERPROFILE (Windows). Mirrors bash `${HOME:-$USERPROFILE}`.
15
+ * - read_state_stage uses the same regex shape as the bash sed: drops an
16
+ * optional surrounding double-quote and stops at the first whitespace.
17
+ * - has_recent_gdd_command is a placeholder that always returns false (matches
18
+ * the bash `return 1` → `is_active` boolean false).
19
+ * - Sourcing guard: helpers are exported on module.exports; main() only runs
20
+ * when invoked as the entry point (require.main === module).
21
+ * - Always exit 0 — every error path is swallowed.
22
+ * - The locked nudge copy appears exactly once in this file (test asserts
23
+ * the nudge string occurs exactly once in the hook source).
24
+ */
25
+
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+
29
+ const NUDGE_LINE =
30
+ 'Tip: run /gdd:start to let GDD inspect this codebase and suggest one first fix.\n';
31
+
32
+ function log(msg) {
33
+ if (process.env.GDD_NUDGE_DEBUG === '1') {
34
+ process.stderr.write(`[gdd first-run-nudge] ${msg}\n`);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Gate 1 — repo already has GDD state, suppress.
40
+ * @param {string} designDir absolute path to <cwd>/.design
41
+ * @returns {boolean}
42
+ */
43
+ function hasDesignState(designDir) {
44
+ try {
45
+ const config = path.join(designDir, 'config.json');
46
+ const state = path.join(designDir, 'STATE.md');
47
+ return isFile(config) || isFile(state);
48
+ } catch (_e) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Gate 2 — per-install dismissal flag.
55
+ * @param {string} homeDir resolved HOME (or USERPROFILE on Windows)
56
+ * @returns {boolean}
57
+ */
58
+ function isDismissed(homeDir) {
59
+ try {
60
+ if (!homeDir) return false;
61
+ return isFile(path.join(homeDir, '.claude', 'gdd-nudge-dismissed'));
62
+ } catch (_e) {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Reads the first `stage:` line out of STATE.md and strips surrounding quoting
69
+ * the way the bash sed expression does (drops an optional surrounding double
70
+ * quote, captures non-quote/non-whitespace chars, ignores any trailing text).
71
+ *
72
+ * @param {string} stateFilePath absolute path to STATE.md
73
+ * @returns {string} the captured stage value, or '' if unavailable
74
+ */
75
+ function readStateStage(stateFilePath) {
76
+ try {
77
+ if (!isFile(stateFilePath)) return '';
78
+ const text = fs.readFileSync(stateFilePath, 'utf8');
79
+ const lines = text.split(/\r?\n/);
80
+ for (const line of lines) {
81
+ if (/^stage:/.test(line)) {
82
+ const m = line.match(/^stage:[ \t]*"?([^"\s]+)"?.*/);
83
+ if (m && m[1]) return m[1];
84
+ return '';
85
+ }
86
+ }
87
+ return '';
88
+ } catch (_e) {
89
+ return '';
90
+ }
91
+ }
92
+
93
+ const ACTIVE_STAGES = new Set(['plan', 'design', 'verify', 'executing', 'discussing']);
94
+
95
+ /**
96
+ * Gate 3 — STATE.md stage belongs to an active pipeline window.
97
+ * @param {string} stateFilePath absolute path to STATE.md
98
+ * @returns {boolean}
99
+ */
100
+ function isActiveStage(stateFilePath) {
101
+ const s = readStateStage(stateFilePath);
102
+ return ACTIVE_STAGES.has(s);
103
+ }
104
+
105
+ /**
106
+ * Gate 4 — recent session history has a gdd:* command.
107
+ * Placeholder: no portable transcript path exposed to SessionStart hooks today.
108
+ * Mirrors the bash version's `return 1` (false) so we never falsely suppress.
109
+ * @returns {boolean}
110
+ */
111
+ function hasRecentGddCommand() {
112
+ return false;
113
+ }
114
+
115
+ function isFile(p) {
116
+ try {
117
+ const st = fs.statSync(p);
118
+ return st.isFile();
119
+ } catch (_e) {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ function resolveHomeDir() {
125
+ // bash: ${HOME:-$USERPROFILE} — HOME wins if set (even on Windows), else USERPROFILE.
126
+ return process.env.HOME || process.env.USERPROFILE || '';
127
+ }
128
+
129
+ function main() {
130
+ try {
131
+ const cwd = process.cwd();
132
+ const designDir = path.join(cwd, '.design');
133
+ const stateFile = path.join(designDir, 'STATE.md');
134
+ const homeDir = resolveHomeDir();
135
+
136
+ if (hasDesignState(designDir)) {
137
+ log('design state present — suppress');
138
+ process.exit(0);
139
+ }
140
+ if (isDismissed(homeDir)) {
141
+ log('dismissal flag present — suppress');
142
+ process.exit(0);
143
+ }
144
+ if (isActiveStage(stateFile)) {
145
+ log('active stage — suppress');
146
+ process.exit(0);
147
+ }
148
+ if (hasRecentGddCommand()) {
149
+ log('recent gdd:* command detected — suppress');
150
+ process.exit(0);
151
+ }
152
+ // All gates passed — emit the locked one-line nudge.
153
+ process.stdout.write(NUDGE_LINE);
154
+ process.exit(0);
155
+ } catch (_e) {
156
+ // Silent-on-failure: every error path exits 0.
157
+ process.exit(0);
158
+ }
159
+ }
160
+
161
+ module.exports = {
162
+ hasDesignState,
163
+ isDismissed,
164
+ readStateStage,
165
+ isActiveStage,
166
+ hasRecentGddCommand,
167
+ };
168
+
169
+ if (require.main === module) {
170
+ main();
171
+ }
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * hooks/gdd-intel-trigger.js — D5 (PostToolUse on Edit|Write)
5
+ *
6
+ * On every Edit/Write that touches a design-authoritative surface
7
+ * (skills/**, agents/**, reference/**, source/skills/**), spawn a
8
+ * background, detached refresh of the .design/intel/ store so downstream
9
+ * consumers (router, planner, audits) see the latest extracts without the
10
+ * user paying for a full rebuild on the next /gdd run.
11
+ *
12
+ * Contract:
13
+ * 1. Read the PostToolUse payload from stdin. Tolerate snake_case and
14
+ * camelCase field names (tool_name/toolName, tool_input/toolInput,
15
+ * file_path/filePath/path).
16
+ * 2. If the edited path matches
17
+ * ^(skills|agents|reference|source/skills)/.*\.(md|json)$
18
+ * (path-separator-agnostic), schedule a background refresh.
19
+ * 3. Otherwise no-op — write {continue:true} and exit 0.
20
+ * 4. Always exit 0. Never block. Never surface errors. Errors only ever
21
+ * land as a stderr breadcrumb (best-effort).
22
+ *
23
+ * Opt-out:
24
+ * Set GDD_DISABLE_INTEL_TRIGGER=1 to silence this hook completely
25
+ * (still writes {continue:true}, exits 0, spawns nothing).
26
+ *
27
+ * Dedup lock:
28
+ * .design/.intel-trigger.lock — a JSON file with {ts: <epoch_ms>}.
29
+ * If the lock is younger than 5 minutes, we assume a refresh is already
30
+ * in flight (or recently ran) and skip spawning again. Rapid sequential
31
+ * edits coalesce into one background rebuild. The lock is best-effort:
32
+ * if the .design/ dir does not exist or we cannot read/write the lock,
33
+ * we still proceed (or still no-op safely).
34
+ *
35
+ * Refresh path:
36
+ * scripts/build-intel.cjs is the rebuilder. It has no `--incremental`
37
+ * flag (incremental is its DEFAULT behavior — invoking it without
38
+ * `--force` re-extracts only changed files via mtime/git-hash). The
39
+ * task spec said "if --incremental exists, spawn it; else emit a
40
+ * breadcrumb." Since the script DOES do incremental by default, we
41
+ * spawn it as `node scripts/build-intel.cjs` (no flags) and surface
42
+ * the convention in the breadcrumb so future maintainers know why
43
+ * no `--incremental` was passed. If the script is ever missing, we
44
+ * emit only a breadcrumb and continue.
45
+ *
46
+ * Spawn shape:
47
+ * child_process.spawn('node', [script], { detached: true,
48
+ * stdio: 'ignore', windowsHide: true }) followed by child.unref().
49
+ * This decouples the child from our process tree so the hook returns
50
+ * immediately and the rebuild happens out of band.
51
+ */
52
+
53
+ const fs = require('node:fs');
54
+ const path = require('node:path');
55
+ const { spawn } = require('node:child_process');
56
+
57
+ const LOCK_TTL_MS = 5 * 60 * 1000; // 5 minutes
58
+ const TARGET_RE = /^(?:skills|agents|reference|source\/skills)\/.*\.(?:md|json)$/;
59
+
60
+ /**
61
+ * Extract the edited file path + tool name from a PostToolUse payload.
62
+ * Returns { tool, filename } or null when nothing usable was found.
63
+ */
64
+ function extractTarget(payload) {
65
+ if (!payload || typeof payload !== 'object') return null;
66
+ const tool = payload.tool_name || payload.toolName;
67
+ if (tool !== 'Write' && tool !== 'Edit') return null;
68
+ const input = payload.tool_input || payload.toolInput || {};
69
+ const filename =
70
+ input.file_path ||
71
+ input.filePath ||
72
+ input.path ||
73
+ (payload.tool_response &&
74
+ (payload.tool_response.filePath || payload.tool_response.file_path)) ||
75
+ '';
76
+ if (!filename) return null;
77
+ return { tool, filename: String(filename) };
78
+ }
79
+
80
+ /**
81
+ * Decide whether the given (absolute or relative) filename, considered
82
+ * relative to `cwd`, lives under one of the design-authoritative roots
83
+ * and is .md or .json. Returns true/false. Path-separator-agnostic.
84
+ */
85
+ function isDesignSurface(filename, cwd) {
86
+ if (!filename) return false;
87
+ let rel;
88
+ try {
89
+ rel = path.isAbsolute(filename)
90
+ ? path.relative(cwd, filename)
91
+ : filename;
92
+ } catch {
93
+ return false;
94
+ }
95
+ if (!rel || rel.startsWith('..')) return false;
96
+ const normalised = rel.replace(/\\/g, '/');
97
+ return TARGET_RE.test(normalised);
98
+ }
99
+
100
+ /**
101
+ * Best-effort: returns true if the lockfile exists AND its timestamp is
102
+ * younger than LOCK_TTL_MS. False on any error (missing dir, parse fail,
103
+ * stat fail) — fail-open so we still trigger the rebuild.
104
+ */
105
+ function lockIsFresh(lockPath) {
106
+ try {
107
+ if (!fs.existsSync(lockPath)) return false;
108
+ const raw = fs.readFileSync(lockPath, 'utf8');
109
+ const parsed = JSON.parse(raw);
110
+ const ts = Number(parsed && parsed.ts);
111
+ if (!Number.isFinite(ts)) return false;
112
+ return Date.now() - ts < LOCK_TTL_MS;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Best-effort: write {ts: Date.now()} to the lockfile, ensuring its
120
+ * parent dir exists. Swallows all errors — locking is purely an
121
+ * optimisation; failure to lock just means the next edit may re-trigger.
122
+ */
123
+ function writeLock(lockPath) {
124
+ try {
125
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
126
+ fs.writeFileSync(lockPath, JSON.stringify({ ts: Date.now() }), 'utf8');
127
+ } catch {
128
+ /* swallow */
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Spawn the intel rebuild as a detached background process. The child is
134
+ * fully decoupled (stdio:'ignore', detached:true, unref()) so the hook
135
+ * returns immediately. Errors are swallowed — the worst case is a stale
136
+ * intel store, which is no worse than the pre-hook baseline.
137
+ */
138
+ function spawnRebuild(cwd, script) {
139
+ try {
140
+ const child = spawn(process.execPath, [script], {
141
+ cwd,
142
+ detached: true,
143
+ stdio: 'ignore',
144
+ windowsHide: true,
145
+ env: process.env,
146
+ });
147
+ child.unref();
148
+ return true;
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Core hook entry. Returns the decision object to write to stdout.
156
+ * Always returns {continue: true}. Exported for unit testing.
157
+ *
158
+ * Optional `opts.spawnImpl` overrides the spawn-rebuild side effect
159
+ * (so tests can assert it was called without forking a real node).
160
+ */
161
+ function main(payload, opts = {}) {
162
+ // Opt-out shortcut — read here (not at module top) so tests can flip the
163
+ // env between calls without re-requiring the module.
164
+ if (process.env.GDD_DISABLE_INTEL_TRIGGER === '1') {
165
+ return { continue: true };
166
+ }
167
+
168
+ const cwd = (payload && payload.cwd) || opts.cwd || process.cwd();
169
+ const target = extractTarget(payload);
170
+ if (!target) return { continue: true };
171
+ if (!isDesignSurface(target.filename, cwd)) return { continue: true };
172
+
173
+ const lockPath = path.join(cwd, '.design', '.intel-trigger.lock');
174
+ if (lockIsFresh(lockPath)) return { continue: true };
175
+
176
+ const script = path.join(cwd, 'scripts', 'build-intel.cjs');
177
+ if (!fs.existsSync(script)) {
178
+ // Follow-up: a missing rebuilder is not this hook's problem to fix.
179
+ try {
180
+ process.stderr.write(
181
+ '[gdd-intel-trigger] would refresh .design/intel/ if scripts/build-intel.cjs --incremental existed\n'
182
+ );
183
+ } catch {
184
+ /* swallow */
185
+ }
186
+ return { continue: true };
187
+ }
188
+
189
+ // Lock first (idempotent if write fails), then spawn. Locking first
190
+ // means a sibling Edit racing this one will see a fresh lock and skip
191
+ // even if our spawn has not yet completed.
192
+ writeLock(lockPath);
193
+ const doSpawn = typeof opts.spawnImpl === 'function' ? opts.spawnImpl : spawnRebuild;
194
+ doSpawn(cwd, script);
195
+
196
+ return { continue: true };
197
+ }
198
+
199
+ /** CLI entrypoint — read JSON from stdin, decide, write {continue:true}. */
200
+ async function run(stdin = process.stdin, stdout = process.stdout) {
201
+ let buf = '';
202
+ try {
203
+ for await (const chunk of stdin) buf += chunk;
204
+ } catch {
205
+ stdout.write(JSON.stringify({ continue: true }));
206
+ return;
207
+ }
208
+ let payload;
209
+ try {
210
+ payload = JSON.parse(buf || '{}');
211
+ } catch {
212
+ stdout.write(JSON.stringify({ continue: true }));
213
+ return;
214
+ }
215
+ let decision;
216
+ try {
217
+ decision = main(payload);
218
+ } catch {
219
+ decision = { continue: true };
220
+ }
221
+ stdout.write(JSON.stringify(decision || { continue: true }));
222
+ }
223
+
224
+ if (require.main === module) {
225
+ run().catch(() => {
226
+ try {
227
+ process.stdout.write(JSON.stringify({ continue: true }));
228
+ } catch {
229
+ /* swallow */
230
+ }
231
+ });
232
+ }
233
+
234
+ module.exports = {
235
+ main,
236
+ extractTarget,
237
+ isDesignSurface,
238
+ lockIsFresh,
239
+ writeLock,
240
+ spawnRebuild,
241
+ LOCK_TTL_MS,
242
+ TARGET_RE,
243
+ };
@@ -40,15 +40,70 @@ function loadBudget(cwd) {
40
40
  return defaults;
41
41
  }
42
42
 
43
+ /**
44
+ * Classify the outcome of an MCP tool call as 'success' | 'timeout' | 'error'.
45
+ *
46
+ * The previous implementation substring-matched 'timeout' / 'failed' against
47
+ * the ENTIRE JSON-stringified response. That fired false positives on legit
48
+ * successful payloads whose content happens to mention those words — e.g. a
49
+ * Figma node literally named "TimeoutBanner", or a summary line "2 of 5 nodes
50
+ * failed to update, retrying...". When the breaker false-positives, it
51
+ * advances consecutive_timeouts and eventually trips on healthy traffic.
52
+ *
53
+ * The fix: use the structured isError / is_error envelope as the primary
54
+ * signal. MCP tool results carry isError=true|false. Anything without an
55
+ * explicit error flag is treated as success — full stop. Only when the
56
+ * envelope says "error" do we then inspect the ERROR-message fields
57
+ * (content[*].text + error.message + error.code + top-level message) to
58
+ * decide between 'timeout' and generic 'error'. Arbitrary content text is
59
+ * never scanned.
60
+ */
43
61
  function classifyOutcome(toolResponse) {
44
62
  if (!toolResponse || typeof toolResponse !== 'object') return 'error';
45
- const text = JSON.stringify(toolResponse).slice(0, 4000).toLowerCase();
46
- // Check timeout FIRST a timed-out call may also set is_error, but we want
47
- // to classify it as "timeout" so consecutive_timeouts advances correctly.
48
- if (text.includes('timeout') || text.includes('timed out') || text.includes('deadline exceeded')) return 'timeout';
49
- if (toolResponse.is_error) return 'error';
50
- if (text.includes('"error"') || text.includes('failed')) return 'error';
51
- return 'success';
63
+
64
+ // MCP standard envelope: isError (camelCase). Claude Code historically
65
+ // passes is_error (snake_case). Accept either; treat absence as success.
66
+ const isError =
67
+ toolResponse.isError === true || toolResponse.is_error === true;
68
+
69
+ if (!isError) return 'success';
70
+
71
+ // Error path: classify timeout vs generic by reading ONLY the dedicated
72
+ // error-message fields, not the entire payload.
73
+ const messageBits = [];
74
+
75
+ // content[] may be a string (legacy) or an array of {type,text} (spec).
76
+ if (typeof toolResponse.content === 'string') {
77
+ messageBits.push(toolResponse.content);
78
+ } else if (Array.isArray(toolResponse.content)) {
79
+ for (const c of toolResponse.content) {
80
+ if (c && typeof c.text === 'string') messageBits.push(c.text);
81
+ }
82
+ }
83
+
84
+ if (toolResponse.error && typeof toolResponse.error === 'object') {
85
+ if (typeof toolResponse.error.message === 'string') {
86
+ messageBits.push(toolResponse.error.message);
87
+ }
88
+ if (typeof toolResponse.error.code === 'string') {
89
+ messageBits.push(toolResponse.error.code);
90
+ }
91
+ }
92
+ if (typeof toolResponse.message === 'string') {
93
+ messageBits.push(toolResponse.message);
94
+ }
95
+
96
+ const combined = messageBits.join(' ').toLowerCase();
97
+ // \btimeout\b matches "timeout" and "request timeout"; \btimed?\s*out\b
98
+ // matches "timed out"; deadline exceeded is gRPC; etimedout is Node fs.
99
+ if (
100
+ /\btimeout\b|\btimed?\s*out\b|\bdeadline\s+exceeded\b|\betimedout\b/.test(
101
+ combined,
102
+ )
103
+ ) {
104
+ return 'timeout';
105
+ }
106
+ return 'error';
52
107
  }
53
108
 
54
109
  function readJsonlTail(filePath) {