gsd-pi 2.24.0 → 2.26.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 +13 -3
- package/dist/headless.js +24 -4
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +64 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +65 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -51,11 +51,11 @@ Use these templates exactly:
|
|
|
51
51
|
5. Write `{{roadmapPath}}` (using Roadmap template) — decompose into demoable vertical slices with checkboxes, risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice.
|
|
52
52
|
6. Seed `.gsd/DECISIONS.md` (using Decisions template)
|
|
53
53
|
7. Update `.gsd/STATE.md`
|
|
54
|
-
8.
|
|
54
|
+
8. {{commitInstruction}}
|
|
55
55
|
9. Say exactly: "Milestone {{milestoneId}} ready."
|
|
56
56
|
|
|
57
57
|
**For multi-milestone**, write in this order:
|
|
58
|
-
1.
|
|
58
|
+
1. For each milestone, call `gsd_generate_milestone_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices` for each.
|
|
59
59
|
2. Write `.gsd/PROJECT.md` — full vision across ALL milestones (using Project template)
|
|
60
60
|
3. Write `.gsd/REQUIREMENTS.md` — full capability contract (using Requirements template)
|
|
61
61
|
4. Seed `.gsd/DECISIONS.md` (using Decisions template)
|
|
@@ -71,7 +71,7 @@ Use these templates exactly:
|
|
|
71
71
|
```
|
|
72
72
|
Each context file should be rich enough that a future agent — with no memory of this conversation — can understand the intent, constraints, dependencies, what the milestone unlocks, and what "done" looks like.
|
|
73
73
|
8. Update `.gsd/STATE.md`
|
|
74
|
-
9.
|
|
74
|
+
9. {{multiMilestoneCommitInstruction}}
|
|
75
75
|
10. Say exactly: "Milestone {{milestoneId}} ready."
|
|
76
76
|
|
|
77
77
|
## Critical Rules
|
|
@@ -82,5 +82,5 @@ Use these templates exactly:
|
|
|
82
82
|
- **Investigate before writing** — always scout the codebase first
|
|
83
83
|
- **Use depends_on frontmatter** for multi-milestone sequences (the state machine reads this field to determine execution order)
|
|
84
84
|
- **Anti-reduction rule** — if the spec describes a big vision, plan the big vision. Do not ask "what's the minimum viable version?" or reduce scope. Phase complex/risky work into later milestones — do not cut it.
|
|
85
|
-
- **Naming convention** —
|
|
85
|
+
- **Naming convention** — always use `gsd_generate_milestone_id` to get milestone IDs. Directories use bare IDs (e.g. `M001/` or `M001-r5jzab/`), files use ID-SUFFIX format (e.g. `M001-CONTEXT.md` or `M001-r5jzab-CONTEXT.md`). Never invent milestone IDs manually.
|
|
86
86
|
- **End with "Milestone {{milestoneId}} ready."** — this triggers auto-start detection
|
|
@@ -201,9 +201,9 @@ When writing context.md, preserve the user's exact terminology, emphasis, and sp
|
|
|
201
201
|
5. Write `{{roadmapPath}}` — use the **Roadmap** output template below. Decompose into demoable vertical slices with checkboxes, risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment.
|
|
202
202
|
6. Seed `.gsd/DECISIONS.md` — use the **Decisions** output template below. Append rows for any architectural or pattern decisions made during discussion.
|
|
203
203
|
7. Update `.gsd/STATE.md`
|
|
204
|
-
8.
|
|
204
|
+
8. {{commitInstruction}}
|
|
205
205
|
|
|
206
|
-
After writing the files
|
|
206
|
+
After writing the files, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
207
207
|
|
|
208
208
|
### Multi-Milestone
|
|
209
209
|
|
|
@@ -211,7 +211,7 @@ Once the user confirms the milestone split:
|
|
|
211
211
|
|
|
212
212
|
#### Phase 1: Shared artifacts
|
|
213
213
|
|
|
214
|
-
1. `mkdir -p .gsd/milestones
|
|
214
|
+
1. For each milestone, call `gsd_generate_milestone_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
|
215
215
|
2. Write `.gsd/PROJECT.md` — use the **Project** output template below.
|
|
216
216
|
3. Write `.gsd/REQUIREMENTS.md` — use the **Requirements** output template below. Capture Active, Deferred, Out of Scope, and any already Validated requirements. Later milestones may have provisional ownership where slice plans do not exist yet.
|
|
217
217
|
4. Seed `.gsd/DECISIONS.md` — use the **Decisions** output template below.
|
|
@@ -271,8 +271,8 @@ For single-milestone projects, do NOT write this file — it is only for multi-m
|
|
|
271
271
|
#### Phase 4: Finalize
|
|
272
272
|
|
|
273
273
|
7. Update `.gsd/STATE.md`
|
|
274
|
-
8.
|
|
274
|
+
8. {{multiMilestoneCommitInstruction}}
|
|
275
275
|
|
|
276
|
-
After writing the files
|
|
276
|
+
After writing the files, say exactly: "Milestone M001 ready." — nothing else. Auto-mode will start automatically.
|
|
277
277
|
|
|
278
278
|
{{inlinedTemplates}}
|
|
@@ -63,7 +63,7 @@ Then:
|
|
|
63
63
|
14. Read the template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md`
|
|
64
64
|
15. Write `{{taskSummaryPath}}`
|
|
65
65
|
16. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
|
|
66
|
-
17. Do not
|
|
66
|
+
17. Do not run git commands — the system reads your task summary after completion and creates a meaningful commit from it (type inferred from title, message from your one-liner, key files from frontmatter). Write a clear, specific one-liner in the summary — it becomes the commit message.
|
|
67
67
|
18. Update `.gsd/STATE.md`
|
|
68
68
|
|
|
69
69
|
All work stays in your working directory: `{{workingDirectory}}`.
|
|
@@ -104,5 +104,5 @@ Once the user confirms depth:
|
|
|
104
104
|
1. Use the **Context** output template below
|
|
105
105
|
2. `mkdir -p` the milestone directory if needed
|
|
106
106
|
3. Write `{{milestoneId}}-CONTEXT.md` — preserve the user's exact terminology, emphasis, and framing. Do not paraphrase nuance into generic summaries. The context file is downstream agents' only window into this conversation.
|
|
107
|
-
4.
|
|
107
|
+
4. {{commitInstruction}}
|
|
108
108
|
5. Say exactly: `"{{milestoneId}} context written."` — nothing else.
|
|
@@ -55,7 +55,7 @@ Once the user is ready to wrap up:
|
|
|
55
55
|
- **Constraints** — anything the user flagged as a hard constraint
|
|
56
56
|
- **Integration Points** — what this slice consumes and produces
|
|
57
57
|
- **Open Questions** — anything still unresolved, with current thinking
|
|
58
|
-
4.
|
|
58
|
+
4. {{commitInstruction}}
|
|
59
59
|
5. Say exactly: `"{{sliceId}} context written."` — nothing else.
|
|
60
60
|
|
|
61
61
|
{{inlinedTemplates}}
|
|
@@ -59,7 +59,7 @@ Then:
|
|
|
59
59
|
- **Scope sanity:** Target 2–5 steps and 3–8 files per task. 10+ steps or 12+ files — must split. Each task must be completable in a single fresh context window.
|
|
60
60
|
- **Feature completeness:** Every task produces real, user-facing progress — not just internal scaffolding.
|
|
61
61
|
9. If planning produced structural decisions, append them to `.gsd/DECISIONS.md`
|
|
62
|
-
10.
|
|
62
|
+
10. {{commitInstruction}}
|
|
63
63
|
11. Update `.gsd/STATE.md`
|
|
64
64
|
|
|
65
65
|
The slice directory and tasks/ subdirectory already exist. Do NOT mkdir. All work stays in your working directory: `{{workingDirectory}}`.
|
|
@@ -79,9 +79,9 @@ Determine where the new milestones should go in the overall sequence. Consider d
|
|
|
79
79
|
|
|
80
80
|
## Output Phase
|
|
81
81
|
|
|
82
|
-
Once the user is satisfied, in a single pass for **each** new milestone
|
|
82
|
+
Once the user is satisfied, in a single pass for **each** new milestone:
|
|
83
83
|
|
|
84
|
-
1. `mkdir -p .gsd/milestones/<ID>/slices
|
|
84
|
+
1. Call `gsd_generate_milestone_id` to get the milestone ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
|
85
85
|
2. Write `.gsd/milestones/<ID>/<ID>-CONTEXT.md` — use the **Context** output template below. Capture intent, scope, risks, constraints, integration points, and relevant requirements. Mark the status as "Queued — pending auto-mode execution." **If this milestone depends on other milestones, add YAML frontmatter with `depends_on`:**
|
|
86
86
|
```yaml
|
|
87
87
|
---
|
|
@@ -96,7 +96,7 @@ Then, after all milestone directories and context files are written:
|
|
|
96
96
|
4. If `.gsd/REQUIREMENTS.md` exists and the queued work introduces new in-scope capabilities or promotes Deferred items, update it.
|
|
97
97
|
5. If discussion produced decisions relevant to existing work, append to `.gsd/DECISIONS.md`.
|
|
98
98
|
6. Append to `.gsd/QUEUE.md`.
|
|
99
|
-
7.
|
|
99
|
+
7. {{commitInstruction}}
|
|
100
100
|
|
|
101
101
|
**Do NOT write roadmaps for queued milestones.**
|
|
102
102
|
**Do NOT update `.gsd/STATE.md`.**
|
|
@@ -57,7 +57,7 @@ Write `{{assessmentPath}}` with a brief confirmation that roadmap coverage still
|
|
|
57
57
|
1. Rewrite the remaining (unchecked) slices in `{{roadmapPath}}`. Keep completed slices exactly as they are (`[x]`). Update the boundary map for changed slices. Update the proof strategy if risks changed. Update requirement coverage if ownership or scope changed.
|
|
58
58
|
2. Write `{{assessmentPath}}` explaining what changed and why — keep it brief and concrete.
|
|
59
59
|
3. If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, update it.
|
|
60
|
-
4.
|
|
60
|
+
4. {{commitInstruction}}
|
|
61
61
|
|
|
62
62
|
**You MUST write the file `{{assessmentPath}}` before finishing.**
|
|
63
63
|
|
|
@@ -53,7 +53,12 @@ function extractSlicesSection(content: string): string {
|
|
|
53
53
|
export function parseRoadmapSlices(content: string): RoadmapSliceEntry[] {
|
|
54
54
|
const slicesSection = extractSlicesSection(content);
|
|
55
55
|
const slices: RoadmapSliceEntry[] = [];
|
|
56
|
-
if (!slicesSection)
|
|
56
|
+
if (!slicesSection) {
|
|
57
|
+
// Fallback: detect prose-style slice headers (## Slice S01: Title)
|
|
58
|
+
// when the LLM writes freeform prose instead of the ## Slices checklist.
|
|
59
|
+
// This prevents a permanent "No slice eligible" block (#807).
|
|
60
|
+
return parseProseSliceHeaders(content);
|
|
61
|
+
}
|
|
57
62
|
|
|
58
63
|
const checkboxItems = slicesSection.split("\n");
|
|
59
64
|
let currentSlice: RoadmapSliceEntry | null = null;
|
|
@@ -88,3 +93,42 @@ export function parseRoadmapSlices(content: string): RoadmapSliceEntry[] {
|
|
|
88
93
|
if (currentSlice) slices.push(currentSlice);
|
|
89
94
|
return slices;
|
|
90
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fallback parser for prose-style roadmaps where the LLM wrote
|
|
99
|
+
* `## Slice S01: Title` headers instead of the machine-readable
|
|
100
|
+
* `## Slices` checklist. Extracts slice IDs and titles so auto-mode
|
|
101
|
+
* can at least identify slices and plan them.
|
|
102
|
+
*
|
|
103
|
+
* Also handles `## S01: Title` and `## S01 — Title` variants.
|
|
104
|
+
*/
|
|
105
|
+
function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
|
|
106
|
+
const slices: RoadmapSliceEntry[] = [];
|
|
107
|
+
const headerPattern = /^##\s+(?:Slice\s+)?(S\d+)[:\s—–-]+\s*(.+)/gm;
|
|
108
|
+
let match: RegExpExecArray | null;
|
|
109
|
+
|
|
110
|
+
while ((match = headerPattern.exec(content)) !== null) {
|
|
111
|
+
const id = match[1]!;
|
|
112
|
+
const title = match[2]!.trim();
|
|
113
|
+
|
|
114
|
+
// Try to extract depends from prose: "Depends on: S01" or "**Depends on:** S01, S02"
|
|
115
|
+
const afterHeader = content.slice(match.index + match[0].length);
|
|
116
|
+
const nextHeader = afterHeader.search(/^##\s/m);
|
|
117
|
+
const section = nextHeader !== -1 ? afterHeader.slice(0, nextHeader) : afterHeader.slice(0, 500);
|
|
118
|
+
|
|
119
|
+
const depsMatch = section.match(/\*{0,2}Depends\s+on:?\*{0,2}\s*(.+)/i);
|
|
120
|
+
let depends: string[] = [];
|
|
121
|
+
if (depsMatch) {
|
|
122
|
+
const rawDeps = depsMatch[1]!.replace(/none/i, "").trim();
|
|
123
|
+
if (rawDeps) {
|
|
124
|
+
depends = expandDependencies(
|
|
125
|
+
rawDeps.split(/[,;]/).map(s => s.trim().replace(/[^A-Za-z0-9]/g, "")).filter(Boolean)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
slices.push({ id, title, risk: "medium" as RiskLevel, depends, done: false, demo: "" });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return slices;
|
|
134
|
+
}
|
|
@@ -62,7 +62,11 @@ export function isValidationTerminal(validationContent: string): boolean {
|
|
|
62
62
|
if (!match) return false;
|
|
63
63
|
const verdict = match[1].match(/verdict:\s*(\S+)/);
|
|
64
64
|
if (!verdict) return false;
|
|
65
|
-
|
|
65
|
+
// 'pass' and 'needs-attention' are always terminal.
|
|
66
|
+
// 'needs-remediation' is treated as terminal to prevent infinite loops
|
|
67
|
+
// when no remediation slices exist in the roadmap (#832). The validation
|
|
68
|
+
// report is preserved on disk for manual review.
|
|
69
|
+
return verdict[1] === 'pass' || verdict[1] === 'needs-attention' || verdict[1] === 'needs-remediation';
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// ─── State Derivation ──────────────────────────────────────────────────────
|
|
@@ -290,19 +294,26 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
290
294
|
|
|
291
295
|
if (complete) {
|
|
292
296
|
// All slices done — check validation and summary state
|
|
297
|
+
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
|
293
298
|
const validationFile = resolveMilestoneFile(basePath, mid, "VALIDATION");
|
|
294
299
|
const validationContent = validationFile ? await cachedLoadFile(validationFile) : null;
|
|
295
300
|
const validationTerminal = validationContent ? isValidationTerminal(validationContent) : false;
|
|
296
|
-
const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
|
|
297
301
|
|
|
298
|
-
if (
|
|
299
|
-
//
|
|
302
|
+
if (summaryFile) {
|
|
303
|
+
// Summary exists → milestone is complete regardless of validation state.
|
|
304
|
+
// The summary is the terminal artifact (#864).
|
|
305
|
+
registry.push({ id: mid, title, status: 'complete' });
|
|
306
|
+
} else if (!validationTerminal && !activeMilestoneFound) {
|
|
307
|
+
// No summary and no terminal validation → validating-milestone
|
|
300
308
|
activeMilestone = { id: mid, title };
|
|
301
309
|
activeRoadmap = roadmap;
|
|
302
310
|
activeMilestoneFound = true;
|
|
303
311
|
registry.push({ id: mid, title, status: 'active' });
|
|
304
|
-
} else if (!
|
|
305
|
-
//
|
|
312
|
+
} else if (!validationTerminal && activeMilestoneFound) {
|
|
313
|
+
// No summary and no terminal validation, but another milestone is already active
|
|
314
|
+
registry.push({ id: mid, title, status: 'pending' });
|
|
315
|
+
} else if (!activeMilestoneFound) {
|
|
316
|
+
// Terminal validation but no summary → completing-milestone
|
|
306
317
|
activeMilestone = { id: mid, title };
|
|
307
318
|
activeRoadmap = roadmap;
|
|
308
319
|
activeMilestoneFound = true;
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
loadPersistedKeys,
|
|
18
18
|
} from "../auto-recovery.ts";
|
|
19
19
|
import { parseRoadmap, clearParseCache } from "../files.ts";
|
|
20
|
+
import { invalidateAllCaches } from "../cache.ts";
|
|
21
|
+
import { deriveState, invalidateStateCache } from "../state.ts";
|
|
20
22
|
|
|
21
23
|
function makeTmpBase(): string {
|
|
22
24
|
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
@@ -584,3 +586,55 @@ test("selfHealRuntimeRecords clears stale record when artifact exists at worktre
|
|
|
584
586
|
cleanup(mainBase);
|
|
585
587
|
}
|
|
586
588
|
});
|
|
589
|
+
|
|
590
|
+
// ─── #793: invalidateAllCaches unblocks skip-loop ─────────────────────────
|
|
591
|
+
// When the skip-loop breaker fires, it must call invalidateAllCaches() (not
|
|
592
|
+
// just invalidateStateCache()) to clear path/parse caches that deriveState
|
|
593
|
+
// depends on. Without this, even after cache invalidation, deriveState reads
|
|
594
|
+
// stale directory listings and returns the same unit, looping forever.
|
|
595
|
+
test("#793: invalidateAllCaches clears all caches so deriveState sees fresh disk state", async () => {
|
|
596
|
+
const base = makeTmpBase();
|
|
597
|
+
try {
|
|
598
|
+
const mid = "M001";
|
|
599
|
+
const sid = "S01";
|
|
600
|
+
const planDir = join(base, ".gsd", "milestones", mid, "slices", sid);
|
|
601
|
+
const tasksDir = join(planDir, "tasks");
|
|
602
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
603
|
+
mkdirSync(join(base, ".gsd", "milestones", mid), { recursive: true });
|
|
604
|
+
|
|
605
|
+
writeFileSync(
|
|
606
|
+
join(base, ".gsd", "milestones", mid, `${mid}-ROADMAP.md`),
|
|
607
|
+
`# M001: Test Milestone\n\n**Vision:** test.\n\n## Slices\n\n- [ ] **${sid}: Slice One** \`risk:low\` \`depends:[]\`\n > After this: done.\n`,
|
|
608
|
+
);
|
|
609
|
+
const planUnchecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [ ] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
610
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planUnchecked);
|
|
611
|
+
writeFileSync(join(tasksDir, "T01-PLAN.md"), "# T01: Task One\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
612
|
+
writeFileSync(join(tasksDir, "T02-PLAN.md"), "# T02: Task Two\n\n**Goal:** t\n\n## Steps\n- step\n\n## Verification\n- v\n");
|
|
613
|
+
|
|
614
|
+
// Warm all caches
|
|
615
|
+
const state1 = await deriveState(base);
|
|
616
|
+
assert.equal(state1.activeTask?.id, "T01", "initial: T01 is active");
|
|
617
|
+
|
|
618
|
+
// Simulate task completion on disk (what the LLM does)
|
|
619
|
+
const planChecked = `# ${sid}: Slice One\n\n**Goal:** test.\n\n## Tasks\n\n- [x] **T01: Task One** \`est:10m\`\n- [ ] **T02: Task Two** \`est:10m\`\n`;
|
|
620
|
+
writeFileSync(join(planDir, `${sid}-PLAN.md`), planChecked);
|
|
621
|
+
writeFileSync(join(tasksDir, "T01-SUMMARY.md"), "---\nid: T01\n---\n# Summary\n");
|
|
622
|
+
|
|
623
|
+
// invalidateStateCache alone: _stateCache cleared but path/parse caches warm
|
|
624
|
+
invalidateStateCache();
|
|
625
|
+
|
|
626
|
+
// invalidateAllCaches: all caches cleared — deriveState must re-read disk
|
|
627
|
+
invalidateAllCaches();
|
|
628
|
+
const state2 = await deriveState(base);
|
|
629
|
+
|
|
630
|
+
// After full invalidation, T01 should be complete and T02 should be next
|
|
631
|
+
assert.notEqual(state2.activeTask?.id, "T01", "#793: T01 not re-dispatched after full invalidation");
|
|
632
|
+
|
|
633
|
+
// Verify the caches are truly cleared by calling clearParseCache and clearPathCache
|
|
634
|
+
// do not throw (they should be no-ops after invalidateAllCaches already cleared them)
|
|
635
|
+
clearParseCache(); // no-op, but should not throw
|
|
636
|
+
assert.ok(true, "clearParseCache after invalidateAllCaches is safe");
|
|
637
|
+
} finally {
|
|
638
|
+
cleanup(base);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
@@ -153,6 +153,64 @@ async function main(): Promise<void> {
|
|
|
153
153
|
// After teardown, originalBase should be null
|
|
154
154
|
assertEq(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
|
|
155
155
|
|
|
156
|
+
// ─── #778: reconcile plan checkboxes on re-attach ─────────────────
|
|
157
|
+
console.log("\n=== #778: reconcile plan checkboxes on re-attach ===");
|
|
158
|
+
{
|
|
159
|
+
// Simulate: T01 [x] was committed to milestone branch, T02 [x] was
|
|
160
|
+
// written to project root by syncStateToProjectRoot() but the
|
|
161
|
+
// auto-commit crashed before it fired. On restart the worktree is
|
|
162
|
+
// re-created from the milestone branch HEAD (T02 still [ ]).
|
|
163
|
+
// reconcilePlanCheckboxes should forward-apply T02 [x] from the root.
|
|
164
|
+
|
|
165
|
+
const planRelPath = join(".gsd", "milestones", "M004", "slices", "S01", "S01-PLAN.md");
|
|
166
|
+
const planDir = join(tempDir, ".gsd", "milestones", "M004", "slices", "S01");
|
|
167
|
+
const { mkdirSync: mkdir, writeFileSync: write, readFileSync: read } = await import("node:fs");
|
|
168
|
+
|
|
169
|
+
// Plan on integration branch (project root): T01 [x], T02 [x]
|
|
170
|
+
mkdir(planDir, { recursive: true });
|
|
171
|
+
write(
|
|
172
|
+
join(tempDir, planRelPath),
|
|
173
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Write integration-branch plan to git so milestone branch starts from it
|
|
177
|
+
run(`git add .`, tempDir);
|
|
178
|
+
run(`git commit -m "add plan with T01 and T02 checked" --allow-empty`, tempDir);
|
|
179
|
+
|
|
180
|
+
// Create milestone branch with only T01 [x] (simulating crash before T02 commit)
|
|
181
|
+
const milestoneBranch = "milestone/M004";
|
|
182
|
+
run(`git checkout -b ${milestoneBranch}`, tempDir);
|
|
183
|
+
mkdir(planDir, { recursive: true });
|
|
184
|
+
write(
|
|
185
|
+
join(tempDir, planRelPath),
|
|
186
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [ ] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
187
|
+
);
|
|
188
|
+
run(`git add .`, tempDir);
|
|
189
|
+
run(`git commit -m "milestone: only T01 checked"`, tempDir);
|
|
190
|
+
run(`git checkout main`, tempDir);
|
|
191
|
+
|
|
192
|
+
// Restore project root plan (T01+T02 [x]) — simulates syncStateToProjectRoot
|
|
193
|
+
write(
|
|
194
|
+
join(tempDir, planRelPath),
|
|
195
|
+
"# S01 Plan\n- [x] **T01:** task one\n- [x] **T02:** task two\n- [ ] **T03:** task three\n",
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Create worktree re-attached to existing milestone branch (T02 still [ ] in branch)
|
|
199
|
+
const wtPath = createAutoWorktree(tempDir, "M004");
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const wtPlanPath = join(wtPath, planRelPath);
|
|
203
|
+
assertTrue(existsSync(wtPlanPath), "plan file exists in worktree after re-attach");
|
|
204
|
+
|
|
205
|
+
const wtPlan = read(wtPlanPath, "utf-8");
|
|
206
|
+
assertTrue(wtPlan.includes("- [x] **T02:"), "T02 should be [x] after reconciliation (was [ ] on branch)");
|
|
207
|
+
assertTrue(wtPlan.includes("- [x] **T01:"), "T01 stays [x]");
|
|
208
|
+
assertTrue(wtPlan.includes("- [ ] **T03:"), "T03 stays [ ] (not in root either)");
|
|
209
|
+
} finally {
|
|
210
|
+
teardownAutoWorktree(tempDir, "M004");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
156
214
|
} finally {
|
|
157
215
|
// Always restore cwd and clean up
|
|
158
216
|
process.chdir(savedCwd);
|
|
@@ -700,6 +700,76 @@ slice: S01
|
|
|
700
700
|
}
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
+
// ─── Test: completed M001 (summary, no validation) skipped for active M003 (#864) ────
|
|
704
|
+
console.log('\n=== completed milestone with summary but no validation is not active (#864) ===');
|
|
705
|
+
{
|
|
706
|
+
const base = createFixtureBase();
|
|
707
|
+
try {
|
|
708
|
+
// M001: all slices done, has summary, no validation
|
|
709
|
+
writeRoadmap(base, 'M001', `# M001: First Milestone\n\n**Vision:** Done.\n\n## Slices\n\n- [x] **S01: Done slice** \`risk:low\` \`depends:[]\`\n > Completed.\n`);
|
|
710
|
+
writeMilestoneSummary(base, 'M001', '---\nid: M001\n---\n\n# M001: First Milestone\n\n**Completed.**');
|
|
711
|
+
// M003: incomplete, should be active
|
|
712
|
+
writeRoadmap(base, 'M003', `# M003: Active Milestone\n\n**Vision:** Do stuff.\n\n## Slices\n\n- [ ] **S01: Work slice** \`risk:low\` \`depends:[]\`\n > Needs work.\n`);
|
|
713
|
+
|
|
714
|
+
const state = await deriveState(base);
|
|
715
|
+
assertEq(state.activeMilestone?.id, 'M003', 'active milestone is M003, not completed M001');
|
|
716
|
+
const m001Entry = state.registry.find(e => e.id === 'M001');
|
|
717
|
+
assertEq(m001Entry?.status, 'complete', 'M001 is marked complete despite no validation');
|
|
718
|
+
} finally {
|
|
719
|
+
cleanup(base);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ─── Test: completed M001 with summary AND validation is complete (#864) ────
|
|
724
|
+
console.log('\n=== completed milestone with summary and validation is complete ===');
|
|
725
|
+
{
|
|
726
|
+
const base = createFixtureBase();
|
|
727
|
+
try {
|
|
728
|
+
writeRoadmap(base, 'M001', `# M001: First Milestone\n\n**Vision:** Done.\n\n## Slices\n\n- [x] **S01: Done slice** \`risk:low\` \`depends:[]\`\n > Completed.\n`);
|
|
729
|
+
writeMilestoneSummary(base, 'M001', '---\nid: M001\n---\n\n# M001: First Milestone\n\n**Completed.**');
|
|
730
|
+
writeMilestoneValidation(base, 'M001', 'pass');
|
|
731
|
+
writeRoadmap(base, 'M003', `# M003: Active Milestone\n\n**Vision:** Do stuff.\n\n## Slices\n\n- [ ] **S01: Work slice** \`risk:low\` \`depends:[]\`\n > Needs work.\n`);
|
|
732
|
+
|
|
733
|
+
const state = await deriveState(base);
|
|
734
|
+
assertEq(state.activeMilestone?.id, 'M003', 'active milestone is M003');
|
|
735
|
+
const m001Entry = state.registry.find(e => e.id === 'M001');
|
|
736
|
+
assertEq(m001Entry?.status, 'complete', 'M001 with both summary and validation is complete');
|
|
737
|
+
} finally {
|
|
738
|
+
cleanup(base);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ─── Test: all slices done, no summary, no validation → needs validation (#864) ────
|
|
743
|
+
console.log('\n=== all slices done, no summary, no validation → validating-milestone ===');
|
|
744
|
+
{
|
|
745
|
+
const base = createFixtureBase();
|
|
746
|
+
try {
|
|
747
|
+
writeRoadmap(base, 'M001', `# M001: First Milestone\n\n**Vision:** Validate me.\n\n## Slices\n\n- [x] **S01: Done slice** \`risk:low\` \`depends:[]\`\n > Completed.\n`);
|
|
748
|
+
// No summary, no validation — this should be active for validation
|
|
749
|
+
|
|
750
|
+
const state = await deriveState(base);
|
|
751
|
+
assertEq(state.activeMilestone?.id, 'M001', 'M001 is active for validation');
|
|
752
|
+
} finally {
|
|
753
|
+
cleanup(base);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ─── Test: all slices done, validation pass, no summary → needs completion (#864) ────
|
|
758
|
+
console.log('\n=== all slices done, validation pass, no summary → completing-milestone ===');
|
|
759
|
+
{
|
|
760
|
+
const base = createFixtureBase();
|
|
761
|
+
try {
|
|
762
|
+
writeRoadmap(base, 'M001', `# M001: First Milestone\n\n**Vision:** Complete me.\n\n## Slices\n\n- [x] **S01: Done slice** \`risk:low\` \`depends:[]\`\n > Completed.\n`);
|
|
763
|
+
writeMilestoneValidation(base, 'M001', 'pass');
|
|
764
|
+
// No summary — validated but not yet completed
|
|
765
|
+
|
|
766
|
+
const state = await deriveState(base);
|
|
767
|
+
assertEq(state.activeMilestone?.id, 'M001', 'M001 is active for completion');
|
|
768
|
+
} finally {
|
|
769
|
+
cleanup(base);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
703
773
|
report();
|
|
704
774
|
}
|
|
705
775
|
|
|
@@ -188,7 +188,7 @@ async function main(): Promise<void> {
|
|
|
188
188
|
cleanups.push(dir);
|
|
189
189
|
mkdirSync(join(dir, ".gsd"), { recursive: true });
|
|
190
190
|
|
|
191
|
-
const result = preDispatchHealthGate(dir);
|
|
191
|
+
const result = await preDispatchHealthGate(dir);
|
|
192
192
|
assertTrue(result.proceed, "gate passes on clean state");
|
|
193
193
|
assertEq(result.issues.length, 0, "no issues on clean state");
|
|
194
194
|
}
|
|
@@ -206,7 +206,7 @@ async function main(): Promise<void> {
|
|
|
206
206
|
unitStartedAt: "2026-03-10T00:01:00Z", completedUnits: 3,
|
|
207
207
|
}));
|
|
208
208
|
|
|
209
|
-
const result = preDispatchHealthGate(dir);
|
|
209
|
+
const result = await preDispatchHealthGate(dir);
|
|
210
210
|
assertTrue(result.proceed, "gate passes after auto-clearing stale lock");
|
|
211
211
|
assertTrue(result.fixesApplied.some(f => f.includes("cleared stale auto.lock")), "reports lock cleared");
|
|
212
212
|
assertTrue(!existsSync(join(dir, ".gsd", "auto.lock")), "lock file removed");
|
|
@@ -222,7 +222,7 @@ async function main(): Promise<void> {
|
|
|
222
222
|
const headHash = run("git rev-parse HEAD", dir);
|
|
223
223
|
writeFileSync(join(dir, ".git", "MERGE_HEAD"), headHash + "\n");
|
|
224
224
|
|
|
225
|
-
const result = preDispatchHealthGate(dir);
|
|
225
|
+
const result = await preDispatchHealthGate(dir);
|
|
226
226
|
assertTrue(result.proceed, "gate passes after auto-healing merge state");
|
|
227
227
|
assertTrue(result.fixesApplied.some(f => f.includes("cleaned merge state")), "reports merge state cleaned");
|
|
228
228
|
assertTrue(!existsSync(join(dir, ".git", "MERGE_HEAD")), "MERGE_HEAD removed");
|
|
@@ -231,6 +231,26 @@ async function main(): Promise<void> {
|
|
|
231
231
|
console.log(" (skipped on Windows)");
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
console.log("\n=== health gate: STATE.md missing — auto-healed ===");
|
|
235
|
+
{
|
|
236
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "doc-proactive-")));
|
|
237
|
+
cleanups.push(dir);
|
|
238
|
+
// Minimal .gsd structure: milestones dir exists but no STATE.md
|
|
239
|
+
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
|
240
|
+
|
|
241
|
+
const stateFile = join(dir, ".gsd", "STATE.md");
|
|
242
|
+
assertTrue(!existsSync(stateFile), "STATE.md does not exist before gate");
|
|
243
|
+
|
|
244
|
+
const result = await preDispatchHealthGate(dir);
|
|
245
|
+
assertTrue(result.proceed, "gate passes after rebuilding STATE.md");
|
|
246
|
+
assertTrue(
|
|
247
|
+
result.fixesApplied.some(f => f.includes("rebuilt missing STATE.md")),
|
|
248
|
+
"reports STATE.md rebuilt",
|
|
249
|
+
);
|
|
250
|
+
assertTrue(existsSync(stateFile), "STATE.md created by auto-heal");
|
|
251
|
+
assertTrue(result.issues.length === 0, "no blocking issues after heal");
|
|
252
|
+
}
|
|
253
|
+
|
|
234
254
|
} finally {
|
|
235
255
|
resetProactiveHealing();
|
|
236
256
|
for (const dir of cleanups) {
|
|
@@ -5,6 +5,7 @@ import { execSync } from "node:child_process";
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
inferCommitType,
|
|
8
|
+
buildTaskCommitMessage,
|
|
8
9
|
GitServiceImpl,
|
|
9
10
|
RUNTIME_EXCLUSION_PATHS,
|
|
10
11
|
VALID_BRANCH_NAME,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type GitPreferences,
|
|
15
16
|
type CommitOptions,
|
|
16
17
|
type PreMergeCheckResult,
|
|
18
|
+
type TaskCommitContext,
|
|
17
19
|
} from "../git-service.ts";
|
|
18
20
|
import { createTestContext } from './test-helpers.ts';
|
|
19
21
|
|
|
@@ -188,6 +190,58 @@ async function main(): Promise<void> {
|
|
|
188
190
|
"'prefix' does not match 'fix' — word boundary prevents partial match"
|
|
189
191
|
);
|
|
190
192
|
|
|
193
|
+
// ─── inferCommitType with oneLiner ──────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
console.log("\n=== inferCommitType with oneLiner ===");
|
|
196
|
+
|
|
197
|
+
assertEq(
|
|
198
|
+
inferCommitType("implement dashboard", "Fixed rendering bug in sidebar"),
|
|
199
|
+
"fix",
|
|
200
|
+
"one-liner with 'fixed' overrides generic title → fix"
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
assertEq(
|
|
204
|
+
inferCommitType("add search", "Optimized query performance with caching"),
|
|
205
|
+
"perf",
|
|
206
|
+
"one-liner with 'performance' and 'caching' → perf"
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// ─── buildTaskCommitMessage ─────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
console.log("\n=== buildTaskCommitMessage ===");
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
const msg = buildTaskCommitMessage({
|
|
215
|
+
taskId: "S01/T02",
|
|
216
|
+
taskTitle: "implement user authentication",
|
|
217
|
+
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
218
|
+
keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
|
|
219
|
+
});
|
|
220
|
+
assertTrue(msg.startsWith("feat(S01/T02):"), "message starts with type(scope)");
|
|
221
|
+
assertTrue(msg.includes("JWT-based auth"), "message includes one-liner content");
|
|
222
|
+
assertTrue(msg.includes("- src/auth.ts"), "message body includes key files");
|
|
223
|
+
assertTrue(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
{
|
|
227
|
+
const msg = buildTaskCommitMessage({
|
|
228
|
+
taskId: "S02/T01",
|
|
229
|
+
taskTitle: "fix login redirect bug",
|
|
230
|
+
});
|
|
231
|
+
assertTrue(msg.startsWith("fix(S02/T01):"), "infers fix type from title");
|
|
232
|
+
assertTrue(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
|
|
233
|
+
assertTrue(!msg.includes("\n"), "no body when no key files");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
{
|
|
237
|
+
const msg = buildTaskCommitMessage({
|
|
238
|
+
taskId: "S01/T03",
|
|
239
|
+
taskTitle: "add tests",
|
|
240
|
+
oneLiner: "Unit tests for auth module with coverage",
|
|
241
|
+
});
|
|
242
|
+
assertTrue(msg.startsWith("test(S01/T03):"), "infers test type");
|
|
243
|
+
}
|
|
244
|
+
|
|
191
245
|
// ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
|
|
192
246
|
|
|
193
247
|
console.log("\n=== RUNTIME_EXCLUSION_PATHS ===");
|
|
@@ -430,13 +484,25 @@ async function main(): Promise<void> {
|
|
|
430
484
|
const svc = new GitServiceImpl(repo);
|
|
431
485
|
|
|
432
486
|
createFile(repo, "src/new-feature.ts", "export const x = 1;");
|
|
433
|
-
const msg = svc.autoCommit("task", "T01");
|
|
434
487
|
|
|
435
|
-
|
|
488
|
+
// Without task context, autoCommit uses generic chore message
|
|
489
|
+
const msg = svc.autoCommit("task", "T01");
|
|
490
|
+
assertEq(msg, "chore(T01): auto-commit after task", "autoCommit returns generic format without task context");
|
|
436
491
|
|
|
437
|
-
// Verify the commit exists
|
|
438
492
|
const log = run("git log --oneline -1", repo);
|
|
439
|
-
assertTrue(log.includes("chore(T01): auto-commit after task"), "commit message is in git log");
|
|
493
|
+
assertTrue(log.includes("chore(T01): auto-commit after task"), "generic commit message is in git log");
|
|
494
|
+
|
|
495
|
+
// With task context, autoCommit uses meaningful message
|
|
496
|
+
createFile(repo, "src/auth.ts", "export function login() {}");
|
|
497
|
+
const msg2 = svc.autoCommit("task", "S01/T02", [], {
|
|
498
|
+
taskId: "S01/T02",
|
|
499
|
+
taskTitle: "implement user authentication endpoint",
|
|
500
|
+
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
501
|
+
keyFiles: ["src/auth.ts"],
|
|
502
|
+
});
|
|
503
|
+
assertTrue(msg2 !== null, "autoCommit with task context returns a message");
|
|
504
|
+
assertTrue(msg2!.startsWith("feat(S01/T02):"), "meaningful commit uses feat type and scope");
|
|
505
|
+
assertTrue(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
|
|
440
506
|
|
|
441
507
|
rmSync(repo, { recursive: true, force: true });
|
|
442
508
|
}
|
|
@@ -65,8 +65,8 @@ console.log('\n=== gsd-db: fresh DB schema init (memory) ===');
|
|
|
65
65
|
|
|
66
66
|
// Check schema_version table
|
|
67
67
|
const adapter = _getAdapter()!;
|
|
68
|
-
const version = adapter.prepare('SELECT version FROM schema_version').get();
|
|
69
|
-
assertEq(version?.['version'],
|
|
68
|
+
const version = adapter.prepare('SELECT MAX(version) as version FROM schema_version').get();
|
|
69
|
+
assertEq(version?.['version'], 3, 'schema version should be 3');
|
|
70
70
|
|
|
71
71
|
// Check tables exist by querying them
|
|
72
72
|
const dRows = adapter.prepare('SELECT count(*) as cnt FROM decisions').get();
|
|
@@ -350,12 +350,11 @@ console.log('=== md-importer: missing file handling ===');
|
|
|
350
350
|
console.log('=== md-importer: schema v1→v2 migration ===');
|
|
351
351
|
|
|
352
352
|
{
|
|
353
|
-
// This test verifies that opening a
|
|
354
|
-
// (The actual migration is tested via the gsd-db.test.ts schema version assertion = 2)
|
|
353
|
+
// This test verifies that opening a fresh DB auto-migrates to current schema version
|
|
355
354
|
openDatabase(':memory:');
|
|
356
355
|
const adapter = _getAdapter();
|
|
357
356
|
const version = adapter?.prepare('SELECT MAX(version) as v FROM schema_version').get();
|
|
358
|
-
assertEq(version?.v,
|
|
357
|
+
assertEq(version?.v, 3, 'new DB should be at schema version 3');
|
|
359
358
|
|
|
360
359
|
// Artifacts table should exist
|
|
361
360
|
const tableCheck = adapter?.prepare("SELECT count(*) as c FROM sqlite_master WHERE type='table' AND name='artifacts'").get();
|