gsd-pi 2.23.0 → 2.25.0

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 (212) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +12 -3
  3. package/dist/headless.d.ts +4 -0
  4. package/dist/headless.js +118 -10
  5. package/dist/help-text.js +22 -7
  6. package/dist/models-resolver.d.ts +0 -11
  7. package/dist/models-resolver.js +0 -15
  8. package/dist/resource-loader.d.ts +0 -1
  9. package/dist/resource-loader.js +64 -18
  10. package/dist/resources/GSD-WORKFLOW.md +12 -9
  11. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  12. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  13. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  14. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  15. package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
  16. package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
  18. package/dist/resources/extensions/gsd/auto.ts +307 -77
  19. package/dist/resources/extensions/gsd/cache.ts +3 -1
  20. package/dist/resources/extensions/gsd/commands.ts +176 -10
  21. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  22. package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  23. package/dist/resources/extensions/gsd/doctor.ts +58 -11
  24. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  25. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  26. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  27. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  28. package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
  29. package/dist/resources/extensions/gsd/index.ts +48 -2
  30. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  31. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  32. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  33. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  34. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  35. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  36. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  39. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
  40. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  48. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  49. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  50. package/dist/resources/extensions/gsd/state.ts +72 -30
  51. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  52. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  53. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  55. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  56. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  57. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  58. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  59. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  60. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  62. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  63. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  64. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  65. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  66. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  67. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  68. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  69. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  70. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  71. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  72. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  73. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  74. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  75. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  76. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  77. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  78. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  79. package/dist/resources/extensions/gsd/types.ts +15 -1
  80. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  81. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  82. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  83. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  84. package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
  85. package/dist/resources/extensions/subagent/index.ts +5 -0
  86. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  87. package/dist/update-check.d.ts +9 -0
  88. package/dist/update-check.js +97 -0
  89. package/package.json +6 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  91. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  92. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  93. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic.js +55 -7
  95. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  98. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  101. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  103. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  106. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +23 -1
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +59 -9
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  117. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  118. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  119. package/packages/pi-ai/src/types.ts +19 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  123. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  133. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
  135. package/scripts/postinstall.js +7 -109
  136. package/src/resources/GSD-WORKFLOW.md +12 -9
  137. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  138. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  139. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  140. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  141. package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
  143. package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
  144. package/src/resources/extensions/gsd/auto.ts +307 -77
  145. package/src/resources/extensions/gsd/cache.ts +3 -1
  146. package/src/resources/extensions/gsd/commands.ts +176 -10
  147. package/src/resources/extensions/gsd/complexity.ts +1 -0
  148. package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  149. package/src/resources/extensions/gsd/doctor.ts +58 -11
  150. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  151. package/src/resources/extensions/gsd/git-service.ts +74 -14
  152. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  153. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  154. package/src/resources/extensions/gsd/guided-flow.ts +109 -12
  155. package/src/resources/extensions/gsd/index.ts +48 -2
  156. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  157. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  158. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  159. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  160. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  161. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  162. package/src/resources/extensions/gsd/preferences.ts +65 -1
  163. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  165. package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
  166. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  170. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  174. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  175. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  176. package/src/resources/extensions/gsd/state.ts +72 -30
  177. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  178. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  179. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  181. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  182. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  183. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  184. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  185. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  187. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  188. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  189. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  190. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  191. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  192. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  193. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  194. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  195. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  196. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  197. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  198. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  199. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  200. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  202. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  203. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  204. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  205. package/src/resources/extensions/gsd/types.ts +15 -1
  206. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  207. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  208. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  209. package/src/resources/extensions/gsd/worktree.ts +9 -2
  210. package/src/resources/extensions/search-the-web/native-search.ts +15 -5
  211. package/src/resources/extensions/subagent/index.ts +5 -0
  212. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -5,6 +5,7 @@ import { execSync } from "node:child_process";
5
5
 
6
6
  import {
7
7
  inferCommitType,
8
+ buildTaskCommitMessage,
8
9
  GitServiceImpl,
9
10
  RUNTIME_EXCLUSION_PATHS,
10
11
  VALID_BRANCH_NAME,
@@ -14,6 +15,7 @@ import {
14
15
  type GitPreferences,
15
16
  type CommitOptions,
16
17
  type PreMergeCheckResult,
18
+ type TaskCommitContext,
17
19
  } from "../git-service.ts";
18
20
  import { createTestContext } from './test-helpers.ts';
19
21
 
@@ -188,6 +190,58 @@ async function main(): Promise<void> {
188
190
  "'prefix' does not match 'fix' — word boundary prevents partial match"
189
191
  );
190
192
 
193
+ // ─── inferCommitType with oneLiner ──────────────────────────────────────
194
+
195
+ console.log("\n=== inferCommitType with oneLiner ===");
196
+
197
+ assertEq(
198
+ inferCommitType("implement dashboard", "Fixed rendering bug in sidebar"),
199
+ "fix",
200
+ "one-liner with 'fixed' overrides generic title → fix"
201
+ );
202
+
203
+ assertEq(
204
+ inferCommitType("add search", "Optimized query performance with caching"),
205
+ "perf",
206
+ "one-liner with 'performance' and 'caching' → perf"
207
+ );
208
+
209
+ // ─── buildTaskCommitMessage ─────────────────────────────────────────────
210
+
211
+ console.log("\n=== buildTaskCommitMessage ===");
212
+
213
+ {
214
+ const msg = buildTaskCommitMessage({
215
+ taskId: "S01/T02",
216
+ taskTitle: "implement user authentication",
217
+ oneLiner: "Added JWT-based auth with refresh token rotation",
218
+ keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
219
+ });
220
+ assertTrue(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
221
+ assertTrue(msg.includes("JWT-based auth"), "message includes one-liner content");
222
+ assertTrue(msg.includes("- src/auth.ts"), "message body includes key files");
223
+ assertTrue(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
224
+ }
225
+
226
+ {
227
+ const msg = buildTaskCommitMessage({
228
+ taskId: "S02/T01",
229
+ taskTitle: "fix login redirect bug",
230
+ });
231
+ assertTrue(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
232
+ assertTrue(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
233
+ assertTrue(!msg.includes("\n"), "no body when no key files");
234
+ }
235
+
236
+ {
237
+ const msg = buildTaskCommitMessage({
238
+ taskId: "S01/T03",
239
+ taskTitle: "add tests",
240
+ oneLiner: "Unit tests for auth module with coverage",
241
+ });
242
+ assertTrue(msg.startsWith("test(S01/T03):"), "infers test type");
243
+ }
244
+
191
245
  // ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
192
246
 
193
247
  console.log("\n=== RUNTIME_EXCLUSION_PATHS ===");
@@ -430,13 +484,25 @@ async function main(): Promise<void> {
430
484
  const svc = new GitServiceImpl(repo);
431
485
 
432
486
  createFile(repo, "src/new-feature.ts", "export const x = 1;");
433
- const msg = svc.autoCommit("task", "T01");
434
487
 
435
- assertEq(msg, "chore(T01): auto-commit after task", "autoCommit returns correct message format");
488
+ // Without task context, autoCommit uses generic chore message
489
+ const msg = svc.autoCommit("task", "T01");
490
+ assertEq(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
436
491
 
437
- // Verify the commit exists
438
492
  const log = run("git log --oneline -1", repo);
439
- assertTrue(log.includes("chore(T01): auto-commit after task"), "commit message is in git log");
493
+ assertTrue(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
494
+
495
+ // With task context, autoCommit uses meaningful message
496
+ createFile(repo, "src/auth.ts", "export function login() {}");
497
+ const msg2 = svc.autoCommit("task", "S01/T02", [], {
498
+ taskId: "S01/T02",
499
+ taskTitle: "implement user authentication endpoint",
500
+ oneLiner: "Added JWT-based auth with refresh token rotation",
501
+ keyFiles: ["src/auth.ts"],
502
+ });
503
+ assertTrue(msg2 !== null, "autoCommit with task context returns a message");
504
+ assertTrue(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
505
+ assertTrue(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
440
506
 
441
507
  rmSync(repo, { recursive: true, force: true });
442
508
  }
@@ -65,8 +65,8 @@ console.log('\n=== gsd-db: fresh DB schema init (memory) ===');
65
65
 
66
66
  // Check schema_version table
67
67
  const adapter = _getAdapter()!;
68
- const version = adapter.prepare('SELECT version FROM schema_version').get();
69
- assertEq(version?.['version'], 2, 'schema version should be 2');
68
+ const version = adapter.prepare('SELECT MAX(version) as version FROM schema_version').get();
69
+ assertEq(version?.['version'], 3, 'schema version should be 3');
70
70
 
71
71
  // Check tables exist by querying them
72
72
  const dRows = adapter.prepare('SELECT count(*) as cnt FROM decisions').get();
@@ -51,6 +51,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
51
51
  writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
52
52
  }
53
53
 
54
+ function writeMilestoneValidation(base: string, mid: string): void {
55
+ const dir = join(base, '.gsd', 'milestones', mid);
56
+ mkdirSync(dir, { recursive: true });
57
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
58
+ }
59
+
54
60
  function cleanup(base: string): void {
55
61
  rmSync(base, { recursive: true, force: true });
56
62
  }
@@ -166,6 +172,7 @@ async function main(): Promise<void> {
166
172
  Did it.
167
173
  `);
168
174
 
175
+ writeMilestoneValidation(base, 'M001');
169
176
  writeMilestoneSummary(base, 'M001', `# M001: Legacy Feature Summary
170
177
 
171
178
  **One-liner summary**
@@ -265,6 +272,7 @@ Everything worked.
265
272
  Did it.
266
273
  `);
267
274
 
275
+ writeMilestoneValidation(base, 'M001');
268
276
  writeMilestoneSummary(base, 'M001', `# M001: Legacy Feature Summary
269
277
 
270
278
  **One-liner summary**
@@ -350,12 +350,11 @@ console.log('=== md-importer: missing file handling ===');
350
350
  console.log('=== md-importer: schema v1→v2 migration ===');
351
351
 
352
352
  {
353
- // This test verifies that opening a v1 DB auto-migrates to v2
354
- // (The actual migration is tested via the gsd-db.test.ts schema version assertion = 2)
353
+ // This test verifies that opening a fresh DB auto-migrates to current schema version
355
354
  openDatabase(':memory:');
356
355
  const adapter = _getAdapter();
357
356
  const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
358
- assertEq(version?.v, 2, 'new DB should be at schema version 2');
357
+ assertEq(version?.v, 3, 'new DB should be at schema version 3');
359
358
 
360
359
  // Artifacts table should exist
361
360
  const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
@@ -0,0 +1,180 @@
1
+ import { createTestContext } from './test-helpers.ts';
2
+ import { parseMemoryResponse, _resetExtractionState } from '../memory-extractor.ts';
3
+ import {
4
+ openDatabase,
5
+ closeDatabase,
6
+ } from '../gsd-db.ts';
7
+ import {
8
+ getActiveMemories,
9
+ applyMemoryActions,
10
+ getActiveMemoriesRanked,
11
+ } from '../memory-store.ts';
12
+ import type { MemoryAction } from '../memory-store.ts';
13
+
14
+ const { assertEq, assertTrue, report } = createTestContext();
15
+
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+ // memory-extractor: parse valid JSON response
18
+ // ═══════════════════════════════════════════════════════════════════════════
19
+
20
+ console.log('\n=== memory-extractor: parse valid JSON ===');
21
+ {
22
+ const response = JSON.stringify([
23
+ { action: 'CREATE', category: 'gotcha', content: 'esbuild drops binaries', confidence: 0.85 },
24
+ { action: 'REINFORCE', id: 'MEM001' },
25
+ { action: 'UPDATE', id: 'MEM002', content: 'revised content' },
26
+ { action: 'SUPERSEDE', id: 'MEM003', superseded_by: 'MEM004' },
27
+ ]);
28
+
29
+ const actions = parseMemoryResponse(response);
30
+ assertEq(actions.length, 4, 'should parse 4 actions');
31
+ assertEq(actions[0].action, 'CREATE', 'first action should be CREATE');
32
+ assertEq((actions[0] as any).category, 'gotcha', 'CREATE category');
33
+ assertEq((actions[0] as any).confidence, 0.85, 'CREATE confidence');
34
+ assertEq(actions[1].action, 'REINFORCE', 'second action should be REINFORCE');
35
+ assertEq(actions[2].action, 'UPDATE', 'third action should be UPDATE');
36
+ assertEq(actions[3].action, 'SUPERSEDE', 'fourth action should be SUPERSEDE');
37
+ }
38
+
39
+ // ═══════════════════════════════════════════════════════════════════════════
40
+ // memory-extractor: parse fenced JSON response
41
+ // ═══════════════════════════════════════════════════════════════════════════
42
+
43
+ console.log('\n=== memory-extractor: parse fenced JSON ===');
44
+ {
45
+ const response = '```json\n[\n {"action": "CREATE", "category": "convention", "content": "test memory"}\n]\n```';
46
+
47
+ const actions = parseMemoryResponse(response);
48
+ assertEq(actions.length, 1, 'should parse 1 action from fenced JSON');
49
+ assertEq(actions[0].action, 'CREATE', 'action should be CREATE');
50
+ }
51
+
52
+ // ═══════════════════════════════════════════════════════════════════════════
53
+ // memory-extractor: parse empty array response
54
+ // ═══════════════════════════════════════════════════════════════════════════
55
+
56
+ console.log('\n=== memory-extractor: parse empty array ===');
57
+ {
58
+ const actions = parseMemoryResponse('[]');
59
+ assertEq(actions.length, 0, 'empty array should parse to empty actions');
60
+ }
61
+
62
+ // ═══════════════════════════════════════════════════════════════════════════
63
+ // memory-extractor: parse malformed response
64
+ // ═══════════════════════════════════════════════════════════════════════════
65
+
66
+ console.log('\n=== memory-extractor: malformed responses ===');
67
+ {
68
+ assertEq(parseMemoryResponse('not json at all'), [], 'garbage text should return []');
69
+ assertEq(parseMemoryResponse('{"action": "CREATE"}'), [], 'non-array should return []');
70
+ assertEq(parseMemoryResponse(''), [], 'empty string should return []');
71
+ assertEq(parseMemoryResponse('```\nbroken\n```'), [], 'fenced non-JSON should return []');
72
+ }
73
+
74
+ // ═══════════════════════════════════════════════════════════════════════════
75
+ // memory-extractor: validation of required fields
76
+ // ═══════════════════════════════════════════════════════════════════════════
77
+
78
+ console.log('\n=== memory-extractor: field validation ===');
79
+ {
80
+ const response = JSON.stringify([
81
+ // Valid CREATE
82
+ { action: 'CREATE', category: 'gotcha', content: 'valid' },
83
+ // Invalid CREATE — missing content
84
+ { action: 'CREATE', category: 'gotcha' },
85
+ // Invalid CREATE — missing category
86
+ { action: 'CREATE', content: 'no category' },
87
+ // Valid REINFORCE
88
+ { action: 'REINFORCE', id: 'MEM001' },
89
+ // Invalid REINFORCE — missing id
90
+ { action: 'REINFORCE' },
91
+ // Valid UPDATE
92
+ { action: 'UPDATE', id: 'MEM002', content: 'new content' },
93
+ // Invalid UPDATE — missing content
94
+ { action: 'UPDATE', id: 'MEM002' },
95
+ // Valid SUPERSEDE
96
+ { action: 'SUPERSEDE', id: 'MEM001', superseded_by: 'MEM002' },
97
+ // Invalid SUPERSEDE — missing superseded_by
98
+ { action: 'SUPERSEDE', id: 'MEM001' },
99
+ // Unknown action
100
+ { action: 'DELETE', id: 'MEM001' },
101
+ // Null entry
102
+ null,
103
+ ]);
104
+
105
+ const actions = parseMemoryResponse(response);
106
+ assertEq(actions.length, 4, 'should only accept 4 valid actions');
107
+ assertEq(actions[0].action, 'CREATE', 'first valid is CREATE');
108
+ assertEq(actions[1].action, 'REINFORCE', 'second valid is REINFORCE');
109
+ assertEq(actions[2].action, 'UPDATE', 'third valid is UPDATE');
110
+ assertEq(actions[3].action, 'SUPERSEDE', 'fourth valid is SUPERSEDE');
111
+ }
112
+
113
+ // ═══════════════════════════════════════════════════════════════════════════
114
+ // Integration: applyMemoryActions with mixed actions
115
+ // ═══════════════════════════════════════════════════════════════════════════
116
+
117
+ console.log('\n=== integration: mixed action lifecycle ===');
118
+ {
119
+ openDatabase(':memory:');
120
+
121
+ // Phase 1: Create initial memories
122
+ applyMemoryActions([
123
+ { action: 'CREATE', category: 'gotcha', content: 'npm run build needs tsc first', confidence: 0.7 },
124
+ { action: 'CREATE', category: 'convention', content: 'all DB queries use named params', confidence: 0.8 },
125
+ { action: 'CREATE', category: 'architecture', content: 'extensions loaded from two paths', confidence: 0.85 },
126
+ ], 'plan-slice', 'M001/S01');
127
+
128
+ let active = getActiveMemoriesRanked(30);
129
+ assertEq(active.length, 3, 'phase 1: 3 active memories');
130
+
131
+ // Phase 2: Reinforce one, update another, create new
132
+ applyMemoryActions([
133
+ { action: 'REINFORCE', id: 'MEM002' },
134
+ { action: 'UPDATE', id: 'MEM001', content: 'npm run build requires tsc --noEmit first' },
135
+ { action: 'CREATE', category: 'pattern', content: 'use INSERT OR IGNORE for idempotency', confidence: 0.75 },
136
+ ], 'execute-task', 'M001/S01/T01');
137
+
138
+ active = getActiveMemoriesRanked(30);
139
+ assertEq(active.length, 4, 'phase 2: 4 active memories');
140
+ assertEq(
141
+ active.find(m => m.id === 'MEM001')?.content,
142
+ 'npm run build requires tsc --noEmit first',
143
+ 'MEM001 content should be updated',
144
+ );
145
+ assertEq(active.find(m => m.id === 'MEM002')?.hit_count, 1, 'MEM002 should be reinforced');
146
+
147
+ // Phase 3: Supersede MEM001 with MEM005
148
+ applyMemoryActions([
149
+ { action: 'CREATE', category: 'gotcha', content: 'build script handles tsc automatically now', confidence: 0.9 },
150
+ { action: 'SUPERSEDE', id: 'MEM001', superseded_by: 'MEM005' },
151
+ ], 'execute-task', 'M001/S01/T02');
152
+
153
+ active = getActiveMemoriesRanked(30);
154
+ assertEq(active.length, 4, 'phase 3: 4 active (1 superseded, 1 created)');
155
+ assertTrue(!active.find(m => m.id === 'MEM001'), 'MEM001 should be superseded');
156
+ assertTrue(!!active.find(m => m.id === 'MEM005'), 'MEM005 should be active');
157
+
158
+ // Verify ranking: MEM003 (0.85) > MEM005 (0.9) but MEM002 has 1 hit
159
+ // MEM002: 0.8 * (1 + 1*0.1) = 0.88
160
+ // MEM003: 0.85 * 1.0 = 0.85
161
+ // MEM005: 0.9 * 1.0 = 0.9
162
+ // MEM004: 0.75 * 1.0 = 0.75
163
+ assertEq(active[0].id, 'MEM005', 'MEM005 should rank first (0.9)');
164
+ assertEq(active[1].id, 'MEM002', 'MEM002 should rank second (0.88)');
165
+
166
+ closeDatabase();
167
+ }
168
+
169
+ // ═══════════════════════════════════════════════════════════════════════════
170
+ // memory-extractor: _resetExtractionState
171
+ // ═══════════════════════════════════════════════════════════════════════════
172
+
173
+ console.log('\n=== memory-extractor: reset extraction state ===');
174
+ {
175
+ // Just verify it doesn't throw
176
+ _resetExtractionState();
177
+ assertTrue(true, '_resetExtractionState should not throw');
178
+ }
179
+
180
+ report();