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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import test from "node:test";
|
|
8
8
|
import assert from "node:assert/strict";
|
|
9
|
-
import { mkdtempSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
9
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import {
|
|
@@ -382,3 +382,118 @@ test("snapshotUnitMetrics counts toolCall blocks correctly (#1713)", () => {
|
|
|
382
382
|
rmSync(tmpBase, { recursive: true, force: true });
|
|
383
383
|
}
|
|
384
384
|
});
|
|
385
|
+
|
|
386
|
+
// ── #1943 — Duplicate metrics entries from idle watchdog ──────────────────────
|
|
387
|
+
|
|
388
|
+
test("#1943 initMetrics deduplicates entries loaded from a corrupted disk ledger", () => {
|
|
389
|
+
const tmpBase = mkdtempSync(join(tmpdir(), "gsd-metrics-dedup-load-"));
|
|
390
|
+
mkdirSync(join(tmpBase, ".gsd"), { recursive: true });
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
resetMetrics();
|
|
394
|
+
|
|
395
|
+
// Simulate a corrupted metrics.json with duplicate entries on disk
|
|
396
|
+
// (same type+id+startedAt but different finishedAt — idle watchdog pattern)
|
|
397
|
+
const corruptedLedger: MetricsLedger = {
|
|
398
|
+
version: 1,
|
|
399
|
+
projectStartedAt: 1700000000000,
|
|
400
|
+
units: [
|
|
401
|
+
makeUnit({ type: "research-slice", id: "M009/S02", startedAt: 1774011016218, finishedAt: 1774011031218, cost: 1.50, tokens: { input: 6600000, output: 100000, cacheRead: 0, cacheWrite: 0, total: 6700000 } }),
|
|
402
|
+
makeUnit({ type: "research-slice", id: "M009/S02", startedAt: 1774011016218, finishedAt: 1774011046218, cost: 1.55, tokens: { input: 6800000, output: 110000, cacheRead: 0, cacheWrite: 0, total: 6910000 } }),
|
|
403
|
+
makeUnit({ type: "research-slice", id: "M009/S02", startedAt: 1774011016218, finishedAt: 1774011061218, cost: 1.60, tokens: { input: 7000000, output: 120000, cacheRead: 0, cacheWrite: 0, total: 7120000 } }),
|
|
404
|
+
makeUnit({ type: "research-slice", id: "M009/S02", startedAt: 1774011016218, finishedAt: 1774011076218, cost: 1.65, tokens: { input: 7200000, output: 130000, cacheRead: 0, cacheWrite: 0, total: 7330000 } }),
|
|
405
|
+
// A different unit — should be preserved
|
|
406
|
+
makeUnit({ type: "execute-task", id: "M001/S01/T01", startedAt: 1774012000000, finishedAt: 1774012060000, cost: 0.50 }),
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
writeFileSync(
|
|
410
|
+
join(tmpBase, ".gsd", "metrics.json"),
|
|
411
|
+
JSON.stringify(corruptedLedger, null, 2),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
// Load the corrupted ledger — duplicates should be collapsed on load
|
|
415
|
+
initMetrics(tmpBase);
|
|
416
|
+
const ledger = getLedger();
|
|
417
|
+
assert.ok(ledger);
|
|
418
|
+
|
|
419
|
+
// The 4 entries with identical (type, id, startedAt) should collapse to 1,
|
|
420
|
+
// keeping the latest (highest finishedAt). Plus the 1 different unit = 2 total.
|
|
421
|
+
assert.equal(
|
|
422
|
+
ledger!.units.length, 2,
|
|
423
|
+
`expected 2 entries after dedup (1 collapsed group + 1 unique), got ${ledger!.units.length}`,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// The surviving duplicate should be the one with the latest finishedAt
|
|
427
|
+
const researchEntry = ledger!.units.find(u => u.type === "research-slice");
|
|
428
|
+
assert.ok(researchEntry);
|
|
429
|
+
assert.equal(researchEntry!.finishedAt, 1774011076218, "should keep the latest finishedAt");
|
|
430
|
+
assert.equal(researchEntry!.cost, 1.65, "should keep the latest cost");
|
|
431
|
+
|
|
432
|
+
// The on-disk file should also be deduplicated
|
|
433
|
+
const diskRaw = readFileSync(join(tmpBase, ".gsd", "metrics.json"), "utf-8");
|
|
434
|
+
const diskLedger: MetricsLedger = JSON.parse(diskRaw);
|
|
435
|
+
assert.equal(diskLedger.units.length, 2, "disk should also have deduplicated entries");
|
|
436
|
+
} finally {
|
|
437
|
+
resetMetrics();
|
|
438
|
+
rmSync(tmpBase, { recursive: true, force: true });
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("#1943 getProjectTotals reports correct cost after dedup (no 35% inflation)", () => {
|
|
443
|
+
// Simulate the exact scenario from the issue: 20 entries for a single dispatch
|
|
444
|
+
// with monotonically increasing token counts and 15s-apart finishedAt values
|
|
445
|
+
const startedAt = 1774011016218;
|
|
446
|
+
const baseCost = 1.50;
|
|
447
|
+
const duplicateUnits: UnitMetrics[] = [];
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < 20; i++) {
|
|
450
|
+
duplicateUnits.push(makeUnit({
|
|
451
|
+
type: "research-slice",
|
|
452
|
+
id: "M009/S02",
|
|
453
|
+
startedAt,
|
|
454
|
+
finishedAt: startedAt + (i + 1) * 15000,
|
|
455
|
+
cost: baseCost + i * 0.05,
|
|
456
|
+
toolCalls: 0,
|
|
457
|
+
tokens: {
|
|
458
|
+
input: 6600000 + i * 200000,
|
|
459
|
+
output: 100000 + i * 10000,
|
|
460
|
+
cacheRead: 0,
|
|
461
|
+
cacheWrite: 0,
|
|
462
|
+
total: 6700000 + i * 210000,
|
|
463
|
+
},
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Without dedup, getProjectTotals would sum all 20 entries' costs
|
|
468
|
+
const rawTotals = getProjectTotals(duplicateUnits);
|
|
469
|
+
// With dedup (only last entry should count), cost should be the last entry's cost
|
|
470
|
+
const lastEntryCost = duplicateUnits[duplicateUnits.length - 1].cost;
|
|
471
|
+
|
|
472
|
+
// This test documents the bug: raw totals inflate cost by summing duplicates
|
|
473
|
+
assert.ok(
|
|
474
|
+
rawTotals.cost > lastEntryCost * 2,
|
|
475
|
+
"raw totals with duplicates inflate cost (bug demonstration)",
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
// After loading through initMetrics (which should dedup), totals should be correct
|
|
479
|
+
const tmpBase = mkdtempSync(join(tmpdir(), "gsd-metrics-cost-inflation-"));
|
|
480
|
+
mkdirSync(join(tmpBase, ".gsd"), { recursive: true });
|
|
481
|
+
try {
|
|
482
|
+
resetMetrics();
|
|
483
|
+
writeFileSync(
|
|
484
|
+
join(tmpBase, ".gsd", "metrics.json"),
|
|
485
|
+
JSON.stringify({ version: 1, projectStartedAt: 1700000000000, units: duplicateUnits }, null, 2),
|
|
486
|
+
);
|
|
487
|
+
initMetrics(tmpBase);
|
|
488
|
+
const ledger = getLedger()!;
|
|
489
|
+
const dedupedTotals = getProjectTotals(ledger.units);
|
|
490
|
+
assert.equal(ledger.units.length, 1, "20 duplicates should collapse to 1 entry");
|
|
491
|
+
assert.equal(
|
|
492
|
+
dedupedTotals.cost, lastEntryCost,
|
|
493
|
+
`deduped cost should be ${lastEntryCost}, not ${dedupedTotals.cost}`,
|
|
494
|
+
);
|
|
495
|
+
} finally {
|
|
496
|
+
resetMetrics();
|
|
497
|
+
rmSync(tmpBase, { recursive: true, force: true });
|
|
498
|
+
}
|
|
499
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// GSD2 — Tests for gsd_milestone_status read-only query tool
|
|
2
|
+
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
import { registerQueryTools } from "../bootstrap/query-tools.ts";
|
|
11
|
+
import {
|
|
12
|
+
openDatabase,
|
|
13
|
+
closeDatabase,
|
|
14
|
+
_getAdapter,
|
|
15
|
+
} from "../gsd-db.ts";
|
|
16
|
+
|
|
17
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function makeMockPi() {
|
|
20
|
+
const tools: any[] = [];
|
|
21
|
+
return {
|
|
22
|
+
registerTool: (tool: any) => tools.push(tool),
|
|
23
|
+
tools,
|
|
24
|
+
} as any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeTmpBase(): string {
|
|
28
|
+
const base = join(tmpdir(), `gsd-query-tool-test-${randomUUID()}`);
|
|
29
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
30
|
+
return base;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cleanup(base: string): void {
|
|
34
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* swallow */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function openTestDb(base: string): void {
|
|
38
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function executeToolInDir(tool: any, params: Record<string, unknown>, dir: string) {
|
|
42
|
+
const originalCwd = process.cwd();
|
|
43
|
+
try {
|
|
44
|
+
process.chdir(dir);
|
|
45
|
+
return await tool.execute("test-call-id", params, undefined, undefined, undefined);
|
|
46
|
+
} finally {
|
|
47
|
+
process.chdir(originalCwd);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Seed helpers ─────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
function seedMilestone(milestoneId: string, title: string, status = "active"): void {
|
|
54
|
+
const db = _getAdapter();
|
|
55
|
+
if (!db) throw new Error("DB not open");
|
|
56
|
+
db.prepare(
|
|
57
|
+
"INSERT OR REPLACE INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)",
|
|
58
|
+
).run(milestoneId, title, status, new Date().toISOString());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function seedSlice(milestoneId: string, sliceId: string, status: string): void {
|
|
62
|
+
const db = _getAdapter();
|
|
63
|
+
if (!db) throw new Error("DB not open");
|
|
64
|
+
db.prepare(
|
|
65
|
+
"INSERT OR REPLACE INTO slices (milestone_id, id, title, status, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
66
|
+
).run(milestoneId, sliceId, `Slice ${sliceId}`, status, new Date().toISOString());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function seedTask(milestoneId: string, sliceId: string, taskId: string, status: string): void {
|
|
70
|
+
const db = _getAdapter();
|
|
71
|
+
if (!db) throw new Error("DB not open");
|
|
72
|
+
db.prepare(
|
|
73
|
+
"INSERT OR REPLACE INTO tasks (milestone_id, slice_id, id, title, status) VALUES (?, ?, ?, ?, ?)",
|
|
74
|
+
).run(milestoneId, sliceId, taskId, `Task ${taskId}`, status);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Registration ─────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
test("registerQueryTools registers gsd_milestone_status tool", () => {
|
|
80
|
+
const pi = makeMockPi();
|
|
81
|
+
registerQueryTools(pi);
|
|
82
|
+
assert.equal(pi.tools.length, 1, "Should register exactly one tool");
|
|
83
|
+
assert.equal(pi.tools[0].name, "gsd_milestone_status");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("gsd_milestone_status has promptGuidelines mentioning prohibited alternatives", () => {
|
|
87
|
+
const pi = makeMockPi();
|
|
88
|
+
registerQueryTools(pi);
|
|
89
|
+
const tool = pi.tools[0];
|
|
90
|
+
assert.ok(Array.isArray(tool.promptGuidelines), "promptGuidelines must be an array");
|
|
91
|
+
assert.ok(tool.promptGuidelines.length >= 1, "Must have at least one guideline");
|
|
92
|
+
const joined = tool.promptGuidelines.join(" ");
|
|
93
|
+
assert.match(joined, /sqlite3|better-sqlite3/, "Guidelines must mention prohibited alternatives");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ─── Happy path: milestone with slices and tasks ──────────────────────────────
|
|
97
|
+
|
|
98
|
+
test("gsd_milestone_status returns milestone metadata and slice statuses", async () => {
|
|
99
|
+
const base = makeTmpBase();
|
|
100
|
+
try {
|
|
101
|
+
openTestDb(base);
|
|
102
|
+
seedMilestone("M001", "Test Milestone");
|
|
103
|
+
seedSlice("M001", "S01", "complete");
|
|
104
|
+
seedSlice("M001", "S02", "active");
|
|
105
|
+
seedTask("M001", "S01", "T01", "done");
|
|
106
|
+
seedTask("M001", "S01", "T02", "done");
|
|
107
|
+
seedTask("M001", "S02", "T01", "pending");
|
|
108
|
+
|
|
109
|
+
const pi = makeMockPi();
|
|
110
|
+
registerQueryTools(pi);
|
|
111
|
+
const tool = pi.tools[0];
|
|
112
|
+
|
|
113
|
+
const result = await executeToolInDir(tool, { milestoneId: "M001" }, base);
|
|
114
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
115
|
+
|
|
116
|
+
assert.equal(parsed.milestoneId, "M001");
|
|
117
|
+
assert.equal(parsed.title, "Test Milestone");
|
|
118
|
+
assert.equal(parsed.status, "active");
|
|
119
|
+
assert.equal(parsed.sliceCount, 2);
|
|
120
|
+
assert.equal(parsed.slices.length, 2);
|
|
121
|
+
|
|
122
|
+
const s01 = parsed.slices.find((s: any) => s.id === "S01");
|
|
123
|
+
assert.ok(s01, "S01 should be in slices");
|
|
124
|
+
assert.equal(s01.status, "complete");
|
|
125
|
+
assert.equal(s01.taskCounts.total, 2);
|
|
126
|
+
assert.equal(s01.taskCounts.done, 2);
|
|
127
|
+
|
|
128
|
+
const s02 = parsed.slices.find((s: any) => s.id === "S02");
|
|
129
|
+
assert.ok(s02, "S02 should be in slices");
|
|
130
|
+
assert.equal(s02.status, "active");
|
|
131
|
+
assert.equal(s02.taskCounts.pending, 1);
|
|
132
|
+
} finally {
|
|
133
|
+
closeDatabase();
|
|
134
|
+
cleanup(base);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ─── Milestone with no slices ─────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
test("gsd_milestone_status returns empty slices array for milestone with no slices", async () => {
|
|
141
|
+
const base = makeTmpBase();
|
|
142
|
+
try {
|
|
143
|
+
openTestDb(base);
|
|
144
|
+
seedMilestone("M002", "Empty Milestone");
|
|
145
|
+
|
|
146
|
+
const pi = makeMockPi();
|
|
147
|
+
registerQueryTools(pi);
|
|
148
|
+
const tool = pi.tools[0];
|
|
149
|
+
|
|
150
|
+
const result = await executeToolInDir(tool, { milestoneId: "M002" }, base);
|
|
151
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
152
|
+
|
|
153
|
+
assert.equal(parsed.milestoneId, "M002");
|
|
154
|
+
assert.equal(parsed.sliceCount, 0);
|
|
155
|
+
assert.deepEqual(parsed.slices, []);
|
|
156
|
+
} finally {
|
|
157
|
+
closeDatabase();
|
|
158
|
+
cleanup(base);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// ─── Missing milestone ────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
test("gsd_milestone_status returns not-found for missing milestone", async () => {
|
|
165
|
+
const base = makeTmpBase();
|
|
166
|
+
try {
|
|
167
|
+
openTestDb(base);
|
|
168
|
+
|
|
169
|
+
const pi = makeMockPi();
|
|
170
|
+
registerQueryTools(pi);
|
|
171
|
+
const tool = pi.tools[0];
|
|
172
|
+
|
|
173
|
+
const result = await executeToolInDir(tool, { milestoneId: "M999" }, base);
|
|
174
|
+
assert.match(result.content[0].text, /M999.*not found/i);
|
|
175
|
+
assert.equal(result.details.found, false);
|
|
176
|
+
} finally {
|
|
177
|
+
closeDatabase();
|
|
178
|
+
cleanup(base);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ─── DB unavailable ───────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
test("gsd_milestone_status handles missing DB gracefully", async () => {
|
|
185
|
+
// Create a directory without .gsd/ to ensure ensureDbOpen has nothing to open
|
|
186
|
+
const base = join(tmpdir(), `gsd-no-db-${randomUUID()}`);
|
|
187
|
+
mkdirSync(base, { recursive: true });
|
|
188
|
+
closeDatabase(); // ensure no prior DB is open
|
|
189
|
+
try {
|
|
190
|
+
const pi = makeMockPi();
|
|
191
|
+
registerQueryTools(pi);
|
|
192
|
+
const tool = pi.tools[0];
|
|
193
|
+
|
|
194
|
+
const result = await executeToolInDir(tool, { milestoneId: "M001" }, base);
|
|
195
|
+
assert.match(result.content[0].text, /GSD database is not available/);
|
|
196
|
+
assert.equal(result.details.error, "db_unavailable");
|
|
197
|
+
} finally {
|
|
198
|
+
closeDatabase();
|
|
199
|
+
cleanup(base);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
@@ -38,8 +38,9 @@ test("upsertMilestonePlanning updates title when DB row pre-exists with empty ti
|
|
|
38
38
|
|
|
39
39
|
// Step 3: upsertMilestonePlanning should update the title
|
|
40
40
|
upsertMilestonePlanning("M099", {
|
|
41
|
+
title: "My Important Milestone",
|
|
41
42
|
vision: "Test vision",
|
|
42
|
-
}
|
|
43
|
+
});
|
|
43
44
|
const afterUpsert = getMilestone("M099");
|
|
44
45
|
assert.ok(afterUpsert);
|
|
45
46
|
assert.equal(
|
|
@@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync, writeFileSync
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
|
|
7
|
-
import { openDatabase, closeDatabase, getMilestone, getMilestoneSlices, updateSliceStatus } from '../gsd-db.ts';
|
|
7
|
+
import { openDatabase, closeDatabase, getMilestone, getMilestoneSlices, getSlice, updateSliceStatus, deleteSlice, insertMilestone } from '../gsd-db.ts';
|
|
8
8
|
import { handlePlanMilestone } from '../tools/plan-milestone.ts';
|
|
9
9
|
import { parseRoadmap } from '../parsers-legacy.ts';
|
|
10
10
|
|
|
@@ -198,33 +198,97 @@ test('handlePlanMilestone reruns idempotently and updates existing planning stat
|
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
test('handlePlanMilestone refuses to re-plan a milestone with completed slices (#2960)', async () => {
|
|
201
|
+
test('handlePlanMilestone preserves completed slice status on re-plan (#2558)', async () => {
|
|
203
202
|
const base = makeTmpBase();
|
|
204
203
|
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
205
204
|
openDatabase(dbPath);
|
|
206
205
|
|
|
207
206
|
try {
|
|
208
|
-
//
|
|
207
|
+
// Initial plan — both slices start as "pending"
|
|
209
208
|
const first = await handlePlanMilestone(validParams(), base);
|
|
210
|
-
assert.ok(!('error' in first), `
|
|
209
|
+
assert.ok(!('error' in first), `unexpected error: ${'error' in first ? first.error : ''}`);
|
|
211
210
|
|
|
212
|
-
// Mark S01 as complete
|
|
213
|
-
updateSliceStatus('M001', 'S01', 'complete');
|
|
211
|
+
// Mark S01 as complete (simulates work done in a worktree)
|
|
212
|
+
updateSliceStatus('M001', 'S01', 'complete', new Date().toISOString());
|
|
214
213
|
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
const s01Before = getSlice('M001', 'S01');
|
|
215
|
+
assert.equal(s01Before?.status, 'complete', 'S01 should be complete before re-plan');
|
|
216
|
+
|
|
217
|
+
// Re-plan the same milestone — S01 must stay "complete", S02 stays "pending"
|
|
218
|
+
const second = await handlePlanMilestone(validParams(), base);
|
|
219
|
+
assert.ok(!('error' in second), `unexpected error: ${'error' in second ? second.error : ''}`);
|
|
220
|
+
|
|
221
|
+
const s01After = getSlice('M001', 'S01');
|
|
222
|
+
assert.equal(s01After?.status, 'complete', 'S01 status must be preserved as complete after re-plan');
|
|
223
|
+
|
|
224
|
+
const s02After = getSlice('M001', 'S02');
|
|
225
|
+
assert.equal(s02After?.status, 'pending', 'S02 should remain pending');
|
|
226
|
+
} finally {
|
|
227
|
+
cleanup(base);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('plan-milestone re-plan preserves completed status and updates slice fields (#2558)', async () => {
|
|
232
|
+
const base = makeTmpBase();
|
|
233
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
234
|
+
openDatabase(dbPath);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// Initial plan — both slices start as "pending"
|
|
238
|
+
const first = await handlePlanMilestone(validParams(), base);
|
|
239
|
+
assert.ok(!('error' in first), `unexpected error: ${'error' in first ? first.error : ''}`);
|
|
240
|
+
|
|
241
|
+
// Mark S01 as complete (simulates work done in worktree, then reconciled)
|
|
242
|
+
updateSliceStatus('M001', 'S01', 'complete', new Date().toISOString());
|
|
243
|
+
assert.equal(getSlice('M001', 'S01')?.status, 'complete');
|
|
244
|
+
|
|
245
|
+
// Re-plan with updated title for S01.
|
|
246
|
+
// The handler must:
|
|
247
|
+
// 1. NOT downgrade S01 from "complete" to "pending"
|
|
248
|
+
// 2. Update S01's non-status fields (title, risk, depends, demo)
|
|
249
|
+
// 3. Keep S02 as "pending"
|
|
250
|
+
const updatedParams = {
|
|
217
251
|
...validParams(),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
252
|
+
slices: [
|
|
253
|
+
{ ...validParams().slices[0], title: 'Updated S01 title', risk: 'high' },
|
|
254
|
+
validParams().slices[1],
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
const second = await handlePlanMilestone(updatedParams, base);
|
|
258
|
+
assert.ok(!('error' in second), `unexpected error: ${'error' in second ? second.error : ''}`);
|
|
223
259
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
assert.equal(
|
|
260
|
+
const s01After = getSlice('M001', 'S01');
|
|
261
|
+
assert.equal(s01After?.status, 'complete', 'completed slice status must survive re-plan');
|
|
262
|
+
assert.equal(s01After?.title, 'Updated S01 title', 'title should update on re-plan');
|
|
263
|
+
assert.equal(s01After?.risk, 'high', 'risk should update on re-plan');
|
|
264
|
+
|
|
265
|
+
const s02After = getSlice('M001', 'S02');
|
|
266
|
+
assert.equal(s02After?.status, 'pending', 'pending slice stays pending');
|
|
267
|
+
} finally {
|
|
268
|
+
cleanup(base);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('handlePlanMilestone promotes pre-existing queued milestone to active (#3022)', async () => {
|
|
273
|
+
const base = makeTmpBase();
|
|
274
|
+
const dbPath = join(base, '.gsd', 'gsd.db');
|
|
275
|
+
openDatabase(dbPath);
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Simulate ensureMilestoneDbRow: pre-create row with status "queued"
|
|
279
|
+
// (this is what gsd_milestone_generate_id does)
|
|
280
|
+
insertMilestone({ id: 'M001', status: 'queued' });
|
|
281
|
+
|
|
282
|
+
const before = getMilestone('M001');
|
|
283
|
+
assert.equal(before?.status, 'queued', 'pre-condition: milestone should start as queued');
|
|
284
|
+
|
|
285
|
+
// Now plan the milestone — status should be promoted to "active"
|
|
286
|
+
const result = await handlePlanMilestone(validParams(), base);
|
|
287
|
+
assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
|
|
288
|
+
|
|
289
|
+
const after = getMilestone('M001');
|
|
290
|
+
assert.equal(after?.status, 'active', 'milestone status should be promoted from queued to active');
|
|
291
|
+
assert.equal(after?.title, 'DB-backed planning', 'milestone title should be set');
|
|
228
292
|
} finally {
|
|
229
293
|
cleanup(base);
|
|
230
294
|
}
|
|
@@ -412,6 +412,16 @@ test("unrecognized format warning is emitted at most once (#2373)", () => {
|
|
|
412
412
|
}
|
|
413
413
|
});
|
|
414
414
|
|
|
415
|
+
test("parsePreferencesMarkdown parses heading+list format without frontmatter (#2036)", () => {
|
|
416
|
+
// A GSD agent recovery session wrote preferences in markdown heading+list
|
|
417
|
+
// format instead of YAML frontmatter. Since the heading+list fallback parser
|
|
418
|
+
// was added, this format is now handled gracefully.
|
|
419
|
+
const content = "## Git\n\n- isolation: none\n";
|
|
420
|
+
const result = parsePreferencesMarkdown(content);
|
|
421
|
+
assert.notEqual(result, null, "heading+list content should be parsed");
|
|
422
|
+
assert.deepStrictEqual(result!.git, { isolation: "none" });
|
|
423
|
+
});
|
|
424
|
+
|
|
415
425
|
// ── Experimental preferences ─────────────────────────────────────────────────
|
|
416
426
|
|
|
417
427
|
test("experimental.rtk: true is accepted and stored", () => {
|
|
@@ -231,6 +231,31 @@ test("complete-slice prompt uses camelCase parameter names matching TypeBox sche
|
|
|
231
231
|
assert.match(toolCallLine!, /sliceId/);
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
+
// ─── File system safety: complete-slice parity with complete-milestone (#2935) ──
|
|
235
|
+
|
|
236
|
+
test("complete-slice prompt includes filesystem safety guard against EISDIR", () => {
|
|
237
|
+
const prompt = readPrompt("complete-slice");
|
|
238
|
+
assert.match(
|
|
239
|
+
prompt,
|
|
240
|
+
/File system safety/i,
|
|
241
|
+
"complete-slice.md must include a 'File system safety' instruction to prevent EISDIR errors when the LLM passes a directory path to the read tool"
|
|
242
|
+
);
|
|
243
|
+
assert.match(
|
|
244
|
+
prompt,
|
|
245
|
+
/never pass.*directory path.*directly to the.*read.*tool/i,
|
|
246
|
+
"complete-slice.md must warn against passing directory paths to the read tool"
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("complete-milestone prompt still has its filesystem safety guard (regression)", () => {
|
|
251
|
+
const prompt = readPrompt("complete-milestone");
|
|
252
|
+
assert.match(
|
|
253
|
+
prompt,
|
|
254
|
+
/File system safety/i,
|
|
255
|
+
"complete-milestone.md must keep its filesystem safety guard"
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
|
|
234
259
|
test("reactive-execute prompt references tool calls instead of checkbox updates", () => {
|
|
235
260
|
const prompt = readPrompt("reactive-execute");
|
|
236
261
|
assert.doesNotMatch(prompt, /checkbox updates/);
|
|
@@ -133,6 +133,19 @@ test("parseRoadmapSlices: table with glyph completion markers (#2841)", () => {
|
|
|
133
133
|
assert.equal(slices[3]?.done, true);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
test("parseRoadmapSlices: table with heavy check mark U+2714 (#2940)", () => {
|
|
137
|
+
const tableContent = [
|
|
138
|
+
"# M003: Heavy Check", "", "## Slices", "",
|
|
139
|
+
"| Slice | Title | Risk | Status |", "|---|---|---|---|",
|
|
140
|
+
"| S01 | First | Low | \u2714 |",
|
|
141
|
+
"| S02 | Second | High | Pending |", "",
|
|
142
|
+
].join("\n");
|
|
143
|
+
const slices = parseRoadmapSlices(tableContent);
|
|
144
|
+
assert.equal(slices.length, 2);
|
|
145
|
+
assert.equal(slices[0]?.done, true, "U+2714 heavy check mark should mark slice as done");
|
|
146
|
+
assert.equal(slices[1]?.done, false);
|
|
147
|
+
});
|
|
148
|
+
|
|
136
149
|
test("parseRoadmapSlices: table with dependencies column (#1736)", () => {
|
|
137
150
|
const tableContent = [
|
|
138
151
|
"# M004: Deps", "", "## Slices", "",
|
|
@@ -393,3 +406,59 @@ test("parseRoadmapSlices: indented H3 headers under ## Slices (#2567)", () => {
|
|
|
393
406
|
assert.equal(slices[1]?.id, "S02");
|
|
394
407
|
assert.equal(slices[1]?.title, "Build");
|
|
395
408
|
});
|
|
409
|
+
|
|
410
|
+
// ── Regression tests for #1884: ✅ (U+2705) completion marker ──────────────
|
|
411
|
+
|
|
412
|
+
test("parseRoadmapSlices: prose headers with ✅ suffix detected as done (#1884)", () => {
|
|
413
|
+
const proseContent = `# M013: Prose Roadmap
|
|
414
|
+
|
|
415
|
+
### S01: Plan Limits & Billing Foundation ✅
|
|
416
|
+
All tasks done.
|
|
417
|
+
|
|
418
|
+
### S02: Usage Tracking
|
|
419
|
+
Not done yet.
|
|
420
|
+
|
|
421
|
+
### S03: Notification System ✅
|
|
422
|
+
Also done.
|
|
423
|
+
`;
|
|
424
|
+
const slices = parseRoadmapSlices(proseContent);
|
|
425
|
+
assert.equal(slices.length, 3);
|
|
426
|
+
assert.equal(slices[0]?.id, "S01");
|
|
427
|
+
assert.equal(slices[0]?.done, true, "S01 with trailing ✅ should be done");
|
|
428
|
+
assert.equal(slices[0]?.title, "Plan Limits & Billing Foundation");
|
|
429
|
+
assert.equal(slices[1]?.done, false);
|
|
430
|
+
assert.equal(slices[2]?.done, true, "S03 with trailing ✅ should be done");
|
|
431
|
+
assert.equal(slices[2]?.title, "Notification System");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("parseRoadmapSlices: prose headers with ✅ prefix before title detected as done (#1884)", () => {
|
|
435
|
+
const proseContent = `# M014: Prose
|
|
436
|
+
|
|
437
|
+
## ✅ S01: Done Slice
|
|
438
|
+
Complete.
|
|
439
|
+
|
|
440
|
+
## S02: Pending Slice
|
|
441
|
+
Not done.
|
|
442
|
+
`;
|
|
443
|
+
const slices = parseRoadmapSlices(proseContent);
|
|
444
|
+
assert.equal(slices.length, 2);
|
|
445
|
+
assert.equal(slices[0]?.done, true, "prefix ✅ should mark as done");
|
|
446
|
+
assert.equal(slices[0]?.title, "Done Slice");
|
|
447
|
+
assert.equal(slices[1]?.done, false);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("parseRoadmapSlices: prose headers with ✅ after separator detected as done (#1884)", () => {
|
|
451
|
+
const proseContent = `# M015: Prose
|
|
452
|
+
|
|
453
|
+
## S01: ✅ First Feature
|
|
454
|
+
Done.
|
|
455
|
+
|
|
456
|
+
## S02: Second Feature
|
|
457
|
+
Not done.
|
|
458
|
+
`;
|
|
459
|
+
const slices = parseRoadmapSlices(proseContent);
|
|
460
|
+
assert.equal(slices.length, 2);
|
|
461
|
+
assert.equal(slices[0]?.done, true, "✅ after colon should mark as done");
|
|
462
|
+
assert.equal(slices[0]?.title, "First Feature");
|
|
463
|
+
assert.equal(slices[1]?.done, false);
|
|
464
|
+
});
|
|
@@ -68,6 +68,36 @@ describe('shared-wal', async () => {
|
|
|
68
68
|
'forward-slash worktree path resolves correctly');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// ─── Test (e1): external-state worktree resolves to project state DB (#2952) ───
|
|
72
|
+
console.log('\n=== shared-wal: resolve external-state worktree path (#2952) ===');
|
|
73
|
+
{
|
|
74
|
+
// External-state layout: ~/.gsd/projects/<hash>/worktrees/<MID>
|
|
75
|
+
// Should resolve to: ~/.gsd/projects/<hash>/gsd.db
|
|
76
|
+
const stateRoot = '/home/user/.gsd/projects/a1b2c3d4';
|
|
77
|
+
const worktreePath = join(stateRoot, 'worktrees', 'M002');
|
|
78
|
+
const result = resolveProjectRootDbPath(worktreePath);
|
|
79
|
+
assert.deepStrictEqual(result, join(stateRoot, 'gsd.db'),
|
|
80
|
+
'external-state worktree path resolves to project state DB (#2952)');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Test (e2): external-state worktree nested subdir (#2952) ─────────
|
|
84
|
+
console.log('\n=== shared-wal: resolve external-state worktree nested subdir (#2952) ===');
|
|
85
|
+
{
|
|
86
|
+
const stateRoot = '/home/user/.gsd/projects/deadbeef42';
|
|
87
|
+
const nestedPath = join(stateRoot, 'worktrees', 'M003', 'src', 'lib');
|
|
88
|
+
const result = resolveProjectRootDbPath(nestedPath);
|
|
89
|
+
assert.deepStrictEqual(result, join(stateRoot, 'gsd.db'),
|
|
90
|
+
'external-state nested worktree subdir resolves to project state DB (#2952)');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Test (e3): external-state worktree with forward slashes (#2952) ──
|
|
94
|
+
console.log('\n=== shared-wal: resolve external-state worktree forward-slash (#2952) ===');
|
|
95
|
+
{
|
|
96
|
+
const result = resolveProjectRootDbPath('/Users/dev/.gsd/projects/cafe0123/worktrees/M001');
|
|
97
|
+
assert.deepStrictEqual(result, join('/Users/dev/.gsd/projects/cafe0123', 'gsd.db'),
|
|
98
|
+
'external-state forward-slash worktree path resolves correctly (#2952)');
|
|
99
|
+
}
|
|
100
|
+
|
|
71
101
|
// ─── Test (e): Concurrent writes — 3 connections to same WAL DB ───────
|
|
72
102
|
console.log('\n=== shared-wal: concurrent writes via WAL ===');
|
|
73
103
|
{
|