gsd-pi 2.81.0-dev.72a81bdf3 → 2.82.0-dev.2841a1e44

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 (243) hide show
  1. package/README.md +49 -30
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/GSD-WORKFLOW.md +3 -1
  4. package/dist/resources/extensions/browser-tools/tools/screenshot.js +1 -0
  5. package/dist/resources/extensions/browser-tools/tools/zoom.js +1 -0
  6. package/dist/resources/extensions/cmux/index.js +5 -0
  7. package/dist/resources/extensions/gsd/auto/orchestrator.js +113 -6
  8. package/dist/resources/extensions/gsd/auto/phases.js +9 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +169 -124
  10. package/dist/resources/extensions/gsd/auto-prompts.js +13 -5
  11. package/dist/resources/extensions/gsd/auto-verification.js +28 -22
  12. package/dist/resources/extensions/gsd/auto.js +128 -52
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +5 -0
  14. package/dist/resources/extensions/gsd/bootstrap/subagent-input.js +16 -7
  15. package/dist/resources/extensions/gsd/bootstrap/system-context.js +55 -12
  16. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +3 -1
  17. package/dist/resources/extensions/gsd/clean-root-preflight.js +170 -8
  18. package/dist/resources/extensions/gsd/commands/catalog.js +4 -1
  19. package/dist/resources/extensions/gsd/commands/handlers/core.js +22 -1
  20. package/dist/resources/extensions/gsd/commands-bootstrap.js +5 -0
  21. package/dist/resources/extensions/gsd/commands-handlers.js +15 -2
  22. package/dist/resources/extensions/gsd/context-store.js +112 -0
  23. package/dist/resources/extensions/gsd/db-writer.js +150 -84
  24. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  25. package/dist/resources/extensions/gsd/doctor-git-checks.js +41 -6
  26. package/dist/resources/extensions/gsd/knowledge-backfill.js +144 -0
  27. package/dist/resources/extensions/gsd/knowledge-capture.js +136 -0
  28. package/dist/resources/extensions/gsd/knowledge-parser.js +154 -0
  29. package/dist/resources/extensions/gsd/knowledge-projection.js +210 -0
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +6 -1
  31. package/dist/resources/extensions/gsd/md-importer.js +1 -1
  32. package/dist/resources/extensions/gsd/memory-backfill.js +73 -17
  33. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +222 -0
  34. package/dist/resources/extensions/gsd/migrate/command.js +5 -0
  35. package/dist/resources/extensions/gsd/migrate/preview.js +9 -0
  36. package/dist/resources/extensions/gsd/migrate/transformer.js +51 -4
  37. package/dist/resources/extensions/gsd/migrate/writer.js +11 -1
  38. package/dist/resources/extensions/gsd/prompts/system.md +2 -2
  39. package/dist/resources/extensions/gsd/provider-switch-observer.js +146 -0
  40. package/dist/resources/extensions/gsd/templates/knowledge.md +2 -2
  41. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +119 -0
  42. package/dist/resources/extensions/gsd/unit-context-manifest.js +25 -2
  43. package/dist/resources/extensions/gsd/verification-verdict.js +26 -0
  44. package/dist/resources/extensions/gsd/worktree-lifecycle.js +21 -2
  45. package/dist/resources/extensions/subagent/index.js +448 -78
  46. package/dist/resources/extensions/subagent/launch.js +77 -0
  47. package/dist/resources/extensions/subagent/run-store.js +148 -0
  48. package/dist/resources/extensions/visual-brief/artifact-policy.js +29 -0
  49. package/dist/resources/extensions/visual-brief/extension-manifest.json +8 -0
  50. package/dist/resources/extensions/visual-brief/index.js +5 -0
  51. package/dist/resources/extensions/visual-brief/page-contract.js +122 -0
  52. package/dist/resources/extensions/visual-brief/prompts.js +111 -0
  53. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  54. package/dist/web/standalone/.next/BUILD_ID +1 -1
  55. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  56. package/dist/web/standalone/.next/build-manifest.json +3 -3
  57. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  58. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  59. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.rsc +2 -2
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  77. package/dist/web/standalone/.next/server/app/index.html +1 -1
  78. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  79. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  81. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
  83. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  84. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  86. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  89. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  90. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  91. package/dist/web/standalone/.next/static/chunks/2973.33f26573894b6153.js +2 -0
  92. package/dist/web/standalone/.next/static/chunks/{8359.e059d86b255fce1c.js → 8359.7eb3bb8f8ecf4c01.js} +2 -2
  93. package/dist/web/standalone/.next/static/chunks/{webpack-de742b64187e13fe.js → webpack-6a95bc41e0f7ec89.js} +1 -1
  94. package/dist/web/standalone/.next/static/css/0262768ec1b89d34.css +1 -0
  95. package/package.json +5 -4
  96. package/packages/contracts/dist/rpc.test.js +7 -0
  97. package/packages/contracts/dist/rpc.test.js.map +1 -1
  98. package/packages/contracts/dist/workflow.d.ts +21 -0
  99. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  100. package/packages/contracts/dist/workflow.js +24 -0
  101. package/packages/contracts/dist/workflow.js.map +1 -1
  102. package/packages/contracts/src/rpc.test.ts +8 -0
  103. package/packages/contracts/src/workflow.ts +24 -0
  104. package/packages/daemon/package.json +2 -2
  105. package/packages/mcp-server/README.md +14 -3
  106. package/packages/mcp-server/dist/workflow-tools.d.ts +0 -3
  107. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  108. package/packages/mcp-server/dist/workflow-tools.js +80 -0
  109. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  110. package/packages/mcp-server/package.json +2 -2
  111. package/packages/mcp-server/src/workflow-tools-parity.test.ts +244 -0
  112. package/packages/mcp-server/src/workflow-tools.test.ts +22 -0
  113. package/packages/mcp-server/src/workflow-tools.ts +168 -0
  114. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  115. package/packages/native/package.json +1 -1
  116. package/packages/pi-agent-core/package.json +1 -1
  117. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  118. package/packages/pi-ai/dist/index.d.ts +2 -2
  119. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  120. package/packages/pi-ai/dist/index.js +1 -1
  121. package/packages/pi-ai/dist/index.js.map +1 -1
  122. package/packages/pi-ai/dist/providers/transform-messages.d.ts +11 -0
  123. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  124. package/packages/pi-ai/dist/providers/transform-messages.js +20 -0
  125. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  126. package/packages/pi-ai/package.json +1 -1
  127. package/packages/pi-ai/src/index.ts +7 -2
  128. package/packages/pi-ai/src/providers/transform-messages.ts +24 -0
  129. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  130. package/packages/pi-coding-agent/dist/core/system-prompt.js +4 -4
  131. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts +2 -0
  133. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.d.ts.map +1 -0
  134. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js +17 -0
  135. package/packages/pi-coding-agent/dist/tests/system-prompt-file-safety.test.js.map +1 -0
  136. package/packages/pi-coding-agent/package.json +1 -1
  137. package/packages/pi-coding-agent/src/core/system-prompt.ts +4 -4
  138. package/packages/pi-coding-agent/src/tests/system-prompt-file-safety.test.ts +22 -0
  139. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  140. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  141. package/packages/pi-tui/dist/tui.js +5 -0
  142. package/packages/pi-tui/dist/tui.js.map +1 -1
  143. package/packages/pi-tui/package.json +1 -1
  144. package/packages/pi-tui/src/tui.ts +6 -0
  145. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  146. package/packages/rpc-client/package.json +1 -1
  147. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  148. package/pkg/package.json +1 -1
  149. package/src/resources/GSD-WORKFLOW.md +3 -1
  150. package/src/resources/extensions/browser-tools/tools/screenshot.ts +1 -0
  151. package/src/resources/extensions/browser-tools/tools/zoom.ts +1 -0
  152. package/src/resources/extensions/cmux/index.ts +6 -0
  153. package/src/resources/extensions/gsd/auto/contracts.ts +46 -11
  154. package/src/resources/extensions/gsd/auto/orchestrator.ts +118 -6
  155. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  156. package/src/resources/extensions/gsd/auto-post-unit.ts +194 -137
  157. package/src/resources/extensions/gsd/auto-prompts.ts +13 -5
  158. package/src/resources/extensions/gsd/auto-verification.ts +36 -34
  159. package/src/resources/extensions/gsd/auto.ts +136 -51
  160. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +6 -0
  161. package/src/resources/extensions/gsd/bootstrap/subagent-input.ts +16 -6
  162. package/src/resources/extensions/gsd/bootstrap/system-context.ts +58 -15
  163. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +3 -2
  164. package/src/resources/extensions/gsd/clean-root-preflight.ts +174 -8
  165. package/src/resources/extensions/gsd/commands/catalog.ts +4 -1
  166. package/src/resources/extensions/gsd/commands/handlers/core.ts +25 -1
  167. package/src/resources/extensions/gsd/commands-bootstrap.ts +10 -0
  168. package/src/resources/extensions/gsd/commands-handlers.ts +19 -2
  169. package/src/resources/extensions/gsd/context-store.ts +120 -1
  170. package/src/resources/extensions/gsd/db-writer.ts +167 -84
  171. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  172. package/src/resources/extensions/gsd/doctor-git-checks.ts +44 -6
  173. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  174. package/src/resources/extensions/gsd/knowledge-backfill.ts +164 -0
  175. package/src/resources/extensions/gsd/knowledge-capture.ts +160 -0
  176. package/src/resources/extensions/gsd/knowledge-parser.ts +174 -0
  177. package/src/resources/extensions/gsd/knowledge-projection.ts +241 -0
  178. package/src/resources/extensions/gsd/markdown-renderer.ts +6 -1
  179. package/src/resources/extensions/gsd/md-importer.ts +1 -1
  180. package/src/resources/extensions/gsd/memory-backfill.ts +89 -17
  181. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +277 -0
  182. package/src/resources/extensions/gsd/migrate/command.ts +5 -0
  183. package/src/resources/extensions/gsd/migrate/preview.ts +10 -0
  184. package/src/resources/extensions/gsd/migrate/transformer.ts +58 -4
  185. package/src/resources/extensions/gsd/migrate/writer.ts +14 -1
  186. package/src/resources/extensions/gsd/prompts/system.md +2 -2
  187. package/src/resources/extensions/gsd/provider-switch-observer.ts +185 -0
  188. package/src/resources/extensions/gsd/templates/knowledge.md +2 -2
  189. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +75 -0
  190. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +408 -4
  191. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +6 -5
  192. package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +4 -4
  193. package/src/resources/extensions/gsd/tests/brief-command.test.ts +89 -0
  194. package/src/resources/extensions/gsd/tests/browser-tools-compatibility-declarations.test.ts +62 -0
  195. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +107 -2
  196. package/src/resources/extensions/gsd/tests/closeout-git-deferral.test.ts +16 -0
  197. package/src/resources/extensions/gsd/tests/context-store-decisions-from-memories.test.ts +312 -0
  198. package/src/resources/extensions/gsd/tests/db-writer.test.ts +13 -8
  199. package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +453 -0
  200. package/src/resources/extensions/gsd/tests/decisions-stop-table-writes.test.ts +348 -0
  201. package/src/resources/extensions/gsd/tests/evidence-cross-ref.test.ts +38 -0
  202. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +8 -4
  203. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +11 -7
  204. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +44 -0
  205. package/src/resources/extensions/gsd/tests/integration/integration-lifecycle.test.ts +13 -5
  206. package/src/resources/extensions/gsd/tests/integration/migrate-command.test.ts +48 -3
  207. package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +323 -0
  208. package/src/resources/extensions/gsd/tests/knowledge-capture.test.ts +242 -0
  209. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -2
  210. package/src/resources/extensions/gsd/tests/load-knowledge-block-rules-only.test.ts +209 -0
  211. package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +316 -0
  212. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +5 -1
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +6 -1
  214. package/src/resources/extensions/gsd/tests/plan-milestone-sketch-render.test.ts +157 -0
  215. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +79 -1
  216. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -0
  217. package/src/resources/extensions/gsd/tests/provider-switch-observer.test.ts +252 -0
  218. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +16 -4
  219. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +6 -0
  220. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +78 -0
  222. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +25 -0
  223. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +16 -0
  224. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +135 -0
  225. package/src/resources/extensions/gsd/unit-context-manifest.ts +35 -2
  226. package/src/resources/extensions/gsd/verification-verdict.ts +47 -0
  227. package/src/resources/extensions/gsd/workflow-logger.ts +4 -0
  228. package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -2
  229. package/src/resources/extensions/subagent/index.ts +567 -103
  230. package/src/resources/extensions/subagent/launch.ts +131 -0
  231. package/src/resources/extensions/subagent/run-store.ts +218 -0
  232. package/src/resources/extensions/subagent/tests/launch.test.ts +115 -0
  233. package/src/resources/extensions/subagent/tests/run-store.test.ts +111 -0
  234. package/src/resources/extensions/visual-brief/artifact-policy.ts +41 -0
  235. package/src/resources/extensions/visual-brief/extension-manifest.json +8 -0
  236. package/src/resources/extensions/visual-brief/index.ts +8 -0
  237. package/src/resources/extensions/visual-brief/page-contract.ts +134 -0
  238. package/src/resources/extensions/visual-brief/prompts.ts +147 -0
  239. package/src/resources/extensions/visual-brief/tests/visual-brief.test.ts +172 -0
  240. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  241. package/dist/web/standalone/.next/static/css/54ec2745c1da488b.css +0 -1
  242. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_buildManifest.js +0 -0
  243. /package/dist/web/standalone/.next/static/{rIkMv4YSNlfSeqmGqWVns → Qgr2B_MRhPxC0z8fwv4vT}/_ssgManifest.js +0 -0
@@ -0,0 +1,348 @@
1
+ // ADR-013 Phase 6 cutover (Stage 3) — destructive lock-in.
2
+ //
3
+ // After this stage, new decisions written via gsd_save_decision land ONLY in
4
+ // the memories table. The decisions table receives no new rows; its existing
5
+ // rows remain for backwards-compat reads until #5756 drops the table.
6
+ //
7
+ // Tests cover five properties:
8
+ // 1. New saveDecisionToDb call does NOT insert into the decisions table
9
+ // 2. The new decision DOES appear as a memory row with sourceDecisionId
10
+ // 3. The rendered DECISIONS.md projection includes the new decision
11
+ // 4. Pre-existing decisions-table rows are untouched
12
+ // 5. Decision IDs are monotonic across both surfaces (max of either + 1)
13
+
14
+ import test from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ mkdtempSync,
20
+ readFileSync,
21
+ rmSync,
22
+ } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { tmpdir } from "node:os";
25
+
26
+ import {
27
+ _getAdapter,
28
+ closeDatabase,
29
+ insertDecision,
30
+ openDatabase,
31
+ } from "../gsd-db.ts";
32
+ import { saveDecisionToDb } from "../db-writer.ts";
33
+ import {
34
+ getAllDecisionsFromMemories,
35
+ queryDecisions,
36
+ } from "../context-store.ts";
37
+
38
+ function makeTmpBase(): string {
39
+ const base = mkdtempSync(join(tmpdir(), "gsd-decisions-stage3-"));
40
+ mkdirSync(join(base, ".gsd"), { recursive: true });
41
+ openDatabase(join(base, ".gsd", "gsd.db"));
42
+ return base;
43
+ }
44
+
45
+ function cleanup(base: string): void {
46
+ try {
47
+ closeDatabase();
48
+ } catch {
49
+ /* noop */
50
+ }
51
+ try {
52
+ rmSync(base, { recursive: true, force: true });
53
+ } catch {
54
+ /* noop */
55
+ }
56
+ }
57
+
58
+ function countDecisionRows(): number {
59
+ const adapter = _getAdapter();
60
+ if (!adapter) return 0;
61
+ const row = adapter
62
+ .prepare("SELECT COUNT(*) as n FROM decisions")
63
+ .get() as { n: number };
64
+ return row.n;
65
+ }
66
+
67
+ function seedLegacyDecision(args: {
68
+ id: string;
69
+ when_context: string;
70
+ scope: string;
71
+ decision: string;
72
+ choice: string;
73
+ rationale: string;
74
+ superseded_by?: string | null;
75
+ }): void {
76
+ insertDecision({
77
+ id: args.id,
78
+ when_context: args.when_context,
79
+ scope: args.scope,
80
+ decision: args.decision,
81
+ choice: args.choice,
82
+ rationale: args.rationale,
83
+ revisable: "Yes",
84
+ made_by: "agent",
85
+ superseded_by: args.superseded_by ?? null,
86
+ });
87
+ }
88
+
89
+ // ─── Core invariants ────────────────────────────────────────────────────────
90
+
91
+ test("saveDecisionToDb no longer writes to the decisions table", async () => {
92
+ const base = makeTmpBase();
93
+ try {
94
+ const before = countDecisionRows();
95
+ assert.equal(before, 0);
96
+
97
+ await saveDecisionToDb(
98
+ {
99
+ when_context: "M001 discuss",
100
+ scope: "M001",
101
+ decision: "Stage 3 destructive: memories-only writes",
102
+ choice: "Drop the upsertDecision call",
103
+ rationale: "Cutover step required by ADR-013 Phase 6",
104
+ revisable: "Yes",
105
+ made_by: "agent",
106
+ },
107
+ base,
108
+ );
109
+
110
+ const after = countDecisionRows();
111
+ assert.equal(after, 0, "decisions table must remain empty after Stage 3 save");
112
+ } finally {
113
+ cleanup(base);
114
+ }
115
+ });
116
+
117
+ test("saveDecisionToDb writes the new decision into memories with sourceDecisionId", async () => {
118
+ const base = makeTmpBase();
119
+ try {
120
+ const { id } = await saveDecisionToDb(
121
+ {
122
+ when_context: "M001 discuss",
123
+ scope: "M001",
124
+ decision: "Use atomic file writes",
125
+ choice: "atomicWriteSync",
126
+ rationale: "crash-safe markdown projections",
127
+ revisable: "Yes",
128
+ made_by: "agent",
129
+ },
130
+ base,
131
+ );
132
+
133
+ const fromMemories = getAllDecisionsFromMemories();
134
+ assert.equal(fromMemories.length, 1);
135
+ assert.equal(fromMemories[0]?.id, id);
136
+ assert.equal(fromMemories[0]?.decision, "Use atomic file writes");
137
+ } finally {
138
+ cleanup(base);
139
+ }
140
+ });
141
+
142
+ test("DECISIONS.md projection includes the memory-only decision", async () => {
143
+ const base = makeTmpBase();
144
+ try {
145
+ await saveDecisionToDb(
146
+ {
147
+ when_context: "M001 discuss",
148
+ scope: "M001",
149
+ decision: "Adopt SQLite",
150
+ choice: "better-sqlite3",
151
+ rationale: "synchronous + native",
152
+ revisable: "Yes",
153
+ made_by: "agent",
154
+ },
155
+ base,
156
+ );
157
+
158
+ const md = readFileSync(join(base, ".gsd", "DECISIONS.md"), "utf-8");
159
+ assert.match(md, /\| D001 \|/);
160
+ assert.match(md, /Adopt SQLite/);
161
+ assert.match(md, /# Decisions Register/);
162
+ } finally {
163
+ cleanup(base);
164
+ }
165
+ });
166
+
167
+ // ─── Existing decisions table is preserved ──────────────────────────────────
168
+
169
+ test("pre-existing decisions-table rows are untouched by Stage 3 saves", async () => {
170
+ const base = makeTmpBase();
171
+ try {
172
+ // Seed two legacy decisions directly into the table — simulates a
173
+ // pre-cutover project.
174
+ seedLegacyDecision({
175
+ id: "D001",
176
+ when_context: "M001 historical",
177
+ scope: "M001",
178
+ decision: "Legacy decision one",
179
+ choice: "A",
180
+ rationale: "r1",
181
+ });
182
+ seedLegacyDecision({
183
+ id: "D002",
184
+ when_context: "M001 historical",
185
+ scope: "M001",
186
+ decision: "Legacy decision two",
187
+ choice: "B",
188
+ rationale: "r2",
189
+ });
190
+ assert.equal(countDecisionRows(), 2);
191
+
192
+ // New save via the cutover path.
193
+ await saveDecisionToDb(
194
+ {
195
+ when_context: "M001 today",
196
+ scope: "M001",
197
+ decision: "Post-cutover decision",
198
+ choice: "C",
199
+ rationale: "r3",
200
+ revisable: "Yes",
201
+ made_by: "agent",
202
+ },
203
+ base,
204
+ );
205
+
206
+ // Decisions table unchanged.
207
+ assert.equal(countDecisionRows(), 2);
208
+
209
+ // Legacy reads still see the pre-existing rows.
210
+ const fromLegacy = queryDecisions();
211
+ assert.equal(fromLegacy.length, 2);
212
+ assert.deepEqual(
213
+ fromLegacy.map((d) => d.id).sort(),
214
+ ["D001", "D002"],
215
+ );
216
+ } finally {
217
+ cleanup(base);
218
+ }
219
+ });
220
+
221
+ // ─── Cross-surface ID monotonicity ─────────────────────────────────────────
222
+
223
+ test("decision IDs are monotonic across legacy table + memory surfaces", async () => {
224
+ const base = makeTmpBase();
225
+ try {
226
+ // Pre-cutover project with D001..D005 in the legacy table (no memory
227
+ // backfill yet — simulates an upgrade that hasn't run backfill).
228
+ for (let i = 1; i <= 5; i++) {
229
+ seedLegacyDecision({
230
+ id: `D${String(i).padStart(3, "0")}`,
231
+ when_context: "historical",
232
+ scope: "M001",
233
+ decision: `Decision ${i}`,
234
+ choice: "x",
235
+ rationale: "y",
236
+ });
237
+ }
238
+ assert.equal(countDecisionRows(), 5);
239
+
240
+ // First Stage 3 save: must pick the next-after-table ID.
241
+ const a = await saveDecisionToDb(
242
+ {
243
+ when_context: "M001 today",
244
+ scope: "M001",
245
+ decision: "First post-cutover",
246
+ choice: "A",
247
+ rationale: "r",
248
+ revisable: "Yes",
249
+ made_by: "agent",
250
+ },
251
+ base,
252
+ );
253
+ assert.equal(a.id, "D006", "first cutover save must follow the legacy table max (D005 -> D006)");
254
+
255
+ // Second save: must pick the next-after-memory ID.
256
+ const b = await saveDecisionToDb(
257
+ {
258
+ when_context: "M001 today",
259
+ scope: "M001",
260
+ decision: "Second post-cutover",
261
+ choice: "B",
262
+ rationale: "r",
263
+ revisable: "Yes",
264
+ made_by: "agent",
265
+ },
266
+ base,
267
+ );
268
+ assert.equal(b.id, "D007", "second cutover save must follow memory max (D006 -> D007)");
269
+
270
+ // Decisions table still has only the original five.
271
+ assert.equal(countDecisionRows(), 5);
272
+ } finally {
273
+ cleanup(base);
274
+ }
275
+ });
276
+
277
+ test("next-ID falls back to D001 when both surfaces are empty", async () => {
278
+ const base = makeTmpBase();
279
+ try {
280
+ const { id } = await saveDecisionToDb(
281
+ {
282
+ when_context: "M001",
283
+ scope: "M001",
284
+ decision: "First ever",
285
+ choice: "A",
286
+ rationale: "r",
287
+ revisable: "Yes",
288
+ made_by: "agent",
289
+ },
290
+ base,
291
+ );
292
+ assert.equal(id, "D001");
293
+ } finally {
294
+ cleanup(base);
295
+ }
296
+ });
297
+
298
+ test("next-ID skips beyond memory-only entries when the legacy table is empty", async () => {
299
+ const base = makeTmpBase();
300
+ try {
301
+ // Three memory-only saves on a fresh project.
302
+ const a = await saveDecisionToDb(
303
+ { when_context: "M001", scope: "M001", decision: "A", choice: "1", rationale: "r", revisable: "Yes", made_by: "agent" },
304
+ base,
305
+ );
306
+ const b = await saveDecisionToDb(
307
+ { when_context: "M001", scope: "M001", decision: "B", choice: "2", rationale: "r", revisable: "Yes", made_by: "agent" },
308
+ base,
309
+ );
310
+ const c = await saveDecisionToDb(
311
+ { when_context: "M001", scope: "M001", decision: "C", choice: "3", rationale: "r", revisable: "Yes", made_by: "agent" },
312
+ base,
313
+ );
314
+
315
+ assert.deepEqual([a.id, b.id, c.id], ["D001", "D002", "D003"]);
316
+ assert.equal(countDecisionRows(), 0, "no decisions-table writes despite three saves");
317
+ } finally {
318
+ cleanup(base);
319
+ }
320
+ });
321
+
322
+ // ─── Defensive: malformed structured_fields don't break next-ID ────────────
323
+
324
+ test("malformed structuredFields in memories are silently skipped during next-ID computation", async () => {
325
+ const base = makeTmpBase();
326
+ try {
327
+ // Insert a memory with broken JSON in structured_fields. The next-ID
328
+ // logic must not throw.
329
+ const adapter = _getAdapter();
330
+ assert.ok(adapter);
331
+ adapter
332
+ .prepare(
333
+ "INSERT INTO memories (id, category, content, confidence, created_at, updated_at, structured_fields) VALUES ('mem-broken', 'architecture', 'oops', 0.5, datetime('now'), datetime('now'), '{\"sourceDecisionId\":\"D999\"')",
334
+ )
335
+ .run();
336
+
337
+ const { id } = await saveDecisionToDb(
338
+ { when_context: "M001", scope: "M001", decision: "Survivor", choice: "x", rationale: "y", revisable: "Yes", made_by: "agent" },
339
+ base,
340
+ );
341
+
342
+ // Broken JSON for D999 doesn't parse, so the surfaced max stays at 0.
343
+ // The new save must still produce a valid sequential ID.
344
+ assert.equal(id, "D001");
345
+ } finally {
346
+ cleanup(base);
347
+ }
348
+ });
@@ -0,0 +1,38 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Tests for verification evidence cross-reference mismatch policy.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { crossReferenceEvidence } from "../safety/evidence-cross-ref.ts";
8
+ import type { EvidenceEntry } from "../safety/evidence-collector.ts";
9
+
10
+ test("claims of passing verification become errors when recorded bash evidence failed", () => {
11
+ const mismatches = crossReferenceEvidence(
12
+ [{ command: "npm test", exitCode: 0, verdict: "passed" }],
13
+ [
14
+ {
15
+ kind: "bash",
16
+ toolCallId: "call-1",
17
+ command: "npm test",
18
+ exitCode: 1,
19
+ outputSnippet: "failed",
20
+ timestamp: Date.now(),
21
+ },
22
+ ] as EvidenceEntry[],
23
+ );
24
+
25
+ assert.equal(mismatches.length, 1);
26
+ assert.equal(mismatches[0].severity, "error");
27
+ assert.match(mismatches[0].reason, /Claimed exitCode=0/);
28
+ });
29
+
30
+ test("missing recorded bash evidence remains a warning", () => {
31
+ const mismatches = crossReferenceEvidence(
32
+ [{ command: "npm test", exitCode: 0, verdict: "passed" }],
33
+ [],
34
+ );
35
+
36
+ assert.equal(mismatches.length, 1);
37
+ assert.equal(mismatches[0].severity, "warning");
38
+ });
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  saveDecisionToDb,
16
16
  } from '../db-writer.ts';
17
+ import { getAllDecisionsFromMemories } from '../context-store.ts';
17
18
 
18
19
  // ═══════════════════════════════════════════════════════════════════════════
19
20
  // Helpers
@@ -31,11 +32,14 @@ function cleanupDir(dir: string): void {
31
32
  } catch { /* swallow */ }
32
33
  }
33
34
 
34
- /** Query all decisions from the DB ordered by seq. */
35
+ /**
36
+ * Query all decisions. ADR-013 Stage 3 (PR #5755): decisions are written
37
+ * exclusively to memories; this helper returns the same Decision shape the
38
+ * previous SELECT * FROM decisions produced so the assertions below stay
39
+ * one-line lookups.
40
+ */
35
41
  function queryAllDecisions(): Array<Record<string, unknown>> {
36
- const adapter = _getAdapter();
37
- if (!adapter) return [];
38
- return adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
42
+ return getAllDecisionsFromMemories().map((d) => ({ ...d }));
39
43
  }
40
44
 
41
45
  // ═══════════════════════════════════════════════════════════════════════════
@@ -14,7 +14,6 @@ import {
14
14
  isDbAvailable,
15
15
  upsertRequirement,
16
16
  getRequirementById,
17
- getDecisionById,
18
17
  _getAdapter,
19
18
  insertArtifact,
20
19
  } from '../gsd-db.ts';
@@ -26,6 +25,7 @@ import {
26
25
  nextDecisionId,
27
26
  nextRequirementId,
28
27
  } from '../db-writer.ts';
28
+ import { getAllDecisionsFromMemories } from '../context-store.ts';
29
29
  import type { Requirement } from '../types.ts';
30
30
 
31
31
  // ═══════════════════════════════════════════════════════════════════════════
@@ -73,12 +73,16 @@ describe('gsd-tools', () => {
73
73
 
74
74
  assert.deepStrictEqual(result.id, 'D001', 'First decision should be D001');
75
75
 
76
- // Verify DB row exists
77
- const row = getDecisionById('D001');
78
- assert.ok(row !== null, 'Decision D001 should exist in DB');
79
- assert.deepStrictEqual(row!.scope, 'architecture', 'Decision scope should match');
80
- assert.deepStrictEqual(row!.decision, 'Use SQLite for metadata', 'Decision text should match');
81
- assert.deepStrictEqual(row!.choice, 'SQLite', 'Decision choice should match');
76
+ // ADR-013 Stage 3: decisions land in memories, not the legacy decisions
77
+ // table. Verify the memory row carries the same content.
78
+ const memoryDecisions = getAllDecisionsFromMemories();
79
+ assert.equal(memoryDecisions.length, 1, 'one memory row exists after save');
80
+ const memDecision = memoryDecisions[0];
81
+ assert.ok(memDecision, 'memory decision exists after save');
82
+ assert.equal(memDecision.id, 'D001');
83
+ assert.deepStrictEqual(memDecision.scope, 'architecture', 'memory decision scope should match');
84
+ assert.deepStrictEqual(memDecision.decision, 'Use SQLite for metadata', 'memory decision text should match');
85
+ assert.deepStrictEqual(memDecision.choice, 'SQLite', 'memory decision choice should match');
82
86
 
83
87
  // Verify DECISIONS.md was generated
84
88
  const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
@@ -1,3 +1,4 @@
1
+ // GSD-2 doctor git integration tests
1
2
  import { describe, test } from 'node:test';
2
3
  import assert from 'node:assert/strict';
3
4
  /**
@@ -761,6 +762,49 @@ describe('doctor-git', async () => {
761
762
  assert.ok(log.includes("gsd snapshot"), "commit is tagged with gsd snapshot");
762
763
  });
763
764
 
765
+ test('stale_uncommitted_changes (skips snapshot when tracked changes contain conflict markers)', async () => {
766
+ const dir = createRepoWithActiveMilestone();
767
+ cleanups.push(dir);
768
+
769
+ const pastDate = new Date(Date.now() - 45 * 60 * 1000).toISOString();
770
+ run(`git commit --amend --no-edit --date="${pastDate}"`, dir);
771
+ execSync(`git commit --amend --no-edit`, {
772
+ cwd: dir,
773
+ stdio: ["ignore", "pipe", "pipe"],
774
+ encoding: "utf-8",
775
+ env: { ...process.env, GIT_COMMITTER_DATE: pastDate },
776
+ });
777
+
778
+ writeFileSync(join(dir, "README.md"), [
779
+ "# test",
780
+ "<<<<<<< Updated upstream",
781
+ "modified content",
782
+ "=======",
783
+ "stashed content",
784
+ ">>>>>>> Stashed changes",
785
+ "",
786
+ ].join("\n"));
787
+
788
+ const commitsBefore = run("git rev-list --count HEAD", dir);
789
+ const fixed = await runGSDDoctor(dir, { fix: true });
790
+ const conflictIssues = fixed.issues.filter(
791
+ i => i.code === ("conflict_markers_in_tracked_files" as typeof i.code),
792
+ );
793
+
794
+ assert.equal(conflictIssues.length, 1, "detects conflict markers before snapshotting");
795
+ assert.equal(conflictIssues[0]?.severity, "error", "conflict marker issue blocks automation");
796
+ assert.equal(conflictIssues[0]?.fixable, false, "conflict marker issue requires manual resolution");
797
+ assert.ok(
798
+ fixed.fixesApplied.some(f => f.includes("gsd snapshot skipped")),
799
+ "fix reports skipped snapshot",
800
+ );
801
+
802
+ const commitsAfter = run("git rev-list --count HEAD", dir);
803
+ assert.equal(commitsAfter, commitsBefore, "no snapshot commit is created");
804
+ assert.equal(run("git diff --cached --name-only", dir), "", "no files are staged");
805
+ assert.match(run("git status --short", dir), /M README\.md/m, "tracked file remains modified");
806
+ });
807
+
764
808
  // ─── Test: stale_uncommitted_changes NOT flagged when recent commit ──
765
809
  test('stale_uncommitted_changes (no false positive on recent commit)', async () => {
766
810
  const dir = createRepoWithActiveMilestone();
@@ -17,10 +17,12 @@ import { migrateFromMarkdown, parseDecisionsTable } from '../../md-importer.ts';
17
17
  import {
18
18
  queryDecisions,
19
19
  queryRequirements,
20
+ getAllDecisionsFromMemories,
20
21
  formatDecisionsForPrompt,
21
22
  formatRequirementsForPrompt,
22
23
  } from '../../context-store.ts';
23
24
  import { saveDecisionToDb, generateDecisionsMd } from '../../db-writer.ts';
25
+ import { backfillDecisionsToMemories } from '../../memory-backfill.ts';
24
26
  import { describe, test, beforeEach, afterEach } from 'node:test';
25
27
  import assert from 'node:assert/strict';
26
28
 
@@ -233,8 +235,8 @@ test('integration-lifecycle: full pipeline', async () => {
233
235
  assert.ok(typeof saved.id === 'string', 'lifecycle: saveDecisionToDb returned an id');
234
236
  assert.match(saved.id, /^D\d+$/, 'lifecycle: saved ID matches D### pattern');
235
237
 
236
- // Query back from DB
237
- const allAfterSave = queryDecisions();
238
+ // Query back from DB (memories — Stage 3 of ADR-013 stopped legacy decisions-table writes)
239
+ const allAfterSave = getAllDecisionsFromMemories();
238
240
  const savedDecision = allAfterSave.find(d => d.id === saved.id);
239
241
  assert.ok(savedDecision !== null && savedDecision !== undefined, `lifecycle: saved decision ${saved.id} found in DB`);
240
242
  assert.deepStrictEqual(savedDecision?.decision, 'integration test write-back decision', 'lifecycle: saved decision text matches');
@@ -253,9 +255,15 @@ test('integration-lifecycle: full pipeline', async () => {
253
255
  assert.deepStrictEqual(reparsedSaved?.rationale, 'proves round-trip fidelity', 'lifecycle: round-trip rationale preserved');
254
256
 
255
257
  // ── Step 8: DB consistency — total count sanity ─────────────────────
256
- const finalCount = queryDecisions().length;
257
- // Original 14 + 1 re-import + 1 saveDecisionToDb = 16
258
- assert.ok(finalCount === DECISIONS_COUNT + 2, `lifecycle: final DB count = ${DECISIONS_COUNT + 2} (got ${finalCount})`);
258
+ // ADR-013 Stage 3 split the write paths: migrateFromMarkdown still
259
+ // populates the legacy decisions table, but saveDecisionToDb now writes
260
+ // only to memories. The unified projection (and the post-#5756 cutover
261
+ // end-state) lives in memories, so simulate what bootstrap does and
262
+ // backfill the table rows into memories before counting.
263
+ backfillDecisionsToMemories();
264
+ const finalCount = getAllDecisionsFromMemories().length;
265
+ // Original 14 + 1 re-import (decisions table → memories via backfill) + 1 saveDecisionToDb = 16
266
+ assert.ok(finalCount === DECISIONS_COUNT + 2, `lifecycle: final memory-store count = ${DECISIONS_COUNT + 2} (got ${finalCount})`);
259
267
 
260
268
  closeDatabase();
261
269
  } finally {
@@ -14,7 +14,9 @@ import {
14
14
  generatePreview,
15
15
  writeGSDDirectory,
16
16
  } from '../../migrate/index.ts';
17
+ import { importWrittenMigrationToDb } from '../../migrate/command.ts';
17
18
  import { deriveState } from '../../state.ts';
19
+ import { closeDatabase, getDecisionById, getRequirementCounts } from '../../gsd-db.ts';
18
20
  import { describe, test, beforeEach, afterEach } from 'node:test';
19
21
  import assert from 'node:assert/strict';
20
22
 
@@ -52,6 +54,16 @@ const SAMPLE_REQUIREMENTS = `# Requirements
52
54
  - Description: Output matches GSD format.
53
55
  `;
54
56
 
57
+ const SAMPLE_REQUIREMENTS_LEGACY_IDS = `# Requirements
58
+
59
+ ## Active
60
+
61
+ - [ ] **CORE-PIPELINE**: Pipeline must work end-to-end.
62
+ - [ ] **OUTPUT-FORMAT**: Output matches GSD format.
63
+ - [ ] **IMPORT-DB**: Migration imports requirements into the DB.
64
+ - [ ] **STATUS-WIDGET**: Status can query migrated requirements.
65
+ `;
66
+
55
67
  const SAMPLE_STATE = `# State
56
68
 
57
69
  **Current Phase:** 20-features
@@ -166,14 +178,14 @@ Depends on foundation work.
166
178
  </context>
167
179
  `;
168
180
 
169
- function createCompleteFixture(): string {
181
+ function createCompleteFixture(requirementsContent: string = SAMPLE_REQUIREMENTS): string {
170
182
  const base = mkdtempSync(join(tmpdir(), 'gsd-cmd-test-'));
171
183
  const planning = join(base, '.planning');
172
184
  mkdirSync(planning, { recursive: true });
173
185
 
174
186
  writeFileSync(join(planning, 'PROJECT.md'), SAMPLE_PROJECT);
175
187
  writeFileSync(join(planning, 'ROADMAP.md'), SAMPLE_ROADMAP);
176
- writeFileSync(join(planning, 'REQUIREMENTS.md'), SAMPLE_REQUIREMENTS);
188
+ writeFileSync(join(planning, 'REQUIREMENTS.md'), requirementsContent);
177
189
  writeFileSync(join(planning, 'STATE.md'), SAMPLE_STATE);
178
190
  writeFileSync(join(planning, 'config.json'), SAMPLE_CONFIG);
179
191
 
@@ -303,6 +315,7 @@ test('Full pipeline: parse → transform → preview → write → deriveState',
303
315
  const expectedTaskPct = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;
304
316
  assert.deepStrictEqual(preview.sliceCompletionPct, expectedSlicePct, 'pipeline: preview sliceCompletionPct');
305
317
  assert.deepStrictEqual(preview.taskCompletionPct, expectedTaskPct, 'pipeline: preview taskCompletionPct');
318
+ assert.deepStrictEqual(preview.decisions.total, 1, 'pipeline: preview decisions total');
306
319
 
307
320
  // Requirements in preview
308
321
  assert.deepStrictEqual(preview.requirements.active, 1, 'pipeline: preview requirements active');
@@ -342,6 +355,39 @@ test('Full pipeline: parse → transform → preview → write → deriveState',
342
355
  }
343
356
  });
344
357
 
358
+ test('Full pipeline: legacy requirement IDs import into DB with canonical IDs', async () => {
359
+ const base = createCompleteFixture(SAMPLE_REQUIREMENTS_LEGACY_IDS);
360
+ const writeTarget = mkdtempSync(join(tmpdir(), 'gsd-cmd-legacy-reqs-'));
361
+ try {
362
+ const parsed = await parsePlanningDirectory(join(base, '.planning'));
363
+ const project = transformToGSD(parsed);
364
+ const preview = generatePreview(project);
365
+
366
+ assert.deepStrictEqual(
367
+ project.requirements.map((req) => req.id),
368
+ ['R001', 'R002', 'R003', 'R004'],
369
+ 'legacy-reqs: transform assigns canonical R IDs',
370
+ );
371
+ assert.ok(
372
+ project.requirements[0]?.description.includes('Legacy ID: CORE-PIPELINE'),
373
+ 'legacy-reqs: original ID survives in migrated requirement content',
374
+ );
375
+
376
+ await writeGSDDirectory(project, writeTarget);
377
+ const imported = await importWrittenMigrationToDb(writeTarget, preview);
378
+ const counts = getRequirementCounts();
379
+
380
+ assert.deepStrictEqual(imported.decisions, 1, 'legacy-reqs: DB import includes migrated decisions');
381
+ assert.deepStrictEqual(imported.requirements, 4, 'legacy-reqs: DB import count matches preview');
382
+ assert.ok(getDecisionById('D001') !== null, 'legacy-reqs: migrated decision is queryable');
383
+ assert.deepStrictEqual(counts.total, 4, 'legacy-reqs: DB stores all migrated requirements');
384
+ } finally {
385
+ closeDatabase();
386
+ rmSync(base, { recursive: true, force: true });
387
+ rmSync(writeTarget, { recursive: true, force: true });
388
+ }
389
+ });
390
+
345
391
  // ─── Test 6: .gsd/ exists detection ────────────────────────────────────
346
392
 
347
393
  test('.gsd/ exists detection', () => {
@@ -357,4 +403,3 @@ test('.gsd/ exists detection', () => {
357
403
  rmSync(base, { recursive: true, force: true });
358
404
  }
359
405
  });
360
-