gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.351157b
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 +44 -6
- 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/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +123 -22
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +45 -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-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 +73 -60
- 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 +9 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- 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 +1 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- 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 +1 -0
- 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/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 +6 -12
- 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/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- 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 +14 -14
- 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/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 +1 -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 +10 -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/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +2 -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 +11 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- 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 +156 -34
- package/src/resources/extensions/gsd/auto/session.ts +9 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +53 -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-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 +74 -60
- 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 +9 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- 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 +3 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- 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 +1 -0
- 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/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-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/forensics-stuck-loops.test.ts +103 -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/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-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 +6 -14
- 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-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-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 → QmuF-eAbuU_2MQ03t38qr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → QmuF-eAbuU_2MQ03t38qr}/_ssgManifest.js +0 -0
|
@@ -402,6 +402,23 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
402
402
|
adapter?.prepare('DELETE FROM decisions WHERE id = :id').run({ ':id': id });
|
|
403
403
|
throw diskErr;
|
|
404
404
|
}
|
|
405
|
+
// #2661: When a decision defers a slice, update the slice status in the DB
|
|
406
|
+
// so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are
|
|
407
|
+
// in split-brain: the decision says "deferred" but the state still says
|
|
408
|
+
// "active", causing auto-mode to keep dispatching the deferred work.
|
|
409
|
+
try {
|
|
410
|
+
const sliceRef = extractDeferredSliceRef(fields);
|
|
411
|
+
if (sliceRef) {
|
|
412
|
+
db.updateSliceStatus(sliceRef.milestoneId, sliceRef.sliceId, 'deferred');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (deferErr) {
|
|
416
|
+
// Non-fatal — log but don't fail the decision save
|
|
417
|
+
logError('manifest', 'failed to update deferred slice status', {
|
|
418
|
+
fn: 'saveDecisionToDb',
|
|
419
|
+
error: String(deferErr.message),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
405
422
|
// Invalidate file-read caches so deriveState() sees the updated markdown.
|
|
406
423
|
// Do NOT clear the artifacts table — we just wrote to it intentionally.
|
|
407
424
|
invalidateStateCache();
|
|
@@ -414,6 +431,33 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
414
431
|
throw err;
|
|
415
432
|
}
|
|
416
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Extract a milestone/slice reference from a deferral decision.
|
|
436
|
+
*
|
|
437
|
+
* Detects deferrals by checking:
|
|
438
|
+
* - scope contains "defer" (e.g., "deferral", "defer")
|
|
439
|
+
* - choice or decision contains "defer" + an M###/S## pattern
|
|
440
|
+
*
|
|
441
|
+
* Returns { milestoneId, sliceId } if found, null otherwise.
|
|
442
|
+
*/
|
|
443
|
+
export function extractDeferredSliceRef(fields) {
|
|
444
|
+
const isDeferral = /\bdefer(?:ral|red|ring|s)?\b/i.test(fields.scope) ||
|
|
445
|
+
/\bdefer(?:ral|red|ring|s)?\b/i.test(fields.choice) ||
|
|
446
|
+
/\bdefer(?:ral|red|ring|s)?\b/i.test(fields.decision);
|
|
447
|
+
if (!isDeferral)
|
|
448
|
+
return null;
|
|
449
|
+
// Look for M###/S## pattern in choice first, then decision
|
|
450
|
+
const slicePattern = /\b(M\d{3,4})\/(S\d{2,3})\b/;
|
|
451
|
+
const choiceMatch = fields.choice.match(slicePattern);
|
|
452
|
+
if (choiceMatch) {
|
|
453
|
+
return { milestoneId: choiceMatch[1], sliceId: choiceMatch[2] };
|
|
454
|
+
}
|
|
455
|
+
const decisionMatch = fields.decision.match(slicePattern);
|
|
456
|
+
if (decisionMatch) {
|
|
457
|
+
return { milestoneId: decisionMatch[1], sliceId: decisionMatch[2] };
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
417
461
|
// ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
|
|
418
462
|
/**
|
|
419
463
|
* Update a requirement in DB and regenerate REQUIREMENTS.md.
|
|
@@ -422,10 +466,34 @@ export async function saveDecisionToDb(fields, basePath) {
|
|
|
422
466
|
export async function updateRequirementInDb(id, updates, basePath) {
|
|
423
467
|
try {
|
|
424
468
|
const db = await import('./gsd-db.js');
|
|
425
|
-
|
|
426
|
-
// If requirement doesn't exist in DB,
|
|
427
|
-
//
|
|
428
|
-
//
|
|
469
|
+
let existing = db.getRequirementById(id);
|
|
470
|
+
// If requirement doesn't exist in DB, seed the entire requirements table
|
|
471
|
+
// from REQUIREMENTS.md first (#3346). This handles the standard workflow
|
|
472
|
+
// where requirements are authored in markdown during discussion but never
|
|
473
|
+
// imported into the database — making gsd_requirement_update always fail
|
|
474
|
+
// with "not_found" at milestone completion.
|
|
475
|
+
if (!existing) {
|
|
476
|
+
const reqFilePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
|
|
477
|
+
try {
|
|
478
|
+
const content = readFileSync(reqFilePath, 'utf-8');
|
|
479
|
+
const { parseRequirementsSections } = await import('./md-importer.js');
|
|
480
|
+
const parsed = parseRequirementsSections(content);
|
|
481
|
+
if (parsed.length > 0) {
|
|
482
|
+
logWarning('manifest', `Seeding ${parsed.length} requirements from REQUIREMENTS.md into DB (first update triggers import)`, { fn: 'updateRequirementInDb' });
|
|
483
|
+
for (const req of parsed) {
|
|
484
|
+
// Only seed if not already in DB (avoid overwriting concurrent inserts)
|
|
485
|
+
if (!db.getRequirementById(req.id)) {
|
|
486
|
+
db.upsertRequirement(req);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Re-check after seeding
|
|
490
|
+
existing = db.getRequirementById(id);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
// REQUIREMENTS.md missing or unparseable — fall through to skeleton
|
|
495
|
+
}
|
|
496
|
+
}
|
|
429
497
|
const base = existing ?? {
|
|
430
498
|
id,
|
|
431
499
|
class: '',
|
|
@@ -498,13 +498,29 @@ function getDbCompletionCounts() {
|
|
|
498
498
|
};
|
|
499
499
|
}
|
|
500
500
|
// ─── Anomaly Detectors ───────────────────────────────────────────────────────
|
|
501
|
-
|
|
502
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Detect units that were dispatched multiple times (stuck in a loop).
|
|
503
|
+
*
|
|
504
|
+
* Counts distinct dispatches by grouping on (type, id, startedAt) first to
|
|
505
|
+
* collapse idle-watchdog duplicate snapshots (#1943), then counts unique
|
|
506
|
+
* startedAt values per type/id to determine actual dispatch count.
|
|
507
|
+
*
|
|
508
|
+
* Exported for testability.
|
|
509
|
+
*/
|
|
510
|
+
export function detectStuckLoops(units, anomalies) {
|
|
511
|
+
// First, collect unique startedAt values per type/id key
|
|
512
|
+
const dispatchMap = new Map();
|
|
503
513
|
for (const u of units) {
|
|
504
514
|
const key = `${u.type}/${u.id}`;
|
|
505
|
-
|
|
515
|
+
let starts = dispatchMap.get(key);
|
|
516
|
+
if (!starts) {
|
|
517
|
+
starts = new Set();
|
|
518
|
+
dispatchMap.set(key, starts);
|
|
519
|
+
}
|
|
520
|
+
starts.add(u.startedAt);
|
|
506
521
|
}
|
|
507
|
-
for (const [key,
|
|
522
|
+
for (const [key, starts] of dispatchMap) {
|
|
523
|
+
const count = starts.size;
|
|
508
524
|
if (count > 1) {
|
|
509
525
|
const [unitType, ...idParts] = key.split("/");
|
|
510
526
|
anomalies.push({
|
|
@@ -1023,11 +1023,12 @@ export function insertMilestone(m) {
|
|
|
1023
1023
|
":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
|
|
1024
1024
|
});
|
|
1025
1025
|
}
|
|
1026
|
-
export function upsertMilestonePlanning(milestoneId, planning
|
|
1026
|
+
export function upsertMilestonePlanning(milestoneId, planning) {
|
|
1027
1027
|
if (!currentDb)
|
|
1028
1028
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1029
1029
|
currentDb.prepare(`UPDATE milestones SET
|
|
1030
|
-
title = COALESCE(:title, title),
|
|
1030
|
+
title = COALESCE(NULLIF(:title, ''), title),
|
|
1031
|
+
status = COALESCE(NULLIF(:status, ''), status),
|
|
1031
1032
|
vision = COALESCE(:vision, vision),
|
|
1032
1033
|
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
1033
1034
|
key_risks = COALESCE(:key_risks, key_risks),
|
|
@@ -1041,7 +1042,8 @@ export function upsertMilestonePlanning(milestoneId, planning, title) {
|
|
|
1041
1042
|
boundary_map_markdown = COALESCE(:boundary_map_markdown, boundary_map_markdown)
|
|
1042
1043
|
WHERE id = :id`).run({
|
|
1043
1044
|
":id": milestoneId,
|
|
1044
|
-
":title": title ??
|
|
1045
|
+
":title": planning.title ?? "",
|
|
1046
|
+
":status": planning.status ?? "",
|
|
1045
1047
|
":vision": planning.vision ?? null,
|
|
1046
1048
|
":success_criteria": planning.successCriteria ? JSON.stringify(planning.successCriteria) : null,
|
|
1047
1049
|
":key_risks": planning.keyRisks ? JSON.stringify(planning.keyRisks) : null,
|
|
@@ -1058,13 +1060,25 @@ export function upsertMilestonePlanning(milestoneId, planning, title) {
|
|
|
1058
1060
|
export function insertSlice(s) {
|
|
1059
1061
|
if (!currentDb)
|
|
1060
1062
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1061
|
-
currentDb.prepare(`INSERT
|
|
1063
|
+
currentDb.prepare(`INSERT INTO slices (
|
|
1062
1064
|
milestone_id, id, title, status, risk, depends, demo, created_at,
|
|
1063
1065
|
goal, success_criteria, proof_level, integration_closure, observability_impact, sequence
|
|
1064
1066
|
) VALUES (
|
|
1065
1067
|
:milestone_id, :id, :title, :status, :risk, :depends, :demo, :created_at,
|
|
1066
1068
|
:goal, :success_criteria, :proof_level, :integration_closure, :observability_impact, :sequence
|
|
1067
|
-
)
|
|
1069
|
+
)
|
|
1070
|
+
ON CONFLICT (milestone_id, id) DO UPDATE SET
|
|
1071
|
+
title = CASE WHEN :raw_title IS NOT NULL THEN excluded.title ELSE slices.title END,
|
|
1072
|
+
status = CASE WHEN slices.status IN ('complete', 'done') THEN slices.status ELSE excluded.status END,
|
|
1073
|
+
risk = CASE WHEN :raw_risk IS NOT NULL THEN excluded.risk ELSE slices.risk END,
|
|
1074
|
+
depends = excluded.depends,
|
|
1075
|
+
demo = CASE WHEN :raw_demo IS NOT NULL THEN excluded.demo ELSE slices.demo END,
|
|
1076
|
+
goal = CASE WHEN :raw_goal IS NOT NULL THEN excluded.goal ELSE slices.goal END,
|
|
1077
|
+
success_criteria = CASE WHEN :raw_success_criteria IS NOT NULL THEN excluded.success_criteria ELSE slices.success_criteria END,
|
|
1078
|
+
proof_level = CASE WHEN :raw_proof_level IS NOT NULL THEN excluded.proof_level ELSE slices.proof_level END,
|
|
1079
|
+
integration_closure = CASE WHEN :raw_integration_closure IS NOT NULL THEN excluded.integration_closure ELSE slices.integration_closure END,
|
|
1080
|
+
observability_impact = CASE WHEN :raw_observability_impact IS NOT NULL THEN excluded.observability_impact ELSE slices.observability_impact END,
|
|
1081
|
+
sequence = CASE WHEN :raw_sequence IS NOT NULL THEN excluded.sequence ELSE slices.sequence END`).run({
|
|
1068
1082
|
":milestone_id": s.milestoneId,
|
|
1069
1083
|
":id": s.id,
|
|
1070
1084
|
":title": s.title ?? "",
|
|
@@ -1079,6 +1093,16 @@ export function insertSlice(s) {
|
|
|
1079
1093
|
":integration_closure": s.planning?.integrationClosure ?? "",
|
|
1080
1094
|
":observability_impact": s.planning?.observabilityImpact ?? "",
|
|
1081
1095
|
":sequence": s.sequence ?? 0,
|
|
1096
|
+
// Raw sentinel params: NULL when caller omitted the field, used in ON CONFLICT guards
|
|
1097
|
+
":raw_title": s.title ?? null,
|
|
1098
|
+
":raw_risk": s.risk ?? null,
|
|
1099
|
+
":raw_demo": s.demo ?? null,
|
|
1100
|
+
":raw_goal": s.planning?.goal ?? null,
|
|
1101
|
+
":raw_success_criteria": s.planning?.successCriteria ?? null,
|
|
1102
|
+
":raw_proof_level": s.planning?.proofLevel ?? null,
|
|
1103
|
+
":raw_integration_closure": s.planning?.integrationClosure ?? null,
|
|
1104
|
+
":raw_observability_impact": s.planning?.observabilityImpact ?? null,
|
|
1105
|
+
":raw_sequence": s.sequence ?? null,
|
|
1082
1106
|
});
|
|
1083
1107
|
}
|
|
1084
1108
|
export function upsertSlicePlanning(milestoneId, sliceId, planning) {
|
|
@@ -1574,19 +1598,31 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
1574
1598
|
definition_of_done, requirement_coverage, boundary_map_markdown
|
|
1575
1599
|
FROM wt.milestones
|
|
1576
1600
|
`).run());
|
|
1577
|
-
// Merge slices — preserve worktree progress
|
|
1601
|
+
// Merge slices — preserve worktree progress but never downgrade completed status (#2558).
|
|
1602
|
+
// Uses INSERT OR REPLACE with a subquery that picks the best status — if the main DB
|
|
1603
|
+
// already has a completed slice, keep that status even if the worktree copy is stale.
|
|
1578
1604
|
merged.slices = countChanges(adapter.prepare(`
|
|
1579
1605
|
INSERT OR REPLACE INTO slices (
|
|
1580
1606
|
milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
|
|
1581
1607
|
full_summary_md, full_uat_md, goal, success_criteria, proof_level,
|
|
1582
1608
|
integration_closure, observability_impact, sequence, replan_triggered_at
|
|
1583
1609
|
)
|
|
1584
|
-
SELECT milestone_id, id, title,
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1610
|
+
SELECT w.milestone_id, w.id, w.title,
|
|
1611
|
+
CASE
|
|
1612
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1613
|
+
THEN m.status ELSE w.status
|
|
1614
|
+
END,
|
|
1615
|
+
w.risk, w.depends, w.demo, w.created_at,
|
|
1616
|
+
CASE
|
|
1617
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1618
|
+
THEN m.completed_at ELSE w.completed_at
|
|
1619
|
+
END,
|
|
1620
|
+
w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
|
|
1621
|
+
w.integration_closure, w.observability_impact, w.sequence, w.replan_triggered_at
|
|
1622
|
+
FROM wt.slices w
|
|
1623
|
+
LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
|
|
1588
1624
|
`).run());
|
|
1589
|
-
// Merge tasks — preserve execution results, status
|
|
1625
|
+
// Merge tasks — preserve execution results, never downgrade completed status (#2558)
|
|
1590
1626
|
merged.tasks = countChanges(adapter.prepare(`
|
|
1591
1627
|
INSERT OR REPLACE INTO tasks (
|
|
1592
1628
|
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
@@ -1595,12 +1631,23 @@ export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
|
1595
1631
|
description, estimate, files, verify, inputs, expected_output,
|
|
1596
1632
|
observability_impact, full_plan_md, sequence
|
|
1597
1633
|
)
|
|
1598
|
-
SELECT milestone_id, slice_id, id, title,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1634
|
+
SELECT w.milestone_id, w.slice_id, w.id, w.title,
|
|
1635
|
+
CASE
|
|
1636
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1637
|
+
THEN m.status ELSE w.status
|
|
1638
|
+
END,
|
|
1639
|
+
w.one_liner, w.narrative,
|
|
1640
|
+
w.verification_result, w.duration,
|
|
1641
|
+
CASE
|
|
1642
|
+
WHEN m.status IN ('complete', 'done') AND w.status NOT IN ('complete', 'done')
|
|
1643
|
+
THEN m.completed_at ELSE w.completed_at
|
|
1644
|
+
END,
|
|
1645
|
+
w.blocker_discovered,
|
|
1646
|
+
w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
|
|
1647
|
+
w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
|
|
1648
|
+
w.observability_impact, w.full_plan_md, w.sequence
|
|
1649
|
+
FROM wt.tasks w
|
|
1650
|
+
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
1604
1651
|
`).run());
|
|
1605
1652
|
// Merge memories — keep worktree-learned insights
|
|
1606
1653
|
merged.memories = countChanges(adapter.prepare(`
|
|
@@ -33,6 +33,7 @@ import { debugLog } from "./debug-logger.js";
|
|
|
33
33
|
import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMilestoneIds, clearReservedMilestoneIds } from "./milestone-ids.js";
|
|
34
34
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
35
35
|
import { selectAndApplyModel } from "./auto-model-selection.js";
|
|
36
|
+
import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
36
37
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
37
38
|
export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
|
|
38
39
|
export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
|
|
@@ -242,6 +243,24 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
|
|
|
242
243
|
});
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
// Scope tools for discuss flows (#2949).
|
|
247
|
+
// Providers with grammar-based constrained decoding (xAI/Grok) return
|
|
248
|
+
// "Grammar is too complex" when the combined tool schema is too large.
|
|
249
|
+
// Discuss flows only need a small subset of GSD tools — strip the heavy
|
|
250
|
+
// planning/execution/completion tools to keep the grammar within limits.
|
|
251
|
+
if (unitType?.startsWith("discuss-")) {
|
|
252
|
+
const currentTools = pi.getActiveTools();
|
|
253
|
+
// Keep all non-GSD tools (builtins, other extensions) and only the
|
|
254
|
+
// GSD tools on the discuss allowlist.
|
|
255
|
+
const scopedTools = currentTools.filter((t) => !t.startsWith("gsd_") || DISCUSS_TOOLS_ALLOWLIST.includes(t));
|
|
256
|
+
pi.setActiveTools(scopedTools);
|
|
257
|
+
debugLog("discuss-tool-scoping", {
|
|
258
|
+
unitType,
|
|
259
|
+
before: currentTools.length,
|
|
260
|
+
after: scopedTools.length,
|
|
261
|
+
removed: currentTools.length - scopedTools.length,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
245
264
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
246
265
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
247
266
|
pi.sendMessage({
|
|
@@ -412,7 +412,33 @@ export function loadLedgerFromDisk(base) {
|
|
|
412
412
|
return loadJsonFileOrNull(metricsPath(base), isMetricsLedger);
|
|
413
413
|
}
|
|
414
414
|
function loadLedger(base) {
|
|
415
|
-
|
|
415
|
+
const raw = loadJsonFile(metricsPath(base), isMetricsLedger, defaultLedger);
|
|
416
|
+
const before = raw.units.length;
|
|
417
|
+
raw.units = deduplicateUnits(raw.units);
|
|
418
|
+
if (raw.units.length < before) {
|
|
419
|
+
// Persist the cleaned ledger so duplicates don't re-accumulate
|
|
420
|
+
saveLedger(base, raw);
|
|
421
|
+
}
|
|
422
|
+
return raw;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Collapse duplicate entries with the same (type, id, startedAt) triple.
|
|
426
|
+
* Keeps the entry with the highest finishedAt (the most complete snapshot).
|
|
427
|
+
*
|
|
428
|
+
* This is a defensive measure against idle-watchdog race conditions that can
|
|
429
|
+
* produce duplicate entries on disk despite the in-memory idempotency guard
|
|
430
|
+
* in snapshotUnitMetrics(). See #1943.
|
|
431
|
+
*/
|
|
432
|
+
function deduplicateUnits(units) {
|
|
433
|
+
const map = new Map();
|
|
434
|
+
for (const u of units) {
|
|
435
|
+
const key = `${u.type}\0${u.id}\0${u.startedAt}`;
|
|
436
|
+
const existing = map.get(key);
|
|
437
|
+
if (!existing || u.finishedAt > existing.finishedAt) {
|
|
438
|
+
map.set(key, u);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return Array.from(map.values());
|
|
416
442
|
}
|
|
417
443
|
function saveLedger(base, data) {
|
|
418
444
|
saveJsonFile(metricsPath(base), data);
|
|
@@ -569,10 +569,12 @@ export function nativeAddAllWithExclusions(basePath, exclusions) {
|
|
|
569
569
|
return;
|
|
570
570
|
}
|
|
571
571
|
// When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
|
|
572
|
-
// "beyond a symbolic link". Fall back to
|
|
573
|
-
//
|
|
572
|
+
// "beyond a symbolic link". Fall back to `git add -u` which only
|
|
573
|
+
// stages changes to already-tracked files — O(tracked) not O(filesystem).
|
|
574
|
+
// Using `git add -A` here would traverse the entire working tree,
|
|
575
|
+
// hanging indefinitely on repos with large untracked data dirs. (#1977)
|
|
574
576
|
if (stderr.includes("beyond a symbolic link")) {
|
|
575
|
-
|
|
577
|
+
gitFileExec(basePath, ["add", "-u"]);
|
|
576
578
|
return;
|
|
577
579
|
}
|
|
578
580
|
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
@@ -148,9 +148,11 @@ export function parsePreferencesMarkdown(content) {
|
|
|
148
148
|
if (/^##\s+\w/m.test(content)) {
|
|
149
149
|
return parseHeadingListFormat(content);
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
// Warn when a non-empty file exists but lacks frontmatter delimiters (#2036).
|
|
152
|
+
if (content.trim().length > 0 && !_warnedUnrecognizedFormat) {
|
|
152
153
|
_warnedUnrecognizedFormat = true;
|
|
153
|
-
console.warn("[
|
|
154
|
+
console.warn("[GSD] Warning: preferences file has unrecognized format — content does not use YAML frontmatter delimiters (---). " +
|
|
155
|
+
"Wrap your preferences in --- fences. See https://github.com/gsd-build/gsd-2/issues/2036");
|
|
154
156
|
}
|
|
155
157
|
return null;
|
|
156
158
|
}
|
|
@@ -301,6 +303,9 @@ function mergePreferences(base, override) {
|
|
|
301
303
|
].filter(Boolean),
|
|
302
304
|
}
|
|
303
305
|
: undefined,
|
|
306
|
+
slice_parallel: (base.slice_parallel || override.slice_parallel)
|
|
307
|
+
? { ...(base.slice_parallel ?? {}), ...(override.slice_parallel ?? {}) }
|
|
308
|
+
: undefined,
|
|
304
309
|
};
|
|
305
310
|
}
|
|
306
311
|
function mergeStringLists(base, override) {
|
|
@@ -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."
|
|
@@ -175,6 +175,7 @@ Templates showing the expected format for each artifact type are in:
|
|
|
175
175
|
- Never guess at library APIs from training data — use `get_library_docs`.
|
|
176
176
|
- Never ask the user to run a command, set a variable, or check something you can check yourself.
|
|
177
177
|
- Never await stale async jobs after editing source — `cancel_job` them first, then re-run.
|
|
178
|
+
- Never query `.gsd/gsd.db` directly via `sqlite3`, `better-sqlite3`, or `node -e require('better-sqlite3')` — the database uses a single-writer WAL connection managed by the engine. Direct access causes reader/writer conflicts and bypasses validation logic. Use `gsd_milestone_status`, `gsd_journal_query`, or other `gsd_*` tools exclusively for all DB reads and writes.
|
|
178
179
|
|
|
179
180
|
### Ask vs infer
|
|
180
181
|
|
|
@@ -38,6 +38,8 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
38
38
|
|
|
39
39
|
**Persist validation results through `gsd_validate_milestone`.** Call it with: `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verificationClasses` (when non-empty), `verdictRationale`, and `remediationPlan` (if verdict is `needs-remediation`). The tool writes the validation to the DB and renders VALIDATION.md to disk.
|
|
40
40
|
|
|
41
|
+
**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. Direct DB access corrupts the WAL and bypasses tool-level validation.
|
|
42
|
+
|
|
41
43
|
If verdict is `needs-remediation`:
|
|
42
44
|
- After calling `gsd_validate_milestone`, use `gsd_reassess_roadmap` to add remediation slices. Pass `milestoneId`, a synthetic `completedSliceId` (e.g. "VALIDATION"), `verdict: "roadmap-adjusted"`, `assessment` text, and `sliceChanges` with the new slices in the `added` array. The tool persists the changes to the DB and re-renders ROADMAP.md.
|
|
43
45
|
- These remediation slices will be planned and executed before validation re-runs.
|
|
@@ -31,7 +31,7 @@ export function markSliceDoneInRoadmap(basePath, mid, sid) {
|
|
|
31
31
|
if (updated === content) {
|
|
32
32
|
updated = content.replace(new RegExp(`^(#{1,4}\\s+(?:\\*{0,2})(?:Slice\\s+)?${sid}\\*{0,2}[:\\s.\\u2014\\u2013-]+\\s*)(.+)`, "m"), (match, prefix, title) => {
|
|
33
33
|
// Already marked done — no-op
|
|
34
|
-
if (
|
|
34
|
+
if (/^[\u2713\u2705]/.test(title) || /[\u2705]\s*$/.test(title) || /\(Complete\)\s*$/i.test(title))
|
|
35
35
|
return match;
|
|
36
36
|
return `${prefix}\u2713 ${title}`;
|
|
37
37
|
});
|
|
@@ -74,7 +74,7 @@ function parseTableSlices(section) {
|
|
|
74
74
|
// Determine completion status from any cell containing [x], "Done", or "Complete"
|
|
75
75
|
const fullRow = line.toLowerCase();
|
|
76
76
|
const done = /\[x\]/i.test(line) ||
|
|
77
|
-
/[
|
|
77
|
+
/[✅☑✓✔]/.test(line) ||
|
|
78
78
|
/\bdone\b/.test(fullRow) ||
|
|
79
79
|
/\bcomplete(?:d)?\b/.test(fullRow);
|
|
80
80
|
// Extract risk from any cell containing risk keywords
|
|
@@ -214,10 +214,10 @@ function parseProseSliceHeaders(content) {
|
|
|
214
214
|
// numeric prefixes (e.g., "1.", "(1)"), bracketed IDs (e.g., "[S01]"),
|
|
215
215
|
// optional checkmark completion marker, and optional leading indentation.
|
|
216
216
|
// Separator after the ID is flexible: colon, dash, em/en dash, dot, or just whitespace.
|
|
217
|
-
const headerPattern = /^\s*#{1,4}\s+\*{0,2}(
|
|
217
|
+
const headerPattern = /^\s*#{1,4}\s+\*{0,2}(?:[\u2713\u2705]\s+)?(?:\d+[.)]\s+)?(?:\(\d+\)\s+)?(?:Slice\s+)?\[?(S\d+)\]?\*{0,2}[:\s.\u2014\u2013-]*\s*(.+)/gm;
|
|
218
218
|
let match;
|
|
219
219
|
// Check for checkmark before the slice ID (e.g., "## checkmark S01: Title")
|
|
220
|
-
const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}\u2713\s+/;
|
|
220
|
+
const prefixCheckPattern = /^\s*#{1,4}\s+\*{0,2}[\u2713\u2705]\s+/;
|
|
221
221
|
while ((match = headerPattern.exec(content)) !== null) {
|
|
222
222
|
const id = match[1];
|
|
223
223
|
let title = match[2].trim().replace(/\*{1,2}$/g, "").trim(); // strip trailing bold markers
|
|
@@ -229,9 +229,13 @@ function parseProseSliceHeaders(content) {
|
|
|
229
229
|
// 3. (Complete) suffix: "## S01: Title (Complete)"
|
|
230
230
|
const line = match[0];
|
|
231
231
|
let done = prefixCheckPattern.test(line);
|
|
232
|
-
if (!done &&
|
|
232
|
+
if (!done && /^[\u2713\u2705]/.test(title)) {
|
|
233
233
|
done = true;
|
|
234
|
-
title = title.replace(
|
|
234
|
+
title = title.replace(/^[\u2713\u2705]\s*/, "");
|
|
235
|
+
}
|
|
236
|
+
if (!done && /[\u2705]\s*$/.test(title)) {
|
|
237
|
+
done = true;
|
|
238
|
+
title = title.replace(/\s*[\u2705]\s*$/, "");
|
|
235
239
|
}
|
|
236
240
|
if (!done && /\(Complete\)\s*$/i.test(title)) {
|
|
237
241
|
done = true;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Slice Parallel Conflict Detection — File overlap analysis between slices.
|
|
3
|
+
*
|
|
4
|
+
* Reads PLAN.md for each slice and extracts file paths mentioned in task
|
|
5
|
+
* descriptions. If two slices share more than 5 file paths, they are considered
|
|
6
|
+
* conflicting and should not run in parallel.
|
|
7
|
+
*
|
|
8
|
+
* Conservative by default: missing PLAN = block parallel execution.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
// ─── File Path Extraction ─────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Extract file paths from a PLAN.md content string.
|
|
15
|
+
* Matches common patterns like `src/...`, `lib/...`, paths with extensions.
|
|
16
|
+
*/
|
|
17
|
+
function extractFilePaths(content) {
|
|
18
|
+
const paths = new Set();
|
|
19
|
+
// Match file-like patterns: word/word paths with extensions, or src/lib/etc prefixed paths
|
|
20
|
+
const patterns = [
|
|
21
|
+
// Paths like src/foo/bar.ts, lib/utils.js, etc.
|
|
22
|
+
/(?:src|lib|test|tests|app|pkg|cmd|internal|components|pages|api|utils|config|scripts|dist|build)\/[\w./-]+\.\w+/g,
|
|
23
|
+
// Generic path with at least one slash and extension
|
|
24
|
+
/(?<!\w)[\w-]+\/[\w./-]+\.\w{1,5}(?!\w)/g,
|
|
25
|
+
];
|
|
26
|
+
for (const pattern of patterns) {
|
|
27
|
+
const matches = content.matchAll(pattern);
|
|
28
|
+
for (const match of matches) {
|
|
29
|
+
paths.add(match[0]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return paths;
|
|
33
|
+
}
|
|
34
|
+
// ─── Conflict Detection ──────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Check if two slices have file conflicts that would block parallel execution.
|
|
37
|
+
*
|
|
38
|
+
* @param basePath Project root path.
|
|
39
|
+
* @param mid Milestone ID.
|
|
40
|
+
* @param sliceA First slice ID.
|
|
41
|
+
* @param sliceB Second slice ID.
|
|
42
|
+
* @returns true if parallel is unsafe (>5 shared files or missing plan).
|
|
43
|
+
*/
|
|
44
|
+
export function hasFileConflict(basePath, mid, sliceA, sliceB) {
|
|
45
|
+
const planPathA = join(basePath, ".gsd", "milestones", mid, sliceA, "PLAN.md");
|
|
46
|
+
const planPathB = join(basePath, ".gsd", "milestones", mid, sliceB, "PLAN.md");
|
|
47
|
+
// Conservative: missing PLAN = block
|
|
48
|
+
if (!existsSync(planPathA) || !existsSync(planPathB)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
const contentA = readFileSync(planPathA, "utf-8");
|
|
52
|
+
const contentB = readFileSync(planPathB, "utf-8");
|
|
53
|
+
const filesA = extractFilePaths(contentA);
|
|
54
|
+
const filesB = extractFilePaths(contentB);
|
|
55
|
+
// If either has no files extracted, no conflict detectable → allow
|
|
56
|
+
if (filesA.size === 0 || filesB.size === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
// Count shared files
|
|
60
|
+
let sharedCount = 0;
|
|
61
|
+
for (const file of filesA) {
|
|
62
|
+
if (filesB.has(file)) {
|
|
63
|
+
sharedCount++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sharedCount > 5;
|
|
67
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Slice Parallel Eligibility — Pure function to determine which slices
|
|
3
|
+
* within a milestone can run in parallel based on dependency satisfaction.
|
|
4
|
+
*
|
|
5
|
+
* This is the slice-level equivalent of parallel-eligibility.ts (which operates
|
|
6
|
+
* at milestone scope). The key difference is the positional fallback: slices
|
|
7
|
+
* without explicit dependencies use sequential ordering as an implicit constraint.
|
|
8
|
+
*/
|
|
9
|
+
// ─── Core Logic ───────────────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* Determine which slices are eligible for parallel execution.
|
|
12
|
+
*
|
|
13
|
+
* Rules:
|
|
14
|
+
* 1. Done slices are never eligible (nothing to do).
|
|
15
|
+
* 2. A slice with explicit `depends` entries is eligible when ALL deps
|
|
16
|
+
* appear in `completedSliceIds`.
|
|
17
|
+
* 3. A slice with NO `depends` entries uses positional fallback: it is
|
|
18
|
+
* eligible only when every positionally-earlier slice is done.
|
|
19
|
+
* This preserves backward compatibility with roadmaps that don't
|
|
20
|
+
* declare inter-slice dependencies.
|
|
21
|
+
*
|
|
22
|
+
* @param slices All slices in the milestone (ordered by position).
|
|
23
|
+
* @param completedSliceIds Set of slice IDs that are already complete.
|
|
24
|
+
* @returns Array of eligible slice descriptors.
|
|
25
|
+
*/
|
|
26
|
+
export function getEligibleSlices(slices, completedSliceIds) {
|
|
27
|
+
const eligible = [];
|
|
28
|
+
for (let i = 0; i < slices.length; i++) {
|
|
29
|
+
const slice = slices[i];
|
|
30
|
+
// Rule 1: skip done slices
|
|
31
|
+
if (slice.done)
|
|
32
|
+
continue;
|
|
33
|
+
const hasExplicitDeps = slice.depends.length > 0;
|
|
34
|
+
if (hasExplicitDeps) {
|
|
35
|
+
// Rule 2: explicit dependencies — all must be satisfied
|
|
36
|
+
const allDepsSatisfied = slice.depends.every(dep => completedSliceIds.has(dep));
|
|
37
|
+
if (allDepsSatisfied) {
|
|
38
|
+
eligible.push({ id: slice.id });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Rule 3: no deps declared — positional fallback
|
|
43
|
+
// Eligible only if all positionally-earlier slices are done
|
|
44
|
+
const allEarlierDone = slices.slice(0, i).every(earlier => earlier.done || completedSliceIds.has(earlier.id));
|
|
45
|
+
if (allEarlierDone) {
|
|
46
|
+
eligible.push({ id: slice.id });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return eligible;
|
|
51
|
+
}
|