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
package/README.md
CHANGED
|
@@ -32,11 +32,13 @@ Full documentation is available in the [`docs/`](./docs/) directory:
|
|
|
32
32
|
- **[Token Optimization](./docs/token-optimization.md)** — profiles, context compression, complexity routing (v2.17)
|
|
33
33
|
- **[Cost Management](./docs/cost-management.md)** — budgets, tracking, projections
|
|
34
34
|
- **[Git Strategy](./docs/git-strategy.md)** — worktree isolation, branching, merge behavior
|
|
35
|
+
- **[Parallel Orchestration](./docs/parallel-orchestration.md)** — run multiple milestones simultaneously
|
|
35
36
|
- **[Working in Teams](./docs/working-in-teams.md)** — unique IDs, shared artifacts
|
|
36
37
|
- **[Skills](./docs/skills.md)** — bundled skills, discovery, custom authoring
|
|
37
38
|
- **[Commands Reference](./docs/commands.md)** — all commands and keyboard shortcuts
|
|
38
39
|
- **[Architecture](./docs/architecture.md)** — system design and dispatch pipeline
|
|
39
|
-
- **[Troubleshooting](./docs/troubleshooting.md)** — common issues, doctor, recovery
|
|
40
|
+
- **[Troubleshooting](./docs/troubleshooting.md)** — common issues, doctor, forensics, recovery
|
|
41
|
+
- **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
|
|
40
42
|
- **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
|
|
41
43
|
|
|
42
44
|
---
|
|
@@ -111,9 +113,11 @@ Each slice flows through phases automatically:
|
|
|
111
113
|
|
|
112
114
|
```
|
|
113
115
|
Research → Plan → Execute (per task) → Complete → Reassess Roadmap → Next Slice
|
|
116
|
+
↓ (all slices done)
|
|
117
|
+
Validate Milestone → Complete Milestone
|
|
114
118
|
```
|
|
115
119
|
|
|
116
|
-
**Research** scouts the codebase and relevant docs. **Plan** decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded. **Complete** writes the summary, UAT script, marks the roadmap, and commits. **Reassess** checks if the roadmap still makes sense given what was learned.
|
|
120
|
+
**Research** scouts the codebase and relevant docs. **Plan** decomposes the slice into tasks with must-haves (mechanically verifiable outcomes). **Execute** runs each task in a fresh context window with only the relevant files pre-loaded. **Complete** writes the summary, UAT script, marks the roadmap, and commits. **Reassess** checks if the roadmap still makes sense given what was learned. **Validate Milestone** runs a reconciliation gate after all slices complete — comparing roadmap success criteria against actual results before sealing the milestone.
|
|
117
121
|
|
|
118
122
|
### `/gsd auto` — The Main Event
|
|
119
123
|
|
|
@@ -555,7 +559,13 @@ Anthropic, OpenAI, Google (Gemini), OpenRouter, GitHub Copilot, Amazon Bedrock,
|
|
|
555
559
|
|
|
556
560
|
If you have a **Claude Max**, **Codex**, or **GitHub Copilot** subscription, you can use those directly — Pi handles the OAuth flow. No API key needed.
|
|
557
561
|
|
|
558
|
-
>
|
|
562
|
+
> **⚠️ Important:** Using OAuth tokens from subscription plans outside their native applications may violate the provider's Terms of Service. In particular:
|
|
563
|
+
>
|
|
564
|
+
> - **Google Gemini** — Using Gemini CLI or Antigravity OAuth tokens in third-party tools has resulted in **Google account suspensions**. This affects your entire Google account, not just the Gemini service. **Use a Gemini API key instead.**
|
|
565
|
+
> - **Claude Max** — Anthropic's ToS may not explicitly permit OAuth use outside Claude's own applications.
|
|
566
|
+
> - **GitHub Copilot** — Usage outside GitHub's own tools may be restricted by your subscription terms.
|
|
567
|
+
>
|
|
568
|
+
> GSD supports API key authentication for all providers as the safe alternative. **We strongly recommend using API keys over OAuth for Google Gemini.**
|
|
559
569
|
|
|
560
570
|
### OpenRouter
|
|
561
571
|
|
package/dist/headless.js
CHANGED
|
@@ -137,18 +137,37 @@ function formatProgress(event, verbose) {
|
|
|
137
137
|
// ---------------------------------------------------------------------------
|
|
138
138
|
// Completion Detection
|
|
139
139
|
// ---------------------------------------------------------------------------
|
|
140
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Detect genuine auto-mode termination notifications.
|
|
142
|
+
*
|
|
143
|
+
* Only matches the actual stop signals emitted by stopAuto():
|
|
144
|
+
* "Auto-mode stopped..."
|
|
145
|
+
* "Step-mode stopped..."
|
|
146
|
+
*
|
|
147
|
+
* Does NOT match progress notifications that happen to contain words like
|
|
148
|
+
* "complete" or "stopped" (e.g., "Override resolved — rewrite-docs completed",
|
|
149
|
+
* "All slices are complete — nothing to discuss", "Skipped 5+ completed units").
|
|
150
|
+
*
|
|
151
|
+
* Blocked detection is separate — checked via isBlockedNotification.
|
|
152
|
+
*/
|
|
153
|
+
const TERMINAL_PREFIXES = ['auto-mode stopped', 'step-mode stopped'];
|
|
141
154
|
const IDLE_TIMEOUT_MS = 15_000;
|
|
155
|
+
// new-milestone is a long-running creative task where the LLM may pause
|
|
156
|
+
// between tool calls (e.g. after mkdir, before writing files). Use a
|
|
157
|
+
// longer idle timeout to avoid killing the session prematurely (#808).
|
|
158
|
+
const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120_000;
|
|
142
159
|
function isTerminalNotification(event) {
|
|
143
160
|
if (event.type !== 'extension_ui_request' || event.method !== 'notify')
|
|
144
161
|
return false;
|
|
145
162
|
const message = String(event.message ?? '').toLowerCase();
|
|
146
|
-
return
|
|
163
|
+
return TERMINAL_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
147
164
|
}
|
|
148
165
|
function isBlockedNotification(event) {
|
|
149
166
|
if (event.type !== 'extension_ui_request' || event.method !== 'notify')
|
|
150
167
|
return false;
|
|
151
|
-
|
|
168
|
+
const message = String(event.message ?? '').toLowerCase();
|
|
169
|
+
// Blocked notifications come through stopAuto as "Auto-mode stopped (Blocked: ...)"
|
|
170
|
+
return message.includes('blocked:');
|
|
152
171
|
}
|
|
153
172
|
function isMilestoneReadyNotification(event) {
|
|
154
173
|
if (event.type !== 'extension_ui_request' || event.method !== 'notify')
|
|
@@ -285,6 +304,7 @@ export async function runHeadless(options) {
|
|
|
285
304
|
});
|
|
286
305
|
// Idle timeout — fallback completion detection
|
|
287
306
|
let idleTimer = null;
|
|
307
|
+
const effectiveIdleTimeout = isNewMilestone ? NEW_MILESTONE_IDLE_TIMEOUT_MS : IDLE_TIMEOUT_MS;
|
|
288
308
|
function resetIdleTimer() {
|
|
289
309
|
if (idleTimer)
|
|
290
310
|
clearTimeout(idleTimer);
|
|
@@ -292,7 +312,7 @@ export async function runHeadless(options) {
|
|
|
292
312
|
idleTimer = setTimeout(() => {
|
|
293
313
|
completed = true;
|
|
294
314
|
resolveCompletion();
|
|
295
|
-
},
|
|
315
|
+
}, effectiveIdleTimeout);
|
|
296
316
|
}
|
|
297
317
|
}
|
|
298
318
|
// Overall timeout
|
|
@@ -19,14 +19,3 @@
|
|
|
19
19
|
* @returns The path to use for models.json
|
|
20
20
|
*/
|
|
21
21
|
export declare function resolveModelsJsonPath(): string;
|
|
22
|
-
/**
|
|
23
|
-
* Check if both GSD and PI models.json files exist.
|
|
24
|
-
*/
|
|
25
|
-
export declare function hasBothModelsFiles(): boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Get the paths to both models.json files.
|
|
28
|
-
*/
|
|
29
|
-
export declare function getModelsPaths(): {
|
|
30
|
-
gsd: string;
|
|
31
|
-
pi: string;
|
|
32
|
-
};
|
package/dist/models-resolver.js
CHANGED
|
@@ -33,18 +33,3 @@ export function resolveModelsJsonPath() {
|
|
|
33
33
|
}
|
|
34
34
|
return GSD_MODELS_PATH;
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Check if both GSD and PI models.json files exist.
|
|
38
|
-
*/
|
|
39
|
-
export function hasBothModelsFiles() {
|
|
40
|
-
return existsSync(GSD_MODELS_PATH) && existsSync(PI_MODELS_PATH);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Get the paths to both models.json files.
|
|
44
|
-
*/
|
|
45
|
-
export function getModelsPaths() {
|
|
46
|
-
return {
|
|
47
|
-
gsd: GSD_MODELS_PATH,
|
|
48
|
-
pi: PI_MODELS_PATH,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
|
|
2
2
|
export declare function discoverExtensionEntryPaths(extensionsDir: string): string[];
|
|
3
3
|
export declare function readManagedResourceVersion(agentDir: string): string | null;
|
|
4
|
-
export declare function readManagedResourceSyncedAt(agentDir: string): number | null;
|
|
5
4
|
export declare function getNewerManagedResourceVersion(agentDir: string, currentVersion: string): string | null;
|
|
6
5
|
/**
|
|
7
6
|
* Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
|
package/dist/resource-loader.js
CHANGED
|
@@ -97,15 +97,6 @@ export function readManagedResourceVersion(agentDir) {
|
|
|
97
97
|
return null;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
export function readManagedResourceSyncedAt(agentDir) {
|
|
101
|
-
try {
|
|
102
|
-
const manifest = JSON.parse(readFileSync(getManagedResourceManifestPath(agentDir), 'utf-8'));
|
|
103
|
-
return typeof manifest?.syncedAt === 'number' ? manifest.syncedAt : null;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
100
|
export function getNewerManagedResourceVersion(agentDir, currentVersion) {
|
|
110
101
|
const managedVersion = readManagedResourceVersion(agentDir);
|
|
111
102
|
if (!managedVersion) {
|
|
@@ -565,25 +565,28 @@ One commit per slice. Individually revertable. Reads like a changelog.
|
|
|
565
565
|
|
|
566
566
|
```
|
|
567
567
|
gsd/M001/S01:
|
|
568
|
-
test(S01): round-trip tests passing
|
|
568
|
+
test(S01/T03): round-trip tests passing
|
|
569
569
|
feat(S01/T03): file writer with round-trip fidelity
|
|
570
|
-
chore(S01/T03): auto-commit after task
|
|
571
570
|
feat(S01/T02): markdown parser for plan files
|
|
572
|
-
chore(S01/T02): auto-commit after task
|
|
573
571
|
feat(S01/T01): core types and interfaces
|
|
574
|
-
|
|
572
|
+
docs(S01): add slice plan
|
|
575
573
|
```
|
|
576
574
|
|
|
577
575
|
### Commit Conventions
|
|
578
576
|
|
|
579
577
|
| When | Format | Example |
|
|
580
578
|
|------|--------|---------|
|
|
581
|
-
|
|
|
582
|
-
|
|
|
583
|
-
|
|
|
584
|
-
|
|
|
579
|
+
| Task completed | `{type}(S01/T02): <one-liner from summary>` | Type inferred from title (`feat`, `fix`, `test`, etc.) |
|
|
580
|
+
| Plan/docs committed | `docs(S01): add slice plan` | Planning artifacts |
|
|
581
|
+
| Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title |
|
|
582
|
+
| State rebuild | `chore(S01/T02): auto-commit after state-rebuild` | Bookkeeping only |
|
|
585
583
|
|
|
586
|
-
|
|
584
|
+
The system reads the task summary after execution and builds a meaningful commit message:
|
|
585
|
+
- **Subject**: `{type}({sliceId}/{taskId}): {one-liner}` — the one-liner from the summary frontmatter
|
|
586
|
+
- **Type**: Inferred from the task title and one-liner (`feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`)
|
|
587
|
+
- **Body**: Key files from the summary frontmatter (up to 8 files listed)
|
|
588
|
+
|
|
589
|
+
Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`
|
|
587
590
|
|
|
588
591
|
### Squash Merge Message
|
|
589
592
|
|
|
@@ -54,6 +54,14 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
54
54
|
? output.slice(0, maxLen) + "\n\n[... truncated, use await_job for full output]"
|
|
55
55
|
: output;
|
|
56
56
|
|
|
57
|
+
// Deliver as follow-up without triggering a new LLM turn (#875).
|
|
58
|
+
// When the agent is streaming: the message is queued and picked up
|
|
59
|
+
// by the agent loop's getFollowUpMessages() after the current turn.
|
|
60
|
+
// When the agent is idle: the message is appended to context so it's
|
|
61
|
+
// visible on the next user-initiated prompt. Previously triggerTurn:true
|
|
62
|
+
// caused spurious autonomous turns — the model would interpret completed
|
|
63
|
+
// job output as requiring action and cascade into unbounded self-reinforcing
|
|
64
|
+
// loops (running more commands, spawning more jobs, burning context).
|
|
57
65
|
pi.sendMessage(
|
|
58
66
|
{
|
|
59
67
|
customType: "async_job_result",
|
|
@@ -64,7 +72,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
64
72
|
].join("\n"),
|
|
65
73
|
display: true,
|
|
66
74
|
},
|
|
67
|
-
{ deliverAs: "followUp"
|
|
75
|
+
{ deliverAs: "followUp" },
|
|
68
76
|
);
|
|
69
77
|
},
|
|
70
78
|
});
|
|
@@ -66,6 +66,7 @@ import { waitForReady } from "./readiness-detector.js";
|
|
|
66
66
|
import { queryShellEnv, sendAndWait, runOnSession } from "./interaction.js";
|
|
67
67
|
import { formatUptime, formatTokenCount, resolveBgShellPersistenceCwd } from "./utilities.js";
|
|
68
68
|
import { BgManagerOverlay } from "./overlay.js";
|
|
69
|
+
import { toPosixPath } from "../shared/path-display.js";
|
|
69
70
|
|
|
70
71
|
// ── Re-exports for consumers ───────────────────────────────────────────────
|
|
71
72
|
|
|
@@ -337,7 +338,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
337
338
|
text += ` type: ${bg.processType}\n`;
|
|
338
339
|
text += ` status: ${bg.status}\n`;
|
|
339
340
|
text += ` command: ${bg.command}\n`;
|
|
340
|
-
text += ` cwd: ${bg.cwd}`;
|
|
341
|
+
text += ` cwd: ${toPosixPath(bg.cwd)}`;
|
|
341
342
|
|
|
342
343
|
if (bg.group) text += `\n group: ${bg.group}`;
|
|
343
344
|
if (bg.readyPort) text += `\n ready_port: ${bg.readyPort}`;
|
|
@@ -694,7 +695,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
694
695
|
}
|
|
695
696
|
|
|
696
697
|
let text = `Shell environment for ${bg.id} (${bg.label}):\n`;
|
|
697
|
-
text += ` cwd: ${envResult.cwd}\n`;
|
|
698
|
+
text += ` cwd: ${toPosixPath(envResult.cwd)}\n`;
|
|
698
699
|
text += ` shell: ${envResult.shell}\n`;
|
|
699
700
|
|
|
700
701
|
const envEntries = Object.entries(envResult.env);
|
|
@@ -328,12 +328,9 @@ export class BgManagerOverlay {
|
|
|
328
328
|
return this.box(inner, width);
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
private
|
|
331
|
+
private processStatusHeader(p: typeof this.viewingProcess, activeTab: "output" | "events"): { statusIcon: string; headerLine: string } {
|
|
332
332
|
const th = this.theme;
|
|
333
|
-
|
|
334
|
-
if (!p) return [""];
|
|
335
|
-
const inner: string[] = [];
|
|
336
|
-
|
|
333
|
+
if (!p) return { statusIcon: "", headerLine: "" };
|
|
337
334
|
const statusIcon = p.alive
|
|
338
335
|
? (p.status === "ready" ? th.fg("success", "●")
|
|
339
336
|
: p.status === "error" ? th.fg("error", "●")
|
|
@@ -343,9 +340,21 @@ export class BgManagerOverlay {
|
|
|
343
340
|
const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
|
|
344
341
|
const typeTag = th.fg("dim", `[${p.processType}]`);
|
|
345
342
|
const portInfo = p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : "";
|
|
346
|
-
const tabIndicator =
|
|
343
|
+
const tabIndicator = activeTab === "output"
|
|
344
|
+
? th.fg("accent", "[Output]") + " " + th.fg("dim", "Events")
|
|
345
|
+
: th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
|
|
346
|
+
const headerLine = `${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`;
|
|
347
|
+
return { statusIcon, headerLine };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private renderOutput(width: number): string[] {
|
|
351
|
+
const th = this.theme;
|
|
352
|
+
const p = this.viewingProcess;
|
|
353
|
+
if (!p) return [""];
|
|
354
|
+
const inner: string[] = [];
|
|
347
355
|
|
|
348
|
-
|
|
356
|
+
const { headerLine } = this.processStatusHeader(p, "output");
|
|
357
|
+
inner.push(headerLine);
|
|
349
358
|
inner.push("");
|
|
350
359
|
|
|
351
360
|
// Unified buffer is already chronologically interleaved
|
|
@@ -384,16 +393,8 @@ export class BgManagerOverlay {
|
|
|
384
393
|
if (!p) return [""];
|
|
385
394
|
const inner: string[] = [];
|
|
386
395
|
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
: p.status === "error" ? th.fg("error", "●")
|
|
390
|
-
: th.fg("warning", "●"))
|
|
391
|
-
: th.fg("dim", "○");
|
|
392
|
-
const name = th.fg("muted", p.label);
|
|
393
|
-
const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
|
|
394
|
-
const tabIndicator = th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
|
|
395
|
-
|
|
396
|
-
inner.push(`${statusIcon} ${name} ${uptime} ${tabIndicator}`);
|
|
396
|
+
const { headerLine } = this.processStatusHeader(p, "events");
|
|
397
|
+
inner.push(headerLine);
|
|
397
398
|
inner.push("");
|
|
398
399
|
|
|
399
400
|
if (p.events.length === 0) {
|
|
@@ -369,32 +369,14 @@ async function applySecrets(
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
if (destination === "vercel" && opts.exec) {
|
|
372
|
+
if ((destination === "vercel" || destination === "convex") && opts.exec) {
|
|
373
373
|
const env = opts.environment ?? "development";
|
|
374
374
|
for (const { key, value } of provided) {
|
|
375
|
+
const cmd = destination === "vercel"
|
|
376
|
+
? `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`
|
|
377
|
+
: `npx convex env set ${key} ${shellEscapeSingle(value)}`;
|
|
375
378
|
try {
|
|
376
|
-
const result = await opts.exec("sh", [
|
|
377
|
-
"-c",
|
|
378
|
-
`printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`,
|
|
379
|
-
]);
|
|
380
|
-
if (result.code !== 0) {
|
|
381
|
-
errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
|
|
382
|
-
} else {
|
|
383
|
-
applied.push(key);
|
|
384
|
-
}
|
|
385
|
-
} catch (err: any) {
|
|
386
|
-
errors.push(`${key}: ${err.message}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (destination === "convex" && opts.exec) {
|
|
392
|
-
for (const { key, value } of provided) {
|
|
393
|
-
try {
|
|
394
|
-
const result = await opts.exec("sh", [
|
|
395
|
-
"-c",
|
|
396
|
-
`npx convex env set ${key} ${shellEscapeSingle(value)}`,
|
|
397
|
-
]);
|
|
379
|
+
const result = await opts.exec("sh", ["-c", cmd]);
|
|
398
380
|
if (result.code !== 0) {
|
|
399
381
|
errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
|
|
400
382
|
} else {
|
|
@@ -103,10 +103,10 @@ export function saveActivityLog(
|
|
|
103
103
|
basePath: string,
|
|
104
104
|
unitType: string,
|
|
105
105
|
unitId: string,
|
|
106
|
-
):
|
|
106
|
+
): string | null {
|
|
107
107
|
try {
|
|
108
108
|
const entries = ctx.sessionManager.getEntries();
|
|
109
|
-
if (!entries || entries.length === 0) return;
|
|
109
|
+
if (!entries || entries.length === 0) return null;
|
|
110
110
|
|
|
111
111
|
const activityDir = join(gsdRoot(basePath), "activity");
|
|
112
112
|
mkdirSync(activityDir, { recursive: true });
|
|
@@ -116,7 +116,7 @@ export function saveActivityLog(
|
|
|
116
116
|
const unitKey = `${unitType}\0${safeUnitId}`;
|
|
117
117
|
// Use lightweight fingerprint instead of serializing all entries (#611)
|
|
118
118
|
const key = snapshotKey(unitType, safeUnitId, entries);
|
|
119
|
-
if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return;
|
|
119
|
+
if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return null;
|
|
120
120
|
|
|
121
121
|
const filePath = nextActivityFilePath(activityDir, state, unitType, safeUnitId);
|
|
122
122
|
// Stream entries to disk line-by-line instead of building one massive string (#611).
|
|
@@ -131,9 +131,11 @@ export function saveActivityLog(
|
|
|
131
131
|
}
|
|
132
132
|
state.nextSeq += 1;
|
|
133
133
|
state.lastSnapshotKeyByUnit.set(unitKey, key);
|
|
134
|
+
return filePath;
|
|
134
135
|
} catch (e) {
|
|
135
136
|
// Don't let logging failures break auto-mode
|
|
136
137
|
void e;
|
|
138
|
+
return null;
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -637,6 +637,12 @@ export async function buildPlanSlicePrompt(
|
|
|
637
637
|
const executorContextConstraints = formatExecutorConstraints();
|
|
638
638
|
|
|
639
639
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
640
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
641
|
+
const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
|
|
642
|
+
const commitInstruction = commitDocsEnabled
|
|
643
|
+
? `Commit: \`docs(${sid}): add slice plan\``
|
|
644
|
+
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
645
|
+
|
|
640
646
|
return loadPrompt("plan-slice", {
|
|
641
647
|
workingDirectory: base,
|
|
642
648
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
@@ -647,6 +653,7 @@ export async function buildPlanSlicePrompt(
|
|
|
647
653
|
inlinedContext,
|
|
648
654
|
dependencySummaries: depContent,
|
|
649
655
|
executorContextConstraints,
|
|
656
|
+
commitInstruction,
|
|
650
657
|
});
|
|
651
658
|
}
|
|
652
659
|
|
|
@@ -1071,6 +1078,12 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1071
1078
|
// Non-fatal — captures module may not be available
|
|
1072
1079
|
}
|
|
1073
1080
|
|
|
1081
|
+
const reassessPrefs = loadEffectiveGSDPreferences();
|
|
1082
|
+
const reassessCommitDocsEnabled = reassessPrefs?.preferences?.git?.commit_docs !== false;
|
|
1083
|
+
const reassessCommitInstruction = reassessCommitDocsEnabled
|
|
1084
|
+
? `Commit: \`docs(${mid}): reassess roadmap after ${completedSliceId}\``
|
|
1085
|
+
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
1086
|
+
|
|
1074
1087
|
return loadPrompt("reassess-roadmap", {
|
|
1075
1088
|
workingDirectory: base,
|
|
1076
1089
|
milestoneId: mid,
|
|
@@ -1081,6 +1094,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1081
1094
|
assessmentPath,
|
|
1082
1095
|
inlinedContext,
|
|
1083
1096
|
deferredCaptures,
|
|
1097
|
+
commitInstruction: reassessCommitInstruction,
|
|
1084
1098
|
});
|
|
1085
1099
|
}
|
|
1086
1100
|
|
|
@@ -90,6 +90,10 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
90
90
|
const dir = resolveMilestonePath(base, mid);
|
|
91
91
|
return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
|
|
92
92
|
}
|
|
93
|
+
case "replan-slice": {
|
|
94
|
+
const dir = resolveSlicePath(base, mid, sid!);
|
|
95
|
+
return dir ? join(dir, buildSliceFileName(sid!, "REPLAN")) : null;
|
|
96
|
+
}
|
|
93
97
|
case "rewrite-docs":
|
|
94
98
|
return null;
|
|
95
99
|
default:
|
|
@@ -127,10 +131,9 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
|
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
if (!absPath) return unitType === "replan-slice";
|
|
134
|
+
// For unit types with no verifiable artifact (null path), the parent directory
|
|
135
|
+
// is missing on disk — treat as stale completion state so the key gets evicted (#313).
|
|
136
|
+
if (!absPath) return false;
|
|
134
137
|
if (!existsSync(absPath)) return false;
|
|
135
138
|
|
|
136
139
|
// plan-slice must produce a plan with actual task entries, not just a scaffold.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* manages create, enter, detect, and teardown for auto-mode worktrees.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
9
|
+
import { existsSync, cpSync, readFileSync, writeFileSync, readdirSync, mkdirSync, realpathSync, utimesSync, unlinkSync } from "node:fs";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
import { copyWorktreeDb, reconcileWorktreeDb, isDbAvailable } from "./gsd-db.js";
|
|
12
12
|
import { execSync, execFileSync } from "node:child_process";
|
|
@@ -134,6 +134,112 @@ export function autoWorktreeBranch(milestoneId: string): string {
|
|
|
134
134
|
* Atomic: chdir + originalBase update happen in the same try block
|
|
135
135
|
* to prevent split-brain.
|
|
136
136
|
*/
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Forward-merge plan checkbox state from the project root into a freshly
|
|
140
|
+
* re-attached worktree (#778).
|
|
141
|
+
*
|
|
142
|
+
* When auto-mode stops via crash (not graceful stop), the milestone branch
|
|
143
|
+
* HEAD may be behind the filesystem state at the project root because
|
|
144
|
+
* syncStateToProjectRoot() runs after every task completion but the final
|
|
145
|
+
* git commit may not have happened before the crash. On restart the worktree
|
|
146
|
+
* is re-attached to the branch HEAD, which has [ ] for the crashed task,
|
|
147
|
+
* causing verifyExpectedArtifact() to fail and triggering an infinite
|
|
148
|
+
* dispatch/skip loop.
|
|
149
|
+
*
|
|
150
|
+
* Fix: after re-attaching, read every *.md plan file in the milestone
|
|
151
|
+
* directory at the project root and apply any [x] checkbox states that are
|
|
152
|
+
* ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
|
|
153
|
+
*
|
|
154
|
+
* This is safe because syncStateToProjectRoot() is the authoritative source
|
|
155
|
+
* of post-task state at the project root — it writes the same [x] the LLM
|
|
156
|
+
* produced, then the auto-commit follows. If the commit never happened, the
|
|
157
|
+
* filesystem copy is still valid and correct.
|
|
158
|
+
*/
|
|
159
|
+
function reconcilePlanCheckboxes(projectRoot: string, wtPath: string, milestoneId: string): void {
|
|
160
|
+
const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
|
|
161
|
+
const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
|
|
162
|
+
if (!existsSync(srcMilestone) || !existsSync(dstMilestone)) return;
|
|
163
|
+
|
|
164
|
+
// Walk all markdown files in the milestone directory (plans, summaries, etc.)
|
|
165
|
+
function walkMd(dir: string): string[] {
|
|
166
|
+
const results: string[] = [];
|
|
167
|
+
try {
|
|
168
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
169
|
+
const full = join(dir, entry.name);
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
results.push(...walkMd(full));
|
|
172
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
173
|
+
results.push(full);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch { /* non-fatal */ }
|
|
177
|
+
return results;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const srcFile of walkMd(srcMilestone)) {
|
|
181
|
+
const rel = srcFile.slice(srcMilestone.length);
|
|
182
|
+
const dstFile = dstMilestone + rel;
|
|
183
|
+
if (!existsSync(dstFile)) continue; // only reconcile existing files
|
|
184
|
+
|
|
185
|
+
let srcContent: string;
|
|
186
|
+
let dstContent: string;
|
|
187
|
+
try {
|
|
188
|
+
srcContent = readFileSync(srcFile, "utf-8");
|
|
189
|
+
dstContent = readFileSync(dstFile, "utf-8");
|
|
190
|
+
} catch { continue; }
|
|
191
|
+
|
|
192
|
+
if (srcContent === dstContent) continue;
|
|
193
|
+
|
|
194
|
+
// Extract all checked task IDs from the source (project root)
|
|
195
|
+
// Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
|
|
196
|
+
const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
|
|
197
|
+
const srcChecked = new Set<string>();
|
|
198
|
+
for (const m of srcContent.matchAll(checkedRe)) srcChecked.add(m[1]);
|
|
199
|
+
|
|
200
|
+
if (srcChecked.size === 0) continue;
|
|
201
|
+
|
|
202
|
+
// Forward-apply: replace [ ] → [x] for any IDs that are checked in src
|
|
203
|
+
let updated = dstContent;
|
|
204
|
+
let changed = false;
|
|
205
|
+
for (const id of srcChecked) {
|
|
206
|
+
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
|
+
const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
|
|
208
|
+
if (uncheckedRe.test(updated)) {
|
|
209
|
+
updated = updated.replace(
|
|
210
|
+
new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"),
|
|
211
|
+
"$1[x]$2",
|
|
212
|
+
);
|
|
213
|
+
changed = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (changed) {
|
|
218
|
+
try {
|
|
219
|
+
writeFileSync(dstFile, updated, "utf-8");
|
|
220
|
+
} catch { /* non-fatal */ }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Also forward-merge completed-units.json (set-union)
|
|
225
|
+
const srcKeys = join(projectRoot, ".gsd", "completed-units.json");
|
|
226
|
+
const dstKeys = join(wtPath, ".gsd", "completed-units.json");
|
|
227
|
+
if (existsSync(srcKeys)) {
|
|
228
|
+
try {
|
|
229
|
+
const src: string[] = JSON.parse(readFileSync(srcKeys, "utf-8"));
|
|
230
|
+
let dst: string[] = [];
|
|
231
|
+
if (existsSync(dstKeys)) {
|
|
232
|
+
try { dst = JSON.parse(readFileSync(dstKeys, "utf-8")); } catch { /* ignore corrupt */ }
|
|
233
|
+
}
|
|
234
|
+
const merged = [...new Set([...dst, ...src])];
|
|
235
|
+
if (merged.length > dst.length) {
|
|
236
|
+
mkdirSync(join(wtPath, ".gsd"), { recursive: true });
|
|
237
|
+
writeFileSync(dstKeys, JSON.stringify(merged), "utf-8");
|
|
238
|
+
}
|
|
239
|
+
} catch { /* non-fatal */ }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
137
243
|
export function createAutoWorktree(basePath: string, milestoneId: string): string {
|
|
138
244
|
const branch = autoWorktreeBranch(milestoneId);
|
|
139
245
|
|
|
@@ -166,6 +272,18 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
166
272
|
// not always fully synced.
|
|
167
273
|
if (!branchExists) {
|
|
168
274
|
copyPlanningArtifacts(basePath, info.path);
|
|
275
|
+
} else {
|
|
276
|
+
// Re-attaching to an existing branch: forward-merge any plan checkpoint
|
|
277
|
+
// state from the project root into the worktree (#778).
|
|
278
|
+
//
|
|
279
|
+
// If auto-mode stopped via crash, the milestone branch HEAD may lag behind
|
|
280
|
+
// the project root filesystem because syncStateToProjectRoot() ran after
|
|
281
|
+
// task completion but the auto-commit never fired. On restart the worktree
|
|
282
|
+
// is re-created from the branch HEAD (which has [ ] for the crashed task),
|
|
283
|
+
// causing verifyExpectedArtifact() to return false → stale-key eviction →
|
|
284
|
+
// infinite dispatch/skip loop. Reconciling here ensures the worktree sees
|
|
285
|
+
// the same [x] state that syncStateToProjectRoot() wrote to the root.
|
|
286
|
+
reconcilePlanCheckboxes(basePath, info.path, milestoneId);
|
|
169
287
|
}
|
|
170
288
|
|
|
171
289
|
// Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
|
|
@@ -194,7 +312,8 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
194
312
|
|
|
195
313
|
/**
|
|
196
314
|
* Copy .gsd/ planning artifacts from source repo to a new worktree.
|
|
197
|
-
* Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md
|
|
315
|
+
* Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md,
|
|
316
|
+
* STATE.md, KNOWLEDGE.md, and OVERRIDES.md.
|
|
198
317
|
* Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir.
|
|
199
318
|
* Best-effort — failures are non-fatal since auto-mode can recreate artifacts.
|
|
200
319
|
*/
|
|
@@ -212,7 +331,7 @@ function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
|
|
|
212
331
|
}
|
|
213
332
|
|
|
214
333
|
// Copy top-level planning files
|
|
215
|
-
for (const file of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "QUEUE.md"]) {
|
|
334
|
+
for (const file of ["DECISIONS.md", "REQUIREMENTS.md", "PROJECT.md", "QUEUE.md", "STATE.md", "KNOWLEDGE.md", "OVERRIDES.md"]) {
|
|
216
335
|
const src = join(srcGsd, file);
|
|
217
336
|
if (existsSync(src)) {
|
|
218
337
|
try {
|
|
@@ -441,6 +560,16 @@ export function mergeMilestoneToMain(
|
|
|
441
560
|
// when main is already checked out in the project-root worktree, #757)
|
|
442
561
|
const currentBranchAtBase = nativeGetCurrentBranch(originalBasePath_);
|
|
443
562
|
if (currentBranchAtBase !== mainBranch) {
|
|
563
|
+
// Remove untracked .gsd/ state files that may conflict with the branch
|
|
564
|
+
// being checked out. These are regenerated by doctor/rebuildState and
|
|
565
|
+
// are not meaningful in the main working tree — the worktree had the
|
|
566
|
+
// real state. Without this, `git checkout main` fails with
|
|
567
|
+
// "Your local changes would be overwritten" (#827).
|
|
568
|
+
const gsdStateFiles = ["STATE.md", "completed-units.json", "auto.lock"];
|
|
569
|
+
for (const f of gsdStateFiles) {
|
|
570
|
+
const p = join(originalBasePath_, ".gsd", f);
|
|
571
|
+
try { unlinkSync(p); } catch { /* non-fatal — file may not exist */ }
|
|
572
|
+
}
|
|
444
573
|
nativeCheckoutBranch(originalBasePath_, mainBranch);
|
|
445
574
|
}
|
|
446
575
|
|