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
|
@@ -187,20 +187,36 @@ function extractMessageText(msg: { role: string; content: unknown }): string {
|
|
|
187
187
|
* call effectively stateless. This version serialises the complete
|
|
188
188
|
* conversation history (system prompt + all user/assistant turns) so
|
|
189
189
|
* Claude Code has full context for multi-turn continuity.
|
|
190
|
+
*
|
|
191
|
+
* History is wrapped in XML-tag structure rather than `[User]`/`[Assistant]`
|
|
192
|
+
* bracket headers. Bracket headers read to the model as an in-context
|
|
193
|
+
* demonstration of how turns are delimited, causing it to fabricate fake
|
|
194
|
+
* user turns in its own output. XML tags read as document structure and
|
|
195
|
+
* don't get mirrored in free text.
|
|
190
196
|
*/
|
|
191
197
|
export function buildPromptFromContext(context: Context): string {
|
|
192
|
-
const
|
|
198
|
+
const hasContent = Boolean(context.systemPrompt) || context.messages.some((m) => extractMessageText(m));
|
|
199
|
+
if (!hasContent) return "";
|
|
200
|
+
|
|
201
|
+
const parts: string[] = [
|
|
202
|
+
"Respond only to the final user message below. " +
|
|
203
|
+
"Do not emit <user_message>, <assistant_message>, or <prior_system_context> tags in your response.",
|
|
204
|
+
];
|
|
193
205
|
|
|
194
206
|
if (context.systemPrompt) {
|
|
195
|
-
parts.push(
|
|
207
|
+
parts.push(`<prior_system_context>\n${context.systemPrompt}\n</prior_system_context>`);
|
|
196
208
|
}
|
|
197
209
|
|
|
210
|
+
const turns: string[] = [];
|
|
198
211
|
for (const msg of context.messages) {
|
|
199
212
|
const text = extractMessageText(msg);
|
|
200
213
|
if (!text) continue;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
214
|
+
const tag =
|
|
215
|
+
msg.role === "user" ? "user_message" : msg.role === "assistant" ? "assistant_message" : "system_message";
|
|
216
|
+
turns.push(`<${tag}>\n${text}\n</${tag}>`);
|
|
217
|
+
}
|
|
218
|
+
if (turns.length > 0) {
|
|
219
|
+
parts.push(`<conversation_history>\n${turns.join("\n")}\n</conversation_history>`);
|
|
204
220
|
}
|
|
205
221
|
|
|
206
222
|
return parts.join("\n\n");
|
|
@@ -518,34 +534,104 @@ export function createClaudeCodeElicitationHandler(
|
|
|
518
534
|
};
|
|
519
535
|
}
|
|
520
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Aborted by the caller's AbortSignal — distinct from exhaustion. GSD's
|
|
539
|
+
* agent loop keys off `stopReason === "aborted"` to treat this as a clean
|
|
540
|
+
* user cancel instead of a retry-eligible provider failure.
|
|
541
|
+
*/
|
|
542
|
+
export function makeAbortedMessage(model: string, lastTextContent: string): AssistantMessage {
|
|
543
|
+
const message: AssistantMessage = {
|
|
544
|
+
role: "assistant",
|
|
545
|
+
content: lastTextContent
|
|
546
|
+
? [{ type: "text", text: lastTextContent }]
|
|
547
|
+
: [{ type: "text", text: "Claude Code stream aborted by caller" }],
|
|
548
|
+
api: "anthropic-messages",
|
|
549
|
+
provider: "claude-code",
|
|
550
|
+
model,
|
|
551
|
+
usage: { ...ZERO_USAGE },
|
|
552
|
+
stopReason: "aborted",
|
|
553
|
+
timestamp: Date.now(),
|
|
554
|
+
};
|
|
555
|
+
return message;
|
|
556
|
+
}
|
|
557
|
+
|
|
521
558
|
// ---------------------------------------------------------------------------
|
|
522
559
|
// SDK options builder
|
|
523
560
|
// ---------------------------------------------------------------------------
|
|
524
561
|
|
|
562
|
+
/**
|
|
563
|
+
* Resolve the Claude Code permission mode for the current run.
|
|
564
|
+
*
|
|
565
|
+
* GSD subagents run underneath a host Claude Code session the user has
|
|
566
|
+
* already consented to, and their work (edits, shell inspection, MCP calls)
|
|
567
|
+
* spans the full workflow toolset. Defaulting the inner SDK to
|
|
568
|
+
* `bypassPermissions` avoids per-tool approval prompts that offer no
|
|
569
|
+
* meaningful safety beyond what the host session and the subagent prompts
|
|
570
|
+
* already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
|
|
571
|
+
* users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
|
|
572
|
+
*
|
|
573
|
+
* Tradeoff: bypass means a prompt-injection payload read from an untrusted
|
|
574
|
+
* file could trigger tool calls without a second gate. Accepted for GSD
|
|
575
|
+
* because the workflow is explicit user intent and the alternative
|
|
576
|
+
* (#4099) is continuous approval fatigue that blocks real work.
|
|
577
|
+
*/
|
|
578
|
+
export async function resolveClaudePermissionMode(
|
|
579
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
580
|
+
): Promise<"bypassPermissions" | "acceptEdits" | "default" | "plan"> {
|
|
581
|
+
const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
|
|
582
|
+
if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
|
|
583
|
+
return override;
|
|
584
|
+
}
|
|
585
|
+
return "bypassPermissions";
|
|
586
|
+
}
|
|
587
|
+
|
|
525
588
|
/**
|
|
526
589
|
* Build the options object passed to the Claude Agent SDK's `query()` call.
|
|
527
590
|
*
|
|
528
591
|
* Extracted for testability — callers can verify session persistence,
|
|
529
592
|
* beta flags, and other configuration without mocking the full SDK.
|
|
593
|
+
*
|
|
594
|
+
* `permissionMode` / `allowDangerouslySkipPermissions` are resolved through
|
|
595
|
+
* {@link resolveClaudePermissionMode} so interactive runs don't silently
|
|
596
|
+
* bypass the SDK's permission gate. Callers that want the old always-bypass
|
|
597
|
+
* behaviour pass `permissionMode: "bypassPermissions"` explicitly.
|
|
530
598
|
*/
|
|
531
599
|
export function buildSdkOptions(
|
|
532
600
|
modelId: string,
|
|
533
601
|
prompt: string,
|
|
602
|
+
overrides?: { permissionMode?: "bypassPermissions" | "acceptEdits" | "default" | "plan" },
|
|
534
603
|
extraOptions: Record<string, unknown> = {},
|
|
535
604
|
): Record<string, unknown> {
|
|
536
605
|
const mcpServers = buildWorkflowMcpServers();
|
|
606
|
+
const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
|
|
537
607
|
const disallowedTools = ["AskUserQuestion"];
|
|
608
|
+
// Pre-authorize the safe built-ins and every registered workflow MCP
|
|
609
|
+
// server's tools. `acceptEdits` mode (the interactive default) only
|
|
610
|
+
// auto-approves file edits — Read/Glob/Grep, basic shell inspection, and
|
|
611
|
+
// every `mcp__gsd-workflow__*` call still surface as "This command
|
|
612
|
+
// requires approval" and block GSD actions (#4099).
|
|
613
|
+
const allowedTools = [
|
|
614
|
+
"Read",
|
|
615
|
+
"Write",
|
|
616
|
+
"Edit",
|
|
617
|
+
"Glob",
|
|
618
|
+
"Grep",
|
|
619
|
+
"Bash(ls:*)",
|
|
620
|
+
"Bash(pwd)",
|
|
621
|
+
...(mcpServers ? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`) : []),
|
|
622
|
+
];
|
|
538
623
|
return {
|
|
539
624
|
pathToClaudeCodeExecutable: getClaudePath(),
|
|
540
625
|
model: modelId,
|
|
541
626
|
includePartialMessages: true,
|
|
542
627
|
persistSession: true,
|
|
543
628
|
cwd: process.cwd(),
|
|
544
|
-
permissionMode
|
|
545
|
-
allowDangerouslySkipPermissions:
|
|
629
|
+
permissionMode,
|
|
630
|
+
allowDangerouslySkipPermissions: permissionMode === "bypassPermissions",
|
|
546
631
|
settingSources: ["project"],
|
|
547
632
|
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
548
633
|
disallowedTools,
|
|
634
|
+
...(allowedTools.length > 0 ? { allowedTools } : {}),
|
|
549
635
|
...(mcpServers ? { mcpServers } : {}),
|
|
550
636
|
betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [],
|
|
551
637
|
...extraOptions,
|
|
@@ -656,6 +742,29 @@ function attachExternalResultsToToolBlocks(
|
|
|
656
742
|
}
|
|
657
743
|
}
|
|
658
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Merge tool-call blocks from the active partial-message builder into the
|
|
747
|
+
* running list of intermediate tool calls, preserving order and de-duping
|
|
748
|
+
* by tool-call id. Exposed for testing the F3 fix (final-turn tool calls
|
|
749
|
+
* dropped when `result` arrives without a preceding synthetic `user`).
|
|
750
|
+
*/
|
|
751
|
+
export function mergePendingToolCalls(
|
|
752
|
+
intermediate: AssistantMessage["content"],
|
|
753
|
+
pending: AssistantMessage["content"],
|
|
754
|
+
): AssistantMessage["content"] {
|
|
755
|
+
const alreadyIncluded = new Set<string>();
|
|
756
|
+
for (const block of intermediate) {
|
|
757
|
+
if (block.type === "toolCall") alreadyIncluded.add(block.id);
|
|
758
|
+
}
|
|
759
|
+
for (const block of pending) {
|
|
760
|
+
if (block.type !== "toolCall") continue;
|
|
761
|
+
if (alreadyIncluded.has(block.id)) continue;
|
|
762
|
+
alreadyIncluded.add(block.id);
|
|
763
|
+
intermediate.push(block);
|
|
764
|
+
}
|
|
765
|
+
return intermediate;
|
|
766
|
+
}
|
|
767
|
+
|
|
659
768
|
// ---------------------------------------------------------------------------
|
|
660
769
|
// streamSimple implementation
|
|
661
770
|
// ---------------------------------------------------------------------------
|
|
@@ -712,9 +821,11 @@ async function pumpSdkMessages(
|
|
|
712
821
|
}
|
|
713
822
|
|
|
714
823
|
const prompt = buildPromptFromContext(context);
|
|
824
|
+
const permissionMode = await resolveClaudePermissionMode();
|
|
715
825
|
const sdkOpts = buildSdkOptions(
|
|
716
826
|
modelId,
|
|
717
827
|
prompt,
|
|
828
|
+
{ permissionMode },
|
|
718
829
|
typeof (options as ClaudeCodeStreamOptions | undefined)?.extensionUIContext === "object"
|
|
719
830
|
? {
|
|
720
831
|
onElicitation: createClaudeCodeElicitationHandler(
|
|
@@ -746,7 +857,17 @@ async function pumpSdkMessages(
|
|
|
746
857
|
stream.push({ type: "start", partial: initialPartial });
|
|
747
858
|
|
|
748
859
|
for await (const msg of queryResult as AsyncIterable<SDKMessage>) {
|
|
749
|
-
if (options?.signal?.aborted)
|
|
860
|
+
if (options?.signal?.aborted) {
|
|
861
|
+
// User-initiated cancel — emit an aborted error so the agent
|
|
862
|
+
// loop classifies this as a deliberate stop, not a transient
|
|
863
|
+
// provider failure that should be retried.
|
|
864
|
+
stream.push({
|
|
865
|
+
type: "error",
|
|
866
|
+
reason: "aborted",
|
|
867
|
+
error: makeAbortedMessage(modelId, lastTextContent),
|
|
868
|
+
});
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
750
871
|
|
|
751
872
|
switch (msg.type) {
|
|
752
873
|
// -- Init --
|
|
@@ -857,6 +978,16 @@ async function pumpSdkMessages(
|
|
|
857
978
|
// events for proper TUI rendering, followed by the text response.
|
|
858
979
|
const finalContent: AssistantMessage["content"] = [];
|
|
859
980
|
|
|
981
|
+
// If the final turn ended without a synthetic user message
|
|
982
|
+
// (e.g. stop_reason: "tool_use" followed directly by result,
|
|
983
|
+
// or a turn with text but no tool execution), the `builder`
|
|
984
|
+
// still holds toolCall blocks that were never pushed into
|
|
985
|
+
// `intermediateToolBlocks`. Fold them in here so they aren't
|
|
986
|
+
// dropped from the final AssistantMessage.
|
|
987
|
+
if (builder) {
|
|
988
|
+
mergePendingToolCalls(intermediateToolBlocks, builder.message.content);
|
|
989
|
+
}
|
|
990
|
+
|
|
860
991
|
// Add tool calls from intermediate turns first (renders above text)
|
|
861
992
|
attachExternalResultsToToolBlocks(intermediateToolBlocks, toolResultsById);
|
|
862
993
|
finalContent.push(...intermediateToolBlocks);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, test } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { PartialMessageBuilder } from "../partial-builder.ts";
|
|
4
|
-
import type { BetaRawMessageStreamEvent } from "../sdk-types.ts";
|
|
3
|
+
import { mapContentBlock, parseMcpToolName, PartialMessageBuilder } from "../partial-builder.ts";
|
|
4
|
+
import type { BetaContentBlock, BetaRawMessageStreamEvent } from "../sdk-types.ts";
|
|
5
5
|
|
|
6
6
|
describe("PartialMessageBuilder — malformed tool arguments (#2574)", () => {
|
|
7
7
|
/**
|
|
@@ -148,3 +148,92 @@ describe("PartialMessageBuilder — malformed tool arguments (#2574)", () => {
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
|
+
|
|
152
|
+
describe("parseMcpToolName", () => {
|
|
153
|
+
test("splits mcp__<server>__<tool> into parts", () => {
|
|
154
|
+
assert.deepEqual(
|
|
155
|
+
parseMcpToolName("mcp__gsd-workflow__gsd_plan_milestone"),
|
|
156
|
+
{ server: "gsd-workflow", tool: "gsd_plan_milestone" },
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("preserves server names containing hyphens", () => {
|
|
161
|
+
assert.deepEqual(
|
|
162
|
+
parseMcpToolName("mcp__my-cool-server__do_thing"),
|
|
163
|
+
{ server: "my-cool-server", tool: "do_thing" },
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("preserves tool names containing underscores", () => {
|
|
168
|
+
assert.deepEqual(
|
|
169
|
+
parseMcpToolName("mcp__srv__a_b_c_d"),
|
|
170
|
+
{ server: "srv", tool: "a_b_c_d" },
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("returns null for non-prefixed names", () => {
|
|
175
|
+
assert.equal(parseMcpToolName("Bash"), null);
|
|
176
|
+
assert.equal(parseMcpToolName("gsd_plan_milestone"), null);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("returns null for malformed prefixes", () => {
|
|
180
|
+
assert.equal(parseMcpToolName("mcp__"), null);
|
|
181
|
+
assert.equal(parseMcpToolName("mcp__server"), null);
|
|
182
|
+
assert.equal(parseMcpToolName("mcp__server__"), null);
|
|
183
|
+
assert.equal(parseMcpToolName("mcp____tool"), null);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("PartialMessageBuilder — MCP tool name normalization", () => {
|
|
188
|
+
test("strips mcp__<server>__ prefix on content_block_start", () => {
|
|
189
|
+
const builder = new PartialMessageBuilder("claude-sonnet-4-20250514");
|
|
190
|
+
const event = builder.handleEvent({
|
|
191
|
+
type: "content_block_start",
|
|
192
|
+
index: 0,
|
|
193
|
+
content_block: {
|
|
194
|
+
type: "tool_use",
|
|
195
|
+
id: "tool_1",
|
|
196
|
+
name: "mcp__gsd-workflow__gsd_plan_milestone",
|
|
197
|
+
input: {},
|
|
198
|
+
},
|
|
199
|
+
} as BetaRawMessageStreamEvent);
|
|
200
|
+
|
|
201
|
+
assert.ok(event, "event should not be null");
|
|
202
|
+
assert.equal(event!.type, "toolcall_start");
|
|
203
|
+
if (event!.type === "toolcall_start") {
|
|
204
|
+
const toolCall = (event.partial.content[event.contentIndex] as any);
|
|
205
|
+
assert.equal(toolCall.name, "gsd_plan_milestone");
|
|
206
|
+
assert.equal(toolCall.mcpServer, "gsd-workflow");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("leaves non-MCP tool names untouched", () => {
|
|
211
|
+
const builder = new PartialMessageBuilder("claude-sonnet-4-20250514");
|
|
212
|
+
const event = builder.handleEvent({
|
|
213
|
+
type: "content_block_start",
|
|
214
|
+
index: 0,
|
|
215
|
+
content_block: { type: "tool_use", id: "tool_1", name: "Bash", input: {} },
|
|
216
|
+
} as BetaRawMessageStreamEvent);
|
|
217
|
+
|
|
218
|
+
assert.ok(event);
|
|
219
|
+
if (event!.type === "toolcall_start") {
|
|
220
|
+
const toolCall = (event.partial.content[event.contentIndex] as any);
|
|
221
|
+
assert.equal(toolCall.name, "Bash");
|
|
222
|
+
assert.equal(toolCall.mcpServer, undefined);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("mapContentBlock strips MCP prefix on full tool_use blocks", () => {
|
|
227
|
+
const block: BetaContentBlock = {
|
|
228
|
+
type: "tool_use",
|
|
229
|
+
id: "tool_2",
|
|
230
|
+
name: "mcp__gsd-workflow__gsd_task_complete",
|
|
231
|
+
input: { taskId: "T001" },
|
|
232
|
+
};
|
|
233
|
+
const mapped = mapContentBlock(block) as any;
|
|
234
|
+
assert.equal(mapped.type, "toolCall");
|
|
235
|
+
assert.equal(mapped.name, "gsd_task_complete");
|
|
236
|
+
assert.equal(mapped.mcpServer, "gsd-workflow");
|
|
237
|
+
assert.deepEqual(mapped.arguments, { taskId: "T001" });
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -6,6 +6,9 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import {
|
|
7
7
|
makeStreamExhaustedErrorMessage,
|
|
8
8
|
getResultErrorMessage,
|
|
9
|
+
makeAbortedMessage,
|
|
10
|
+
mergePendingToolCalls,
|
|
11
|
+
resolveClaudePermissionMode,
|
|
9
12
|
buildPromptFromContext,
|
|
10
13
|
buildSdkOptions,
|
|
11
14
|
createClaudeCodeElicitationHandler,
|
|
@@ -16,7 +19,7 @@ import {
|
|
|
16
19
|
parseClaudeLookupOutput,
|
|
17
20
|
roundResultToElicitationContent,
|
|
18
21
|
} from "../stream-adapter.ts";
|
|
19
|
-
import type { Context, Message } from "@gsd/pi-ai";
|
|
22
|
+
import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
|
|
20
23
|
import type { SDKUserMessage } from "../sdk-types.ts";
|
|
21
24
|
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
@@ -164,6 +167,98 @@ describe("stream-adapter — full context prompt (#2859)", () => {
|
|
|
164
167
|
});
|
|
165
168
|
});
|
|
166
169
|
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Bug #4102 — transcript fabrication regression tests
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
describe("stream-adapter — no transcript fabrication (#4102)", () => {
|
|
175
|
+
test("buildPromptFromContext never emits forbidden [User]/[Assistant] bracket headers", () => {
|
|
176
|
+
const context: Context = {
|
|
177
|
+
systemPrompt: "You are a helpful assistant.",
|
|
178
|
+
messages: [
|
|
179
|
+
{ role: "user", content: "First" } as Message,
|
|
180
|
+
{
|
|
181
|
+
role: "assistant",
|
|
182
|
+
content: [{ type: "text", text: "Second" }],
|
|
183
|
+
api: "anthropic-messages",
|
|
184
|
+
provider: "claude-code",
|
|
185
|
+
model: "claude-sonnet-4-20250514",
|
|
186
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
187
|
+
stopReason: "stop",
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
} as Message,
|
|
190
|
+
{ role: "user", content: "Third" } as Message,
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const prompt = buildPromptFromContext(context);
|
|
195
|
+
|
|
196
|
+
assert.ok(!prompt.includes("[User]"), "prompt must not include literal [User] bracket header");
|
|
197
|
+
assert.ok(!prompt.includes("[Assistant]"), "prompt must not include literal [Assistant] bracket header");
|
|
198
|
+
assert.ok(!prompt.includes("[System]"), "prompt must not include literal [System] bracket header");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("buildPromptFromContext wraps history in XML-tag structure", () => {
|
|
202
|
+
const context: Context = {
|
|
203
|
+
systemPrompt: "You are helpful.",
|
|
204
|
+
messages: [
|
|
205
|
+
{ role: "user", content: "Hello" } as Message,
|
|
206
|
+
{
|
|
207
|
+
role: "assistant",
|
|
208
|
+
content: [{ type: "text", text: "Hi there" }],
|
|
209
|
+
api: "anthropic-messages",
|
|
210
|
+
provider: "claude-code",
|
|
211
|
+
model: "claude-sonnet-4-20250514",
|
|
212
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
213
|
+
stopReason: "stop",
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
} as Message,
|
|
216
|
+
],
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const prompt = buildPromptFromContext(context);
|
|
220
|
+
|
|
221
|
+
assert.ok(prompt.includes("<conversation_history>"), "prompt must wrap history in <conversation_history>");
|
|
222
|
+
assert.ok(prompt.includes("</conversation_history>"), "prompt must close <conversation_history>");
|
|
223
|
+
assert.ok(prompt.includes("<user_message>\nHello\n</user_message>"), "user turn must use <user_message> tags");
|
|
224
|
+
assert.ok(prompt.includes("<assistant_message>\nHi there\n</assistant_message>"), "assistant turn must use <assistant_message> tags");
|
|
225
|
+
assert.ok(prompt.includes("<prior_system_context>\nYou are helpful.\n</prior_system_context>"), "system prompt must use <prior_system_context> tags");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("buildPromptFromContext includes a do-not-echo-tags directive as primary instruction", () => {
|
|
229
|
+
const context: Context = {
|
|
230
|
+
messages: [{ role: "user", content: "Anything" } as Message],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const prompt = buildPromptFromContext(context);
|
|
234
|
+
|
|
235
|
+
assert.ok(
|
|
236
|
+
prompt.startsWith("Respond only to the final user message"),
|
|
237
|
+
"primary directive must lead the prompt",
|
|
238
|
+
);
|
|
239
|
+
assert.ok(prompt.includes("Do not emit <user_message>"), "directive must forbid emitting user_message tag");
|
|
240
|
+
assert.ok(prompt.includes("<assistant_message>"), "directive must mention assistant_message tag");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("buildPromptFromContext omits <conversation_history> when there are no messages but a system prompt", () => {
|
|
244
|
+
const context: Context = {
|
|
245
|
+
systemPrompt: "Seed",
|
|
246
|
+
messages: [],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const prompt = buildPromptFromContext(context);
|
|
250
|
+
|
|
251
|
+
assert.ok(prompt.includes("<prior_system_context>"), "system prompt must still render");
|
|
252
|
+
assert.ok(!prompt.includes("<conversation_history>"), "no history wrapper when messages are empty");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("buildPromptFromContext still returns empty string when context is entirely empty", () => {
|
|
256
|
+
const context: Context = { messages: [] };
|
|
257
|
+
const prompt = buildPromptFromContext(context);
|
|
258
|
+
assert.equal(prompt, "", "empty context must not emit a bare directive");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
167
262
|
describe("stream-adapter — Claude Code external tool results", () => {
|
|
168
263
|
test("extractToolResultsFromSdkUserMessage maps tool_result content to tool payloads", () => {
|
|
169
264
|
const message: SDKUserMessage = {
|
|
@@ -274,6 +369,16 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
274
369
|
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
275
370
|
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
276
371
|
assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
|
|
372
|
+
assert.deepEqual(options.allowedTools, [
|
|
373
|
+
"Read",
|
|
374
|
+
"Write",
|
|
375
|
+
"Edit",
|
|
376
|
+
"Glob",
|
|
377
|
+
"Grep",
|
|
378
|
+
"Bash(ls:*)",
|
|
379
|
+
"Bash(pwd)",
|
|
380
|
+
"mcp__gsd-workflow__*",
|
|
381
|
+
]);
|
|
277
382
|
} finally {
|
|
278
383
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
279
384
|
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
@@ -302,6 +407,16 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
302
407
|
const mcpServers = options.mcpServers as Record<string, any>;
|
|
303
408
|
assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
|
|
304
409
|
assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]);
|
|
410
|
+
assert.deepEqual(options.allowedTools, [
|
|
411
|
+
"Read",
|
|
412
|
+
"Write",
|
|
413
|
+
"Edit",
|
|
414
|
+
"Glob",
|
|
415
|
+
"Grep",
|
|
416
|
+
"Bash(ls:*)",
|
|
417
|
+
"Bash(pwd)",
|
|
418
|
+
"mcp__custom-workflow__*",
|
|
419
|
+
]);
|
|
305
420
|
} finally {
|
|
306
421
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
307
422
|
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
@@ -414,7 +529,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
414
529
|
delete process.env.GSD_WORKFLOW_MCP_ARGS;
|
|
415
530
|
delete process.env.GSD_WORKFLOW_MCP_ENV;
|
|
416
531
|
delete process.env.GSD_WORKFLOW_MCP_CWD;
|
|
417
|
-
const options = buildSdkOptions("claude-sonnet-4-20250514", "test", { onElicitation });
|
|
532
|
+
const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { onElicitation });
|
|
418
533
|
assert.equal(options.onElicitation, onElicitation);
|
|
419
534
|
} finally {
|
|
420
535
|
process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND;
|
|
@@ -680,6 +795,134 @@ describe("stream-adapter — MCP elicitation bridge", () => {
|
|
|
680
795
|
});
|
|
681
796
|
});
|
|
682
797
|
|
|
798
|
+
// ---------------------------------------------------------------------------
|
|
799
|
+
// F2 — abort vs stream-exhausted classification
|
|
800
|
+
// ---------------------------------------------------------------------------
|
|
801
|
+
|
|
802
|
+
describe("stream-adapter — abort classification (F2)", () => {
|
|
803
|
+
test("makeAbortedMessage sets stopReason to 'aborted', not 'error'", () => {
|
|
804
|
+
const message = makeAbortedMessage("claude-sonnet-4-6", "");
|
|
805
|
+
assert.equal(message.stopReason, "aborted");
|
|
806
|
+
assert.equal(message.errorMessage, undefined);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("makeAbortedMessage preserves last-seen text content", () => {
|
|
810
|
+
const message = makeAbortedMessage("claude-sonnet-4-6", "partial mid-stream text");
|
|
811
|
+
assert.deepEqual(message.content, [{ type: "text", text: "partial mid-stream text" }]);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
test("aborted message is distinguishable from stream-exhausted error", () => {
|
|
815
|
+
const aborted = makeAbortedMessage("claude-sonnet-4-6", "");
|
|
816
|
+
const exhausted = makeStreamExhaustedErrorMessage("claude-sonnet-4-6", "");
|
|
817
|
+
assert.notEqual(aborted.stopReason, exhausted.stopReason);
|
|
818
|
+
assert.equal(exhausted.errorMessage, "stream_exhausted_without_result");
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// ---------------------------------------------------------------------------
|
|
823
|
+
// F3 — final-turn tool calls not dropped
|
|
824
|
+
// ---------------------------------------------------------------------------
|
|
825
|
+
|
|
826
|
+
describe("stream-adapter — final-turn tool-call merge (F3)", () => {
|
|
827
|
+
function toolCall(id: string, name = "bash"): AssistantMessage["content"][number] {
|
|
828
|
+
return { type: "toolCall", id, name, arguments: {} };
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
test("mergePendingToolCalls appends tool calls not already in intermediate", () => {
|
|
832
|
+
const intermediate: AssistantMessage["content"] = [toolCall("tool-1")];
|
|
833
|
+
const pending: AssistantMessage["content"] = [
|
|
834
|
+
toolCall("tool-2"),
|
|
835
|
+
{ type: "text", text: "trailing text" },
|
|
836
|
+
];
|
|
837
|
+
const merged = mergePendingToolCalls(intermediate, pending);
|
|
838
|
+
assert.equal(merged.length, 2);
|
|
839
|
+
assert.equal((merged[0] as any).id, "tool-1");
|
|
840
|
+
assert.equal((merged[1] as any).id, "tool-2");
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("mergePendingToolCalls is idempotent across duplicate ids", () => {
|
|
844
|
+
const intermediate: AssistantMessage["content"] = [toolCall("tool-1")];
|
|
845
|
+
const pending: AssistantMessage["content"] = [toolCall("tool-1"), toolCall("tool-2")];
|
|
846
|
+
const merged = mergePendingToolCalls(intermediate, pending);
|
|
847
|
+
assert.equal(merged.length, 2);
|
|
848
|
+
assert.deepEqual(
|
|
849
|
+
merged.map((b) => (b as any).id),
|
|
850
|
+
["tool-1", "tool-2"],
|
|
851
|
+
);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("mergePendingToolCalls ignores non-toolCall blocks from pending", () => {
|
|
855
|
+
const intermediate: AssistantMessage["content"] = [];
|
|
856
|
+
const pending: AssistantMessage["content"] = [
|
|
857
|
+
{ type: "text", text: "hello" },
|
|
858
|
+
{ type: "thinking", thinking: "pondering" },
|
|
859
|
+
toolCall("tool-1"),
|
|
860
|
+
];
|
|
861
|
+
const merged = mergePendingToolCalls(intermediate, pending);
|
|
862
|
+
assert.equal(merged.length, 1);
|
|
863
|
+
assert.equal((merged[0] as any).id, "tool-1");
|
|
864
|
+
});
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// ---------------------------------------------------------------------------
|
|
868
|
+
// F10 — permission mode is configurable
|
|
869
|
+
// ---------------------------------------------------------------------------
|
|
870
|
+
|
|
871
|
+
describe("stream-adapter — permission mode (F10)", () => {
|
|
872
|
+
// Earlier tests in this file set GSD_WORKFLOW_MCP_* env vars and restore
|
|
873
|
+
// them by reassigning from `prev.*`. When `prev.*` was undefined, node
|
|
874
|
+
// coerces the assignment to the literal string "undefined", which then
|
|
875
|
+
// fails JSON.parse inside buildWorkflowMcpServers. Clear the relevant
|
|
876
|
+
// slots before each permission-mode test so buildSdkOptions doesn't throw.
|
|
877
|
+
function clearWorkflowMcpEnv(): void {
|
|
878
|
+
for (const key of [
|
|
879
|
+
"GSD_WORKFLOW_MCP_COMMAND",
|
|
880
|
+
"GSD_WORKFLOW_MCP_NAME",
|
|
881
|
+
"GSD_WORKFLOW_MCP_ARGS",
|
|
882
|
+
"GSD_WORKFLOW_MCP_ENV",
|
|
883
|
+
"GSD_WORKFLOW_MCP_CWD",
|
|
884
|
+
]) {
|
|
885
|
+
if (process.env[key] === undefined || process.env[key] === "undefined") {
|
|
886
|
+
delete process.env[key];
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
test("buildSdkOptions defaults to bypassPermissions for backwards compatibility", () => {
|
|
892
|
+
clearWorkflowMcpEnv();
|
|
893
|
+
const opts = buildSdkOptions("claude-sonnet-4-6", "test");
|
|
894
|
+
assert.equal(opts.permissionMode, "bypassPermissions");
|
|
895
|
+
assert.equal(opts.allowDangerouslySkipPermissions, true);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
test("buildSdkOptions respects explicit acceptEdits override", () => {
|
|
899
|
+
clearWorkflowMcpEnv();
|
|
900
|
+
const opts = buildSdkOptions("claude-sonnet-4-6", "test", { permissionMode: "acceptEdits" });
|
|
901
|
+
assert.equal(opts.permissionMode, "acceptEdits");
|
|
902
|
+
assert.equal(
|
|
903
|
+
opts.allowDangerouslySkipPermissions,
|
|
904
|
+
false,
|
|
905
|
+
"allowDangerouslySkipPermissions must be false for non-bypass modes",
|
|
906
|
+
);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
test("resolveClaudePermissionMode honours the GSD_CLAUDE_CODE_PERMISSION_MODE env override", async () => {
|
|
910
|
+
const env = { GSD_CLAUDE_CODE_PERMISSION_MODE: "acceptEdits" } as NodeJS.ProcessEnv;
|
|
911
|
+
const mode = await resolveClaudePermissionMode(env);
|
|
912
|
+
assert.equal(mode, "acceptEdits");
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
test("resolveClaudePermissionMode rejects unknown override values (fallback path)", async () => {
|
|
916
|
+
const env = { GSD_CLAUDE_CODE_PERMISSION_MODE: "nonsense" } as NodeJS.ProcessEnv;
|
|
917
|
+
const mode = await resolveClaudePermissionMode(env);
|
|
918
|
+
// Unknown override falls back to auto-detect → either bypass or acceptEdits
|
|
919
|
+
assert.ok(
|
|
920
|
+
mode === "bypassPermissions" || mode === "acceptEdits",
|
|
921
|
+
`expected bypass or acceptEdits, got ${mode}`,
|
|
922
|
+
);
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
|
|
683
926
|
describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
|
|
684
927
|
test("getClaudeLookupCommand uses where on Windows", () => {
|
|
685
928
|
assert.equal(getClaudeLookupCommand("win32"), "where claude");
|