gsd-pi 2.22.0 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +25 -1
  2. package/dist/cli.js +74 -7
  3. package/dist/headless.d.ts +25 -0
  4. package/dist/headless.js +454 -0
  5. package/dist/help-text.js +47 -0
  6. package/dist/mcp-server.d.ts +20 -3
  7. package/dist/mcp-server.js +21 -1
  8. package/dist/models-resolver.d.ts +32 -0
  9. package/dist/models-resolver.js +50 -0
  10. package/dist/resource-loader.js +64 -9
  11. package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
  12. package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
  13. package/dist/resources/extensions/bg-shell/types.ts +33 -1
  14. package/dist/resources/extensions/browser-tools/capture.ts +18 -16
  15. package/dist/resources/extensions/browser-tools/index.ts +20 -0
  16. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  17. package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  18. package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  19. package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
  20. package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
  21. package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  22. package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  23. package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  24. package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  25. package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  26. package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
  28. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
  30. package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
  31. package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
  32. package/dist/resources/extensions/gsd/auto.ts +560 -52
  33. package/dist/resources/extensions/gsd/captures.ts +49 -0
  34. package/dist/resources/extensions/gsd/commands.ts +194 -11
  35. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  37. package/dist/resources/extensions/gsd/diff-context.ts +73 -80
  38. package/dist/resources/extensions/gsd/doctor.ts +76 -12
  39. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  40. package/dist/resources/extensions/gsd/forensics.ts +95 -52
  41. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  42. package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
  43. package/dist/resources/extensions/gsd/index.ts +34 -1
  44. package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
  45. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  46. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  47. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  48. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  49. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  50. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  51. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
  52. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  54. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  56. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  57. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  58. package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
  59. package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
  60. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  61. package/dist/resources/extensions/gsd/state.ts +72 -30
  62. package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  63. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  64. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  65. package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  66. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  67. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  68. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  69. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  70. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  71. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  72. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  73. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  74. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  75. package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  76. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  77. package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  78. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  79. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  80. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  81. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  82. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  83. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  84. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  85. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  86. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  87. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  88. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  89. package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
  90. package/dist/resources/extensions/gsd/types.ts +15 -1
  91. package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  92. package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
  93. package/dist/resources/extensions/subagent/index.ts +5 -0
  94. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  95. package/dist/update-check.d.ts +9 -0
  96. package/dist/update-check.js +97 -0
  97. package/package.json +6 -1
  98. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic.js +16 -7
  100. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  103. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  106. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  110. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  112. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +21 -8
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  117. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  118. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
  125. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
  127. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
  129. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
  131. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
  133. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/index.js +1 -1
  137. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  138. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  139. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  140. package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
  141. package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
  142. package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
  143. package/packages/pi-coding-agent/src/index.ts +1 -0
  144. package/scripts/postinstall.js +7 -109
  145. package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
  146. package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
  147. package/src/resources/extensions/bg-shell/types.ts +33 -1
  148. package/src/resources/extensions/browser-tools/capture.ts +18 -16
  149. package/src/resources/extensions/browser-tools/index.ts +20 -0
  150. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  151. package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  152. package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  153. package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
  154. package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
  155. package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  156. package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  157. package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  158. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  159. package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  160. package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  161. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  163. package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
  164. package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
  165. package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
  166. package/src/resources/extensions/gsd/auto.ts +560 -52
  167. package/src/resources/extensions/gsd/captures.ts +49 -0
  168. package/src/resources/extensions/gsd/commands.ts +194 -11
  169. package/src/resources/extensions/gsd/complexity.ts +1 -0
  170. package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  171. package/src/resources/extensions/gsd/diff-context.ts +73 -80
  172. package/src/resources/extensions/gsd/doctor.ts +76 -12
  173. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  174. package/src/resources/extensions/gsd/forensics.ts +95 -52
  175. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  176. package/src/resources/extensions/gsd/guided-flow.ts +85 -5
  177. package/src/resources/extensions/gsd/index.ts +34 -1
  178. package/src/resources/extensions/gsd/mcp-server.ts +33 -12
  179. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  180. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  181. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  182. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  183. package/src/resources/extensions/gsd/preferences.ts +65 -1
  184. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  185. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  188. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  190. package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  191. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
  193. package/src/resources/extensions/gsd/session-forensics.ts +36 -2
  194. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  195. package/src/resources/extensions/gsd/state.ts +72 -30
  196. package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  197. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  198. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  199. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  200. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  201. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  202. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  203. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  204. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  205. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  206. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  207. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  209. package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  210. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  211. package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  212. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  214. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  215. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  216. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  217. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  218. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  219. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  220. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  221. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  222. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  223. package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
  224. package/src/resources/extensions/gsd/types.ts +15 -1
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  226. package/src/resources/extensions/gsd/workspace-index.ts +34 -6
  227. package/src/resources/extensions/subagent/index.ts +5 -0
  228. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -0,0 +1,534 @@
1
+ /**
2
+ * Integration test for `gsd headless` CLI subcommand
3
+ *
4
+ * Validates that the headless CLI entry point works end-to-end:
5
+ * 1. Creates a temp dir with a complete .gsd/ project fixture
6
+ * 2. Initializes a git repo in the temp dir
7
+ * 3. Spawns `node dist/loader.js headless --json next` as a child process
8
+ * 4. Waits for the process to exit (with a 5-minute timeout)
9
+ * 5. Validates exit code, JSONL stdout, stderr progress, and task artifact
10
+ *
11
+ * Auth: Uses OAuth credentials from ~/.gsd/agent/auth.json (Claude Code Max).
12
+ * Falls back to ANTHROPIC_API_KEY env var if OAuth is not configured (D013).
13
+ *
14
+ * Usage:
15
+ * npx tsx src/resources/extensions/gsd/tests/integration/headless-command.ts
16
+ * Add --dry-run to validate fixture without running the agent.
17
+ */
18
+
19
+ import { mkdtempSync, mkdirSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ import { tmpdir, homedir } from "node:os";
22
+ import { fileURLToPath } from "node:url";
23
+ import { dirname } from "node:path";
24
+ import { spawn, execSync } from "node:child_process";
25
+
26
+ // ── Configuration ────────────────────────────────────────────────────────────
27
+
28
+ const TIMEOUT_MS = parseInt(process.env.HEADLESS_TIMEOUT_MS ?? "300000", 10); // 5 minutes
29
+ const DRY_RUN = process.argv.includes("--dry-run");
30
+
31
+ // ── Fixture Data ─────────────────────────────────────────────────────────────
32
+ // A complete .gsd/ project state that deriveState() can parse.
33
+ // The trivial task asks the agent to create a single file — zero questions needed.
34
+
35
+ const FIXTURE_PROJECT_MD = `# Project
36
+
37
+ ## What This Is
38
+
39
+ Headless proof test project. A minimal fixture used to validate GSD auto-mode via RPC.
40
+
41
+ ## Core Value
42
+
43
+ Proves headless auto-mode works end-to-end.
44
+
45
+ ## Current State
46
+
47
+ Empty project with GSD milestone planned.
48
+
49
+ ## Architecture / Key Patterns
50
+
51
+ - Single milestone, single slice, single task
52
+
53
+ ## Capability Contract
54
+
55
+ None.
56
+
57
+ ## Milestone Sequence
58
+
59
+ - [ ] M001: Headless Proof — Create a test file to prove the agent loop works
60
+ `;
61
+
62
+ const FIXTURE_STATE_MD = `# GSD State
63
+
64
+ **Active Milestone:** M001 — Headless Proof
65
+ **Active Slice:** S01 — Create Test File
66
+ **Phase:** executing
67
+ **Requirements Status:** 0 active · 0 validated · 0 deferred · 0 out of scope
68
+
69
+ ## Milestone Registry
70
+ - 🔄 **M001:** Headless Proof
71
+
72
+ ## Recent Decisions
73
+ - None recorded
74
+
75
+ ## Blockers
76
+ - None
77
+
78
+ ## Next Action
79
+ Execute T01: Create hello.txt in slice S01.
80
+ `;
81
+
82
+ const FIXTURE_CONTEXT_MD = `# M001: Headless Proof — Context
83
+
84
+ **Gathered:** 2025-01-01
85
+ **Status:** Ready for planning
86
+
87
+ ## Project Description
88
+
89
+ A minimal test project for validating GSD auto-mode in headless/RPC mode.
90
+
91
+ ## Why This Milestone
92
+
93
+ Proves that the agent loop can complete a task without a TUI attached.
94
+
95
+ ## User-Visible Outcome
96
+
97
+ ### When this milestone is complete, the user can:
98
+
99
+ - Run GSD in headless mode and have it complete a trivial task
100
+
101
+ ### Entry point / environment
102
+
103
+ - Entry point: RPC mode via headless-proof.ts
104
+ - Environment: local dev
105
+ - Live dependencies involved: none
106
+
107
+ ## Completion Class
108
+
109
+ - Contract complete means: agent creates the requested file
110
+ - Integration complete means: not applicable
111
+ - Operational complete means: not applicable
112
+
113
+ ## Final Integrated Acceptance
114
+
115
+ To call this milestone complete, we must prove:
116
+
117
+ - Agent creates hello.txt with the correct content
118
+
119
+ ## Risks and Unknowns
120
+
121
+ - None — this is a trivial proof task
122
+
123
+ ## Existing Codebase / Prior Art
124
+
125
+ - None
126
+
127
+ ## Relevant Requirements
128
+
129
+ - None
130
+
131
+ ## Scope
132
+
133
+ ### In Scope
134
+
135
+ - Creating a single file
136
+
137
+ ### Out of Scope / Non-Goals
138
+
139
+ - Everything else
140
+
141
+ ## Technical Constraints
142
+
143
+ - None
144
+
145
+ ## Integration Points
146
+
147
+ - None
148
+
149
+ ## Open Questions
150
+
151
+ - None
152
+ `;
153
+
154
+ const FIXTURE_ROADMAP_MD = `# M001: Headless Proof
155
+
156
+ **Vision:** Prove GSD auto-mode works headlessly.
157
+
158
+ ## Success Criteria
159
+
160
+ - Agent creates hello.txt with content "Hello from headless GSD"
161
+
162
+ ## Key Risks / Unknowns
163
+
164
+ - None
165
+
166
+ ## Slices
167
+
168
+ - [ ] **S01: Create Test File** \`risk:low\` \`depends:[]\`
169
+ > After this: hello.txt exists in the project root
170
+
171
+ ## Boundary Map
172
+
173
+ ### S01
174
+
175
+ Produces:
176
+ - hello.txt file in project root
177
+
178
+ Consumes:
179
+ - nothing (first slice)
180
+ `;
181
+
182
+ const FIXTURE_PLAN_MD = `# S01: Create Test File
183
+
184
+ **Goal:** Create a single file to prove the agent loop works headlessly.
185
+ **Demo:** hello.txt exists with the correct content after the agent runs.
186
+
187
+ ## Must-Haves
188
+
189
+ - hello.txt created with content "Hello from headless GSD"
190
+
191
+ ## Verification
192
+
193
+ - File hello.txt exists in project root with content "Hello from headless GSD"
194
+
195
+ ## Tasks
196
+
197
+ - [ ] **T01: Create hello.txt** \`est:5m\`
198
+ - Why: Proves the agent can execute a tool call and produce an artifact
199
+ - Files: \`hello.txt\`
200
+ - Do: Create a file called hello.txt in the project root with the content "Hello from headless GSD"
201
+ - Verify: File exists with correct content
202
+ - Done when: hello.txt exists with content "Hello from headless GSD"
203
+
204
+ ## Files Likely Touched
205
+
206
+ - \`hello.txt\`
207
+ `;
208
+
209
+ const FIXTURE_TASK_PLAN_MD = `---
210
+ estimated_steps: 1
211
+ estimated_files: 1
212
+ ---
213
+
214
+ # T01: Create hello.txt
215
+
216
+ **Slice:** S01 — Create Test File
217
+ **Milestone:** M001
218
+
219
+ ## Description
220
+
221
+ Create a file called hello.txt in the project root with the content "Hello from headless GSD".
222
+
223
+ ## Steps
224
+
225
+ 1. Create the file hello.txt with the content "Hello from headless GSD"
226
+
227
+ ## Must-Haves
228
+
229
+ - [ ] hello.txt created with content "Hello from headless GSD"
230
+
231
+ ## Verification
232
+
233
+ - File hello.txt exists in project root with content "Hello from headless GSD"
234
+
235
+ ## Expected Output
236
+
237
+ - \`hello.txt\` — file containing "Hello from headless GSD"
238
+ `;
239
+
240
+ // ── Fixture Creation ─────────────────────────────────────────────────────────
241
+
242
+ function createFixture(): string {
243
+ const tmpDir = mkdtempSync(join(tmpdir(), "gsd-headless-cmd-"));
244
+
245
+ // Initialize git repo (GSD requires it for branch-per-slice)
246
+ execSync("git init -b main", { cwd: tmpDir, stdio: "pipe" });
247
+ execSync('git config user.email "test@test.com"', { cwd: tmpDir, stdio: "pipe" });
248
+ execSync('git config user.name "Test"', { cwd: tmpDir, stdio: "pipe" });
249
+
250
+ // Create .gsd/ structure
251
+ const gsdDir = join(tmpDir, ".gsd");
252
+ const milestonesDir = join(gsdDir, "milestones");
253
+ const m001Dir = join(milestonesDir, "M001");
254
+ const slicesDir = join(m001Dir, "slices");
255
+ const s01Dir = join(slicesDir, "S01");
256
+ const tasksDir = join(s01Dir, "tasks");
257
+
258
+ mkdirSync(tasksDir, { recursive: true });
259
+
260
+ // Write fixture files
261
+ writeFileSync(join(gsdDir, "PROJECT.md"), FIXTURE_PROJECT_MD);
262
+ writeFileSync(join(gsdDir, "STATE.md"), FIXTURE_STATE_MD);
263
+ writeFileSync(join(m001Dir, "M001-CONTEXT.md"), FIXTURE_CONTEXT_MD);
264
+ writeFileSync(join(m001Dir, "M001-ROADMAP.md"), FIXTURE_ROADMAP_MD);
265
+ writeFileSync(join(s01Dir, "S01-PLAN.md"), FIXTURE_PLAN_MD);
266
+ writeFileSync(join(tasksDir, "T01-PLAN.md"), FIXTURE_TASK_PLAN_MD);
267
+
268
+ // Add .gitignore for runtime files
269
+ writeFileSync(join(tmpDir, ".gitignore"), [
270
+ ".gsd/auto.lock",
271
+ ".gsd/completed-units.json",
272
+ ".gsd/metrics.json",
273
+ ".gsd/activity/",
274
+ ".gsd/runtime/",
275
+ ].join("\n") + "\n");
276
+
277
+ // Initial commit so GSD has a clean git state
278
+ execSync("git add -A && git commit -m 'init: headless command test fixture'", {
279
+ cwd: tmpDir,
280
+ stdio: "pipe",
281
+ });
282
+
283
+ return tmpDir;
284
+ }
285
+
286
+ function cleanup(dir: string): void {
287
+ try {
288
+ rmSync(dir, { recursive: true, force: true });
289
+ } catch {
290
+ // Best effort
291
+ console.warn(` [warn] Failed to clean up temp dir: ${dir}`);
292
+ }
293
+ }
294
+
295
+ // ── JSONL Parsing ────────────────────────────────────────────────────────────
296
+
297
+ interface JsonlEvent {
298
+ type?: string;
299
+ [key: string]: unknown;
300
+ }
301
+
302
+ function parseJsonlLines(output: string): JsonlEvent[] {
303
+ const events: JsonlEvent[] = [];
304
+ for (const line of output.split("\n")) {
305
+ const trimmed = line.trim();
306
+ if (!trimmed) continue;
307
+ try {
308
+ events.push(JSON.parse(trimmed) as JsonlEvent);
309
+ } catch {
310
+ // Not valid JSON — skip (could be non-JSONL output)
311
+ }
312
+ }
313
+ return events;
314
+ }
315
+
316
+ // ── Main ─────────────────────────────────────────────────────────────────────
317
+
318
+ async function main(): Promise<void> {
319
+ const __filename = fileURLToPath(import.meta.url);
320
+ const __dirname = dirname(__filename);
321
+ // Resolve gsd-2 repo root (6 levels up from tests/integration/)
322
+ const repoRoot = join(__dirname, "..", "..", "..", "..", "..", "..");
323
+
324
+ console.log("=== GSD Headless Command Integration Test ===\n");
325
+
326
+ // ── Step 1: Create fixture ──────────────────────────────────────────────
327
+ console.log("[1/6] Creating fixture...");
328
+ const fixtureDir = createFixture();
329
+ console.log(` Fixture created at: ${fixtureDir}`);
330
+
331
+ // Validate fixture structure
332
+ const requiredFiles = [
333
+ ".gsd/PROJECT.md",
334
+ ".gsd/STATE.md",
335
+ ".gsd/milestones/M001/M001-CONTEXT.md",
336
+ ".gsd/milestones/M001/M001-ROADMAP.md",
337
+ ".gsd/milestones/M001/slices/S01/S01-PLAN.md",
338
+ ".gsd/milestones/M001/slices/S01/tasks/T01-PLAN.md",
339
+ ];
340
+
341
+ for (const file of requiredFiles) {
342
+ const fullPath = join(fixtureDir, file);
343
+ if (!existsSync(fullPath)) {
344
+ console.error(` FAIL: Missing fixture file: ${file}`);
345
+ cleanup(fixtureDir);
346
+ process.exit(1);
347
+ }
348
+ console.log(` OK ${file}`);
349
+ }
350
+
351
+ // ── Step 2: Validate environment ────────────────────────────────────────
352
+ console.log("\n[2/6] Validating environment...");
353
+
354
+ // Auth: prefer OAuth credentials from ~/.gsd/agent/auth.json (D013).
355
+ // Fall back to ANTHROPIC_API_KEY env var if present.
356
+ const authJsonPath = join(homedir(), ".gsd", "agent", "auth.json");
357
+ let hasOAuth = false;
358
+ if (existsSync(authJsonPath)) {
359
+ try {
360
+ const authData = JSON.parse(readFileSync(authJsonPath, "utf-8"));
361
+ hasOAuth = authData?.anthropic?.type === "oauth";
362
+ } catch {
363
+ // Non-fatal
364
+ }
365
+ }
366
+
367
+ if (hasOAuth) {
368
+ console.log(" OK OAuth credentials found in ~/.gsd/agent/auth.json (Claude Code Max)");
369
+ } else if (process.env.ANTHROPIC_API_KEY) {
370
+ console.log(" OK ANTHROPIC_API_KEY present (env var fallback)");
371
+ } else {
372
+ console.error(" FAIL: No auth available. Need either:");
373
+ console.error(" - OAuth credentials in ~/.gsd/agent/auth.json (Claude Code Max)");
374
+ console.error(" - ANTHROPIC_API_KEY environment variable");
375
+ cleanup(fixtureDir);
376
+ process.exit(1);
377
+ }
378
+
379
+ const loaderPath = join(repoRoot, "dist", "loader.js");
380
+ if (!existsSync(loaderPath)) {
381
+ console.error(` FAIL: CLI not found at ${loaderPath}. Run 'npm run build' first.`);
382
+ cleanup(fixtureDir);
383
+ process.exit(1);
384
+ }
385
+ console.log(` OK CLI found at ${loaderPath}`);
386
+
387
+ // ── Step 3: Dry-run exit ────────────────────────────────────────────────
388
+ if (DRY_RUN) {
389
+ console.log("\n[dry-run] Fixture validated. Skipping headless execution.");
390
+ console.log("[dry-run] All checks passed.\n");
391
+ cleanup(fixtureDir);
392
+ process.exit(0);
393
+ }
394
+
395
+ // ── Step 4: Spawn headless command ──────────────────────────────────────
396
+ console.log("\n[3/6] Spawning headless command...");
397
+ console.log(` Command: node ${loaderPath} headless --json next`);
398
+ console.log(` CWD: ${fixtureDir}`);
399
+ console.log(` Timeout: ${TIMEOUT_MS / 1000}s`);
400
+
401
+ const { exitCode, stdout, stderr } = await new Promise<{
402
+ exitCode: number | null;
403
+ stdout: string;
404
+ stderr: string;
405
+ }>((resolve) => {
406
+ let stdoutBuf = "";
407
+ let stderrBuf = "";
408
+ let settled = false;
409
+
410
+ const child = spawn("node", [loaderPath, "headless", "--json", "next"], {
411
+ cwd: fixtureDir,
412
+ env: { ...process.env },
413
+ stdio: ["ignore", "pipe", "pipe"],
414
+ });
415
+
416
+ child.stdout.on("data", (chunk: Buffer) => {
417
+ stdoutBuf += chunk.toString();
418
+ });
419
+
420
+ child.stderr.on("data", (chunk: Buffer) => {
421
+ const text = chunk.toString();
422
+ stderrBuf += text;
423
+ // Stream stderr for live progress visibility
424
+ process.stderr.write(` [headless] ${text}`);
425
+ });
426
+
427
+ const timer = setTimeout(() => {
428
+ if (!settled) {
429
+ settled = true;
430
+ console.error(`\n TIMEOUT: Process did not exit within ${TIMEOUT_MS / 1000}s. Killing...`);
431
+ child.kill("SIGTERM");
432
+ // Give it a moment to exit gracefully, then force kill
433
+ setTimeout(() => {
434
+ if (!child.killed) child.kill("SIGKILL");
435
+ }, 5000);
436
+ resolve({ exitCode: null, stdout: stdoutBuf, stderr: stderrBuf });
437
+ }
438
+ }, TIMEOUT_MS);
439
+
440
+ child.on("close", (code) => {
441
+ if (!settled) {
442
+ settled = true;
443
+ clearTimeout(timer);
444
+ resolve({ exitCode: code, stdout: stdoutBuf, stderr: stderrBuf });
445
+ }
446
+ });
447
+
448
+ child.on("error", (err) => {
449
+ if (!settled) {
450
+ settled = true;
451
+ clearTimeout(timer);
452
+ stderrBuf += `\nSpawn error: ${err.message}`;
453
+ resolve({ exitCode: 1, stdout: stdoutBuf, stderr: stderrBuf });
454
+ }
455
+ });
456
+ });
457
+
458
+ // ── Step 5: Validate results ────────────────────────────────────────────
459
+ console.log("\n[4/6] Validating process output...");
460
+
461
+ let allPassed = true;
462
+
463
+ // Check 1: Exit code
464
+ const exitOk = exitCode === 0;
465
+ console.log(` ${exitOk ? "PASS" : "FAIL"} Exit code: ${exitCode ?? "null (timeout)"}`);
466
+ if (!exitOk) allPassed = false;
467
+
468
+ // Check 2: stdout contains JSONL events
469
+ const events = parseJsonlLines(stdout);
470
+ const hasJsonlEvents = events.length > 0;
471
+ console.log(` ${hasJsonlEvents ? "PASS" : "FAIL"} JSONL events in stdout: ${events.length}`);
472
+ if (!hasJsonlEvents) allPassed = false;
473
+
474
+ if (hasJsonlEvents) {
475
+ // Summarize event types
476
+ const typeCounts: Record<string, number> = {};
477
+ for (const event of events) {
478
+ const type = String(event.type ?? "unknown");
479
+ typeCounts[type] = (typeCounts[type] ?? 0) + 1;
480
+ }
481
+ console.log(` Event types: ${JSON.stringify(typeCounts)}`);
482
+ }
483
+
484
+ // Check 3: stderr contains progress output
485
+ const hasStderrOutput = stderr.trim().length > 0;
486
+ console.log(` ${hasStderrOutput ? "PASS" : "FAIL"} stderr contains progress output: ${hasStderrOutput} (${stderr.length} bytes)`);
487
+ if (!hasStderrOutput) allPassed = false;
488
+
489
+ // ── Step 6: Verify artifact ─────────────────────────────────────────────
490
+ console.log("\n[5/6] Verifying task artifact...");
491
+
492
+ const helloPath = join(fixtureDir, "hello.txt");
493
+ const artifactExists = existsSync(helloPath);
494
+ console.log(` ${artifactExists ? "PASS" : "FAIL"} hello.txt exists: ${artifactExists}`);
495
+ if (!artifactExists) allPassed = false;
496
+
497
+ if (artifactExists) {
498
+ const content = readFileSync(helloPath, "utf-8").trim();
499
+ const contentMatch = content === "Hello from headless GSD";
500
+ console.log(` ${contentMatch ? "PASS" : "WARN"} hello.txt content: "${content.slice(0, 80)}"`);
501
+ }
502
+
503
+ // ── Summary ─────────────────────────────────────────────────────────────
504
+ console.log("\n[6/6] Summary");
505
+ console.log(` Exit code: ${exitCode ?? "null (timeout)"}`);
506
+ console.log(` JSONL events: ${events.length}`);
507
+ console.log(` stderr length: ${stderr.length} bytes`);
508
+ console.log(` hello.txt exists: ${artifactExists}`);
509
+
510
+ // Cleanup
511
+ cleanup(fixtureDir);
512
+
513
+ if (allPassed) {
514
+ console.log("\n=== PASSED ===\n");
515
+ process.exit(0);
516
+ } else {
517
+ // Print diagnostic info on failure
518
+ if (stdout.length > 0) {
519
+ console.log(`\n--- stdout (last 2000 chars) ---`);
520
+ console.log(stdout.slice(-2000));
521
+ }
522
+ if (stderr.length > 0) {
523
+ console.log(`\n--- stderr (last 2000 chars) ---`);
524
+ console.log(stderr.slice(-2000));
525
+ }
526
+ console.log("\n=== FAILED ===\n");
527
+ process.exit(1);
528
+ }
529
+ }
530
+
531
+ main().catch((err) => {
532
+ console.error("Unhandled error:", err);
533
+ process.exit(1);
534
+ });
@@ -51,6 +51,12 @@ function writeMilestoneSummary(base: string, mid: string, content: string): void
51
51
  writeFileSync(join(dir, `${mid}-SUMMARY.md`), content);
52
52
  }
53
53
 
54
+ function writeMilestoneValidation(base: string, mid: string): void {
55
+ const dir = join(base, '.gsd', 'milestones', mid);
56
+ mkdirSync(dir, { recursive: true });
57
+ writeFileSync(join(dir, `${mid}-VALIDATION.md`), `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nPassed.`);
58
+ }
59
+
54
60
  function cleanup(base: string): void {
55
61
  rmSync(base, { recursive: true, force: true });
56
62
  }
@@ -166,6 +172,7 @@ async function main(): Promise<void> {
166
172
  Did it.
167
173
  `);
168
174
 
175
+ writeMilestoneValidation(base, 'M001');
169
176
  writeMilestoneSummary(base, 'M001', `# M001: Legacy Feature Summary
170
177
 
171
178
  **One-liner summary**
@@ -265,6 +272,7 @@ Everything worked.
265
272
  Did it.
266
273
  `);
267
274
 
275
+ writeMilestoneValidation(base, 'M001');
268
276
  writeMilestoneSummary(base, 'M001', `# M001: Legacy Feature Summary
269
277
 
270
278
  **One-liner summary**
@@ -263,12 +263,12 @@ async function main(): Promise<void> {
263
263
  // No REQUIREMENTS.md since empty requirements
264
264
  assertTrue(!existsSync(join(base, '.gsd', 'REQUIREMENTS.md')), 'complete: REQUIREMENTS.md NOT written (empty)');
265
265
 
266
- // deriveState: all slices done, all tasks done — needs milestone summary for 'complete'
267
- // Without milestone summary, it should be 'completing-milestone' or 'summarizing'
266
+ // deriveState: all slices done, all tasks done — needs validation then milestone summary
267
+ // Without VALIDATION file, it should be 'validating-milestone'
268
268
  const state = await deriveState(base);
269
- // All slices are done in roadmap. Milestone summary doesn't exist.
270
- // deriveState should return 'completing-milestone' since all slices done but no milestone summary.
271
- assertEq(state.phase, 'completing-milestone', 'complete: deriveState phase is completing-milestone');
269
+ // All slices are done in roadmap. No VALIDATION or SUMMARY exists.
270
+ // deriveState should return 'validating-milestone' since validation gate precedes completion.
271
+ assertEq(state.phase, 'validating-milestone', 'complete: deriveState phase is validating-milestone');
272
272
  assertTrue(state.activeMilestone !== null, 'complete: deriveState has activeMilestone');
273
273
  assertEq(state.activeMilestone!.id, 'M001', 'complete: deriveState activeMilestone is M001');
274
274