gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.3118184
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 -3
- package/dist/cli.js +76 -3
- package/dist/mcp-server.js +37 -14
- package/dist/onboarding.js +10 -0
- package/dist/resources/agents/debugger.md +58 -0
- package/dist/resources/agents/doc-writer.md +43 -0
- package/dist/resources/agents/git-ops.md +56 -0
- package/dist/resources/agents/javascript-pro.md +46 -271
- package/dist/resources/agents/planner.md +55 -0
- package/dist/resources/agents/refactorer.md +47 -0
- package/dist/resources/agents/reviewer.md +48 -0
- package/dist/resources/agents/security.md +59 -0
- package/dist/resources/agents/tester.md +50 -0
- package/dist/resources/agents/typescript-pro.md +41 -235
- package/dist/resources/extensions/async-jobs/await-tool.js +7 -4
- package/dist/resources/extensions/async-jobs/job-manager.js +28 -3
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +132 -10
- package/dist/resources/extensions/gsd/auto/loop.js +84 -1
- package/dist/resources/extensions/gsd/auto/phases.js +4 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +6 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
- package/dist/resources/extensions/gsd/auto-recovery.js +11 -0
- package/dist/resources/extensions/gsd/auto-start.js +24 -4
- package/dist/resources/extensions/gsd/auto.js +29 -19
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +9 -11
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
- package/dist/resources/extensions/gsd/commands-handlers.js +4 -1
- package/dist/resources/extensions/gsd/context-injector.js +1 -1
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +3 -7
- package/dist/resources/extensions/gsd/definition-io.js +15 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +4 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +6 -3
- package/dist/resources/extensions/gsd/error-classifier.js +4 -1
- package/dist/resources/extensions/gsd/gate-registry.js +208 -0
- package/dist/resources/extensions/gsd/git-service.js +11 -8
- package/dist/resources/extensions/gsd/gitignore.js +12 -6
- package/dist/resources/extensions/gsd/gsd-db.js +90 -6
- package/dist/resources/extensions/gsd/key-manager.js +2 -0
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
- package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
- package/dist/resources/extensions/gsd/notification-store.js +5 -4
- package/dist/resources/extensions/gsd/preferences-skills.js +2 -34
- package/dist/resources/extensions/gsd/preferences-types.js +15 -0
- package/dist/resources/extensions/gsd/preferences.js +16 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +4 -1
- package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
- package/dist/resources/extensions/gsd/state.js +29 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +7 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +30 -3
- package/dist/resources/extensions/gsd/write-intercept.js +10 -1
- package/dist/resources/extensions/ollama/index.js +17 -10
- package/dist/resources/extensions/ollama/ollama-client.js +35 -6
- package/dist/resources/extensions/ollama/ollama-discovery.js +32 -6
- package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
- package/dist/resources/extensions/subagent/agents.js +8 -0
- package/dist/resources/extensions/subagent/index.js +17 -0
- package/dist/startup-model-validation.d.ts +0 -1
- package/dist/startup-model-validation.js +6 -2
- 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/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +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/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
- package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
- package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
- package/dist/web/standalone/.next/server/chunks/63.js +8 -8
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/edge-runtime-webpack.js +2 -0
- package/dist/web/standalone/.next/server/functions-config-manifest.json +0 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +29 -2
- package/dist/web/standalone/.next/server/middleware.js +4 -12
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/server.d.ts +12 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +90 -42
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/server.ts +110 -38
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +1 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.custom.d.ts +105 -0
- package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.custom.js +97 -0
- package/packages/pi-ai/dist/models.custom.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +648 -140
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +867 -370
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.d.ts +2 -0
- package/packages/pi-ai/dist/models.generated.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/models.generated.test.js +334 -0
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -0
- package/packages/pi-ai/dist/models.test.js +105 -0
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -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/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +5 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts +2 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js +57 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.test.js.map +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +1 -0
- package/packages/pi-ai/src/models.custom.ts +98 -0
- package/packages/pi-ai/src/models.generated.test.ts +373 -0
- package/packages/pi-ai/src/models.generated.ts +867 -370
- package/packages/pi-ai/src/models.test.ts +135 -0
- package/packages/pi-ai/src/types.ts +1 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.test.ts +71 -0
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +4 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.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/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
- package/packages/pi-tui/dist/components/__tests__/editor.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/editor.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +12 -0
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +27 -0
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/src/components/__tests__/editor.test.ts +18 -0
- package/packages/pi-tui/src/components/__tests__/input.test.ts +18 -0
- package/packages/pi-tui/src/keys.ts +32 -0
- package/pkg/package.json +1 -1
- package/src/resources/agents/debugger.md +58 -0
- package/src/resources/agents/doc-writer.md +43 -0
- package/src/resources/agents/git-ops.md +56 -0
- package/src/resources/agents/javascript-pro.md +46 -271
- package/src/resources/agents/planner.md +55 -0
- package/src/resources/agents/refactorer.md +47 -0
- package/src/resources/agents/reviewer.md +48 -0
- package/src/resources/agents/security.md +59 -0
- package/src/resources/agents/tester.md +50 -0
- package/src/resources/agents/typescript-pro.md +41 -235
- package/src/resources/extensions/async-jobs/await-tool.test.ts +40 -7
- package/src/resources/extensions/async-jobs/await-tool.ts +7 -4
- package/src/resources/extensions/async-jobs/job-manager.ts +33 -3
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +139 -8
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +245 -2
- package/src/resources/extensions/gsd/auto/loop.ts +89 -1
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +7 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -0
- package/src/resources/extensions/gsd/auto-start.ts +31 -4
- package/src/resources/extensions/gsd/auto.ts +29 -20
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -10
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
- package/src/resources/extensions/gsd/commands-handlers.ts +5 -1
- package/src/resources/extensions/gsd/context-injector.ts +1 -1
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +4 -8
- package/src/resources/extensions/gsd/definition-io.ts +18 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +5 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +6 -3
- package/src/resources/extensions/gsd/error-classifier.ts +4 -1
- package/src/resources/extensions/gsd/gate-registry.ts +251 -0
- package/src/resources/extensions/gsd/git-service.ts +11 -8
- package/src/resources/extensions/gsd/gitignore.ts +12 -6
- package/src/resources/extensions/gsd/gsd-db.ts +105 -6
- package/src/resources/extensions/gsd/key-manager.ts +2 -0
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
- package/src/resources/extensions/gsd/notification-store.ts +5 -4
- package/src/resources/extensions/gsd/preferences-skills.ts +2 -36
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +19 -6
- package/src/resources/extensions/gsd/prompt-loader.ts +6 -1
- package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +122 -13
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
- package/src/resources/extensions/gsd/state.ts +33 -2
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/definition-io.test.ts +57 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +107 -5
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +8 -6
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-artifact-verification.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/projection-regression.test.ts +96 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +97 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +41 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
- package/src/resources/extensions/gsd/types.ts +26 -0
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +29 -3
- package/src/resources/extensions/gsd/write-intercept.ts +10 -1
- package/src/resources/extensions/ollama/index.ts +17 -8
- package/src/resources/extensions/ollama/ollama-client.ts +35 -6
- package/src/resources/extensions/ollama/ollama-discovery.ts +37 -6
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +54 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
- package/src/resources/extensions/subagent/agents.ts +10 -0
- package/src/resources/extensions/subagent/index.ts +18 -0
- package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → NzO79SOz9jHX-VY5-0t2O}/_ssgManifest.js +0 -0
|
@@ -13,6 +13,69 @@ import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } fr
|
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
15
15
|
import { resolveEngine } from "../engine-resolver.js";
|
|
16
|
+
import { logWarning } from "../workflow-logger.js";
|
|
17
|
+
import { gsdRoot } from "../paths.js";
|
|
18
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
// ── Stuck detection persistence (#3704) ──────────────────────────────────
|
|
21
|
+
// Persist stuck detection state to disk so it survives session restarts.
|
|
22
|
+
// Without this, restarting auto-mode resets all counters, allowing the
|
|
23
|
+
// same blocked unit to burn a full retry budget each session.
|
|
24
|
+
function stuckStatePath(basePath) {
|
|
25
|
+
return join(gsdRoot(basePath), "runtime", "stuck-state.json");
|
|
26
|
+
}
|
|
27
|
+
function loadStuckState(basePath) {
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
|
|
30
|
+
return {
|
|
31
|
+
recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
|
|
32
|
+
stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
debugLog("autoLoop", { phase: "load-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
37
|
+
return { recentUnits: [], stuckRecoveryAttempts: 0 };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveStuckState(basePath, state) {
|
|
41
|
+
try {
|
|
42
|
+
const filePath = stuckStatePath(basePath);
|
|
43
|
+
mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
|
|
44
|
+
writeFileSync(filePath, JSON.stringify({
|
|
45
|
+
recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
|
|
46
|
+
stuckRecoveryAttempts: state.stuckRecoveryAttempts,
|
|
47
|
+
updatedAt: new Date().toISOString(),
|
|
48
|
+
}) + "\n");
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ── Memory pressure monitoring (#3331) ──────────────────────────────────
|
|
55
|
+
// Check heap usage every N iterations and trigger graceful shutdown before
|
|
56
|
+
// the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
|
|
57
|
+
// limit (--max-old-space-size or default ~1.5-4GB depending on platform).
|
|
58
|
+
const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
|
|
59
|
+
const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
|
|
60
|
+
function checkMemoryPressure() {
|
|
61
|
+
const mem = process.memoryUsage();
|
|
62
|
+
// v8.getHeapStatistics() gives heap_size_limit but requires import
|
|
63
|
+
// Use a conservative estimate: RSS > 3GB is danger zone on most systems
|
|
64
|
+
const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
|
|
65
|
+
const rssMB = Math.round(mem.rss / 1024 / 1024);
|
|
66
|
+
// Try to get the actual V8 heap limit
|
|
67
|
+
let limitMB = 4096; // conservative default
|
|
68
|
+
try {
|
|
69
|
+
const v8 = require("node:v8");
|
|
70
|
+
const stats = v8.getHeapStatistics();
|
|
71
|
+
limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
limitMB = 4096; /* v8 stats unavailable — use conservative default */
|
|
75
|
+
}
|
|
76
|
+
const pct = heapMB / limitMB;
|
|
77
|
+
return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
|
|
78
|
+
}
|
|
16
79
|
/**
|
|
17
80
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
18
81
|
* runUnit → finalize → repeat. Exits when s.active becomes false or a
|
|
@@ -24,7 +87,13 @@ import { resolveEngine } from "../engine-resolver.js";
|
|
|
24
87
|
export async function autoLoop(ctx, pi, s, deps) {
|
|
25
88
|
debugLog("autoLoop", { phase: "enter" });
|
|
26
89
|
let iteration = 0;
|
|
27
|
-
|
|
90
|
+
// Load persisted stuck state so counters survive session restarts (#3704)
|
|
91
|
+
const persisted = loadStuckState(s.basePath);
|
|
92
|
+
const loopState = {
|
|
93
|
+
recentUnits: persisted.recentUnits,
|
|
94
|
+
stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
|
|
95
|
+
consecutiveFinalizeTimeouts: 0,
|
|
96
|
+
};
|
|
28
97
|
let consecutiveErrors = 0;
|
|
29
98
|
let consecutiveCooldowns = 0;
|
|
30
99
|
const recentErrorMessages = [];
|
|
@@ -44,6 +113,19 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
44
113
|
await deps.stopAuto(ctx, pi, `Safety: loop exceeded ${MAX_LOOP_ITERATIONS} iterations — possible runaway`);
|
|
45
114
|
break;
|
|
46
115
|
}
|
|
116
|
+
// ── Memory pressure check (#3331) ──
|
|
117
|
+
// Graceful shutdown before OOM killer sends SIGKILL.
|
|
118
|
+
if (iteration % MEMORY_CHECK_INTERVAL === 0) {
|
|
119
|
+
const mem = checkMemoryPressure();
|
|
120
|
+
debugLog("autoLoop", { phase: "memory-check", ...mem });
|
|
121
|
+
if (mem.pressured) {
|
|
122
|
+
logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
|
|
123
|
+
await deps.stopAuto(ctx, pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
|
|
124
|
+
`Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
|
|
125
|
+
`Resume with /gsd auto to continue from where you left off.`);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
47
129
|
if (!s.cmdCtx) {
|
|
48
130
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
49
131
|
break;
|
|
@@ -162,6 +244,7 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
162
244
|
consecutiveCooldowns = 0;
|
|
163
245
|
recentErrorMessages.length = 0;
|
|
164
246
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
247
|
+
saveStuckState(s.basePath, loopState); // persist across session restarts (#3704)
|
|
165
248
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
166
249
|
if (reconcileResult.outcome === "milestone-complete") {
|
|
167
250
|
await deps.stopAuto(ctx, pi, "Workflow complete");
|
|
@@ -13,6 +13,7 @@ import { runUnit } from "./run-unit.js";
|
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { PROJECT_FILES } from "../detection.js";
|
|
15
15
|
import { MergeConflictError } from "../git-service.js";
|
|
16
|
+
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
16
17
|
import { join, basename, dirname, parse as parsePath } from "node:path";
|
|
17
18
|
import { existsSync, cpSync, readdirSync } from "node:fs";
|
|
18
19
|
import { logWarning, logError } from "../workflow-logger.js";
|
|
@@ -770,6 +771,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
|
|
|
770
771
|
s.currentUnit.id === unitId);
|
|
771
772
|
const previousTier = s.currentUnitRouting?.tier;
|
|
772
773
|
s.currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
774
|
+
setCurrentPhase(unitType);
|
|
773
775
|
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
774
776
|
const unitStartSeq = ic.nextSeq();
|
|
775
777
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
@@ -1115,6 +1117,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1115
1117
|
// Detach session from the timed-out unit so late async completions
|
|
1116
1118
|
// cannot mutate state for the next unit (#3757).
|
|
1117
1119
|
s.currentUnit = null;
|
|
1120
|
+
clearCurrentPhase();
|
|
1118
1121
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1119
1122
|
debugLog("autoLoop", {
|
|
1120
1123
|
phase: "pre-verification-timeout",
|
|
@@ -1189,6 +1192,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) {
|
|
|
1189
1192
|
// Detach session from the timed-out unit so late async completions
|
|
1190
1193
|
// cannot mutate state for the next unit (#3757).
|
|
1191
1194
|
s.currentUnit = null;
|
|
1195
|
+
clearCurrentPhase();
|
|
1192
1196
|
loopState.consecutiveFinalizeTimeouts++;
|
|
1193
1197
|
debugLog("autoLoop", {
|
|
1194
1198
|
phase: "post-verification-timeout",
|
|
@@ -16,6 +16,7 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
17
17
|
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
|
|
18
18
|
import { invalidateAllCaches } from "./cache.js";
|
|
19
|
+
import { rebuildState } from "./doctor.js";
|
|
19
20
|
import { parseUnitId } from "./unit-id.js";
|
|
20
21
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
21
22
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
@@ -288,6 +289,11 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
288
289
|
debugLog("postUnit", { phase: "browser-teardown", status: "closed" });
|
|
289
290
|
}
|
|
290
291
|
});
|
|
292
|
+
// Keep the on-disk STATE.md aligned with the live derived state after
|
|
293
|
+
// ordinary unit completion, before any worktree state is synced back.
|
|
294
|
+
await runSafely("postUnit", "state-rebuild", async () => {
|
|
295
|
+
await rebuildState(s.basePath);
|
|
296
|
+
});
|
|
291
297
|
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
292
298
|
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
293
299
|
await runSafely("postUnit", "worktree-sync", () => {
|
|
@@ -15,7 +15,8 @@ import { getLoadedSkills } from "@gsd/pi-coding-agent";
|
|
|
15
15
|
import { join, basename } from "node:path";
|
|
16
16
|
import { existsSync } from "node:fs";
|
|
17
17
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
18
|
-
import {
|
|
18
|
+
import { getPendingGatesForTurn } from "./gsd-db.js";
|
|
19
|
+
import { assertGateCoverage, getGatesForTurn, } from "./gate-registry.js";
|
|
19
20
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
20
21
|
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
21
22
|
import { logWarning } from "./workflow-logger.js";
|
|
@@ -1221,6 +1222,13 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
1221
1222
|
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
1222
1223
|
: "";
|
|
1223
1224
|
const phaseAnchorSection = planAnchor ? formatAnchorForPrompt(planAnchor) : "";
|
|
1225
|
+
// Task-scoped gates owned by execute-task (Q5/Q6/Q7). Pull only the
|
|
1226
|
+
// gates that plan-slice actually seeded for this task — tasks with no
|
|
1227
|
+
// external dependencies legitimately skip Q5, tasks with no runtime
|
|
1228
|
+
// load dimension skip Q6, etc.
|
|
1229
|
+
const etPending = getPendingGatesForTurn(mid, sid, "execute-task", tid);
|
|
1230
|
+
assertGateCoverage(etPending, "execute-task", { requireAll: false });
|
|
1231
|
+
const gatesToClose = renderGatesToCloseBlock(getGatesForTurn("execute-task"), { pending: new Set(etPending.map((g) => g.gate_id)), allowOmit: true });
|
|
1224
1232
|
return loadPrompt("execute-task", {
|
|
1225
1233
|
overridesSection,
|
|
1226
1234
|
runtimeContext,
|
|
@@ -1238,6 +1246,7 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
1238
1246
|
taskSummaryPath,
|
|
1239
1247
|
inlinedTemplates,
|
|
1240
1248
|
verificationBudget,
|
|
1249
|
+
gatesToClose,
|
|
1241
1250
|
skillActivation: buildSkillActivationBlock({
|
|
1242
1251
|
base,
|
|
1243
1252
|
milestoneId: mid,
|
|
@@ -1298,6 +1307,15 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
1298
1307
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
1299
1308
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
1300
1309
|
const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
|
|
1310
|
+
// Gates owned by complete-slice (e.g. Q8). Pull from the DB so the
|
|
1311
|
+
// prompt only prompts for gates the plan actually seeded. The tool
|
|
1312
|
+
// handler closes each gate based on the SUMMARY.md section content
|
|
1313
|
+
// after the assistant calls gsd_complete_slice.
|
|
1314
|
+
const csPending = getPendingGatesForTurn(mid, sid, "complete-slice");
|
|
1315
|
+
// coverage check: every pending row must be owned by complete-slice.
|
|
1316
|
+
// requireAll:false because a slice may have already closed some gates.
|
|
1317
|
+
assertGateCoverage(csPending, "complete-slice", { requireAll: false });
|
|
1318
|
+
const gatesToClose = renderGatesToCloseBlock(getGatesForTurn("complete-slice"), { pending: new Set(csPending.map((g) => g.gate_id)), allowOmit: true });
|
|
1301
1319
|
return loadPrompt("complete-slice", {
|
|
1302
1320
|
workingDirectory: base,
|
|
1303
1321
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
@@ -1306,6 +1324,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
1306
1324
|
inlinedContext,
|
|
1307
1325
|
sliceSummaryPath,
|
|
1308
1326
|
sliceUatPath,
|
|
1327
|
+
gatesToClose,
|
|
1309
1328
|
});
|
|
1310
1329
|
}
|
|
1311
1330
|
export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
@@ -1498,6 +1517,15 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1498
1517
|
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1499
1518
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
1500
1519
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
1520
|
+
// Every milestone validation turn owns MV01–MV04 unconditionally: the
|
|
1521
|
+
// registry is the source of truth for which gates the validator must
|
|
1522
|
+
// address, and the block below is what the template renders so the
|
|
1523
|
+
// assistant can never accidentally skip one.
|
|
1524
|
+
const mvGates = getGatesForTurn("validate-milestone");
|
|
1525
|
+
const gatesToEvaluate = renderGatesToCloseBlock(mvGates, {
|
|
1526
|
+
pending: new Set(mvGates.map((g) => g.id)),
|
|
1527
|
+
allowOmit: false,
|
|
1528
|
+
});
|
|
1501
1529
|
return loadPrompt("validate-milestone", {
|
|
1502
1530
|
workingDirectory: base,
|
|
1503
1531
|
milestoneId: mid,
|
|
@@ -1506,6 +1534,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
1506
1534
|
inlinedContext,
|
|
1507
1535
|
validationPath: validationOutputPath,
|
|
1508
1536
|
remediationRound: String(remediationRound),
|
|
1537
|
+
gatesToEvaluate,
|
|
1509
1538
|
skillActivation: buildSkillActivationBlock({
|
|
1510
1539
|
base,
|
|
1511
1540
|
milestoneId: mid,
|
|
@@ -1740,26 +1769,43 @@ export async function buildReactiveExecutePrompt(mid, midTitle, sid, sTitle, rea
|
|
|
1740
1769
|
});
|
|
1741
1770
|
}
|
|
1742
1771
|
// ─── Gate Evaluation ──────────────────────────────────────────────────────
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1772
|
+
//
|
|
1773
|
+
// Gate definitions (question, guidance, owner turn) now live in
|
|
1774
|
+
// gate-registry.ts so that prompt builders, dispatch rules, state
|
|
1775
|
+
// derivation, and tool handlers all consult the same source of truth.
|
|
1776
|
+
// See gate-registry.ts for the full ownership map.
|
|
1777
|
+
/**
|
|
1778
|
+
* Render a "Gates to Close" block for turns like `complete-slice` and
|
|
1779
|
+
* `validate-milestone` that own gates which are closed as a side-effect
|
|
1780
|
+
* of writing artifact sections (not via a dedicated gate-evaluate
|
|
1781
|
+
* subagent loop).
|
|
1782
|
+
*
|
|
1783
|
+
* Returns a plain-text block or an empty string if there are no gates to
|
|
1784
|
+
* close, so callers can drop it straight into a template variable.
|
|
1785
|
+
*/
|
|
1786
|
+
function renderGatesToCloseBlock(gates, opts) {
|
|
1787
|
+
const applicable = gates.filter((g) => opts.pending.has(g.id));
|
|
1788
|
+
if (applicable.length === 0)
|
|
1789
|
+
return "";
|
|
1790
|
+
const lines = [];
|
|
1791
|
+
lines.push("## Gates to Close");
|
|
1792
|
+
lines.push("");
|
|
1793
|
+
lines.push("These quality gates are still pending for this unit. You MUST address every one before calling the closing tool — the handler closes the DB row based on whether the corresponding artifact section is present.");
|
|
1794
|
+
lines.push("");
|
|
1795
|
+
for (const def of applicable) {
|
|
1796
|
+
lines.push(`### ${def.id} — ${def.promptSection}`);
|
|
1797
|
+
lines.push("");
|
|
1798
|
+
lines.push(`**Question:** ${def.question}`);
|
|
1799
|
+
lines.push("");
|
|
1800
|
+
lines.push(def.guidance);
|
|
1801
|
+
if (opts.allowOmit) {
|
|
1802
|
+
lines.push("");
|
|
1803
|
+
lines.push(`If this gate genuinely does not apply to this unit, leave the **${def.promptSection}** section empty and the handler will record it as \`omitted\`. Otherwise, fill the section with concrete evidence.`);
|
|
1804
|
+
}
|
|
1805
|
+
lines.push("");
|
|
1806
|
+
}
|
|
1807
|
+
return lines.join("\n").trimEnd();
|
|
1808
|
+
}
|
|
1763
1809
|
export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, basePath) {
|
|
1764
1810
|
// Build individual research-slice prompts for each slice
|
|
1765
1811
|
const subagentSections = [];
|
|
@@ -1784,24 +1830,33 @@ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, b
|
|
|
1784
1830
|
});
|
|
1785
1831
|
}
|
|
1786
1832
|
export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base) {
|
|
1787
|
-
|
|
1833
|
+
// Pull only the gates this turn actually owns (Q3/Q4). Filter via the
|
|
1834
|
+
// registry so that scope:"slice" gates owned by other turns (Q8) can't
|
|
1835
|
+
// leak into this prompt and can't block dispatch via silent skip.
|
|
1836
|
+
const pending = getPendingGatesForTurn(mid, sid, "gate-evaluate");
|
|
1837
|
+
// Fails loudly if the pending list contains a gate id the registry
|
|
1838
|
+
// doesn't own for this turn. Missing owned gates is allowed here —
|
|
1839
|
+
// `gate-evaluate` is dispatched whenever *any* of its owned gates are
|
|
1840
|
+
// pending, not only when all of them are.
|
|
1841
|
+
assertGateCoverage(pending, "gate-evaluate", { requireAll: false });
|
|
1788
1842
|
// Load the slice plan for context
|
|
1789
1843
|
const planFile = resolveSliceFile(base, mid, sid, "PLAN");
|
|
1790
1844
|
const planContent = planFile ? (await loadFile(planFile)) ?? "(plan file empty)" : "(plan file not found)";
|
|
1791
|
-
// Build per-gate subagent prompts
|
|
1845
|
+
// Build per-gate subagent prompts from the pending rows. Because the
|
|
1846
|
+
// registry has already validated every row, `getGateDefinition` cannot
|
|
1847
|
+
// return undefined here.
|
|
1848
|
+
const pendingIds = new Set(pending.map((g) => g.gate_id));
|
|
1849
|
+
const gateDefs = getGatesForTurn("gate-evaluate").filter((def) => pendingIds.has(def.id));
|
|
1792
1850
|
const subagentSections = [];
|
|
1793
1851
|
const gateListLines = [];
|
|
1794
|
-
for (const
|
|
1795
|
-
|
|
1796
|
-
if (!meta)
|
|
1797
|
-
continue;
|
|
1798
|
-
gateListLines.push(`- **${gate.gate_id}**: ${meta.question}`);
|
|
1852
|
+
for (const def of gateDefs) {
|
|
1853
|
+
gateListLines.push(`- **${def.id}**: ${def.question}`);
|
|
1799
1854
|
const subPrompt = [
|
|
1800
|
-
`You are evaluating quality gate **${
|
|
1855
|
+
`You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
|
|
1801
1856
|
"",
|
|
1802
|
-
`## Question: ${
|
|
1857
|
+
`## Question: ${def.question}`,
|
|
1803
1858
|
"",
|
|
1804
|
-
|
|
1859
|
+
def.guidance,
|
|
1805
1860
|
"",
|
|
1806
1861
|
"## Slice Plan",
|
|
1807
1862
|
"",
|
|
@@ -1813,13 +1868,13 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base)
|
|
|
1813
1868
|
`Call the \`gsd_save_gate_result\` tool with:`,
|
|
1814
1869
|
`- \`milestoneId\`: "${mid}"`,
|
|
1815
1870
|
`- \`sliceId\`: "${sid}"`,
|
|
1816
|
-
`- \`gateId\`: "${
|
|
1871
|
+
`- \`gateId\`: "${def.id}"`,
|
|
1817
1872
|
"- `verdict`: \"pass\" (no concerns), \"flag\" (concerns found), or \"omitted\" (not applicable)",
|
|
1818
1873
|
"- `rationale`: one-sentence justification",
|
|
1819
1874
|
"- `findings`: detailed markdown findings (or empty if omitted)",
|
|
1820
1875
|
].join("\n");
|
|
1821
1876
|
subagentSections.push([
|
|
1822
|
-
`### ${
|
|
1877
|
+
`### ${def.id}: ${def.question}`,
|
|
1823
1878
|
"",
|
|
1824
1879
|
"Use this as the prompt for a `subagent` call:",
|
|
1825
1880
|
"",
|
|
@@ -224,6 +224,17 @@ export function verifyExpectedArtifact(unitType, unitId, base) {
|
|
|
224
224
|
if (!isValidationTerminal(validationContent))
|
|
225
225
|
return false;
|
|
226
226
|
}
|
|
227
|
+
if (unitType === "plan-milestone") {
|
|
228
|
+
try {
|
|
229
|
+
const roadmap = parseLegacyRoadmap(readFileSync(absPath, "utf-8"));
|
|
230
|
+
if (roadmap.slices.length === 0)
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
logWarning("recovery", `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
227
238
|
// plan-slice must produce a plan with actual task entries, not just a scaffold.
|
|
228
239
|
// The plan file may exist from a prior discussion/context step with only headings
|
|
229
240
|
// but no tasks. Without this check the artifact is considered "complete" and the
|
|
@@ -190,16 +190,33 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
190
190
|
//
|
|
191
191
|
// Precedence:
|
|
192
192
|
// 1) Explicit session override via /gsd model (this session)
|
|
193
|
-
// 2) GSD model preferences from PREFERENCES.md
|
|
194
|
-
// 3) Current session model from settings/session restore
|
|
193
|
+
// 2) GSD model preferences from PREFERENCES.md (validated against live auth)
|
|
194
|
+
// 3) Current session model from settings/session restore (if provider ready)
|
|
195
195
|
//
|
|
196
196
|
// This preserves #3517 defaults while honoring explicit runtime model
|
|
197
197
|
// selection for subsequent /gsd runs in the same session.
|
|
198
198
|
const manualSessionOverride = getSessionModelOverride(ctx.sessionManager.getSessionId());
|
|
199
199
|
const preferredModel = resolveDefaultSessionModel(ctx.model?.provider);
|
|
200
|
+
// Validate the preferred model against the live registry + provider auth so
|
|
201
|
+
// an unconfigured PREFERENCES.md entry (no API key / OAuth) can't become the
|
|
202
|
+
// start-model snapshot. Without this, every subsequent unit would try to
|
|
203
|
+
// fall back to an unusable model.
|
|
204
|
+
let validatedPreferredModel;
|
|
205
|
+
if (preferredModel) {
|
|
206
|
+
const { resolveModelId } = await import("./auto-model-selection.js");
|
|
207
|
+
const available = ctx.modelRegistry.getAvailable();
|
|
208
|
+
const match = resolveModelId(`${preferredModel.provider}/${preferredModel.id}`, available, ctx.model?.provider);
|
|
209
|
+
if (match) {
|
|
210
|
+
validatedPreferredModel = { provider: match.provider, id: match.id };
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
ctx.ui.notify(`Preferred model ${preferredModel.provider}/${preferredModel.id} from PREFERENCES.md is not configured; falling back to session default.`, "warning");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const sessionModelReady = ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
|
|
200
217
|
const startModelSnapshot = manualSessionOverride
|
|
201
|
-
??
|
|
202
|
-
?? (ctx.model
|
|
218
|
+
?? validatedPreferredModel
|
|
219
|
+
?? (sessionModelReady && ctx.model
|
|
203
220
|
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
204
221
|
: null);
|
|
205
222
|
try {
|
|
@@ -453,6 +470,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
453
470
|
// Successfully resolved an active milestone — reset the re-entry guard
|
|
454
471
|
s.consecutiveCompleteBootstraps = 0;
|
|
455
472
|
// ── Initialize session state ──
|
|
473
|
+
// Notify shared phase state so subagent conflict checks can fire
|
|
474
|
+
const { activateGSD: activateGSDPhaseState } = await import("../shared/gsd-phase-state.js");
|
|
475
|
+
activateGSDPhaseState();
|
|
456
476
|
s.active = true;
|
|
457
477
|
s.stepMode = requestedStepMode;
|
|
458
478
|
s.verbose = verboseMode;
|
|
@@ -34,6 +34,7 @@ import { preDispatchHealthGate, resetProactiveHealing, setLevelChangeCallback, }
|
|
|
34
34
|
import { clearSkillSnapshot } from "./skill-discovery.js";
|
|
35
35
|
import { captureAvailableSkills, resetSkillTelemetry, } from "./skill-telemetry.js";
|
|
36
36
|
import { getRtkSessionSavings } from "../shared/rtk-session-stats.js";
|
|
37
|
+
import { deactivateGSD } from "../shared/gsd-phase-state.js";
|
|
37
38
|
import { initMetrics, resetMetrics, getLedger, getProjectTotals, formatCost, formatTokenCount, } from "./metrics.js";
|
|
38
39
|
import { logWarning } from "./workflow-logger.js";
|
|
39
40
|
import { homedir } from "node:os";
|
|
@@ -374,6 +375,7 @@ function handleLostSessionLock(ctx, lockStatus) {
|
|
|
374
375
|
});
|
|
375
376
|
s.active = false;
|
|
376
377
|
s.paused = false;
|
|
378
|
+
deactivateGSD();
|
|
377
379
|
clearUnitTimeout();
|
|
378
380
|
restoreProjectRootEnv();
|
|
379
381
|
restoreMilestoneLockEnv();
|
|
@@ -406,6 +408,7 @@ function handleLostSessionLock(ctx, lockStatus) {
|
|
|
406
408
|
function cleanupAfterLoopExit(ctx) {
|
|
407
409
|
s.currentUnit = null;
|
|
408
410
|
s.active = false;
|
|
411
|
+
deactivateGSD();
|
|
409
412
|
clearUnitTimeout();
|
|
410
413
|
restoreProjectRootEnv();
|
|
411
414
|
restoreMilestoneLockEnv();
|
|
@@ -422,9 +425,13 @@ function cleanupAfterLoopExit(ctx) {
|
|
|
422
425
|
/* best-effort — mirror stopAuto cleanup */
|
|
423
426
|
logWarning("session", `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
424
427
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
+
// A transient provider-error pause intentionally leaves the paused badge
|
|
429
|
+
// visible so the user still has a resumable auto-mode signal on screen.
|
|
430
|
+
if (!s.paused) {
|
|
431
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
432
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
433
|
+
ctx.ui.setFooter(undefined);
|
|
434
|
+
}
|
|
428
435
|
// Restore CWD out of worktree back to original project root
|
|
429
436
|
if (s.originalBasePath) {
|
|
430
437
|
s.basePath = s.originalBasePath;
|
|
@@ -526,7 +533,22 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
526
533
|
catch (e) {
|
|
527
534
|
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
|
528
535
|
}
|
|
529
|
-
// ── Step 5: DB
|
|
536
|
+
// ── Step 5: Rebuild state while DB is still open (#3599) ──
|
|
537
|
+
// rebuildState() calls deriveState() which needs the DB for authoritative
|
|
538
|
+
// state. Previously this ran after closeDatabase(), forcing a filesystem
|
|
539
|
+
// fallback that could disagree with the DB-backed dispatch decisions —
|
|
540
|
+
// a split-brain where dispatch says "blocked" but STATE.md shows work.
|
|
541
|
+
if (s.basePath) {
|
|
542
|
+
try {
|
|
543
|
+
await rebuildState(s.basePath);
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
debugLog("stop-rebuild-state-failed", {
|
|
547
|
+
error: e instanceof Error ? e.message : String(e),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// ── Step 6: DB cleanup ──
|
|
530
552
|
if (isDbAvailable()) {
|
|
531
553
|
try {
|
|
532
554
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
@@ -538,7 +560,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
538
560
|
});
|
|
539
561
|
}
|
|
540
562
|
}
|
|
541
|
-
// ── Step
|
|
563
|
+
// ── Step 7: Restore basePath and chdir ──
|
|
542
564
|
try {
|
|
543
565
|
if (s.originalBasePath) {
|
|
544
566
|
s.basePath = s.originalBasePath;
|
|
@@ -554,7 +576,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
554
576
|
catch (e) {
|
|
555
577
|
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
556
578
|
}
|
|
557
|
-
// ── Step
|
|
579
|
+
// ── Step 8: Ledger notification ──
|
|
558
580
|
try {
|
|
559
581
|
const ledger = getLedger();
|
|
560
582
|
if (ledger && ledger.units.length > 0) {
|
|
@@ -568,17 +590,6 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
568
590
|
catch (e) {
|
|
569
591
|
debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
|
|
570
592
|
}
|
|
571
|
-
// ── Step 8: Rebuild state ──
|
|
572
|
-
if (s.basePath) {
|
|
573
|
-
try {
|
|
574
|
-
await rebuildState(s.basePath);
|
|
575
|
-
}
|
|
576
|
-
catch (e) {
|
|
577
|
-
debugLog("stop-rebuild-state-failed", {
|
|
578
|
-
error: e instanceof Error ? e.message : String(e),
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
593
|
// ── Step 9: Cmux sidebar / event log ──
|
|
583
594
|
try {
|
|
584
595
|
clearCmuxSidebar(loadedPreferences);
|
|
@@ -743,6 +754,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
|
|
|
743
754
|
_resetPendingResolve();
|
|
744
755
|
s.active = false;
|
|
745
756
|
s.paused = true;
|
|
757
|
+
deactivateGSD();
|
|
746
758
|
restoreProjectRootEnv();
|
|
747
759
|
restoreMilestoneLockEnv();
|
|
748
760
|
s.pendingVerificationRetry = null;
|
|
@@ -1290,8 +1302,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1290
1302
|
pi.sendMessage({ customType: "gsd-auto", content: hookPrompt, display: true }, { triggerTurn: true });
|
|
1291
1303
|
return true;
|
|
1292
1304
|
}
|
|
1293
|
-
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1294
|
-
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
1295
1305
|
// Re-export recovery functions for external consumers
|
|
1296
1306
|
export { buildLoopRemediationSteps, } from "./auto-recovery.js";
|
|
1297
1307
|
export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
@@ -919,12 +919,12 @@ export function registerDbTools(pi) {
|
|
|
919
919
|
const saveGateResultTool = {
|
|
920
920
|
name: "gsd_save_gate_result",
|
|
921
921
|
label: "Save Gate Result",
|
|
922
|
-
description: "Save the result of a quality gate evaluation (Q3-Q8) to the GSD database. " +
|
|
922
|
+
description: "Save the result of a quality gate evaluation (Q3-Q8 or MV01-MV04) to the GSD database. " +
|
|
923
923
|
"Called by gate evaluation sub-agents after analyzing a specific quality question.",
|
|
924
924
|
promptSnippet: "Save quality gate evaluation result (verdict, rationale, findings)",
|
|
925
925
|
promptGuidelines: [
|
|
926
926
|
"Use gsd_save_gate_result after evaluating a quality gate question.",
|
|
927
|
-
"gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8.",
|
|
927
|
+
"gateId must be one of: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, MV04.",
|
|
928
928
|
"verdict must be: pass (no concerns), flag (concerns found), or omitted (not applicable).",
|
|
929
929
|
"rationale should be a one-sentence justification for the verdict.",
|
|
930
930
|
"findings should contain detailed markdown analysis (or empty string if omitted).",
|
|
@@ -932,7 +932,7 @@ export function registerDbTools(pi) {
|
|
|
932
932
|
parameters: Type.Object({
|
|
933
933
|
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
934
934
|
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
935
|
-
gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, or
|
|
935
|
+
gateId: Type.String({ description: "Gate ID: Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, or MV04" }),
|
|
936
936
|
taskId: Type.Optional(Type.String({ description: "Task ID for task-scoped gates (Q5/Q6/Q7)" })),
|
|
937
937
|
verdict: Type.String({ description: "pass, flag, or omitted" }),
|
|
938
938
|
rationale: Type.String({ description: "One-sentence justification" }),
|
|
@@ -172,14 +172,10 @@ export function registerHooks(pi) {
|
|
|
172
172
|
// Only gate-shaped ask_user_questions calls should block execution.
|
|
173
173
|
// The gate stays pending until the user selects the approval option.
|
|
174
174
|
if (event.toolName === "ask_user_questions") {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
180
|
-
if (typeof questionId === "string") {
|
|
181
|
-
setPendingGate(questionId);
|
|
182
|
-
}
|
|
175
|
+
const questions = event.input?.questions ?? [];
|
|
176
|
+
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
177
|
+
if (typeof questionId === "string") {
|
|
178
|
+
setPendingGate(questionId);
|
|
183
179
|
}
|
|
184
180
|
}
|
|
185
181
|
// ── Discussion gate enforcement: block tool calls while gate is pending ──
|
|
@@ -261,8 +257,6 @@ export function registerHooks(pi) {
|
|
|
261
257
|
return;
|
|
262
258
|
const milestoneId = getDiscussionMilestoneId(process.cwd());
|
|
263
259
|
const queueActive = isQueuePhaseActive();
|
|
264
|
-
if (!milestoneId && !queueActive)
|
|
265
|
-
return;
|
|
266
260
|
const details = event.details;
|
|
267
261
|
// ── Discussion gate enforcement: handle gate question responses ──
|
|
268
262
|
// If the result is cancelled or has no response, the pending gate stays active
|
|
@@ -293,12 +287,16 @@ export function registerHooks(pi) {
|
|
|
293
287
|
// Only unlock the gate if the user selected the first option (confirmation).
|
|
294
288
|
// Cross-references against the question's defined options to reject free-form "Other" text.
|
|
295
289
|
const answer = details.response?.answers?.[question.id];
|
|
290
|
+
const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
|
|
296
291
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
297
|
-
markDepthVerified(
|
|
292
|
+
markDepthVerified(inferredMilestoneId);
|
|
293
|
+
clearPendingGate();
|
|
298
294
|
}
|
|
299
295
|
break;
|
|
300
296
|
}
|
|
301
297
|
}
|
|
298
|
+
if (!milestoneId && !queueActive)
|
|
299
|
+
return;
|
|
302
300
|
if (!milestoneId)
|
|
303
301
|
return;
|
|
304
302
|
const basePath = process.cwd();
|
|
@@ -71,9 +71,6 @@ export function registerShortcuts(pi) {
|
|
|
71
71
|
description: shortcutDesc(GSD_SHORTCUTS.parallel.action, GSD_SHORTCUTS.parallel.command),
|
|
72
72
|
handler: openParallelOverlay,
|
|
73
73
|
});
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
description: shortcutDesc(`${GSD_SHORTCUTS.parallel.action} (fallback)`, GSD_SHORTCUTS.parallel.command),
|
|
77
|
-
handler: openParallelOverlay,
|
|
78
|
-
});
|
|
74
|
+
// No Ctrl+Shift+P fallback — conflicts with cycleModelBackward (shift+ctrl+p).
|
|
75
|
+
// Use Ctrl+Alt+P or /gsd parallel watch instead.
|
|
79
76
|
}
|