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
@@ -8,7 +8,7 @@
8
8
 
9
9
  import test from "node:test";
10
10
  import assert from "node:assert/strict";
11
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, realpathSync } from "node:fs";
11
+ import { existsSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, realpathSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { tmpdir } from "node:os";
14
14
  import { execSync } from "node:child_process";
@@ -208,8 +208,8 @@ test("postflightPopStash conflict warning names the exact stash ref", () => {
208
208
  assert.match(postflight.message, /failed after merge of milestone M005C/);
209
209
 
210
210
  const warning = notifications.find((n) => n.level === "warning")?.msg ?? "";
211
- assert.match(warning, /git stash pop stash@\{\d+\}/);
212
211
  assert.match(warning, /git stash apply stash@\{\d+\}/);
212
+ assert.match(warning, /git stash drop stash@\{\d+\}/);
213
213
  } finally {
214
214
  try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
215
215
  }
@@ -279,3 +279,108 @@ test("postflightPopStash falls back to milestone marker prefix when exact marker
279
279
  try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
280
280
  }
281
281
  });
282
+
283
+ test("postflightPopStash preserves stash when untracked collision differs from merged file", () => {
284
+ const repo = createTempRepo();
285
+ try {
286
+ writeFileSync(join(repo, "tests.txt"), "local preflight test\n");
287
+ const preflight = preflightCleanRoot(repo, "M009", () => {});
288
+ assert.equal(preflight.stashPushed, true, "preflight must stash untracked file");
289
+
290
+ writeFileSync(join(repo, "tests.txt"), "merged milestone test\n");
291
+ run("git add tests.txt", repo);
292
+ run('git commit -m "feat: add merged test"', repo);
293
+
294
+ const notifications: Array<{ msg: string; level: string }> = [];
295
+ const postflight = postflightPopStash(repo, "M009", preflight.stashMarker, (msg, level) => {
296
+ notifications.push({ msg, level });
297
+ });
298
+
299
+ assert.equal(postflight.needsManualRecovery, false, "different already-present untracked files must not stop auto-mode");
300
+ assert.equal(postflight.resolution, "already-present-preserved");
301
+ assert.deepEqual(postflight.collidedPaths, ["tests.txt"]);
302
+ assert.equal(readFileSync(join(repo, "tests.txt"), "utf-8"), "merged milestone test\n");
303
+ assert.equal(run("git status --porcelain", repo), "", "merged file must stay clean");
304
+
305
+ const stashList = run("git stash list", repo);
306
+ assert.ok(preflight.stashMarker && stashList.includes(preflight.stashMarker), "stash backup must be preserved");
307
+ assert.ok(
308
+ notifications.some((n) => n.level === "warning" && n.msg.includes("preserving")),
309
+ "user must be warned that the stash was preserved as backup",
310
+ );
311
+ } finally {
312
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
313
+ }
314
+ });
315
+
316
+ test("postflightPopStash drops stash when untracked collision is identical to merged file", () => {
317
+ const repo = createTempRepo();
318
+ try {
319
+ writeFileSync(join(repo, "tests.txt"), "same test content\n");
320
+ const preflight = preflightCleanRoot(repo, "M010", () => {});
321
+ assert.equal(preflight.stashPushed, true, "preflight must stash untracked file");
322
+
323
+ writeFileSync(join(repo, "tests.txt"), "same test content\n");
324
+ run("git add tests.txt", repo);
325
+ run('git commit -m "feat: add same test"', repo);
326
+
327
+ const postflight = postflightPopStash(repo, "M010", preflight.stashMarker, () => {});
328
+
329
+ assert.equal(postflight.needsManualRecovery, false, "identical already-present files must not stop auto-mode");
330
+ assert.equal(postflight.resolution, "already-present-dropped");
331
+ assert.equal(readFileSync(join(repo, "tests.txt"), "utf-8"), "same test content\n");
332
+
333
+ const stashList = run("git stash list", repo);
334
+ assert.ok(!stashList.includes(preflight.stashMarker ?? ""), "identical stash must be dropped");
335
+ } finally {
336
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
337
+ }
338
+ });
339
+
340
+ test("postflightPopStash requires manual recovery for mixed tracked changes and untracked collision", () => {
341
+ const repo = createTempRepo();
342
+ try {
343
+ writeFileSync(join(repo, "README.md"), "# local tracked work\n");
344
+ writeFileSync(join(repo, "tests.txt"), "local untracked work\n");
345
+ const preflight = preflightCleanRoot(repo, "M011", () => {});
346
+ assert.equal(preflight.stashPushed, true, "preflight must stash mixed changes");
347
+
348
+ writeFileSync(join(repo, "tests.txt"), "merged milestone test\n");
349
+ run("git add tests.txt", repo);
350
+ run('git commit -m "feat: add merged test"', repo);
351
+
352
+ const postflight = postflightPopStash(repo, "M011", preflight.stashMarker, () => {});
353
+
354
+ assert.equal(postflight.needsManualRecovery, true, "tracked stash payload must still require manual recovery");
355
+ assert.equal(postflight.resolution, "manual-recovery");
356
+ const stashList = run("git stash list", repo);
357
+ assert.ok(preflight.stashMarker && stashList.includes(preflight.stashMarker), "mixed stash must be preserved");
358
+ } finally {
359
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
360
+ }
361
+ });
362
+
363
+ test("postflightPopStash requires manual recovery when an untracked stash path is missing after collision", () => {
364
+ const repo = createTempRepo();
365
+ try {
366
+ writeFileSync(join(repo, "tests.txt"), "local untracked work\n");
367
+ writeFileSync(join(repo, "other-tests.txt"), "other local untracked work\n");
368
+ const preflight = preflightCleanRoot(repo, "M012", () => {});
369
+ assert.equal(preflight.stashPushed, true, "preflight must stash untracked files");
370
+
371
+ writeFileSync(join(repo, "tests.txt"), "merged milestone test\n");
372
+ run("git add tests.txt", repo);
373
+ run('git commit -m "feat: add one merged test"', repo);
374
+
375
+ const postflight = postflightPopStash(repo, "M012", preflight.stashMarker, () => {});
376
+
377
+ assert.equal(postflight.needsManualRecovery, true, "partial untracked restores must still require manual recovery");
378
+ assert.equal(postflight.resolution, "manual-recovery");
379
+ assert.equal(existsSync(join(repo, "other-tests.txt")), true, "git may partially restore the non-colliding path");
380
+ assert.match(run("git status --porcelain", repo), /\?\? other-tests\.txt/, "partial restore must leave manual recovery visible");
381
+ const stashList = run("git stash list", repo);
382
+ assert.ok(preflight.stashMarker && stashList.includes(preflight.stashMarker), "stash must remain for manual recovery");
383
+ } finally {
384
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
385
+ }
386
+ });
@@ -0,0 +1,16 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Tests closeout git action deferral policy for auto-mode units.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { shouldDeferCloseoutGitAction } from "../auto-post-unit.ts";
8
+
9
+ test("execute-task defers closeout git action until verification passes", () => {
10
+ assert.equal(shouldDeferCloseoutGitAction("execute-task"), true);
11
+ });
12
+
13
+ test("non execute-task units keep pre-verification closeout git action", () => {
14
+ assert.equal(shouldDeferCloseoutGitAction("plan-slice"), false);
15
+ assert.equal(shouldDeferCloseoutGitAction("complete-slice"), false);
16
+ });
@@ -0,0 +1,312 @@
1
+ // ADR-013 Phase 6 cutover (Stage 1) — queryDecisionsFromMemories parity test.
2
+ //
3
+ // Verifies that reading active decisions from the `memories` table returns
4
+ // the same Decision[] shape and content as the legacy `queryDecisions` read
5
+ // from the `decisions` table, once Phase 5 dual-write has populated both
6
+ // surfaces. Lock-in regression for the prompt-inline read path which was
7
+ // switched to the memories source in auto-prompts.ts:inlineDecisionsFromDb.
8
+ //
9
+ // Scope of parity: ACTIVE decisions only. Superseded rows are intentionally
10
+ // skipped by the existing backfill, so this test does not assert parity for
11
+ // the supersedes-chain — that gap is acknowledged in
12
+ // queryDecisionsFromMemories' contract and tracked for Stage 2/3.
13
+
14
+ import test from "node:test";
15
+ import assert from "node:assert/strict";
16
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { tmpdir } from "node:os";
19
+
20
+ import {
21
+ closeDatabase,
22
+ insertDecision,
23
+ openDatabase,
24
+ } from "../gsd-db.ts";
25
+ import { saveDecisionToDb } from "../db-writer.ts";
26
+ import {
27
+ queryDecisions,
28
+ queryDecisionsFromMemories,
29
+ } from "../context-store.ts";
30
+
31
+ function makeTmpBase(): string {
32
+ const base = mkdtempSync(join(tmpdir(), "gsd-decisions-memories-"));
33
+ mkdirSync(join(base, ".gsd"), { recursive: true });
34
+ openDatabase(join(base, ".gsd", "gsd.db"));
35
+ return base;
36
+ }
37
+
38
+ function cleanup(base: string): void {
39
+ try {
40
+ closeDatabase();
41
+ } catch {
42
+ /* noop */
43
+ }
44
+ try {
45
+ rmSync(base, { recursive: true, force: true });
46
+ } catch {
47
+ /* noop */
48
+ }
49
+ }
50
+
51
+ async function seedDecision(
52
+ base: string,
53
+ fields: {
54
+ when_context: string;
55
+ scope: string;
56
+ decision: string;
57
+ choice: string;
58
+ rationale: string;
59
+ revisable?: string;
60
+ made_by?: "human" | "agent" | "collaborative";
61
+ },
62
+ ): Promise<string> {
63
+ // saveDecisionToDb writes ONLY to memories post-Stage-3. For parity tests
64
+ // comparing the legacy `queryDecisions` against `queryDecisionsFromMemories`,
65
+ // mirror the same row into the legacy decisions table directly so both
66
+ // surfaces hold the same data and the parity assertion is well-defined.
67
+ const result = await saveDecisionToDb(
68
+ {
69
+ when_context: fields.when_context,
70
+ scope: fields.scope,
71
+ decision: fields.decision,
72
+ choice: fields.choice,
73
+ rationale: fields.rationale,
74
+ revisable: fields.revisable ?? "Yes",
75
+ made_by: fields.made_by ?? "agent",
76
+ },
77
+ base,
78
+ );
79
+ insertDecision({
80
+ id: result.id,
81
+ when_context: fields.when_context,
82
+ scope: fields.scope,
83
+ decision: fields.decision,
84
+ choice: fields.choice,
85
+ rationale: fields.rationale,
86
+ revisable: fields.revisable ?? "Yes",
87
+ made_by: fields.made_by ?? "agent",
88
+ superseded_by: null,
89
+ });
90
+ return result.id;
91
+ }
92
+
93
+ test("queryDecisionsFromMemories returns empty when no decisions exist", () => {
94
+ const base = makeTmpBase();
95
+ try {
96
+ assert.deepEqual(queryDecisionsFromMemories(), []);
97
+ assert.deepEqual(queryDecisionsFromMemories({ milestoneId: "M001" }), []);
98
+ } finally {
99
+ cleanup(base);
100
+ }
101
+ });
102
+
103
+ test("queryDecisionsFromMemories matches queryDecisions for a single active decision", async () => {
104
+ const base = makeTmpBase();
105
+ try {
106
+ await seedDecision(base, {
107
+ when_context: "M001 discuss phase",
108
+ scope: "M001",
109
+ decision: "Adopt SQLite for persistence",
110
+ choice: "better-sqlite3",
111
+ rationale: "Native, synchronous, well-supported",
112
+ });
113
+
114
+ const fromDecisions = queryDecisions();
115
+ const fromMemories = queryDecisionsFromMemories();
116
+
117
+ assert.equal(fromDecisions.length, 1);
118
+ assert.equal(fromMemories.length, 1);
119
+
120
+ // Compare the user-visible Decision fields. seq differs across tables
121
+ // (memories.seq vs decisions.seq) so it's intentionally excluded.
122
+ const { seq: _seq1, ...d1 } = fromDecisions[0]!;
123
+ const { seq: _seq2, ...d2 } = fromMemories[0]!;
124
+ assert.deepEqual(d1, d2);
125
+ } finally {
126
+ cleanup(base);
127
+ }
128
+ });
129
+
130
+ test("queryDecisionsFromMemories preserves decision order across multiple writes", async () => {
131
+ const base = makeTmpBase();
132
+ try {
133
+ const id1 = await seedDecision(base, {
134
+ when_context: "M001 discuss",
135
+ scope: "M001",
136
+ decision: "First decision",
137
+ choice: "A",
138
+ rationale: "first",
139
+ });
140
+ const id2 = await seedDecision(base, {
141
+ when_context: "M001 plan",
142
+ scope: "M001",
143
+ decision: "Second decision",
144
+ choice: "B",
145
+ rationale: "second",
146
+ });
147
+ const id3 = await seedDecision(base, {
148
+ when_context: "M002 discuss",
149
+ scope: "M002",
150
+ decision: "Third decision",
151
+ choice: "C",
152
+ rationale: "third",
153
+ });
154
+
155
+ const fromMemories = queryDecisionsFromMemories();
156
+ assert.equal(fromMemories.length, 3);
157
+ assert.deepEqual(
158
+ fromMemories.map((d) => d.id),
159
+ [id1, id2, id3],
160
+ );
161
+ } finally {
162
+ cleanup(base);
163
+ }
164
+ });
165
+
166
+ test("queryDecisionsFromMemories filters by milestoneId (substring match on when_context)", async () => {
167
+ const base = makeTmpBase();
168
+ try {
169
+ await seedDecision(base, {
170
+ when_context: "M001 discuss",
171
+ scope: "M001",
172
+ decision: "M001 decision",
173
+ choice: "A",
174
+ rationale: "x",
175
+ });
176
+ await seedDecision(base, {
177
+ when_context: "M002 plan",
178
+ scope: "M002",
179
+ decision: "M002 decision",
180
+ choice: "B",
181
+ rationale: "y",
182
+ });
183
+ await seedDecision(base, {
184
+ when_context: "M001 execute",
185
+ scope: "M001-S01",
186
+ decision: "M001 follow-up",
187
+ choice: "C",
188
+ rationale: "z",
189
+ });
190
+ await seedDecision(base, {
191
+ when_context: "M003 plan",
192
+ scope: "M003",
193
+ decision: "Use M001 as precedent",
194
+ choice: "D",
195
+ rationale: "Mentions M001 outside when_context",
196
+ });
197
+
198
+ const m001 = queryDecisionsFromMemories({ milestoneId: "M001" });
199
+ assert.equal(m001.length, 2, "two decisions reference M001 in when_context");
200
+ assert.ok(m001.every((d) => d.when_context.includes("M001")));
201
+
202
+ const m002 = queryDecisionsFromMemories({ milestoneId: "M002" });
203
+ assert.equal(m002.length, 1);
204
+ assert.equal(m002[0]?.decision, "M002 decision");
205
+ } finally {
206
+ cleanup(base);
207
+ }
208
+ });
209
+
210
+ test("queryDecisionsFromMemories filters by scope (exact match, no prefix collisions)", async () => {
211
+ const base = makeTmpBase();
212
+ try {
213
+ await seedDecision(base, {
214
+ when_context: "M001 discuss",
215
+ scope: "M001",
216
+ decision: "Milestone-level",
217
+ choice: "A",
218
+ rationale: "x",
219
+ });
220
+ await seedDecision(base, {
221
+ when_context: "M001 plan",
222
+ scope: "M001-S01",
223
+ decision: "Slice-level",
224
+ choice: "B",
225
+ rationale: "y",
226
+ });
227
+ await seedDecision(base, {
228
+ when_context: "M001 plan",
229
+ scope: "M001-S02",
230
+ decision: "Different slice",
231
+ choice: "C",
232
+ rationale: "z",
233
+ });
234
+
235
+ // Exact-scope filter must not match prefix-similar values.
236
+ const milestoneScope = queryDecisionsFromMemories({ scope: "M001" });
237
+ assert.equal(milestoneScope.length, 1, "scope=M001 must not match M001-S01 / M001-S02");
238
+ assert.equal(milestoneScope[0]?.scope, "M001");
239
+
240
+ const sliceScope = queryDecisionsFromMemories({ scope: "M001-S01" });
241
+ assert.equal(sliceScope.length, 1);
242
+ assert.equal(sliceScope[0]?.scope, "M001-S01");
243
+ } finally {
244
+ cleanup(base);
245
+ }
246
+ });
247
+
248
+ test("queryDecisionsFromMemories matches queryDecisions for combined milestoneId + scope filters", async () => {
249
+ const base = makeTmpBase();
250
+ try {
251
+ await seedDecision(base, {
252
+ when_context: "M001 discuss",
253
+ scope: "M001",
254
+ decision: "A",
255
+ choice: "1",
256
+ rationale: "x",
257
+ });
258
+ await seedDecision(base, {
259
+ when_context: "M001 plan",
260
+ scope: "M001-S01",
261
+ decision: "B",
262
+ choice: "2",
263
+ rationale: "y",
264
+ });
265
+ await seedDecision(base, {
266
+ when_context: "M002 discuss",
267
+ scope: "M002",
268
+ decision: "C",
269
+ choice: "3",
270
+ rationale: "z",
271
+ });
272
+
273
+ const opts = { milestoneId: "M001", scope: "M001-S01" };
274
+ const fromDecisions = queryDecisions(opts);
275
+ const fromMemories = queryDecisionsFromMemories(opts);
276
+
277
+ assert.equal(fromDecisions.length, fromMemories.length);
278
+ assert.equal(fromMemories.length, 1);
279
+ assert.equal(fromMemories[0]?.id, fromDecisions[0]?.id);
280
+ assert.equal(fromMemories[0]?.scope, "M001-S01");
281
+ } finally {
282
+ cleanup(base);
283
+ }
284
+ });
285
+
286
+ test("queryDecisionsFromMemories ignores memories without a sourceDecisionId marker", async () => {
287
+ const base = makeTmpBase();
288
+ try {
289
+ // Insert a user-authored memory (no sourceDecisionId) — must not appear in
290
+ // the decisions-from-memories projection.
291
+ const { createMemory } = await import("../memory-store.ts");
292
+ createMemory({
293
+ category: "architecture",
294
+ content: "User-authored architecture note, not derived from a decision",
295
+ scope: "project",
296
+ });
297
+
298
+ await seedDecision(base, {
299
+ when_context: "M001 discuss",
300
+ scope: "M001",
301
+ decision: "Real decision",
302
+ choice: "A",
303
+ rationale: "x",
304
+ });
305
+
306
+ const fromMemories = queryDecisionsFromMemories();
307
+ assert.equal(fromMemories.length, 1, "user-authored memory must not appear as a decision");
308
+ assert.equal(fromMemories[0]?.decision, "Real decision");
309
+ } finally {
310
+ cleanup(base);
311
+ }
312
+ });
@@ -27,6 +27,7 @@ import {
27
27
  saveArtifactToDb,
28
28
  extractDeferredSliceRef,
29
29
  } from '../db-writer.ts';
30
+ import { getAllDecisionsFromMemories } from '../context-store.ts';
30
31
  import type { Decision, Requirement } from '../types.ts';
31
32
 
32
33
  // ═══════════════════════════════════════════════════════════════════════════
@@ -326,11 +327,14 @@ describe('db-writer', () => {
326
327
 
327
328
  assert.deepStrictEqual(result.id, 'D001', 'saveDecisionToDb returns D001 as first ID');
328
329
 
329
- // Verify DB state
330
- const dbDecision = getDecisionById('D001');
331
- assert.ok(!!dbDecision, 'decision exists in DB after save');
332
- assert.deepStrictEqual(dbDecision?.scope, 'arch', 'DB decision has correct scope');
333
- assert.deepStrictEqual(dbDecision?.choice, 'Option A', 'DB decision has correct choice');
330
+ // ADR-013 Stage 3: decisions land in memories, not the legacy table.
331
+ const memoryDecisions = getAllDecisionsFromMemories();
332
+ assert.equal(memoryDecisions.length, 1, 'one memory row exists after save');
333
+ const memDecision = memoryDecisions[0];
334
+ assert.ok(memDecision, 'memory decision exists after save');
335
+ assert.equal(memDecision.id, 'D001');
336
+ assert.equal(memDecision.scope, 'arch', 'memory decision has correct scope');
337
+ assert.equal(memDecision.choice, 'Option A', 'memory decision has correct choice');
334
338
 
335
339
  // Verify markdown file was written
336
340
  const mdPath = path.join(tmpDir, '.gsd', 'DECISIONS.md');
@@ -394,10 +398,11 @@ describe('db-writer', () => {
394
398
  assert.match(id, /^D\d{3}$/, `ID ${id} should match D### pattern`);
395
399
  }
396
400
 
397
- // Verify all 5 exist in DB
401
+ // ADR-013 Stage 3: verify all 5 exist in the memories table (decisions
402
+ // table receives no writes from saveDecisionToDb post-cutover).
403
+ const memoryIds = new Set(getAllDecisionsFromMemories().map((d) => d.id));
398
404
  for (const id of ids) {
399
- const row = getDecisionById(id);
400
- assert.ok(row, `Decision ${id} should exist in DB`);
405
+ assert.ok(memoryIds.has(id), `Decision ${id} should exist in memories`);
401
406
  }
402
407
  } finally {
403
408
  closeDatabase();