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.
- package/README.md +25 -1
- package/dist/cli.js +74 -7
- package/dist/headless.d.ts +25 -0
- package/dist/headless.js +454 -0
- package/dist/help-text.js +47 -0
- package/dist/mcp-server.d.ts +20 -3
- package/dist/mcp-server.js +21 -1
- package/dist/models-resolver.d.ts +32 -0
- package/dist/models-resolver.js +50 -0
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/dist/resources/extensions/bg-shell/types.ts +33 -1
- package/dist/resources/extensions/browser-tools/capture.ts +18 -16
- package/dist/resources/extensions/browser-tools/index.ts +20 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +560 -52
- package/dist/resources/extensions/gsd/captures.ts +49 -0
- package/dist/resources/extensions/gsd/commands.ts +194 -11
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/dist/resources/extensions/gsd/diff-context.ts +73 -80
- package/dist/resources/extensions/gsd/doctor.ts +76 -12
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/forensics.ts +95 -52
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +21 -8
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
- package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
- package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
- package/src/resources/extensions/bg-shell/types.ts +33 -1
- package/src/resources/extensions/browser-tools/capture.ts +18 -16
- package/src/resources/extensions/browser-tools/index.ts +20 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
- package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
- package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
- package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
- package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
- package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
- package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
- package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
- package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +560 -52
- package/src/resources/extensions/gsd/captures.ts +49 -0
- package/src/resources/extensions/gsd/commands.ts +194 -11
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
- package/src/resources/extensions/gsd/diff-context.ts +73 -80
- package/src/resources/extensions/gsd/doctor.ts +76 -12
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +95 -52
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +85 -5
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/mcp-server.ts +33 -12
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
- package/src/resources/extensions/gsd/session-forensics.ts +36 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
- package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
- package/src/resources/extensions/gsd/workspace-index.ts +34 -6
- package/src/resources/extensions/subagent/index.ts +5 -0
- 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
|
|
267
|
-
// Without
|
|
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.
|
|
270
|
-
// deriveState should return '
|
|
271
|
-
assertEq(state.phase, '
|
|
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
|
|