gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff
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 +46 -134
- package/dist/cli.js +48 -6
- package/dist/headless-query.js +11 -1
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +157 -22
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-timers.js +2 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +4 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
- package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +74 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +28 -22
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- package/dist/update-cmd.js +4 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +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.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +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.js.nft.json +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.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- 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/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -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/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +194 -33
- package/src/resources/extensions/gsd/auto/session.ts +14 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-timers.ts +2 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +4 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
- package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +67 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +26 -25
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
|
@@ -6,9 +6,10 @@ import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
6
6
|
|
|
7
7
|
import { logWarning } from "../workflow-logger.js";
|
|
8
8
|
import { debugTime } from "../debug-logger.js";
|
|
9
|
-
import { loadPrompt } from "../prompt-loader.js";
|
|
9
|
+
import { loadPrompt, getTemplatesDir } from "../prompt-loader.js";
|
|
10
10
|
import { readForensicsMarker } from "../forensics.js";
|
|
11
11
|
import { resolveAllSkillReferences, renderPreferencesForSystemPrompt, loadEffectiveGSDPreferences } from "../preferences.js";
|
|
12
|
+
import { resolveSkillReference } from "../preferences-skills.js";
|
|
12
13
|
import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
|
|
13
14
|
import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "../skill-discovery.js";
|
|
14
15
|
import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
|
|
@@ -20,6 +21,31 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../../cmux/index.
|
|
|
20
21
|
|
|
21
22
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Bundled skill triggers — resolved dynamically at runtime instead of
|
|
26
|
+
* hardcoding absolute paths in the system prompt template. Only skills
|
|
27
|
+
* that actually exist on disk are included in the table. (#3575)
|
|
28
|
+
*/
|
|
29
|
+
const BUNDLED_SKILL_TRIGGERS: Array<{ trigger: string; skill: string }> = [
|
|
30
|
+
{ trigger: "Frontend UI - web components, pages, landing pages, dashboards, React/HTML/CSS, styling", skill: "frontend-design" },
|
|
31
|
+
{ trigger: "macOS or iOS apps - SwiftUI, Xcode, App Store", skill: "swiftui" },
|
|
32
|
+
{ trigger: "Debugging - complex bugs, failing tests, root-cause investigation after standard approaches fail", skill: "debug-like-expert" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function buildBundledSkillsTable(): string {
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
const rows: string[] = [];
|
|
38
|
+
for (const { trigger, skill } of BUNDLED_SKILL_TRIGGERS) {
|
|
39
|
+
const resolution = resolveSkillReference(skill, cwd);
|
|
40
|
+
if (resolution.method === "unresolved") continue; // skill not installed — omit from prompt
|
|
41
|
+
rows.push(`| ${trigger} | \`${resolution.resolvedPath}\` |`);
|
|
42
|
+
}
|
|
43
|
+
if (rows.length === 0) {
|
|
44
|
+
return "*No bundled skills found. Install skills to `~/.agents/skills/` or `~/.claude/skills/`.*";
|
|
45
|
+
}
|
|
46
|
+
return `| Trigger | Skill to load |\n|---|---|\n${rows.join("\n")}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
function warnDeprecatedAgentInstructions(): void {
|
|
24
50
|
const paths = [
|
|
25
51
|
join(gsdHome, "agent-instructions.md"),
|
|
@@ -43,7 +69,10 @@ export async function buildBeforeAgentStartResult(
|
|
|
43
69
|
if (!existsSync(join(process.cwd(), ".gsd"))) return undefined;
|
|
44
70
|
|
|
45
71
|
const stopContextTimer = debugTime("context-inject");
|
|
46
|
-
const systemContent = loadPrompt("system"
|
|
72
|
+
const systemContent = loadPrompt("system", {
|
|
73
|
+
bundledSkillsTable: buildBundledSkillsTable(),
|
|
74
|
+
templatesDir: getTemplatesDir(),
|
|
75
|
+
});
|
|
47
76
|
const loadedPreferences = loadEffectiveGSDPreferences();
|
|
48
77
|
if (shouldPromptToEnableCmux(loadedPreferences?.preferences)) {
|
|
49
78
|
markCmuxPromptShown();
|
|
@@ -43,21 +43,27 @@ export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined,
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
/** Parse doctor command args into structured flags and positionals (pure, no I/O). */
|
|
47
|
+
export function parseDoctorArgs(args: string) {
|
|
47
48
|
const trimmed = args.trim();
|
|
48
|
-
// Extract flags before positional parsing
|
|
49
49
|
const jsonMode = trimmed.includes("--json");
|
|
50
50
|
const dryRun = trimmed.includes("--dry-run");
|
|
51
|
+
const fixFlag = trimmed.includes("--fix");
|
|
51
52
|
const includeBuild = trimmed.includes("--build");
|
|
52
53
|
const includeTests = trimmed.includes("--test");
|
|
53
|
-
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
54
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test|--fix/g, "").trim();
|
|
54
55
|
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
55
56
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
56
57
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
58
|
+
return { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
62
|
+
const { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope } = parseDoctorArgs(args);
|
|
57
63
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
58
64
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
59
65
|
const report = await runGSDDoctor(projectRoot(), {
|
|
60
|
-
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
66
|
+
fix: mode === "fix" || mode === "heal" || dryRun || fixFlag,
|
|
61
67
|
dryRun,
|
|
62
68
|
scope: effectiveScope,
|
|
63
69
|
includeBuild,
|
|
@@ -19,3 +19,47 @@ export const DIR_CACHE_MAX = 200;
|
|
|
19
19
|
|
|
20
20
|
/** Max parse-cache entries before eviction. */
|
|
21
21
|
export const CACHE_MAX = 50;
|
|
22
|
+
|
|
23
|
+
// ─── Tool Scoping ─────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* GSD tools allowed during discuss flows (#2949).
|
|
27
|
+
*
|
|
28
|
+
* xAI/Grok (and potentially other providers with grammar-based constrained
|
|
29
|
+
* decoding) return "Grammar is too complex" (HTTP 400) when the combined
|
|
30
|
+
* tool schemas exceed their internal grammar limit. The full GSD tool set
|
|
31
|
+
* registers ~33 tools with deeply nested schemas; discuss flows only need
|
|
32
|
+
* a small subset.
|
|
33
|
+
*
|
|
34
|
+
* By scoping tools to this allowlist during discuss dispatches, the grammar
|
|
35
|
+
* sent to the provider stays well under provider limits.
|
|
36
|
+
*
|
|
37
|
+
* Included tools and why:
|
|
38
|
+
* - gsd_summary_save: writes CONTEXT.md artifacts (all discuss prompts)
|
|
39
|
+
* - gsd_save_summary: alias for above
|
|
40
|
+
* - gsd_decision_save: records decisions (discuss.md output phase)
|
|
41
|
+
* - gsd_save_decision: alias for above
|
|
42
|
+
* - gsd_plan_milestone: writes roadmap (discuss.md single/multi milestone)
|
|
43
|
+
* - gsd_milestone_plan: alias for above
|
|
44
|
+
* - gsd_milestone_generate_id: generates milestone IDs (discuss.md multi-milestone)
|
|
45
|
+
* - gsd_generate_milestone_id: alias for above
|
|
46
|
+
* - gsd_requirement_update: updates requirements during discuss
|
|
47
|
+
* - gsd_update_requirement: alias for above
|
|
48
|
+
*/
|
|
49
|
+
export const DISCUSS_TOOLS_ALLOWLIST: readonly string[] = [
|
|
50
|
+
// Context / summary writing
|
|
51
|
+
"gsd_summary_save",
|
|
52
|
+
"gsd_save_summary",
|
|
53
|
+
// Decision recording
|
|
54
|
+
"gsd_decision_save",
|
|
55
|
+
"gsd_save_decision",
|
|
56
|
+
// Milestone planning (needed for discuss.md output phase)
|
|
57
|
+
"gsd_plan_milestone",
|
|
58
|
+
"gsd_milestone_plan",
|
|
59
|
+
// Milestone ID generation (multi-milestone flow)
|
|
60
|
+
"gsd_milestone_generate_id",
|
|
61
|
+
"gsd_generate_milestone_id",
|
|
62
|
+
// Requirement updates
|
|
63
|
+
"gsd_requirement_update",
|
|
64
|
+
"gsd_update_requirement",
|
|
65
|
+
];
|
|
@@ -469,6 +469,23 @@ export async function saveDecisionToDb(
|
|
|
469
469
|
adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
|
|
470
470
|
throw diskErr;
|
|
471
471
|
}
|
|
472
|
+
// #2661: When a decision defers a slice, update the slice status in the DB
|
|
473
|
+
// so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are
|
|
474
|
+
// in split-brain: the decision says "deferred" but the state still says
|
|
475
|
+
// "active", causing auto-mode to keep dispatching the deferred work.
|
|
476
|
+
try {
|
|
477
|
+
const sliceRef = extractDeferredSliceRef(fields);
|
|
478
|
+
if (sliceRef) {
|
|
479
|
+
db.updateSliceStatus(sliceRef.milestoneId, sliceRef.sliceId, 'deferred');
|
|
480
|
+
}
|
|
481
|
+
} catch (deferErr) {
|
|
482
|
+
// Non-fatal — log but don't fail the decision save
|
|
483
|
+
logError('manifest', 'failed to update deferred slice status', {
|
|
484
|
+
fn: 'saveDecisionToDb',
|
|
485
|
+
error: String((deferErr as Error).message),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
472
489
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
473
490
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
474
491
|
invalidateStateCache();
|
|
@@ -482,6 +499,39 @@ export async function saveDecisionToDb(
|
|
|
482
499
|
}
|
|
483
500
|
}
|
|
484
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Extract a milestone/slice reference from a deferral decision.
|
|
504
|
+
*
|
|
505
|
+
* Detects deferrals by checking:
|
|
506
|
+
* - scope contains "defer" (e.g., "deferral", "defer")
|
|
507
|
+
* - choice or decision contains "defer" + an M###/S## pattern
|
|
508
|
+
*
|
|
509
|
+
* Returns { milestoneId, sliceId } if found, null otherwise.
|
|
510
|
+
*/
|
|
511
|
+
export function extractDeferredSliceRef(
|
|
512
|
+
fields: Pick<SaveDecisionFields, 'scope' | 'decision' | 'choice'>,
|
|
513
|
+
): { milestoneId: string; sliceId: string } | null {
|
|
514
|
+
const isDeferral =
|
|
515
|
+
/\bdefer(?:ral|red|ring|s)?\b/i.test(fields.scope) ||
|
|
516
|
+
/\bdefer(?:ral|red|ring|s)?\b/i.test(fields.choice) ||
|
|
517
|
+
/\bdefer(?:ral|red|ring|s)?\b/i.test(fields.decision);
|
|
518
|
+
|
|
519
|
+
if (!isDeferral) return null;
|
|
520
|
+
|
|
521
|
+
// Look for M###/S## pattern in choice first, then decision
|
|
522
|
+
const slicePattern = /\b(M\d{3,4})\/(S\d{2,3})\b/;
|
|
523
|
+
const choiceMatch = fields.choice.match(slicePattern);
|
|
524
|
+
if (choiceMatch) {
|
|
525
|
+
return { milestoneId: choiceMatch[1], sliceId: choiceMatch[2] };
|
|
526
|
+
}
|
|
527
|
+
const decisionMatch = fields.decision.match(slicePattern);
|
|
528
|
+
if (decisionMatch) {
|
|
529
|
+
return { milestoneId: decisionMatch[1], sliceId: decisionMatch[2] };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
485
535
|
// ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
|
|
486
536
|
|
|
487
537
|
/**
|
|
@@ -496,11 +546,35 @@ export async function updateRequirementInDb(
|
|
|
496
546
|
try {
|
|
497
547
|
const db = await import('./gsd-db.js');
|
|
498
548
|
|
|
499
|
-
|
|
549
|
+
let existing = db.getRequirementById(id);
|
|
550
|
+
|
|
551
|
+
// If requirement doesn't exist in DB, seed the entire requirements table
|
|
552
|
+
// from REQUIREMENTS.md first (#3346). This handles the standard workflow
|
|
553
|
+
// where requirements are authored in markdown during discussion but never
|
|
554
|
+
// imported into the database — making gsd_requirement_update always fail
|
|
555
|
+
// with "not_found" at milestone completion.
|
|
556
|
+
if (!existing) {
|
|
557
|
+
const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
558
|
+
try {
|
|
559
|
+
const content = readFileSync(reqFilePath, 'utf-8');
|
|
560
|
+
const { parseRequirementsSections } = await import('./md-importer.js');
|
|
561
|
+
const parsed = parseRequirementsSections(content);
|
|
562
|
+
if (parsed.length > 0) {
|
|
563
|
+
logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
|
|
564
|
+
for (const req of parsed) {
|
|
565
|
+
// Only seed if not already in DB (avoid overwriting concurrent inserts)
|
|
566
|
+
if (!db.getRequirementById(req.id)) {
|
|
567
|
+
db.upsertRequirement(req);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// Re-check after seeding
|
|
571
|
+
existing = db.getRequirementById(id);
|
|
572
|
+
}
|
|
573
|
+
} catch {
|
|
574
|
+
// REQUIREMENTS.md missing or unparseable — fall through to skeleton
|
|
575
|
+
}
|
|
576
|
+
}
|
|
500
577
|
|
|
501
|
-
// If requirement doesn't exist in DB, create a skeleton and merge updates.
|
|
502
|
-
// This handles the case where requirements were written to REQUIREMENTS.md
|
|
503
|
-
// but never imported into the database (see #2919).
|
|
504
578
|
const base: Requirement = existing ?? {
|
|
505
579
|
id,
|
|
506
580
|
class: '',
|
|
@@ -38,7 +38,7 @@ import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./comm
|
|
|
38
38
|
|
|
39
39
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
40
40
|
|
|
41
|
-
interface ForensicAnomaly {
|
|
41
|
+
export interface ForensicAnomaly {
|
|
42
42
|
type: "stuck-loop" | "cost-spike" | "timeout" | "missing-artifact" | "crash" | "doctor-issue" | "error-trace" | "journal-stuck" | "journal-guard-block" | "journal-rapid-iterations" | "journal-worktree-failure";
|
|
43
43
|
severity: "info" | "warning" | "error";
|
|
44
44
|
unitType?: string;
|
|
@@ -640,13 +640,29 @@ function getDbCompletionCounts(): DbCompletionCounts | null {
|
|
|
640
640
|
|
|
641
641
|
// ─── Anomaly Detectors ───────────────────────────────────────────────────────
|
|
642
642
|
|
|
643
|
-
|
|
644
|
-
|
|
643
|
+
/**
|
|
644
|
+
* Detect units that were dispatched multiple times (stuck in a loop).
|
|
645
|
+
*
|
|
646
|
+
* Counts distinct dispatches by grouping on (type, id, startedAt) first to
|
|
647
|
+
* collapse idle-watchdog duplicate snapshots (#1943), then counts unique
|
|
648
|
+
* startedAt values per type/id to determine actual dispatch count.
|
|
649
|
+
*
|
|
650
|
+
* Exported for testability.
|
|
651
|
+
*/
|
|
652
|
+
export function detectStuckLoops(units: UnitMetrics[], anomalies: ForensicAnomaly[]): void {
|
|
653
|
+
// First, collect unique startedAt values per type/id key
|
|
654
|
+
const dispatchMap = new Map<string, Set<number>>();
|
|
645
655
|
for (const u of units) {
|
|
646
656
|
const key = `${u.type}/${u.id}`;
|
|
647
|
-
|
|
657
|
+
let starts = dispatchMap.get(key);
|
|
658
|
+
if (!starts) {
|
|
659
|
+
starts = new Set();
|
|
660
|
+
dispatchMap.set(key, starts);
|
|
661
|
+
}
|
|
662
|
+
starts.add(u.startedAt);
|
|
648
663
|
}
|
|
649
|
-
for (const [key,
|
|
664
|
+
for (const [key, starts] of dispatchMap) {
|
|
665
|
+
const count = starts.size;
|
|
650
666
|
if (count > 1) {
|
|
651
667
|
const [unitType, ...idParts] = key.split("/");
|
|
652
668
|
anomalies.push({
|
|
@@ -1136,11 +1136,12 @@ export function insertMilestone(m: {
|
|
|
1136
1136
|
});
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
|
-
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord
|
|
1139
|
+
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string }): void {
|
|
1140
1140
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1141
1141
|
currentDb.prepare(
|
|
1142
1142
|
`UPDATE milestones SET
|
|
1143
|
-
title = COALESCE(:title, title),
|
|
1143
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
1144
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
1144
1145
|
vision = COALESCE(:vision, vision),
|
|
1145
1146
|
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
1146
1147
|
key_risks = COALESCE(:key_risks, key_risks),
|
|
@@ -1155,7 +1156,8 @@ export function upsertMilestonePlanning(milestoneId: string, planning: Partial<M
|
|
|
1155
1156
|
WHERE id = :id`,
|
|
1156
1157
|
).run({
|
|
1157
1158
|
":id": milestoneId,
|
|
1158
|
-
":title": title ??
|
|
1159
|
+
":title": planning.title ?? "",
|
|
1160
|
+
":status": planning.status ?? "",
|
|
1159
1161
|
":vision": planning.vision ?? null,
|
|
1160
1162
|
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
1161
1163
|
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
@@ -1183,13 +1185,25 @@ export function insertSlice(s: {
|
|
|
1183
1185
|
}): void {
|
|
1184
1186
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1185
1187
|
currentDb.prepare(
|
|
1186
|
-
`INSERT
|
|
1188
|
+
`INSERT INTO slices (
|
|
1187
1189
|
milestone_id, id, title, status, risk, depends, demo, created_at,
|
|
1188
1190
|
goal, success_criteria, proof_level, integration_closure, observability_impact, sequence
|
|
1189
1191
|
) VALUES (
|
|
1190
1192
|
:milestone_id, :id, :title, :status, :risk, :depends, :demo, :created_at,
|
|
1191
1193
|
:goal, :success_criteria, :proof_level, :integration_closure, :observability_impact, :sequence
|
|
1192
|
-
)
|
|
1194
|
+
)
|
|
1195
|
+
ON CONFLICT (milestone_id, id) DO UPDATE SET
|
|
1196
|
+
title = CASE WHEN :raw_title IS NOT NULL THEN excluded.title ELSE slices.title END,
|
|
1197
|
+
status = CASE WHEN slices.status IN ('complete', 'done') THEN slices.status ELSE excluded.status END,
|
|
1198
|
+
risk = CASE WHEN :raw_risk IS NOT NULL THEN excluded.risk ELSE slices.risk END,
|
|
1199
|
+
depends = excluded.depends,
|
|
1200
|
+
demo = CASE WHEN :raw_demo IS NOT NULL THEN excluded.demo ELSE slices.demo END,
|
|
1201
|
+
goal = CASE WHEN :raw_goal IS NOT NULL THEN excluded.goal ELSE slices.goal END,
|
|
1202
|
+
success_criteria = CASE WHEN :raw_success_criteria IS NOT NULL THEN excluded.success_criteria ELSE slices.success_criteria END,
|
|
1203
|
+
proof_level = CASE WHEN :raw_proof_level IS NOT NULL THEN excluded.proof_level ELSE slices.proof_level END,
|
|
1204
|
+
integration_closure = CASE WHEN :raw_integration_closure IS NOT NULL THEN excluded.integration_closure ELSE slices.integration_closure END,
|
|
1205
|
+
observability_impact = CASE WHEN :raw_observability_impact IS NOT NULL THEN excluded.observability_impact ELSE slices.observability_impact END,
|
|
1206
|
+
sequence = CASE WHEN :raw_sequence IS NOT NULL THEN excluded.sequence ELSE slices.sequence END`,
|
|
1193
1207
|
).run({
|
|
1194
1208
|
":milestone_id": s.milestoneId,
|
|
1195
1209
|
":id": s.id,
|
|
@@ -1205,6 +1219,16 @@ export function insertSlice(s: {
|
|
|
1205
1219
|
":integration_closure": s.planning?.integrationClosure ?? "",
|
|
1206
1220
|
":observability_impact": s.planning?.observabilityImpact ?? "",
|
|
1207
1221
|
":sequence": s.sequence ?? 0,
|
|
1222
|
+
// Raw sentinel params: NULL when caller omitted the field, used in ON CONFLICT guards
|
|
1223
|
+
":raw_title": s.title ?? null,
|
|
1224
|
+
":raw_risk": s.risk ?? null,
|
|
1225
|
+
":raw_demo": s.demo ?? null,
|
|
1226
|
+
":raw_goal": s.planning?.goal ?? null,
|
|
1227
|
+
":raw_success_criteria": s.planning?.successCriteria ?? null,
|
|
1228
|
+
":raw_proof_level": s.planning?.proofLevel ?? null,
|
|
1229
|
+
":raw_integration_closure": s.planning?.integrationClosure ?? null,
|
|
1230
|
+
":raw_observability_impact": s.planning?.observabilityImpact ?? null,
|
|
1231
|
+
":raw_sequence": s.sequence ?? null,
|
|
1208
1232
|
});
|
|
1209
1233
|
}
|
|
1210
1234
|
|
|
@@ -1884,20 +1908,32 @@ export function reconcileWorktreeDb(
|
|
|
1884
1908
|
FROM wt.milestones
|
|
1885
1909
|
`).run());
|
|
1886
1910
|
|
|
1887
|
-
// Merge slices — preserve worktree progress
|
|
1911
|
+
// Merge slices — preserve worktree progress but never downgrade completed status (#2558).
|
|
1912
|
+
// Uses INSERT OR REPLACE with a subquery that picks the best status — if the main DB
|
|
1913
|
+
// already has a completed slice, keep that status even if the worktree copy is stale.
|
|
1888
1914
|
merged.slices = countChanges(adapter.prepare(`
|
|
1889
1915
|
INSERT OR REPLACE INTO slices (
|
|
1890
1916
|
milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
|
|
1891
1917
|
full_summary_md, full_uat_md, goal, success_criteria, proof_level,
|
|
1892
1918
|
integration_closure, observability_impact, sequence, replan_triggered_at
|
|
1893
1919
|
)
|
|
1894
|
-
SELECT milestone_id, id, title,
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1920
|
+
SELECT w.milestone_id, w.id, w.title,
|
|
1921
|
+
CASE
|
|
1922
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1923
|
+
THEN m.status ELSE w.status
|
|
1924
|
+
END,
|
|
1925
|
+
w.risk, w.depends, w.demo, w.created_at,
|
|
1926
|
+
CASE
|
|
1927
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1928
|
+
THEN m.completed_at ELSE w.completed_at
|
|
1929
|
+
END,
|
|
1930
|
+
w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
|
|
1931
|
+
w.integration_closure, w.observability_impact, w.sequence, w.replan_triggered_at
|
|
1932
|
+
FROM wt.slices w
|
|
1933
|
+
LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
|
|
1898
1934
|
`).run());
|
|
1899
1935
|
|
|
1900
|
-
// Merge tasks — preserve execution results, status
|
|
1936
|
+
// Merge tasks — preserve execution results, never downgrade completed status (#2558)
|
|
1901
1937
|
merged.tasks = countChanges(adapter.prepare(`
|
|
1902
1938
|
INSERT OR REPLACE INTO tasks (
|
|
1903
1939
|
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
@@ -1906,12 +1942,23 @@ export function reconcileWorktreeDb(
|
|
|
1906
1942
|
description, estimate, files, verify, inputs, expected_output,
|
|
1907
1943
|
observability_impact, full_plan_md, sequence
|
|
1908
1944
|
)
|
|
1909
|
-
SELECT milestone_id, slice_id, id, title,
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1945
|
+
SELECT w.milestone_id, w.slice_id, w.id, w.title,
|
|
1946
|
+
CASE
|
|
1947
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1948
|
+
THEN m.status ELSE w.status
|
|
1949
|
+
END,
|
|
1950
|
+
w.one_liner, w.narrative,
|
|
1951
|
+
w.verification_result, w.duration,
|
|
1952
|
+
CASE
|
|
1953
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1954
|
+
THEN m.completed_at ELSE w.completed_at
|
|
1955
|
+
END,
|
|
1956
|
+
w.blocker_discovered,
|
|
1957
|
+
w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
|
|
1958
|
+
w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
|
|
1959
|
+
w.observability_impact, w.full_plan_md, w.sequence
|
|
1960
|
+
FROM wt.tasks w
|
|
1961
|
+
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
1915
1962
|
`).run());
|
|
1916
1963
|
|
|
1917
1964
|
// Merge memories — keep worktree-learned insights
|
|
@@ -39,6 +39,7 @@ import { debugLog } from "./debug-logger.js";
|
|
|
39
39
|
import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds, clearReservedMilestoneIds } from "./milestone-ids.js";
|
|
40
40
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
41
41
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
42
|
+
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
42
43
|
|
|
43
44
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
44
45
|
export {
|
|
@@ -289,6 +290,27 @@ async function dispatchWorkflow(
|
|
|
289
290
|
}
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
// Scope tools for discuss flows (#2949).
|
|
294
|
+
// Providers with grammar-based constrained decoding (xAI/Grok) return
|
|
295
|
+
// "Grammar is too complex" when the combined tool schema is too large.
|
|
296
|
+
// Discuss flows only need a small subset of GSD tools — strip the heavy
|
|
297
|
+
// planning/execution/completion tools to keep the grammar within limits.
|
|
298
|
+
if (unitType?.startsWith("discuss-")) {
|
|
299
|
+
const currentTools = pi.getActiveTools();
|
|
300
|
+
// Keep all non-GSD tools (builtins, other extensions) and only the
|
|
301
|
+
// GSD tools on the discuss allowlist.
|
|
302
|
+
const scopedTools = currentTools.filter(
|
|
303
|
+
(t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t),
|
|
304
|
+
);
|
|
305
|
+
pi.setActiveTools(scopedTools);
|
|
306
|
+
debugLog("discuss-tool-scoping", {
|
|
307
|
+
unitType,
|
|
308
|
+
before: currentTools.length,
|
|
309
|
+
after: scopedTools.length,
|
|
310
|
+
removed: currentTools.length - scopedTools.length,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
292
314
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
293
315
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
294
316
|
|
|
@@ -567,7 +567,34 @@ export function loadLedgerFromDisk(base: string): MetricsLedger | null {
|
|
|
567
567
|
}
|
|
568
568
|
|
|
569
569
|
function loadLedger(base: string): MetricsLedger {
|
|
570
|
-
|
|
570
|
+
const raw = loadJsonFile(metricsPath(base), isMetricsLedger, defaultLedger);
|
|
571
|
+
const before = raw.units.length;
|
|
572
|
+
raw.units = deduplicateUnits(raw.units);
|
|
573
|
+
if (raw.units.length < before) {
|
|
574
|
+
// Persist the cleaned ledger so duplicates don't re-accumulate
|
|
575
|
+
saveLedger(base, raw);
|
|
576
|
+
}
|
|
577
|
+
return raw;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Collapse duplicate entries with the same (type, id, startedAt) triple.
|
|
582
|
+
* Keeps the entry with the highest finishedAt (the most complete snapshot).
|
|
583
|
+
*
|
|
584
|
+
* This is a defensive measure against idle-watchdog race conditions that can
|
|
585
|
+
* produce duplicate entries on disk despite the in-memory idempotency guard
|
|
586
|
+
* in snapshotUnitMetrics(). See #1943.
|
|
587
|
+
*/
|
|
588
|
+
function deduplicateUnits(units: UnitMetrics[]): UnitMetrics[] {
|
|
589
|
+
const map = new Map<string, UnitMetrics>();
|
|
590
|
+
for (const u of units) {
|
|
591
|
+
const key = `${u.type}\0${u.id}\0${u.startedAt}`;
|
|
592
|
+
const existing = map.get(key);
|
|
593
|
+
if (!existing || u.finishedAt > existing.finishedAt) {
|
|
594
|
+
map.set(key, u);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return Array.from(map.values());
|
|
571
598
|
}
|
|
572
599
|
|
|
573
600
|
function saveLedger(base: string, data: MetricsLedger): void {
|
|
@@ -724,10 +724,12 @@ export function nativeAddAllWithExclusions(basePath: string, exclusions: readonl
|
|
|
724
724
|
return;
|
|
725
725
|
}
|
|
726
726
|
// When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
|
|
727
|
-
// "beyond a symbolic link". Fall back to
|
|
728
|
-
//
|
|
727
|
+
// "beyond a symbolic link". Fall back to `git add -u` which only
|
|
728
|
+
// stages changes to already-tracked files — O(tracked) not O(filesystem).
|
|
729
|
+
// Using `git add -A` here would traverse the entire working tree,
|
|
730
|
+
// hanging indefinitely on repos with large untracked data dirs. (#1977)
|
|
729
731
|
if (stderr.includes("beyond a symbolic link")) {
|
|
730
|
-
|
|
732
|
+
gitFileExec(basePath, ["add", "-u"]);
|
|
731
733
|
return;
|
|
732
734
|
}
|
|
733
735
|
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
@@ -104,6 +104,8 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
104
104
|
"context_management",
|
|
105
105
|
"experimental",
|
|
106
106
|
"codebase",
|
|
107
|
+
"slice_parallel",
|
|
108
|
+
"safety_harness",
|
|
107
109
|
]);
|
|
108
110
|
|
|
109
111
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -288,6 +290,20 @@ export interface GSDPreferences {
|
|
|
288
290
|
experimental?: ExperimentalPreferences;
|
|
289
291
|
/** Configuration for the codebase map generator (/gsd codebase). */
|
|
290
292
|
codebase?: CodebaseMapPreferences;
|
|
293
|
+
/** Slice-level parallelism within a milestone. Disabled by default. */
|
|
294
|
+
slice_parallel?: { enabled?: boolean; max_workers?: number };
|
|
295
|
+
/** LLM safety harness configuration. Monitors, validates, and constrains LLM behavior during auto-mode. Enabled by default with warn-and-continue policy. */
|
|
296
|
+
safety_harness?: {
|
|
297
|
+
enabled?: boolean;
|
|
298
|
+
evidence_collection?: boolean;
|
|
299
|
+
file_change_validation?: boolean;
|
|
300
|
+
evidence_cross_reference?: boolean;
|
|
301
|
+
destructive_command_warnings?: boolean;
|
|
302
|
+
content_validation?: boolean;
|
|
303
|
+
checkpoints?: boolean;
|
|
304
|
+
auto_rollback?: boolean;
|
|
305
|
+
timeout_scale_cap?: number;
|
|
306
|
+
};
|
|
291
307
|
}
|
|
292
308
|
|
|
293
309
|
export interface LoadedGSDPreferences {
|
|
@@ -224,9 +224,13 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
|
|
|
224
224
|
return parseHeadingListFormat(content);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
// Warn when a non-empty file exists but lacks frontmatter delimiters (#2036).
|
|
228
|
+
if (content.trim().length > 0 && !_warnedUnrecognizedFormat) {
|
|
228
229
|
_warnedUnrecognizedFormat = true;
|
|
229
|
-
console.warn(
|
|
230
|
+
console.warn(
|
|
231
|
+
"[GSD] Warning: preferences file has unrecognized format — content does not use YAML frontmatter delimiters (---). " +
|
|
232
|
+
"Wrap your preferences in --- fences. See https://github.com/gsd-build/gsd-2/issues/2036",
|
|
233
|
+
);
|
|
230
234
|
}
|
|
231
235
|
return null;
|
|
232
236
|
}
|
|
@@ -384,6 +388,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
384
388
|
].filter(Boolean),
|
|
385
389
|
}
|
|
386
390
|
: undefined,
|
|
391
|
+
slice_parallel: (base.slice_parallel || override.slice_parallel)
|
|
392
|
+
? { ...(base.slice_parallel ?? {}), ...(override.slice_parallel ?? {}) }
|
|
393
|
+
: undefined,
|
|
387
394
|
};
|
|
388
395
|
}
|
|
389
396
|
|
|
@@ -51,6 +51,14 @@ const __extensionDir = resolveExtensionDir();
|
|
|
51
51
|
const promptsDir = join(__extensionDir, "prompts");
|
|
52
52
|
const templatesDir = join(__extensionDir, "templates");
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Return the resolved templates directory path for use in prompts.
|
|
56
|
+
* Avoids hardcoding `~/.gsd/agent/extensions/gsd/templates/` in templates. (#3575)
|
|
57
|
+
*/
|
|
58
|
+
export function getTemplatesDir(): string {
|
|
59
|
+
return templatesDir;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
// Cache all templates eagerly at module load — a running session uses the
|
|
55
63
|
// template versions that were on disk at startup, immune to later overwrites.
|
|
56
64
|
const templateCache = new Map<string, string>();
|
|
@@ -24,6 +24,8 @@ Then:
|
|
|
24
24
|
7. Fill the **Decision Re-evaluation** table in the milestone summary. For each key decision from `.gsd/DECISIONS.md` made during this milestone, evaluate whether it is still valid given what was actually built. Flag decisions that should be revisited next milestone.
|
|
25
25
|
8. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
26
26
|
|
|
27
|
+
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — the engine owns the WAL connection. Use `gsd_milestone_status` to read milestone and slice state. All data you need is already inlined in the context above or accessible via the `gsd_*` tools — never via direct SQL.
|
|
28
|
+
|
|
27
29
|
### Verification Gate — STOP if verification failed
|
|
28
30
|
|
|
29
31
|
**If ANY verification failure was recorded in steps 3, 4, or 5, you MUST follow the failure path below. Do NOT proceed to step 9.**
|
|
@@ -35,6 +35,8 @@ Then:
|
|
|
35
35
|
|
|
36
36
|
**Autonomous execution:** Do not call `ask_user_questions` or `secure_env_collect`. You are running in auto-mode — there is no human available to answer questions. Make reasonable assumptions and document them in the slice summary. If a decision genuinely requires human input, note it in the summary and proceed with the best available option.
|
|
37
37
|
|
|
38
|
+
**File system safety:** Task summaries are preloaded in the inlined context above. If you need to re-read any of them, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` to list file paths first — never pass `{{slicePath}}` or any other directory path directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
39
|
+
|
|
38
40
|
**You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
|
|
39
41
|
|
|
40
42
|
When done, say: "Slice {{sliceId}} complete."
|
|
@@ -9,6 +9,7 @@ Rules:
|
|
|
9
9
|
4. For missing summaries or UAT files, generate the real artifact from existing slice/task context when possible — do not leave placeholders if you can reconstruct the real content.
|
|
10
10
|
5. After each repair cluster, verify the relevant invariant directly from disk.
|
|
11
11
|
6. When done, rerun `/gsd doctor {{doctorCommandSuffix}}` mentally by ensuring the remaining issue set for this scope is reduced or cleared.
|
|
12
|
+
7. Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — use `gsd_milestone_status` to inspect DB state. Direct access bypasses the WAL connection owned by the engine and can corrupt in-flight writes.
|
|
12
13
|
|
|
13
14
|
## Doctor Summary
|
|
14
15
|
|
|
@@ -116,6 +116,8 @@ A unit dispatched more than once (`type/id` appears multiple times) indicates a
|
|
|
116
116
|
|
|
117
117
|
5. **Read the actual GSD source code** at `{{gsdSourceDir}}` to confirm or deny each hypothesis. Do not guess what code does — read it.
|
|
118
118
|
|
|
119
|
+
**DB inspection:** If you need to check DB state as part of investigation, use `gsd_milestone_status` — never run `sqlite3 .gsd/gsd.db` or `node -e require('better-sqlite3')` directly. The engine holds a WAL write lock; direct access will either fail or return stale data.
|
|
120
|
+
|
|
119
121
|
6. **Trace the code path** from the entry point (usually `auto-loop.ts` dispatch or `auto-dispatch.ts`) through to the failure point. Follow function calls across files.
|
|
120
122
|
|
|
121
123
|
7. **Identify the specific file and line** where the bug lives. Determine what kind of defect it is:
|
|
@@ -63,4 +63,6 @@ If `.gsd/REQUIREMENTS.md` exists and requirement ownership or status changed, up
|
|
|
63
63
|
|
|
64
64
|
{{commitInstruction}}
|
|
65
65
|
|
|
66
|
+
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')`. Use `gsd_milestone_status` to read current milestone and slice state. All roadmap mutations go through `gsd_reassess_roadmap` — the tool writes to the DB and re-renders ROADMAP.md atomically.
|
|
67
|
+
|
|
66
68
|
When done, say: "Roadmap reassessed."
|