gsd-pi 2.63.0-dev.026d309 → 2.63.0-dev.786f0ff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -134
- package/dist/cli.js +48 -6
- package/dist/headless-query.js +11 -1
- package/dist/help-text.js +4 -1
- package/dist/onboarding.js +15 -8
- package/dist/resource-loader.js +18 -3
- package/dist/resources/extensions/cmux/index.js +21 -12
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
- package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
- package/dist/resources/extensions/gsd/auto/loop.js +4 -0
- package/dist/resources/extensions/gsd/auto/phases.js +157 -22
- package/dist/resources/extensions/gsd/auto/session.js +12 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +9 -3
- package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
- package/dist/resources/extensions/gsd/auto-start.js +10 -21
- package/dist/resources/extensions/gsd/auto-timers.js +2 -1
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +19 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
- package/dist/resources/extensions/gsd/constants.js +42 -0
- package/dist/resources/extensions/gsd/db-writer.js +72 -4
- package/dist/resources/extensions/gsd/forensics.js +20 -4
- package/dist/resources/extensions/gsd/gsd-db.js +64 -17
- package/dist/resources/extensions/gsd/guided-flow.js +19 -0
- package/dist/resources/extensions/gsd/metrics.js +27 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences.js +7 -2
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +4 -7
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
- package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
- package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
- package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
- package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
- package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
- package/dist/resources/extensions/gsd/state.js +74 -14
- package/dist/resources/extensions/gsd/status-guards.js +11 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
- package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
- package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
- package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
- package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
- package/dist/resources/extensions/mcp-client/auth.js +101 -0
- package/dist/resources/extensions/mcp-client/index.js +10 -1
- package/dist/resources/extensions/ollama/index.js +28 -22
- package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
- package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
- package/dist/resources/extensions/ollama/ollama-client.js +23 -32
- package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
- package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
- package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
- package/dist/update-cmd.js +4 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +50 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
- package/packages/pi-agent-core/src/agent-loop.ts +53 -0
- package/packages/pi-ai/dist/types.d.ts +16 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/types.ts +18 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
- package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
- package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
- package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
- package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
- package/src/resources/extensions/cmux/index.ts +18 -12
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
- package/src/resources/extensions/gsd/auto/loop.ts +5 -0
- package/src/resources/extensions/gsd/auto/phases.ts +194 -33
- package/src/resources/extensions/gsd/auto/session.ts +14 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
- package/src/resources/extensions/gsd/auto-start.ts +11 -20
- package/src/resources/extensions/gsd/auto-timers.ts +2 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +22 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
- package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
- package/src/resources/extensions/gsd/constants.ts +44 -0
- package/src/resources/extensions/gsd/db-writer.ts +78 -4
- package/src/resources/extensions/gsd/forensics.ts +21 -5
- package/src/resources/extensions/gsd/gsd-db.ts +64 -17
- package/src/resources/extensions/gsd/guided-flow.ts +22 -0
- package/src/resources/extensions/gsd/metrics.ts +28 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +9 -2
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +4 -7
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
- package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
- package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
- package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
- package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
- package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
- package/src/resources/extensions/gsd/state.ts +67 -12
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
- package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
- package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
- package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
- package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
- package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
- package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
- package/src/resources/extensions/gsd/types.ts +44 -22
- package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
- package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
- package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
- package/src/resources/extensions/mcp-client/auth.ts +149 -0
- package/src/resources/extensions/mcp-client/index.ts +16 -1
- package/src/resources/extensions/ollama/index.ts +26 -25
- package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
- package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
- package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
- package/src/resources/extensions/ollama/ollama-client.ts +30 -30
- package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
- package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
- package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
- package/src/resources/extensions/ollama/types.ts +23 -0
- package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TTlAguZQ5vR9EOv6G8cel → SDB1T-4NqkMjYirjjqQhr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test: S##-CONTEXT.md from slice discussion must be
|
|
3
|
+
* injected into all 5 downstream prompt builders (#3452).
|
|
4
|
+
*
|
|
5
|
+
* Scans auto-prompts.ts for the 5 builder functions and verifies
|
|
6
|
+
* each one resolves and inlines the slice-level CONTEXT file.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
|
|
17
|
+
const source = readFileSync(autoPromptsPath, "utf-8");
|
|
18
|
+
|
|
19
|
+
const BUILDERS = [
|
|
20
|
+
"buildResearchSlicePrompt",
|
|
21
|
+
"buildPlanSlicePrompt",
|
|
22
|
+
"buildCompleteSlicePrompt",
|
|
23
|
+
"buildReplanSlicePrompt",
|
|
24
|
+
"buildReassessRoadmapPrompt",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
describe("slice CONTEXT.md injection into prompt builders (#3452)", () => {
|
|
28
|
+
for (const builder of BUILDERS) {
|
|
29
|
+
test(`${builder} resolves slice CONTEXT file`, () => {
|
|
30
|
+
// Find the function body
|
|
31
|
+
const fnStart = source.indexOf(`export async function ${builder}`);
|
|
32
|
+
assert.ok(fnStart !== -1, `${builder} should exist in auto-prompts.ts`);
|
|
33
|
+
|
|
34
|
+
// Get a reasonable chunk after the function start (enough to cover the inlining section)
|
|
35
|
+
const chunk = source.slice(fnStart, fnStart + 3000);
|
|
36
|
+
|
|
37
|
+
// Must resolve the slice CONTEXT path
|
|
38
|
+
assert.ok(
|
|
39
|
+
chunk.includes('resolveSliceFile(base, mid,') && chunk.includes('"CONTEXT"'),
|
|
40
|
+
`${builder} should call resolveSliceFile with "CONTEXT"`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Must inline it with inlineFileOptional
|
|
44
|
+
assert.ok(
|
|
45
|
+
chunk.includes('Slice Context'),
|
|
46
|
+
`${builder} should inline slice CONTEXT with a "Slice Context" label`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for slice-level parallel conflict detection.
|
|
3
|
+
* Verifies hasFileConflict() correctly identifies when two slices
|
|
4
|
+
* touch too many of the same files to safely run in parallel.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
import { hasFileConflict } from "../slice-parallel-conflict.js";
|
|
14
|
+
|
|
15
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function makeTmpBase(): string {
|
|
18
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-slice-conflict-test-"));
|
|
19
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
20
|
+
return base;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeSlicePlan(base: string, mid: string, sid: string, content: string): void {
|
|
24
|
+
const dir = join(base, ".gsd", "milestones", mid, sid);
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
writeFileSync(join(dir, "PLAN.md"), content, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("hasFileConflict", () => {
|
|
30
|
+
let base: string;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
base = makeTmpBase();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
rmSync(base, { recursive: true, force: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("two slices with >5 overlapping file paths → blocked (true)", () => {
|
|
41
|
+
const planA = `# Plan S01
|
|
42
|
+
## Tasks
|
|
43
|
+
- T01: Update src/auth/login.ts
|
|
44
|
+
- T02: Update src/auth/register.ts
|
|
45
|
+
- T03: Update src/auth/session.ts
|
|
46
|
+
- T04: Update src/auth/middleware.ts
|
|
47
|
+
- T05: Update src/auth/types.ts
|
|
48
|
+
- T06: Update src/auth/utils.ts
|
|
49
|
+
`;
|
|
50
|
+
const planB = `# Plan S02
|
|
51
|
+
## Tasks
|
|
52
|
+
- T01: Refactor src/auth/login.ts
|
|
53
|
+
- T02: Refactor src/auth/register.ts
|
|
54
|
+
- T03: Refactor src/auth/session.ts
|
|
55
|
+
- T04: Refactor src/auth/middleware.ts
|
|
56
|
+
- T05: Refactor src/auth/types.ts
|
|
57
|
+
- T06: Refactor src/auth/utils.ts
|
|
58
|
+
`;
|
|
59
|
+
writeSlicePlan(base, "M001", "S01", planA);
|
|
60
|
+
writeSlicePlan(base, "M001", "S02", planB);
|
|
61
|
+
assert.equal(hasFileConflict(base, "M001", "S01", "S02"), true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("two slices with 0 overlapping paths → allowed (false)", () => {
|
|
65
|
+
const planA = `# Plan S01
|
|
66
|
+
## Tasks
|
|
67
|
+
- T01: Create src/api/routes.ts
|
|
68
|
+
- T02: Create src/api/handlers.ts
|
|
69
|
+
`;
|
|
70
|
+
const planB = `# Plan S02
|
|
71
|
+
## Tasks
|
|
72
|
+
- T01: Create src/ui/components.ts
|
|
73
|
+
- T02: Create src/ui/styles.ts
|
|
74
|
+
`;
|
|
75
|
+
writeSlicePlan(base, "M001", "S01", planA);
|
|
76
|
+
writeSlicePlan(base, "M001", "S02", planB);
|
|
77
|
+
assert.equal(hasFileConflict(base, "M001", "S01", "S02"), false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("missing PLAN.md → conservative block (true)", () => {
|
|
81
|
+
// Only create one slice's plan
|
|
82
|
+
writeSlicePlan(base, "M001", "S01", "# Plan\n- T01: src/foo.ts");
|
|
83
|
+
// S02 has no plan at all
|
|
84
|
+
assert.equal(hasFileConflict(base, "M001", "S01", "S02"), true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("one slice empty plan → allowed (false)", () => {
|
|
88
|
+
writeSlicePlan(base, "M001", "S01", "# Plan S01\n## Tasks\n- T01: Create src/foo.ts");
|
|
89
|
+
writeSlicePlan(base, "M001", "S02", "# Plan S02\n## Tasks\n(no tasks yet)");
|
|
90
|
+
assert.equal(hasFileConflict(base, "M001", "S01", "S02"), false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for slice-level parallel eligibility.
|
|
3
|
+
* Verifies getEligibleSlices() correctly determines which slices
|
|
4
|
+
* can run in parallel based on dependency satisfaction.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
|
|
10
|
+
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
11
|
+
|
|
12
|
+
describe("getEligibleSlices", () => {
|
|
13
|
+
it("diamond DAG: S01 done, S02 depends:[S01], S03 depends:[S01] → both eligible", () => {
|
|
14
|
+
const slices = [
|
|
15
|
+
{ id: "S01", done: true, depends: [] },
|
|
16
|
+
{ id: "S02", done: false, depends: ["S01"] },
|
|
17
|
+
{ id: "S03", done: false, depends: ["S01"] },
|
|
18
|
+
];
|
|
19
|
+
const completed = new Set(["S01"]);
|
|
20
|
+
const result = getEligibleSlices(slices, completed);
|
|
21
|
+
const ids = result.map(s => s.id);
|
|
22
|
+
assert.deepStrictEqual(ids.sort(), ["S02", "S03"]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("linear chain: S01→S02→S03, only S01 done → only S02 eligible", () => {
|
|
26
|
+
const slices = [
|
|
27
|
+
{ id: "S01", done: true, depends: [] },
|
|
28
|
+
{ id: "S02", done: false, depends: ["S01"] },
|
|
29
|
+
{ id: "S03", done: false, depends: ["S02"] },
|
|
30
|
+
];
|
|
31
|
+
const completed = new Set(["S01"]);
|
|
32
|
+
const result = getEligibleSlices(slices, completed);
|
|
33
|
+
assert.equal(result.length, 1);
|
|
34
|
+
assert.equal(result[0].id, "S02");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("no deps declared: S01 done, S02 no deps, S03 no deps → only S02 eligible (positional fallback)", () => {
|
|
38
|
+
const slices = [
|
|
39
|
+
{ id: "S01", done: true, depends: [] },
|
|
40
|
+
{ id: "S02", done: false, depends: [] },
|
|
41
|
+
{ id: "S03", done: false, depends: [] },
|
|
42
|
+
];
|
|
43
|
+
const completed = new Set(["S01"]);
|
|
44
|
+
const result = getEligibleSlices(slices, completed);
|
|
45
|
+
// Positional fallback: when no deps declared, only the first non-done slice
|
|
46
|
+
// after all positionally-earlier slices are done is eligible
|
|
47
|
+
assert.equal(result.length, 1);
|
|
48
|
+
assert.equal(result[0].id, "S02");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("all done: empty result", () => {
|
|
52
|
+
const slices = [
|
|
53
|
+
{ id: "S01", done: true, depends: [] },
|
|
54
|
+
{ id: "S02", done: true, depends: ["S01"] },
|
|
55
|
+
{ id: "S03", done: true, depends: ["S02"] },
|
|
56
|
+
];
|
|
57
|
+
const completed = new Set(["S01", "S02", "S03"]);
|
|
58
|
+
const result = getEligibleSlices(slices, completed);
|
|
59
|
+
assert.equal(result.length, 0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("empty input: empty result", () => {
|
|
63
|
+
const result = getEligibleSlices([], new Set());
|
|
64
|
+
assert.equal(result.length, 0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("mixed deps and no-deps: only dep-satisfied slices with explicit deps are eligible alongside positional", () => {
|
|
68
|
+
const slices = [
|
|
69
|
+
{ id: "S01", done: true, depends: [] },
|
|
70
|
+
{ id: "S02", done: false, depends: ["S01"] }, // explicit dep satisfied
|
|
71
|
+
{ id: "S03", done: false, depends: [] }, // no deps, positional fallback
|
|
72
|
+
{ id: "S04", done: false, depends: ["S01"] }, // explicit dep satisfied
|
|
73
|
+
];
|
|
74
|
+
const completed = new Set(["S01"]);
|
|
75
|
+
const result = getEligibleSlices(slices, completed);
|
|
76
|
+
const ids = result.map(s => s.id);
|
|
77
|
+
// S02 and S04 have explicit deps satisfied; S03 has no deps but
|
|
78
|
+
// positionally S02 (before it) is not done, so S03 is blocked by positional rule
|
|
79
|
+
assert.ok(ids.includes("S02"), "S02 should be eligible (dep on S01 satisfied)");
|
|
80
|
+
assert.ok(ids.includes("S04"), "S04 should be eligible (dep on S01 satisfied)");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("unsatisfied dependency blocks slice", () => {
|
|
84
|
+
const slices = [
|
|
85
|
+
{ id: "S01", done: false, depends: [] },
|
|
86
|
+
{ id: "S02", done: false, depends: ["S01"] },
|
|
87
|
+
];
|
|
88
|
+
const completed = new Set<string>();
|
|
89
|
+
const result = getEligibleSlices(slices, completed);
|
|
90
|
+
// S01 has no deps and is first → eligible by positional
|
|
91
|
+
// S02 depends on S01 which is not completed → blocked
|
|
92
|
+
assert.equal(result.length, 1);
|
|
93
|
+
assert.equal(result[0].id, "S01");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural tests for slice-level parallel orchestrator.
|
|
3
|
+
* Verifies the orchestrator module exists and has the correct shape,
|
|
4
|
+
* env var usage, and preference gating.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const gsdDir = join(__dirname, "..");
|
|
15
|
+
|
|
16
|
+
describe("slice-parallel-orchestrator structural tests", () => {
|
|
17
|
+
it("orchestrator uses GSD_SLICE_LOCK env var", () => {
|
|
18
|
+
const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
|
|
19
|
+
assert.ok(
|
|
20
|
+
source.includes("GSD_SLICE_LOCK"),
|
|
21
|
+
"Orchestrator must use GSD_SLICE_LOCK env var to isolate slice workers",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("orchestrator sets GSD_PARALLEL_WORKER=1 to prevent nesting", () => {
|
|
26
|
+
const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
|
|
27
|
+
assert.ok(
|
|
28
|
+
source.includes("GSD_PARALLEL_WORKER"),
|
|
29
|
+
"Orchestrator must set GSD_PARALLEL_WORKER to prevent nested parallel",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("maxWorkers default is 2", () => {
|
|
34
|
+
const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
|
|
35
|
+
// Check that default max workers is 2 (in opts.maxWorkers ?? 2 or similar)
|
|
36
|
+
assert.ok(
|
|
37
|
+
source.includes("maxWorkers") && source.includes("2"),
|
|
38
|
+
"Default maxWorkers should be 2",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("orchestrator imports GSD_MILESTONE_LOCK for milestone isolation", () => {
|
|
43
|
+
const source = readFileSync(join(gsdDir, "slice-parallel-orchestrator.ts"), "utf-8");
|
|
44
|
+
assert.ok(
|
|
45
|
+
source.includes("GSD_MILESTONE_LOCK"),
|
|
46
|
+
"Orchestrator must also pass GSD_MILESTONE_LOCK for milestone context",
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("slice_parallel preference gating", () => {
|
|
52
|
+
it("preferences-types.ts includes slice_parallel in interface", () => {
|
|
53
|
+
const source = readFileSync(join(gsdDir, "preferences-types.ts"), "utf-8");
|
|
54
|
+
assert.ok(
|
|
55
|
+
source.includes("slice_parallel"),
|
|
56
|
+
"GSDPreferences should have slice_parallel field",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("slice_parallel is in KNOWN_PREFERENCE_KEYS", () => {
|
|
61
|
+
const source = readFileSync(join(gsdDir, "preferences-types.ts"), "utf-8");
|
|
62
|
+
assert.ok(
|
|
63
|
+
source.includes('"slice_parallel"'),
|
|
64
|
+
'KNOWN_PREFERENCE_KEYS should include "slice_parallel"',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("state.ts checks GSD_SLICE_LOCK for slice isolation", () => {
|
|
69
|
+
const source = readFileSync(join(gsdDir, "state.ts"), "utf-8");
|
|
70
|
+
assert.ok(
|
|
71
|
+
source.includes("GSD_SLICE_LOCK"),
|
|
72
|
+
"State derivation should check GSD_SLICE_LOCK for slice-level parallel isolation",
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("auto.ts imports slice parallel orchestrator when enabled", () => {
|
|
77
|
+
const source = readFileSync(join(gsdDir, "auto.ts"), "utf-8");
|
|
78
|
+
assert.ok(
|
|
79
|
+
source.includes("slice_parallel") || source.includes("slice-parallel"),
|
|
80
|
+
"auto.ts should reference slice_parallel for dispatch gating",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -123,6 +123,48 @@ test("Rule 3: A-A-A-A triggers Rule 2 not Rule 3", () => {
|
|
|
123
123
|
);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
// ─── Rule 4: ENOENT same path twice in window (#3575) ───────────────────────
|
|
127
|
+
|
|
128
|
+
test("Rule 4: same ENOENT path in two entries triggers stuck", () => {
|
|
129
|
+
const result = detectStuck([
|
|
130
|
+
{ key: "A", error: "ENOENT: no such file or directory, access '/home/user/.gsd/agent/skills/debug-like-expert/SKILL.md'" },
|
|
131
|
+
{ key: "B" },
|
|
132
|
+
{ key: "A", error: "ENOENT: no such file or directory, access '/home/user/.gsd/agent/skills/debug-like-expert/SKILL.md'" },
|
|
133
|
+
]);
|
|
134
|
+
assert.notEqual(result, null);
|
|
135
|
+
assert.equal(result!.stuck, true);
|
|
136
|
+
assert.ok(result!.reason.includes("Missing file"), `reason was: ${result!.reason}`);
|
|
137
|
+
assert.ok(result!.reason.includes("ENOENT"), `reason was: ${result!.reason}`);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("Rule 4: different ENOENT paths do not trigger stuck", () => {
|
|
141
|
+
const result = detectStuck([
|
|
142
|
+
{ key: "A", error: "ENOENT: no such file or directory, access '/path/a'" },
|
|
143
|
+
{ key: "B", error: "ENOENT: no such file or directory, access '/path/b'" },
|
|
144
|
+
]);
|
|
145
|
+
assert.equal(result, null);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("Rule 4: single ENOENT does not trigger stuck", () => {
|
|
149
|
+
const result = detectStuck([
|
|
150
|
+
{ key: "A", error: "ENOENT: no such file or directory, access '/path/a'" },
|
|
151
|
+
{ key: "B" },
|
|
152
|
+
]);
|
|
153
|
+
assert.equal(result, null);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("Rule 4: ENOENT paths non-consecutive still triggers", () => {
|
|
157
|
+
const result = detectStuck([
|
|
158
|
+
{ key: "A", error: "ENOENT: no such file or directory, access '/missing/skill'" },
|
|
159
|
+
{ key: "B" },
|
|
160
|
+
{ key: "C" },
|
|
161
|
+
{ key: "D", error: "ENOENT: no such file or directory, access '/missing/skill'" },
|
|
162
|
+
]);
|
|
163
|
+
assert.notEqual(result, null);
|
|
164
|
+
assert.equal(result!.stuck, true);
|
|
165
|
+
assert.ok(result!.reason.includes("/missing/skill"), `reason was: ${result!.reason}`);
|
|
166
|
+
});
|
|
167
|
+
|
|
126
168
|
// ─── Gap documentation: 3-unit cycle evades detection ────────────────────────
|
|
127
169
|
|
|
128
170
|
test("Three-unit cycle A-B-C-A-B-C does NOT trigger stuck (documents gap L13)", () => {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for #2883: gsd_complete_slice tool invocation fails with
|
|
3
|
+
* JSON truncation, causing stuck retry loop.
|
|
4
|
+
*
|
|
5
|
+
* When a GSD tool is invoked with malformed/truncated JSON arguments, the tool
|
|
6
|
+
* execution fails (isError: true). But postUnitPreVerification only checks if
|
|
7
|
+
* the expected artifact exists on disk — it does not know the tool itself failed.
|
|
8
|
+
* When the artifact is missing (because the tool never ran), it sets up
|
|
9
|
+
* pendingVerificationRetry, re-dispatching the same unit with the same truncated
|
|
10
|
+
* input, creating a stuck loop.
|
|
11
|
+
*
|
|
12
|
+
* The fix adds a `lastToolInvocationError` field to AutoSession. When a GSD tool
|
|
13
|
+
* execution ends with isError, the error is recorded. postUnitPreVerification
|
|
14
|
+
* checks this field before retrying — if a tool invocation error occurred, it
|
|
15
|
+
* pauses auto-mode instead of retrying.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, test } from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { AutoSession } from "../auto/session.ts";
|
|
20
|
+
|
|
21
|
+
// ─── AutoSession.lastToolInvocationError field ───────────────────────────
|
|
22
|
+
|
|
23
|
+
describe("#2883: tool invocation error tracking on AutoSession", () => {
|
|
24
|
+
test("lastToolInvocationError defaults to null", () => {
|
|
25
|
+
const s = new AutoSession();
|
|
26
|
+
assert.equal(s.lastToolInvocationError, null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("lastToolInvocationError is cleared on reset()", () => {
|
|
30
|
+
const s = new AutoSession();
|
|
31
|
+
s.lastToolInvocationError = "Validation failed for tool gsd_complete_slice";
|
|
32
|
+
assert.ok(s.lastToolInvocationError);
|
|
33
|
+
s.reset();
|
|
34
|
+
assert.equal(s.lastToolInvocationError, null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("lastToolInvocationError can store truncated JSON error", () => {
|
|
38
|
+
const s = new AutoSession();
|
|
39
|
+
const errorMsg = "Expected ',' or '}' in JSON at position 4096";
|
|
40
|
+
s.lastToolInvocationError = errorMsg;
|
|
41
|
+
assert.equal(s.lastToolInvocationError, errorMsg);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ─── isToolInvocationError classifier ────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
import { isToolInvocationError } from "../auto-tool-tracking.ts";
|
|
48
|
+
|
|
49
|
+
describe("#2883: isToolInvocationError classification", () => {
|
|
50
|
+
test("detects JSON validation failure pattern", () => {
|
|
51
|
+
assert.equal(
|
|
52
|
+
isToolInvocationError("Validation failed for tool gsd_complete_slice: Expected ',' or '}' in JSON"),
|
|
53
|
+
true,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("detects truncated JSON parse error", () => {
|
|
58
|
+
assert.equal(
|
|
59
|
+
isToolInvocationError("Expected ',' or '}' in JSON at position 4096"),
|
|
60
|
+
true,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("detects Unexpected end of JSON input", () => {
|
|
65
|
+
assert.equal(
|
|
66
|
+
isToolInvocationError("Unexpected end of JSON input"),
|
|
67
|
+
true,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("detects Unexpected token in JSON", () => {
|
|
72
|
+
assert.equal(
|
|
73
|
+
isToolInvocationError("Unexpected token < in JSON at position 0"),
|
|
74
|
+
true,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("detects 'Validation failed for tool' prefix", () => {
|
|
79
|
+
assert.equal(
|
|
80
|
+
isToolInvocationError("Validation failed for tool gsd_slice_complete"),
|
|
81
|
+
true,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("returns false for normal tool errors (business logic)", () => {
|
|
86
|
+
assert.equal(
|
|
87
|
+
isToolInvocationError("Slice S01 is already complete"),
|
|
88
|
+
false,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("returns false for empty string", () => {
|
|
93
|
+
assert.equal(isToolInvocationError(""), false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("returns false for generic error", () => {
|
|
97
|
+
assert.equal(isToolInvocationError("Something went wrong"), false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("returns false for network errors (handled elsewhere)", () => {
|
|
101
|
+
assert.equal(isToolInvocationError("ECONNRESET"), false);
|
|
102
|
+
});
|
|
103
|
+
});
|