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,453 @@
1
+ // ADR-013 Phase 6 cutover (Stage 2a) — locks in the four behavioral changes:
2
+ //
3
+ // 1. memory-backfill includes superseded decisions + preserves
4
+ // structuredFields.superseded_by
5
+ // 2. memory-backfill drift auto-heal: when a decision's superseded_by
6
+ // changes after migration, the memory's structuredFields update
7
+ // 3. context-store.queryDecisionsFromMemories filters out rows whose
8
+ // structuredFields.superseded_by is set (active-only)
9
+ // 4. context-store.getAllDecisionsFromMemories returns the full register
10
+ // including superseded rows, and the saveDecisionToDb projection regen
11
+ // uses it — producing byte-equivalent DECISIONS.md to the legacy path
12
+
13
+ import test from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdtempSync, mkdirSync, rmSync, readFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+
19
+ import {
20
+ _getAdapter,
21
+ closeDatabase,
22
+ insertDecision,
23
+ openDatabase,
24
+ } from "../gsd-db.ts";
25
+ import { saveDecisionToDb, generateDecisionsMd } from "../db-writer.ts";
26
+ import {
27
+ getAllDecisionsFromMemories,
28
+ queryDecisionsFromMemories,
29
+ } from "../context-store.ts";
30
+ import { backfillDecisionsToMemories } from "../memory-backfill.ts";
31
+ import type { Decision } from "../types.ts";
32
+
33
+ function makeTmpBase(): string {
34
+ const base = mkdtempSync(join(tmpdir(), "gsd-decisions-stage2a-"));
35
+ mkdirSync(join(base, ".gsd"), { recursive: true });
36
+ openDatabase(join(base, ".gsd", "gsd.db"));
37
+ return base;
38
+ }
39
+
40
+ function cleanup(base: string): void {
41
+ try {
42
+ closeDatabase();
43
+ } catch {
44
+ /* noop */
45
+ }
46
+ try {
47
+ rmSync(base, { recursive: true, force: true });
48
+ } catch {
49
+ /* noop */
50
+ }
51
+ }
52
+
53
+ function seedRawDecision(args: {
54
+ id: string;
55
+ when_context: string;
56
+ scope: string;
57
+ decision: string;
58
+ choice: string;
59
+ rationale: string;
60
+ revisable?: string;
61
+ made_by?: "human" | "agent" | "collaborative";
62
+ superseded_by?: string | null;
63
+ }): void {
64
+ insertDecision({
65
+ id: args.id,
66
+ when_context: args.when_context,
67
+ scope: args.scope,
68
+ decision: args.decision,
69
+ choice: args.choice,
70
+ rationale: args.rationale,
71
+ revisable: args.revisable ?? "Yes",
72
+ made_by: args.made_by ?? "agent",
73
+ superseded_by: args.superseded_by ?? null,
74
+ });
75
+ }
76
+
77
+ function setDecisionSupersededBy(id: string, supersededBy: string): void {
78
+ const adapter = _getAdapter();
79
+ if (!adapter) throw new Error("DB adapter unavailable");
80
+ adapter
81
+ .prepare("UPDATE decisions SET superseded_by = :s WHERE id = :id")
82
+ .run({ ":s": supersededBy, ":id": id });
83
+ }
84
+
85
+ // ─── Backfill: superseded inclusion + structuredFields preservation ────────
86
+
87
+ test("backfill migrates superseded decisions (no active-only filter)", () => {
88
+ const base = makeTmpBase();
89
+ try {
90
+ seedRawDecision({
91
+ id: "D001",
92
+ when_context: "M001 discuss",
93
+ scope: "M001",
94
+ decision: "Use A",
95
+ choice: "A",
96
+ rationale: "first idea",
97
+ superseded_by: "D002",
98
+ });
99
+ seedRawDecision({
100
+ id: "D002",
101
+ when_context: "M001 discuss",
102
+ scope: "M001",
103
+ decision: "Switch to B",
104
+ choice: "B",
105
+ rationale: "second thought",
106
+ superseded_by: null,
107
+ });
108
+
109
+ const written = backfillDecisionsToMemories();
110
+ assert.equal(written, 2, "both active and superseded rows should be migrated");
111
+
112
+ const all = getAllDecisionsFromMemories();
113
+ assert.equal(all.length, 2);
114
+ const d001 = all.find((d) => d.id === "D001");
115
+ const d002 = all.find((d) => d.id === "D002");
116
+ assert.ok(d001 && d002);
117
+ assert.equal(d001.superseded_by, "D002", "structuredFields.superseded_by must be preserved");
118
+ assert.equal(d002.superseded_by, null);
119
+ } finally {
120
+ cleanup(base);
121
+ }
122
+ });
123
+
124
+ test("backfill is idempotent — re-running over migrated rows is a no-op", () => {
125
+ const base = makeTmpBase();
126
+ try {
127
+ seedRawDecision({
128
+ id: "D001",
129
+ when_context: "M001",
130
+ scope: "M001",
131
+ decision: "x",
132
+ choice: "y",
133
+ rationale: "z",
134
+ });
135
+
136
+ const first = backfillDecisionsToMemories();
137
+ assert.equal(first, 1);
138
+ const second = backfillDecisionsToMemories();
139
+ assert.equal(second, 0, "already-migrated rows must not be re-inserted");
140
+ } finally {
141
+ cleanup(base);
142
+ }
143
+ });
144
+
145
+ // ─── Drift auto-heal ───────────────────────────────────────────────────────
146
+
147
+ test("backfill auto-heals when a source decision's superseded_by changes after migration", () => {
148
+ const base = makeTmpBase();
149
+ try {
150
+ seedRawDecision({
151
+ id: "D001",
152
+ when_context: "M001",
153
+ scope: "M001",
154
+ decision: "Original",
155
+ choice: "A",
156
+ rationale: "r",
157
+ });
158
+ // Initial migration: D001 is active.
159
+ backfillDecisionsToMemories();
160
+ const beforeHeal = getAllDecisionsFromMemories().find((d) => d.id === "D001");
161
+ assert.equal(beforeHeal?.superseded_by, null);
162
+
163
+ // Simulate md-importer setting superseded_by on the existing decision row.
164
+ seedRawDecision({
165
+ id: "D002",
166
+ when_context: "M001",
167
+ scope: "M001",
168
+ decision: "New",
169
+ choice: "B",
170
+ rationale: "newer",
171
+ });
172
+ setDecisionSupersededBy("D001", "D002");
173
+
174
+ // Second backfill pass: should heal D001's memory + migrate D002.
175
+ backfillDecisionsToMemories();
176
+
177
+ const afterHeal = getAllDecisionsFromMemories().find((d) => d.id === "D001");
178
+ assert.equal(
179
+ afterHeal?.superseded_by,
180
+ "D002",
181
+ "drift auto-heal must update structuredFields.superseded_by on existing migrated memories",
182
+ );
183
+ const d002 = getAllDecisionsFromMemories().find((d) => d.id === "D002");
184
+ assert.equal(d002?.superseded_by, null, "newly-migrated D002 stays active");
185
+ } finally {
186
+ cleanup(base);
187
+ }
188
+ });
189
+
190
+ test("backfill drift auto-heal updates only the selected memory row", () => {
191
+ const base = makeTmpBase();
192
+ try {
193
+ seedRawDecision({
194
+ id: "D001",
195
+ when_context: "M001",
196
+ scope: "M001",
197
+ decision: "Original",
198
+ choice: "A",
199
+ rationale: "r",
200
+ });
201
+ backfillDecisionsToMemories();
202
+
203
+ const adapter = _getAdapter();
204
+ if (!adapter) throw new Error("DB adapter unavailable");
205
+ const now = new Date().toISOString();
206
+ adapter
207
+ .prepare(
208
+ `INSERT INTO memories (
209
+ id, category, content, confidence, created_at, updated_at, scope, tags, structured_fields
210
+ ) VALUES (
211
+ :id, 'architecture', 'duplicate marker', 0.8, :created_at, :updated_at, 'project', '[]', :structured_fields
212
+ )`,
213
+ )
214
+ .run({
215
+ ":id": "manual-duplicate-D001",
216
+ ":created_at": now,
217
+ ":updated_at": now,
218
+ ":structured_fields": JSON.stringify({
219
+ sourceDecisionId: "D001",
220
+ superseded_by: null,
221
+ note: "manual duplicate should not be healed as a side effect",
222
+ }),
223
+ });
224
+
225
+ setDecisionSupersededBy("D001", "D002");
226
+ backfillDecisionsToMemories();
227
+
228
+ const rows = adapter
229
+ .prepare("SELECT id, structured_fields FROM memories WHERE structured_fields LIKE :pattern ORDER BY seq")
230
+ .all({ ":pattern": '%"sourceDecisionId":"D001"%' }) as Array<{
231
+ id: string;
232
+ structured_fields: string;
233
+ }>;
234
+ assert.equal(rows.length, 2);
235
+ const healed = rows.find((row) => row.id !== "manual-duplicate-D001");
236
+ const duplicate = rows.find((row) => row.id === "manual-duplicate-D001");
237
+ assert.equal(JSON.parse(healed?.structured_fields ?? "{}").superseded_by, "D002");
238
+ assert.equal(
239
+ JSON.parse(duplicate?.structured_fields ?? "{}").superseded_by,
240
+ null,
241
+ "drift auto-heal must not update every memory matching the sourceDecisionId pattern",
242
+ );
243
+ } finally {
244
+ cleanup(base);
245
+ }
246
+ });
247
+
248
+ // ─── queryDecisionsFromMemories: active-only via structuredFields ──────────
249
+
250
+ test("queryDecisionsFromMemories filters out rows whose structuredFields.superseded_by is set", () => {
251
+ const base = makeTmpBase();
252
+ try {
253
+ seedRawDecision({
254
+ id: "D001",
255
+ when_context: "M001",
256
+ scope: "M001",
257
+ decision: "Old",
258
+ choice: "A",
259
+ rationale: "r1",
260
+ superseded_by: "D002",
261
+ });
262
+ seedRawDecision({
263
+ id: "D002",
264
+ when_context: "M001",
265
+ scope: "M001",
266
+ decision: "New",
267
+ choice: "B",
268
+ rationale: "r2",
269
+ });
270
+ backfillDecisionsToMemories();
271
+
272
+ const active = queryDecisionsFromMemories();
273
+ assert.equal(active.length, 1, "only the non-superseded decision should appear");
274
+ assert.equal(active[0]?.id, "D002");
275
+ } finally {
276
+ cleanup(base);
277
+ }
278
+ });
279
+
280
+ // ─── getAllDecisionsFromMemories: full register including superseded ───────
281
+
282
+ test("getAllDecisionsFromMemories returns the full register including superseded", () => {
283
+ const base = makeTmpBase();
284
+ try {
285
+ seedRawDecision({
286
+ id: "D001",
287
+ when_context: "M001",
288
+ scope: "M001",
289
+ decision: "First",
290
+ choice: "A",
291
+ rationale: "r1",
292
+ superseded_by: "D002",
293
+ });
294
+ seedRawDecision({
295
+ id: "D002",
296
+ when_context: "M001",
297
+ scope: "M001",
298
+ decision: "Second",
299
+ choice: "B",
300
+ rationale: "r2",
301
+ });
302
+ backfillDecisionsToMemories();
303
+
304
+ const all = getAllDecisionsFromMemories();
305
+ assert.equal(all.length, 2);
306
+ assert.deepEqual(
307
+ all.map((d) => ({ id: d.id, superseded_by: d.superseded_by })),
308
+ [
309
+ { id: "D001", superseded_by: "D002" },
310
+ { id: "D002", superseded_by: null },
311
+ ],
312
+ );
313
+ } finally {
314
+ cleanup(base);
315
+ }
316
+ });
317
+
318
+ // ─── Projection parity: legacy table source vs memories source ─────────────
319
+
320
+ function decisionFromLegacyRow(row: Record<string, unknown>): Decision {
321
+ return {
322
+ seq: row["seq"] as number,
323
+ id: row["id"] as string,
324
+ when_context: row["when_context"] as string,
325
+ scope: row["scope"] as string,
326
+ decision: row["decision"] as string,
327
+ choice: row["choice"] as string,
328
+ rationale: row["rationale"] as string,
329
+ revisable: row["revisable"] as string,
330
+ made_by: ((row["made_by"] as string) ?? "agent") as Decision["made_by"],
331
+ superseded_by: (row["superseded_by"] as string) ?? null,
332
+ };
333
+ }
334
+
335
+ test("DECISIONS.md projection from memories matches the legacy decisions-table render", async () => {
336
+ const base = makeTmpBase();
337
+ try {
338
+ // Seed three decisions with mixed superseded chains directly into the
339
+ // decisions table to ensure the legacy-side fixture is realistic.
340
+ seedRawDecision({
341
+ id: "D001",
342
+ when_context: "M001 discuss",
343
+ scope: "M001",
344
+ decision: "Initial direction",
345
+ choice: "A",
346
+ rationale: "rationale-1",
347
+ superseded_by: "D003",
348
+ });
349
+ seedRawDecision({
350
+ id: "D002",
351
+ when_context: "M001 plan",
352
+ scope: "M001-S01",
353
+ decision: "Active call",
354
+ choice: "B",
355
+ rationale: "rationale-2",
356
+ });
357
+ seedRawDecision({
358
+ id: "D003",
359
+ when_context: "M002 review",
360
+ scope: "M002",
361
+ decision: "Replacement for D001",
362
+ choice: "C",
363
+ rationale: "rationale-3",
364
+ });
365
+
366
+ // Run backfill so memories carries the full chain.
367
+ backfillDecisionsToMemories();
368
+
369
+ // Legacy render: read the decisions table directly, render via the
370
+ // existing generateDecisionsMd helper.
371
+ const adapter = _getAdapter();
372
+ if (!adapter) throw new Error("DB adapter unavailable");
373
+ const legacyRows = adapter.prepare("SELECT * FROM decisions ORDER BY seq").all() as Array<
374
+ Record<string, unknown>
375
+ >;
376
+ const legacyDecisions = legacyRows.map(decisionFromLegacyRow);
377
+ const legacyMd = generateDecisionsMd(legacyDecisions);
378
+
379
+ // Memory-sourced render: same generator, but Decision[] from memories.
380
+ const memoryDecisions = getAllDecisionsFromMemories();
381
+ const memoryMd = generateDecisionsMd(memoryDecisions);
382
+
383
+ assert.equal(memoryMd, legacyMd, "memory-sourced projection must match decisions-table render byte-for-byte");
384
+ } finally {
385
+ cleanup(base);
386
+ }
387
+ });
388
+
389
+ test("saveDecisionToDb writes a DECISIONS.md projection sourced from memories that round-trips", async () => {
390
+ const base = makeTmpBase();
391
+ try {
392
+ // Use the public write path so dual-write + projection happen end-to-end.
393
+ await saveDecisionToDb(
394
+ {
395
+ when_context: "M001 discuss",
396
+ scope: "M001",
397
+ decision: "Adopt SQLite",
398
+ choice: "better-sqlite3",
399
+ rationale: "synchronous + native",
400
+ revisable: "Yes",
401
+ made_by: "agent",
402
+ },
403
+ base,
404
+ );
405
+ await saveDecisionToDb(
406
+ {
407
+ when_context: "M001 plan",
408
+ scope: "M001-S01",
409
+ decision: "Schema versioning",
410
+ choice: "header column",
411
+ rationale: "simplest to read",
412
+ },
413
+ base,
414
+ );
415
+
416
+ const md = readFileSync(join(base, ".gsd", "DECISIONS.md"), "utf-8");
417
+ // The projection must include both rows by ID — proving the regen
418
+ // sourced from memories (where dual-write landed them) rather than
419
+ // silently skipping anything.
420
+ assert.match(md, /\| D001 \|/);
421
+ assert.match(md, /\| D002 \|/);
422
+ assert.match(md, /Adopt SQLite/);
423
+ assert.match(md, /Schema versioning/);
424
+ assert.match(md, /# Decisions Register/);
425
+ assert.match(md, /\| D002 \|[^\n]*\| Yes \| agent \|/);
426
+ } finally {
427
+ cleanup(base);
428
+ }
429
+ });
430
+
431
+ test("saveDecisionToDb injects a projection fallback when memory mirror is absent", async () => {
432
+ const base = makeTmpBase();
433
+ try {
434
+ const result = await saveDecisionToDb(
435
+ {
436
+ when_context: "M001 fallback",
437
+ scope: "M001",
438
+ decision: "",
439
+ choice: "",
440
+ rationale: "",
441
+ },
442
+ base,
443
+ );
444
+
445
+ assert.equal(result.id, "D001");
446
+ assert.equal(getAllDecisionsFromMemories().some((d) => d.id === "D001"), false);
447
+
448
+ const md = readFileSync(join(base, ".gsd", "DECISIONS.md"), "utf-8");
449
+ assert.match(md, /\| D001 \| M001 fallback \| M001 \| \| \| \| Yes \| agent \|/);
450
+ } finally {
451
+ cleanup(base);
452
+ }
453
+ });