gsd-pi 2.77.0-dev.c9c8932c6 → 2.77.0-dev.cfd69e714
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 +1 -1
- package/dist/claude-cli-check.js +5 -1
- package/dist/cli-web-branch.d.ts +1 -0
- package/dist/cli-web-branch.js +3 -0
- package/dist/cli.js +24 -2
- package/dist/headless.js +49 -4
- package/dist/provider-migrations.d.ts +18 -0
- package/dist/provider-migrations.js +14 -0
- package/dist/resource-loader.d.ts +40 -0
- package/dist/resource-loader.js +32 -13
- package/dist/resources/extensions/browser-tools/capture.js +9 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
- package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
- package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +495 -27
- package/dist/resources/extensions/github-sync/templates.js +103 -0
- package/dist/resources/extensions/google-search/index.js +3 -2
- package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
- package/dist/resources/extensions/gsd/auto/loop.js +124 -2
- package/dist/resources/extensions/gsd/auto/phases.js +57 -39
- package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto/session.js +6 -2
- package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +166 -29
- package/dist/resources/extensions/gsd/auto-loop.js +1 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +181 -64
- package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
- package/dist/resources/extensions/gsd/auto-recovery.js +197 -48
- package/dist/resources/extensions/gsd/auto-start.js +107 -29
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +180 -34
- package/dist/resources/extensions/gsd/auto.js +80 -21
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
- package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
- package/dist/resources/extensions/gsd/component-loader.js +447 -0
- package/dist/resources/extensions/gsd/component-types.js +69 -0
- package/dist/resources/extensions/gsd/context-store.js +23 -7
- package/dist/resources/extensions/gsd/detection.js +49 -1
- package/dist/resources/extensions/gsd/dispatch-guard.js +2 -17
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/dist/resources/extensions/gsd/file-lock.js +49 -9
- package/dist/resources/extensions/gsd/forensics.js +106 -0
- package/dist/resources/extensions/gsd/gate-registry.js +2 -2
- package/dist/resources/extensions/gsd/git-constants.js +28 -1
- package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
- package/dist/resources/extensions/gsd/git-service.js +127 -2
- package/dist/resources/extensions/gsd/gitignore.js +1 -0
- package/dist/resources/extensions/gsd/gsd-db.js +6 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
- package/dist/resources/extensions/gsd/guided-flow.js +39 -13
- package/dist/resources/extensions/gsd/journal.js +17 -2
- package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
- package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
- package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
- package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
- package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
- package/dist/resources/extensions/gsd/model-router.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
- package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/dist/resources/extensions/gsd/reports.js +5 -4
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
- package/dist/resources/extensions/gsd/service-tier.js +5 -2
- package/dist/resources/extensions/gsd/session-lock.js +19 -10
- package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
- package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
- package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
- package/dist/resources/extensions/gsd/state.js +69 -58
- package/dist/resources/extensions/gsd/sync-lock.js +98 -42
- package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
- package/dist/resources/extensions/gsd/uok/audit.js +18 -2
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
- package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
- package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
- package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
- package/dist/resources/extensions/gsd/uok/writer.js +82 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +86 -8
- package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
- package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
- package/dist/resources/extensions/mcp-client/auth.js +10 -1
- package/dist/resources/extensions/mcp-client/index.js +121 -10
- package/dist/resources/extensions/ollama/index.js +5 -1
- package/dist/resources/extensions/remote-questions/manager.js +11 -5
- package/dist/resources/skills/create-skill/SKILL.md +2 -2
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
- package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- 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/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-1832629448831fdc.js → webpack-2e68521d7c82f7c2.js} +1 -1
- package/package.json +4 -5
- package/packages/daemon/src/logger.ts +4 -3
- package/packages/mcp-server/README.md +3 -3
- package/packages/mcp-server/dist/env-writer.d.ts +1 -0
- package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
- package/packages/mcp-server/dist/env-writer.js +74 -6
- package/packages/mcp-server/dist/env-writer.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +24 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +111 -87
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +15 -6
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/env-writer.test.ts +79 -1
- package/packages/mcp-server/src/env-writer.ts +76 -6
- package/packages/mcp-server/src/mcp-server.test.ts +25 -3
- package/packages/mcp-server/src/readers/graph.test.ts +87 -15
- package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
- package/packages/mcp-server/src/server.ts +158 -105
- package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
- package/packages/mcp-server/src/workflow-tools.ts +19 -6
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
- package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
- package/packages/native/src/__tests__/ps.test.mjs +14 -8
- package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
- package/packages/native/src/__tests__/truncate.test.mjs +17 -2
- package/packages/pi-agent-core/src/agent-loop.test.ts +5 -15
- package/packages/pi-agent-core/src/agent.test.ts +96 -102
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/capability-patches.js +9 -2
- package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
- package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
- package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/generated/openai.js +17 -0
- package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.test.js +43 -70
- package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +36 -11
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +44 -0
- package/packages/pi-ai/src/models/capability-patches.ts +10 -2
- package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
- package/packages/pi-ai/src/models/generated/openai.ts +17 -0
- package/packages/pi-ai/src/models.generated.test.ts +46 -73
- package/packages/pi-ai/src/models.test.ts +48 -11
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
- package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
- package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +21 -16
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
- package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +61 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +30 -4
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +17 -0
- package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
- package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
- package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
- package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -2
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -0
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
- 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/dynamic-border.test.js +20 -13
- package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -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 +31 -9
- 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 +30 -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/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +125 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
- package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
- package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
- package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
- package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +24 -17
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +58 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +35 -4
- package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
- package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
- package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
- package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
- package/packages/pi-coding-agent/src/core/system-prompt.ts +35 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +146 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
- package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js +18 -8
- package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
- package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +51 -6
- package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
- package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
- package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
- package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
- package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +14 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +19 -0
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/dist/components/image.test.js +6 -5
- package/packages/pi-tui/dist/components/image.test.js.map +1 -1
- package/packages/pi-tui/dist/editor-component.d.ts +2 -0
- package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/packages/pi-tui/dist/editor-component.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +20 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/src/__tests__/autocomplete.test.ts +24 -8
- package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
- package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +62 -6
- package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
- package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
- package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
- package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
- package/packages/pi-tui/src/components/editor.ts +22 -0
- package/packages/pi-tui/src/components/image.test.ts +10 -5
- package/packages/pi-tui/src/editor-component.ts +3 -0
- package/packages/pi-tui/src/stdin-buffer.ts +26 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/dist/rpc-client.test.js +101 -51
- package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
- package/packages/rpc-client/src/rpc-client.test.ts +109 -52
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/scripts/install.js +15 -1
- package/src/resources/extensions/browser-tools/capture.ts +12 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
- package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
- package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
- package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
- package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +532 -29
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +941 -91
- package/src/resources/extensions/github-sync/templates.ts +151 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
- package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
- package/src/resources/extensions/google-search/index.ts +3 -2
- package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
- package/src/resources/extensions/gsd/auto/loop.ts +142 -2
- package/src/resources/extensions/gsd/auto/phases.ts +62 -38
- package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
- package/src/resources/extensions/gsd/auto/run-unit.ts +16 -2
- package/src/resources/extensions/gsd/auto/session.ts +7 -2
- package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +176 -29
- package/src/resources/extensions/gsd/auto-loop.ts +1 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +193 -73
- package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
- package/src/resources/extensions/gsd/auto-recovery.ts +230 -51
- package/src/resources/extensions/gsd/auto-start.ts +127 -9
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +190 -31
- package/src/resources/extensions/gsd/auto.ts +95 -23
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
- package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
- package/src/resources/extensions/gsd/component-loader.ts +598 -0
- package/src/resources/extensions/gsd/component-types.ts +362 -0
- package/src/resources/extensions/gsd/context-store.ts +25 -8
- package/src/resources/extensions/gsd/detection.ts +58 -1
- package/src/resources/extensions/gsd/dispatch-guard.ts +2 -20
- package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
- package/src/resources/extensions/gsd/file-lock.ts +84 -11
- package/src/resources/extensions/gsd/forensics.ts +118 -1
- package/src/resources/extensions/gsd/gate-registry.ts +2 -2
- package/src/resources/extensions/gsd/git-constants.ts +30 -1
- package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
- package/src/resources/extensions/gsd/git-service.ts +150 -2
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/gsd-db.ts +6 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
- package/src/resources/extensions/gsd/guided-flow.ts +57 -14
- package/src/resources/extensions/gsd/journal.ts +38 -3
- package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
- package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
- package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
- package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
- package/src/resources/extensions/gsd/model-router.ts +6 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
- package/src/resources/extensions/gsd/reports.ts +5 -4
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
- package/src/resources/extensions/gsd/service-tier.ts +5 -2
- package/src/resources/extensions/gsd/session-lock.ts +20 -10
- package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
- package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
- package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
- package/src/resources/extensions/gsd/state.ts +76 -66
- package/src/resources/extensions/gsd/sync-lock.ts +97 -39
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
- package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -285
- package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
- package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +5 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
- package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +14 -4
- package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
- package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
- package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
- package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +54 -41
- package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +213 -0
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
- package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
- package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
- package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
- package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
- package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
- package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
- package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
- package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
- package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
- package/src/resources/extensions/gsd/types.ts +3 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
- package/src/resources/extensions/gsd/uok/audit.ts +20 -2
- package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
- package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
- package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
- package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
- package/src/resources/extensions/gsd/uok/writer.ts +113 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +22 -3
- package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +109 -7
- package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
- package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
- package/src/resources/extensions/mcp-client/auth.ts +12 -1
- package/src/resources/extensions/mcp-client/index.ts +132 -11
- package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
- package/src/resources/extensions/ollama/index.ts +5 -1
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
- package/src/resources/extensions/remote-questions/manager.ts +36 -4
- package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
- package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
- package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
- package/src/resources/skills/create-skill/SKILL.md +2 -2
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
- package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
- package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
- package/dist/web/standalone/.next/static/chunks/2826.d445fb428ef41fa1.js +0 -9
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
- package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
- package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
- package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -157
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
- package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
- package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
- package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
- package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
- package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
- /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → SvCJDZPQW104bR1KnBQg1}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{H51SzzS_EEgdBsDVhrLBv → SvCJDZPQW104bR1KnBQg1}/_ssgManifest.js +0 -0
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
buildPromptFromContext,
|
|
14
14
|
buildSdkQueryPrompt,
|
|
15
15
|
buildSdkOptions,
|
|
16
|
+
createClaudeCodeCanUseToolHandler,
|
|
17
|
+
buildBashPermissionPattern,
|
|
18
|
+
buildBashPermissionPatternOptions,
|
|
19
|
+
bashCommandMatchesSavedRules,
|
|
16
20
|
createClaudeCodeElicitationHandler,
|
|
17
21
|
extractImageBlocksFromContext,
|
|
18
22
|
extractToolResultsFromSdkUserMessage,
|
|
@@ -20,11 +24,58 @@ import {
|
|
|
20
24
|
parseAskUserQuestionsElicitation,
|
|
21
25
|
parseTextInputElicitation,
|
|
22
26
|
parseClaudeLookupOutput,
|
|
27
|
+
resolveBundledClaudeCliPath,
|
|
28
|
+
normalizeClaudePathForSdk,
|
|
23
29
|
roundResultToElicitationContent,
|
|
24
30
|
} from "../stream-adapter.ts";
|
|
25
31
|
import type { AssistantMessage, Context, Message } from "@gsd/pi-ai";
|
|
26
32
|
import type { SDKUserMessage } from "../sdk-types.ts";
|
|
27
33
|
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Env helpers — `GSD_WORKFLOW_MCP_*` save/restore
|
|
36
|
+
//
|
|
37
|
+
// The naive pattern `process.env.X = prev.X` breaks when `prev.X` is
|
|
38
|
+
// undefined: Node coerces the assignment to the literal string
|
|
39
|
+
// "undefined", which then pollutes subsequent tests that read the var
|
|
40
|
+
// and assume it's absent. Issue #4808 documents the resulting bleed.
|
|
41
|
+
//
|
|
42
|
+
// `setWorkflowMcpEnv` returns a `restore()` closure that either
|
|
43
|
+
// re-assigns the previous string value OR `delete`s the key when the
|
|
44
|
+
// original was absent. Call in a try/finally; restore in the finally.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const WORKFLOW_MCP_ENV_KEYS = [
|
|
48
|
+
"GSD_WORKFLOW_MCP_COMMAND",
|
|
49
|
+
"GSD_WORKFLOW_MCP_NAME",
|
|
50
|
+
"GSD_WORKFLOW_MCP_ARGS",
|
|
51
|
+
"GSD_WORKFLOW_MCP_ENV",
|
|
52
|
+
"GSD_WORKFLOW_MCP_CWD",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
type WorkflowMcpEnvKey = (typeof WORKFLOW_MCP_ENV_KEYS)[number];
|
|
56
|
+
|
|
57
|
+
function setWorkflowMcpEnv(
|
|
58
|
+
values: Partial<Record<WorkflowMcpEnvKey, string>>,
|
|
59
|
+
): () => void {
|
|
60
|
+
const prev: Partial<Record<WorkflowMcpEnvKey, string | undefined>> = {};
|
|
61
|
+
for (const key of WORKFLOW_MCP_ENV_KEYS) {
|
|
62
|
+
prev[key] = process.env[key];
|
|
63
|
+
}
|
|
64
|
+
for (const [key, value] of Object.entries(values)) {
|
|
65
|
+
process.env[key] = value;
|
|
66
|
+
}
|
|
67
|
+
return function restore() {
|
|
68
|
+
for (const key of WORKFLOW_MCP_ENV_KEYS) {
|
|
69
|
+
const previous = prev[key];
|
|
70
|
+
if (previous === undefined) {
|
|
71
|
+
delete process.env[key];
|
|
72
|
+
} else {
|
|
73
|
+
process.env[key] = previous;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
28
79
|
// ---------------------------------------------------------------------------
|
|
29
80
|
// Existing tests — exhausted stream fallback (#2575)
|
|
30
81
|
// ---------------------------------------------------------------------------
|
|
@@ -737,19 +788,14 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
737
788
|
});
|
|
738
789
|
|
|
739
790
|
test("buildSdkOptions includes workflow MCP server config when env is set", () => {
|
|
740
|
-
const
|
|
741
|
-
GSD_WORKFLOW_MCP_COMMAND:
|
|
742
|
-
GSD_WORKFLOW_MCP_NAME:
|
|
743
|
-
GSD_WORKFLOW_MCP_ARGS:
|
|
744
|
-
GSD_WORKFLOW_MCP_ENV:
|
|
745
|
-
GSD_WORKFLOW_MCP_CWD:
|
|
746
|
-
};
|
|
791
|
+
const restore = setWorkflowMcpEnv({
|
|
792
|
+
GSD_WORKFLOW_MCP_COMMAND: "node",
|
|
793
|
+
GSD_WORKFLOW_MCP_NAME: "gsd-workflow",
|
|
794
|
+
GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
|
|
795
|
+
GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
|
|
796
|
+
GSD_WORKFLOW_MCP_CWD: "/tmp/project",
|
|
797
|
+
});
|
|
747
798
|
try {
|
|
748
|
-
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
749
|
-
process.env.GSD_WORKFLOW_MCP_NAME = "gsd-workflow";
|
|
750
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
|
|
751
|
-
process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
|
|
752
|
-
process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
|
|
753
799
|
|
|
754
800
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
755
801
|
const mcpServers = options.mcpServers as Record<string, any>;
|
|
@@ -761,72 +807,63 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
761
807
|
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
762
808
|
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
763
809
|
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
|
|
764
|
-
assert.deepEqual(options.disallowedTools, [
|
|
810
|
+
assert.deepEqual(options.disallowedTools, []);
|
|
765
811
|
assert.deepEqual(options.allowedTools, [
|
|
766
812
|
"Read",
|
|
767
813
|
"Write",
|
|
768
814
|
"Edit",
|
|
769
815
|
"Glob",
|
|
770
816
|
"Grep",
|
|
771
|
-
"Bash
|
|
772
|
-
"
|
|
817
|
+
"Bash",
|
|
818
|
+
"Agent",
|
|
819
|
+
"WebFetch",
|
|
820
|
+
"WebSearch",
|
|
821
|
+
"AskUserQuestion",
|
|
773
822
|
"mcp__gsd-workflow__*",
|
|
774
823
|
]);
|
|
775
824
|
} finally {
|
|
776
|
-
|
|
777
|
-
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
778
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
|
|
779
|
-
process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
|
|
780
|
-
process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
|
|
825
|
+
restore();
|
|
781
826
|
}
|
|
782
827
|
});
|
|
783
828
|
|
|
784
|
-
test("buildSdkOptions
|
|
785
|
-
const
|
|
786
|
-
GSD_WORKFLOW_MCP_COMMAND:
|
|
787
|
-
GSD_WORKFLOW_MCP_NAME:
|
|
788
|
-
GSD_WORKFLOW_MCP_ARGS:
|
|
789
|
-
GSD_WORKFLOW_MCP_ENV:
|
|
790
|
-
GSD_WORKFLOW_MCP_CWD:
|
|
791
|
-
};
|
|
829
|
+
test("buildSdkOptions auto-approves every tool for custom workflow MCP server names", () => {
|
|
830
|
+
const restore = setWorkflowMcpEnv({
|
|
831
|
+
GSD_WORKFLOW_MCP_COMMAND: "node",
|
|
832
|
+
GSD_WORKFLOW_MCP_NAME: "custom-workflow",
|
|
833
|
+
GSD_WORKFLOW_MCP_ARGS: JSON.stringify(["packages/mcp-server/dist/cli.js"]),
|
|
834
|
+
GSD_WORKFLOW_MCP_ENV: JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" }),
|
|
835
|
+
GSD_WORKFLOW_MCP_CWD: "/tmp/project",
|
|
836
|
+
});
|
|
792
837
|
try {
|
|
793
|
-
process.env.GSD_WORKFLOW_MCP_COMMAND = "node";
|
|
794
|
-
process.env.GSD_WORKFLOW_MCP_NAME = "custom-workflow";
|
|
795
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = JSON.stringify(["packages/mcp-server/dist/cli.js"]);
|
|
796
|
-
process.env.GSD_WORKFLOW_MCP_ENV = JSON.stringify({ GSD_CLI_PATH: "/tmp/gsd" });
|
|
797
|
-
process.env.GSD_WORKFLOW_MCP_CWD = "/tmp/project";
|
|
798
838
|
|
|
799
839
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test");
|
|
800
840
|
const mcpServers = options.mcpServers as Record<string, any>;
|
|
801
841
|
assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config");
|
|
802
|
-
assert.deepEqual(options.disallowedTools, [
|
|
842
|
+
assert.deepEqual(options.disallowedTools, []);
|
|
803
843
|
assert.deepEqual(options.allowedTools, [
|
|
804
844
|
"Read",
|
|
805
845
|
"Write",
|
|
806
846
|
"Edit",
|
|
807
847
|
"Glob",
|
|
808
848
|
"Grep",
|
|
809
|
-
"Bash
|
|
810
|
-
"
|
|
849
|
+
"Bash",
|
|
850
|
+
"Agent",
|
|
851
|
+
"WebFetch",
|
|
852
|
+
"WebSearch",
|
|
853
|
+
"AskUserQuestion",
|
|
811
854
|
"mcp__custom-workflow__*",
|
|
812
855
|
]);
|
|
813
856
|
} finally {
|
|
814
|
-
|
|
815
|
-
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
816
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
|
|
817
|
-
process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
|
|
818
|
-
process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
|
|
857
|
+
restore();
|
|
819
858
|
}
|
|
820
859
|
});
|
|
821
860
|
|
|
822
861
|
test("buildSdkOptions auto-discovers bundled MCP server even without env hints", () => {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
|
|
829
|
-
};
|
|
862
|
+
// Use setWorkflowMcpEnv with no values to save current state;
|
|
863
|
+
// restore() in finally will put it back correctly (including
|
|
864
|
+
// deleting any keys that started as undefined — the #4808 bug
|
|
865
|
+
// the naive `process.env.X = prev.X` pattern introduced).
|
|
866
|
+
const restore = setWorkflowMcpEnv({});
|
|
830
867
|
try {
|
|
831
868
|
delete process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
832
869
|
delete process.env.GSD_WORKFLOW_MCP_NAME;
|
|
@@ -845,29 +882,21 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
845
882
|
const mcpServers = (options as any).mcpServers;
|
|
846
883
|
if (mcpServers) {
|
|
847
884
|
assert.ok(mcpServers["gsd-workflow"], "if present, must be gsd-workflow");
|
|
848
|
-
assert.deepEqual((options as any).disallowedTools, [
|
|
885
|
+
assert.deepEqual((options as any).disallowedTools, []);
|
|
849
886
|
} else {
|
|
850
|
-
assert.deepEqual((options as any).disallowedTools, [
|
|
887
|
+
assert.deepEqual((options as any).disallowedTools, []);
|
|
851
888
|
}
|
|
852
889
|
rmSync(emptyDir, { recursive: true, force: true });
|
|
853
890
|
} finally {
|
|
854
|
-
|
|
855
|
-
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
856
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
|
|
857
|
-
process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
|
|
858
|
-
process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
|
|
891
|
+
restore();
|
|
859
892
|
}
|
|
860
893
|
});
|
|
861
894
|
|
|
862
895
|
test("buildSdkOptions auto-detects local workflow MCP dist CLI when present", () => {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
|
|
868
|
-
GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
|
|
869
|
-
GSD_CLI_PATH: process.env.GSD_CLI_PATH,
|
|
870
|
-
};
|
|
896
|
+
// GSD_CLI_PATH isn't in WORKFLOW_MCP_ENV_KEYS, so save+restore it
|
|
897
|
+
// manually around setWorkflowMcpEnv which handles the MCP keys.
|
|
898
|
+
const prevCliPath = process.env.GSD_CLI_PATH;
|
|
899
|
+
const restore = setWorkflowMcpEnv({});
|
|
871
900
|
const originalCwd = process.cwd();
|
|
872
901
|
const repoDir = mkdtempSync(join(tmpdir(), "claude-mcp-detect-"));
|
|
873
902
|
try {
|
|
@@ -894,27 +923,22 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
894
923
|
assert.equal(srv.env.GSD_CLI_PATH, "/tmp/gsd");
|
|
895
924
|
assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1");
|
|
896
925
|
assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, resolvedRepoDir);
|
|
897
|
-
assert.deepEqual(options.disallowedTools, [
|
|
926
|
+
assert.deepEqual(options.disallowedTools, []);
|
|
898
927
|
} finally {
|
|
899
928
|
process.chdir(originalCwd);
|
|
900
929
|
rmSync(repoDir, { recursive: true, force: true });
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
930
|
+
restore();
|
|
931
|
+
// GSD_CLI_PATH isn't in setWorkflowMcpEnv's scope — restore it here.
|
|
932
|
+
if (prevCliPath === undefined) {
|
|
933
|
+
delete process.env.GSD_CLI_PATH;
|
|
934
|
+
} else {
|
|
935
|
+
process.env.GSD_CLI_PATH = prevCliPath;
|
|
936
|
+
}
|
|
907
937
|
}
|
|
908
938
|
});
|
|
909
939
|
|
|
910
940
|
test("buildSdkOptions preserves runtime callbacks such as onElicitation", () => {
|
|
911
|
-
const
|
|
912
|
-
GSD_WORKFLOW_MCP_COMMAND: process.env.GSD_WORKFLOW_MCP_COMMAND,
|
|
913
|
-
GSD_WORKFLOW_MCP_NAME: process.env.GSD_WORKFLOW_MCP_NAME,
|
|
914
|
-
GSD_WORKFLOW_MCP_ARGS: process.env.GSD_WORKFLOW_MCP_ARGS,
|
|
915
|
-
GSD_WORKFLOW_MCP_ENV: process.env.GSD_WORKFLOW_MCP_ENV,
|
|
916
|
-
GSD_WORKFLOW_MCP_CWD: process.env.GSD_WORKFLOW_MCP_CWD,
|
|
917
|
-
};
|
|
941
|
+
const restore = setWorkflowMcpEnv({});
|
|
918
942
|
const onElicitation = async () => ({ action: "decline" as const });
|
|
919
943
|
try {
|
|
920
944
|
delete process.env.GSD_WORKFLOW_MCP_COMMAND;
|
|
@@ -925,11 +949,7 @@ describe("stream-adapter — session persistence (#2859)", () => {
|
|
|
925
949
|
const options = buildSdkOptions("claude-sonnet-4-20250514", "test", undefined, { onElicitation });
|
|
926
950
|
assert.equal(options.onElicitation, onElicitation);
|
|
927
951
|
} finally {
|
|
928
|
-
|
|
929
|
-
process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;
|
|
930
|
-
process.env.GSD_WORKFLOW_MCP_ARGS = prev.GSD_WORKFLOW_MCP_ARGS;
|
|
931
|
-
process.env.GSD_WORKFLOW_MCP_ENV = prev.GSD_WORKFLOW_MCP_ENV;
|
|
932
|
-
process.env.GSD_WORKFLOW_MCP_CWD = prev.GSD_WORKFLOW_MCP_CWD;
|
|
952
|
+
restore();
|
|
933
953
|
}
|
|
934
954
|
});
|
|
935
955
|
});
|
|
@@ -1282,14 +1302,14 @@ describe("stream-adapter — permission mode (F10)", () => {
|
|
|
1282
1302
|
}
|
|
1283
1303
|
}
|
|
1284
1304
|
|
|
1285
|
-
test("buildSdkOptions defaults to
|
|
1305
|
+
test("buildSdkOptions defaults to bypassPermissions (globally unblocks all tools)", () => {
|
|
1286
1306
|
clearWorkflowMcpEnv();
|
|
1287
1307
|
const opts = buildSdkOptions("claude-sonnet-4-6", "test");
|
|
1288
|
-
assert.equal(opts.permissionMode, "
|
|
1308
|
+
assert.equal(opts.permissionMode, "bypassPermissions");
|
|
1289
1309
|
assert.equal(
|
|
1290
1310
|
opts.allowDangerouslySkipPermissions,
|
|
1291
|
-
|
|
1292
|
-
"allowDangerouslySkipPermissions must be
|
|
1311
|
+
true,
|
|
1312
|
+
"allowDangerouslySkipPermissions must be true when permissionMode is bypassPermissions",
|
|
1293
1313
|
);
|
|
1294
1314
|
});
|
|
1295
1315
|
|
|
@@ -1304,9 +1324,9 @@ describe("stream-adapter — permission mode (F10)", () => {
|
|
|
1304
1324
|
);
|
|
1305
1325
|
});
|
|
1306
1326
|
|
|
1307
|
-
test("resolveClaudePermissionMode defaults to
|
|
1327
|
+
test("resolveClaudePermissionMode defaults to bypassPermissions when no env var is set (globally unblocks all tools)", async () => {
|
|
1308
1328
|
const mode = await resolveClaudePermissionMode({});
|
|
1309
|
-
assert.equal(mode, "
|
|
1329
|
+
assert.equal(mode, "bypassPermissions");
|
|
1310
1330
|
});
|
|
1311
1331
|
|
|
1312
1332
|
test("resolveClaudePermissionMode honours the GSD_CLAUDE_CODE_PERMISSION_MODE env override", async () => {
|
|
@@ -1357,8 +1377,838 @@ describe("stream-adapter — Windows Claude path lookup (#3770)", () => {
|
|
|
1357
1377
|
assert.equal(getClaudeLookupCommand("linux"), "which claude");
|
|
1358
1378
|
});
|
|
1359
1379
|
|
|
1360
|
-
test("parseClaudeLookupOutput
|
|
1361
|
-
const output =
|
|
1362
|
-
|
|
1380
|
+
test("parseClaudeLookupOutput prefers .exe on win32 when where output includes shims", () => {
|
|
1381
|
+
const output = [
|
|
1382
|
+
"C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude",
|
|
1383
|
+
"C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude.cmd",
|
|
1384
|
+
"C:\\Program Files\\Claude\\claude.exe",
|
|
1385
|
+
].join("\r\n");
|
|
1386
|
+
assert.equal(parseClaudeLookupOutput(output, "win32"), "C:\\Program Files\\Claude\\claude.exe");
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
test("parseClaudeLookupOutput keeps first line on non-win32 platforms", () => {
|
|
1390
|
+
const output = "/usr/local/bin/claude\n/opt/homebrew/bin/claude\n";
|
|
1391
|
+
assert.equal(parseClaudeLookupOutput(output, "darwin"), "/usr/local/bin/claude");
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
test("normalizeClaudePathForSdk swaps Windows shim paths to bundled cli.js", () => {
|
|
1395
|
+
const shimPath = "C:\\Users\\djeff\\AppData\\Roaming\\npm\\claude";
|
|
1396
|
+
const bundled = "C:\\repo\\node_modules\\@anthropic-ai\\claude-agent-sdk\\cli.js";
|
|
1397
|
+
assert.equal(normalizeClaudePathForSdk(shimPath, "win32", bundled), bundled);
|
|
1398
|
+
assert.equal(normalizeClaudePathForSdk("C:\\Program Files\\Claude\\claude.exe", "win32", bundled), "C:\\Program Files\\Claude\\claude.exe");
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
test("resolveBundledClaudeCliPath returns a .js path when SDK package is present", () => {
|
|
1402
|
+
const resolved = resolveBundledClaudeCliPath();
|
|
1403
|
+
assert.ok(resolved, "expected sdk cli.js to be resolvable in test workspace");
|
|
1404
|
+
assert.match(resolved!, /[\\/]@anthropic-ai[\\/]claude-agent-sdk[\\/]cli\.js$/);
|
|
1405
|
+
});
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
// ---------------------------------------------------------------------------
|
|
1409
|
+
// canUseTool handler (#4383)
|
|
1410
|
+
// ---------------------------------------------------------------------------
|
|
1411
|
+
|
|
1412
|
+
describe("stream-adapter — canUseTool handler", () => {
|
|
1413
|
+
function makeOptions(overrides: Partial<{ signal: AbortSignal; suggestions: Array<Record<string, unknown>>; title: string; description: string; toolUseID: string }> = {}) {
|
|
1414
|
+
return {
|
|
1415
|
+
signal: overrides.signal ?? new AbortController().signal,
|
|
1416
|
+
toolUseID: overrides.toolUseID ?? "toolu_test123",
|
|
1417
|
+
...(overrides.title !== undefined ? { title: overrides.title } : {}),
|
|
1418
|
+
...(overrides.description !== undefined ? { description: overrides.description } : {}),
|
|
1419
|
+
...(overrides.suggestions !== undefined ? { suggestions: overrides.suggestions } : {}),
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Point process.cwd() at an empty temp dir so the real repo's
|
|
1424
|
+
// .claude/settings.local.json (which may already contain rules like
|
|
1425
|
+
// "Bash(gh pr list:*)") does not short-circuit the permission flow.
|
|
1426
|
+
// Returns a cleanup function that restores cwd and removes the temp dir.
|
|
1427
|
+
// biome-ignore lint/suspicious/noExplicitAny: test-only monkey-patch
|
|
1428
|
+
function withIsolatedCwd(): () => void {
|
|
1429
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-canusetool-")));
|
|
1430
|
+
const orig = process.cwd;
|
|
1431
|
+
process.cwd = () => dir;
|
|
1432
|
+
return () => {
|
|
1433
|
+
process.cwd = orig;
|
|
1434
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
test("returns undefined when no UI context is provided", () => {
|
|
1439
|
+
const handler = createClaudeCodeCanUseToolHandler(undefined);
|
|
1440
|
+
assert.equal(handler, undefined);
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
test("shows select dialog with Allow/Always Allow/Deny and returns allow", async () => {
|
|
1444
|
+
let selectPrompt = "";
|
|
1445
|
+
let selectOptions: string[] = [];
|
|
1446
|
+
const ui = {
|
|
1447
|
+
select: async (prompt: string, options: string[]) => {
|
|
1448
|
+
selectPrompt = prompt;
|
|
1449
|
+
selectOptions = options;
|
|
1450
|
+
return "Allow";
|
|
1451
|
+
},
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1455
|
+
assert.ok(handler);
|
|
1456
|
+
|
|
1457
|
+
const input = { command: "ls -la" };
|
|
1458
|
+
const result = await handler!("Bash", input, makeOptions({
|
|
1459
|
+
title: "Claude wants to run: ls -la",
|
|
1460
|
+
description: "List directory contents",
|
|
1461
|
+
}));
|
|
1462
|
+
|
|
1463
|
+
assert.equal(result.behavior, "allow");
|
|
1464
|
+
assert.deepEqual((result as any).updatedInput, input);
|
|
1465
|
+
assert.equal((result as any).toolUseID, "toolu_test123");
|
|
1466
|
+
// Allow (one-time) should NOT include updatedPermissions
|
|
1467
|
+
assert.equal((result as any).updatedPermissions, undefined);
|
|
1468
|
+
assert.deepEqual(selectOptions, ["Allow", "Always Allow", "Deny"]);
|
|
1469
|
+
// Prompt includes title and input summary
|
|
1470
|
+
assert.ok(selectPrompt.includes("Claude wants to run: ls -la"));
|
|
1471
|
+
assert.ok(selectPrompt.includes("ls -la"));
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
test("returns deny when user selects Deny", async () => {
|
|
1475
|
+
const ui = {
|
|
1476
|
+
select: async () => "Deny",
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1479
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1480
|
+
const result = await handler!("Bash", { command: "rm -rf /" }, makeOptions());
|
|
1481
|
+
|
|
1482
|
+
assert.equal(result.behavior, "deny");
|
|
1483
|
+
assert.equal((result as any).message, "User denied");
|
|
1484
|
+
assert.equal((result as any).toolUseID, "toolu_test123");
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test("returns deny when user dismisses dialog (undefined)", async () => {
|
|
1488
|
+
const ui = {
|
|
1489
|
+
select: async () => undefined,
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1493
|
+
const result = await handler!("Bash", { command: "echo hi" }, makeOptions());
|
|
1494
|
+
|
|
1495
|
+
assert.equal(result.behavior, "deny");
|
|
1496
|
+
assert.equal((result as any).message, "User denied");
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
test("Always Allow for Bash patches SDK suggestions with smart ruleContent", async () => {
|
|
1500
|
+
const notified: string[] = [];
|
|
1501
|
+
const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
|
|
1502
|
+
const suggestions = [{
|
|
1503
|
+
type: "addRules",
|
|
1504
|
+
rules: [{ toolName: "Bash", ruleContent: "ls -la /tmp" }],
|
|
1505
|
+
behavior: "allow",
|
|
1506
|
+
destination: "localSettings",
|
|
1507
|
+
}];
|
|
1508
|
+
|
|
1509
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1510
|
+
const result = await handler!("Bash", { command: "ls -la /tmp" }, makeOptions({ suggestions }));
|
|
1511
|
+
|
|
1512
|
+
assert.equal(result.behavior, "allow");
|
|
1513
|
+
// Should patch ruleContent with our smart pattern, preserving SDK structure
|
|
1514
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1515
|
+
type: "addRules",
|
|
1516
|
+
rules: [{ toolName: "Bash", ruleContent: "ls:*" }],
|
|
1517
|
+
behavior: "allow",
|
|
1518
|
+
destination: "localSettings",
|
|
1519
|
+
}]);
|
|
1520
|
+
assert.equal(notified.length, 1);
|
|
1521
|
+
assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(ls:*)"));
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
test("Always Allow for Bash with subcommand-sensitive CLI captures verb", async () => {
|
|
1525
|
+
const cleanup = withIsolatedCwd();
|
|
1526
|
+
try {
|
|
1527
|
+
const notified: string[] = [];
|
|
1528
|
+
// First select call: pick "Always Allow ..."; second call (level
|
|
1529
|
+
// picker): pick the "git push" granularity explicitly.
|
|
1530
|
+
let selectCall = 0;
|
|
1531
|
+
const ui = {
|
|
1532
|
+
select: async (_p: string, opts: string[]) => {
|
|
1533
|
+
selectCall++;
|
|
1534
|
+
if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1535
|
+
return "Bash(git push:*)";
|
|
1536
|
+
},
|
|
1537
|
+
notify: (msg: string) => notified.push(msg),
|
|
1538
|
+
};
|
|
1539
|
+
const suggestions = [{
|
|
1540
|
+
type: "addRules",
|
|
1541
|
+
rules: [{ toolName: "Bash", ruleContent: "git push origin main" }],
|
|
1542
|
+
behavior: "allow",
|
|
1543
|
+
destination: "localSettings",
|
|
1544
|
+
}];
|
|
1545
|
+
|
|
1546
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1547
|
+
const result = await handler!("Bash", { command: "git push origin main" }, makeOptions({ suggestions }));
|
|
1548
|
+
|
|
1549
|
+
assert.equal(result.behavior, "allow");
|
|
1550
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1551
|
+
type: "addRules",
|
|
1552
|
+
rules: [{ toolName: "Bash", ruleContent: "git push:*" }],
|
|
1553
|
+
behavior: "allow",
|
|
1554
|
+
destination: "localSettings",
|
|
1555
|
+
}]);
|
|
1556
|
+
assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(git push:*)"));
|
|
1557
|
+
} finally {
|
|
1558
|
+
cleanup();
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
test("Always Allow for Bash without suggestions builds proper PermissionUpdate", async () => {
|
|
1563
|
+
const cleanup = withIsolatedCwd();
|
|
1564
|
+
try {
|
|
1565
|
+
const notified: string[] = [];
|
|
1566
|
+
let selectCall = 0;
|
|
1567
|
+
const ui = {
|
|
1568
|
+
select: async (_p: string, opts: string[]) => {
|
|
1569
|
+
selectCall++;
|
|
1570
|
+
if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1571
|
+
return "Bash(gh pr list:*)";
|
|
1572
|
+
},
|
|
1573
|
+
notify: (msg: string) => notified.push(msg),
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1577
|
+
const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
|
|
1578
|
+
|
|
1579
|
+
assert.equal(result.behavior, "allow");
|
|
1580
|
+
// No SDK suggestions → builds PermissionUpdate from scratch
|
|
1581
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1582
|
+
type: "addRules",
|
|
1583
|
+
rules: [{ toolName: "Bash", ruleContent: "gh pr list:*" }],
|
|
1584
|
+
behavior: "allow",
|
|
1585
|
+
destination: "localSettings",
|
|
1586
|
+
}]);
|
|
1587
|
+
assert.ok(notified[0].includes("Saved:") && notified[0].includes("Bash(gh pr list:*)"));
|
|
1588
|
+
} finally {
|
|
1589
|
+
cleanup();
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
test("Always Allow for non-Bash tools passes SDK suggestions through", async () => {
|
|
1594
|
+
const notified: string[] = [];
|
|
1595
|
+
const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
|
|
1596
|
+
const suggestions = [{
|
|
1597
|
+
type: "addRules",
|
|
1598
|
+
rules: [{ toolName: "Write" }],
|
|
1599
|
+
behavior: "allow",
|
|
1600
|
+
destination: "localSettings",
|
|
1601
|
+
}];
|
|
1602
|
+
|
|
1603
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1604
|
+
const result = await handler!("Write", { file_path: "/tmp/test.txt" }, makeOptions({ suggestions }));
|
|
1605
|
+
|
|
1606
|
+
assert.equal(result.behavior, "allow");
|
|
1607
|
+
assert.deepEqual((result as any).updatedPermissions, suggestions);
|
|
1608
|
+
// Non-Bash tools don't emit a post-selection notification (only Bash runs the level picker)
|
|
1609
|
+
assert.equal(notified.length, 0);
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
test("Always Allow for non-Bash without suggestions omits updatedPermissions", async () => {
|
|
1613
|
+
const notified: string[] = [];
|
|
1614
|
+
const ui = { select: async (_p: string, opts: string[]) => opts.find((o) => o.startsWith("Always Allow"))!, notify: (msg: string) => notified.push(msg) };
|
|
1615
|
+
|
|
1616
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1617
|
+
const result = await handler!("Write", { file_path: "/tmp/test.txt" }, makeOptions());
|
|
1618
|
+
|
|
1619
|
+
assert.equal(result.behavior, "allow");
|
|
1620
|
+
assert.equal((result as any).updatedPermissions, undefined);
|
|
1621
|
+
// No suggestions → no notification
|
|
1622
|
+
assert.equal(notified.length, 0);
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
test("prompt includes command text for Bash tools", async () => {
|
|
1626
|
+
let selectPrompt = "";
|
|
1627
|
+
const ui = {
|
|
1628
|
+
select: async (prompt: string) => {
|
|
1629
|
+
selectPrompt = prompt;
|
|
1630
|
+
return "Allow";
|
|
1631
|
+
},
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1635
|
+
await handler!("Bash", { command: "git status" }, makeOptions());
|
|
1636
|
+
assert.ok(selectPrompt.includes("git status"), `prompt should include command: ${selectPrompt}`);
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
test("prompt includes file_path for file tools", async () => {
|
|
1640
|
+
let selectPrompt = "";
|
|
1641
|
+
const ui = {
|
|
1642
|
+
select: async (prompt: string) => {
|
|
1643
|
+
selectPrompt = prompt;
|
|
1644
|
+
return "Allow";
|
|
1645
|
+
},
|
|
1646
|
+
};
|
|
1647
|
+
|
|
1648
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1649
|
+
await handler!("Write", { file_path: "/tmp/test.txt", content: "hello" }, makeOptions());
|
|
1650
|
+
assert.ok(selectPrompt.includes("/tmp/test.txt"), `prompt should include file path: ${selectPrompt}`);
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
test("uses title from options when available", async () => {
|
|
1654
|
+
let selectPrompt = "";
|
|
1655
|
+
const ui = {
|
|
1656
|
+
select: async (prompt: string) => {
|
|
1657
|
+
selectPrompt = prompt;
|
|
1658
|
+
return "Allow";
|
|
1659
|
+
},
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1663
|
+
await handler!("WebFetch", {}, makeOptions({ title: "Claude wants to fetch: https://example.com" }));
|
|
1664
|
+
assert.ok(selectPrompt.includes("Claude wants to fetch: https://example.com"));
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
test("falls back to default title when options.title is missing", async () => {
|
|
1668
|
+
let selectPrompt = "";
|
|
1669
|
+
const ui = {
|
|
1670
|
+
select: async (prompt: string) => {
|
|
1671
|
+
selectPrompt = prompt;
|
|
1672
|
+
return "Allow";
|
|
1673
|
+
},
|
|
1674
|
+
};
|
|
1675
|
+
|
|
1676
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1677
|
+
await handler!("WebFetch", { url: "https://example.com" }, makeOptions());
|
|
1678
|
+
assert.ok(selectPrompt.includes("Allow Claude Code to use: WebFetch?"));
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
test("returns deny when signal is already aborted", async () => {
|
|
1682
|
+
const ui = {
|
|
1683
|
+
select: async () => { throw new Error("should not be called"); },
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
const controller = new AbortController();
|
|
1687
|
+
controller.abort();
|
|
1688
|
+
|
|
1689
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1690
|
+
const result = await handler!("Bash", {}, makeOptions({ signal: controller.signal }));
|
|
1691
|
+
|
|
1692
|
+
assert.equal(result.behavior, "deny");
|
|
1693
|
+
assert.equal((result as any).message, "Aborted");
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
test("returns deny when ui.select throws", async () => {
|
|
1697
|
+
const ui = {
|
|
1698
|
+
select: async () => { throw new Error("dialog crashed"); },
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1701
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1702
|
+
const result = await handler!("Bash", {}, makeOptions());
|
|
1703
|
+
|
|
1704
|
+
assert.equal(result.behavior, "deny");
|
|
1705
|
+
assert.equal((result as any).message, "Aborted");
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
test("buildSdkOptions passes canUseTool through extraOptions", () => {
|
|
1709
|
+
const canUseTool = async () => ({ behavior: "allow" as const, updatedInput: {}, toolUseID: "test" });
|
|
1710
|
+
const opts = buildSdkOptions("claude-sonnet-4-6", "test", undefined, { canUseTool });
|
|
1711
|
+
assert.equal(opts.canUseTool, canUseTool);
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
test("Always Allow shows level picker and user broadens to base command", async () => {
|
|
1715
|
+
const cleanup = withIsolatedCwd();
|
|
1716
|
+
try {
|
|
1717
|
+
const prompts: string[] = [];
|
|
1718
|
+
const levelOpts: string[][] = [];
|
|
1719
|
+
let selectCall = 0;
|
|
1720
|
+
const ui = {
|
|
1721
|
+
select: async (prompt: string, opts: string[]) => {
|
|
1722
|
+
prompts.push(prompt);
|
|
1723
|
+
selectCall++;
|
|
1724
|
+
if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1725
|
+
levelOpts.push(opts);
|
|
1726
|
+
return "Bash(gh:*)";
|
|
1727
|
+
},
|
|
1728
|
+
notify: () => {},
|
|
1729
|
+
};
|
|
1730
|
+
|
|
1731
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1732
|
+
const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
|
|
1733
|
+
|
|
1734
|
+
assert.equal(result.behavior, "allow");
|
|
1735
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1736
|
+
type: "addRules",
|
|
1737
|
+
rules: [{ toolName: "Bash", ruleContent: "gh:*" }],
|
|
1738
|
+
behavior: "allow",
|
|
1739
|
+
destination: "localSettings",
|
|
1740
|
+
}]);
|
|
1741
|
+
// Second dialog offered every granularity level
|
|
1742
|
+
assert.deepEqual(levelOpts[0], [
|
|
1743
|
+
"Bash(gh:*)",
|
|
1744
|
+
"Bash(gh pr:*)",
|
|
1745
|
+
"Bash(gh pr list:*)",
|
|
1746
|
+
]);
|
|
1747
|
+
assert.ok(prompts[1].includes("Save permission at which level?"));
|
|
1748
|
+
} finally {
|
|
1749
|
+
cleanup();
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
test("Always Allow narrows to mid-level pattern when user picks Bash(gh pr:*)", async () => {
|
|
1754
|
+
const cleanup = withIsolatedCwd();
|
|
1755
|
+
try {
|
|
1756
|
+
let selectCall = 0;
|
|
1757
|
+
const ui = {
|
|
1758
|
+
select: async (_p: string, opts: string[]) => {
|
|
1759
|
+
selectCall++;
|
|
1760
|
+
if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1761
|
+
return "Bash(gh pr:*)";
|
|
1762
|
+
},
|
|
1763
|
+
notify: () => {},
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1767
|
+
const result = await handler!("Bash", { command: "gh pr list --limit 5" }, makeOptions());
|
|
1768
|
+
|
|
1769
|
+
assert.equal(result.behavior, "allow");
|
|
1770
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1771
|
+
type: "addRules",
|
|
1772
|
+
rules: [{ toolName: "Bash", ruleContent: "gh pr:*" }],
|
|
1773
|
+
behavior: "allow",
|
|
1774
|
+
destination: "localSettings",
|
|
1775
|
+
}]);
|
|
1776
|
+
} finally {
|
|
1777
|
+
cleanup();
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
test("Always Allow skips level picker when only one pattern is available", async () => {
|
|
1782
|
+
const cleanup = withIsolatedCwd();
|
|
1783
|
+
try {
|
|
1784
|
+
const prompts: string[] = [];
|
|
1785
|
+
const ui = {
|
|
1786
|
+
select: async (prompt: string, opts: string[]) => {
|
|
1787
|
+
prompts.push(prompt);
|
|
1788
|
+
return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1789
|
+
},
|
|
1790
|
+
notify: () => {},
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1794
|
+
const result = await handler!("Bash", { command: "ls -la /tmp" }, makeOptions());
|
|
1795
|
+
|
|
1796
|
+
assert.equal(result.behavior, "allow");
|
|
1797
|
+
// "ls" has no subcommand tokens before the flag → single-option path
|
|
1798
|
+
assert.equal(prompts.length, 1, "should not show a second dialog");
|
|
1799
|
+
assert.deepEqual((result as any).updatedPermissions, [{
|
|
1800
|
+
type: "addRules",
|
|
1801
|
+
rules: [{ toolName: "Bash", ruleContent: "ls:*" }],
|
|
1802
|
+
behavior: "allow",
|
|
1803
|
+
destination: "localSettings",
|
|
1804
|
+
}]);
|
|
1805
|
+
} finally {
|
|
1806
|
+
cleanup();
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
test("Always Allow denies the tool when level picker is dismissed", async () => {
|
|
1811
|
+
const cleanup = withIsolatedCwd();
|
|
1812
|
+
try {
|
|
1813
|
+
const notified: string[] = [];
|
|
1814
|
+
let selectCall = 0;
|
|
1815
|
+
const ui = {
|
|
1816
|
+
select: async (_p: string, opts: string[]) => {
|
|
1817
|
+
selectCall++;
|
|
1818
|
+
if (selectCall === 1) return opts.find((o) => o.startsWith("Always Allow"))!;
|
|
1819
|
+
return undefined; // user dismissed level picker
|
|
1820
|
+
},
|
|
1821
|
+
notify: (msg: string) => notified.push(msg),
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
const handler = createClaudeCodeCanUseToolHandler(ui as any);
|
|
1825
|
+
const result = await handler!("Bash", { command: "gh pr list" }, makeOptions());
|
|
1826
|
+
|
|
1827
|
+
// Dismissing the level picker cancels the tool use — a one-time allow
|
|
1828
|
+
// would leave the spawned agent running even though the user bailed.
|
|
1829
|
+
assert.equal(result.behavior, "deny");
|
|
1830
|
+
assert.equal((result as any).updatedPermissions, undefined);
|
|
1831
|
+
assert.equal(notified.length, 0, "no 'Saved:' notification when nothing was saved");
|
|
1832
|
+
} finally {
|
|
1833
|
+
cleanup();
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
});
|
|
1837
|
+
|
|
1838
|
+
// ---------------------------------------------------------------------------
|
|
1839
|
+
// buildBashPermissionPattern — smart permission granularity
|
|
1840
|
+
// ---------------------------------------------------------------------------
|
|
1841
|
+
|
|
1842
|
+
describe("buildBashPermissionPattern", () => {
|
|
1843
|
+
test("simple command wildcards all args", () => {
|
|
1844
|
+
assert.equal(buildBashPermissionPattern("ping -n 4 localhost"), "Bash(ping:*)");
|
|
1845
|
+
assert.equal(buildBashPermissionPattern("echo hello world"), "Bash(echo:*)");
|
|
1846
|
+
assert.equal(buildBashPermissionPattern("ls -la /tmp"), "Bash(ls:*)");
|
|
1847
|
+
assert.equal(buildBashPermissionPattern("node server.js"), "Bash(node:*)");
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
test("git captures one subcommand", () => {
|
|
1851
|
+
assert.equal(buildBashPermissionPattern("git push origin main"), "Bash(git push:*)");
|
|
1852
|
+
assert.equal(buildBashPermissionPattern("git log --oneline"), "Bash(git log:*)");
|
|
1853
|
+
assert.equal(buildBashPermissionPattern("git status"), "Bash(git status:*)");
|
|
1854
|
+
});
|
|
1855
|
+
|
|
1856
|
+
test("gh captures two subcommands", () => {
|
|
1857
|
+
assert.equal(buildBashPermissionPattern("gh pr list"), "Bash(gh pr list:*)");
|
|
1858
|
+
assert.equal(buildBashPermissionPattern("gh pr create --title foo"), "Bash(gh pr create:*)");
|
|
1859
|
+
assert.equal(buildBashPermissionPattern("gh issue view 123"), "Bash(gh issue view:*)");
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1862
|
+
test("npm captures one subcommand", () => {
|
|
1863
|
+
assert.equal(buildBashPermissionPattern("npm install lodash"), "Bash(npm install:*)");
|
|
1864
|
+
assert.equal(buildBashPermissionPattern("npm publish"), "Bash(npm publish:*)");
|
|
1865
|
+
assert.equal(buildBashPermissionPattern("npm run test"), "Bash(npm run:*)");
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
test("npx captures package name", () => {
|
|
1869
|
+
assert.equal(buildBashPermissionPattern("npx vitest run"), "Bash(npx vitest:*)");
|
|
1870
|
+
assert.equal(buildBashPermissionPattern("npx --version"), "Bash(npx --version:*)");
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
test("docker captures one subcommand", () => {
|
|
1874
|
+
assert.equal(buildBashPermissionPattern("docker ps -a"), "Bash(docker ps:*)");
|
|
1875
|
+
assert.equal(buildBashPermissionPattern("docker rm container1"), "Bash(docker rm:*)");
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
test("aws captures two subcommands", () => {
|
|
1879
|
+
assert.equal(buildBashPermissionPattern("aws s3 cp file.txt s3://bucket/"), "Bash(aws s3 cp:*)");
|
|
1880
|
+
assert.equal(buildBashPermissionPattern("aws ec2 describe-instances"), "Bash(aws ec2 describe-instances:*)");
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
test("skips sudo wrapper", () => {
|
|
1884
|
+
assert.equal(buildBashPermissionPattern("sudo ping localhost"), "Bash(ping:*)");
|
|
1885
|
+
assert.equal(buildBashPermissionPattern("sudo git push"), "Bash(git push:*)");
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
test("skips env wrapper and VAR=val assignments", () => {
|
|
1889
|
+
assert.equal(buildBashPermissionPattern("env NODE_ENV=prod node server.js"), "Bash(node:*)");
|
|
1890
|
+
assert.equal(buildBashPermissionPattern("NODE_ENV=prod node server.js"), "Bash(node:*)");
|
|
1891
|
+
assert.equal(buildBashPermissionPattern("FOO=bar BAZ=qux git push"), "Bash(git push:*)");
|
|
1892
|
+
});
|
|
1893
|
+
|
|
1894
|
+
test("strips path from executable", () => {
|
|
1895
|
+
assert.equal(buildBashPermissionPattern("/usr/bin/git push"), "Bash(git push:*)");
|
|
1896
|
+
assert.equal(buildBashPermissionPattern("C:\\Windows\\ping.exe localhost"), "Bash(ping:*)");
|
|
1897
|
+
});
|
|
1898
|
+
|
|
1899
|
+
test("empty or whitespace-only command", () => {
|
|
1900
|
+
assert.equal(buildBashPermissionPattern(""), "Bash(*)");
|
|
1901
|
+
assert.equal(buildBashPermissionPattern(" "), "Bash(*)");
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
test("chained commands — extracts pattern from the meaningful segment", () => {
|
|
1905
|
+
assert.equal(buildBashPermissionPattern("cd /foo && gh pr list --limit 5"), "Bash(gh pr list:*)");
|
|
1906
|
+
assert.equal(buildBashPermissionPattern("cd C:/Users/djeff/repos/gsd-2 && gh pr list --limit 5"), "Bash(gh pr list:*)");
|
|
1907
|
+
assert.equal(buildBashPermissionPattern("cd /tmp && git push origin main"), "Bash(git push:*)");
|
|
1908
|
+
assert.equal(buildBashPermissionPattern("export FOO=1 && npm install lodash"), "Bash(npm install:*)");
|
|
1909
|
+
assert.equal(buildBashPermissionPattern("mkdir -p out; docker ps -a"), "Bash(docker ps:*)");
|
|
1910
|
+
assert.equal(buildBashPermissionPattern("echo start || ping localhost"), "Bash(ping:*)");
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
test("skips trailing || true / || : error suppressors", () => {
|
|
1914
|
+
assert.equal(
|
|
1915
|
+
buildBashPermissionPattern("cd C:/Users/djeff/repos/gsd-2 && gh pr create --dry-run --title \"test\" --body \"test\" 2>&1 || true"),
|
|
1916
|
+
"Bash(gh pr create:*)",
|
|
1917
|
+
);
|
|
1918
|
+
assert.equal(buildBashPermissionPattern("gh pr list || true"), "Bash(gh pr list:*)");
|
|
1919
|
+
assert.equal(buildBashPermissionPattern("git push || :"), "Bash(git push:*)");
|
|
1920
|
+
assert.equal(buildBashPermissionPattern("cd /tmp && npm install || echo failed"), "Bash(npm install:*)");
|
|
1921
|
+
});
|
|
1922
|
+
|
|
1923
|
+
test("single command is unaffected by chain extraction", () => {
|
|
1924
|
+
assert.equal(buildBashPermissionPattern("gh pr list"), "Bash(gh pr list:*)");
|
|
1925
|
+
assert.equal(buildBashPermissionPattern("git push origin main"), "Bash(git push:*)");
|
|
1926
|
+
});
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// ---------------------------------------------------------------------------
|
|
1930
|
+
// buildBashPermissionPatternOptions — granularity level menu
|
|
1931
|
+
// ---------------------------------------------------------------------------
|
|
1932
|
+
|
|
1933
|
+
describe("buildBashPermissionPatternOptions", () => {
|
|
1934
|
+
test("offers every prefix from base to full subcommand chain", () => {
|
|
1935
|
+
assert.deepEqual(buildBashPermissionPatternOptions("gh pr list"), [
|
|
1936
|
+
"Bash(gh:*)",
|
|
1937
|
+
"Bash(gh pr:*)",
|
|
1938
|
+
"Bash(gh pr list:*)",
|
|
1939
|
+
]);
|
|
1940
|
+
assert.deepEqual(buildBashPermissionPatternOptions("git push origin main"), [
|
|
1941
|
+
"Bash(git:*)",
|
|
1942
|
+
"Bash(git push:*)",
|
|
1943
|
+
"Bash(git push origin:*)",
|
|
1944
|
+
"Bash(git push origin main:*)",
|
|
1945
|
+
]);
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1948
|
+
test("stops at first flag — flags are args, not verbs", () => {
|
|
1949
|
+
assert.deepEqual(buildBashPermissionPatternOptions("gh pr create --title foo"), [
|
|
1950
|
+
"Bash(gh:*)",
|
|
1951
|
+
"Bash(gh pr:*)",
|
|
1952
|
+
"Bash(gh pr create:*)",
|
|
1953
|
+
]);
|
|
1954
|
+
assert.deepEqual(buildBashPermissionPatternOptions("git log --oneline"), [
|
|
1955
|
+
"Bash(git:*)",
|
|
1956
|
+
"Bash(git log:*)",
|
|
1957
|
+
]);
|
|
1958
|
+
});
|
|
1959
|
+
|
|
1960
|
+
test("single-option when there is no subcommand to choose from", () => {
|
|
1961
|
+
assert.deepEqual(buildBashPermissionPatternOptions("ls -la /tmp"), ["Bash(ls:*)"]);
|
|
1962
|
+
assert.deepEqual(buildBashPermissionPatternOptions("ping -n 4 localhost"), ["Bash(ping:*)"]);
|
|
1963
|
+
assert.deepEqual(buildBashPermissionPatternOptions("node"), ["Bash(node:*)"]);
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
test("extracts meaningful segment from compound commands", () => {
|
|
1967
|
+
assert.deepEqual(buildBashPermissionPatternOptions("cd /foo && gh pr list"), [
|
|
1968
|
+
"Bash(gh:*)",
|
|
1969
|
+
"Bash(gh pr:*)",
|
|
1970
|
+
"Bash(gh pr list:*)",
|
|
1971
|
+
]);
|
|
1972
|
+
assert.deepEqual(buildBashPermissionPatternOptions("gh pr create --dry-run || true"), [
|
|
1973
|
+
"Bash(gh:*)",
|
|
1974
|
+
"Bash(gh pr:*)",
|
|
1975
|
+
"Bash(gh pr create:*)",
|
|
1976
|
+
]);
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
test("caps at three subcommand tokens to keep the menu short", () => {
|
|
1980
|
+
const result = buildBashPermissionPatternOptions("foo bar baz qux quux corge");
|
|
1981
|
+
// base + 3 sub tokens = 4 patterns max
|
|
1982
|
+
assert.equal(result.length, 4);
|
|
1983
|
+
assert.deepEqual(result, [
|
|
1984
|
+
"Bash(foo:*)",
|
|
1985
|
+
"Bash(foo bar:*)",
|
|
1986
|
+
"Bash(foo bar baz:*)",
|
|
1987
|
+
"Bash(foo bar baz qux:*)",
|
|
1988
|
+
]);
|
|
1989
|
+
});
|
|
1990
|
+
|
|
1991
|
+
test("skips sudo/env wrappers like the single-pattern variant", () => {
|
|
1992
|
+
assert.deepEqual(buildBashPermissionPatternOptions("sudo git push origin"), [
|
|
1993
|
+
"Bash(git:*)",
|
|
1994
|
+
"Bash(git push:*)",
|
|
1995
|
+
"Bash(git push origin:*)",
|
|
1996
|
+
]);
|
|
1997
|
+
assert.deepEqual(buildBashPermissionPatternOptions("NODE_ENV=prod node server.js"), [
|
|
1998
|
+
"Bash(node:*)",
|
|
1999
|
+
"Bash(node server.js:*)",
|
|
2000
|
+
]);
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
test("empty command returns the catch-all pattern", () => {
|
|
2004
|
+
assert.deepEqual(buildBashPermissionPatternOptions(""), ["Bash(*)"]);
|
|
2005
|
+
assert.deepEqual(buildBashPermissionPatternOptions(" "), ["Bash(*)"]);
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
2008
|
+
|
|
2009
|
+
// ---------------------------------------------------------------------------
|
|
2010
|
+
// bashCommandMatchesSavedRules — compound command bypass for saved rules
|
|
2011
|
+
// ---------------------------------------------------------------------------
|
|
2012
|
+
|
|
2013
|
+
describe("bashCommandMatchesSavedRules — compound command bypass", () => {
|
|
2014
|
+
let tempDir: string;
|
|
2015
|
+
let originalCwd: string;
|
|
2016
|
+
|
|
2017
|
+
// Create a temp project directory with .claude/settings.local.json
|
|
2018
|
+
function setupSettings(allow: string[]): void {
|
|
2019
|
+
const claudeDir = join(tempDir, ".claude");
|
|
2020
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
2021
|
+
writeFileSync(
|
|
2022
|
+
join(claudeDir, "settings.local.json"),
|
|
2023
|
+
JSON.stringify({ permissions: { allow } }),
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// biome-ignore lint/suspicious/noExplicitAny: test-only monkey-patch
|
|
2028
|
+
let origCwd: any;
|
|
2029
|
+
|
|
2030
|
+
// Monkey-patch process.cwd() to point at our temp dir
|
|
2031
|
+
function setCwd(dir: string): void {
|
|
2032
|
+
origCwd = process.cwd;
|
|
2033
|
+
process.cwd = () => dir;
|
|
2034
|
+
}
|
|
2035
|
+
function restoreCwd(): void {
|
|
2036
|
+
if (origCwd) process.cwd = origCwd;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
test("matches cd-prefixed compound command against saved prefix rule", () => {
|
|
2040
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2041
|
+
try {
|
|
2042
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2043
|
+
setCwd(tempDir);
|
|
2044
|
+
assert.equal(
|
|
2045
|
+
bashCommandMatchesSavedRules("cd /some/path && gh pr list --limit 5"),
|
|
2046
|
+
true,
|
|
2047
|
+
);
|
|
2048
|
+
} finally {
|
|
2049
|
+
restoreCwd();
|
|
2050
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
|
|
2054
|
+
test("matches cd-prefixed compound command with exact subcommand", () => {
|
|
2055
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2056
|
+
try {
|
|
2057
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2058
|
+
setCwd(tempDir);
|
|
2059
|
+
assert.equal(
|
|
2060
|
+
bashCommandMatchesSavedRules("cd C:/Users/foo/repos/bar && gh pr list"),
|
|
2061
|
+
true,
|
|
2062
|
+
);
|
|
2063
|
+
} finally {
|
|
2064
|
+
restoreCwd();
|
|
2065
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2066
|
+
}
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
test("rejects when leading segment is not cd", () => {
|
|
2070
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2071
|
+
try {
|
|
2072
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2073
|
+
setCwd(tempDir);
|
|
2074
|
+
// "rm -rf /tmp" is not a cd command — should not auto-approve
|
|
2075
|
+
assert.equal(
|
|
2076
|
+
bashCommandMatchesSavedRules("rm -rf /tmp && gh pr list"),
|
|
2077
|
+
false,
|
|
2078
|
+
);
|
|
2079
|
+
} finally {
|
|
2080
|
+
restoreCwd();
|
|
2081
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
test("rejects when meaningful segment does not match any rule", () => {
|
|
2086
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2087
|
+
try {
|
|
2088
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2089
|
+
setCwd(tempDir);
|
|
2090
|
+
assert.equal(
|
|
2091
|
+
bashCommandMatchesSavedRules("cd /path && gh issue create --title foo"),
|
|
2092
|
+
false,
|
|
2093
|
+
);
|
|
2094
|
+
} finally {
|
|
2095
|
+
restoreCwd();
|
|
2096
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
|
|
2100
|
+
test("matches simple (non-compound) commands against on-disk rules", () => {
|
|
2101
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2102
|
+
try {
|
|
2103
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2104
|
+
setCwd(tempDir);
|
|
2105
|
+
// Simple commands must also be checked — the SDK's in-memory cache
|
|
2106
|
+
// may be stale if the rule was added mid-session via "Always Allow"
|
|
2107
|
+
assert.equal(bashCommandMatchesSavedRules("gh pr list --limit 5"), true);
|
|
2108
|
+
assert.equal(bashCommandMatchesSavedRules("gh pr list"), true);
|
|
2109
|
+
} finally {
|
|
2110
|
+
restoreCwd();
|
|
2111
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2112
|
+
}
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
test("returns false for simple commands with no matching rule", () => {
|
|
2116
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2117
|
+
try {
|
|
2118
|
+
setupSettings(["Bash(gh pr list:*)"]);
|
|
2119
|
+
setCwd(tempDir);
|
|
2120
|
+
assert.equal(bashCommandMatchesSavedRules("gh issue list --limit 5"), false);
|
|
2121
|
+
} finally {
|
|
2122
|
+
restoreCwd();
|
|
2123
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2127
|
+
test("returns false when no settings file exists", () => {
|
|
2128
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2129
|
+
try {
|
|
2130
|
+
// No .claude/settings.local.json created
|
|
2131
|
+
setCwd(tempDir);
|
|
2132
|
+
assert.equal(
|
|
2133
|
+
bashCommandMatchesSavedRules("cd /path && gh pr list"),
|
|
2134
|
+
false,
|
|
2135
|
+
);
|
|
2136
|
+
} finally {
|
|
2137
|
+
restoreCwd();
|
|
2138
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
|
|
2142
|
+
test("matches exact rule (non-prefix)", () => {
|
|
2143
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2144
|
+
try {
|
|
2145
|
+
setupSettings(["Bash(ping -n 4 localhost)"]);
|
|
2146
|
+
setCwd(tempDir);
|
|
2147
|
+
assert.equal(
|
|
2148
|
+
bashCommandMatchesSavedRules("cd /path && ping -n 4 localhost"),
|
|
2149
|
+
true,
|
|
2150
|
+
);
|
|
2151
|
+
} finally {
|
|
2152
|
+
restoreCwd();
|
|
2153
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
test("handles multiple cd segments before the meaningful command", () => {
|
|
2158
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2159
|
+
try {
|
|
2160
|
+
setupSettings(["Bash(npm install:*)"]);
|
|
2161
|
+
setCwd(tempDir);
|
|
2162
|
+
assert.equal(
|
|
2163
|
+
bashCommandMatchesSavedRules("cd /home && cd project && npm install lodash"),
|
|
2164
|
+
true,
|
|
2165
|
+
);
|
|
2166
|
+
} finally {
|
|
2167
|
+
restoreCwd();
|
|
2168
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
|
|
2172
|
+
test("matches compound command with trailing || true suppressor", () => {
|
|
2173
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2174
|
+
try {
|
|
2175
|
+
setupSettings(["Bash(gh pr create:*)"]);
|
|
2176
|
+
setCwd(tempDir);
|
|
2177
|
+
assert.equal(
|
|
2178
|
+
bashCommandMatchesSavedRules('cd C:/Users/djeff/repos/gsd-2 && gh pr create --dry-run --title "test" --body "test" 2>&1 || true'),
|
|
2179
|
+
true,
|
|
2180
|
+
);
|
|
2181
|
+
assert.equal(
|
|
2182
|
+
bashCommandMatchesSavedRules("gh pr create --dry-run || true"),
|
|
2183
|
+
true,
|
|
2184
|
+
);
|
|
2185
|
+
assert.equal(
|
|
2186
|
+
bashCommandMatchesSavedRules("cd /tmp && git push || :"),
|
|
2187
|
+
false, // rule is for gh pr create, not git push
|
|
2188
|
+
);
|
|
2189
|
+
} finally {
|
|
2190
|
+
restoreCwd();
|
|
2191
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
test("reads rules from settings.json as well as settings.local.json", () => {
|
|
2196
|
+
tempDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-rules-")));
|
|
2197
|
+
try {
|
|
2198
|
+
const claudeDir = join(tempDir, ".claude");
|
|
2199
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
2200
|
+
writeFileSync(
|
|
2201
|
+
join(claudeDir, "settings.json"),
|
|
2202
|
+
JSON.stringify({ permissions: { allow: ["Bash(git push:*)"] } }),
|
|
2203
|
+
);
|
|
2204
|
+
setCwd(tempDir);
|
|
2205
|
+
assert.equal(
|
|
2206
|
+
bashCommandMatchesSavedRules("cd /repo && git push origin main"),
|
|
2207
|
+
true,
|
|
2208
|
+
);
|
|
2209
|
+
} finally {
|
|
2210
|
+
restoreCwd();
|
|
2211
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
2212
|
+
}
|
|
1363
2213
|
});
|
|
1364
2214
|
});
|