@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
@@ -1,24 +1,23 @@
1
- // scripts/lib/apply-reflections/incubator-proposals.cjs — Plan 29-05
1
+ // scripts/lib/apply-reflections/incubator-proposals.cjs
2
2
  //
3
3
  // Incubator-draft proposal class for /gdd:apply-reflections. Consumes drafts
4
- // authored by scripts/lib/incubator-author.cjs (Plan 29-04) at
4
+ // authored by scripts/lib/incubator-author.cjs at
5
5
  // `.design/reflections/incubator/<slug>/` and exposes the 7 actions surfaced
6
6
  // in skills/apply-reflections/SKILL.md.
7
7
  //
8
8
  // Exports: discoverIncubatorDrafts, renderProposal, applyAccept, applyReject,
9
9
  // applyEdit, checkStage1Gate, recordOptIn.
10
10
  //
11
- // Decisions honoured:
12
- // * D-01 — checkStage1Gate is read-only; recordOptIn is the sole writer and
13
- // only fires on explicit user confirmation. No auto-flip ever.
14
- // * D-04 — applyAccept performs the full draft → final-artifact write +
15
- // registry append in one call. No intermediate state.
16
- // * D-05 — applyAccept calls validateScope from
17
- // scripts/validate-incubator-scope.cjs BEFORE any filesystem
18
- // mutation. Failure throws; registry and incubator subdir
19
- // untouched. Non-bypassable.
20
- // * D-12 DRAFT.md is copied verbatim, so the drafter's `delegate_to: null`
21
- // frontmatter survives the promotion.
11
+ // Invariants:
12
+ // * checkStage1Gate is read-only; recordOptIn is the sole state writer and
13
+ // only fires on explicit user confirmation. No auto-flip ever.
14
+ // * applyAccept performs the full draft → final-artifact write + registry
15
+ // append in one call. No intermediate state.
16
+ // * applyAccept calls validateScope from
17
+ // scripts/validate-incubator-scope.cjs BEFORE any filesystem mutation.
18
+ // Failure throws; registry and incubator subdir untouched. Non-bypassable.
19
+ // * DRAFT.md is copied verbatim, so the drafter's `delegate_to: null`
20
+ // frontmatter survives the promotion.
22
21
  //
23
22
  // Style: CommonJS, zero external deps (node:fs / node:path / node:child_process /
24
23
  // node:os only).
@@ -31,13 +30,14 @@ const child_process = require('node:child_process');
31
30
  const os = require('node:os');
32
31
 
33
32
  const { validateScope } = require('../../validate-incubator-scope.cjs');
33
+ const touchesPatternMiner = require('../touches-pattern-miner.cjs');
34
34
 
35
35
  // --- Constants ---
36
36
 
37
37
  const DEFAULT_INCUBATOR_DIR = '.design/reflections/incubator';
38
38
  const DEFAULT_REGISTRY_PATH = 'reference/registry.json';
39
39
  const DEFAULT_GATE_SPEC_PATH = 'reference/capability-gap-stage-gate.md';
40
- const DEFAULT_STATE_PATH = '.planning/STATE.md';
40
+ const DEFAULT_STATE_PATH = '.design/STATE.md';
41
41
  const OPT_IN_HEADING = '## Capability-gap Stage-1 opt-in';
42
42
  const OPT_IN_TOKEN_RE = /Stage-1 opt-in|capability.gap.*opt.in|confirmed_by/i;
43
43
 
@@ -94,7 +94,7 @@ function discoverIncubatorDrafts(options) {
94
94
  const drafts = [];
95
95
  for (const ent of entries) {
96
96
  if (!ent.isDirectory()) continue;
97
- if (ent.name === 'archive') continue; // D-06: archived drafts not surfaced
97
+ if (ent.name === 'archive') continue; // archived drafts not surfaced
98
98
 
99
99
  const slugDir = path.join(incubatorDir, ent.name);
100
100
  const manifestPath = path.join(slugDir, 'manifest.json');
@@ -199,15 +199,15 @@ function renderUnifiedDiff(oldText, newText) {
199
199
  return out.join('\n');
200
200
  }
201
201
 
202
- // --- applyAccept (D-04 + D-05) ---
202
+ // --- applyAccept ---
203
203
 
204
204
  /**
205
- * Promote draft → final artifact + registry entry in one call (D-04).
205
+ * Promote draft → final artifact + registry entry in one call.
206
206
  *
207
- * Order: validateScope (D-05; throws → no writes) → read DRAFT.md →
207
+ * Order: validateScope (throws → no writes) → read DRAFT.md →
208
208
  * [dryRun: return intent] → mkdirp parent → atomic-write target →
209
209
  * append-and-atomic-write registry → fs.rm incubator subdir last
210
- * (so partial failure leaves draft retryable — T-29.05-04).
210
+ * (so partial failure leaves draft retryable).
211
211
  */
212
212
  function applyAccept(draft, options) {
213
213
  const o = options || {};
@@ -217,7 +217,7 @@ function applyAccept(draft, options) {
217
217
  : path.join(repoRoot, o.registryPath || DEFAULT_REGISTRY_PATH);
218
218
  const dryRun = !!o.dryRun;
219
219
 
220
- // Step 1 — D-05 scope guard. THROWS on failure; registry untouched.
220
+ // Step 1 — scope guard. THROWS on failure; registry untouched.
221
221
  validateScope(draft.target_path, { repoRoot });
222
222
 
223
223
  const draftBody = fs.readFileSync(draft.draft_path, 'utf8');
@@ -275,7 +275,7 @@ function appendRegistryEntry(registryPath, kind, entry) {
275
275
  throw new Error(`[incubator-proposals] registry root must be an object: ${registryPath}`);
276
276
  }
277
277
 
278
- // Phase 14.5 self-authoring shape: { agents: [...], skills: [...] }.
278
+ // Self-authoring registry shape: { agents: [...], skills: [...] }.
279
279
  // Initialize missing arrays additively so we never clobber another schema's data.
280
280
  if (kind === 'agent') {
281
281
  if (!Array.isArray(registry.agents)) registry.agents = [];
@@ -356,10 +356,10 @@ function applyEdit(draft, options) {
356
356
  }
357
357
  }
358
358
 
359
- // --- checkStage1Gate (D-01: read-only) ---
359
+ // --- checkStage1Gate (read-only) ---
360
360
 
361
361
  /**
362
- * Read-only Stage-1 gate inspection (D-01).
362
+ * Read-only Stage-1 gate inspection.
363
363
  * thresholdMet = count(registry entries with origin === 'incubator') ≥ K
364
364
  * optInRecorded = state file contains an opt-in token
365
365
  * summary = human-readable one-liner
@@ -404,7 +404,7 @@ function checkStage1Gate(options) {
404
404
  /**
405
405
  * Pull `K` out of capability-gap-stage-gate.md. The doc encodes K as a row
406
406
  * in a markdown table: `| K | 3 | Minimum number of stable clusters... |`.
407
- * If absent or unparseable, fall back to 3 (Phase 29 D-03 default).
407
+ * If absent or unparseable, fall back to 3 (the documented default).
408
408
  */
409
409
  function readK(gateSpecPath) {
410
410
  const src = safeReadFileSync(gateSpecPath);
@@ -415,12 +415,12 @@ function readK(gateSpecPath) {
415
415
  return Number.isFinite(v) && v > 0 ? v : 3;
416
416
  }
417
417
 
418
- // --- recordOptIn (D-01: explicit-only) ---
418
+ // --- recordOptIn (explicit-only) ---
419
419
 
420
420
  /**
421
421
  * Persist the user's explicit Stage-1 opt-in to STATE.md. Idempotent.
422
422
  * IMPORTANT: this is the SOLE state writer in this module. Only invoke after
423
- * explicit user confirmation in the apply-reflections UX (D-01).
423
+ * explicit user confirmation in the apply-reflections UX.
424
424
  */
425
425
  function recordOptIn(options) {
426
426
  const o = options || {};
@@ -442,10 +442,41 @@ function recordOptIn(options) {
442
442
  return { optInRecorded: true, at, confirmedBy };
443
443
  }
444
444
 
445
+ // --- Touches-pattern proposals (Batch D D8 wire) ---
446
+ //
447
+ // Surfaces recurring `Touches:` signatures across archived task files as
448
+ // auto-crystallization proposals alongside the incubator drafts. The miner
449
+ // is opt-in via `proposalsRoot` arg (defaults to the touches-pattern-miner
450
+ // default location); when no archive exists yet, returns an empty list.
451
+
452
+ function discoverTouchesPatternProposals(opts = {}) {
453
+ const cwd = opts.cwd || process.cwd();
454
+ const cycleDir = path.join(cwd, opts.cycleDir || touchesPatternMiner.DEFAULT_CYCLE_DIR);
455
+ if (!fs.existsSync(cycleDir)) return [];
456
+ try {
457
+ const mined = touchesPatternMiner.mine({
458
+ cycleDir,
459
+ minTasks: opts.minTasks,
460
+ minCycles: opts.minCycles,
461
+ });
462
+ if (!Array.isArray(mined) || mined.length === 0) return [];
463
+ return mined.map((pattern, i) => ({
464
+ type: 'touches-pattern',
465
+ id: `touches-pattern-${i + 1}`,
466
+ pattern,
467
+ summary: pattern.signature || pattern.canonical || `Pattern #${i + 1}`,
468
+ }));
469
+ } catch (_err) {
470
+ // Miner is best-effort — never block apply-reflections on its failure.
471
+ return [];
472
+ }
473
+ }
474
+
445
475
  // --- Exports ---
446
476
 
447
477
  module.exports = {
448
478
  discoverIncubatorDrafts,
479
+ discoverTouchesPatternProposals,
449
480
  renderProposal,
450
481
  applyAccept,
451
482
  applyReject,
@@ -306,9 +306,12 @@ function buildManifest(sources) {
306
306
  category,
307
307
  capabilities: ['Read', 'Write'],
308
308
  websiteURL: homepage || '',
309
+ // Codex uses /gdd- prefix uniformly (not /gdd: like Claude Code).
310
+ // Both lines use the same prefix to avoid the documented inconsistency
311
+ // (audit P1 #4 — committed manifest had mixed /gdd: and $gdd-).
309
312
  defaultPrompt: [
310
- 'Run /gdd:brief to start a design cycle.',
311
- 'Use $gdd-explore to audit a screen.',
313
+ 'Run /gdd-brief to start a design cycle.',
314
+ 'Run /gdd-explore to audit a screen.',
312
315
  ],
313
316
  brandColor: '#10A37F',
314
317
  };
@@ -24,6 +24,26 @@
24
24
  *
25
25
  * Pure / side-effect-free: no fs, no env, no path. `convert` is a
26
26
  * deterministic string → string transform.
27
+ *
28
+ * KNOWN LIMITATION — sibling .md files (audit batch H6, 2026-06-04):
29
+ * The Cursor install drops ONLY `skills/<name>/SKILL.md`. Sibling
30
+ * `.md` files living next to SKILL.md (e.g. `discover-procedure.md`,
31
+ * `explore-procedure.md`, `cache-policy.md`) are NOT enumerated by
32
+ * `runtime-artifact-layout.cjs#skillsKind` and therefore NOT installed.
33
+ * Source SKILL.md files reference siblings via relative paths
34
+ * (e.g. `./discover-procedure.md`); on Cursor those links resolve to
35
+ * nothing.
36
+ *
37
+ * This is a systemic limitation of the current `multi-artifact`
38
+ * pipeline, not a Cursor-specific bug — it affects every runtime that
39
+ * uses `skillsKind` (claude global, cursor, codex, copilot, antigravity,
40
+ * windsurf, augment, trae, qwen, codebuddy). Fix requires extending the
41
+ * StagedArtifact contract to emit multiple files per skill (one
42
+ * SKILL.md + N siblings), updating `computeDestPath`, the foreign-file
43
+ * detection in `detectMultiArtifactInstalled`, and the uninstall
44
+ * enumeration in `uninstallMultiArtifact`. Tracked as a follow-up
45
+ * beyond batch H6 scope. See `connections/cursor.md` for user-facing
46
+ * guidance.
27
47
  */
28
48
 
29
49
  const shared = require('./shared.cjs');
@@ -120,7 +120,7 @@ async function runReportFlow(args) {
120
120
  submitted: false,
121
121
  reason: 'disabled',
122
122
  surface: reason, // 'env' | 'config'
123
- message: `/gdd:report-issue is disabled by ${reasonMsg}. Run \`gsd-health\` to see the active disable surface.`,
123
+ message: `/gdd:report-issue is disabled by ${reasonMsg}. Run \`/gdd:health\` to see the active disable surface.`,
124
124
  };
125
125
  }
126
126
 
@@ -18,7 +18,10 @@
18
18
  "name": "apply-reflections",
19
19
  "description": "Review and selectively apply proposals from .design/reflections/<cycle-slug>.md. Diffs each proposal, prompts user to accept/skip/edit, then writes changes.",
20
20
  "argument_hint": "[--cycle <slug>] [--filter <FRONTMATTER|REFERENCE|BUDGET|QUESTION|GLOBAL-SKILL>] [--dry-run]",
21
- "tools": "Read, Write, Edit, Bash, Glob"
21
+ "tools": "Read, Write, Edit, Bash, Glob",
22
+ "composes_with": [
23
+ "audit"
24
+ ]
22
25
  },
23
26
  {
24
27
  "name": "audit",
@@ -40,7 +43,7 @@
40
43
  },
41
44
  {
42
45
  "name": "bootstrap-ds",
43
- "description": "Bootstraps a design system for a GREENFIELD project that has none - no Figma, no tokens, no component library. Takes a brand input (primary color + optional secondary + tone tags + target framework) and emits a coherent OKLCH token system (color tints, modular type scale, 4pt/8pt spacing, radius + motion defaults) in 3 variants to pick from, then scaffolds proof components (button/input/card). Use at the start of a brand-new project, or when {{command_prefix}}discover finds no existing design system. Never invents a brand; never overwrites an existing DS.",
46
+ "description": "Bootstraps a design system for a GREENFIELD project that has none - no Figma, no tokens, no component library. Takes a brand input (primary color + optional secondary + tone tags + target framework) and emits a coherent OKLCH token system (color tints, modular type scale, 4pt/8pt spacing, radius + motion defaults) in 3 variants to pick from, then scaffolds proof components (button/input/card). Use at the start of a brand-new project, or when {{command_prefix}}explore finds no existing design system. Never invents a brand; never overwrites an existing DS.",
44
47
  "argument_hint": "[--primary <color>] [--secondary <color>] [--tone <tags>] [--framework web|native-ios|native-android|flutter]",
45
48
  "user_invocable": true,
46
49
  "tools": "Read, Write, Bash, Glob, Grep, AskUserQuestion, Task"
@@ -50,6 +53,9 @@
50
53
  "description": "Stage 1 of 5 design intake that captures problem statement, audience, constraints, success metrics, and scope into .design/BRIEF.md, and bootstraps .design/STATE.md if missing. Use when starting a new design cycle and before {{command_prefix}}explore. Activates for requests involving capturing a problem statement, defining audience and constraints, or starting a new design brief.",
51
54
  "argument_hint": "[--re-brief to redo intake on existing project]",
52
55
  "tools": "Read, Write, AskUserQuestion, mcp__gdd_state__frontmatter_update, mcp__gdd_state__set_status, mcp__gdd_state__update_progress, mcp__gdd_state__get",
56
+ "composes_with": [
57
+ "explore"
58
+ ],
53
59
  "next_skills": [
54
60
  "explore"
55
61
  ]
@@ -63,7 +69,7 @@
63
69
  },
64
70
  {
65
71
  "name": "cache-manager",
66
- "description": "Maintains .design/cache-manifest.json for Layer B explicit cache per D-08. Computes deterministic SHA-256 input-hash from (agent-path + sorted-input-file-paths + input-content-hashes). On spawn: lookup key → return cached blob if within TTL, else miss. On completion: write result + TTL. Consulted by hooks/budget-enforcer.js before every Agent spawn.",
72
+ "description": "Maintains .design/cache-manifest.json for Layer B explicit cache per D-08. Computes deterministic SHA-256 input-hash from (agent-path + sorted-input-file-paths + input-content-hashes). On spawn: lookup key → return cached blob if within TTL, else miss. On completion: write result + TTL. Consulted by hooks/budget-enforcer.ts before every Agent spawn.",
67
73
  "user_invocable": false,
68
74
  "tools": "Read, Bash, Write",
69
75
  "disable_model_invocation": true
@@ -76,15 +82,21 @@
76
82
  },
77
83
  {
78
84
  "name": "compare",
79
- "description": "Compute the delta between the `DESIGN.md` baseline (from scan) and the `DESIGN-VERIFICATION.md` result (from verify), reporting per-category score delta, anti-pattern delta (resolved vs new), must-have pass/fail change, and design drift (regressions without covering tasks in `DESIGN-PLAN.md`). Use after `verify` to measure whether a design pipeline cycle actually improved the design. Writes `.design/COMPARE-REPORT.md`. Activates for requests involving diffing a design baseline against verification output, or a before-after design delta.",
85
+ "description": "Compute the delta between the `DESIGN.md` baseline (from explore) and the `DESIGN-VERIFICATION.md` result (from verify), reporting per-category score delta, anti-pattern delta (resolved vs new), must-have pass/fail change, and design drift (regressions without covering tasks in `DESIGN-PLAN.md`). Use after `verify` to measure whether a design pipeline cycle actually improved the design. Writes `.design/COMPARE-REPORT.md`. Activates for requests involving diffing a design baseline against verification output, or a before-after design delta.",
80
86
  "argument_hint": "",
81
- "user_invocable": true
87
+ "user_invocable": true,
88
+ "composes_with": [
89
+ "verify"
90
+ ]
82
91
  },
83
92
  {
84
93
  "name": "complete-cycle",
85
94
  "description": "Cycle closeout command that marks CYCLES.md entry complete, archives pipeline artifacts to .design/archive/cycle-N/, generates EXPERIENCE.md, rebuilds the search index, and resets STATE.md. Use when a design cycle has shipped and you're ready to start the next one.",
86
95
  "argument_hint": "[<retrospective note>]",
87
- "tools": "Read, Write, Bash, AskUserQuestion"
96
+ "tools": "Read, Write, Bash, AskUserQuestion",
97
+ "composes_with": [
98
+ "audit"
99
+ ]
88
100
  },
89
101
  {
90
102
  "name": "connections",
@@ -112,7 +124,10 @@
112
124
  "name": "darkmode",
113
125
  "description": "Audit a project's dark mode implementation - detects architecture (CSS custom props, Tailwind `dark:` prefix, or JS class toggle), runs architecture-specific contrast / token-override / anti-pattern / meta-property checks, and writes a prioritized fix list to `.design/DARKMODE-AUDIT.md`. Use when the user wants to verify dark mode quality without re-running the full design pipeline. Read-only - no score writeback to `DESIGN.md`. Activates for requests involving auditing dark mode, checking dark-theme contrast, or dark-mode anti-patterns.",
114
126
  "argument_hint": "",
115
- "user_invocable": true
127
+ "user_invocable": true,
128
+ "composes_with": [
129
+ "audit"
130
+ ]
116
131
  },
117
132
  {
118
133
  "name": "debug",
@@ -127,22 +142,23 @@
127
142
  "argument_hint": "[--auto] [--parallel] [--variants N]",
128
143
  "user_invocable": true,
129
144
  "tools": "Read, Write, Bash, Grep, Glob, Task, AskUserQuestion, mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__update_progress, mcp__gdd_state__set_status, mcp__gdd_state__add_blocker, mcp__gdd_state__resolve_blocker, mcp__gdd_state__checkpoint",
145
+ "composes_with": [
146
+ "figma-write",
147
+ "paper-write",
148
+ "pencil-write"
149
+ ],
130
150
  "next_skills": [
131
151
  "verify"
132
152
  ]
133
153
  },
134
- {
135
- "name": "discover",
136
- "frontmatter_name": "discover",
137
- "description": "Stage 1.5 of 4 orchestrator that probes Figma / Refero / Pinterest connections, spawns design-context-builder (auto-detect + interview) and (via lazy gate) design-context-checker (6-dimension validator), producing .design/DESIGN-CONTEXT.md. Use after {{command_prefix}}scan when a fast-path context build is wanted instead of the full {{command_prefix}}explore. Activates for requests involving detecting an existing design system, inventorying tokens and components, or onboarding a brownfield repo.",
138
- "argument_hint": "[--auto] [--incremental] [--full]",
139
- "user_invocable": true
140
- },
141
154
  {
142
155
  "name": "discuss",
143
156
  "description": "Adaptive design interview command that spawns design-discussant in normal / --all / --spec mode to gather decisions via one-question-at-a-time AskUserQuestion, writing D-XX entries to STATE.md <decisions>. Use when locking design decisions outside the main pipeline or backfilling missing context.",
144
157
  "argument_hint": "[topic] [--all] [--spec] [--cycle <name>]",
145
- "tools": "Read, Write, Task"
158
+ "tools": "Read, Write, Task",
159
+ "composes_with": [
160
+ "list-assumptions"
161
+ ]
146
162
  },
147
163
  {
148
164
  "name": "do",
@@ -155,6 +171,11 @@
155
171
  "description": "Stage 2 of 5 - unified exploration merging inventory grep + design interview. Probes 6 connections, scans the codebase, conducts the AskUserQuestion interview, and writes .design/DESIGN.md + DESIGN-DEBT.md + DESIGN-CONTEXT.md. Use after {{command_prefix}}brief to map the existing system and lock decisions before planning. Activates for requests involving researching design direction, gathering references, or exploring visual options.",
156
172
  "argument_hint": "[--skip-interview] [--skip-scan] [--incremental] [--full]",
157
173
  "tools": "Read, Write, Bash, Grep, Glob, Task, AskUserQuestion, mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__probe_connections, mcp__gdd_state__update_progress, mcp__gdd_state__set_status, mcp__gdd_state__add_blocker, mcp__gdd_state__checkpoint, mcp__gdd_state__add_decision",
174
+ "composes_with": [
175
+ "discuss",
176
+ "list-assumptions",
177
+ "sketch"
178
+ ],
158
179
  "next_skills": [
159
180
  "plan"
160
181
  ]
@@ -272,13 +293,19 @@
272
293
  "name": "new-cycle",
273
294
  "description": "Start a new design cycle. Creates cycle scope in STATE.md, initializes .design/CYCLES.md entry. Each cycle has its own goal and tracks its own decisions/tasks/pipeline runs.",
274
295
  "argument_hint": "[<goal>]",
275
- "tools": "Read, Write, AskUserQuestion"
296
+ "tools": "Read, Write, AskUserQuestion",
297
+ "composes_with": [
298
+ "brief"
299
+ ]
276
300
  },
277
301
  {
278
302
  "name": "new-project",
279
303
  "description": "Initialize a new get-design-done project. Gathers project context, creates PROJECT.md and STATE.md, initializes first cycle. Run once per project before any pipeline stage.",
280
304
  "argument_hint": "[--name <project-name>]",
281
305
  "tools": "Read, Write, AskUserQuestion, Bash, Glob",
306
+ "composes_with": [
307
+ "brief"
308
+ ],
282
309
  "next_skills": [
283
310
  "brief"
284
311
  ]
@@ -327,6 +354,20 @@
327
354
  "tools": "Read, Write, Bash, Grep, Glob",
328
355
  "registered_in_phase": "56"
329
356
  },
357
+ {
358
+ "name": "paper-write",
359
+ "description": "Push design decisions from `.design/DESIGN-CONTEXT.md` into the active paper.design canvas by dispatching the `design-paper-writer` agent in one of three modes (annotate / tokenize / roundtrip). Use when the user has completed a design pipeline cycle and wants the decisions reflected in their paper.design canvas. Operates proposal->confirm with `--dry-run`.",
360
+ "argument_hint": "<annotate|tokenize|roundtrip> [--dry-run]",
361
+ "user_invocable": true,
362
+ "tools": "Read, Write, Bash, Grep, Glob"
363
+ },
364
+ {
365
+ "name": "pencil-write",
366
+ "description": "Update local `.pen` source files with design decisions from `.design/DESIGN-CONTEXT.md` by dispatching the `design-pencil-writer` agent in one of two modes (annotate / roundtrip). Use when the user has completed a design pipeline cycle and wants the decisions reflected in their `.pen` files. Operates proposal->confirm with `--dry-run`.",
367
+ "argument_hint": "<annotate|roundtrip> [--dry-run]",
368
+ "user_invocable": true,
369
+ "tools": "Read, Write, Bash, Grep, Glob"
370
+ },
330
371
  {
331
372
  "name": "pause",
332
373
  "description": "Write a numbered checkpoint so work can resume in a new session without re-running completed stages.",
@@ -480,17 +521,10 @@
480
521
  },
481
522
  {
482
523
  "name": "router",
483
- "description": "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}. Deterministic - no model call. Invoked once at command entry before any Agent spawn. Read by hooks/budget-enforcer.js.",
524
+ "description": "Routes a /gdd command to fast|quick|full path + S|M|L|XL complexity_class and returns {path, complexity_class, model_tier_overrides, resolved_models, estimated_cost_usd, cache_hits}. Deterministic - no model call. Invoked once at command entry before any Agent spawn. Read by hooks/budget-enforcer.ts.",
484
525
  "argument_hint": "<intent-string> [<target-artifacts-csv>]",
485
526
  "tools": "Read, Bash, Grep"
486
527
  },
487
- {
488
- "name": "scan",
489
- "frontmatter_name": "scan",
490
- "description": "Pre-pipeline initializer that maps an existing repo's design system (colors, typography, spacing, components, tokens), runs the anti-pattern audit, scores the 7 weighted categories, and writes DESIGN.md + .design/DESIGN-DEBT.md. Use when starting work in any new or existing repo before {{command_prefix}}discover. Activates for requests involving a fast read-only anti-pattern sweep, a quick design lint, or spotting slop without a full audit.",
491
- "argument_hint": "[--quick] [--full]",
492
- "user_invocable": true
493
- },
494
528
  {
495
529
  "name": "settings",
496
530
  "description": "Manage .design/config.json settings. Subcommands: profile, parallelism, cleanup, show.",
@@ -503,13 +537,19 @@
503
537
  "description": "Post-verify PR flow - creates a clean PR branch, invokes code review check, and prepares the PR for merge. Activates for requests involving finishing a cycle, packaging design output, or moving work to a pull request.",
504
538
  "argument_hint": "[--title <PR title>] [--draft]",
505
539
  "tools": "Read, Write, Bash, AskUserQuestion, Task",
506
- "disable_model_invocation": true
540
+ "disable_model_invocation": true,
541
+ "composes_with": [
542
+ "pr-branch"
543
+ ]
507
544
  },
508
545
  {
509
546
  "name": "sketch",
510
547
  "description": "Multi-variant HTML design exploration that creates .design/sketches/<slug>/ with N standalone variants (default 3), browser-openable directly via file:// without a build step. Use when answering 'what could this look like?' before committing to a direction.",
511
548
  "argument_hint": "[topic] [--variants N] [--quick]",
512
- "tools": "Read, Write, AskUserQuestion, Bash"
549
+ "tools": "Read, Write, AskUserQuestion, Bash",
550
+ "composes_with": [
551
+ "sketch-wrap-up"
552
+ ]
513
553
  },
514
554
  {
515
555
  "name": "sketch-wrap-up",
@@ -526,7 +566,10 @@
526
566
  "name": "spike",
527
567
  "description": "Timeboxed feasibility experiment that creates .design/spikes/<slug>/ with HYPOTHESIS.md, success/failure criteria, scratch/ subdirectory, and a default 60-minute timebox. Use when answering 'can this work?' before betting design or implementation effort on a risky approach.",
528
568
  "argument_hint": "[hypothesis] [--timebox <minutes>]",
529
- "tools": "Read, Write, Bash, AskUserQuestion"
569
+ "tools": "Read, Write, Bash, AskUserQuestion",
570
+ "composes_with": [
571
+ "spike-wrap-up"
572
+ ]
530
573
  },
531
574
  {
532
575
  "name": "spike-wrap-up",
@@ -565,7 +608,7 @@
565
608
  {
566
609
  "name": "synthesize",
567
610
  "frontmatter_name": "synthesize",
568
- "description": "Streaming synthesizer - collapses N parallel-agent markdown outputs into one compact merged summary via a single Haiku 4.5 call. Invoked inline by orchestrators (skills/map/, skills/discover/, skills/plan/) after parallel spawns return. Not user-invocable.",
611
+ "description": "Streaming synthesizer - collapses N parallel-agent markdown outputs into one compact merged summary via a single Haiku 4.5 call. Invoked inline by orchestrators (skills/map/, skills/explore/, skills/plan/) after parallel spawns return. Not user-invocable.",
569
612
  "tools": "Read, Task",
570
613
  "user_invocable": false,
571
614
  "disable_model_invocation": true
@@ -631,6 +674,10 @@
631
674
  "argument_hint": "[--auto] [--post-handoff]",
632
675
  "user_invocable": true,
633
676
  "tools": "mcp__gdd_state__get, mcp__gdd_state__transition_stage, mcp__gdd_state__add_must_have, mcp__gdd_state__add_blocker, mcp__gdd_state__resolve_blocker, mcp__gdd_state__update_progress, mcp__gdd_state__set_status, mcp__gdd_state__checkpoint, mcp__gdd_state__probe_connections",
677
+ "composes_with": [
678
+ "audit",
679
+ "debug"
680
+ ],
634
681
  "next_skills": [
635
682
  "ship"
636
683
  ]
@@ -46,7 +46,8 @@ function _findPackageRoot(startDir) {
46
46
  const PKG_ROOT = _findPackageRoot(__dirname);
47
47
 
48
48
  // ---------------------------------------------------------------------------
49
- // Lazy-require state-backend.cjs (Executor A).
49
+ // Lazy-require state-backend.cjs (loaded on first call so optional better-sqlite3
50
+ // binding does not crash module load).
50
51
  // ---------------------------------------------------------------------------
51
52
  let _backend = null;
52
53
  function _requireBackend() {
@@ -65,7 +66,7 @@ function _requireBackend() {
65
66
  }
66
67
 
67
68
  // ---------------------------------------------------------------------------
68
- // Lazy-require migrate-to-sqlite.cjs (Executor B) - used by recover().
69
+ // Lazy-require migrate-to-sqlite.cjs - used by recover().
69
70
  // ---------------------------------------------------------------------------
70
71
  let _migrate = null;
71
72
  function _requireMigrate() {
@@ -202,6 +203,42 @@ function query(sql, opts = {}) {
202
203
  }
203
204
  }
204
205
 
206
+ // ---------------------------------------------------------------------------
207
+ // _safeBackup(srcPath, bakPath) - H5 backup-guard.
208
+ //
209
+ // Copy srcPath to bakPath, then verify the backup exists AND is non-empty
210
+ // AFTER the copy. Returns true only when the backup is a faithful non-empty
211
+ // copy of the source. Callers MUST check the return value before unlinking
212
+ // the source - the dangerous pattern is `copy → unconditional unlink`, where
213
+ // a silent copy failure (or zero-byte destination) means the unlink deletes
214
+ // the only remaining data.
215
+ //
216
+ // Defensive: never throws. Returns false on any error or empty backup.
217
+ //
218
+ // @param {string} srcPath path to source file (must exist)
219
+ // @param {string} bakPath path for the backup (created/overwritten)
220
+ // @returns {boolean} true iff bakPath exists and is non-empty after copy
221
+ // ---------------------------------------------------------------------------
222
+
223
+ function _safeBackup(srcPath, bakPath) {
224
+ try {
225
+ fs.copyFileSync(srcPath, bakPath);
226
+ } catch {
227
+ return false;
228
+ }
229
+ // Post-copy verification: the backup must EXIST and be NON-EMPTY.
230
+ // copyFileSync can silently produce a 0-byte file in some failure modes
231
+ // (interrupted IO, full disk after open). An empty backup is not a backup;
232
+ // unlinking the source after one would destroy the data.
233
+ try {
234
+ const st = fs.statSync(bakPath);
235
+ if (!st.isFile() || st.size === 0) return false;
236
+ return true;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+
205
242
  // ---------------------------------------------------------------------------
206
243
  // rotateBak(dbPath) - shift .bak.0..9, cap at 10.
207
244
  // ---------------------------------------------------------------------------
@@ -247,12 +284,10 @@ function backupCycle(opts = {}) {
247
284
  }
248
285
  rotateBak(dbPath);
249
286
  const bak0 = `${dbPath}.bak.0`;
250
- try {
251
- fs.copyFileSync(dbPath, bak0);
287
+ if (_safeBackup(dbPath, bak0)) {
252
288
  return { backed_up: true, path: bak0 };
253
- } catch (err) {
254
- return { backed_up: false, message: `backupCycle: copy failed: ${err.message}` };
255
289
  }
290
+ return { backed_up: false, message: `backupCycle: copy failed or backup is empty at ${bak0}` };
256
291
  }
257
292
 
258
293
  // ---------------------------------------------------------------------------
@@ -282,9 +317,18 @@ function demigrate(opts = {}) {
282
317
  };
283
318
  }
284
319
  // Take a backup before removing.
320
+ // H5 backup-guard: only unlink when the backup is a faithful non-empty copy.
321
+ // If the copy failed (or produced a 0-byte file), refuse to unlink the source -
322
+ // we'd be deleting the only remaining data.
285
323
  rotateBak(dbPath);
286
324
  const bak0 = `${dbPath}.bak.0`;
287
- try { fs.copyFileSync(dbPath, bak0); } catch { /* best-effort backup */ }
325
+ if (!_safeBackup(dbPath, bak0)) {
326
+ return {
327
+ demigrated: false,
328
+ message: `demigrate: refusing to remove ${dbPath} - backup at ${bak0} ` +
329
+ `is missing or empty after copyFileSync (would lose data).`,
330
+ };
331
+ }
288
332
  try {
289
333
  fs.unlinkSync(dbPath);
290
334
  } catch (err) {
@@ -330,10 +374,23 @@ async function recover(opts = {}) {
330
374
  const dbPath = opts.dbPath || backend.sqlitePath(opts.projectRoot || process.cwd());
331
375
 
332
376
  // Step 1: Rotate existing (possibly corrupt) file to .bak.0.
377
+ // H5 backup-guard: only unlink the source after a verified non-empty backup.
378
+ // For recover() the source MAY already be corrupt - so an empty/failed backup
379
+ // is still significant signal. We refuse to unlink when the backup is missing
380
+ // OR zero bytes, so the operator retains a copy of the corrupt file for
381
+ // diagnostics. The caller can manually delete and retry once the backup
382
+ // location is writable.
333
383
  if (fs.existsSync(dbPath)) {
334
384
  rotateBak(dbPath);
335
385
  const bak0 = `${dbPath}.bak.0`;
336
- try { fs.copyFileSync(dbPath, bak0); } catch { /* best-effort */ }
386
+ if (!_safeBackup(dbPath, bak0)) {
387
+ return {
388
+ recovered: false,
389
+ message: `recover: refusing to remove ${dbPath} - backup at ${bak0} ` +
390
+ `is missing or empty after copyFileSync (would lose corrupt file ` +
391
+ `before rebuild). Resolve disk/permission issues and retry.`,
392
+ };
393
+ }
337
394
  try { fs.unlinkSync(dbPath); } catch (err) {
338
395
  return { recovered: false, message: `recover: could not remove corrupt ${dbPath}: ${err.message}` };
339
396
  }
@@ -344,7 +401,7 @@ async function recover(opts = {}) {
344
401
  if (!migrate) {
345
402
  return {
346
403
  recovered: false,
347
- message: 'recover: migrate-to-sqlite.cjs not available (Executor B not yet present). ' +
404
+ message: 'recover: migrate-to-sqlite.cjs could not be loaded (require failed). ' +
348
405
  'Cannot rebuild SQLite from markdown.',
349
406
  };
350
407
  }
@@ -399,5 +456,6 @@ module.exports = {
399
456
  // Expose internals for testing.
400
457
  _assertReadonly,
401
458
  _firstToken,
459
+ _safeBackup,
402
460
  DENIED_TOKENS,
403
461
  };