gsd-pi 2.63.0 → 2.64.0
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 +12 -12
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +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 +12 -12
- 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/dist/welcome-screen.js +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/package.json +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/pkg/package.json +1 -1
- 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/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #2344: Auto-loop hangs after plan-slice completes
|
|
3
|
+
* because postUnitPostVerification() never resolves.
|
|
4
|
+
*
|
|
5
|
+
* When postUnitPostVerification() hangs (e.g., due to a module import
|
|
6
|
+
* deadlock or SQLite transaction hang), the auto-loop blocks forever
|
|
7
|
+
* with no error message, no notification, and no recovery.
|
|
8
|
+
*
|
|
9
|
+
* The fix adds a timeout guard around postUnitPostVerification() in
|
|
10
|
+
* runFinalize(). If it doesn't resolve within the timeout, the function
|
|
11
|
+
* force-returns "continue" and logs an error, allowing the loop to
|
|
12
|
+
* proceed to the next iteration.
|
|
13
|
+
*
|
|
14
|
+
* This test verifies the timeout utility used by the fix, since the
|
|
15
|
+
* full runFinalize function has too many transitive dependencies for
|
|
16
|
+
* isolated unit testing.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
20
|
+
import {
|
|
21
|
+
withTimeout,
|
|
22
|
+
FINALIZE_POST_TIMEOUT_MS,
|
|
23
|
+
} from "../auto/finalize-timeout.ts";
|
|
24
|
+
|
|
25
|
+
const { assertTrue, assertEq, report } = createTestContext();
|
|
26
|
+
|
|
27
|
+
// ═══ Test: withTimeout resolves when inner promise resolves promptly ══════════
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
console.log("\n=== #2344: withTimeout passes through when promise resolves ===");
|
|
31
|
+
|
|
32
|
+
const result = await withTimeout(
|
|
33
|
+
Promise.resolve("ok"),
|
|
34
|
+
1000,
|
|
35
|
+
"test-timeout",
|
|
36
|
+
);
|
|
37
|
+
assertEq(result.value, "ok", "should return inner value");
|
|
38
|
+
assertEq(result.timedOut, false, "should not be timed out");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ═══ Test: withTimeout returns fallback when inner promise hangs ══════════════
|
|
42
|
+
|
|
43
|
+
{
|
|
44
|
+
console.log("\n=== #2344: withTimeout returns fallback on hang ===");
|
|
45
|
+
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const result = await withTimeout(
|
|
48
|
+
new Promise<string>(() => {
|
|
49
|
+
// Never resolves
|
|
50
|
+
}),
|
|
51
|
+
100, // short timeout for testing
|
|
52
|
+
"test-timeout",
|
|
53
|
+
);
|
|
54
|
+
const elapsed = Date.now() - startTime;
|
|
55
|
+
|
|
56
|
+
assertEq(result.timedOut, true, "should report timeout");
|
|
57
|
+
assertEq(result.value, undefined, "value should be undefined on timeout");
|
|
58
|
+
assertTrue(elapsed >= 90, `should wait at least 90ms (took ${elapsed}ms)`);
|
|
59
|
+
assertTrue(elapsed < 500, `should not wait too long (took ${elapsed}ms)`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ═══ Test: withTimeout handles rejection gracefully ═══════════════════════════
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
console.log("\n=== #2344: withTimeout propagates rejection ===");
|
|
66
|
+
|
|
67
|
+
let caught = false;
|
|
68
|
+
try {
|
|
69
|
+
await withTimeout(
|
|
70
|
+
Promise.reject(new Error("boom")),
|
|
71
|
+
1000,
|
|
72
|
+
"test-timeout",
|
|
73
|
+
);
|
|
74
|
+
} catch (err: any) {
|
|
75
|
+
caught = true;
|
|
76
|
+
assertEq(err.message, "boom", "should propagate the error");
|
|
77
|
+
}
|
|
78
|
+
assertTrue(caught, "rejection should propagate");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ═══ Test: FINALIZE_POST_TIMEOUT_MS is defined and reasonable ═════════════════
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
console.log("\n=== #2344: timeout constant is defined and reasonable ===");
|
|
85
|
+
|
|
86
|
+
assertTrue(
|
|
87
|
+
typeof FINALIZE_POST_TIMEOUT_MS === "number",
|
|
88
|
+
"FINALIZE_POST_TIMEOUT_MS should be a number",
|
|
89
|
+
);
|
|
90
|
+
assertTrue(
|
|
91
|
+
FINALIZE_POST_TIMEOUT_MS >= 30_000,
|
|
92
|
+
`timeout should be >= 30s (got ${FINALIZE_POST_TIMEOUT_MS}ms)`,
|
|
93
|
+
);
|
|
94
|
+
assertTrue(
|
|
95
|
+
FINALIZE_POST_TIMEOUT_MS <= 120_000,
|
|
96
|
+
`timeout should be <= 120s (got ${FINALIZE_POST_TIMEOUT_MS}ms)`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ═══ Test: withTimeout cleans up timer on success ════════════════════════════
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
console.log("\n=== #2344: withTimeout cleans up timer on success ===");
|
|
104
|
+
|
|
105
|
+
// If the timer isn't cleaned up, this test would keep the process alive.
|
|
106
|
+
// Relying on process.exit behavior — if test completes, timers were cleaned.
|
|
107
|
+
const result = await withTimeout(
|
|
108
|
+
new Promise<string>((r) => setTimeout(() => r("delayed"), 50)),
|
|
109
|
+
5000,
|
|
110
|
+
"cleanup-test",
|
|
111
|
+
);
|
|
112
|
+
assertEq(result.value, "delayed", "should resolve with delayed value");
|
|
113
|
+
assertEq(result.timedOut, false, "should not time out");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
report();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #3453: dynamic model routing must be disabled for
|
|
3
|
+
* flat-rate providers like GitHub Copilot where all models cost the same
|
|
4
|
+
* per request — routing only degrades quality with no cost benefit.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, test } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { isFlatRateProvider, resolvePreferredModelConfig } from "../auto-model-selection.ts";
|
|
10
|
+
|
|
11
|
+
describe("flat-rate provider routing guard (#3453)", () => {
|
|
12
|
+
|
|
13
|
+
test("isFlatRateProvider returns true for github-copilot", () => {
|
|
14
|
+
assert.equal(isFlatRateProvider("github-copilot"), true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("isFlatRateProvider returns true for copilot alias", () => {
|
|
18
|
+
assert.equal(isFlatRateProvider("copilot"), true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("isFlatRateProvider is case-insensitive", () => {
|
|
22
|
+
assert.equal(isFlatRateProvider("GitHub-Copilot"), true);
|
|
23
|
+
assert.equal(isFlatRateProvider("GITHUB-COPILOT"), true);
|
|
24
|
+
assert.equal(isFlatRateProvider("Copilot"), true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("isFlatRateProvider returns false for anthropic", () => {
|
|
28
|
+
assert.equal(isFlatRateProvider("anthropic"), false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("isFlatRateProvider returns false for openai", () => {
|
|
32
|
+
assert.equal(isFlatRateProvider("openai"), false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("resolvePreferredModelConfig returns undefined for copilot start model", () => {
|
|
36
|
+
// When the user's start model is on a flat-rate provider,
|
|
37
|
+
// resolvePreferredModelConfig should not synthesize a routing
|
|
38
|
+
// config from tier_models — it should return undefined so the
|
|
39
|
+
// user's selected model is preserved.
|
|
40
|
+
const result = resolvePreferredModelConfig("execute-task", {
|
|
41
|
+
provider: "github-copilot",
|
|
42
|
+
id: "claude-sonnet-4",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Should be undefined (no routing config created for flat-rate)
|
|
46
|
+
// Note: this only tests the guard — if explicit per-unit config exists
|
|
47
|
+
// in preferences, that takes precedence regardless.
|
|
48
|
+
assert.equal(result, undefined, "Should not create routing config for copilot");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forensics detectStuckLoops tests — #1943
|
|
3
|
+
*
|
|
4
|
+
* Verifies that detectStuckLoops counts distinct dispatches (unique startedAt
|
|
5
|
+
* values per type/id) instead of raw entry count, which produces false-positive
|
|
6
|
+
* stuck-loop anomalies when idle-watchdog duplicate metrics entries exist.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import type { UnitMetrics } from "../metrics.js";
|
|
12
|
+
import { detectStuckLoops, type ForensicAnomaly } from "../forensics.js";
|
|
13
|
+
|
|
14
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function makeUnit(overrides: Partial<UnitMetrics> = {}): UnitMetrics {
|
|
17
|
+
return {
|
|
18
|
+
type: "execute-task",
|
|
19
|
+
id: "M001/S01/T01",
|
|
20
|
+
model: "claude-sonnet-4-20250514",
|
|
21
|
+
startedAt: 1000,
|
|
22
|
+
finishedAt: 2000,
|
|
23
|
+
tokens: { input: 1000, output: 500, cacheRead: 200, cacheWrite: 100, total: 1800 },
|
|
24
|
+
cost: 0.05,
|
|
25
|
+
toolCalls: 3,
|
|
26
|
+
assistantMessages: 2,
|
|
27
|
+
userMessages: 1,
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
test("#1943 detectStuckLoops does not flag idle-watchdog duplicates as stuck loops", () => {
|
|
36
|
+
const anomalies: ForensicAnomaly[] = [];
|
|
37
|
+
const startedAt = 1774011016218;
|
|
38
|
+
|
|
39
|
+
// 20 entries with the SAME startedAt — these are idle-watchdog duplicates,
|
|
40
|
+
// not real re-dispatches. They should count as 1 dispatch.
|
|
41
|
+
const units: UnitMetrics[] = [];
|
|
42
|
+
for (let i = 0; i < 20; i++) {
|
|
43
|
+
units.push(makeUnit({
|
|
44
|
+
type: "research-slice",
|
|
45
|
+
id: "M009/S02",
|
|
46
|
+
startedAt,
|
|
47
|
+
finishedAt: startedAt + (i + 1) * 15000,
|
|
48
|
+
cost: 1.50 + i * 0.05,
|
|
49
|
+
toolCalls: 0,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
detectStuckLoops(units, anomalies);
|
|
54
|
+
|
|
55
|
+
// A single dispatch (same startedAt) should NOT trigger a stuck-loop anomaly
|
|
56
|
+
assert.equal(
|
|
57
|
+
anomalies.length, 0,
|
|
58
|
+
`expected 0 anomalies for 20 watchdog snapshots of the same dispatch, got ${anomalies.length}: ${anomalies.map(a => a.summary).join(", ")}`,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("#1943 detectStuckLoops correctly flags real re-dispatches", () => {
|
|
63
|
+
const anomalies: ForensicAnomaly[] = [];
|
|
64
|
+
|
|
65
|
+
// 3 entries with DIFFERENT startedAt values — these are real re-dispatches
|
|
66
|
+
const units: UnitMetrics[] = [
|
|
67
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 1000, finishedAt: 2000, cost: 0.05 }),
|
|
68
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 3000, finishedAt: 4000, cost: 0.06 }),
|
|
69
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 5000, finishedAt: 6000, cost: 0.07 }),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
detectStuckLoops(units, anomalies);
|
|
73
|
+
|
|
74
|
+
assert.equal(anomalies.length, 1, "3 distinct dispatches of the same unit should flag 1 anomaly");
|
|
75
|
+
assert.equal(anomalies[0].type, "stuck-loop");
|
|
76
|
+
assert.ok(anomalies[0].summary.includes("3 times"), `summary should mention 3 dispatches: ${anomalies[0].summary}`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("#1943 detectStuckLoops ignores watchdog duplicates but flags real re-dispatches in mixed data", () => {
|
|
80
|
+
const anomalies: ForensicAnomaly[] = [];
|
|
81
|
+
|
|
82
|
+
const units: UnitMetrics[] = [
|
|
83
|
+
// 5 watchdog duplicates for dispatch 1 (same startedAt = 1000)
|
|
84
|
+
...Array.from({ length: 5 }, (_, i) =>
|
|
85
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 1000, finishedAt: 1000 + (i + 1) * 15000, cost: 0.05 + i * 0.01 }),
|
|
86
|
+
),
|
|
87
|
+
// 3 watchdog duplicates for dispatch 2 (same startedAt = 100000)
|
|
88
|
+
...Array.from({ length: 3 }, (_, i) =>
|
|
89
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 100000, finishedAt: 100000 + (i + 1) * 15000, cost: 0.08 + i * 0.01 }),
|
|
90
|
+
),
|
|
91
|
+
// 1 entry for dispatch 3 (startedAt = 200000)
|
|
92
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 200000, finishedAt: 260000, cost: 0.10 }),
|
|
93
|
+
// Different unit — only 1 dispatch, should NOT be flagged
|
|
94
|
+
makeUnit({ type: "plan-slice", id: "M001/S01", startedAt: 500, finishedAt: 1500, cost: 0.02 }),
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
detectStuckLoops(units, anomalies);
|
|
98
|
+
|
|
99
|
+
// M001/S01/T01 has 3 distinct dispatches (startedAt: 1000, 100000, 200000) — should be flagged
|
|
100
|
+
// M001/S01 has 1 dispatch — should NOT be flagged
|
|
101
|
+
assert.equal(anomalies.length, 1, `expected 1 anomaly (for the 3x dispatched task), got ${anomalies.length}`);
|
|
102
|
+
assert.ok(anomalies[0].summary.includes("3 times"));
|
|
103
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// GSD2 — Regression tests for git-checkpoint rollback (#3576)
|
|
2
|
+
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
|
|
3
|
+
|
|
4
|
+
import { describe, it } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { createCheckpoint, rollbackToCheckpoint, cleanupCheckpoint } from "../safety/git-checkpoint.js";
|
|
11
|
+
|
|
12
|
+
function git(args: string[], cwd: string): string {
|
|
13
|
+
return execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createTempRepo(): string {
|
|
17
|
+
const dir = mkdtempSync(join(tmpdir(), "ckpt-test-"));
|
|
18
|
+
git(["init"], dir);
|
|
19
|
+
git(["config", "user.email", "test@test.com"], dir);
|
|
20
|
+
git(["config", "user.name", "Test"], dir);
|
|
21
|
+
writeFileSync(join(dir, "file.txt"), "initial\n");
|
|
22
|
+
git(["add", "."], dir);
|
|
23
|
+
git(["commit", "-m", "init"], dir);
|
|
24
|
+
git(["branch", "-M", "main"], dir);
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("git-checkpoint rollback", () => {
|
|
29
|
+
it("rolls back to checkpoint on checked-out branch", (t) => {
|
|
30
|
+
const repo = createTempRepo();
|
|
31
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
32
|
+
|
|
33
|
+
// Create checkpoint at initial commit
|
|
34
|
+
const sha = createCheckpoint(repo, "unit-1");
|
|
35
|
+
assert.ok(sha, "checkpoint should return a SHA");
|
|
36
|
+
|
|
37
|
+
// Make a second commit
|
|
38
|
+
writeFileSync(join(repo, "file.txt"), "modified\n");
|
|
39
|
+
git(["add", "."], repo);
|
|
40
|
+
git(["commit", "-m", "second"], repo);
|
|
41
|
+
|
|
42
|
+
const headBefore = git(["rev-parse", "HEAD"], repo);
|
|
43
|
+
assert.notEqual(headBefore, sha, "HEAD should have advanced");
|
|
44
|
+
|
|
45
|
+
// Rollback — this must work on the checked-out branch
|
|
46
|
+
const result = rollbackToCheckpoint(repo, "unit-1", sha);
|
|
47
|
+
assert.equal(result, true, "rollback should succeed");
|
|
48
|
+
|
|
49
|
+
const headAfter = git(["rev-parse", "HEAD"], repo);
|
|
50
|
+
assert.equal(headAfter, sha, "HEAD should match checkpoint SHA after rollback");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns false on detached HEAD", (t) => {
|
|
54
|
+
const repo = createTempRepo();
|
|
55
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
56
|
+
|
|
57
|
+
const sha = git(["rev-parse", "HEAD"], repo);
|
|
58
|
+
git(["checkout", "--detach", sha], repo);
|
|
59
|
+
|
|
60
|
+
const result = rollbackToCheckpoint(repo, "unit-2", sha);
|
|
61
|
+
assert.equal(result, false, "rollback should fail on detached HEAD");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("cleans up checkpoint ref after rollback", (t) => {
|
|
65
|
+
const repo = createTempRepo();
|
|
66
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
67
|
+
|
|
68
|
+
const sha = createCheckpoint(repo, "unit-3");
|
|
69
|
+
assert.ok(sha);
|
|
70
|
+
|
|
71
|
+
// Ref should exist
|
|
72
|
+
const refBefore = git(["for-each-ref", "refs/gsd/checkpoints/unit-3", "--format=%(objectname)"], repo);
|
|
73
|
+
assert.equal(refBefore, sha);
|
|
74
|
+
|
|
75
|
+
rollbackToCheckpoint(repo, "unit-3", sha);
|
|
76
|
+
|
|
77
|
+
// Ref should be cleaned up
|
|
78
|
+
const refAfter = git(["for-each-ref", "refs/gsd/checkpoints/unit-3", "--format=%(objectname)"], repo);
|
|
79
|
+
assert.equal(refAfter, "", "checkpoint ref should be removed after rollback");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("cleanupCheckpoint removes the ref without error", (t) => {
|
|
83
|
+
const repo = createTempRepo();
|
|
84
|
+
t.after(() => rmSync(repo, { recursive: true, force: true }));
|
|
85
|
+
|
|
86
|
+
const sha = createCheckpoint(repo, "unit-4");
|
|
87
|
+
assert.ok(sha);
|
|
88
|
+
|
|
89
|
+
cleanupCheckpoint(repo, "unit-4");
|
|
90
|
+
|
|
91
|
+
const ref = git(["for-each-ref", "refs/gsd/checkpoints/unit-4", "--format=%(objectname)"], repo);
|
|
92
|
+
assert.equal(ref, "", "ref should be gone");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice } from '../gsd-db.ts';
|
|
5
|
+
|
|
6
|
+
test('insertSlice with minimal args does not wipe populated fields', (t) => {
|
|
7
|
+
t.after(() => { try { closeDatabase(); } catch { /* noop */ } });
|
|
8
|
+
openDatabase(":memory:");
|
|
9
|
+
|
|
10
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
11
|
+
|
|
12
|
+
// First insert: full data
|
|
13
|
+
insertSlice({
|
|
14
|
+
id: 'S01',
|
|
15
|
+
milestoneId: 'M001',
|
|
16
|
+
title: 'Auth flow',
|
|
17
|
+
status: 'in-progress',
|
|
18
|
+
risk: 'high',
|
|
19
|
+
demo: 'Login page renders.',
|
|
20
|
+
sequence: 3,
|
|
21
|
+
planning: {
|
|
22
|
+
goal: 'Secure authentication',
|
|
23
|
+
successCriteria: 'All tests pass',
|
|
24
|
+
proofLevel: 'integration',
|
|
25
|
+
integrationClosure: 'Fully integrated',
|
|
26
|
+
observabilityImpact: 'Metrics available',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const before = getSlice('M001', 'S01');
|
|
31
|
+
assert.ok(before, 'slice should exist after first insert');
|
|
32
|
+
assert.equal(before.title, 'Auth flow');
|
|
33
|
+
assert.equal(before.demo, 'Login page renders.');
|
|
34
|
+
assert.equal(before.risk, 'high');
|
|
35
|
+
|
|
36
|
+
// Second insert: minimal "ensure exists" call (mirrors complete-task.ts usage)
|
|
37
|
+
insertSlice({ id: 'S01', milestoneId: 'M001' });
|
|
38
|
+
|
|
39
|
+
const after = getSlice('M001', 'S01');
|
|
40
|
+
assert.ok(after, 'slice should still exist after second insert');
|
|
41
|
+
|
|
42
|
+
// These must NOT be wiped to empty strings
|
|
43
|
+
assert.equal(after.title, 'Auth flow', 'title must survive minimal re-insert');
|
|
44
|
+
assert.equal(after.demo, 'Login page renders.', 'demo must survive minimal re-insert');
|
|
45
|
+
assert.equal(after.risk, 'high', 'risk must survive minimal re-insert');
|
|
46
|
+
assert.equal(after.sequence, 3, 'sequence must survive minimal re-insert');
|
|
47
|
+
|
|
48
|
+
// Planning fields must also survive
|
|
49
|
+
assert.equal(after.goal, 'Secure authentication', 'goal must survive minimal re-insert');
|
|
50
|
+
assert.equal(after.success_criteria, 'All tests pass', 'success_criteria must survive');
|
|
51
|
+
assert.equal(after.proof_level, 'integration', 'proof_level must survive');
|
|
52
|
+
assert.equal(after.integration_closure, 'Fully integrated', 'integration_closure must survive');
|
|
53
|
+
assert.equal(after.observability_impact, 'Metrics available', 'observability_impact must survive');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('insertSlice ON CONFLICT preserves completed status', (t) => {
|
|
57
|
+
t.after(() => { try { closeDatabase(); } catch { /* noop */ } });
|
|
58
|
+
openDatabase(":memory:");
|
|
59
|
+
|
|
60
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
61
|
+
|
|
62
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Done slice', status: 'complete' });
|
|
63
|
+
|
|
64
|
+
// Re-insert with pending status (default) should NOT overwrite complete
|
|
65
|
+
insertSlice({ id: 'S01', milestoneId: 'M001' });
|
|
66
|
+
|
|
67
|
+
const after = getSlice('M001', 'S01');
|
|
68
|
+
assert.ok(after);
|
|
69
|
+
assert.equal(after.status, 'complete', 'completed status must not be overwritten');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('insertSlice ON CONFLICT allows explicit updates to non-empty values', (t) => {
|
|
73
|
+
t.after(() => { try { closeDatabase(); } catch { /* noop */ } });
|
|
74
|
+
openDatabase(":memory:");
|
|
75
|
+
|
|
76
|
+
insertMilestone({ id: 'M001', title: 'Milestone', status: 'active' });
|
|
77
|
+
|
|
78
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Original', demo: 'Old demo', risk: 'low' });
|
|
79
|
+
|
|
80
|
+
// Explicit update with real values should overwrite
|
|
81
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'Updated', demo: 'New demo', risk: 'high' });
|
|
82
|
+
|
|
83
|
+
const after = getSlice('M001', 'S01');
|
|
84
|
+
assert.ok(after);
|
|
85
|
+
assert.equal(after.title, 'Updated', 'explicit title update should apply');
|
|
86
|
+
assert.equal(after.demo, 'New demo', 'explicit demo update should apply');
|
|
87
|
+
assert.equal(after.risk, 'high', 'explicit risk update should apply');
|
|
88
|
+
});
|
|
@@ -1246,7 +1246,7 @@ describe('git-service', async () => {
|
|
|
1246
1246
|
test('nativeAddAllWithExclusions: symlinked .gsd fallback', () => {
|
|
1247
1247
|
// When .gsd is a symlink, git rejects `:!.gsd/...` pathspecs with
|
|
1248
1248
|
// "fatal: pathspec '...' is beyond a symbolic link". The fix falls
|
|
1249
|
-
// back to
|
|
1249
|
+
// back to `git add -u` (tracked files only), NOT `git add -A`.
|
|
1250
1250
|
const repo = initTempRepo();
|
|
1251
1251
|
|
|
1252
1252
|
// Create the real .gsd directory outside the repo, then symlink it
|
|
@@ -1258,11 +1258,18 @@ describe('git-service', async () => {
|
|
|
1258
1258
|
// Symlink .gsd -> external directory
|
|
1259
1259
|
symlinkSync(externalGsd, join(repo, ".gsd"));
|
|
1260
1260
|
|
|
1261
|
-
// Add .gitignore so
|
|
1261
|
+
// Add .gitignore so .gsd/ is ignored
|
|
1262
1262
|
writeFileSync(join(repo, ".gitignore"), ".gsd\n");
|
|
1263
1263
|
|
|
1264
|
-
// Create a
|
|
1264
|
+
// Create a tracked file and commit it, then modify it
|
|
1265
1265
|
createFile(repo, "src/app.ts", "export const x = 1;");
|
|
1266
|
+
run("git add -A", repo);
|
|
1267
|
+
run('git commit -m "add app"', repo);
|
|
1268
|
+
writeFileSync(join(repo, "src/app.ts"), "export const x = 2;");
|
|
1269
|
+
|
|
1270
|
+
// Create an untracked file simulating large data (NOT in .gitignore)
|
|
1271
|
+
// This is the key scenario: large untracked dirs that git add -A would traverse
|
|
1272
|
+
createFile(repo, "data/large-model.bin", "pretend this is 10GB");
|
|
1266
1273
|
|
|
1267
1274
|
// nativeAddAllWithExclusions should NOT throw despite .gsd being a symlink
|
|
1268
1275
|
let threw = false;
|
|
@@ -1274,9 +1281,15 @@ describe('git-service', async () => {
|
|
|
1274
1281
|
}
|
|
1275
1282
|
assert.ok(!threw, "nativeAddAllWithExclusions does not throw with symlinked .gsd");
|
|
1276
1283
|
|
|
1277
|
-
// Verify the
|
|
1284
|
+
// Verify the tracked modified file was staged
|
|
1278
1285
|
const staged = run("git diff --cached --name-only", repo);
|
|
1279
|
-
assert.ok(staged.includes("src/app.ts"), "
|
|
1286
|
+
assert.ok(staged.includes("src/app.ts"), "modified tracked file staged despite symlinked .gsd");
|
|
1287
|
+
|
|
1288
|
+
// CRITICAL: untracked files must NOT be staged — the symlink fallback
|
|
1289
|
+
// should use `git add -u` (tracked only), not `git add -A` (all files).
|
|
1290
|
+
// Using `git add -A` on a repo with large untracked data dirs hangs. (#1977)
|
|
1291
|
+
assert.ok(!staged.includes("data/large-model.bin"),
|
|
1292
|
+
"symlink fallback must not stage untracked files (would hang on large repos)");
|
|
1280
1293
|
assert.ok(!staged.includes(".gsd"), ".gsd content not staged");
|
|
1281
1294
|
|
|
1282
1295
|
rmSync(repo, { recursive: true, force: true });
|
|
@@ -1435,13 +1448,20 @@ describe('git-service', async () => {
|
|
|
1435
1448
|
run('git add .gitignore', repo);
|
|
1436
1449
|
run('git commit -m "add gitignore"', repo);
|
|
1437
1450
|
|
|
1451
|
+
// Pre-commit a tracked source file so git add -u can stage modifications.
|
|
1452
|
+
// The symlink fallback uses git add -u (tracked files only), so the file
|
|
1453
|
+
// must be tracked before the autoCommit scenario runs.
|
|
1454
|
+
createFile(repo, "src/feature.ts", "export const feature = true;");
|
|
1455
|
+
run('git add src/feature.ts', repo);
|
|
1456
|
+
run('git commit -m "add feature"', repo);
|
|
1457
|
+
|
|
1438
1458
|
// Simulate new milestone artifacts created during execution
|
|
1439
1459
|
writeFileSync(join(externalGsd, "milestones", "M009", "M009-SUMMARY.md"), "# M009 Summary");
|
|
1440
1460
|
writeFileSync(join(externalGsd, "milestones", "M009", "S01-SUMMARY.md"), "# S01 Summary");
|
|
1441
1461
|
writeFileSync(join(externalGsd, "milestones", "M009", "T01-VERIFY.json"), '{"passed":true}');
|
|
1442
1462
|
|
|
1443
|
-
//
|
|
1444
|
-
|
|
1463
|
+
// Modify the tracked source file — git add -u will stage this change
|
|
1464
|
+
writeFileSync(join(repo, "src/feature.ts"), "export const feature = false; // updated");
|
|
1445
1465
|
|
|
1446
1466
|
const svc = new GitServiceImpl(repo);
|
|
1447
1467
|
const msg = svc.autoCommit("complete-milestone", "M009");
|
|
@@ -357,3 +357,37 @@ test('writeBlockerPlaceholder: does NOT update DB for non-execute-task types', a
|
|
|
357
357
|
cleanup(base);
|
|
358
358
|
}
|
|
359
359
|
});
|
|
360
|
+
|
|
361
|
+
test('writeBlockerPlaceholder: updates DB slice status for complete-slice (#2653)', async () => {
|
|
362
|
+
const base = createFixtureBase();
|
|
363
|
+
try {
|
|
364
|
+
const { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, isDbAvailable } =
|
|
365
|
+
await import("../../gsd-db.ts");
|
|
366
|
+
|
|
367
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
368
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
|
|
369
|
+
|
|
370
|
+
openDatabase(dbPath);
|
|
371
|
+
try {
|
|
372
|
+
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
|
373
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active" });
|
|
374
|
+
|
|
375
|
+
// complete-slice blocker should update slice DB status to "complete"
|
|
376
|
+
writeBlockerPlaceholder("complete-slice", "M001/S01", base, "context exhaustion recovery");
|
|
377
|
+
|
|
378
|
+
const slice = getSlice("M001", "S01");
|
|
379
|
+
assert.equal(slice?.status, "complete",
|
|
380
|
+
"writeBlockerPlaceholder must update DB slice status to 'complete' for complete-slice so dispatch guard unblocks downstream (#2653)");
|
|
381
|
+
|
|
382
|
+
// Verify the full chain works: verifyExpectedArtifact should return true
|
|
383
|
+
// (requires both UAT file and DB status = complete)
|
|
384
|
+
// Note: the placeholder writes a SUMMARY file, but complete-slice also needs UAT.
|
|
385
|
+
// The placeholder itself doesn't write UAT, so artifact verification may still fail
|
|
386
|
+
// for complete-slice — but the DB status is now correct, breaking the circular dep.
|
|
387
|
+
} finally {
|
|
388
|
+
if (isDbAvailable()) closeDatabase();
|
|
389
|
+
}
|
|
390
|
+
} finally {
|
|
391
|
+
cleanup(base);
|
|
392
|
+
}
|
|
393
|
+
});
|