gsd-pi 2.52.0 → 2.53.0-dev.07ffe51
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 +55 -32
- package/dist/headless-query.js +1 -1
- package/dist/resources/extensions/get-secrets-from-user.js +7 -0
- package/dist/resources/extensions/gsd/auto/phases.js +28 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +5 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +70 -14
- package/dist/resources/extensions/gsd/auto.js +22 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +4 -10
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/dist/resources/extensions/gsd/git-service.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +4 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/state.js +18 -29
- package/dist/resources/extensions/gsd/status-guards.js +12 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -3
- package/dist/resources/extensions/gsd/tools/complete-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/complete-task.js +4 -3
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +4 -14
- package/dist/resources/extensions/gsd/tools/plan-slice.js +4 -14
- package/dist/resources/extensions/gsd/tools/plan-task.js +4 -14
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +6 -7
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
- package/dist/resources/extensions/gsd/tools/replan-slice.js +5 -6
- package/dist/resources/extensions/gsd/validation.js +21 -0
- package/dist/resources/extensions/shared/rtk.js +14 -4
- package/dist/rtk.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +9 -0
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-024d82be84800e52.js → webpack-bca0e732db0dcec3.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +36 -8
- package/src/resources/extensions/get-secrets-from-user.ts +8 -0
- package/src/resources/extensions/gsd/auto/phases.ts +38 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +73 -14
- package/src/resources/extensions/gsd/auto.ts +21 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -11
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +3 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/src/resources/extensions/gsd/git-service.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +4 -3
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/state.ts +18 -29
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +64 -30
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +4 -3
- package/src/resources/extensions/gsd/tools/complete-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/complete-task.ts +4 -3
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +4 -16
- package/src/resources/extensions/gsd/tools/plan-slice.ts +4 -16
- package/src/resources/extensions/gsd/tools/plan-task.ts +4 -16
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -7
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
- package/src/resources/extensions/gsd/tools/replan-slice.ts +5 -7
- package/src/resources/extensions/gsd/validation.ts +23 -0
- package/src/resources/extensions/shared/rtk.ts +22 -4
- package/dist/web/standalone/.next/static/chunks/4024.21054f459af5cc78.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{vlgS2rkXjxeKhgXhdp4lh → Q5pfrfJIvgUKR3LJLVB0T}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vlgS2rkXjxeKhgXhdp4lh → Q5pfrfJIvgUKR3LJLVB0T}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -27,55 +27,78 @@ One command. Walk away. Come back to a built project with clean git history.
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
## What's New in v2.
|
|
30
|
+
## What's New in v2.52.0
|
|
31
31
|
|
|
32
|
-
###
|
|
32
|
+
### VS Code Extension & Web UI
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
- **VS Code integration** — status bar, file decorations, bash terminal, session tree, conversation history, and code lens. (#2651)
|
|
35
|
+
- **Dark mode contrast** — raised token floor and flattened opacity tier system for better readability. (#2734)
|
|
36
|
+
- **Auth token gate** — synthetic 401 on missing token, unauthenticated boot state, and recovery screen. (#2740)
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
- **v3 — state machine guards, actor identity, reversibility** — introduces formal state machine guards, tracks which actor (human vs agent) initiated each transition, and makes transitions reversible.
|
|
38
|
-
- **Hardened** — closes TOCTOU race conditions, intercepts bypass attempts, and resolves status inconsistencies.
|
|
38
|
+
### Capability Metadata & Model Routing
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
### v2.45.0 — New Commands and Capabilities
|
|
43
|
-
|
|
44
|
-
- **`/gsd rethink`** — conversational project reorganization. Rethink your milestone structure, slice decomposition, or overall approach through guided discussion. (#2459)
|
|
45
|
-
- **`/gsd mcp`** — MCP server status and connectivity. Check which MCP servers are configured, connected, and healthy. (#2362)
|
|
46
|
-
- **Complete offline mode** — GSD now works fully offline with local models. (#2429)
|
|
47
|
-
- **Global KNOWLEDGE.md injection** — `~/.gsd/agent/KNOWLEDGE.md` is injected into the system prompt, so cross-project knowledge persists globally. (#2331)
|
|
48
|
-
- **Mobile-responsive web UI** — the browser interface now works on phones and tablets. (#2354)
|
|
49
|
-
- **DB tool previews** — `renderCall`/`renderResult` previews on DB tools show what each tool call does before and after execution. (#2273)
|
|
50
|
-
- **Message timestamps** — user and assistant messages now include timestamps. (#2368)
|
|
40
|
+
- **Capability-based model selection** — replaced model-ID pattern matching with capability metadata, making custom provider integration more reliable. (#2548)
|
|
51
41
|
|
|
52
42
|
### Key Changes
|
|
53
43
|
|
|
54
|
-
-
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
44
|
+
- **`--bare` mode** — wired across headless, pi-coding-agent, and resource-loader for minimal-output operation.
|
|
45
|
+
- **RPC protocol v2** — new types, init handshake with version detection, and runId generation on prompt/steer/follow_up commands.
|
|
46
|
+
- **PREFERENCES.md rename** — `preferences.md` renamed to `PREFERENCES.md` for consistency. (#2700, #2738)
|
|
47
|
+
- **Comprehensive SQLite audit** — indexes, caching, safety, and reconciliation fixes across gsd-db.
|
|
48
|
+
- **Unified error classifier** — three overlapping error classifiers consolidated into a single classify-decide-act pipeline.
|
|
58
49
|
|
|
59
50
|
### Key Fixes
|
|
60
51
|
|
|
61
|
-
- **Auto-mode
|
|
62
|
-
- **
|
|
63
|
-
-
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
67
|
-
- **Windows
|
|
68
|
-
- **
|
|
52
|
+
- **Auto-mode stops on provider errors** — auto loop now halts after provider errors instead of retrying indefinitely. (#2762, #2764)
|
|
53
|
+
- **Transaction safety** — state machine guards moved inside transactions in 5 tool handlers (#2752), and `transaction()` made re-entrant.
|
|
54
|
+
- **Worktree seeding** — `preferences.md` seeded into auto-mode worktrees and included in worktree sync. (#2693)
|
|
55
|
+
- **Idle watchdog** — interactive tools exempted from stall detection (#2676), and filesystem activity no longer overrides stalled-tool detection. (#2697)
|
|
56
|
+
- **Milestone guards** — `allSlicesDone` guarded against vacuous truth on empty slice arrays (#2679), and `complete-milestone` dispatch blocked when validation is `needs-remediation`. (#2682)
|
|
57
|
+
- **Docker overhaul** — fragile setup replaced with proven container patterns. (#2716)
|
|
58
|
+
- **Windows** — EINVAL prevented by disabling detached process groups on Win32. (#2744)
|
|
59
|
+
- **Audit log** — `setLogBasePath` wired into engine init to resurrect audit logging. (#2745)
|
|
60
|
+
|
|
61
|
+
### v2.51.0 — Skills, RTK, and Verification
|
|
62
|
+
|
|
63
|
+
- **`/terminal` command** — direct shell execution from the slash command interface. (#2349)
|
|
64
|
+
- **Managed RTK integration** — RTK binary auto-provisioned with opt-in preference and web UI toggle. (#2620)
|
|
65
|
+
- **Verification classes** — compliance checked before milestone completion, with classes injected into validation prompts. (#2621, #2623)
|
|
66
|
+
- **Skills overhaul** — 30+ new skill packs covering major frameworks, databases, and cloud platforms; curated catalog with `~/.agents/skills/` as primary directory.
|
|
67
|
+
|
|
68
|
+
### v2.50.0 — Quality Gates
|
|
69
|
+
|
|
70
|
+
- **Quality gates** — 8-question quality gates added to planning and completion templates, with parallel evaluation via `evaluating-gates` phase.
|
|
71
|
+
- **Structured error propagation** — errors wired through `UnitResult` for better diagnostics.
|
|
72
|
+
|
|
73
|
+
### v2.49.0 — Git Trailers & Yolo Mode
|
|
74
|
+
|
|
75
|
+
- **`--yolo` flag** — `/gsd auto --yolo` for non-interactive project init.
|
|
76
|
+
- **Git trailers** — GSD metadata moved from commit subject scopes to git trailers.
|
|
77
|
+
|
|
78
|
+
### v2.48.0 — Forensics & Discussion
|
|
79
|
+
|
|
80
|
+
- **`/gsd discuss` for queued milestones** — target milestones still in the queue. (#2349)
|
|
81
|
+
- **Enhanced forensics** — journal and activity log awareness added to `/gsd forensics`.
|
|
82
|
+
|
|
83
|
+
### v2.47.0 — External Providers
|
|
84
|
+
|
|
85
|
+
- **External tool execution mode** — `externalToolExecution` mode for external providers in agent-core.
|
|
86
|
+
- **Claude Code CLI provider** — new provider extension for Claude Code CLI. (#2382)
|
|
69
87
|
|
|
70
|
-
### Previous highlights (v2.42–v2.
|
|
88
|
+
### Previous highlights (v2.42–v2.46)
|
|
71
89
|
|
|
90
|
+
- **Single-writer state engine** — disciplined state transitions with machine guards, actor identity, reversibility, and TOCTOU hardening. (#2494)
|
|
91
|
+
- **`/gsd rethink`** — conversational project reorganization. (#2459)
|
|
92
|
+
- **`/gsd mcp`** — MCP server status and connectivity. (#2362)
|
|
93
|
+
- **Complete offline mode** — fully offline with local models. (#2429)
|
|
94
|
+
- **Global KNOWLEDGE.md injection** — cross-project knowledge via `~/.gsd/agent/KNOWLEDGE.md`. (#2331)
|
|
95
|
+
- **Mobile-responsive web UI** — browser interface works on phones and tablets. (#2354)
|
|
96
|
+
- **Default isolation mode changed to `none`** — set `git.isolation: worktree` explicitly if needed. (#2481)
|
|
72
97
|
- **Non-API-key provider extensions** — support for Claude Code CLI and similar providers. (#2382)
|
|
73
98
|
- **Docker sandbox template** — official Docker template for isolated auto mode. (#2360)
|
|
74
99
|
- **DB-backed planning tools** — write-side state transitions use atomic SQLite tool calls. (#2141)
|
|
75
100
|
- **Declarative workflow engine** — YAML workflows through auto-loop. (#2024)
|
|
76
101
|
- **`/gsd fast`** — toggle service tier for prioritized API routing. (#1862)
|
|
77
|
-
- **Forensics dedup** — duplicate detection before issue creation. (#2105)
|
|
78
|
-
- **Startup optimizations** — pre-compiled extensions, compile cache, batch discovery. (#2125)
|
|
79
102
|
|
|
80
103
|
---
|
|
81
104
|
|
package/dist/headless-query.js
CHANGED
|
@@ -36,7 +36,7 @@ export async function handleQuery(basePath) {
|
|
|
36
36
|
const state = await deriveState(basePath);
|
|
37
37
|
// Derive next dispatch action
|
|
38
38
|
let next;
|
|
39
|
-
if (!state.activeMilestone) {
|
|
39
|
+
if (!state.activeMilestone?.id) {
|
|
40
40
|
next = {
|
|
41
41
|
action: 'stop',
|
|
42
42
|
reason: state.phase === 'complete' ? 'All milestones complete.' : state.nextAction,
|
|
@@ -25,6 +25,11 @@ function maskPreview(value) {
|
|
|
25
25
|
function shellEscapeSingle(value) {
|
|
26
26
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
27
27
|
}
|
|
28
|
+
function hydrateProcessEnv(key, value) {
|
|
29
|
+
// Make newly collected secrets immediately visible to the current session.
|
|
30
|
+
// Some extensions read process.env directly and do not reload .env on every call.
|
|
31
|
+
process.env[key] = value;
|
|
32
|
+
}
|
|
28
33
|
async function writeEnvKey(filePath, key, value) {
|
|
29
34
|
let content = "";
|
|
30
35
|
try {
|
|
@@ -246,6 +251,7 @@ async function applySecrets(provided, destination, opts) {
|
|
|
246
251
|
try {
|
|
247
252
|
await writeEnvKey(opts.envFilePath, key, value);
|
|
248
253
|
applied.push(key);
|
|
254
|
+
hydrateProcessEnv(key, value);
|
|
249
255
|
}
|
|
250
256
|
catch (err) {
|
|
251
257
|
errors.push(`${key}: ${err.message}`);
|
|
@@ -265,6 +271,7 @@ async function applySecrets(provided, destination, opts) {
|
|
|
265
271
|
}
|
|
266
272
|
else {
|
|
267
273
|
applied.push(key);
|
|
274
|
+
hydrateProcessEnv(key, value);
|
|
268
275
|
}
|
|
269
276
|
}
|
|
270
277
|
catch (err) {
|
|
@@ -15,12 +15,20 @@ import { PROJECT_FILES } from "../detection.js";
|
|
|
15
15
|
import { MergeConflictError } from "../git-service.js";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { existsSync, cpSync } from "node:fs";
|
|
18
|
-
import { logWarning } from "../workflow-logger.js";
|
|
18
|
+
import { logWarning, logError } from "../workflow-logger.js";
|
|
19
19
|
import { gsdRoot } from "../paths.js";
|
|
20
20
|
import { atomicWriteSync } from "../atomic-write.js";
|
|
21
21
|
import { verifyExpectedArtifact } from "../auto-recovery.js";
|
|
22
22
|
import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
23
23
|
// ─── generateMilestoneReport ──────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the base path for milestone reports.
|
|
26
|
+
* Prefers originalBasePath (project root) over basePath (which may be a worktree).
|
|
27
|
+
* Exported for testing as _resolveReportBasePath.
|
|
28
|
+
*/
|
|
29
|
+
export function _resolveReportBasePath(s) {
|
|
30
|
+
return s.originalBasePath || s.basePath;
|
|
31
|
+
}
|
|
24
32
|
/**
|
|
25
33
|
* Generate and write an HTML milestone report snapshot.
|
|
26
34
|
* Extracted from the milestone-transition block in autoLoop.
|
|
@@ -30,18 +38,19 @@ async function generateMilestoneReport(s, ctx, milestoneId) {
|
|
|
30
38
|
const { generateHtmlReport } = await importExtensionModule(import.meta.url, "../export-html.js");
|
|
31
39
|
const { writeReportSnapshot } = await importExtensionModule(import.meta.url, "../reports.js");
|
|
32
40
|
const { basename } = await import("node:path");
|
|
33
|
-
const
|
|
41
|
+
const reportBasePath = _resolveReportBasePath(s);
|
|
42
|
+
const snapData = await loadVisualizerData(reportBasePath);
|
|
34
43
|
const completedMs = snapData.milestones.find((m) => m.id === milestoneId);
|
|
35
44
|
const msTitle = completedMs?.title ?? milestoneId;
|
|
36
45
|
const gsdVersion = process.env.GSD_VERSION ?? "0.0.0";
|
|
37
|
-
const projName = basename(
|
|
46
|
+
const projName = basename(reportBasePath);
|
|
38
47
|
const doneSlices = snapData.milestones.reduce((acc, m) => acc + m.slices.filter((sl) => sl.done).length, 0);
|
|
39
48
|
const totalSlices = snapData.milestones.reduce((acc, m) => acc + m.slices.length, 0);
|
|
40
49
|
const outPath = writeReportSnapshot({
|
|
41
|
-
basePath:
|
|
50
|
+
basePath: reportBasePath,
|
|
42
51
|
html: generateHtmlReport(snapData, {
|
|
43
52
|
projectName: projName,
|
|
44
|
-
projectPath:
|
|
53
|
+
projectPath: reportBasePath,
|
|
45
54
|
gsdVersion,
|
|
46
55
|
milestoneId,
|
|
47
56
|
indexRelPath: "index.html",
|
|
@@ -50,7 +59,7 @@ async function generateMilestoneReport(s, ctx, milestoneId) {
|
|
|
50
59
|
milestoneTitle: msTitle,
|
|
51
60
|
kind: "milestone",
|
|
52
61
|
projectName: projName,
|
|
53
|
-
projectPath:
|
|
62
|
+
projectPath: reportBasePath,
|
|
54
63
|
gsdVersion,
|
|
55
64
|
totalCost: snapData.totals?.cost ?? 0,
|
|
56
65
|
totalTokens: snapData.totals?.tokens.total ?? 0,
|
|
@@ -160,8 +169,11 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
160
169
|
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
161
170
|
return { action: "break", reason: "merge-conflict" };
|
|
162
171
|
}
|
|
163
|
-
// Non-conflict merge errors —
|
|
164
|
-
|
|
172
|
+
// Non-conflict merge errors — stop auto to avoid advancing with unmerged work
|
|
173
|
+
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
174
|
+
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
175
|
+
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
176
|
+
return { action: "break", reason: "merge-failed" };
|
|
165
177
|
}
|
|
166
178
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
167
179
|
deps.invalidateAllCaches();
|
|
@@ -228,6 +240,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
228
240
|
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
229
241
|
return { action: "break", reason: "merge-conflict" };
|
|
230
242
|
}
|
|
243
|
+
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
244
|
+
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
245
|
+
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
246
|
+
return { action: "break", reason: "merge-failed" };
|
|
231
247
|
}
|
|
232
248
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
233
249
|
}
|
|
@@ -295,6 +311,10 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
295
311
|
await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
|
|
296
312
|
return { action: "break", reason: "merge-conflict" };
|
|
297
313
|
}
|
|
314
|
+
logError("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId, error: String(mergeErr) });
|
|
315
|
+
ctx.ui.notify(`Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /gsd auto to resume.`, "error");
|
|
316
|
+
await deps.stopAuto(ctx, pi, `Merge error on milestone ${s.currentMilestoneId}: ${String(mergeErr)}`);
|
|
317
|
+
return { action: "break", reason: "merge-failed" };
|
|
298
318
|
}
|
|
299
319
|
// PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
|
|
300
320
|
}
|
|
@@ -531,8 +531,12 @@ export const DISPATCH_RULES = [
|
|
|
531
531
|
if (validationPath) {
|
|
532
532
|
const validationContent = await loadFile(validationPath);
|
|
533
533
|
if (validationContent) {
|
|
534
|
-
|
|
534
|
+
// Accept either the structured template format (table with MET/N/A)
|
|
535
|
+
// or prose evidence patterns the validation agent may emit.
|
|
536
|
+
const structuredMatch = validationContent.includes("Operational") &&
|
|
535
537
|
(validationContent.includes("MET") || validationContent.includes("N/A"));
|
|
538
|
+
const proseMatch = /[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(validationContent);
|
|
539
|
+
const hasOperationalCheck = structuredMatch || proseMatch;
|
|
536
540
|
if (!hasOperationalCheck) {
|
|
537
541
|
return {
|
|
538
542
|
action: "stop",
|
|
@@ -104,6 +104,21 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
+
// ─── Build Artifact Auto-Resolve ─────────────────────────────────────────────
|
|
108
|
+
/** Patterns for machine-generated build artifacts that can be safely
|
|
109
|
+
* auto-resolved by accepting --theirs during merge. These files are
|
|
110
|
+
* regenerable and never contain meaningful manual edits. */
|
|
111
|
+
export const SAFE_AUTO_RESOLVE_PATTERNS = [
|
|
112
|
+
/\.tsbuildinfo$/,
|
|
113
|
+
/\.pyc$/,
|
|
114
|
+
/\/__pycache__\//,
|
|
115
|
+
/\.DS_Store$/,
|
|
116
|
+
/\.map$/,
|
|
117
|
+
];
|
|
118
|
+
/** Returns true if the file path is safe to auto-resolve during merge.
|
|
119
|
+
* Covers `.gsd/` state files and common build artifacts. */
|
|
120
|
+
export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
|
|
121
|
+
SAFE_AUTO_RESOLVE_PATTERNS.some((re) => re.test(filePath));
|
|
107
122
|
// ─── Dispatch-Level Sync (project root ↔ worktree) ──────────────────────────
|
|
108
123
|
/**
|
|
109
124
|
* Sync milestone artifacts from project root INTO worktree before deriveState.
|
|
@@ -1191,27 +1206,27 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1191
1206
|
? mergeResult.conflicts
|
|
1192
1207
|
: nativeConflictFiles(originalBasePath_);
|
|
1193
1208
|
if (conflictedFiles.length > 0) {
|
|
1194
|
-
// Separate
|
|
1195
|
-
// GSD state files
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
const
|
|
1199
|
-
const codeConflicts = conflictedFiles.filter((f) => !f
|
|
1200
|
-
// Auto-resolve
|
|
1201
|
-
if (
|
|
1202
|
-
for (const
|
|
1209
|
+
// Separate auto-resolvable conflicts (GSD state files + build artifacts)
|
|
1210
|
+
// from real code conflicts. GSD state files diverge between branches
|
|
1211
|
+
// during normal operation. Build artifacts are machine-generated and
|
|
1212
|
+
// regenerable. Both are safe to accept from the milestone branch.
|
|
1213
|
+
const autoResolvable = conflictedFiles.filter(isSafeToAutoResolve);
|
|
1214
|
+
const codeConflicts = conflictedFiles.filter((f) => !isSafeToAutoResolve(f));
|
|
1215
|
+
// Auto-resolve safe conflicts by accepting the milestone branch version
|
|
1216
|
+
if (autoResolvable.length > 0) {
|
|
1217
|
+
for (const safeFile of autoResolvable) {
|
|
1203
1218
|
try {
|
|
1204
|
-
nativeCheckoutTheirs(originalBasePath_, [
|
|
1205
|
-
nativeAddPaths(originalBasePath_, [
|
|
1219
|
+
nativeCheckoutTheirs(originalBasePath_, [safeFile]);
|
|
1220
|
+
nativeAddPaths(originalBasePath_, [safeFile]);
|
|
1206
1221
|
}
|
|
1207
1222
|
catch {
|
|
1208
1223
|
// If checkout --theirs fails, try removing the file from the merge
|
|
1209
1224
|
// (it's a runtime file that shouldn't be committed anyway)
|
|
1210
|
-
nativeRmForce(originalBasePath_, [
|
|
1225
|
+
nativeRmForce(originalBasePath_, [safeFile]);
|
|
1211
1226
|
}
|
|
1212
1227
|
}
|
|
1213
1228
|
}
|
|
1214
|
-
// If there are still
|
|
1229
|
+
// If there are still real code conflicts, escalate
|
|
1215
1230
|
if (codeConflicts.length > 0) {
|
|
1216
1231
|
// Pop stash before throwing so local work is not lost (#2151).
|
|
1217
1232
|
if (stashed) {
|
|
@@ -1256,7 +1271,48 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1256
1271
|
});
|
|
1257
1272
|
}
|
|
1258
1273
|
catch {
|
|
1259
|
-
// Stash pop
|
|
1274
|
+
// Stash pop after squash merge can conflict on .gsd/ state files that
|
|
1275
|
+
// diverged between branches. Left unresolved, these UU entries block
|
|
1276
|
+
// every subsequent merge. Auto-resolve them the same way we handle
|
|
1277
|
+
// .gsd/ conflicts during the merge itself: accept HEAD (the just-committed
|
|
1278
|
+
// version) and drop the now-applied stash.
|
|
1279
|
+
const uu = nativeConflictFiles(originalBasePath_);
|
|
1280
|
+
const gsdUU = uu.filter((f) => f.startsWith(".gsd/"));
|
|
1281
|
+
const nonGsdUU = uu.filter((f) => !f.startsWith(".gsd/"));
|
|
1282
|
+
if (gsdUU.length > 0) {
|
|
1283
|
+
for (const f of gsdUU) {
|
|
1284
|
+
try {
|
|
1285
|
+
// Accept the committed (HEAD) version of the state file
|
|
1286
|
+
execFileSync("git", ["checkout", "HEAD", "--", f], {
|
|
1287
|
+
cwd: originalBasePath_,
|
|
1288
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1289
|
+
encoding: "utf-8",
|
|
1290
|
+
});
|
|
1291
|
+
nativeAddPaths(originalBasePath_, [f]);
|
|
1292
|
+
}
|
|
1293
|
+
catch {
|
|
1294
|
+
// Last resort: remove the conflicted state file
|
|
1295
|
+
nativeRmForce(originalBasePath_, [f]);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (nonGsdUU.length === 0) {
|
|
1300
|
+
// All conflicts were .gsd/ files — safe to drop the stash
|
|
1301
|
+
try {
|
|
1302
|
+
execFileSync("git", ["stash", "drop"], {
|
|
1303
|
+
cwd: originalBasePath_,
|
|
1304
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1305
|
+
encoding: "utf-8",
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
catch { /* stash may already be consumed */ }
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
// Non-.gsd conflicts remain — leave stash for manual resolution
|
|
1312
|
+
logWarning("reconcile", "Stash pop conflict on non-.gsd files after merge", {
|
|
1313
|
+
files: nonGsdUU.join(", "),
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1260
1316
|
}
|
|
1261
1317
|
}
|
|
1262
1318
|
// 9b. Safety check (#1792): if nothing was committed, verify the milestone
|
|
@@ -215,6 +215,12 @@ export function stopAutoRemote(projectRoot) {
|
|
|
215
215
|
const lock = readCrashLock(projectRoot);
|
|
216
216
|
if (!lock)
|
|
217
217
|
return { found: false };
|
|
218
|
+
// Never SIGTERM ourselves — a stale lock with our own PID is not a remote
|
|
219
|
+
// session, it is leftover from a prior loop exit in this process. (#2730)
|
|
220
|
+
if (lock.pid === process.pid) {
|
|
221
|
+
clearLock(projectRoot);
|
|
222
|
+
return { found: false };
|
|
223
|
+
}
|
|
218
224
|
if (!isLockProcessAlive(lock)) {
|
|
219
225
|
// Stale lock — clean it up
|
|
220
226
|
clearLock(projectRoot);
|
|
@@ -239,6 +245,10 @@ export function checkRemoteAutoSession(projectRoot) {
|
|
|
239
245
|
const lock = readCrashLock(projectRoot);
|
|
240
246
|
if (!lock)
|
|
241
247
|
return { running: false };
|
|
248
|
+
// Our own PID is not a "remote" session — it is a stale lock left by this
|
|
249
|
+
// process (e.g. after step-mode exit without full cleanup). (#2730)
|
|
250
|
+
if (lock.pid === process.pid)
|
|
251
|
+
return { running: false };
|
|
242
252
|
if (!isLockProcessAlive(lock)) {
|
|
243
253
|
// Stale lock from a dead process — not a live remote session
|
|
244
254
|
return { running: false };
|
|
@@ -321,6 +331,18 @@ function cleanupAfterLoopExit(ctx) {
|
|
|
321
331
|
s.currentUnit = null;
|
|
322
332
|
s.active = false;
|
|
323
333
|
clearUnitTimeout();
|
|
334
|
+
// Clear crash lock and release session lock so the next `/gsd next` does
|
|
335
|
+
// not see a stale lock with the current PID and treat it as a "remote"
|
|
336
|
+
// session (which would cause it to SIGTERM itself). (#2730)
|
|
337
|
+
try {
|
|
338
|
+
if (lockBase())
|
|
339
|
+
clearLock(lockBase());
|
|
340
|
+
if (lockBase())
|
|
341
|
+
releaseSessionLock(lockBase());
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
/* best-effort — mirror stopAuto cleanup */
|
|
345
|
+
}
|
|
324
346
|
ctx.ui.setStatus("gsd-auto", undefined);
|
|
325
347
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
326
348
|
ctx.ui.setFooter(undefined);
|
|
@@ -77,15 +77,9 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
77
77
|
retryState.currentRetryModelId = undefined;
|
|
78
78
|
ctx.ui.notify(`Network retries exhausted for ${currentModelId}. Attempting model fallback.`, "warning");
|
|
79
79
|
}
|
|
80
|
-
// ---
|
|
81
|
-
// Rate
|
|
82
|
-
|
|
83
|
-
if (cls.kind === "rate-limit") {
|
|
84
|
-
await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, true);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
// --- Server/connection/stream errors: try model fallback first ---
|
|
88
|
-
if (cls.kind === "network" || cls.kind === "server" || cls.kind === "connection" || cls.kind === "stream") {
|
|
80
|
+
// --- Transient errors: try model fallback first, then pause ---
|
|
81
|
+
// Rate limits are often per-model, so switching models can bypass them.
|
|
82
|
+
if (cls.kind === "rate-limit" || cls.kind === "network" || cls.kind === "server" || cls.kind === "connection" || cls.kind === "stream") {
|
|
89
83
|
// Try model fallback
|
|
90
84
|
const dash = getAutoDashboardData();
|
|
91
85
|
if (dash.currentUnit) {
|
|
@@ -128,7 +122,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
128
122
|
}
|
|
129
123
|
// --- Transient fallback: pause with auto-resume ---
|
|
130
124
|
if (isTransient(cls)) {
|
|
131
|
-
await pauseTransientWithBackoff(cls, pi, ctx, errorDetail,
|
|
125
|
+
await pauseTransientWithBackoff(cls, pi, ctx, errorDetail, cls.kind === "rate-limit");
|
|
132
126
|
return;
|
|
133
127
|
}
|
|
134
128
|
// --- Permanent / unknown: pause indefinitely ---
|
|
@@ -337,7 +337,7 @@ async function configureGit(ctx, prefs) {
|
|
|
337
337
|
const gitBooleanFields = [
|
|
338
338
|
{ key: "auto_push", label: "Auto-push commits after committing", defaultVal: false },
|
|
339
339
|
{ key: "push_branches", label: "Push milestone branches to remote", defaultVal: false },
|
|
340
|
-
{ key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal:
|
|
340
|
+
{ key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal: true },
|
|
341
341
|
];
|
|
342
342
|
for (const field of gitBooleanFields) {
|
|
343
343
|
const current = git[field.key];
|
|
@@ -361,7 +361,7 @@ async function configureGit(ctx, prefs) {
|
|
|
361
361
|
}
|
|
362
362
|
// pre_merge_check
|
|
363
363
|
const currentPreMerge = git.pre_merge_check !== undefined ? String(git.pre_merge_check) : "";
|
|
364
|
-
const preMergeChoice = await ctx.ui.select(`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default:
|
|
364
|
+
const preMergeChoice = await ctx.ui.select(`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default: auto)"}:`, ["true", "false", "auto", "(keep current)"]);
|
|
365
365
|
if (preMergeChoice && preMergeChoice !== "(keep current)") {
|
|
366
366
|
if (preMergeChoice === "auto") {
|
|
367
367
|
git.pre_merge_check = "auto";
|
|
@@ -487,7 +487,7 @@ export async function configureMode(ctx, prefs) {
|
|
|
487
487
|
if (modeStr && modeStr !== "(keep current)") {
|
|
488
488
|
if (modeStr.startsWith("solo")) {
|
|
489
489
|
prefs.mode = "solo";
|
|
490
|
-
ctx.ui.notify("Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=
|
|
490
|
+
ctx.ui.notify("Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=auto, merge_strategy=squash, isolation=worktree, unique_milestone_ids=false", "info");
|
|
491
491
|
}
|
|
492
492
|
else if (modeStr.startsWith("team")) {
|
|
493
493
|
prefs.mode = "team";
|
|
@@ -126,8 +126,8 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
126
126
|
- `auto_push`: boolean — automatically push commits to the remote after committing. Default: `false`.
|
|
127
127
|
- `push_branches`: boolean — push the milestone branch to the remote after commits. Default: `false`.
|
|
128
128
|
- `remote`: string — git remote name to push to. Default: `"origin"`.
|
|
129
|
-
- `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `
|
|
130
|
-
- `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `
|
|
129
|
+
- `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `true`.
|
|
130
|
+
- `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `"auto"`.
|
|
131
131
|
- `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
|
|
132
132
|
- `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
|
|
133
133
|
- `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
|
|
@@ -446,11 +446,12 @@ export class GitServiceImpl {
|
|
|
446
446
|
}
|
|
447
447
|
/**
|
|
448
448
|
* Create a snapshot ref for the given label (typically a slice branch name).
|
|
449
|
-
*
|
|
449
|
+
* Enabled by default; opt out with prefs.snapshots === false.
|
|
450
|
+
* Ref path: refs/gsd/snapshots/<label>/<timestamp>
|
|
450
451
|
* The ref points at HEAD, capturing the current commit before destructive operations.
|
|
451
452
|
*/
|
|
452
453
|
createSnapshot(label) {
|
|
453
|
-
if (this.prefs.snapshots
|
|
454
|
+
if (this.prefs.snapshots === false)
|
|
454
455
|
return;
|
|
455
456
|
const now = new Date();
|
|
456
457
|
const ts = now.getFullYear().toString()
|
|
@@ -470,7 +471,7 @@ export class GitServiceImpl {
|
|
|
470
471
|
* Stub: to be implemented in T03.
|
|
471
472
|
*/
|
|
472
473
|
runPreMergeCheck() {
|
|
473
|
-
if (this.prefs.pre_merge_check === false
|
|
474
|
+
if (this.prefs.pre_merge_check === false) {
|
|
474
475
|
return { passed: true, skipped: true };
|
|
475
476
|
}
|
|
476
477
|
// Determine command: explicit string or auto-detect from package.json
|
|
@@ -408,8 +408,9 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
408
408
|
// Invalidate caches to pick up artifacts written by a just-completed discuss/plan
|
|
409
409
|
invalidateAllCaches();
|
|
410
410
|
const state = await deriveState(basePath);
|
|
411
|
-
// No active milestone
|
|
412
|
-
|
|
411
|
+
// No active milestone (or corrupted milestone with undefined id) —
|
|
412
|
+
// check for pending milestones to discuss instead
|
|
413
|
+
if (!state.activeMilestone?.id) {
|
|
413
414
|
const pendingMilestones = state.registry.filter(m => m.status === "pending");
|
|
414
415
|
if (pendingMilestones.length === 0) {
|
|
415
416
|
ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
|
|
@@ -864,7 +865,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
864
865
|
}
|
|
865
866
|
}
|
|
866
867
|
const state = await deriveState(basePath);
|
|
867
|
-
if (!state.activeMilestone) {
|
|
868
|
+
if (!state.activeMilestone?.id) {
|
|
868
869
|
// Guard: if a discuss session is already in flight, don't re-inject the prompt.
|
|
869
870
|
// Both /gsd and /gsd auto reach this branch when no milestone exists yet.
|
|
870
871
|
// Without this guard, every subsequent /gsd call overwrites pendingAutoStart
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Critical invariant: rendered markdown must round-trip through
|
|
9
9
|
// parseRoadmap(), parsePlan(), parseSummary() in files.ts.
|
|
10
10
|
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
11
12
|
import { join, relative } from "node:path";
|
|
12
13
|
import { createRequire } from "node:module";
|
|
13
14
|
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, getArtifact, insertArtifact, getGateResults, } from "./gsd-db.js";
|
|
@@ -262,7 +263,7 @@ function renderSlicePlanMarkdown(slice, tasks, gates = []) {
|
|
|
262
263
|
lines.push("## Tasks");
|
|
263
264
|
lines.push("");
|
|
264
265
|
for (const task of tasks) {
|
|
265
|
-
const done = task.status
|
|
266
|
+
const done = isClosedStatus(task.status) ? "x" : " ";
|
|
266
267
|
const estimate = task.estimate.trim() ? ` \`est:${task.estimate.trim()}\`` : "";
|
|
267
268
|
lines.push(`- [${done}] **${task.id}: ${task.title || task.id}**${estimate}`);
|
|
268
269
|
if (task.description.trim()) {
|
|
@@ -435,7 +436,7 @@ export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
|
|
|
435
436
|
// Apply checkbox patches for each task
|
|
436
437
|
let updated = content;
|
|
437
438
|
for (const task of tasks) {
|
|
438
|
-
const isDone = task.status
|
|
439
|
+
const isDone = isClosedStatus(task.status);
|
|
439
440
|
const tid = task.id;
|
|
440
441
|
if (isDone) {
|
|
441
442
|
// Set [x]
|
|
@@ -660,7 +661,7 @@ export function detectStaleRenders(basePath) {
|
|
|
660
661
|
const content = readFileSync(planPath, "utf-8");
|
|
661
662
|
const parsed = parsePlan(content);
|
|
662
663
|
for (const task of tasks) {
|
|
663
|
-
const isDoneInDb = task.status
|
|
664
|
+
const isDoneInDb = isClosedStatus(task.status);
|
|
664
665
|
const planTask = parsed.tasks.find((t) => t.id === task.id);
|
|
665
666
|
if (!planTask)
|
|
666
667
|
continue;
|
|
@@ -684,7 +685,7 @@ export function detectStaleRenders(basePath) {
|
|
|
684
685
|
}
|
|
685
686
|
// Check missing task summary files
|
|
686
687
|
for (const task of tasks) {
|
|
687
|
-
if ((task.status
|
|
688
|
+
if (isClosedStatus(task.status) && task.full_summary_md) {
|
|
688
689
|
const slicePath = resolveSlicePath(basePath, milestone.id, slice.id);
|
|
689
690
|
if (slicePath) {
|
|
690
691
|
const tasksDir = join(slicePath, "tasks");
|
|
@@ -131,8 +131,15 @@ function appendWorkerLog(basePath, milestoneId, chunk) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
function restoreRuntimeState(basePath) {
|
|
134
|
-
if (state?.active)
|
|
135
|
-
|
|
134
|
+
if (state?.active) {
|
|
135
|
+
// Verify at least one worker is alive — if all are in terminal states,
|
|
136
|
+
// the cached state is stale and we should fall through to cleanup.
|
|
137
|
+
const hasLiveWorker = [...state.workers.values()].some((w) => w.state !== "error" && w.state !== "stopped");
|
|
138
|
+
if (hasLiveWorker)
|
|
139
|
+
return true;
|
|
140
|
+
// All workers dead — clear stale state so restoreState() can clean up.
|
|
141
|
+
state = null;
|
|
142
|
+
}
|
|
136
143
|
const restored = restoreState(basePath);
|
|
137
144
|
if (restored && restored.workers.length > 0) {
|
|
138
145
|
const config = resolveParallelConfig(undefined);
|
|
@@ -778,6 +785,15 @@ export function refreshWorkerStatuses(basePath, options = {}) {
|
|
|
778
785
|
for (const worker of state.workers.values()) {
|
|
779
786
|
state.totalCost += worker.cost;
|
|
780
787
|
}
|
|
788
|
+
// If all workers are in a terminal state (error/stopped), the orchestration
|
|
789
|
+
// is finished — deactivate and clean up so zombie workers don't persist.
|
|
790
|
+
const allDead = [...state.workers.values()].every((w) => w.state === "error" || w.state === "stopped");
|
|
791
|
+
if (allDead) {
|
|
792
|
+
state.active = false;
|
|
793
|
+
removeStateFile(basePath);
|
|
794
|
+
state = null;
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
781
797
|
// Persist updated state for crash recovery
|
|
782
798
|
persistState(basePath);
|
|
783
799
|
}
|