gsd-pi 2.41.0-dev.9446b20 → 2.41.0-dev.97349b1

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 (218) hide show
  1. package/README.md +69 -29
  2. package/dist/cli-web-branch.d.ts +6 -0
  3. package/dist/cli-web-branch.js +17 -0
  4. package/dist/onboarding.js +2 -1
  5. package/dist/resources/extensions/gsd/auto/loop.js +9 -1
  6. package/dist/resources/extensions/gsd/auto/phases.js +26 -8
  7. package/dist/resources/extensions/gsd/auto-dashboard.js +6 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
  10. package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
  11. package/dist/resources/extensions/gsd/auto-start.js +8 -3
  12. package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
  13. package/dist/resources/extensions/gsd/auto.js +36 -1
  14. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
  15. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
  16. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
  18. package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
  19. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  20. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  21. package/dist/resources/extensions/gsd/context-store.js +4 -3
  22. package/dist/resources/extensions/gsd/db-writer.js +5 -2
  23. package/dist/resources/extensions/gsd/detection.js +1 -1
  24. package/dist/resources/extensions/gsd/doctor.js +11 -1
  25. package/dist/resources/extensions/gsd/exit-command.js +12 -2
  26. package/dist/resources/extensions/gsd/export.js +9 -13
  27. package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
  28. package/dist/resources/extensions/gsd/files.js +28 -11
  29. package/dist/resources/extensions/gsd/forensics.js +10 -3
  30. package/dist/resources/extensions/gsd/git-service.js +5 -1
  31. package/dist/resources/extensions/gsd/gsd-db.js +25 -8
  32. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  33. package/dist/resources/extensions/gsd/guided-flow.js +7 -3
  34. package/dist/resources/extensions/gsd/journal.js +85 -0
  35. package/dist/resources/extensions/gsd/md-importer.js +5 -0
  36. package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
  38. package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
  39. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  40. package/dist/resources/extensions/gsd/preferences.js +1 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
  42. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  43. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  44. package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  46. package/dist/resources/extensions/gsd/repo-identity.js +46 -2
  47. package/dist/resources/extensions/gsd/rule-registry.js +489 -0
  48. package/dist/resources/extensions/gsd/rule-types.js +6 -0
  49. package/dist/resources/extensions/gsd/service-tier.js +138 -0
  50. package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
  51. package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
  52. package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
  53. package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
  54. package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
  55. package/dist/resources/extensions/subagent/index.js +7 -3
  56. package/dist/resources/extensions/voice/index.js +4 -4
  57. package/dist/web/standalone/.next/BUILD_ID +1 -1
  58. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  59. package/dist/web/standalone/.next/build-manifest.json +3 -3
  60. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  61. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  63. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/index.html +1 -1
  94. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  101. package/dist/web/standalone/.next/server/chunks/229.js +3 -3
  102. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  105. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  106. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  107. package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
  108. package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
  109. package/dist/web-mode.d.ts +2 -0
  110. package/dist/web-mode.js +29 -7
  111. package/package.json +1 -1
  112. package/packages/native/src/__tests__/text.test.mjs +33 -0
  113. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
  114. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
  118. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
  119. package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
  120. package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
  121. package/src/resources/extensions/gsd/auto/loop.ts +10 -1
  122. package/src/resources/extensions/gsd/auto/phases.ts +28 -8
  123. package/src/resources/extensions/gsd/auto/types.ts +4 -0
  124. package/src/resources/extensions/gsd/auto-dashboard.ts +7 -2
  125. package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
  126. package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
  127. package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
  128. package/src/resources/extensions/gsd/auto-start.ts +8 -3
  129. package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
  130. package/src/resources/extensions/gsd/auto.ts +40 -1
  131. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
  132. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
  133. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  134. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
  135. package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
  136. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  137. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  138. package/src/resources/extensions/gsd/context-store.ts +4 -3
  139. package/src/resources/extensions/gsd/db-writer.ts +6 -2
  140. package/src/resources/extensions/gsd/detection.ts +1 -1
  141. package/src/resources/extensions/gsd/doctor.ts +12 -1
  142. package/src/resources/extensions/gsd/exit-command.ts +14 -2
  143. package/src/resources/extensions/gsd/export.ts +8 -15
  144. package/src/resources/extensions/gsd/extension-manifest.json +2 -2
  145. package/src/resources/extensions/gsd/files.ts +29 -12
  146. package/src/resources/extensions/gsd/forensics.ts +9 -3
  147. package/src/resources/extensions/gsd/git-service.ts +5 -4
  148. package/src/resources/extensions/gsd/gsd-db.ts +37 -8
  149. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  150. package/src/resources/extensions/gsd/guided-flow.ts +7 -3
  151. package/src/resources/extensions/gsd/journal.ts +134 -0
  152. package/src/resources/extensions/gsd/md-importer.ts +6 -0
  153. package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
  154. package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
  155. package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
  156. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  157. package/src/resources/extensions/gsd/preferences.ts +1 -0
  158. package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
  159. package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
  160. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  161. package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
  162. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  163. package/src/resources/extensions/gsd/repo-identity.ts +47 -2
  164. package/src/resources/extensions/gsd/rule-registry.ts +599 -0
  165. package/src/resources/extensions/gsd/rule-types.ts +68 -0
  166. package/src/resources/extensions/gsd/service-tier.ts +171 -0
  167. package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
  168. package/src/resources/extensions/gsd/templates/decisions.md +2 -2
  169. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
  170. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
  171. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
  172. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
  173. package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
  174. package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
  175. package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
  176. package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
  177. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
  178. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
  179. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
  180. package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
  181. package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
  182. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
  183. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
  184. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
  185. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
  186. package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
  187. package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
  188. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  189. package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
  190. package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
  191. package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
  192. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
  193. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
  194. package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
  195. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
  196. package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
  197. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
  198. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
  199. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
  200. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
  201. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
  202. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
  203. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
  204. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
  205. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
  206. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
  207. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
  208. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
  209. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
  210. package/src/resources/extensions/gsd/types.ts +3 -0
  211. package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
  212. package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
  213. package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
  214. package/src/resources/extensions/subagent/index.ts +7 -3
  215. package/src/resources/extensions/voice/index.ts +4 -4
  216. package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
  217. /package/dist/web/standalone/.next/static/{02cti5IXH7FycOqkbAkWL → ZrI3HOoXD7Fh84fAHZVxb}/_buildManifest.js +0 -0
  218. /package/dist/web/standalone/.next/static/{02cti5IXH7FycOqkbAkWL → ZrI3HOoXD7Fh84fAHZVxb}/_ssgManifest.js +0 -0
@@ -386,6 +386,91 @@ test("verifyExpectedArtifact plan-slice fails for plan with no tasks (#699)", ()
386
386
  }
387
387
  });
388
388
 
389
+ // ─── verifyExpectedArtifact: heading-style plan tasks (#1691) ─────────────
390
+
391
+ test("verifyExpectedArtifact accepts plan-slice with heading-style tasks (### T01 --)", () => {
392
+ const base = makeTmpBase();
393
+ try {
394
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
395
+ const tasksDir = join(sliceDir, "tasks");
396
+ mkdirSync(tasksDir, { recursive: true });
397
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
398
+ "# S01: Test Slice",
399
+ "",
400
+ "## Tasks",
401
+ "",
402
+ "### T01 -- Implement feature",
403
+ "",
404
+ "Feature description.",
405
+ "",
406
+ "### T02 -- Write tests",
407
+ "",
408
+ "Test description.",
409
+ ].join("\n"));
410
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
411
+ writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02 Plan");
412
+ assert.strictEqual(
413
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
414
+ true,
415
+ "Heading-style plan with task entries should be treated as completed artifact",
416
+ );
417
+ } finally {
418
+ cleanup(base);
419
+ }
420
+ });
421
+
422
+ test("verifyExpectedArtifact accepts plan-slice with colon-style heading tasks (### T01:)", () => {
423
+ const base = makeTmpBase();
424
+ try {
425
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
426
+ const tasksDir = join(sliceDir, "tasks");
427
+ mkdirSync(tasksDir, { recursive: true });
428
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
429
+ "# S01: Test Slice",
430
+ "",
431
+ "## Tasks",
432
+ "",
433
+ "### T01: Implement feature",
434
+ "",
435
+ "Feature description.",
436
+ ].join("\n"));
437
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01 Plan");
438
+ assert.strictEqual(
439
+ verifyExpectedArtifact("plan-slice", "M001/S01", base),
440
+ true,
441
+ "Colon heading-style plan should be treated as completed artifact",
442
+ );
443
+ } finally {
444
+ cleanup(base);
445
+ }
446
+ });
447
+
448
+ test("verifyExpectedArtifact execute-task passes for heading-style plan entry (#1691)", () => {
449
+ const base = makeTmpBase();
450
+ try {
451
+ const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
452
+ const tasksDir = join(sliceDir, "tasks");
453
+ mkdirSync(tasksDir, { recursive: true });
454
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
455
+ "# S01: Test Slice",
456
+ "",
457
+ "## Tasks",
458
+ "",
459
+ "### T01 -- Implement feature",
460
+ "",
461
+ "Feature description.",
462
+ ].join("\n"));
463
+ writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "# T01 Summary\n\nDone.");
464
+ assert.strictEqual(
465
+ verifyExpectedArtifact("execute-task", "M001/S01/T01", base),
466
+ true,
467
+ "execute-task should pass for heading-style plan entry when summary exists",
468
+ );
469
+ } finally {
470
+ cleanup(base);
471
+ }
472
+ });
473
+
389
474
  // ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
390
475
 
391
476
  test("selfHealRuntimeRecords clears stale dispatched records (#769)", async () => {
@@ -101,8 +101,8 @@ test('secrets gate: pending keys exist — gate triggers collection, manifest up
101
101
  const status = await getManifestStatus(tmp, 'M001');
102
102
  assert.notStrictEqual(status, null, 'manifest should exist');
103
103
  assert.ok(status!.pending.length > 0, 'should have pending keys');
104
- assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B']);
105
- assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING']);
104
+ assert.deepStrictEqual(status!.pending, ['GSD_GATE_TEST_PEND_A', 'GSD_GATE_TEST_PEND_B'], 'pending keys');
105
+ assert.deepStrictEqual(status!.existing, ['GSD_GATE_TEST_EXISTING'], 'existing keys');
106
106
 
107
107
  // (b) Call collectSecretsFromManifest with no-UI context
108
108
  // With hasUI: false, collectOneSecret returns null → pending keys become "skipped"
@@ -569,6 +569,208 @@ async function main(): Promise<void> {
569
569
  assertTrue(existsSync(join(repo, "landed.ts")), "landed.ts present on main");
570
570
  }
571
571
 
572
+ // ─── Test 14: Stale branch ref — worktree HEAD ahead of branch (#1846) ─
573
+ console.log("\n=== stale branch ref — fast-forward before squash merge (#1846) ===");
574
+ {
575
+ const repo = freshRepo();
576
+ const wtPath = createAutoWorktree(repo, "M140");
577
+
578
+ // Add a first slice normally — this advances both the branch ref and HEAD
579
+ addSliceToMilestone(repo, wtPath, "M140", "S01", "Initial work", [
580
+ { file: "initial.ts", content: "export const initial = true;\n", message: "add initial" },
581
+ ]);
582
+
583
+ // Now simulate the bug: detach HEAD in the worktree, then make commits
584
+ // that advance HEAD but leave the milestone/M140 branch ref behind.
585
+ const branchRefBefore = run("git rev-parse milestone/M140", wtPath);
586
+ run("git checkout --detach HEAD", wtPath);
587
+
588
+ // Add multiple commits on the detached HEAD (simulates agent work)
589
+ writeFileSync(join(wtPath, "feature-a.ts"), "export const featureA = true;\n");
590
+ run("git add .", wtPath);
591
+ run('git commit -m "add feature-a"', wtPath);
592
+
593
+ writeFileSync(join(wtPath, "feature-b.ts"), "export const featureB = true;\n");
594
+ run("git add .", wtPath);
595
+ run('git commit -m "add feature-b"', wtPath);
596
+
597
+ writeFileSync(join(wtPath, "feature-c.ts"), "export const featureC = true;\n");
598
+ run("git add .", wtPath);
599
+ run('git commit -m "add feature-c"', wtPath);
600
+
601
+ // Verify: branch ref is stale, HEAD is ahead
602
+ const branchRefAfter = run("git rev-parse milestone/M140", wtPath);
603
+ const worktreeHead = run("git rev-parse HEAD", wtPath);
604
+ assertEq(branchRefBefore, branchRefAfter, "branch ref unchanged (stale)");
605
+ assertTrue(worktreeHead !== branchRefAfter, "worktree HEAD ahead of branch ref");
606
+
607
+ const roadmap = makeRoadmap("M140", "Stale ref milestone", [
608
+ { id: "S01", title: "Initial work" },
609
+ ]);
610
+
611
+ // The fix should fast-forward the branch ref to worktree HEAD before
612
+ // squash-merging, so ALL commits are captured.
613
+ let threw = false;
614
+ let errMsg = "";
615
+ try {
616
+ const result = mergeMilestoneToMain(repo, "M140", roadmap);
617
+ assertTrue(result.commitMessage.includes("feat(M140)"), "merge commit created");
618
+ } catch (err) {
619
+ threw = true;
620
+ errMsg = err instanceof Error ? err.message : String(err);
621
+ }
622
+ assertTrue(!threw, `should not throw with stale branch ref (got: ${errMsg})`);
623
+
624
+ // ALL files from detached HEAD commits must be on main — not just
625
+ // the ones from the stale branch ref
626
+ assertTrue(existsSync(join(repo, "initial.ts")), "initial.ts on main");
627
+ assertTrue(existsSync(join(repo, "feature-a.ts")), "feature-a.ts on main (#1846)");
628
+ assertTrue(existsSync(join(repo, "feature-b.ts")), "feature-b.ts on main (#1846)");
629
+ assertTrue(existsSync(join(repo, "feature-c.ts")), "feature-c.ts on main (#1846)");
630
+ }
631
+
632
+ // ─── Test 15: Diverged worktree HEAD — throws instead of losing data (#1846) ─
633
+ console.log("\n=== diverged worktree HEAD — throws on divergence (#1846) ===");
634
+ {
635
+ const repo = freshRepo();
636
+ const wtPath = createAutoWorktree(repo, "M150");
637
+
638
+ addSliceToMilestone(repo, wtPath, "M150", "S01", "Base work", [
639
+ { file: "base.ts", content: "export const base = true;\n", message: "add base" },
640
+ ]);
641
+
642
+ run("git checkout --detach HEAD", wtPath);
643
+ writeFileSync(join(wtPath, "detached-work.ts"), "export const detached = true;\n");
644
+ run("git add .", wtPath);
645
+ run('git commit -m "detached work"', wtPath);
646
+
647
+ run("git checkout milestone/M150", repo);
648
+ writeFileSync(join(repo, "diverged-work.ts"), "export const diverged = true;\n");
649
+ run("git add .", repo);
650
+ run('git commit -m "diverged work on branch"', repo);
651
+ run("git checkout main", repo);
652
+
653
+ process.chdir(wtPath);
654
+
655
+ const roadmap = makeRoadmap("M150", "Diverged milestone", [
656
+ { id: "S01", title: "Base work" },
657
+ ]);
658
+
659
+ let threw = false;
660
+ let errMsg = "";
661
+ try {
662
+ mergeMilestoneToMain(repo, "M150", roadmap);
663
+ } catch (err) {
664
+ threw = true;
665
+ errMsg = err instanceof Error ? err.message : String(err);
666
+ }
667
+ assertTrue(threw, "throws when worktree HEAD diverged from branch ref (#1846)");
668
+ assertTrue(errMsg.includes("diverged"), "error message mentions divergence (#1846)");
669
+
670
+ const branches = run("git branch", repo);
671
+ assertTrue(branches.includes("milestone/M150"), "milestone branch preserved on divergence (#1846)");
672
+ }
673
+
674
+ // ─── Test 16: #1853 Bug 1 — SQUASH_MSG cleaned up after squash-merge ──
675
+ console.log("\n=== #1853 bug 1: SQUASH_MSG cleaned up after successful squash-merge ===");
676
+ {
677
+ const repo = freshRepo();
678
+ const wtPath = createAutoWorktree(repo, "M160");
679
+
680
+ addSliceToMilestone(repo, wtPath, "M160", "S01", "SQUASH_MSG cleanup test", [
681
+ { file: "squash-cleanup.ts", content: "export const cleanup = true;\n", message: "add squash-cleanup" },
682
+ ]);
683
+
684
+ const roadmap = makeRoadmap("M160", "SQUASH_MSG cleanup", [
685
+ { id: "S01", title: "SQUASH_MSG cleanup test" },
686
+ ]);
687
+
688
+ const squashMsgPath = join(repo, ".git", "SQUASH_MSG");
689
+ writeFileSync(squashMsgPath, "leftover squash message\n");
690
+ assertTrue(existsSync(squashMsgPath), "SQUASH_MSG planted before merge");
691
+
692
+ const result = mergeMilestoneToMain(repo, "M160", roadmap);
693
+ assertTrue(result.commitMessage.includes("feat(M160)"), "merge commit created");
694
+
695
+ assertTrue(
696
+ !existsSync(squashMsgPath),
697
+ "#1853: SQUASH_MSG must not persist after successful squash-merge",
698
+ );
699
+ }
700
+
701
+ // ─── Test 17: #1853 Bug 2 — uncommitted worktree code survives teardown ──
702
+ console.log("\n=== #1853 bug 2: uncommitted worktree changes committed before teardown ===");
703
+ {
704
+ const repo = freshRepo();
705
+ const wtPath = createAutoWorktree(repo, "M170");
706
+
707
+ addSliceToMilestone(repo, wtPath, "M170", "S01", "Teardown safety test", [
708
+ { file: "safe-file.ts", content: "export const safe = true;\n", message: "add safe file" },
709
+ ]);
710
+
711
+ writeFileSync(join(wtPath, "uncommitted-agent-code.ts"), "export const lost = true;\n");
712
+
713
+ const roadmap = makeRoadmap("M170", "Teardown safety", [
714
+ { id: "S01", title: "Teardown safety test" },
715
+ ]);
716
+
717
+ const result = mergeMilestoneToMain(repo, "M170", roadmap);
718
+ assertTrue(result.commitMessage.includes("feat(M170)"), "merge commit created");
719
+
720
+ assertTrue(
721
+ existsSync(join(repo, "uncommitted-agent-code.ts")),
722
+ "#1853: uncommitted worktree code must survive teardown",
723
+ );
724
+ }
725
+
726
+ // ─── Test 18: #1906 — codeFilesChanged false when only .gsd/ metadata merged ──
727
+ console.log("\n=== #1906: codeFilesChanged=false when only .gsd/ metadata merged ===");
728
+ {
729
+ const repo = freshRepo();
730
+ const wtPath = createAutoWorktree(repo, "M180");
731
+
732
+ // Only add .gsd/ metadata files — no actual code
733
+ mkdirSync(join(wtPath, ".gsd", "milestones", "M180"), { recursive: true });
734
+ writeFileSync(
735
+ join(wtPath, ".gsd", "milestones", "M180", "SUMMARY.md"),
736
+ "# M180 Summary\n\nThis milestone was planned but not implemented.\n",
737
+ );
738
+ run("git add .", wtPath);
739
+ run('git commit -m "chore: add milestone summary"', wtPath);
740
+
741
+ const roadmap = makeRoadmap("M180", "Metadata-only milestone", []);
742
+
743
+ const result = mergeMilestoneToMain(repo, "M180", roadmap);
744
+ assertEq(
745
+ result.codeFilesChanged,
746
+ false,
747
+ "#1906: codeFilesChanged must be false when only .gsd/ files were merged",
748
+ );
749
+ }
750
+
751
+ // ─── Test 19: #1906 — codeFilesChanged true when real code is merged ──
752
+ console.log("\n=== #1906: codeFilesChanged=true when real code is merged ===");
753
+ {
754
+ const repo = freshRepo();
755
+ const wtPath = createAutoWorktree(repo, "M190");
756
+
757
+ addSliceToMilestone(repo, wtPath, "M190", "S01", "Real code", [
758
+ { file: "real-code.ts", content: "export const real = true;\n", message: "add real code" },
759
+ ]);
760
+
761
+ const roadmap = makeRoadmap("M190", "Code milestone", [
762
+ { id: "S01", title: "Real code" },
763
+ ]);
764
+
765
+ const result = mergeMilestoneToMain(repo, "M190", roadmap);
766
+ assertEq(
767
+ result.codeFilesChanged,
768
+ true,
769
+ "#1906: codeFilesChanged must be true when real code files were merged",
770
+ );
771
+ assertTrue(existsSync(join(repo, "real-code.ts")), "real-code.ts merged to main");
772
+ }
773
+
572
774
  } finally {
573
775
  process.chdir(savedCwd);
574
776
  for (const d of tempDirs) {
@@ -119,12 +119,23 @@ test("captures: loadPendingCaptures filters resolved entries", () => {
119
119
  const id1 = appendCapture(tmp, "pending one");
120
120
  appendCapture(tmp, "pending two");
121
121
 
122
- // Resolve the first one
123
122
  markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
124
123
 
125
124
  const pending = loadPendingCaptures(tmp);
126
125
  assert.strictEqual(pending.length, 1, "should have 1 pending");
127
126
  assert.strictEqual(pending[0].text, "pending two");
127
+ } finally {
128
+ rmSync(tmp, { recursive: true, force: true });
129
+ }
130
+ });
131
+
132
+ test("captures: loadAllCaptures preserves resolved entries", () => {
133
+ const tmp = makeTempDir("cap-all-resolved");
134
+ try {
135
+ const id1 = appendCapture(tmp, "pending one");
136
+ appendCapture(tmp, "pending two");
137
+
138
+ markCaptureResolved(tmp, id1, "note", "acknowledged", "just a note");
128
139
 
129
140
  const all = loadAllCaptures(tmp);
130
141
  assert.strictEqual(all.length, 2, "all should still have 2");
@@ -51,17 +51,17 @@ console.log('\n=== context-store: query all active decisions ===');
51
51
  insertDecision({
52
52
  id: 'D001', when_context: 'M001/S01', scope: 'architecture',
53
53
  decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
54
- revisable: 'yes', superseded_by: 'D003', // superseded!
54
+ revisable: 'yes', made_by: 'agent', superseded_by: 'D003', // superseded!
55
55
  });
56
56
  insertDecision({
57
57
  id: 'D002', when_context: 'M001/S01', scope: 'architecture',
58
58
  decision: 'use WAL mode', choice: 'WAL', rationale: 'concurrent reads',
59
- revisable: 'no', superseded_by: null,
59
+ revisable: 'no', made_by: 'agent', superseded_by: null,
60
60
  });
61
61
  insertDecision({
62
62
  id: 'D003', when_context: 'M002/S01', scope: 'performance',
63
63
  decision: 'use better-sqlite3', choice: 'better-sqlite3', rationale: 'faster',
64
- revisable: 'yes', superseded_by: null,
64
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
65
65
  });
66
66
 
67
67
  const all = queryDecisions();
@@ -81,11 +81,13 @@ console.log('\n=== context-store: query decisions by milestone ===');
81
81
  insertDecision({
82
82
  id: 'D001', when_context: 'M001/S01', scope: 'architecture',
83
83
  decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
84
+ made_by: 'agent',
84
85
  superseded_by: null,
85
86
  });
86
87
  insertDecision({
87
88
  id: 'D002', when_context: 'M002/S02', scope: 'architecture',
88
89
  decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
90
+ made_by: 'agent',
89
91
  superseded_by: null,
90
92
  });
91
93
 
@@ -107,11 +109,13 @@ console.log('\n=== context-store: query decisions by scope ===');
107
109
  insertDecision({
108
110
  id: 'D001', when_context: 'M001/S01', scope: 'architecture',
109
111
  decision: 'decision A', choice: 'A', rationale: 'r', revisable: 'yes',
112
+ made_by: 'agent',
110
113
  superseded_by: null,
111
114
  });
112
115
  insertDecision({
113
116
  id: 'D002', when_context: 'M001/S01', scope: 'performance',
114
117
  decision: 'decision B', choice: 'B', rationale: 'r', revisable: 'yes',
118
+ made_by: 'agent',
115
119
  superseded_by: null,
116
120
  });
117
121
 
@@ -248,12 +252,12 @@ console.log('\n=== context-store: formatDecisionsForPrompt ===');
248
252
  {
249
253
  seq: 1, id: 'D001', when_context: 'M001/S01', scope: 'architecture',
250
254
  decision: 'use SQLite', choice: 'node:sqlite', rationale: 'built-in',
251
- revisable: 'yes', superseded_by: null,
255
+ revisable: 'yes', made_by: 'agent', superseded_by: null,
252
256
  },
253
257
  {
254
258
  seq: 2, id: 'D002', when_context: 'M001/S02', scope: 'performance',
255
259
  decision: 'use WAL', choice: 'WAL', rationale: 'concurrent',
256
- revisable: 'no', superseded_by: null,
260
+ revisable: 'no', made_by: 'human', superseded_by: null,
257
261
  },
258
262
  ]);
259
263
 
@@ -323,6 +327,7 @@ console.log('\n=== context-store: sub-5ms query timing ===');
323
327
  choice: `choice ${i}`,
324
328
  rationale: `rationale ${i}`,
325
329
  revisable: i % 3 === 0 ? 'no' : 'yes',
330
+ made_by: 'agent',
326
331
  superseded_by: null,
327
332
  });
328
333
  }
@@ -72,18 +72,17 @@ describe("continue-here", () => {
72
72
  const budget = computeBudgets(128_000);
73
73
  const threshold = budget.continueThresholdPercent;
74
74
 
75
- // Simulate repeated polls with percent above threshold
76
- let fired = false;
77
- let fireCount = 0;
75
+ // Simulate repeated polls with percent above threshold using a reducer
76
+ // so there is no control flow inside the test body.
78
77
  const usagePercents = [75, 80, 85, 90, 95];
79
-
80
- for (const percent of usagePercents) {
81
- if (fired) continue; // one-shot guard
82
- if (percent >= threshold) {
83
- fired = true;
84
- fireCount++;
85
- }
86
- }
78
+ const { fired, fireCount } = usagePercents.reduce(
79
+ (acc, percent) => {
80
+ if (acc.fired) return acc; // one-shot guard
81
+ if (percent >= threshold) return { fired: true, fireCount: acc.fireCount + 1 };
82
+ return acc;
83
+ },
84
+ { fired: false, fireCount: 0 },
85
+ );
87
86
 
88
87
  assert.equal(fireCount, 1, "must fire exactly once");
89
88
  assert.equal(fired, true);
@@ -97,16 +96,17 @@ describe("continue-here", () => {
97
96
  { name: "1M", contextWindow: 1_000_000 },
98
97
  ];
99
98
 
100
- it("all model sizes produce continueThresholdPercent of 70", () => {
101
- for (const { name, contextWindow } of modelSizes) {
99
+ const thresholdCases: Array<[string, number]> = [
100
+ ["128K", 128_000],
101
+ ["200K", 200_000],
102
+ ["1M", 1_000_000],
103
+ ];
104
+ for (const [name, contextWindow] of thresholdCases) {
105
+ it(`${name} model produces continueThresholdPercent of 70`, () => {
102
106
  const budget = computeBudgets(contextWindow);
103
- assert.equal(
104
- budget.continueThresholdPercent,
105
- 70,
106
- `${name} model should have 70% threshold`,
107
- );
108
- }
109
- });
107
+ assert.equal(budget.continueThresholdPercent, 70, `${name} model should have 70% threshold`);
108
+ });
109
+ }
110
110
 
111
111
  it("larger models produce larger verificationBudgetChars", () => {
112
112
  const budgets = modelSizes.map(({ contextWindow }) => computeBudgets(contextWindow));
@@ -59,6 +59,7 @@ const SAMPLE_DECISIONS: Decision[] = [
59
59
  choice: 'better-sqlite3',
60
60
  rationale: 'Sync API',
61
61
  revisable: 'No',
62
+ made_by: 'collaborative',
62
63
  superseded_by: null,
63
64
  },
64
65
  {
@@ -70,6 +71,7 @@ const SAMPLE_DECISIONS: Decision[] = [
70
71
  choice: '.gsd/gsd.db',
71
72
  rationale: 'Derived state',
72
73
  revisable: 'No',
74
+ made_by: 'agent',
73
75
  superseded_by: null,
74
76
  },
75
77
  {
@@ -81,6 +83,7 @@ const SAMPLE_DECISIONS: Decision[] = [
81
83
  choice: 'node:sqlite fallback',
82
84
  rationale: 'Zero deps',
83
85
  revisable: 'Yes',
86
+ made_by: 'human',
84
87
  superseded_by: null,
85
88
  },
86
89
  ];
@@ -166,6 +169,7 @@ console.log('\n── generateDecisionsMd round-trip ──');
166
169
  assertEq(rt.choice, orig.choice, `decision ${orig.id} choice round-trips`);
167
170
  assertEq(rt.rationale, orig.rationale, `decision ${orig.id} rationale round-trips`);
168
171
  assertEq(rt.revisable, orig.revisable, `decision ${orig.id} revisable round-trips`);
172
+ assertEq(rt.made_by, orig.made_by, `decision ${orig.id} made_by round-trips`);
169
173
  }
170
174
  }
171
175
 
@@ -177,6 +181,7 @@ console.log('\n── generateDecisionsMd format ──');
177
181
  assertTrue(md.includes('<!-- Append-only'), 'contains HTML comment block');
178
182
  assertTrue(md.includes('| # | When | Scope'), 'contains table header');
179
183
  assertTrue(md.includes('|---|------|-------'), 'contains separator row');
184
+ assertTrue(md.includes('| Made By |'), 'contains Made By column header');
180
185
  }
181
186
 
182
187
  console.log('\n── generateDecisionsMd empty input ──');
@@ -200,6 +205,7 @@ console.log('\n── generateDecisionsMd pipe escaping ──');
200
205
  choice: 'A',
201
206
  rationale: 'Better',
202
207
  revisable: 'No',
208
+ made_by: 'agent',
203
209
  superseded_by: null,
204
210
  };
205
211
  const md = generateDecisionsMd([withPipe]);
@@ -291,6 +297,7 @@ console.log('\n── nextDecisionId ──');
291
297
  choice: 'test choice',
292
298
  rationale: 'test',
293
299
  revisable: 'No',
300
+ made_by: 'agent',
294
301
  superseded_by: null,
295
302
  });
296
303
  upsertDecision({
@@ -301,6 +308,7 @@ console.log('\n── nextDecisionId ──');
301
308
  choice: 'test choice',
302
309
  rationale: 'test',
303
310
  revisable: 'No',
311
+ made_by: 'agent',
304
312
  superseded_by: null,
305
313
  });
306
314
 
@@ -520,6 +528,7 @@ console.log('\n── Full DB round-trip: decisions ──');
520
528
  choice: d.choice,
521
529
  rationale: d.rationale,
522
530
  revisable: d.revisable,
531
+ made_by: d.made_by,
523
532
  superseded_by: d.superseded_by,
524
533
  });
525
534
  }
@@ -536,6 +545,7 @@ console.log('\n── Full DB round-trip: decisions ──');
536
545
  choice: row['choice'] as string,
537
546
  rationale: row['rationale'] as string,
538
547
  revisable: row['revisable'] as string,
548
+ made_by: (row['made_by'] as string as import('../types.js').DecisionMadeBy) ?? 'agent',
539
549
  superseded_by: (row['superseded_by'] as string) ?? null,
540
550
  }));
541
551
 
@@ -80,7 +80,7 @@ test("COMPLETION_TRANSITION_CODES only contains slice summary code", () => {
80
80
  );
81
81
  });
82
82
 
83
- test("fixLevel:task — fixes roadmap checkbox and UAT stub immediately, defers only summary (#1808)", async () => {
83
+ test("fixLevel:task — fixes UAT stub immediately, defers summary and roadmap checkbox (#1808, #1910)", async () => {
84
84
  const tmp = makeTmp("partial-deferral");
85
85
  try {
86
86
  buildScaffold(tmp);
@@ -101,15 +101,16 @@ test("fixLevel:task — fixes roadmap checkbox and UAT stub immediately, defers
101
101
  const sliceUatPath = join(tmp, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
102
102
  assert.ok(existsSync(sliceUatPath), "should have created UAT stub immediately");
103
103
 
104
- // Roadmap checkbox SHOULD be marked done (mechanical bookkeeping, no longer deferred)
104
+ // Roadmap checkbox must NOT be checked without summary on disk (#1910).
105
+ // Checking it without the summary causes deriveState() to skip complete-slice.
105
106
  const roadmapContent = readFileSync(join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "utf8");
106
- assert.ok(roadmapContent.includes("- [x] **S01"), "roadmap should show S01 as checked");
107
+ assert.ok(roadmapContent.includes("- [ ] **S01"), "roadmap must NOT be checked without summary on disk (#1910)");
107
108
  } finally {
108
109
  rmSync(tmp, { recursive: true, force: true });
109
110
  }
110
111
  });
111
112
 
112
- test("fixLevel:task — session crash after last task leaves roadmap and UAT consistent (#1808)", async () => {
113
+ test("fixLevel:task — session crash after last task leaves UAT consistent, roadmap deferred with summary (#1808, #1910)", async () => {
113
114
  const tmp = makeTmp("crash-consistency");
114
115
  try {
115
116
  buildScaffold(tmp);
@@ -121,13 +122,7 @@ test("fixLevel:task — session crash after last task leaves roadmap and UAT con
121
122
  // A new session starts and runs doctor again at task level.
122
123
  const report2 = await runGSDDoctor(tmp, { fix: true, fixLevel: "task" });
123
124
 
124
- // The only remaining issue should be the deferred summary.
125
- // Roadmap and UAT should already be fixed from the first run.
126
125
  const remainingCodes = report2.issues.map(i => i.code);
127
- assert.ok(
128
- !remainingCodes.includes("all_tasks_done_roadmap_not_checked"),
129
- "roadmap should already be fixed from first doctor run"
130
- );
131
126
  assert.ok(
132
127
  !remainingCodes.includes("all_tasks_done_missing_slice_uat"),
133
128
  "UAT should already be fixed from first doctor run"
@@ -137,6 +132,16 @@ test("fixLevel:task — session crash after last task leaves roadmap and UAT con
137
132
  remainingCodes.includes("all_tasks_done_missing_slice_summary"),
138
133
  "summary should still be detected as missing (deferred)"
139
134
  );
135
+ // Roadmap should still be unchecked because summary doesn't exist (#1910)
136
+ assert.ok(
137
+ remainingCodes.includes("all_tasks_done_roadmap_not_checked"),
138
+ "roadmap should still be unchecked — summary does not exist on disk (#1910)"
139
+ );
140
+ // Must NOT produce the cascade error from checking roadmap without summary
141
+ assert.ok(
142
+ !remainingCodes.includes("slice_checked_missing_summary"),
143
+ "must not produce slice_checked_missing_summary (#1910)"
144
+ );
140
145
  } finally {
141
146
  rmSync(tmp, { recursive: true, force: true });
142
147
  }
@@ -63,7 +63,7 @@ Done.
63
63
  `);
64
64
  }
65
65
 
66
- test("fixLevel:task — defers only summary stub, fixes roadmap and UAT immediately (#1808)", async () => {
66
+ test("fixLevel:task — defers summary stub and roadmap checkbox, fixes UAT immediately (#1808, #1910)", async () => {
67
67
  const tmp = makeTmp("task-level");
68
68
  try {
69
69
  buildScaffold(tmp);
@@ -79,13 +79,14 @@ test("fixLevel:task — defers only summary stub, fixes roadmap and UAT immediat
79
79
  const sliceSummaryPath = join(tmp, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
80
80
  assert.ok(!existsSync(sliceSummaryPath), "should NOT have created summary stub");
81
81
 
82
- // Roadmap SHOULD be marked done (mechanical bookkeeping, no longer deferred)
82
+ // Roadmap must NOT be checked without summary on disk (#1910)
83
83
  const roadmapContent = readFileSync(join(tmp, ".gsd", "milestones", "M001", "M001-ROADMAP.md"), "utf8");
84
- assert.ok(roadmapContent.includes("- [x] **S01"), "roadmap should show S01 as checked");
84
+ assert.ok(roadmapContent.includes("- [ ] **S01"), "roadmap must NOT be checked without summary (#1910)");
85
85
 
86
- // Fixes applied should NOT include summary but SHOULD include roadmap
86
+ // Fixes applied should NOT include summary or roadmap
87
87
  for (const f of report.fixesApplied) {
88
88
  assert.ok(!f.includes("SUMMARY"), `should not have fixed summary: ${f}`);
89
+ assert.ok(!f.includes("ROADMAP") && !f.includes("roadmap"), `should not have fixed roadmap: ${f}`);
89
90
  }
90
91
  } finally {
91
92
  rmSync(tmp, { recursive: true, force: true });