gsd-pi 2.66.1-dev.3c26b49 → 2.66.1-dev.3cea7ac

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 (230) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +79 -11
  2. package/dist/resources/extensions/claude-code-cli/partial-builder.js +4 -3
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +10 -3
  4. package/dist/resources/extensions/gsd/auto/loop.js +13 -1
  5. package/dist/resources/extensions/gsd/auto/phases.js +10 -4
  6. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto/session.js +1 -1
  8. package/dist/resources/extensions/gsd/auto-dashboard.js +65 -15
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +30 -28
  10. package/dist/resources/extensions/gsd/auto-prompts.js +6 -6
  11. package/dist/resources/extensions/gsd/auto-recovery.js +11 -12
  12. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +18 -6
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +59 -5
  14. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +8 -5
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +186 -14
  16. package/dist/resources/extensions/gsd/codebase-generator.js +4 -0
  17. package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -3
  18. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +10 -4
  19. package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -1
  20. package/dist/resources/extensions/gsd/detection.js +6 -0
  21. package/dist/resources/extensions/gsd/files.js +19 -2
  22. package/dist/resources/extensions/gsd/guided-flow.js +12 -8
  23. package/dist/resources/extensions/gsd/index.js +1 -1
  24. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +2 -0
  25. package/dist/resources/extensions/gsd/parsers-legacy.js +3 -1
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  28. package/dist/resources/extensions/gsd/prompts/discuss.md +3 -3
  29. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  31. package/dist/resources/extensions/gsd/prompts/rethink.md +6 -2
  32. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  34. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  36. package/dist/resources/extensions/gsd/safety/file-change-validator.js +2 -1
  37. package/dist/resources/extensions/gsd/state.js +2 -1
  38. package/dist/resources/extensions/gsd/visualizer-overlay.js +27 -26
  39. package/dist/resources/extensions/gsd/workflow-reconcile.js +46 -7
  40. package/dist/resources/extensions/remote-questions/manager.js +8 -0
  41. package/dist/resources/extensions/shared/interview-ui.js +10 -0
  42. package/dist/web/standalone/.next/BUILD_ID +1 -1
  43. package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
  44. package/dist/web/standalone/.next/build-manifest.json +2 -2
  45. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  46. package/dist/web/standalone/.next/required-server-files.json +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.html +1 -1
  64. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
  71. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  72. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  73. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  74. package/dist/web/standalone/server.js +1 -1
  75. package/package.json +1 -1
  76. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/providers/anthropic-shared.js +4 -3
  78. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  79. package/packages/pi-ai/dist/utils/json-parse.d.ts.map +1 -1
  80. package/packages/pi-ai/dist/utils/json-parse.js +11 -1
  81. package/packages/pi-ai/dist/utils/json-parse.js.map +1 -1
  82. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/utils/repair-tool-json.js +60 -1
  84. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  85. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts +2 -0
  86. package/packages/pi-ai/dist/utils/tests/json-parse.test.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/utils/tests/json-parse.test.js +14 -0
  88. package/packages/pi-ai/dist/utils/tests/json-parse.test.js.map +1 -0
  89. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +10 -0
  90. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  91. package/packages/pi-ai/src/providers/anthropic-shared.ts +4 -3
  92. package/packages/pi-ai/src/utils/json-parse.ts +11 -1
  93. package/packages/pi-ai/src/utils/repair-tool-json.ts +69 -1
  94. package/packages/pi-ai/src/utils/tests/json-parse.test.ts +17 -0
  95. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +13 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +17 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +2 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js +2 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  114. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  115. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +18 -0
  116. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +2 -1
  117. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +11 -2
  118. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +2 -1
  119. package/packages/pi-coding-agent/src/modes/interactive/components/scoped-models-selector.ts +2 -1
  120. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +2 -2
  121. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +13 -0
  122. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  123. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts +2 -0
  124. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.d.ts.map +1 -0
  125. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +35 -0
  126. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -0
  127. package/packages/pi-tui/dist/__tests__/tui.test.d.ts +2 -0
  128. package/packages/pi-tui/dist/__tests__/tui.test.d.ts.map +1 -0
  129. package/packages/pi-tui/dist/__tests__/tui.test.js +43 -0
  130. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -0
  131. package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
  132. package/packages/pi-tui/dist/autocomplete.js +9 -7
  133. package/packages/pi-tui/dist/autocomplete.js.map +1 -1
  134. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts +2 -0
  135. package/packages/pi-tui/dist/components/__tests__/editor.test.d.ts.map +1 -0
  136. package/packages/pi-tui/dist/components/__tests__/editor.test.js +54 -0
  137. package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -0
  138. package/packages/pi-tui/dist/components/editor.d.ts +3 -1
  139. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  140. package/packages/pi-tui/dist/components/editor.js +14 -3
  141. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  142. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  143. package/packages/pi-tui/dist/stdin-buffer.js +6 -0
  144. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  145. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/tui.js +8 -0
  147. package/packages/pi-tui/dist/tui.js.map +1 -1
  148. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +15 -0
  149. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +43 -0
  150. package/packages/pi-tui/src/__tests__/tui.test.ts +50 -0
  151. package/packages/pi-tui/src/autocomplete.ts +9 -7
  152. package/packages/pi-tui/src/components/__tests__/editor.test.ts +64 -0
  153. package/packages/pi-tui/src/components/editor.ts +14 -3
  154. package/packages/pi-tui/src/stdin-buffer.ts +7 -0
  155. package/packages/pi-tui/src/tui.ts +9 -0
  156. package/src/resources/extensions/ask-user-questions.ts +103 -11
  157. package/src/resources/extensions/claude-code-cli/partial-builder.ts +4 -3
  158. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +12 -3
  159. package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +17 -0
  160. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +18 -0
  161. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -1
  162. package/src/resources/extensions/gsd/auto/loop.ts +14 -1
  163. package/src/resources/extensions/gsd/auto/phases.ts +10 -5
  164. package/src/resources/extensions/gsd/auto/run-unit.ts +14 -2
  165. package/src/resources/extensions/gsd/auto/session.ts +1 -1
  166. package/src/resources/extensions/gsd/auto-dashboard.ts +76 -16
  167. package/src/resources/extensions/gsd/auto-dispatch.ts +36 -35
  168. package/src/resources/extensions/gsd/auto-prompts.ts +5 -6
  169. package/src/resources/extensions/gsd/auto-recovery.ts +15 -15
  170. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +27 -6
  171. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +67 -6
  172. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +11 -8
  173. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +209 -16
  174. package/src/resources/extensions/gsd/codebase-generator.ts +4 -0
  175. package/src/resources/extensions/gsd/commands/handlers/core.ts +6 -6
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +11 -4
  177. package/src/resources/extensions/gsd/custom-workflow-engine.ts +3 -1
  178. package/src/resources/extensions/gsd/detection.ts +6 -0
  179. package/src/resources/extensions/gsd/files.ts +21 -2
  180. package/src/resources/extensions/gsd/guided-flow.ts +15 -8
  181. package/src/resources/extensions/gsd/index.ts +6 -0
  182. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +2 -0
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +3 -1
  184. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  185. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +7 -7
  186. package/src/resources/extensions/gsd/prompts/discuss.md +3 -3
  187. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -3
  188. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
  189. package/src/resources/extensions/gsd/prompts/rethink.md +6 -2
  190. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +4 -4
  193. package/src/resources/extensions/gsd/prompts/worktree-merge.md +3 -1
  194. package/src/resources/extensions/gsd/safety/file-change-validator.ts +4 -1
  195. package/src/resources/extensions/gsd/state.ts +2 -1
  196. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +52 -1
  197. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +50 -2
  198. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +48 -0
  199. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  200. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +44 -0
  201. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +7 -1
  202. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +31 -0
  203. package/src/resources/extensions/gsd/tests/detection.test.ts +37 -0
  204. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +50 -0
  205. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +35 -0
  206. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +34 -0
  207. package/src/resources/extensions/gsd/tests/health-widget.test.ts +45 -0
  208. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +53 -13
  209. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -2
  210. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +1 -1
  211. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +3 -4
  212. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +21 -0
  213. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +71 -2
  214. package/src/resources/extensions/gsd/tests/parsers.test.ts +25 -0
  215. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +8 -1
  216. package/src/resources/extensions/gsd/tests/queue-execution-guard.test.ts +9 -0
  217. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +19 -0
  218. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +73 -0
  219. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +98 -0
  220. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +2 -2
  221. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +26 -0
  222. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +59 -0
  223. package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +91 -0
  224. package/src/resources/extensions/gsd/tests/write-gate.test.ts +210 -35
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +31 -27
  226. package/src/resources/extensions/gsd/workflow-reconcile.ts +59 -8
  227. package/src/resources/extensions/remote-questions/manager.ts +9 -0
  228. package/src/resources/extensions/shared/interview-ui.ts +13 -0
  229. /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_buildManifest.js +0 -0
  230. /package/dist/web/standalone/.next/static/{ZzNRjwBFLOhqEu4BYCQi9 → HxFcJ8GrYNPsg9ARz7GPz}/_ssgManifest.js +0 -0
@@ -12,7 +12,7 @@ You are a project reorganization assistant for a GSD (Get Shit Done) project. Th
12
12
 
13
13
  1. Present the current milestone order as a clear numbered list with status indicators (e.g. ✅ complete, ▶ active, ⏳ pending, ⏸ parked)
14
14
  2. Ask: **"What would you like to change?"**
15
- 3. Execute changes conversationally, confirming destructive operations before proceeding
15
+ 3. Execute changes conversationally, confirming destructive operations before proceeding. **Non-bypassable:** For any destructive operation (discard, skip, reorder that breaks dependencies), you MUST get explicit user confirmation before executing. If the user does not respond, gives an ambiguous answer, or `ask_user_questions` fails, you MUST re-ask — never rationalize past the block. A missing confirmation is a "do not proceed."
16
16
 
17
17
  ## Supported Operations
18
18
 
@@ -53,8 +53,12 @@ gsd_skip_slice({ milestoneId: "M003", sliceId: "S02", reason: "Descoped — feat
53
53
  Skipped slices are treated as closed by the state machine (like "complete" but distinct). Use when a slice is no longer needed or has been superseded. The slice data is preserved for reference.
54
54
  **Do NOT** just check the slice checkbox in the roadmap — this does not update the DB and auto-mode will resume the slice.
55
55
 
56
+ **CRITICAL — Non-bypassable gate:** Skipping a slice is a permanent DB operation. You MUST confirm with the user before calling `gsd_skip_slice`. If the user does not respond or gives an ambiguous answer, you MUST re-ask — never proceed without explicit approval.
57
+
56
58
  ### Discard a milestone
57
- **Permanently** delete a milestone directory and prune it from QUEUE-ORDER.json. **Always confirm with the user before discarding.** Warn explicitly if the milestone has completed work.
59
+ **Permanently** delete a milestone directory and prune it from QUEUE-ORDER.json.
60
+
61
+ **CRITICAL — Non-bypassable gate:** Discarding is irreversible. You MUST confirm with the user before discarding. Warn explicitly if the milestone has completed work. If the user does not respond or gives an ambiguous answer, you MUST re-ask — never rationalize past the block. A missing confirmation is a "do not discard."
58
62
 
59
63
  ### Add a new milestone
60
64
  Use the `gsd_milestone_generate_id` tool to get the next ID, then call `gsd_summary_save` with `milestone_id: {ID}`, `artifact_type: "CONTEXT"`, and the scope/goals/success criteria as `content` — the tool writes the context file to disk and persists to DB. Update QUEUE-ORDER.json to place it at the desired position.
@@ -38,7 +38,7 @@ GSD ships with bundled skills. Load the relevant skill file with the `read` tool
38
38
  - Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
39
39
  - Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
40
40
  - In enduring files, write current state only unless the file is explicitly historical.
41
- - **Never take outward-facing actions on GitHub (or any external service) without explicit user confirmation.** This includes: creating issues, closing issues, merging PRs, approving PRs, posting comments, pushing to remote branches, publishing packages, or any other action that affects state outside the local filesystem. Read-only operations (listing, viewing, diffing) are fine. Always present what you intend to do and get a clear "yes" before executing.
41
+ - **Never take outward-facing actions on GitHub (or any external service) without explicit user confirmation.** This includes: creating issues, closing issues, merging PRs, approving PRs, posting comments, pushing to remote branches, publishing packages, or any other action that affects state outside the local filesystem. Read-only operations (listing, viewing, diffing) are fine. Always present what you intend to do and get a clear "yes" before executing. **Non-bypassable:** If the user does not respond, gives an ambiguous answer, or `ask_user_questions` fails, you MUST re-ask — never rationalize past the block ("tool not responding, I'll proceed" is forbidden). A missing "yes" is a "no."
42
42
 
43
43
  If a `GSD Skill Preferences` block is present below this contract, treat it as explicit durable guidance for which skills to use, prefer, or avoid during GSD work. Follow it where it does not conflict with required GSD artifact rules, verification requirements, or higher-priority system/developer instructions.
44
44
 
@@ -51,7 +51,7 @@ For each capture, classify it as one of:
51
51
 
52
52
  For captures classified as **note** or **defer**, auto-confirm without asking — these are low-impact.
53
53
  For captures classified as **stop** or **backtrack**, auto-confirm without asking — these are urgent user directives that must be honored immediately.
54
- For captures classified as **quick-task**, **inject**, or **replan**, ask the user to confirm or choose a different classification.
54
+ For captures classified as **quick-task**, **inject**, or **replan**, ask the user to confirm or choose a different classification. **Non-bypassable:** If `ask_user_questions` fails, errors, or the user does not respond, you MUST re-ask — never auto-confirm these classifications without explicit user approval.
55
55
 
56
56
  3. **Update** `.gsd/CAPTURES.md` — for each capture, update its section with the confirmed classification:
57
57
  - Change `**Status:** pending` to `**Status:** resolved`
@@ -14,7 +14,7 @@ This is remediation round {{remediationRound}}. If this is round 0, this is the
14
14
 
15
15
  ## Context
16
16
 
17
- All relevant context has been preloaded below — the roadmap, all slice summaries, UAT results, requirements, decisions, and project context are inlined. Start working immediately without re-reading these files.
17
+ All relevant context has been preloaded below — the roadmap, all slice summaries, assessment results, requirements, decisions, and project context are inlined. Start working immediately without re-reading these files.
18
18
 
19
19
  {{inlinedContext}}
20
20
 
@@ -30,8 +30,8 @@ Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directo
30
30
  **Reviewer B — Cross-Slice Integration**
31
31
  Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, check that the producing slice's SUMMARY confirms it produced the artifact, and the consuming slice's SUMMARY confirms it consumed it. Output a markdown table: Boundary | Producer Summary | Consumer Summary | Status. End with a one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
32
32
 
33
- **Reviewer C — UAT & Acceptance Criteria**
34
- Prompt: "Review milestone {{milestoneId}} UAT and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for acceptance criteria. Check for UAT-RESULT files in each slice directory. Verify each acceptance criterion maps to either a passing UAT result or clear SUMMARY evidence. Output a checklist: [ ] Criterion | Evidence. End with a one-line verdict: PASS if all criteria met, NEEDS-ATTENTION if gaps exist."
33
+ **Reviewer C — Assessment & Acceptance Criteria**
34
+ Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for acceptance criteria. Check for ASSESSMENT files in each slice directory. Verify each acceptance criterion maps to either a passing assessment result or clear SUMMARY evidence. Output a checklist: [ ] Criterion | Evidence. End with a one-line verdict: PASS if all criteria met, NEEDS-ATTENTION if gaps exist."
35
35
 
36
36
  ### Step 2 — Synthesize Findings
37
37
 
@@ -59,7 +59,7 @@ reviewers: 3
59
59
  ## Reviewer B — Cross-Slice Integration
60
60
  <paste Reviewer B output>
61
61
 
62
- ## Reviewer C — UAT & Acceptance Criteria
62
+ ## Reviewer C — Assessment & Acceptance Criteria
63
63
  <paste Reviewer C output>
64
64
 
65
65
  ## Synthesis
@@ -90,9 +90,11 @@ Present a merge plan to the user:
90
90
 
91
91
  Ask the user to confirm the merge plan before proceeding.
92
92
 
93
+ **CRITICAL — Non-bypassable gate:** Do NOT execute any merge commands until the user explicitly approves the merge plan. If `ask_user_questions` fails, errors, returns no response, or the user's response is ambiguous, you MUST re-ask — never rationalize past the block. "No response, I'll proceed with the clean merges," "the plan looks safe, merging," or any other self-authorization is **forbidden**. The gate exists to protect the user's branches; treat a block as an instruction to wait, not an obstacle to work around.
94
+
93
95
  ### Step 4: Execute Merge
94
96
 
95
- Once confirmed, run all commands from `{{mainTreePath}}` (your CWD):
97
+ Once the user has explicitly confirmed, run all commands from `{{mainTreePath}}` (your CWD):
96
98
 
97
99
  1. Ensure you are on the target branch: `git checkout {{mainBranch}}`
98
100
  2. If there are conflicts requiring manual reconciliation, apply the reconciled versions first
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { execFileSync } from "node:child_process";
13
+ import { normalizePlannedFileReference } from "../files.js";
13
14
  import { logWarning } from "../workflow-logger.js";
14
15
 
15
16
  // ─── Types ──────────────────────────────────────────────────────────────────
@@ -57,7 +58,9 @@ export function validateFileChanges(
57
58
 
58
59
  // Normalize expected paths (strip leading ./ or /)
59
60
  const normalizedExpected = new Set(
60
- [...allExpected].map(f => f.replace(/^\.\//, "").replace(/^\//, "")),
61
+ [...allExpected].map((f) =>
62
+ normalizePlannedFileReference(f).replace(/^\.\//, "").replace(/^\//, ""),
63
+ ),
61
64
  );
62
65
 
63
66
  // Compute symmetric difference
@@ -1317,7 +1317,8 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
1317
1317
  ? `All milestones complete. ${activeReqs} active requirement${activeReqs === 1 ? '' : 's'} in REQUIREMENTS.md ${activeReqs === 1 ? 'has' : 'have'} not been mapped to a milestone.`
1318
1318
  : 'All milestones complete.';
1319
1319
  return {
1320
- activeMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
1320
+ activeMilestone: null,
1321
+ lastCompletedMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
1321
1322
  activeSlice: null,
1322
1323
  activeTask: null,
1323
1324
  phase: 'complete',
@@ -1,7 +1,8 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
5
6
 
6
7
  import {
7
8
  unitVerb,
@@ -11,11 +12,29 @@ import {
11
12
  formatWidgetTokens,
12
13
  estimateTimeRemaining,
13
14
  extractUatSliceId,
15
+ getWidgetMode,
16
+ cycleWidgetMode,
17
+ _resetWidgetModeForTests,
14
18
  } from "../auto-dashboard.ts";
15
19
 
16
20
  const autoSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto.ts"), "utf-8");
17
21
  const dashboardSource = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "auto-dashboard.ts"), "utf-8");
18
22
 
23
+ function makeTempDir(prefix: string): string {
24
+ return join(
25
+ tmpdir(),
26
+ `gsd-auto-dashboard-test-${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
27
+ );
28
+ }
29
+
30
+ function cleanup(dir: string): void {
31
+ try {
32
+ rmSync(dir, { recursive: true, force: true });
33
+ } catch {
34
+ // best-effort
35
+ }
36
+ }
37
+
19
38
  // ─── unitVerb ─────────────────────────────────────────────────────────────
20
39
 
21
40
  test("unitVerb maps known unit types to verbs", () => {
@@ -209,3 +228,35 @@ test("extractUatSliceId returns null for invalid formats", () => {
209
228
  assert.equal(extractUatSliceId(""), null);
210
229
  assert.equal(extractUatSliceId("M001/T01"), null);
211
230
  });
231
+
232
+ test("widget mode respects project preference precedence and persists there", (t) => {
233
+ const homeDir = makeTempDir("home");
234
+ const projectDir = makeTempDir("project");
235
+ const globalPrefsPath = join(homeDir, ".gsd", "preferences.md");
236
+ const projectPrefsPath = join(projectDir, ".gsd", "preferences.md");
237
+
238
+ mkdirSync(join(homeDir, ".gsd"), { recursive: true });
239
+ mkdirSync(join(projectDir, ".gsd"), { recursive: true });
240
+ writeFileSync(globalPrefsPath, "---\nversion: 1\nwidget_mode: off\n---\n", "utf-8");
241
+ writeFileSync(projectPrefsPath, "---\nversion: 1\nwidget_mode: small\n---\n", "utf-8");
242
+
243
+ t.after(() => {
244
+ cleanup(homeDir);
245
+ cleanup(projectDir);
246
+ _resetWidgetModeForTests();
247
+ });
248
+
249
+ _resetWidgetModeForTests();
250
+
251
+ assert.equal(getWidgetMode(projectPrefsPath, globalPrefsPath), "small", "project widget_mode overrides global");
252
+ assert.equal(
253
+ cycleWidgetMode(projectPrefsPath, globalPrefsPath),
254
+ "min",
255
+ "cycling advances from the project-owned mode",
256
+ );
257
+
258
+ const projectPrefs = readFileSync(projectPrefsPath, "utf-8");
259
+ const globalPrefs = readFileSync(globalPrefsPath, "utf-8");
260
+ assert.match(projectPrefs, /widget_mode:\s*min/);
261
+ assert.match(globalPrefs, /widget_mode:\s*off/);
262
+ });
@@ -1,4 +1,4 @@
1
- import test from "node:test";
1
+ import test, { mock } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
@@ -191,6 +191,54 @@ test("runUnit returns cancelled when session creation times out", async () => {
191
191
  assert.equal(pi.calls.length, 0);
192
192
  });
193
193
 
194
+ test("runUnit keeps the session-switch guard across a late newSession settlement", async () => {
195
+ _resetPendingResolve();
196
+ mock.timers.enable();
197
+
198
+ try {
199
+ const ctx = makeMockCtx();
200
+ const pi = makeMockPi();
201
+ // Use delays longer than NEW_SESSION_TIMEOUT_MS (120s) so the timeout fires
202
+ const firstSession = makeMockSession({ newSessionDelayMs: 200_000 });
203
+ const secondSession = makeMockSession({ newSessionDelayMs: 200_000 });
204
+
205
+ const firstRun = runUnit(ctx, pi, firstSession, "task", "T01", "prompt");
206
+
207
+ // Tick past the 120s session timeout
208
+ mock.timers.tick(121_000);
209
+ await Promise.resolve();
210
+
211
+ const firstResult = await firstRun;
212
+ assert.equal(firstResult.status, "cancelled");
213
+ assert.equal(isSessionSwitchInFlight(), true, "guard should remain set after the timed-out session");
214
+
215
+ mock.timers.tick(1);
216
+ const secondRun = runUnit(ctx, pi, secondSession, "task", "T02", "prompt");
217
+
218
+ mock.timers.tick(100_000);
219
+ await Promise.resolve();
220
+ assert.equal(
221
+ isSessionSwitchInFlight(),
222
+ true,
223
+ "late settlement from the first session must not clear the newer session guard",
224
+ );
225
+
226
+ // Tick past the second session's timeout (121s total > 120s NEW_SESSION_TIMEOUT_MS)
227
+ mock.timers.tick(21_001);
228
+ await Promise.resolve();
229
+
230
+ const secondResult = await secondRun;
231
+ assert.equal(secondResult.status, "cancelled");
232
+
233
+ // Tick past the second session's delayed promise (200s) so .finally() fires
234
+ mock.timers.tick(80_000);
235
+ await Promise.resolve();
236
+ assert.equal(isSessionSwitchInFlight(), false, "guard should clear after the newer session settles");
237
+ } finally {
238
+ mock.timers.reset();
239
+ }
240
+ });
241
+
194
242
  test("runUnit returns cancelled when s.active is false before sendMessage", async () => {
195
243
  _resetPendingResolve();
196
244
 
@@ -412,7 +460,7 @@ function makeMockDeps(
412
460
  getCurrentBranch: () => "main",
413
461
  autoWorktreeBranch: () => "auto/M001",
414
462
  resolveMilestoneFile: () => null,
415
- reconcileMergeState: () => false,
463
+ reconcileMergeState: () => "clean",
416
464
  getLedger: () => null,
417
465
  getProjectTotals: () => ({ cost: 0 }),
418
466
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -0,0 +1,48 @@
1
+ import test, { afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { verifyExpectedArtifact } from "../auto-recovery.ts";
8
+ import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow } from "../gsd-db.ts";
9
+
10
+ const tmpDirs: string[] = [];
11
+
12
+ function makeTmpProject(): string {
13
+ const dir = mkdtempSync(join(tmpdir(), "auto-recovery-"));
14
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
15
+ openDatabase(join(dir, ".gsd", "gsd.db"));
16
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
17
+ insertSlice({
18
+ milestoneId: "M001",
19
+ id: "S01",
20
+ title: "Test Slice",
21
+ status: "pending",
22
+ risk: "low",
23
+ depends: [],
24
+ });
25
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
26
+ tmpDirs.push(dir);
27
+ return dir;
28
+ }
29
+
30
+ afterEach(() => {
31
+ closeDatabase();
32
+ for (const dir of tmpDirs) {
33
+ try {
34
+ rmSync(dir, { recursive: true, force: true });
35
+ } catch {
36
+ // Best-effort cleanup only.
37
+ }
38
+ }
39
+ tmpDirs.length = 0;
40
+ });
41
+
42
+ test("verifyExpectedArtifact checks pending gate-evaluate artifacts without ESM require failures", () => {
43
+ const base = makeTmpProject();
44
+
45
+ const verified = verifyExpectedArtifact("gate-evaluate", "M001/S01/gates+Q3", base);
46
+
47
+ assert.equal(verified, false, "pending gates should keep gate-evaluate unverified");
48
+ });
@@ -138,6 +138,28 @@ test("generateCodebaseMap: excludes .gsd/ files", () => {
138
138
  }
139
139
  });
140
140
 
141
+ test("generateCodebaseMap: excludes .claude/ and other tool directories", () => {
142
+ const base = makeTmpRepo();
143
+ try {
144
+ addFile(base, "src/main.ts");
145
+ addFile(base, ".claude/CLAUDE.md");
146
+ addFile(base, ".claude/memory/user.md");
147
+ addFile(base, ".plans/plan.md");
148
+ addFile(base, ".cursor/settings.json");
149
+ addFile(base, ".vscode/settings.json");
150
+
151
+ const result = generateCodebaseMap(base);
152
+ assert.ok(result.content.includes("`src/main.ts`"), "should include src/main.ts");
153
+ assert.ok(!result.content.includes("CLAUDE.md"), "should exclude .claude/ files");
154
+ assert.ok(!result.content.includes("user.md"), "should exclude .claude/memory/ files");
155
+ assert.ok(!result.content.includes(".plans"), "should exclude .plans/ files");
156
+ assert.ok(!result.content.includes(".cursor"), "should exclude .cursor/ files");
157
+ assert.ok(!result.content.includes(".vscode"), "should exclude .vscode/ files");
158
+ } finally {
159
+ cleanup(base);
160
+ }
161
+ });
162
+
141
163
  test("generateCodebaseMap: excludes binary and lock files", () => {
142
164
  const base = makeTmpRepo();
143
165
  try {
@@ -0,0 +1,44 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { handleCoreCommand } from "../commands/handlers/core.ts";
5
+
6
+ function makeCtx(customResult: unknown) {
7
+ const notices: Array<{ message: string; type?: string }> = [];
8
+ return {
9
+ hasUI: true,
10
+ ui: {
11
+ custom: async () => customResult,
12
+ notify: (message: string, type?: string) => {
13
+ notices.push({ message, type });
14
+ },
15
+ },
16
+ notices,
17
+ };
18
+ }
19
+
20
+ test("visualize only falls back when ctx.ui.custom() is unavailable", async () => {
21
+ const successCtx = makeCtx(true);
22
+ const success = await handleCoreCommand("visualize", successCtx as any);
23
+ assert.equal(success, true);
24
+ assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
25
+
26
+ const fallbackCtx = makeCtx(undefined);
27
+ const fallback = await handleCoreCommand("visualize", fallbackCtx as any);
28
+ assert.equal(fallback, true);
29
+ assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers fallback warning");
30
+ assert.match(fallbackCtx.notices[0]!.message, /interactive terminal/i);
31
+ });
32
+
33
+ test("show-config only falls back when ctx.ui.custom() is unavailable", async () => {
34
+ const successCtx = makeCtx(true);
35
+ const success = await handleCoreCommand("show-config", successCtx as any);
36
+ assert.equal(success, true);
37
+ assert.equal(successCtx.notices.length, 0, "successful overlay close does not trigger fallback");
38
+
39
+ const fallbackCtx = makeCtx(undefined);
40
+ const fallback = await handleCoreCommand("show-config", fallbackCtx as any);
41
+ assert.equal(fallback, true);
42
+ assert.equal(fallbackCtx.notices.length, 1, "unavailable overlay triggers text fallback");
43
+ assert.match(fallbackCtx.notices[0]!.message, /GSD Configuration/);
44
+ });
@@ -178,7 +178,7 @@ function makeMockDeps(overrides?: Partial<LoopDeps>): LoopDeps & { callLog: stri
178
178
  getCurrentBranch: () => "main",
179
179
  autoWorktreeBranch: () => "auto/M001",
180
180
  resolveMilestoneFile: () => null,
181
- reconcileMergeState: () => false,
181
+ reconcileMergeState: () => "clean",
182
182
  getLedger: () => null,
183
183
  getProjectTotals: () => ({ cost: 0 }),
184
184
  formatCost: (c: number) => `$${c.toFixed(2)}`,
@@ -311,6 +311,12 @@ describe("Custom engine loop integration", () => {
311
311
  `stopAuto reason should include "Workflow complete", got: ${stopEntry}`,
312
312
  );
313
313
 
314
+ assert.equal(
315
+ deps.callLog.filter((e: string) => e === "deriveState").length,
316
+ 3,
317
+ "custom engine should stop immediately after a milestone-complete reconcile",
318
+ );
319
+
314
320
  // Verify dev path was NOT used (resolveDispatch should not appear)
315
321
  assert.ok(
316
322
  !deps.callLog.includes("resolveDispatch"),
@@ -249,6 +249,37 @@ describe("CustomWorkflowEngine.reconcile", () => {
249
249
  const graph = readGraph(runDir);
250
250
  assert.equal(graph.steps[0].status, "complete");
251
251
  });
252
+
253
+ it("re-reads GRAPH.yaml before reconcile so concurrent edits are preserved", async () => {
254
+ const { engine, runDir } = setupEngine([
255
+ makeStep({ id: "step-1" }),
256
+ makeStep({ id: "step-2", dependsOn: ["step-1"] }),
257
+ ], "wf");
258
+
259
+ const staleState = await engine.deriveState("/unused");
260
+
261
+ // Simulate another process appending a new step after deriveState() ran.
262
+ writeGraph(runDir, makeGraph([
263
+ makeStep({ id: "step-1" }),
264
+ makeStep({ id: "step-2", dependsOn: ["step-1"] }),
265
+ makeStep({ id: "step-3", dependsOn: ["step-2"] }),
266
+ ], "wf"));
267
+
268
+ const result = await engine.reconcile(staleState, {
269
+ unitType: "custom-step",
270
+ unitId: "wf/step-1",
271
+ startedAt: Date.now() - 1000,
272
+ finishedAt: Date.now(),
273
+ });
274
+
275
+ assert.equal(result.outcome, "continue");
276
+
277
+ const graph = readGraph(runDir);
278
+ assert.equal(graph.steps.length, 3, "reconcile should preserve the concurrent graph edit");
279
+ assert.equal(graph.steps[0].status, "complete");
280
+ assert.equal(graph.steps[1].status, "pending");
281
+ assert.equal(graph.steps[2].status, "pending");
282
+ });
252
283
  });
253
284
 
254
285
  // ─── getDisplayMetadata ──────────────────────────────────────────────────
@@ -17,6 +17,7 @@ import {
17
17
  detectProjectState,
18
18
  detectV1Planning,
19
19
  detectProjectSignals,
20
+ scanProjectFiles,
20
21
  } from "../detection.ts";
21
22
 
22
23
  function makeTempDir(prefix: string): string {
@@ -1188,3 +1189,39 @@ test("detectProjectSignals: Spring Boot settings-defined catalog accessor emits
1188
1189
  cleanup(dir);
1189
1190
  }
1190
1191
  });
1192
+
1193
+ // ─── scanProjectFiles: RECURSIVE_SCAN_IGNORED_DIRS ──────────────────────
1194
+
1195
+ test("scanProjectFiles: excludes .claude, .gsd, .planning, .plans, .cursor, .vscode directories", () => {
1196
+ const dir = makeTempDir("scan-ignore-dotdirs");
1197
+ try {
1198
+ // Create project files that should be included
1199
+ mkdirSync(join(dir, "src"), { recursive: true });
1200
+ writeFileSync(join(dir, "src", "main.ts"), "// main\n", "utf-8");
1201
+ writeFileSync(join(dir, "README.md"), "# Project\n", "utf-8");
1202
+
1203
+ // Create tool directories that should be excluded
1204
+ const excludedDirs = [".claude", ".gsd", ".planning", ".plans", ".cursor", ".vscode"];
1205
+ for (const d of excludedDirs) {
1206
+ mkdirSync(join(dir, d), { recursive: true });
1207
+ writeFileSync(join(dir, d, "config.json"), "{}\n", "utf-8");
1208
+ }
1209
+ // Nested .claude directory
1210
+ mkdirSync(join(dir, ".claude", "memory"), { recursive: true });
1211
+ writeFileSync(join(dir, ".claude", "memory", "user.md"), "# Memory\n", "utf-8");
1212
+
1213
+ const files = scanProjectFiles(dir);
1214
+
1215
+ // Should include project files
1216
+ assert.ok(files.includes("src/main.ts"), "should include src/main.ts");
1217
+ assert.ok(files.includes("README.md"), "should include README.md");
1218
+
1219
+ // Should exclude all tool directories
1220
+ for (const d of excludedDirs) {
1221
+ const hasExcluded = files.some((f) => f.startsWith(`${d}/`));
1222
+ assert.ok(!hasExcluded, `should exclude ${d}/ directory but found: ${files.filter((f) => f.startsWith(`${d}/`)).join(", ")}`);
1223
+ }
1224
+ } finally {
1225
+ cleanup(dir);
1226
+ }
1227
+ });
@@ -0,0 +1,50 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ import { validateFileChanges } from "../safety/file-change-validator.ts";
9
+
10
+ function git(cwd: string, ...args: string[]): string {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ encoding: "utf-8",
15
+ }).trim();
16
+ }
17
+
18
+ test("validateFileChanges ignores inline descriptions in expected output paths", (t) => {
19
+ const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
20
+ t.after(() => rmSync(base, { recursive: true, force: true }));
21
+
22
+ mkdirSync(join(base, "definitions"), { recursive: true });
23
+ git(base, "init");
24
+ git(base, "config", "user.email", "test@example.com");
25
+ git(base, "config", "user.name", "Test User");
26
+
27
+ const target = join(base, "definitions", "ac-audit.md");
28
+ writeFileSync(target, "initial\n");
29
+ git(base, "add", ".");
30
+ git(base, "commit", "-m", "initial");
31
+
32
+ writeFileSync(target, "updated\n");
33
+ git(base, "add", ".");
34
+ git(base, "commit", "-m", "update");
35
+
36
+ const audit = validateFileChanges(
37
+ base,
38
+ ["definitions/ac-audit.md — current state of AC CRM, tags, pipelines, automations"],
39
+ [],
40
+ );
41
+
42
+ assert.ok(audit, "audit should be produced when expected output exists");
43
+ assert.deepEqual(audit.unexpectedFiles, []);
44
+ assert.deepEqual(audit.missingFiles, []);
45
+ assert.equal(
46
+ audit.violations.some((v) => v.severity === "warning"),
47
+ false,
48
+ "described expected output should not trigger unexpected-file warnings",
49
+ );
50
+ });
@@ -245,6 +245,41 @@ describe('gsd-tools', () => {
245
245
  }
246
246
  });
247
247
 
248
+ test('gsd_summary_save supports CONTEXT-DRAFT persistence', async () => {
249
+ const tmpDir = makeTmpDir();
250
+ try {
251
+ const dbPath = path.join(tmpDir, '.gsd', 'gsd.db');
252
+ openDatabase(dbPath);
253
+
254
+ await saveArtifactToDb(
255
+ {
256
+ path: 'milestones/M001/M001-CONTEXT-DRAFT.md',
257
+ artifact_type: 'CONTEXT-DRAFT',
258
+ content: '# M001 Draft Context\n\nDraft notes.',
259
+ milestone_id: 'M001',
260
+ },
261
+ tmpDir,
262
+ );
263
+
264
+ const draftPath = path.join(tmpDir, '.gsd', 'milestones', 'M001', 'M001-CONTEXT-DRAFT.md');
265
+ assert.ok(fs.existsSync(draftPath), 'Draft context file should be created');
266
+ const draftContent = fs.readFileSync(draftPath, 'utf-8');
267
+ assert.ok(draftContent.includes('Draft Context'), 'Draft context file should contain draft content');
268
+
269
+ const adapter = _getAdapter();
270
+ assert.ok(adapter !== null, 'Adapter should be available');
271
+ const rows = adapter!.prepare(
272
+ "SELECT * FROM artifacts WHERE path = 'milestones/M001/M001-CONTEXT-DRAFT.md'",
273
+ ).all();
274
+ assert.deepStrictEqual(rows.length, 1, 'Should have 1 draft artifact row');
275
+ assert.deepStrictEqual(rows[0]['artifact_type'] as string, 'CONTEXT-DRAFT', 'Artifact type should be CONTEXT-DRAFT');
276
+
277
+ closeDatabase();
278
+ } finally {
279
+ cleanupDir(tmpDir);
280
+ }
281
+ });
282
+
248
283
  test('DB unavailable error paths', async () => {
249
284
  // (d) All tools return isError when DB unavailable
250
285
  // Close any open DB and don't open a new one
@@ -10,11 +10,15 @@
10
10
 
11
11
  import { describe, test, beforeEach } from "node:test";
12
12
  import assert from "node:assert/strict";
13
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { tmpdir } from "node:os";
13
16
 
14
17
  import {
15
18
  getDiscussionMilestoneId,
16
19
  setPendingAutoStart,
17
20
  clearPendingAutoStart,
21
+ checkAutoStartAfterDiscuss,
18
22
  } from "../guided-flow.ts";
19
23
 
20
24
  // ─── Tests ─────────────────────────────────────────────────────────────────
@@ -95,3 +99,33 @@ describe("#2985 Bug 4 — getDiscussionMilestoneId must be keyed by basePath", (
95
99
  assert.equal(result, "M001", "should return the only active milestone for backward compat");
96
100
  });
97
101
  });
102
+
103
+ test("checkAutoStartAfterDiscuss fails closed when a multi-milestone manifest is missing", () => {
104
+ const base = mkdtempSync(join(tmpdir(), "gsd-auto-start-manifest-"));
105
+ try {
106
+ const gsdDir = join(base, ".gsd");
107
+ const milestoneDir = join(gsdDir, "milestones", "M001");
108
+ mkdirSync(milestoneDir, { recursive: true });
109
+ mkdirSync(join(gsdDir, "milestones", "M002"), { recursive: true });
110
+ writeFileSync(
111
+ join(gsdDir, "PROJECT.md"),
112
+ `# Project\n\n| M001 | First milestone | active |\n| M002 | Second milestone | queued |\n`,
113
+ );
114
+ writeFileSync(join(gsdDir, "STATE.md"), "# State\n");
115
+ writeFileSync(join(milestoneDir, "M001-CONTEXT.md"), "# M001 Context\n");
116
+
117
+ clearPendingAutoStart();
118
+ setPendingAutoStart(base, {
119
+ basePath: base,
120
+ milestoneId: "M001",
121
+ ctx: { ui: { notify: () => undefined } } as any,
122
+ pi: { setActiveTools: () => undefined, getActiveTools: () => [] } as any,
123
+ });
124
+
125
+ const started = checkAutoStartAfterDiscuss();
126
+ assert.equal(started, false, "auto-start should fail closed without the manifest");
127
+ } finally {
128
+ clearPendingAutoStart();
129
+ rmSync(base, { recursive: true, force: true });
130
+ }
131
+ });