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,288 @@
|
|
|
1
|
+
// GSD — regression tests for issue #2630
|
|
2
|
+
// Milestone/slice artifact rendering must not corrupt existing markdown.
|
|
3
|
+
// Three bugs: (A) milestone title double-prefix, (B) full_uat_md demo fallback,
|
|
4
|
+
// (C) STATE.md title double-prefix.
|
|
5
|
+
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
renderPlanContent,
|
|
11
|
+
renderRoadmapContent,
|
|
12
|
+
renderStateContent,
|
|
13
|
+
} from '../workflow-projections.ts';
|
|
14
|
+
import type { SliceRow, TaskRow, MilestoneRow } from '../gsd-db.ts';
|
|
15
|
+
import type { GSDState } from '../types.ts';
|
|
16
|
+
|
|
17
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function makeSliceRow(overrides?: Partial<SliceRow>): SliceRow {
|
|
20
|
+
return {
|
|
21
|
+
milestone_id: 'M001',
|
|
22
|
+
id: 'S04',
|
|
23
|
+
title: 'Dependency-driven scene pipeline and state truth',
|
|
24
|
+
status: 'complete',
|
|
25
|
+
risk: 'high',
|
|
26
|
+
depends: ['S03'],
|
|
27
|
+
demo: '',
|
|
28
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
29
|
+
completed_at: '2026-01-15T00:00:00Z',
|
|
30
|
+
full_summary_md: '',
|
|
31
|
+
full_uat_md: `# S04: Dependency-driven scene pipeline and state truth — UAT
|
|
32
|
+
|
|
33
|
+
**Milestone:** M001
|
|
34
|
+
**Written:** 2026-01-15
|
|
35
|
+
|
|
36
|
+
## UAT Type: Functional
|
|
37
|
+
|
|
38
|
+
### Scenario 1: Pipeline processes dependencies
|
|
39
|
+
**Given** a scene with dependencies
|
|
40
|
+
**When** the pipeline runs
|
|
41
|
+
**Then** dependencies are resolved in order`,
|
|
42
|
+
goal: 'Build dependency-driven scene pipeline',
|
|
43
|
+
success_criteria: '',
|
|
44
|
+
proof_level: '',
|
|
45
|
+
integration_closure: '',
|
|
46
|
+
observability_impact: '',
|
|
47
|
+
sequence: 4,
|
|
48
|
+
replan_triggered_at: null,
|
|
49
|
+
...overrides,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function makeTaskRow(overrides?: Partial<TaskRow>): TaskRow {
|
|
54
|
+
return {
|
|
55
|
+
milestone_id: 'M001',
|
|
56
|
+
slice_id: 'S04',
|
|
57
|
+
id: 'T01',
|
|
58
|
+
title: 'Test Task',
|
|
59
|
+
status: 'done',
|
|
60
|
+
one_liner: '',
|
|
61
|
+
narrative: '',
|
|
62
|
+
verification_result: '',
|
|
63
|
+
duration: '',
|
|
64
|
+
completed_at: null,
|
|
65
|
+
blocker_discovered: false,
|
|
66
|
+
deviations: '',
|
|
67
|
+
known_issues: '',
|
|
68
|
+
key_files: [],
|
|
69
|
+
key_decisions: [],
|
|
70
|
+
full_summary_md: '',
|
|
71
|
+
full_plan_md: '',
|
|
72
|
+
description: 'Test description',
|
|
73
|
+
estimate: '30m',
|
|
74
|
+
files: [],
|
|
75
|
+
verify: 'npm test',
|
|
76
|
+
inputs: [],
|
|
77
|
+
expected_output: [],
|
|
78
|
+
observability_impact: '',
|
|
79
|
+
sequence: 0,
|
|
80
|
+
...overrides,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function makeMilestoneRow(overrides?: Partial<MilestoneRow>): MilestoneRow {
|
|
85
|
+
return {
|
|
86
|
+
id: 'M001',
|
|
87
|
+
title: 'Topic-to-pipeline foundation',
|
|
88
|
+
status: 'active',
|
|
89
|
+
depends_on: [],
|
|
90
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
91
|
+
completed_at: null,
|
|
92
|
+
vision: 'Build the topic-to-pipeline foundation',
|
|
93
|
+
success_criteria: [],
|
|
94
|
+
key_risks: [],
|
|
95
|
+
proof_strategy: [],
|
|
96
|
+
verification_contract: '',
|
|
97
|
+
verification_integration: '',
|
|
98
|
+
verification_operational: '',
|
|
99
|
+
verification_uat: '',
|
|
100
|
+
definition_of_done: [],
|
|
101
|
+
requirement_coverage: '',
|
|
102
|
+
boundary_map_markdown: '',
|
|
103
|
+
...overrides,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function makeGSDState(overrides?: Partial<GSDState>): GSDState {
|
|
108
|
+
return {
|
|
109
|
+
activeMilestone: { id: 'M001', title: 'Topic-to-pipeline foundation' },
|
|
110
|
+
activeSlice: { id: 'S01', title: 'Auth Layer' },
|
|
111
|
+
activeTask: null,
|
|
112
|
+
phase: 'executing',
|
|
113
|
+
recentDecisions: [],
|
|
114
|
+
blockers: [],
|
|
115
|
+
nextAction: 'Continue execution',
|
|
116
|
+
registry: [],
|
|
117
|
+
requirements: undefined,
|
|
118
|
+
...overrides,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Bug A: milestone title double-prefix ────────────────────────────────
|
|
123
|
+
// When params.title already contains "M001: ", the H1 should NOT become
|
|
124
|
+
// "# M001: M001: Topic-to-pipeline foundation"
|
|
125
|
+
|
|
126
|
+
test('#2630 renderRoadmapContent: milestone title with pre-existing ID prefix renders without duplication', () => {
|
|
127
|
+
const milestone = makeMilestoneRow({ title: 'M001: Topic-to-pipeline foundation' });
|
|
128
|
+
const content = renderRoadmapContent(milestone, []);
|
|
129
|
+
|
|
130
|
+
// The H1 must be exactly "# M001: Topic-to-pipeline foundation", not "# M001: M001: ..."
|
|
131
|
+
assert.ok(
|
|
132
|
+
content.includes('# M001: Topic-to-pipeline foundation'),
|
|
133
|
+
`expected single prefix in H1, got: ${content.split('\n')[0]}`,
|
|
134
|
+
);
|
|
135
|
+
assert.ok(
|
|
136
|
+
!content.includes('M001: M001:'),
|
|
137
|
+
`found double prefix in roadmap H1: ${content.split('\n')[0]}`,
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('#2630 renderStateContent: active milestone title with pre-existing ID prefix renders without duplication', () => {
|
|
142
|
+
const state = makeGSDState({
|
|
143
|
+
activeMilestone: { id: 'M001', title: 'M001: Topic-to-pipeline foundation' },
|
|
144
|
+
});
|
|
145
|
+
const content = renderStateContent(state);
|
|
146
|
+
|
|
147
|
+
assert.ok(
|
|
148
|
+
!content.includes('M001: M001:'),
|
|
149
|
+
`found double prefix in STATE.md: ${content}`,
|
|
150
|
+
);
|
|
151
|
+
assert.ok(
|
|
152
|
+
content.includes('**Active Milestone:** M001: Topic-to-pipeline foundation'),
|
|
153
|
+
`expected single prefix, got: ${content}`,
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('#2630 renderStateContent: registry entry with pre-existing ID prefix renders without duplication', () => {
|
|
158
|
+
const state = makeGSDState({
|
|
159
|
+
registry: [
|
|
160
|
+
{ id: 'M001', title: 'M001: Topic-to-pipeline foundation', status: 'active' },
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
const content = renderStateContent(state);
|
|
164
|
+
|
|
165
|
+
assert.ok(
|
|
166
|
+
!content.includes('M001: M001:'),
|
|
167
|
+
`found double prefix in registry: ${content}`,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// ─── Bug D: PLAN.md slice title double-prefix ──────────────────────────────
|
|
172
|
+
// When sliceRow.title already contains "S04: ", the H1 should NOT become
|
|
173
|
+
// "# S04: S04: Dependency-driven scene pipeline and state truth"
|
|
174
|
+
|
|
175
|
+
test('#2630 renderPlanContent: slice title with pre-existing ID prefix renders without duplication', () => {
|
|
176
|
+
const slice = makeSliceRow({ title: 'S04: Dependency-driven scene pipeline and state truth' });
|
|
177
|
+
const content = renderPlanContent(slice, []);
|
|
178
|
+
|
|
179
|
+
// The H1 must be exactly "# S04: Dependency-driven scene pipeline and state truth"
|
|
180
|
+
assert.ok(
|
|
181
|
+
content.includes('# S04: Dependency-driven scene pipeline and state truth'),
|
|
182
|
+
`expected single prefix in H1, got: ${content.split('\n')[0]}`,
|
|
183
|
+
);
|
|
184
|
+
assert.ok(
|
|
185
|
+
!content.includes('S04: S04:'),
|
|
186
|
+
`found double prefix in PLAN.md H1: ${content.split('\n')[0]}`,
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('#2630 renderPlanContent: slice title without prefix still renders correctly', () => {
|
|
191
|
+
const slice = makeSliceRow({ title: 'Dependency-driven scene pipeline and state truth' });
|
|
192
|
+
const content = renderPlanContent(slice, []);
|
|
193
|
+
|
|
194
|
+
assert.ok(
|
|
195
|
+
content.startsWith('# S04: Dependency-driven scene pipeline and state truth'),
|
|
196
|
+
`expected prefixed H1, got: ${content.split('\n')[0]}`,
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ─── Bug B: full_uat_md as demo fallback ─────────────────────────────────
|
|
201
|
+
// When slice.demo is empty and full_uat_md is a multi-line UAT document,
|
|
202
|
+
// the renderers must NOT inject the entire UAT body.
|
|
203
|
+
|
|
204
|
+
test('#2630 renderPlanContent: empty demo must not inject full_uat_md body into plan', () => {
|
|
205
|
+
const slice = makeSliceRow({ demo: '' });
|
|
206
|
+
const content = renderPlanContent(slice, []);
|
|
207
|
+
|
|
208
|
+
// The **Demo:** line must be a single line, not multi-line UAT content
|
|
209
|
+
const demoLine = content.split('\n').find(l => l.startsWith('**Demo:**'));
|
|
210
|
+
assert.ok(demoLine, 'should have a Demo line');
|
|
211
|
+
|
|
212
|
+
// Must not contain UAT headings or body
|
|
213
|
+
assert.ok(
|
|
214
|
+
!content.includes('## UAT Type'),
|
|
215
|
+
`plan contains UAT body content: ${content}`,
|
|
216
|
+
);
|
|
217
|
+
assert.ok(
|
|
218
|
+
!content.includes('**Milestone:** M001'),
|
|
219
|
+
`plan contains UAT metadata: ${content}`,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// The Demo line must not contain newlines (single line only)
|
|
223
|
+
assert.ok(
|
|
224
|
+
!demoLine!.includes('\n'),
|
|
225
|
+
`Demo line must be single line, got: ${demoLine}`,
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('#2630 renderPlanContent: null demo must not inject full_uat_md body into plan', () => {
|
|
230
|
+
const slice = makeSliceRow({ demo: null as unknown as string });
|
|
231
|
+
const content = renderPlanContent(slice, []);
|
|
232
|
+
|
|
233
|
+
assert.ok(
|
|
234
|
+
!content.includes('## UAT Type'),
|
|
235
|
+
`plan contains UAT body content when demo is null`,
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('#2630 renderRoadmapContent: empty demo must not inject full_uat_md into roadmap table', () => {
|
|
240
|
+
const milestone = makeMilestoneRow();
|
|
241
|
+
const slices = [makeSliceRow({ demo: '' })];
|
|
242
|
+
|
|
243
|
+
const content = renderRoadmapContent(milestone, slices);
|
|
244
|
+
|
|
245
|
+
// Roadmap table cell for "After this" must be single-line
|
|
246
|
+
assert.ok(
|
|
247
|
+
!content.includes('## UAT Type'),
|
|
248
|
+
`roadmap contains UAT body content: ${content}`,
|
|
249
|
+
);
|
|
250
|
+
assert.ok(
|
|
251
|
+
!content.includes('**Milestone:** M001'),
|
|
252
|
+
`roadmap contains UAT metadata: ${content}`,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// The table row containing S04 must be a single line
|
|
256
|
+
const s04Line = content.split('\n').find(l => l.includes('| S04 |'));
|
|
257
|
+
assert.ok(s04Line, 'should have S04 table row');
|
|
258
|
+
assert.ok(
|
|
259
|
+
!s04Line!.includes('# S04:'),
|
|
260
|
+
`roadmap table cell contains UAT heading: ${s04Line}`,
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('#2630 renderRoadmapContent: null demo must not inject full_uat_md into roadmap table', () => {
|
|
265
|
+
const milestone = makeMilestoneRow();
|
|
266
|
+
const slices = [makeSliceRow({ demo: null as unknown as string })];
|
|
267
|
+
|
|
268
|
+
const content = renderRoadmapContent(milestone, slices);
|
|
269
|
+
|
|
270
|
+
assert.ok(
|
|
271
|
+
!content.includes('## UAT Type'),
|
|
272
|
+
`roadmap contains UAT body content when demo is null`,
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('#2630 renderPlanContent: with valid demo string does not use full_uat_md', () => {
|
|
277
|
+
const slice = makeSliceRow({ demo: 'Login flow works end-to-end' });
|
|
278
|
+
const content = renderPlanContent(slice, []);
|
|
279
|
+
|
|
280
|
+
assert.ok(
|
|
281
|
+
content.includes('**Demo:** After this: Login flow works end-to-end'),
|
|
282
|
+
`expected demo text, got: ${content}`,
|
|
283
|
+
);
|
|
284
|
+
assert.ok(
|
|
285
|
+
!content.includes('UAT'),
|
|
286
|
+
`should not contain UAT when demo is provided`,
|
|
287
|
+
);
|
|
288
|
+
});
|
|
@@ -325,7 +325,7 @@ test("auto/phases.ts: selectAndApplyModel called exactly once and before updateP
|
|
|
325
325
|
// Extract the runUnitPhase function body
|
|
326
326
|
const fnStart = src.indexOf("export async function runUnitPhase");
|
|
327
327
|
assert.ok(fnStart > 0, "runUnitPhase should exist in phases.ts");
|
|
328
|
-
const fnBody = src.slice(fnStart, fnStart +
|
|
328
|
+
const fnBody = src.slice(fnStart, fnStart + 12000);
|
|
329
329
|
|
|
330
330
|
// selectAndApplyModel must appear exactly once
|
|
331
331
|
const allOccurrences = [...fnBody.matchAll(/selectAndApplyModel\(/g)];
|
|
@@ -2107,11 +2107,11 @@ test("autoLoop rejects execute-task with 0 tool calls as hallucinated (#1833)",
|
|
|
2107
2107
|
// The task should NOT have been added to completedUnits on the first iteration
|
|
2108
2108
|
// (0 tool calls), but SHOULD be added on the second iteration (5 tool calls)
|
|
2109
2109
|
const warningNotification = notifications.find(
|
|
2110
|
-
(n) => n.includes("0 tool calls") && n.includes("
|
|
2110
|
+
(n) => n.includes("0 tool calls") && n.includes("context exhaustion"),
|
|
2111
2111
|
);
|
|
2112
2112
|
assert.ok(
|
|
2113
2113
|
warningNotification,
|
|
2114
|
-
"should notify about 0 tool calls
|
|
2114
|
+
"should notify about 0 tool calls context exhaustion",
|
|
2115
2115
|
);
|
|
2116
2116
|
|
|
2117
2117
|
// Verify deriveState was called at least twice (two iterations)
|
|
@@ -2122,7 +2122,7 @@ test("autoLoop rejects execute-task with 0 tool calls as hallucinated (#1833)",
|
|
|
2122
2122
|
);
|
|
2123
2123
|
});
|
|
2124
2124
|
|
|
2125
|
-
test("autoLoop
|
|
2125
|
+
test("autoLoop rejects complete-slice with 0 tool calls as context-exhausted (#2653)", async () => {
|
|
2126
2126
|
_resetPendingResolve();
|
|
2127
2127
|
|
|
2128
2128
|
const ctx = makeMockCtx();
|
|
@@ -2130,6 +2130,7 @@ test("autoLoop does NOT reject non-execute-task units with 0 tool calls (#1833)"
|
|
|
2130
2130
|
ctx.sessionManager = { getSessionFile: () => "/tmp/session.json" };
|
|
2131
2131
|
const pi = makeMockPi();
|
|
2132
2132
|
|
|
2133
|
+
let iterationCount = 0;
|
|
2133
2134
|
const notifications: string[] = [];
|
|
2134
2135
|
ctx.ui.notify = (msg: string) => { notifications.push(msg); };
|
|
2135
2136
|
|
|
@@ -2163,7 +2164,7 @@ test("autoLoop does NOT reject non-execute-task units with 0 tool calls (#1833)"
|
|
|
2163
2164
|
};
|
|
2164
2165
|
},
|
|
2165
2166
|
closeoutUnit: async () => {
|
|
2166
|
-
// complete-slice with 0 tool calls
|
|
2167
|
+
// complete-slice with 0 tool calls — context exhausted, no progress
|
|
2167
2168
|
mockLedger.units.push({
|
|
2168
2169
|
type: "complete-slice",
|
|
2169
2170
|
id: "M001/S01",
|
|
@@ -2177,31 +2178,51 @@ test("autoLoop does NOT reject non-execute-task units with 0 tool calls (#1833)"
|
|
|
2177
2178
|
getLedger: () => mockLedger,
|
|
2178
2179
|
postUnitPostVerification: async () => {
|
|
2179
2180
|
deps.callLog.push("postUnitPostVerification");
|
|
2180
|
-
|
|
2181
|
+
iterationCount++;
|
|
2182
|
+
// Deactivate after 2nd iteration
|
|
2183
|
+
s.active = iterationCount < 2;
|
|
2181
2184
|
return "continue" as const;
|
|
2182
2185
|
},
|
|
2183
2186
|
});
|
|
2184
2187
|
|
|
2185
2188
|
const loopPromise = autoLoop(ctx, pi, s, deps);
|
|
2186
2189
|
|
|
2190
|
+
// First iteration: complete-slice with 0 tool calls → rejected
|
|
2187
2191
|
await new Promise((r) => setTimeout(r, 50));
|
|
2188
2192
|
resolveAgentEnd(makeEvent());
|
|
2189
2193
|
|
|
2194
|
+
// Second iteration: re-dispatched, this time with tool calls
|
|
2195
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2196
|
+
mockLedger.units.length = 0;
|
|
2197
|
+
(deps as any).closeoutUnit = async () => {
|
|
2198
|
+
mockLedger.units.push({
|
|
2199
|
+
type: "complete-slice",
|
|
2200
|
+
id: "M001/S01",
|
|
2201
|
+
startedAt: s.currentUnit?.startedAt ?? Date.now(),
|
|
2202
|
+
toolCalls: 3,
|
|
2203
|
+
assistantMessages: 2,
|
|
2204
|
+
tokens: { input: 200, output: 400, total: 600, cacheRead: 0, cacheWrite: 0 },
|
|
2205
|
+
cost: 0.30,
|
|
2206
|
+
});
|
|
2207
|
+
};
|
|
2208
|
+
resolveAgentEnd(makeEvent());
|
|
2209
|
+
|
|
2190
2210
|
await loopPromise;
|
|
2191
2211
|
|
|
2192
|
-
// Should
|
|
2212
|
+
// Should have a warning about 0 tool calls for complete-slice
|
|
2193
2213
|
const warningNotification = notifications.find(
|
|
2194
|
-
(n) => n.includes("0 tool calls")
|
|
2214
|
+
(n) => n.includes("0 tool calls"),
|
|
2195
2215
|
);
|
|
2196
2216
|
assert.ok(
|
|
2197
|
-
|
|
2198
|
-
"should
|
|
2217
|
+
warningNotification,
|
|
2218
|
+
"should flag complete-slice with 0 tool calls as failed (#2653)",
|
|
2199
2219
|
);
|
|
2200
2220
|
|
|
2201
|
-
// Verify
|
|
2221
|
+
// Verify deriveState was called at least twice (two iterations: rejected + retry)
|
|
2222
|
+
const deriveCount = deps.callLog.filter((c) => c === "deriveState").length;
|
|
2202
2223
|
assert.ok(
|
|
2203
|
-
|
|
2204
|
-
|
|
2224
|
+
deriveCount >= 2,
|
|
2225
|
+
`deriveState should be called at least 2 times for retry (got ${deriveCount})`,
|
|
2205
2226
|
);
|
|
2206
2227
|
});
|
|
2207
2228
|
|
|
@@ -193,6 +193,64 @@ describe("createGridLayout", () => {
|
|
|
193
193
|
});
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
+
describe("CmuxClient stdio isolation", () => {
|
|
197
|
+
test("runSync and runAsync explicitly set stdio to prevent terminal interference", () => {
|
|
198
|
+
// Read the cmux index source and verify that execFileSync/spawn calls
|
|
199
|
+
// inside runSync/runAsync include stdio options that isolate stdin and stderr.
|
|
200
|
+
// This prevents the cmux CLI child process from inheriting the parent's
|
|
201
|
+
// stdin/stderr, which can steal keyboard input or corrupt TUI rendering (#1922).
|
|
202
|
+
const cmuxIndexPath = path.resolve(
|
|
203
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
204
|
+
"../../cmux/index.ts",
|
|
205
|
+
);
|
|
206
|
+
const source = fs.readFileSync(cmuxIndexPath, "utf-8");
|
|
207
|
+
|
|
208
|
+
// Extract runSync method body
|
|
209
|
+
const runSyncMatch = source.match(/private runSync\(args: string\[\]\)[^{]*\{([\s\S]*?)\n \}/);
|
|
210
|
+
assert.ok(runSyncMatch, "runSync method must exist");
|
|
211
|
+
const runSyncBody = runSyncMatch[1];
|
|
212
|
+
assert.ok(
|
|
213
|
+
runSyncBody.includes('stdio:'),
|
|
214
|
+
"runSync must explicitly set stdio to prevent terminal interference (see #1922)",
|
|
215
|
+
);
|
|
216
|
+
assert.ok(
|
|
217
|
+
runSyncBody.includes('"ignore"'),
|
|
218
|
+
"runSync stdio must ignore stdin to prevent stealing keyboard input from TUI",
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// Extract runAsync method body
|
|
222
|
+
const runAsyncMatch = source.match(/private async runAsync\(args: string\[\]\)[^{]*\{([\s\S]*?)\n \}/);
|
|
223
|
+
assert.ok(runAsyncMatch, "runAsync method must exist");
|
|
224
|
+
const runAsyncBody = runAsyncMatch[1];
|
|
225
|
+
assert.ok(
|
|
226
|
+
runAsyncBody.includes('stdio:'),
|
|
227
|
+
"runAsync must explicitly set stdio to prevent terminal interference (see #1922)",
|
|
228
|
+
);
|
|
229
|
+
assert.ok(
|
|
230
|
+
runAsyncBody.includes('"ignore"'),
|
|
231
|
+
"runAsync stdio must ignore stdin to prevent stealing keyboard input from TUI",
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("isCmuxCliAvailable uses stdio ignore to prevent terminal interference", () => {
|
|
236
|
+
const cmuxIndexPath = path.resolve(
|
|
237
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
238
|
+
"../../cmux/index.ts",
|
|
239
|
+
);
|
|
240
|
+
const source = fs.readFileSync(cmuxIndexPath, "utf-8");
|
|
241
|
+
|
|
242
|
+
// Find isCmuxCliAvailable or the cli-check function body
|
|
243
|
+
const fnMatch = source.match(/function isCmuxCliAvailable[\s\S]*?\{([\s\S]*?)\n\}/);
|
|
244
|
+
if (!fnMatch) return; // function may be inlined or renamed — skip rather than fail
|
|
245
|
+
|
|
246
|
+
const fnBody = fnMatch[1];
|
|
247
|
+
assert.ok(
|
|
248
|
+
fnBody.includes('"ignore"') || !fnBody.includes('execFileSync'),
|
|
249
|
+
"isCmuxCliAvailable must not inherit parent stdio (see #1922)",
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
196
254
|
describe("cmux extension discovery opt-out", () => {
|
|
197
255
|
test("cmux directory has package.json with pi manifest to prevent auto-discovery as extension", () => {
|
|
198
256
|
const cmuxDir = path.resolve(
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cold-resume-db-reopen.test.ts — Regression test for #2940.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the paused-session resume path in auto.ts opens the project
|
|
5
|
+
* database before calling rebuildState() / deriveState(), matching the fresh
|
|
6
|
+
* bootstrap path in auto-start.ts.
|
|
7
|
+
*
|
|
8
|
+
* Without this, cold resume falls back to markdown parsing which misreads
|
|
9
|
+
* done cells and redispatches wrong slices.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
16
|
+
|
|
17
|
+
const { assertTrue, report } = createTestContext();
|
|
18
|
+
|
|
19
|
+
const autoSrc = readFileSync(join(import.meta.dirname, "..", "auto.ts"), "utf-8");
|
|
20
|
+
|
|
21
|
+
console.log("\n=== #2940: resume path opens DB before rebuildState/deriveState ===");
|
|
22
|
+
|
|
23
|
+
// The resume block is the `if (s.paused) { ... }` section that calls rebuildState/deriveState.
|
|
24
|
+
// Locate the resume section by finding `s.paused = false;` followed by `rebuildState`.
|
|
25
|
+
const resumeSectionStart = autoSrc.indexOf("if (s.paused) {", autoSrc.indexOf("// If resuming from paused state"));
|
|
26
|
+
assertTrue(resumeSectionStart > 0, "auto.ts has the paused-session resume block");
|
|
27
|
+
|
|
28
|
+
const resumeSection = autoSrc.slice(resumeSectionStart, resumeSectionStart + 3000);
|
|
29
|
+
|
|
30
|
+
// The resume path must open the DB before rebuildState/deriveState
|
|
31
|
+
const rebuildIdx = resumeSection.indexOf("rebuildState(");
|
|
32
|
+
assertTrue(rebuildIdx > 0, "resume block calls rebuildState");
|
|
33
|
+
|
|
34
|
+
const deriveIdx = resumeSection.indexOf("deriveState(");
|
|
35
|
+
assertTrue(deriveIdx > 0, "resume block calls deriveState");
|
|
36
|
+
|
|
37
|
+
// There must be a DB open call before the first rebuildState call
|
|
38
|
+
const dbOpenPatterns = [
|
|
39
|
+
"openProjectDbIfPresent(",
|
|
40
|
+
"openDatabase(",
|
|
41
|
+
"ensureDbOpen(",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const preDeriveSection = resumeSection.slice(0, rebuildIdx);
|
|
45
|
+
const hasDbOpen = dbOpenPatterns.some(pat => preDeriveSection.includes(pat));
|
|
46
|
+
assertTrue(
|
|
47
|
+
hasDbOpen,
|
|
48
|
+
"resume path must open DB before rebuildState/deriveState (#2940)",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
report();
|
|
@@ -264,6 +264,146 @@ describe("complete-milestone", () => {
|
|
|
264
264
|
);
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
+
test("sanitizeCompleteMilestoneParams normalizes string parameters", async () => {
|
|
268
|
+
const { sanitizeCompleteMilestoneParams } = await import("../bootstrap/sanitize-complete-milestone.ts");
|
|
269
|
+
|
|
270
|
+
// Simulate params as they might arrive from the SDK after partial JSON parse:
|
|
271
|
+
// - numbers instead of strings
|
|
272
|
+
// - null instead of arrays
|
|
273
|
+
// - extra whitespace in strings
|
|
274
|
+
// - undefined optional fields
|
|
275
|
+
const raw: any = {
|
|
276
|
+
milestoneId: " M011 ",
|
|
277
|
+
title: 42, // number instead of string
|
|
278
|
+
oneLiner: " One-liner with spaces ",
|
|
279
|
+
narrative: "# Big markdown\n\nWith newlines and `backticks`\n\n```ts\ncode();\n```\n",
|
|
280
|
+
successCriteriaResults: null, // null instead of string
|
|
281
|
+
definitionOfDoneResults: undefined, // undefined instead of string
|
|
282
|
+
requirementOutcomes: 12345, // number instead of string
|
|
283
|
+
keyDecisions: "not an array", // string instead of array
|
|
284
|
+
keyFiles: null, // null instead of array
|
|
285
|
+
lessonsLearned: [" lesson one ", null, "", " lesson two "],
|
|
286
|
+
followUps: " follow up ",
|
|
287
|
+
deviations: undefined,
|
|
288
|
+
verificationPassed: "true", // string instead of boolean
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const sanitized = sanitizeCompleteMilestoneParams(raw);
|
|
292
|
+
|
|
293
|
+
// String fields are trimmed and coerced
|
|
294
|
+
assert.strictEqual(sanitized.milestoneId, "M011");
|
|
295
|
+
assert.strictEqual(sanitized.title, "42");
|
|
296
|
+
assert.strictEqual(sanitized.oneLiner, "One-liner with spaces");
|
|
297
|
+
assert.ok(sanitized.narrative.includes("# Big markdown"), "narrative preserves markdown");
|
|
298
|
+
assert.strictEqual(sanitized.successCriteriaResults, "");
|
|
299
|
+
assert.strictEqual(sanitized.definitionOfDoneResults, "");
|
|
300
|
+
assert.strictEqual(sanitized.requirementOutcomes, "12345");
|
|
301
|
+
|
|
302
|
+
// Array fields are normalized
|
|
303
|
+
assert.ok(Array.isArray(sanitized.keyDecisions), "keyDecisions is an array");
|
|
304
|
+
assert.deepStrictEqual(sanitized.keyDecisions, []);
|
|
305
|
+
assert.ok(Array.isArray(sanitized.keyFiles), "keyFiles is an array");
|
|
306
|
+
assert.deepStrictEqual(sanitized.keyFiles, []);
|
|
307
|
+
assert.deepStrictEqual(sanitized.lessonsLearned, ["lesson one", "lesson two"]);
|
|
308
|
+
|
|
309
|
+
// Optional fields — toStr() returns "" for undefined/null
|
|
310
|
+
assert.strictEqual(sanitized.followUps, "follow up");
|
|
311
|
+
assert.strictEqual(sanitized.deviations, "");
|
|
312
|
+
|
|
313
|
+
// Boolean coercion
|
|
314
|
+
assert.strictEqual(sanitized.verificationPassed, true);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("sanitizeCompleteMilestoneParams handles large markdown content", async () => {
|
|
318
|
+
const { sanitizeCompleteMilestoneParams } = await import("../bootstrap/sanitize-complete-milestone.ts");
|
|
319
|
+
|
|
320
|
+
// Generate a large markdown string (~25k characters to exceed the 23667 position from the bug)
|
|
321
|
+
const largeMd = "# Milestone Summary\n\n" +
|
|
322
|
+
Array.from({ length: 500 }, (_, i) =>
|
|
323
|
+
`## Section ${i}\n\n` +
|
|
324
|
+
`- [x] Task ${i} completed with \`code\` and **bold** text\n` +
|
|
325
|
+
` - Sub-item with special chars: <, >, &, ", '\n` +
|
|
326
|
+
` - Another sub-item: \`\`\`ts\nconst x = ${i};\n\`\`\`\n`
|
|
327
|
+
).join("\n");
|
|
328
|
+
|
|
329
|
+
assert.ok(largeMd.length > 23667, `generated markdown is ${largeMd.length} chars, must exceed 23667`);
|
|
330
|
+
|
|
331
|
+
const raw: any = {
|
|
332
|
+
milestoneId: "M011",
|
|
333
|
+
title: "Content Depth, Narrative & Onboarding",
|
|
334
|
+
oneLiner: "Large milestone with many slices",
|
|
335
|
+
narrative: largeMd,
|
|
336
|
+
successCriteriaResults: largeMd,
|
|
337
|
+
definitionOfDoneResults: largeMd,
|
|
338
|
+
requirementOutcomes: largeMd,
|
|
339
|
+
keyDecisions: ["decision 1", "decision 2"],
|
|
340
|
+
keyFiles: ["file1.ts", "file2.ts"],
|
|
341
|
+
lessonsLearned: ["lesson 1"],
|
|
342
|
+
followUps: "Some follow-ups",
|
|
343
|
+
deviations: "Some deviations",
|
|
344
|
+
verificationPassed: true,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const sanitized = sanitizeCompleteMilestoneParams(raw);
|
|
348
|
+
|
|
349
|
+
// Large content should pass through without truncation or corruption
|
|
350
|
+
assert.strictEqual(sanitized.narrative, largeMd.trim());
|
|
351
|
+
assert.strictEqual(sanitized.successCriteriaResults, largeMd.trim());
|
|
352
|
+
assert.strictEqual(sanitized.definitionOfDoneResults, largeMd.trim());
|
|
353
|
+
assert.strictEqual(sanitized.requirementOutcomes, largeMd.trim());
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("milestoneCompleteExecute uses sanitized params", async () => {
|
|
357
|
+
// This test verifies that the execute function sanitizes params before passing
|
|
358
|
+
// to handleCompleteMilestone. We test indirectly: if we pass numeric milestoneId,
|
|
359
|
+
// the handler should still receive a string (and return a meaningful error, not a crash).
|
|
360
|
+
const { handleCompleteMilestone } = await import("../tools/complete-milestone.ts");
|
|
361
|
+
const { sanitizeCompleteMilestoneParams } = await import("../bootstrap/sanitize-complete-milestone.ts");
|
|
362
|
+
const base = createFixtureBase();
|
|
363
|
+
try {
|
|
364
|
+
// Simulate what milestoneCompleteExecute should do: sanitize then call handler
|
|
365
|
+
const raw: any = {
|
|
366
|
+
milestoneId: 42, // number — would crash without sanitization
|
|
367
|
+
title: "Test",
|
|
368
|
+
oneLiner: "Test",
|
|
369
|
+
narrative: "Test narrative",
|
|
370
|
+
successCriteriaResults: "Results",
|
|
371
|
+
definitionOfDoneResults: "Done",
|
|
372
|
+
requirementOutcomes: "Outcomes",
|
|
373
|
+
keyDecisions: null, // null — would crash .length without sanitization
|
|
374
|
+
keyFiles: "not-array", // string — would crash .map without sanitization
|
|
375
|
+
lessonsLearned: undefined, // undefined — would crash .map without sanitization
|
|
376
|
+
followUps: "",
|
|
377
|
+
deviations: "",
|
|
378
|
+
verificationPassed: true,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const sanitized = sanitizeCompleteMilestoneParams(raw);
|
|
382
|
+
|
|
383
|
+
// Verify sanitization didn't crash and produced valid typed params
|
|
384
|
+
assert.strictEqual(typeof sanitized.milestoneId, "string", "milestoneId is a string after sanitization");
|
|
385
|
+
assert.ok(Array.isArray(sanitized.keyDecisions), "keyDecisions is array after sanitization");
|
|
386
|
+
assert.ok(Array.isArray(sanitized.keyFiles), "keyFiles is array after sanitization");
|
|
387
|
+
assert.ok(Array.isArray(sanitized.lessonsLearned), "lessonsLearned is array after sanitization");
|
|
388
|
+
assert.strictEqual(typeof sanitized.verificationPassed, "boolean", "verificationPassed is boolean after sanitization");
|
|
389
|
+
|
|
390
|
+
// Calling handleCompleteMilestone may throw GSD_STALE_STATE (no DB in test env)
|
|
391
|
+
// but it should NOT throw TypeError from type mismatches — that's the bug fix.
|
|
392
|
+
try {
|
|
393
|
+
await handleCompleteMilestone(sanitized, base);
|
|
394
|
+
} catch (err: any) {
|
|
395
|
+
// GSD_STALE_STATE or "No database open" is acceptable — it means we got past
|
|
396
|
+
// the type-sensitive code and failed on DB access, which is expected in tests.
|
|
397
|
+
assert.ok(
|
|
398
|
+
err.code === "GSD_STALE_STATE" || err.message?.includes("database"),
|
|
399
|
+
`expected DB error, got: ${err.message}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
} finally {
|
|
403
|
+
cleanup(base);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
267
407
|
test("deriveState completing-milestone integration", async () => {
|
|
268
408
|
const { deriveState, isMilestoneComplete } = await import("../state.ts");
|
|
269
409
|
const { invalidateAllCaches: invalidateAllCachesDynamic } = await import("../cache.ts");
|