gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.479ad0e78
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/dist/claude-cli-check.js +32 -3
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.js +35 -1
- package/dist/onboarding.js +45 -0
- package/dist/resource-loader.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
- package/dist/resources/extensions/gsd/auto/loop.js +9 -0
- package/dist/resources/extensions/gsd/auto/phases.js +58 -5
- package/dist/resources/extensions/gsd/auto/run-unit.js +38 -2
- package/dist/resources/extensions/gsd/auto/session.js +22 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +16 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +14 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +25 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +32 -1
- package/dist/resources/extensions/gsd/auto-start.js +58 -57
- package/dist/resources/extensions/gsd/auto-worktree.js +51 -53
- package/dist/resources/extensions/gsd/auto.js +70 -28
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +52 -6
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +34 -2
- package/dist/resources/extensions/gsd/clean-root-preflight.js +93 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
- package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/error-classifier.js +10 -3
- package/dist/resources/extensions/gsd/exec-history.js +120 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
- package/dist/resources/extensions/gsd/gitignore.js +1 -0
- package/dist/resources/extensions/gsd/gsd-db.js +149 -31
- package/dist/resources/extensions/gsd/guided-flow.js +190 -1
- package/dist/resources/extensions/gsd/health-widget.js +4 -1
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/key-manager.js +28 -0
- package/dist/resources/extensions/gsd/model-router.js +36 -3
- package/dist/resources/extensions/gsd/pre-execution-checks.js +44 -9
- package/dist/resources/extensions/gsd/preferences-types.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
- package/dist/resources/extensions/gsd/preferences.js +17 -17
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +96 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +13 -5
- package/dist/resources/extensions/gsd/safety/safety-harness.js +5 -1
- package/dist/resources/extensions/gsd/token-counter.js +22 -5
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +16 -10
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
- package/dist/resources/extensions/gsd/uok/plan-v2.js +20 -3
- package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +50 -10
- package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/dist/resources/skills/write-docs/SKILL.md +2 -1
- 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 +17 -17
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.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/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 +17 -17
- 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/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/server.js +1 -1
- package/dist/welcome-screen.js +6 -1
- package/dist/wizard.js +2 -0
- package/package.json +1 -1
- package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
- package/packages/mcp-server/dist/remote-questions.js +732 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts +7 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +70 -8
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.d.ts +14 -0
- package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +49 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +64 -25
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -1
- package/packages/mcp-server/src/mcp-server.test.ts +67 -0
- package/packages/mcp-server/src/remote-questions.test.ts +294 -0
- package/packages/mcp-server/src/remote-questions.ts +916 -0
- package/packages/mcp-server/src/server.ts +89 -14
- package/packages/mcp-server/src/session-manager.ts +43 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
- package/packages/mcp-server/src/workflow-tools.ts +84 -43
- package/packages/mcp-server/tsconfig.test.json +19 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/models/custom.d.ts +38 -0
- package/packages/pi-ai/dist/models/custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/custom.js +41 -0
- package/packages/pi-ai/dist/models/custom.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js +1 -1
- package/packages/pi-ai/dist/providers/anthropic-auth.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +27 -4
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +8 -3
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js +80 -0
- package/packages/pi-ai/dist/providers/minimax-tool-name.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +16 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/models/custom.ts +42 -0
- package/packages/pi-ai/src/providers/anthropic-auth.test.ts +1 -1
- package/packages/pi-ai/src/providers/anthropic-shared.ts +26 -5
- package/packages/pi-ai/src/providers/anthropic.ts +9 -3
- package/packages/pi-ai/src/providers/minimax-tool-name.test.ts +98 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/simple-options.ts +17 -1
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +3 -2
- 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.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
- package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +90 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +10 -6
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +45 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- 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 +13 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +3 -2
- package/packages/pi-coding-agent/src/core/agent-session.ts +11 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +2 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +102 -10
- package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
- package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -1
- package/packages/pi-coding-agent/src/core/session-manager.ts +10 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
- package/src/resources/extensions/gsd/auto/loop-deps.ts +14 -0
- package/src/resources/extensions/gsd/auto/loop.ts +9 -0
- package/src/resources/extensions/gsd/auto/phases.ts +82 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +40 -2
- package/src/resources/extensions/gsd/auto/session.ts +35 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +16 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +17 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +28 -1
- package/src/resources/extensions/gsd/auto-recovery.ts +26 -1
- package/src/resources/extensions/gsd/auto-start.ts +60 -68
- package/src/resources/extensions/gsd/auto-worktree.ts +62 -63
- package/src/resources/extensions/gsd/auto.ts +73 -28
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +54 -6
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +35 -2
- package/src/resources/extensions/gsd/clean-root-preflight.ts +111 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
- package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/error-classifier.ts +10 -3
- package/src/resources/extensions/gsd/exec-history.ts +153 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
- package/src/resources/extensions/gsd/gitignore.ts +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +157 -33
- package/src/resources/extensions/gsd/guided-flow.ts +222 -1
- package/src/resources/extensions/gsd/health-widget.ts +3 -1
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/journal.ts +2 -1
- package/src/resources/extensions/gsd/key-manager.ts +28 -0
- package/src/resources/extensions/gsd/model-router.ts +42 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +46 -10
- package/src/resources/extensions/gsd/preferences-types.ts +46 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
- package/src/resources/extensions/gsd/preferences.ts +17 -17
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
- package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +119 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +17 -4
- package/src/resources/extensions/gsd/safety/safety-harness.ts +9 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +188 -2
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/auto-start-bootstrap-await-3420.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +237 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +447 -1
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/integration/gitignore-tracked-gsd.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +272 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +356 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/resume-dispatch-worktree.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +413 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -5
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
- package/src/resources/extensions/gsd/token-counter.ts +22 -5
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
- package/src/resources/extensions/gsd/uok/plan-v2.ts +26 -3
- package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +54 -9
- package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
- package/src/resources/skills/write-docs/SKILL.md +2 -1
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → JgU2F-5N9mTyB7kUSSk9A}/_ssgManifest.js +0 -0
|
@@ -186,6 +186,45 @@ test("flat_rate_providers is a recognized preference key (no warning)", () => {
|
|
|
186
186
|
);
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
test("slice_parallel preferences validate and pass through", () => {
|
|
190
|
+
const { preferences, errors, warnings } = validatePreferences({
|
|
191
|
+
slice_parallel: { enabled: true, max_workers: 8 },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
assert.equal(errors.length, 0);
|
|
195
|
+
assert.equal(warnings.filter(w => w.includes("slice_parallel")).length, 0);
|
|
196
|
+
assert.deepEqual(preferences.slice_parallel, { enabled: true, max_workers: 8 });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("slice_parallel rejects invalid values and warns on unknown keys", () => {
|
|
200
|
+
const { preferences, errors, warnings } = validatePreferences({
|
|
201
|
+
slice_parallel: {
|
|
202
|
+
enabled: "yes",
|
|
203
|
+
max_workers: 9,
|
|
204
|
+
future_mode: true,
|
|
205
|
+
},
|
|
206
|
+
} as any);
|
|
207
|
+
|
|
208
|
+
assert.ok(errors.some(e => e.includes("slice_parallel.enabled")), "should reject non-boolean enabled");
|
|
209
|
+
assert.ok(errors.some(e => e.includes("slice_parallel.max_workers")), "should reject max_workers outside 1..8");
|
|
210
|
+
assert.ok(warnings.some(w => w.includes('unknown slice_parallel key "future_mode"')));
|
|
211
|
+
assert.equal(preferences.slice_parallel, undefined);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("slice_parallel numeric max_workers is bounded to 1..8", () => {
|
|
215
|
+
const low = validatePreferences({ slice_parallel: { max_workers: 1 } });
|
|
216
|
+
const high = validatePreferences({ slice_parallel: { max_workers: 8 } });
|
|
217
|
+
const tooLow = validatePreferences({ slice_parallel: { max_workers: 0 } });
|
|
218
|
+
const tooHigh = validatePreferences({ slice_parallel: { max_workers: 9 } });
|
|
219
|
+
|
|
220
|
+
assert.equal(low.errors.length, 0);
|
|
221
|
+
assert.equal(low.preferences.slice_parallel?.max_workers, 1);
|
|
222
|
+
assert.equal(high.errors.length, 0);
|
|
223
|
+
assert.equal(high.preferences.slice_parallel?.max_workers, 8);
|
|
224
|
+
assert.ok(tooLow.errors.some(e => e.includes("slice_parallel.max_workers")));
|
|
225
|
+
assert.ok(tooHigh.errors.some(e => e.includes("slice_parallel.max_workers")));
|
|
226
|
+
});
|
|
227
|
+
|
|
189
228
|
test("valid values pass through correctly", () => {
|
|
190
229
|
const { preferences: p1 } = validatePreferences({ budget_enforcement: "halt" });
|
|
191
230
|
assert.equal(p1.budget_enforcement, "halt");
|
|
@@ -606,6 +645,44 @@ test("loadEffectiveGSDPreferences preserves experimental prefs across global+pro
|
|
|
606
645
|
}
|
|
607
646
|
});
|
|
608
647
|
|
|
648
|
+
test("loadEffectiveGSDPreferences exposes slice_parallel prefs to runtime callers", () => {
|
|
649
|
+
const originalCwd = process.cwd();
|
|
650
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
651
|
+
const tempProject = mkdtempSync(join(tmpdir(), "gsd-slice-parallel-project-"));
|
|
652
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-slice-parallel-home-"));
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
656
|
+
|
|
657
|
+
writeFileSync(
|
|
658
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
659
|
+
[
|
|
660
|
+
"---",
|
|
661
|
+
"version: 1",
|
|
662
|
+
"slice_parallel:",
|
|
663
|
+
" enabled: true",
|
|
664
|
+
" max_workers: 3",
|
|
665
|
+
"---",
|
|
666
|
+
].join("\n"),
|
|
667
|
+
"utf-8",
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
671
|
+
process.chdir(tempProject);
|
|
672
|
+
|
|
673
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
674
|
+
assert.notEqual(loaded, null);
|
|
675
|
+
assert.equal(loaded!.preferences.slice_parallel?.enabled, true);
|
|
676
|
+
assert.equal(loaded!.preferences.slice_parallel?.max_workers, 3);
|
|
677
|
+
} finally {
|
|
678
|
+
process.chdir(originalCwd);
|
|
679
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
680
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
681
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
682
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
609
686
|
test("preferences paths use canonical uppercase filenames", () => {
|
|
610
687
|
const originalCwd = process.cwd();
|
|
611
688
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -632,6 +709,39 @@ test("preferences paths use canonical uppercase filenames", () => {
|
|
|
632
709
|
}
|
|
633
710
|
});
|
|
634
711
|
|
|
712
|
+
test("explicit base path preference loading survives a deleted cwd (#4498)", (t) => {
|
|
713
|
+
const originalCwd = process.cwd();
|
|
714
|
+
const originalGsdHome = process.env.GSD_HOME;
|
|
715
|
+
const tempProject = mkdtempSync(join(tmpdir(), "gsd-prefs-base-project-"));
|
|
716
|
+
const tempGsdHome = mkdtempSync(join(tmpdir(), "gsd-prefs-base-home-"));
|
|
717
|
+
const deletedCwd = mkdtempSync(join(tmpdir(), "gsd-prefs-deleted-cwd-"));
|
|
718
|
+
|
|
719
|
+
t.after(() => {
|
|
720
|
+
process.chdir(originalCwd);
|
|
721
|
+
if (originalGsdHome === undefined) delete process.env.GSD_HOME;
|
|
722
|
+
else process.env.GSD_HOME = originalGsdHome;
|
|
723
|
+
rmSync(tempProject, { recursive: true, force: true });
|
|
724
|
+
rmSync(tempGsdHome, { recursive: true, force: true });
|
|
725
|
+
rmSync(deletedCwd, { recursive: true, force: true });
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
mkdirSync(join(tempProject, ".gsd"), { recursive: true });
|
|
729
|
+
writeFileSync(
|
|
730
|
+
join(tempProject, ".gsd", "PREFERENCES.md"),
|
|
731
|
+
"---\nversion: 1\nlanguage: Swedish\ngit:\n isolation: worktree\n---\n",
|
|
732
|
+
"utf-8",
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
process.env.GSD_HOME = tempGsdHome;
|
|
736
|
+
process.chdir(deletedCwd);
|
|
737
|
+
rmSync(deletedCwd, { recursive: true, force: true });
|
|
738
|
+
|
|
739
|
+
const loaded = loadEffectiveGSDPreferences(tempProject);
|
|
740
|
+
assert.notEqual(loaded, null);
|
|
741
|
+
assert.equal(loaded!.preferences.language, "Swedish");
|
|
742
|
+
assert.equal(getIsolationMode(tempProject), "worktree");
|
|
743
|
+
});
|
|
744
|
+
|
|
635
745
|
test("uppercase PREFERENCES.md wins over legacy lowercase preferences.md", () => {
|
|
636
746
|
const originalCwd = process.cwd();
|
|
637
747
|
const originalGsdHome = process.env.GSD_HOME;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Guard test — every key in KNOWN_PREFERENCE_KEYS must be reachable from the
|
|
2
|
+
// /gsd prefs wizard. Without this guard, a new preference can be added to the
|
|
3
|
+
// schema without anyone wiring it into the TUI, silently re-creating the gap
|
|
4
|
+
// this test exists to prevent.
|
|
5
|
+
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
|
|
10
|
+
import { KNOWN_PREFERENCE_KEYS } from "../preferences-types.ts";
|
|
11
|
+
|
|
12
|
+
// Keys exposed via a dedicated command rather than the wizard. They're still
|
|
13
|
+
// reachable by the user, just not inside the category menu flow. If you add a
|
|
14
|
+
// new key here, add a comment explaining where it lives.
|
|
15
|
+
const EXPOSED_OUTSIDE_WIZARD = new Set<string>([
|
|
16
|
+
"version", // auto-managed by writePreferencesFile
|
|
17
|
+
"modelOverrides", // advanced routing — edit PREFERENCES.md directly (not in KNOWN_PREFERENCE_KEYS)
|
|
18
|
+
"context_mode", // advanced sandbox config (gsd_exec + compaction) — enabled by default; edit PREFERENCES.md directly to tune timeouts/caps. Wizard coverage tracked separately.
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
test("every KNOWN_PREFERENCE_KEYS entry is reachable from the wizard source", () => {
|
|
22
|
+
const src = readFileSync(
|
|
23
|
+
new URL("../commands-prefs-wizard.ts", import.meta.url),
|
|
24
|
+
"utf-8",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const missing: string[] = [];
|
|
28
|
+
for (const key of KNOWN_PREFERENCE_KEYS) {
|
|
29
|
+
if (EXPOSED_OUTSIDE_WIZARD.has(key)) continue;
|
|
30
|
+
// The key must appear somewhere in the wizard — either as a direct
|
|
31
|
+
// prefs[...] / pref reference, or in the orderedKeys serialization list.
|
|
32
|
+
// A plain substring match is enough because all prefs-wizard references
|
|
33
|
+
// use the exact key name.
|
|
34
|
+
if (!src.includes(`"${key}"`) && !src.includes(`.${key}`)) {
|
|
35
|
+
missing.push(key);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
assert.deepEqual(
|
|
40
|
+
missing,
|
|
41
|
+
[],
|
|
42
|
+
`These preference keys are in KNOWN_PREFERENCE_KEYS but are not referenced anywhere in the /gsd prefs wizard — they cannot be configured through the UI. Either add wizard coverage or add them to EXPOSED_OUTSIDE_WIZARD with an explanatory comment:\n${missing.join("\n")}`,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { resolveExtensionDirFromCandidates } from "../prompt-loader.ts";
|
|
6
|
+
|
|
7
|
+
function makeExists(paths: Set<string>): (path: string) => boolean {
|
|
8
|
+
return (path: string) => paths.has(path);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test("resolveExtensionDirFromCandidates prefers user-local dir when both trees are valid", () => {
|
|
12
|
+
const moduleDir = "/npm/global/gsd";
|
|
13
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
14
|
+
const paths = new Set<string>([
|
|
15
|
+
join(moduleDir, "prompts"),
|
|
16
|
+
join(moduleDir, "templates", "task-summary.md"),
|
|
17
|
+
join(agentDir, "prompts"),
|
|
18
|
+
join(agentDir, "templates", "task-summary.md"),
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
22
|
+
assert.equal(resolved, agentDir);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("resolveExtensionDirFromCandidates rejects module dir missing task-summary template", () => {
|
|
26
|
+
const moduleDir = "/npm/global/gsd";
|
|
27
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
28
|
+
const paths = new Set<string>([
|
|
29
|
+
join(moduleDir, "prompts"),
|
|
30
|
+
// Missing module templates/task-summary.md on purpose.
|
|
31
|
+
join(agentDir, "prompts"),
|
|
32
|
+
join(agentDir, "templates", "task-summary.md"),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
36
|
+
assert.equal(resolved, agentDir);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolveExtensionDirFromCandidates falls back to prompts-only dir when neither tree is fully valid", () => {
|
|
40
|
+
const moduleDir = "/npm/global/gsd";
|
|
41
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
42
|
+
const paths = new Set<string>([
|
|
43
|
+
join(moduleDir, "prompts"),
|
|
44
|
+
// Neither side has templates/task-summary.md.
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
48
|
+
assert.equal(resolved, moduleDir);
|
|
49
|
+
});
|
|
@@ -45,6 +45,13 @@ test("classifyError treats usage-limit phrasing as transient rate-limit (#4373)"
|
|
|
45
45
|
assert.equal(result.kind, "rate-limit");
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
test("classifyError treats extra-usage phrasing as transient rate-limit (#4397)", () => {
|
|
49
|
+
const result = classifyError("You are out of extra usage. Please wait before retrying.");
|
|
50
|
+
assert.ok(isTransient(result));
|
|
51
|
+
assert.equal(result.kind, "rate-limit");
|
|
52
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs === 60_000);
|
|
53
|
+
});
|
|
54
|
+
|
|
48
55
|
test("classifyError treats OpenRouter affordability errors as transient rate-limit class", () => {
|
|
49
56
|
const result = classifyError(
|
|
50
57
|
"402 This request requires more credits, or fewer max_tokens. You requested up to 32000 tokens, but can only afford 329.",
|
|
@@ -673,6 +680,47 @@ test("MAX_TRANSIENT_AUTO_RESUMES is at least 8 for sustained overload resilience
|
|
|
673
680
|
);
|
|
674
681
|
});
|
|
675
682
|
|
|
683
|
+
// ── Stream idle timeout / partial response (#4558) ──────────────────────────
|
|
684
|
+
|
|
685
|
+
test("classifyError: 'Stream idle timeout - partial response received' is transient network", () => {
|
|
686
|
+
const result = classifyError("API Error: Stream idle timeout - partial response received");
|
|
687
|
+
assert.ok(isTransient(result), "stream idle timeout must be transient");
|
|
688
|
+
assert.equal(result.kind, "network");
|
|
689
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs > 0);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("classifyError: 'stream idle timeout' (lowercase) is transient network", () => {
|
|
693
|
+
const result = classifyError("stream idle timeout");
|
|
694
|
+
assert.ok(isTransient(result), "lowercase stream idle timeout must be transient");
|
|
695
|
+
assert.equal(result.kind, "network");
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
test("classifyError: 'partial response received' alone is transient network", () => {
|
|
699
|
+
const result = classifyError("partial response received");
|
|
700
|
+
assert.ok(isTransient(result), "partial response received must be transient");
|
|
701
|
+
assert.equal(result.kind, "network");
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// ── Context overflow / context window exceeded (#4528) ───────────────────────
|
|
705
|
+
|
|
706
|
+
test("classifyError: MiniMax context window error is transient server", () => {
|
|
707
|
+
const result = classifyError("400 invalid params, context window exceeds limit (2013)");
|
|
708
|
+
assert.ok(isTransient(result), "context window exceeded must be transient");
|
|
709
|
+
assert.equal(result.kind, "server");
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
test("classifyError: 'context length exceeded' is transient server", () => {
|
|
713
|
+
const result = classifyError("context length exceeded: max 128000 tokens");
|
|
714
|
+
assert.ok(isTransient(result), "context length exceeded must be transient");
|
|
715
|
+
assert.equal(result.kind, "server");
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("classifyError: 'context window' with 'exceed' is transient server", () => {
|
|
719
|
+
const result = classifyError("context window exceeded for this model");
|
|
720
|
+
assert.ok(isTransient(result), "context window exceeded must be transient");
|
|
721
|
+
assert.equal(result.kind, "server");
|
|
722
|
+
});
|
|
723
|
+
|
|
676
724
|
// ── agent-session retryable regex handles server_error (#1166) ──────────────
|
|
677
725
|
|
|
678
726
|
test("agent-session retryable error regex matches server_error (underscore)", () => {
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD-2 / guided-flow — regression tests for #4573
|
|
3
|
+
*
|
|
4
|
+
* Covers two recovery paths:
|
|
5
|
+
* - maybeHandleReadyPhraseWithoutFiles: nudge when LLM emits
|
|
6
|
+
* "Milestone M001 ready." without writing CONTEXT.md / ROADMAP.md
|
|
7
|
+
* - maybeHandleEmptyIntentTurn: nudge when LLM narrates intent but
|
|
8
|
+
* emits no tool-use blocks
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, beforeEach } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
setPendingAutoStart,
|
|
19
|
+
clearPendingAutoStart,
|
|
20
|
+
maybeHandleReadyPhraseWithoutFiles,
|
|
21
|
+
maybeHandleEmptyIntentTurn,
|
|
22
|
+
resetEmptyTurnCounter,
|
|
23
|
+
} from "../guided-flow.ts";
|
|
24
|
+
|
|
25
|
+
// ─── Test harness ──────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
interface MockCapture {
|
|
28
|
+
notifies: Array<{ msg: string; level: string }>;
|
|
29
|
+
messages: Array<{ payload: any; options: any }>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function mkCapture(): MockCapture {
|
|
33
|
+
return { notifies: [], messages: [] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mkCtx(cap: MockCapture): any {
|
|
37
|
+
return {
|
|
38
|
+
ui: {
|
|
39
|
+
notify: (msg: string, level: string) => {
|
|
40
|
+
cap.notifies.push({ msg, level });
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function mkPi(cap: MockCapture, opts: { sendThrows?: boolean } = {}): any {
|
|
47
|
+
return {
|
|
48
|
+
sendMessage: (payload: any, options: any) => {
|
|
49
|
+
if (opts.sendThrows) throw new Error("send failed");
|
|
50
|
+
cap.messages.push({ payload, options });
|
|
51
|
+
},
|
|
52
|
+
setActiveTools: () => undefined,
|
|
53
|
+
getActiveTools: () => [],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mkBase(): string {
|
|
58
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-4573-"));
|
|
59
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
60
|
+
return base;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function assistantMsg(text: string, opts: { toolUse?: boolean } = {}): any {
|
|
64
|
+
const content: any[] = [];
|
|
65
|
+
if (text) content.push({ type: "text", text });
|
|
66
|
+
if (opts.toolUse) content.push({ type: "tool_use", name: "whatever", input: {} });
|
|
67
|
+
return { role: "assistant", content };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── ready-phrase recovery (Layer 2) ───────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
describe("#4573 maybeHandleReadyPhraseWithoutFiles", () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
clearPendingAutoStart();
|
|
75
|
+
resetEmptyTurnCounter();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("no pending entry → no-op", () => {
|
|
79
|
+
const cap = mkCapture();
|
|
80
|
+
const event = { messages: [assistantMsg("Milestone M001 ready.")] };
|
|
81
|
+
const handled = maybeHandleReadyPhraseWithoutFiles(event);
|
|
82
|
+
assert.equal(handled, false);
|
|
83
|
+
assert.equal(cap.messages.length, 0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("pending entry, ready phrase, no files → notify + sendMessage", () => {
|
|
87
|
+
const base = mkBase();
|
|
88
|
+
try {
|
|
89
|
+
const cap = mkCapture();
|
|
90
|
+
setPendingAutoStart(base, {
|
|
91
|
+
basePath: base,
|
|
92
|
+
milestoneId: "M001",
|
|
93
|
+
ctx: mkCtx(cap),
|
|
94
|
+
pi: mkPi(cap),
|
|
95
|
+
});
|
|
96
|
+
const handled = maybeHandleReadyPhraseWithoutFiles({
|
|
97
|
+
messages: [assistantMsg("Milestone M001 ready.")],
|
|
98
|
+
});
|
|
99
|
+
assert.equal(handled, true);
|
|
100
|
+
assert.equal(cap.messages.length, 1);
|
|
101
|
+
assert.equal(cap.messages[0].payload.customType, "gsd-ready-no-files");
|
|
102
|
+
assert.equal(cap.messages[0].options.triggerTurn, true);
|
|
103
|
+
assert.ok(
|
|
104
|
+
cap.notifies.some((n) => /rejected/.test(n.msg)),
|
|
105
|
+
"user notified about rejection",
|
|
106
|
+
);
|
|
107
|
+
} finally {
|
|
108
|
+
clearPendingAutoStart();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("retry cap — after MAX_READY_REJECTS the nudge stops and entry clears", () => {
|
|
113
|
+
const base = mkBase();
|
|
114
|
+
try {
|
|
115
|
+
const cap = mkCapture();
|
|
116
|
+
setPendingAutoStart(base, {
|
|
117
|
+
basePath: base,
|
|
118
|
+
milestoneId: "M001",
|
|
119
|
+
ctx: mkCtx(cap),
|
|
120
|
+
pi: mkPi(cap),
|
|
121
|
+
});
|
|
122
|
+
const event = { messages: [assistantMsg("Milestone M001 ready.")] };
|
|
123
|
+
|
|
124
|
+
const first = maybeHandleReadyPhraseWithoutFiles(event);
|
|
125
|
+
const second = maybeHandleReadyPhraseWithoutFiles(event);
|
|
126
|
+
const third = maybeHandleReadyPhraseWithoutFiles(event); // > MAX
|
|
127
|
+
|
|
128
|
+
assert.equal(first, true);
|
|
129
|
+
assert.equal(second, true);
|
|
130
|
+
assert.equal(third, true); // still returns true (handled via give-up)
|
|
131
|
+
assert.equal(cap.messages.length, 2, "only 2 nudges sent (MAX_READY_REJECTS=2)");
|
|
132
|
+
assert.ok(
|
|
133
|
+
cap.notifies.some((n) => /Stopping auto-nudge/.test(n.msg)),
|
|
134
|
+
"gives up with error notify",
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// After giving up, a fresh re-entry starts clean
|
|
138
|
+
const fourth = maybeHandleReadyPhraseWithoutFiles(event);
|
|
139
|
+
assert.equal(fourth, false, "pending entry was cleared — nothing to handle");
|
|
140
|
+
} finally {
|
|
141
|
+
clearPendingAutoStart();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("files present → no nudge (happy path already fired)", () => {
|
|
146
|
+
const base = mkBase();
|
|
147
|
+
try {
|
|
148
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# ctx");
|
|
149
|
+
const cap = mkCapture();
|
|
150
|
+
setPendingAutoStart(base, {
|
|
151
|
+
basePath: base,
|
|
152
|
+
milestoneId: "M001",
|
|
153
|
+
ctx: mkCtx(cap),
|
|
154
|
+
pi: mkPi(cap),
|
|
155
|
+
});
|
|
156
|
+
const handled = maybeHandleReadyPhraseWithoutFiles({
|
|
157
|
+
messages: [assistantMsg("Milestone M001 ready.")],
|
|
158
|
+
});
|
|
159
|
+
assert.equal(handled, false);
|
|
160
|
+
assert.equal(cap.messages.length, 0);
|
|
161
|
+
} finally {
|
|
162
|
+
clearPendingAutoStart();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("last message lacks ready phrase → no-op", () => {
|
|
167
|
+
const base = mkBase();
|
|
168
|
+
try {
|
|
169
|
+
const cap = mkCapture();
|
|
170
|
+
setPendingAutoStart(base, {
|
|
171
|
+
basePath: base,
|
|
172
|
+
milestoneId: "M001",
|
|
173
|
+
ctx: mkCtx(cap),
|
|
174
|
+
pi: mkPi(cap),
|
|
175
|
+
});
|
|
176
|
+
const handled = maybeHandleReadyPhraseWithoutFiles({
|
|
177
|
+
messages: [assistantMsg("Let me think about the slices first.")],
|
|
178
|
+
});
|
|
179
|
+
assert.equal(handled, false);
|
|
180
|
+
assert.equal(cap.messages.length, 0);
|
|
181
|
+
} finally {
|
|
182
|
+
clearPendingAutoStart();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("fresh entry after give-up resets counter", () => {
|
|
187
|
+
const base = mkBase();
|
|
188
|
+
try {
|
|
189
|
+
const cap = mkCapture();
|
|
190
|
+
// First cycle: exhaust cap
|
|
191
|
+
setPendingAutoStart(base, {
|
|
192
|
+
basePath: base,
|
|
193
|
+
milestoneId: "M001",
|
|
194
|
+
ctx: mkCtx(cap),
|
|
195
|
+
pi: mkPi(cap),
|
|
196
|
+
});
|
|
197
|
+
const event = { messages: [assistantMsg("Milestone M001 ready.")] };
|
|
198
|
+
maybeHandleReadyPhraseWithoutFiles(event);
|
|
199
|
+
maybeHandleReadyPhraseWithoutFiles(event);
|
|
200
|
+
maybeHandleReadyPhraseWithoutFiles(event); // clears entry
|
|
201
|
+
|
|
202
|
+
// New /gsd run — re-seeds entry; counter must be 0 again
|
|
203
|
+
cap.messages.length = 0;
|
|
204
|
+
setPendingAutoStart(base, {
|
|
205
|
+
basePath: base,
|
|
206
|
+
milestoneId: "M001",
|
|
207
|
+
ctx: mkCtx(cap),
|
|
208
|
+
pi: mkPi(cap),
|
|
209
|
+
});
|
|
210
|
+
const handled = maybeHandleReadyPhraseWithoutFiles(event);
|
|
211
|
+
assert.equal(handled, true);
|
|
212
|
+
assert.equal(cap.messages.length, 1, "fresh entry fires nudge again");
|
|
213
|
+
} finally {
|
|
214
|
+
clearPendingAutoStart();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// ─── empty-turn recovery (Layer 3) ────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
describe("#4573 maybeHandleEmptyIntentTurn", () => {
|
|
222
|
+
beforeEach(() => {
|
|
223
|
+
clearPendingAutoStart();
|
|
224
|
+
resetEmptyTurnCounter();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("no pending entry + isAuto false → no-op (interactive discuss is user-driven)", () => {
|
|
228
|
+
const event = { messages: [assistantMsg("I'll write the CONTEXT.md now.")] };
|
|
229
|
+
const handled = maybeHandleEmptyIntentTurn(event, false);
|
|
230
|
+
assert.equal(handled, false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("text-only turn WITHOUT commit phrase → not flagged (legitimate text)", () => {
|
|
234
|
+
const base = mkBase();
|
|
235
|
+
try {
|
|
236
|
+
const cap = mkCapture();
|
|
237
|
+
setPendingAutoStart(base, {
|
|
238
|
+
basePath: base,
|
|
239
|
+
milestoneId: "M001",
|
|
240
|
+
ctx: mkCtx(cap),
|
|
241
|
+
pi: mkPi(cap),
|
|
242
|
+
});
|
|
243
|
+
const handled = maybeHandleEmptyIntentTurn(
|
|
244
|
+
{ messages: [assistantMsg("Here is the roadmap preview — three slices.")] },
|
|
245
|
+
false,
|
|
246
|
+
);
|
|
247
|
+
assert.equal(handled, false);
|
|
248
|
+
assert.equal(cap.messages.length, 0);
|
|
249
|
+
} finally {
|
|
250
|
+
clearPendingAutoStart();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("text-only turn ending in question → treated as user-handoff, not flagged", () => {
|
|
255
|
+
const base = mkBase();
|
|
256
|
+
try {
|
|
257
|
+
const cap = mkCapture();
|
|
258
|
+
setPendingAutoStart(base, {
|
|
259
|
+
basePath: base,
|
|
260
|
+
milestoneId: "M001",
|
|
261
|
+
ctx: mkCtx(cap),
|
|
262
|
+
pi: mkPi(cap),
|
|
263
|
+
});
|
|
264
|
+
const handled = maybeHandleEmptyIntentTurn(
|
|
265
|
+
{ messages: [assistantMsg("Ready to write, or want to adjust?")] },
|
|
266
|
+
false,
|
|
267
|
+
);
|
|
268
|
+
assert.equal(handled, false);
|
|
269
|
+
} finally {
|
|
270
|
+
clearPendingAutoStart();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("commit-intent phrase WITHOUT tool call → nudge fires", () => {
|
|
275
|
+
const base = mkBase();
|
|
276
|
+
try {
|
|
277
|
+
const cap = mkCapture();
|
|
278
|
+
setPendingAutoStart(base, {
|
|
279
|
+
basePath: base,
|
|
280
|
+
milestoneId: "M001",
|
|
281
|
+
ctx: mkCtx(cap),
|
|
282
|
+
pi: mkPi(cap),
|
|
283
|
+
});
|
|
284
|
+
const handled = maybeHandleEmptyIntentTurn(
|
|
285
|
+
{ messages: [assistantMsg("I'll now write the CONTEXT.md file.")] },
|
|
286
|
+
false,
|
|
287
|
+
);
|
|
288
|
+
assert.equal(handled, true);
|
|
289
|
+
assert.equal(cap.messages.length, 1);
|
|
290
|
+
assert.equal(cap.messages[0].payload.customType, "gsd-empty-turn-recovery");
|
|
291
|
+
} finally {
|
|
292
|
+
clearPendingAutoStart();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("commit-intent WITH tool-use block → not flagged", () => {
|
|
297
|
+
const base = mkBase();
|
|
298
|
+
try {
|
|
299
|
+
const cap = mkCapture();
|
|
300
|
+
setPendingAutoStart(base, {
|
|
301
|
+
basePath: base,
|
|
302
|
+
milestoneId: "M001",
|
|
303
|
+
ctx: mkCtx(cap),
|
|
304
|
+
pi: mkPi(cap),
|
|
305
|
+
});
|
|
306
|
+
const handled = maybeHandleEmptyIntentTurn(
|
|
307
|
+
{ messages: [assistantMsg("I'll write the file now.", { toolUse: true })] },
|
|
308
|
+
false,
|
|
309
|
+
);
|
|
310
|
+
assert.equal(handled, false);
|
|
311
|
+
assert.equal(cap.messages.length, 0);
|
|
312
|
+
} finally {
|
|
313
|
+
clearPendingAutoStart();
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("ready phrase is NOT treated as empty-turn (handled by other recovery path)", () => {
|
|
318
|
+
const base = mkBase();
|
|
319
|
+
try {
|
|
320
|
+
const cap = mkCapture();
|
|
321
|
+
setPendingAutoStart(base, {
|
|
322
|
+
basePath: base,
|
|
323
|
+
milestoneId: "M001",
|
|
324
|
+
ctx: mkCtx(cap),
|
|
325
|
+
pi: mkPi(cap),
|
|
326
|
+
});
|
|
327
|
+
const handled = maybeHandleEmptyIntentTurn(
|
|
328
|
+
{ messages: [assistantMsg("Milestone M001 ready.")] },
|
|
329
|
+
false,
|
|
330
|
+
);
|
|
331
|
+
assert.equal(handled, false);
|
|
332
|
+
} finally {
|
|
333
|
+
clearPendingAutoStart();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("empty-turn retry cap — stops after MAX_EMPTY_TURN_RETRIES", () => {
|
|
338
|
+
const base = mkBase();
|
|
339
|
+
try {
|
|
340
|
+
const cap = mkCapture();
|
|
341
|
+
setPendingAutoStart(base, {
|
|
342
|
+
basePath: base,
|
|
343
|
+
milestoneId: "M001",
|
|
344
|
+
ctx: mkCtx(cap),
|
|
345
|
+
pi: mkPi(cap),
|
|
346
|
+
});
|
|
347
|
+
const event = { messages: [assistantMsg("I'll write the CONTEXT.md file.")] };
|
|
348
|
+
|
|
349
|
+
maybeHandleEmptyIntentTurn(event, false); // 1
|
|
350
|
+
maybeHandleEmptyIntentTurn(event, false); // 2
|
|
351
|
+
const third = maybeHandleEmptyIntentTurn(event, false); // > cap
|
|
352
|
+
|
|
353
|
+
assert.equal(cap.messages.length, 2, "only 2 nudges sent");
|
|
354
|
+
assert.equal(third, false, "after cap, no further injection");
|
|
355
|
+
assert.ok(
|
|
356
|
+
cap.notifies.some((n) => /Stopping auto-nudge/.test(n.msg)),
|
|
357
|
+
"user notified of give-up",
|
|
358
|
+
);
|
|
359
|
+
} finally {
|
|
360
|
+
clearPendingAutoStart();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("resetEmptyTurnCounter clears state after a successful tool-use turn", () => {
|
|
365
|
+
const base = mkBase();
|
|
366
|
+
try {
|
|
367
|
+
const cap = mkCapture();
|
|
368
|
+
setPendingAutoStart(base, {
|
|
369
|
+
basePath: base,
|
|
370
|
+
milestoneId: "M001",
|
|
371
|
+
ctx: mkCtx(cap),
|
|
372
|
+
pi: mkPi(cap),
|
|
373
|
+
});
|
|
374
|
+
const event = { messages: [assistantMsg("I'll write the CONTEXT.md file.")] };
|
|
375
|
+
|
|
376
|
+
maybeHandleEmptyIntentTurn(event, false); // 1
|
|
377
|
+
maybeHandleEmptyIntentTurn(event, false); // 2 — at cap
|
|
378
|
+
resetEmptyTurnCounter(); // simulate a successful tool-use turn in between
|
|
379
|
+
|
|
380
|
+
cap.messages.length = 0;
|
|
381
|
+
const after = maybeHandleEmptyIntentTurn(event, false);
|
|
382
|
+
assert.equal(after, true, "counter reset — nudge fires again");
|
|
383
|
+
assert.equal(cap.messages.length, 1);
|
|
384
|
+
} finally {
|
|
385
|
+
clearPendingAutoStart();
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -45,9 +45,15 @@ describe('restore tools after discuss flow scoping (#3628)', () => {
|
|
|
45
45
|
})
|
|
46
46
|
|
|
47
47
|
it('savedTools is restored after sendMessage', () => {
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// #4573: guided-flow.ts now contains multiple `triggerTurn: true` calls
|
|
49
|
+
// (ready-phrase and empty-turn recovery paths). The discuss-flow scoping
|
|
50
|
+
// sendMessage is the one that follows `savedTools = currentTools`, so
|
|
51
|
+
// anchor the search there rather than at the first `triggerTurn: true`.
|
|
52
|
+
const savedToolsAssign = src.indexOf('savedTools = currentTools')
|
|
53
|
+
assert.ok(savedToolsAssign !== -1, 'savedTools = currentTools must exist')
|
|
54
|
+
|
|
55
|
+
const sendMsg = src.indexOf('triggerTurn: true', savedToolsAssign)
|
|
56
|
+
assert.ok(sendMsg !== -1, 'discuss-flow sendMessage with triggerTurn must exist after savedTools capture')
|
|
51
57
|
|
|
52
58
|
// After sendMessage, savedTools should be restored via setActiveTools
|
|
53
59
|
const afterSend = src.slice(sendMsg, sendMsg + 500)
|