gsd-pi 2.82.0-dev.3709f22a5 → 2.82.0-dev.4285182e8

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 (193) hide show
  1. package/README.md +2 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/loop.js +14 -1
  5. package/dist/resources/extensions/gsd/auto/phases.js +53 -29
  6. package/dist/resources/extensions/gsd/auto/session.js +4 -0
  7. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
  8. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +13 -19
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
  11. package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
  12. package/dist/resources/extensions/gsd/auto-start.js +3 -3
  13. package/dist/resources/extensions/gsd/auto-verification.js +17 -4
  14. package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
  15. package/dist/resources/extensions/gsd/auto.js +14 -8
  16. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
  18. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
  19. package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
  20. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  21. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
  23. package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
  24. package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
  25. package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
  26. package/dist/resources/extensions/gsd/forensics.js +3 -3
  27. package/dist/resources/extensions/gsd/git-service.js +6 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +20 -6
  29. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
  30. package/dist/resources/extensions/gsd/guided-flow.js +8 -5
  31. package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
  32. package/dist/resources/extensions/gsd/paths.js +4 -0
  33. package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
  34. package/dist/resources/extensions/gsd/state.js +3 -3
  35. package/dist/resources/extensions/gsd/status-guards.js +7 -0
  36. package/dist/resources/extensions/gsd/templates/plan.md +1 -0
  37. package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
  38. package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
  39. package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
  40. package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
  41. package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
  42. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  43. package/dist/web/standalone/.next/BUILD_ID +1 -1
  44. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  45. package/dist/web/standalone/.next/build-manifest.json +3 -3
  46. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  47. package/dist/web/standalone/.next/react-loadable-manifest.json +3 -3
  48. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/index.html +1 -1
  66. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  73. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  76. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  77. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  78. package/dist/web/standalone/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
  79. package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
  80. package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
  81. package/package.json +1 -1
  82. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
  84. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  85. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
  86. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
  88. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
  89. package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
  90. package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
  91. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  92. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
  93. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
  95. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
  98. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +25 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +24 -10
  104. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  105. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
  106. package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
  107. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
  108. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +30 -1
  109. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +29 -10
  110. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  111. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
  112. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
  113. package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
  114. package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
  115. package/packages/pi-tui/dist/terminal.d.ts +2 -0
  116. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  117. package/packages/pi-tui/dist/terminal.js +12 -0
  118. package/packages/pi-tui/dist/terminal.js.map +1 -1
  119. package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
  120. package/packages/pi-tui/src/terminal.ts +11 -0
  121. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  122. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
  123. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
  124. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  125. package/src/resources/extensions/gsd/auto/phases.ts +60 -36
  126. package/src/resources/extensions/gsd/auto/session.ts +4 -0
  127. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
  128. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
  129. package/src/resources/extensions/gsd/auto-dispatch.ts +13 -19
  130. package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
  131. package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
  132. package/src/resources/extensions/gsd/auto-start.ts +2 -3
  133. package/src/resources/extensions/gsd/auto-verification.ts +22 -2
  134. package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
  135. package/src/resources/extensions/gsd/auto.ts +13 -8
  136. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
  137. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
  138. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
  139. package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
  140. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  141. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  142. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
  143. package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
  144. package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
  145. package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
  146. package/src/resources/extensions/gsd/forensics.ts +3 -3
  147. package/src/resources/extensions/gsd/git-service.ts +6 -3
  148. package/src/resources/extensions/gsd/gsd-db.ts +18 -6
  149. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
  150. package/src/resources/extensions/gsd/guided-flow.ts +8 -5
  151. package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
  152. package/src/resources/extensions/gsd/paths.ts +5 -0
  153. package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
  154. package/src/resources/extensions/gsd/state.ts +3 -3
  155. package/src/resources/extensions/gsd/status-guards.ts +8 -0
  156. package/src/resources/extensions/gsd/templates/plan.md +1 -0
  157. package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
  158. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
  159. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
  160. package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
  161. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
  162. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
  163. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
  164. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
  165. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  166. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
  167. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
  168. package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
  169. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
  170. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
  171. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
  172. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
  173. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
  174. package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
  175. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
  176. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
  177. package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
  178. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
  179. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
  180. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
  181. package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
  182. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
  183. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
  184. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  185. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
  186. package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
  187. package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
  188. package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
  189. package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
  190. package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
  191. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
  192. /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
  193. /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
@@ -591,7 +591,7 @@ async function handleAllSlicesDone(
591
591
  recentDecisions: [],
592
592
  blockers: [
593
593
  `Milestone ${activeMilestone.id} validation verdict is needs-remediation but all slices are complete. ` +
594
- `Add remediation slices via gsd_reassess_roadmap or override the verdict manually.`,
594
+ `Add remediation slices via gsd_reassess_roadmap, or run \`/gsd verdict pass --rationale "..."\` to override.`,
595
595
  ],
596
596
  nextAction: `Resolve ${activeMilestone.id} remediation before proceeding.`,
597
597
  registry, requirements,
@@ -1314,7 +1314,7 @@ export async function _deriveStateImpl(
1314
1314
  recentDecisions: [],
1315
1315
  blockers: [
1316
1316
  `Milestone ${activeMilestone.id} validation verdict is needs-remediation but all slices are complete. ` +
1317
- `Add remediation slices via gsd_reassess_roadmap or override the verdict manually.`,
1317
+ `Add remediation slices via gsd_reassess_roadmap, or run \`/gsd verdict pass --rationale "..."\` to override.`,
1318
1318
  ],
1319
1319
  nextAction: `Resolve ${activeMilestone.id} remediation before proceeding.`,
1320
1320
  registry,
@@ -1441,7 +1441,7 @@ export async function _deriveStateImpl(
1441
1441
  const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
1442
1442
  if (summaryPath && existsSync(summaryPath)) {
1443
1443
  t.done = true;
1444
- logWarning("reconcile", `task ${activeMilestone.id}/${activeSlice.id}/${t.id} reconciled via SUMMARY on disk (#2514)`, { mid: activeMilestone.id, sid: activeSlice.id, tid: t.id });
1444
+ logWarning("reconcile", `task ${activeMilestone.id}/${activeSlice.id}/${t.id} reconciled via SUMMARY on disk`, { mid: activeMilestone.id, sid: activeSlice.id, tid: t.id });
1445
1445
  }
1446
1446
  }
1447
1447
 
@@ -30,3 +30,11 @@ export function isInactiveStatus(status: string): boolean {
30
30
  export function isSkippedForDispatch(status: string): boolean {
31
31
  return isClosedStatus(status) || status === "parked" || isDeferredStatus(status);
32
32
  }
33
+
34
+ /**
35
+ * Returns true when a milestone is future/backlog work (not currently executing).
36
+ * Includes legacy/project-specific alias "planned" for compatibility.
37
+ */
38
+ export function isFutureMilestoneStatus(status: string): boolean {
39
+ return status === "pending" || status === "queued" || status === "planned";
40
+ }
@@ -132,6 +132,7 @@
132
132
  Verify field rules:
133
133
  - MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
134
134
  - MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, or output trimming
135
+ - MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
135
136
  - For content/document tasks: verify file existence, section count, YAML validity, or word count
136
137
  NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
137
138
  - If no command can verify the output, write: "Manual review — file exists and is non-empty"
@@ -57,6 +57,12 @@ skills_used:
57
57
  - {{howToVerifyThisTaskIsActuallyDone}}
58
58
  - {{commandToRun_OR_behaviorToCheck}}
59
59
 
60
+ ## Verify Rules
61
+
62
+ - Use a real executable check, not prose.
63
+ - If the check needs file-content assertions, write a `node:test` file and run it with `node --test` or a package test script.
64
+ - Do not use inline `node -e` assertions for verification.
65
+
60
66
  ## Observability Impact
61
67
 
62
68
  <!-- OMIT THIS SECTION ENTIRELY for simple tasks that don't touch runtime boundaries,
@@ -129,6 +129,8 @@ function makeMockPi() {
129
129
  setModelCalls.push(args);
130
130
  return true;
131
131
  },
132
+ getThinkingLevel: () => "off",
133
+ setThinkingLevel: () => {},
132
134
  calls,
133
135
  setModelCalls,
134
136
  } as any;
@@ -6,7 +6,7 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "nod
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
8
 
9
- import { cleanupAfterLoopExit, rerootCommandSession, stopAuto } from "../auto.ts";
9
+ import { cleanupAfterLoopExit, pauseAuto, rerootCommandSession, stopAuto } from "../auto.ts";
10
10
  import { autoSession } from "../auto-runtime-state.ts";
11
11
  import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
12
12
  import { WorktreeLifecycle } from "../worktree-lifecycle.ts";
@@ -43,6 +43,52 @@ test("cleanupAfterLoopExit preserves paused auto badge after provider pause", as
43
43
  }
44
44
  });
45
45
 
46
+ test("cleanupAfterLoopExit preserves paused worktree session and visible failure output", async (t) => {
47
+ const base = mkdtempSync(join(tmpdir(), "gsd-paused-session-preserve-"));
48
+ const worktree = join(base, ".gsd", "worktrees", "M001");
49
+ const previousCwd = process.cwd();
50
+ const newSessionWorkspaces: string[] = [];
51
+ let restoreCalls = 0;
52
+
53
+ t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function () {
54
+ restoreCalls += 1;
55
+ });
56
+
57
+ mkdirSync(worktree, { recursive: true });
58
+ process.chdir(worktree);
59
+ autoSession.reset();
60
+ autoSession.active = true;
61
+ autoSession.paused = true;
62
+ autoSession.basePath = worktree;
63
+ autoSession.originalBasePath = base;
64
+ autoSession.cmdCtx = {
65
+ newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
66
+ newSessionWorkspaces.push(workspaceRoot);
67
+ return { cancelled: false };
68
+ },
69
+ } as any;
70
+
71
+ try {
72
+ await cleanupAfterLoopExit({
73
+ ui: {
74
+ setStatus: () => {},
75
+ setWidget: () => {},
76
+ notify: () => {},
77
+ },
78
+ } as any);
79
+
80
+ assert.equal(restoreCalls, 0, "paused cleanup must not restore out of the active worktree");
81
+ assert.deepEqual(newSessionWorkspaces, [], "paused cleanup must not start a blank rerooted session");
82
+ assert.equal(autoSession.basePath, worktree);
83
+ assert.equal(realpathSync(process.cwd()), realpathSync(worktree));
84
+ assert.equal(autoSession.paused, true);
85
+ } finally {
86
+ autoSession.reset();
87
+ process.chdir(previousCwd);
88
+ rmSync(base, { recursive: true, force: true });
89
+ }
90
+ });
91
+
46
92
  test("cleanupAfterLoopExit clears status and progress widget without replacing outcome surface", async () => {
47
93
  const statusCalls: unknown[] = [];
48
94
  const widgetCalls: unknown[] = [];
@@ -117,6 +163,98 @@ test("cleanupAfterLoopExit clears progress widget after stopAuto reset", async (
117
163
  }
118
164
  });
119
165
 
166
+ test("pauseAuto preserves artifact retry counts across pause/resume", async () => {
167
+ const base = mkdtempSync(join(tmpdir(), "gsd-pause-retry-count-"));
168
+ const previousCwd = process.cwd();
169
+ const retryKey = "execute-task:M001/S01/T01";
170
+
171
+ autoSession.reset();
172
+ autoSession.active = true;
173
+ autoSession.verificationRetryCount.set(retryKey, 2);
174
+ autoSession.pendingVerificationRetry = {
175
+ unitId: "M001/S01/T01",
176
+ failureContext: "Missing expected artifact (attempt 2/3).",
177
+ attempt: 2,
178
+ };
179
+
180
+ try {
181
+ process.chdir(base);
182
+ await pauseAuto();
183
+
184
+ assert.equal(autoSession.paused, true);
185
+ assert.equal(autoSession.pendingVerificationRetry, null);
186
+ assert.equal(autoSession.verificationRetryCount.get(retryKey), 2);
187
+ } finally {
188
+ autoSession.reset();
189
+ process.chdir(previousCwd);
190
+ rmSync(base, { recursive: true, force: true });
191
+ }
192
+ });
193
+
194
+ test("cleanupAfterLoopExit preserves step-mode surface and worktree session after completed step", async (t) => {
195
+ const base = mkdtempSync(join(tmpdir(), "gsd-step-surface-"));
196
+ const worktree = join(base, ".gsd", "worktrees", "M001");
197
+ const previousCwd = process.cwd();
198
+ const statusCalls: unknown[] = [];
199
+ const widgetCalls: unknown[] = [];
200
+ const newSessionWorkspaces: string[] = [];
201
+ let restoreCalls = 0;
202
+
203
+ t.mock.method(WorktreeLifecycle.prototype, "restoreToProjectRoot", function () {
204
+ restoreCalls += 1;
205
+ });
206
+
207
+ mkdirSync(worktree, { recursive: true });
208
+ process.chdir(worktree);
209
+ autoSession.reset();
210
+ autoSession.active = true;
211
+ autoSession.paused = false;
212
+ autoSession.stepMode = true;
213
+ autoSession.preserveStepSurfaceAfterLoopExit = true;
214
+ autoSession.basePath = worktree;
215
+ autoSession.originalBasePath = base;
216
+ autoSession.cmdCtx = {
217
+ newSession: async ({ workspaceRoot }: { workspaceRoot: string }) => {
218
+ newSessionWorkspaces.push(workspaceRoot);
219
+ return { cancelled: false };
220
+ },
221
+ } as any;
222
+
223
+ try {
224
+ await cleanupAfterLoopExit({
225
+ hasUI: true,
226
+ ui: {
227
+ setStatus: (...args: unknown[]) => statusCalls.push(args),
228
+ setWidget: (...args: unknown[]) => widgetCalls.push(args),
229
+ setHeader: () => {},
230
+ notify: () => {},
231
+ },
232
+ } as any);
233
+
234
+ assert.deepEqual(statusCalls, [], "step-mode cleanup must leave the NEXT badge visible");
235
+ assert.equal(
236
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-progress" && args[1] === undefined),
237
+ false,
238
+ "step-mode cleanup must not clear the completed step progress surface",
239
+ );
240
+ assert.equal(
241
+ widgetCalls.some((args) => Array.isArray(args) && args[0] === "gsd-health"),
242
+ false,
243
+ "step-mode cleanup must not replace the progress surface with idle health",
244
+ );
245
+ assert.deepEqual(newSessionWorkspaces, [], "step-mode cleanup must not re-root the visible command session");
246
+ assert.equal(restoreCalls, 0, "step-mode cleanup must not restore out of the active worktree");
247
+ assert.equal(autoSession.active, false);
248
+ assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
249
+ assert.equal(autoSession.basePath, worktree);
250
+ assert.equal(realpathSync(process.cwd()), realpathSync(worktree));
251
+ } finally {
252
+ autoSession.reset();
253
+ process.chdir(previousCwd);
254
+ rmSync(base, { recursive: true, force: true });
255
+ }
256
+ });
257
+
120
258
  test("cleanupAfterLoopExit restores project root through lifecycle and preserves chdir", async (t) => {
121
259
  const base = mkdtempSync(join(tmpdir(), "gsd-cleanup-lifecycle-"));
122
260
  const worktree = join(base, ".gsd", "worktrees", "M001");
@@ -1,4 +1,5 @@
1
- // GSD-2 — Tests for step-mode completion messages in auto-post-unit
1
+ // Project/App: GSD-2
2
+ // File Purpose: Tests for step-mode completion messages in auto-post-unit.
2
3
 
3
4
  import test from "node:test";
4
5
  import assert from "node:assert/strict";
@@ -31,7 +32,7 @@ test("buildStepCompleteMessage: milestone complete surfaces review guidance", ()
31
32
  assert.doesNotMatch(msg, /Next:/);
32
33
  });
33
34
 
34
- test("buildStepCompleteMessage: mid-flight step includes next unit label and /clear hint", () => {
35
+ test("buildStepCompleteMessage: mid-flight step includes next unit label and /gsd next hint", () => {
35
36
  const state = makeState({
36
37
  phase: "executing",
37
38
  activeSlice: { id: "S01", title: "Core" },
@@ -40,7 +41,7 @@ test("buildStepCompleteMessage: mid-flight step includes next unit label and /cl
40
41
  const msg = buildStepCompleteMessage(state);
41
42
  assert.match(msg, /Next: Execute T03: Wire notify/);
42
43
  assert.match(msg, /\/clear/);
43
- assert.match(msg, /\/gsd to continue/);
44
+ assert.match(msg, /\/gsd next to continue one step/);
44
45
  });
45
46
 
46
47
  test("buildStepCompleteMessage: unknown phase falls back to generic continue label", () => {
@@ -51,9 +52,9 @@ test("buildStepCompleteMessage: unknown phase falls back to generic continue lab
51
52
  assert.match(msg, /\/clear/);
52
53
  });
53
54
 
54
- test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd", () => {
55
+ test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd next", () => {
55
56
  assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/clear/);
56
- assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd/);
57
+ assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd next/);
57
58
  });
58
59
 
59
60
  test("shouldReturnStepWizardAfterUnit: terminal milestone completion continues to merge-back path", () => {
@@ -807,6 +807,51 @@ test("hasImplementationArtifacts finds integration implementation-only commits w
807
807
  }
808
808
  });
809
809
 
810
+ test("hasImplementationArtifacts ignores corrupted milestone/* integration metadata", () => {
811
+ const base = makeGitBase();
812
+ try {
813
+ mkdirSync(join(base, "src"), { recursive: true });
814
+ writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
815
+ execFileSync("git", ["add", "src/feature.ts"], { cwd: base, stdio: "ignore" });
816
+ execFileSync("git", ["commit", "-m", "feat: add milestone feature\n\nGSD-Task: S01/T01"], { cwd: base, stdio: "ignore" });
817
+
818
+ mkdirSync(join(base, ".gsd"), { recursive: true });
819
+ openDatabase(join(base, ".gsd", "gsd.db"));
820
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
821
+ insertSlice({
822
+ id: "S01",
823
+ milestoneId: "M001",
824
+ title: "Slice One",
825
+ status: "complete",
826
+ risk: "low",
827
+ depends: [],
828
+ });
829
+ insertTask({
830
+ id: "T01",
831
+ sliceId: "S01",
832
+ milestoneId: "M001",
833
+ title: "Task One",
834
+ status: "complete",
835
+ });
836
+
837
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
838
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
839
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
840
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
841
+ execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
842
+
843
+ writeFileSync(
844
+ join(base, ".gsd", "milestones", "M001", "M001-META.json"),
845
+ JSON.stringify({ integrationBranch: "milestone/M001" }, null, 2) + "\n",
846
+ );
847
+
848
+ const result = hasImplementationArtifacts(base, "M001");
849
+ assert.equal(result, "present", "corrupted milestone integration metadata should fall back to main branch for artifact detection");
850
+ } finally {
851
+ cleanup(base);
852
+ }
853
+ });
854
+
810
855
  test("hasImplementationArtifacts backfills untagged main implementation commits from completed task file hints", () => {
811
856
  const base = makeGitBase();
812
857
  try {
@@ -1043,24 +1088,50 @@ test("hasImplementationArtifacts binds GSD-Task trailer to milestone via DB stat
1043
1088
  }
1044
1089
  });
1045
1090
 
1046
- test("hasImplementationArtifacts does not bind GSD-Task trailer without milestone ownership evidence", () => {
1091
+ test("hasImplementationArtifacts does not claim Sxx/Tyy commit trailers across milestones when ownership points elsewhere", () => {
1047
1092
  const base = makeGitBase();
1048
1093
  try {
1049
1094
  writeFileSync(join(base, ".git", "info", "exclude"), ".gsd/\n");
1095
+ mkdirSync(join(base, ".gsd"), { recursive: true });
1096
+ openDatabase(join(base, ".gsd", "gsd.db"));
1097
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
1098
+ insertMilestone({ id: "M002", title: "Milestone Two", status: "active" });
1099
+ insertSlice({
1100
+ id: "S01",
1101
+ milestoneId: "M002",
1102
+ title: "Slice One",
1103
+ status: "complete",
1104
+ risk: "low",
1105
+ depends: [],
1106
+ });
1107
+ insertTask({
1108
+ id: "T01",
1109
+ sliceId: "S01",
1110
+ milestoneId: "M002",
1111
+ title: "Task One",
1112
+ status: "complete",
1113
+ });
1114
+
1050
1115
  mkdirSync(join(base, "src"), { recursive: true });
1051
1116
  writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
1052
1117
  execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
1053
1118
  execFileSync(
1054
1119
  "git",
1055
- ["commit", "-m", "feat: add feature\n\nGSD-Task: S01/T01"],
1120
+ ["commit", "-m", "feat: add sibling feature\n\nGSD-Task: S01/T01"],
1056
1121
  { cwd: base, stdio: "ignore" },
1057
1122
  );
1058
1123
 
1059
- const result = hasImplementationArtifacts(base, "M001");
1124
+ const m001Result = hasImplementationArtifacts(base, "M001");
1125
+ const m002Result = hasImplementationArtifacts(base, "M002");
1060
1126
  assert.equal(
1061
- result,
1127
+ m001Result,
1062
1128
  "absent",
1063
- "S01/T01 shape alone must not bind an implementation commit to M001",
1129
+ "Sxx/Tyy commit trailers owned by M002 must not be attributed to M001",
1130
+ );
1131
+ assert.equal(
1132
+ m002Result,
1133
+ "present",
1134
+ "the owning milestone should still claim the implementation-bearing commit",
1064
1135
  );
1065
1136
  } finally {
1066
1137
  cleanup(base);
@@ -0,0 +1,87 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+
8
+ import { checkoutBranchWithStashGuard } from "../auto-worktree.ts";
9
+
10
+ function git(args: string[], cwd: string): string {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ encoding: "utf-8",
15
+ });
16
+ }
17
+
18
+ function createRepo(t: { after: (fn: () => void) => void }): string {
19
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "checkout-stash-guard-")));
20
+ t.after(() => rmSync(dir, { recursive: true, force: true }));
21
+ git(["init"], dir);
22
+ git(["config", "user.email", "test@example.com"], dir);
23
+ git(["config", "user.name", "Test User"], dir);
24
+ writeFileSync(join(dir, "note.txt"), "base\n");
25
+ git(["add", "note.txt"], dir);
26
+ git(["commit", "-m", "init"], dir);
27
+ git(["branch", "-M", "main"], dir);
28
+ return dir;
29
+ }
30
+
31
+ describe("checkoutBranchWithStashGuard", () => {
32
+ test("restores dirty working tree after successful checkout", (t) => {
33
+ const repo = createRepo(t);
34
+ git(["checkout", "-b", "milestone/M001"], repo);
35
+ git(["checkout", "main"], repo);
36
+
37
+ writeFileSync(join(repo, "note.txt"), "dirty\n");
38
+
39
+ checkoutBranchWithStashGuard(repo, "milestone/M001", "test-success");
40
+
41
+ const branch = git(["branch", "--show-current"], repo).trim();
42
+ assert.equal(branch, "milestone/M001");
43
+ const content = git(["show", "HEAD:note.txt"], repo).trim();
44
+ assert.equal(content, "base");
45
+ const wtContent = readFileSync(join(repo, "note.txt"), "utf8");
46
+ assert.equal(wtContent, "dirty\n");
47
+ const status = git(["status", "--porcelain"], repo);
48
+ assert.match(status, /note\.txt/);
49
+ });
50
+
51
+ test("restores dirty working tree when checkout throws", (t) => {
52
+ const repo = createRepo(t);
53
+ writeFileSync(join(repo, "note.txt"), "dirty\n");
54
+
55
+ assert.throws(
56
+ () => checkoutBranchWithStashGuard(repo, "milestone/DOES-NOT-EXIST", "test-failure"),
57
+ );
58
+
59
+ const status = git(["status", "--porcelain"], repo);
60
+ assert.match(status, /note\.txt/);
61
+ const stashList = git(["stash", "list"], repo).trim();
62
+ assert.equal(stashList, "");
63
+ });
64
+
65
+ test("surfaces distinct error when checkout succeeds but stash pop conflicts", (t) => {
66
+ const repo = createRepo(t);
67
+ // Branch B has a divergent version of note.txt so popping a stash made
68
+ // against main will conflict after the checkout to B.
69
+ git(["checkout", "-b", "milestone/B"], repo);
70
+ writeFileSync(join(repo, "note.txt"), "B-version\n");
71
+ git(["add", "note.txt"], repo);
72
+ git(["commit", "-m", "B"], repo);
73
+ git(["checkout", "main"], repo);
74
+
75
+ writeFileSync(join(repo, "note.txt"), "local\n");
76
+
77
+ assert.throws(
78
+ () => checkoutBranchWithStashGuard(repo, "milestone/B", "test-pop-failure"),
79
+ /checkout to 'milestone\/B' succeeded but stash restore failed/,
80
+ );
81
+
82
+ const branch = git(["branch", "--show-current"], repo).trim();
83
+ assert.equal(branch, "milestone/B");
84
+ const stashList = git(["stash", "list"], repo).trim();
85
+ assert.match(stashList, /gsd: checkout stash/);
86
+ });
87
+ });