gsd-pi 2.63.0 → 2.64.0-dev.9c14bd0
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 +14 -8
- package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +222 -11
- 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-verification.js +138 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
- package/dist/resources/extensions/gsd/auto.js +24 -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/post-execution-checks.js +407 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
- package/dist/resources/extensions/gsd/preferences-types.js +6 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
- package/dist/resources/extensions/gsd/preferences.js +11 -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/verification-evidence.js +18 -0
- 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 +15 -15
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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/boot/route_client-reference-manifest.js +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/input/route_client-reference-manifest.js +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/resize/route_client-reference-manifest.js +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/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +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/captures/route_client-reference-manifest.js +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/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +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/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +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/export-data/route_client-reference-manifest.js +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/files/route_client-reference-manifest.js +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/forensics/route_client-reference-manifest.js +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/git/route_client-reference-manifest.js +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/history/route_client-reference-manifest.js +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/hooks/route_client-reference-manifest.js +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/inspect/route_client-reference-manifest.js +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/knowledge/route_client-reference-manifest.js +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/live-state/route_client-reference-manifest.js +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/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +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/projects/route_client-reference-manifest.js +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/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +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/browser/route_client-reference-manifest.js +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/command/route_client-reference-manifest.js +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/events/route_client-reference-manifest.js +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/session/manage/route_client-reference-manifest.js +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/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +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/skill-health/route_client-reference-manifest.js +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/steer/route_client-reference-manifest.js +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/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +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/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +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/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +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/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- 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/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +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 +16 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +263 -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-verification.ts +190 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
- package/src/resources/extensions/gsd/auto.ts +26 -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/post-execution-checks.ts +539 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
- package/src/resources/extensions/gsd/preferences-types.ts +44 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
- package/src/resources/extensions/gsd/preferences.ts +13 -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/auto-start-time-persistence.test.ts +50 -0
- 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/enhanced-verification-integration.test.ts +526 -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/post-exec-retry-bypass.test.ts +312 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
- package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
- 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/verification-evidence.ts +68 -0
- 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/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → SoxM61WC_ia7R2gk4VMpJ}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → SoxM61WC_ia7R2gk4VMpJ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-param-optionality — Verifies that enrichment/metadata parameters on
|
|
3
|
+
* planning and completion tools are optional, not required.
|
|
4
|
+
*
|
|
5
|
+
* Models with limited tool-calling capability (e.g. kimi-k2.5, glm-5-turbo)
|
|
6
|
+
* cannot reliably populate 20+ top-level parameters in a single tool call.
|
|
7
|
+
* This test ensures that only the core identification and content parameters
|
|
8
|
+
* are required, while enrichment arrays (patterns, requirements, files, etc.)
|
|
9
|
+
* are optional — so any model can call the tool successfully.
|
|
10
|
+
*
|
|
11
|
+
* See: https://github.com/gsd-build/gsd-2/issues/2771
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { test } from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { registerDbTools } from "../bootstrap/db-tools.ts";
|
|
17
|
+
import { Value } from "@sinclair/typebox/value";
|
|
18
|
+
|
|
19
|
+
// ─── Mock PI ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function makeMockPi() {
|
|
22
|
+
const tools: any[] = [];
|
|
23
|
+
return {
|
|
24
|
+
registerTool: (tool: any) => tools.push(tool),
|
|
25
|
+
tools,
|
|
26
|
+
} as any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pi = makeMockPi();
|
|
30
|
+
registerDbTools(pi);
|
|
31
|
+
|
|
32
|
+
function getTool(name: string) {
|
|
33
|
+
return pi.tools.find((t: any) => t.name === name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Helper: count required top-level properties ─────────────────────────────
|
|
37
|
+
|
|
38
|
+
function getRequiredProps(tool: any): string[] {
|
|
39
|
+
const schema = tool.parameters;
|
|
40
|
+
return schema.required ?? [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getOptionalProps(tool: any): string[] {
|
|
44
|
+
const schema = tool.parameters;
|
|
45
|
+
const allProps = Object.keys(schema.properties ?? {});
|
|
46
|
+
const required = new Set(schema.required ?? []);
|
|
47
|
+
return allProps.filter((p: string) => !required.has(p));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── gsd_slice_complete: enrichment arrays must be optional ──────────────────
|
|
51
|
+
|
|
52
|
+
test("gsd_slice_complete — enrichment arrays are optional", () => {
|
|
53
|
+
const tool = getTool("gsd_slice_complete");
|
|
54
|
+
assert.ok(tool, "gsd_slice_complete must be registered");
|
|
55
|
+
|
|
56
|
+
const required = new Set(getRequiredProps(tool));
|
|
57
|
+
|
|
58
|
+
// Core identification and content fields MUST be required
|
|
59
|
+
const coreRequired = [
|
|
60
|
+
"sliceId",
|
|
61
|
+
"milestoneId",
|
|
62
|
+
"sliceTitle",
|
|
63
|
+
"oneLiner",
|
|
64
|
+
"narrative",
|
|
65
|
+
"verification",
|
|
66
|
+
"uatContent",
|
|
67
|
+
];
|
|
68
|
+
for (const field of coreRequired) {
|
|
69
|
+
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Enrichment/metadata arrays MUST be optional
|
|
73
|
+
const enrichmentFields = [
|
|
74
|
+
"keyFiles",
|
|
75
|
+
"keyDecisions",
|
|
76
|
+
"patternsEstablished",
|
|
77
|
+
"observabilitySurfaces",
|
|
78
|
+
"provides",
|
|
79
|
+
"requirementsSurfaced",
|
|
80
|
+
"drillDownPaths",
|
|
81
|
+
"affects",
|
|
82
|
+
"requirementsAdvanced",
|
|
83
|
+
"requirementsValidated",
|
|
84
|
+
"requirementsInvalidated",
|
|
85
|
+
"filesModified",
|
|
86
|
+
"requires",
|
|
87
|
+
"deviations",
|
|
88
|
+
"knownLimitations",
|
|
89
|
+
"followUps",
|
|
90
|
+
];
|
|
91
|
+
for (const field of enrichmentFields) {
|
|
92
|
+
assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("gsd_slice_complete — validates with only core params", () => {
|
|
97
|
+
const tool = getTool("gsd_slice_complete");
|
|
98
|
+
assert.ok(tool, "gsd_slice_complete must be registered");
|
|
99
|
+
|
|
100
|
+
const minimalParams = {
|
|
101
|
+
sliceId: "S01",
|
|
102
|
+
milestoneId: "M001",
|
|
103
|
+
sliceTitle: "Test slice",
|
|
104
|
+
oneLiner: "Did the thing",
|
|
105
|
+
narrative: "We did it step by step.",
|
|
106
|
+
verification: "Tests pass.",
|
|
107
|
+
uatContent: "## UAT\n- [x] Works",
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Should pass schema validation with only core params
|
|
111
|
+
const errors = [...Value.Errors(tool.parameters, minimalParams)];
|
|
112
|
+
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ─── gsd_plan_milestone: enrichment arrays must be optional ──────────────────
|
|
116
|
+
|
|
117
|
+
test("gsd_plan_milestone — enrichment arrays are optional", () => {
|
|
118
|
+
const tool = getTool("gsd_plan_milestone");
|
|
119
|
+
assert.ok(tool, "gsd_plan_milestone must be registered");
|
|
120
|
+
|
|
121
|
+
const required = new Set(getRequiredProps(tool));
|
|
122
|
+
|
|
123
|
+
// Core fields
|
|
124
|
+
const coreRequired = ["milestoneId", "title", "vision", "slices"];
|
|
125
|
+
for (const field of coreRequired) {
|
|
126
|
+
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Enrichment fields must be optional
|
|
130
|
+
const enrichmentFields = [
|
|
131
|
+
"successCriteria",
|
|
132
|
+
"keyRisks",
|
|
133
|
+
"proofStrategy",
|
|
134
|
+
"verificationContract",
|
|
135
|
+
"verificationIntegration",
|
|
136
|
+
"verificationOperational",
|
|
137
|
+
"verificationUat",
|
|
138
|
+
"definitionOfDone",
|
|
139
|
+
"requirementCoverage",
|
|
140
|
+
"boundaryMapMarkdown",
|
|
141
|
+
];
|
|
142
|
+
for (const field of enrichmentFields) {
|
|
143
|
+
assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("gsd_plan_milestone — validates with only core params", () => {
|
|
148
|
+
const tool = getTool("gsd_plan_milestone");
|
|
149
|
+
assert.ok(tool, "gsd_plan_milestone must be registered");
|
|
150
|
+
|
|
151
|
+
const minimalParams = {
|
|
152
|
+
milestoneId: "M001",
|
|
153
|
+
title: "Test milestone",
|
|
154
|
+
vision: "Build the thing.",
|
|
155
|
+
slices: [
|
|
156
|
+
{
|
|
157
|
+
sliceId: "S01",
|
|
158
|
+
title: "First slice",
|
|
159
|
+
risk: "Low",
|
|
160
|
+
depends: [],
|
|
161
|
+
demo: "After this, X works",
|
|
162
|
+
goal: "Set up X",
|
|
163
|
+
successCriteria: "X is set up",
|
|
164
|
+
proofLevel: "unit-tests",
|
|
165
|
+
integrationClosure: "N/A",
|
|
166
|
+
observabilityImpact: "None",
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const errors = [...Value.Errors(tool.parameters, minimalParams)];
|
|
172
|
+
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ─── gsd_task_complete: enrichment arrays must be optional ───────────────────
|
|
176
|
+
|
|
177
|
+
test("gsd_task_complete — enrichment arrays are optional", () => {
|
|
178
|
+
const tool = getTool("gsd_task_complete");
|
|
179
|
+
assert.ok(tool, "gsd_task_complete must be registered");
|
|
180
|
+
|
|
181
|
+
const required = new Set(getRequiredProps(tool));
|
|
182
|
+
|
|
183
|
+
// Core fields
|
|
184
|
+
const coreRequired = [
|
|
185
|
+
"taskId",
|
|
186
|
+
"sliceId",
|
|
187
|
+
"milestoneId",
|
|
188
|
+
"oneLiner",
|
|
189
|
+
"narrative",
|
|
190
|
+
"verification",
|
|
191
|
+
];
|
|
192
|
+
for (const field of coreRequired) {
|
|
193
|
+
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Enrichment fields must be optional
|
|
197
|
+
const enrichmentFields = [
|
|
198
|
+
"keyFiles",
|
|
199
|
+
"keyDecisions",
|
|
200
|
+
"deviations",
|
|
201
|
+
"knownIssues",
|
|
202
|
+
"blockerDiscovered",
|
|
203
|
+
"verificationEvidence",
|
|
204
|
+
];
|
|
205
|
+
for (const field of enrichmentFields) {
|
|
206
|
+
assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("gsd_task_complete — validates with only core params", () => {
|
|
211
|
+
const tool = getTool("gsd_task_complete");
|
|
212
|
+
assert.ok(tool, "gsd_task_complete must be registered");
|
|
213
|
+
|
|
214
|
+
const minimalParams = {
|
|
215
|
+
taskId: "T01",
|
|
216
|
+
sliceId: "S01",
|
|
217
|
+
milestoneId: "M001",
|
|
218
|
+
oneLiner: "Implemented the feature",
|
|
219
|
+
narrative: "Created the module and wired it up.",
|
|
220
|
+
verification: "npm test passes.",
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const errors = [...Value.Errors(tool.parameters, minimalParams)];
|
|
224
|
+
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ─── gsd_complete_milestone: enrichment arrays must be optional ──────────────
|
|
228
|
+
|
|
229
|
+
test("gsd_complete_milestone — enrichment arrays are optional", () => {
|
|
230
|
+
const tool = getTool("gsd_complete_milestone");
|
|
231
|
+
assert.ok(tool, "gsd_complete_milestone must be registered");
|
|
232
|
+
|
|
233
|
+
const required = new Set(getRequiredProps(tool));
|
|
234
|
+
|
|
235
|
+
// Core fields
|
|
236
|
+
const coreRequired = [
|
|
237
|
+
"milestoneId",
|
|
238
|
+
"title",
|
|
239
|
+
"oneLiner",
|
|
240
|
+
"narrative",
|
|
241
|
+
"verificationPassed",
|
|
242
|
+
];
|
|
243
|
+
for (const field of coreRequired) {
|
|
244
|
+
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Enrichment fields must be optional
|
|
248
|
+
const enrichmentFields = [
|
|
249
|
+
"successCriteriaResults",
|
|
250
|
+
"definitionOfDoneResults",
|
|
251
|
+
"requirementOutcomes",
|
|
252
|
+
"keyDecisions",
|
|
253
|
+
"keyFiles",
|
|
254
|
+
"lessonsLearned",
|
|
255
|
+
];
|
|
256
|
+
for (const field of enrichmentFields) {
|
|
257
|
+
assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("gsd_complete_milestone — validates with only core params", () => {
|
|
262
|
+
const tool = getTool("gsd_complete_milestone");
|
|
263
|
+
assert.ok(tool, "gsd_complete_milestone must be registered");
|
|
264
|
+
|
|
265
|
+
const minimalParams = {
|
|
266
|
+
milestoneId: "M001",
|
|
267
|
+
title: "Test milestone",
|
|
268
|
+
oneLiner: "Finished it.",
|
|
269
|
+
narrative: "All work completed.",
|
|
270
|
+
verificationPassed: true,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const errors = [...Value.Errors(tool.parameters, minimalParams)];
|
|
274
|
+
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ─── gsd_plan_slice: enrichment fields must be optional ──────────────────────
|
|
278
|
+
|
|
279
|
+
test("gsd_plan_slice — enrichment fields are optional", () => {
|
|
280
|
+
const tool = getTool("gsd_plan_slice");
|
|
281
|
+
assert.ok(tool, "gsd_plan_slice must be registered");
|
|
282
|
+
|
|
283
|
+
const required = new Set(getRequiredProps(tool));
|
|
284
|
+
|
|
285
|
+
// Core fields
|
|
286
|
+
const coreRequired = ["milestoneId", "sliceId", "goal", "tasks"];
|
|
287
|
+
for (const field of coreRequired) {
|
|
288
|
+
assert.ok(required.has(field), `core field "${field}" must be required`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Enrichment fields
|
|
292
|
+
const enrichmentFields = [
|
|
293
|
+
"successCriteria",
|
|
294
|
+
"proofLevel",
|
|
295
|
+
"integrationClosure",
|
|
296
|
+
"observabilityImpact",
|
|
297
|
+
];
|
|
298
|
+
for (const field of enrichmentFields) {
|
|
299
|
+
assert.ok(!required.has(field), `enrichment field "${field}" must be optional, not required`);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("gsd_plan_slice — validates with only core params", () => {
|
|
304
|
+
const tool = getTool("gsd_plan_slice");
|
|
305
|
+
assert.ok(tool, "gsd_plan_slice must be registered");
|
|
306
|
+
|
|
307
|
+
const minimalParams = {
|
|
308
|
+
milestoneId: "M001",
|
|
309
|
+
sliceId: "S01",
|
|
310
|
+
goal: "Implement feature X",
|
|
311
|
+
tasks: [
|
|
312
|
+
{
|
|
313
|
+
taskId: "T01",
|
|
314
|
+
title: "Build X",
|
|
315
|
+
description: "Build the thing",
|
|
316
|
+
estimate: "2h",
|
|
317
|
+
files: ["src/x.ts"],
|
|
318
|
+
verify: "npm test",
|
|
319
|
+
inputs: [],
|
|
320
|
+
expectedOutput: ["src/x.ts"],
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const errors = [...Value.Errors(tool.parameters, minimalParams)];
|
|
326
|
+
assert.strictEqual(errors.length, 0, `Minimal params should validate but got errors: ${errors.map(e => `${e.path}: ${e.message}`).join(", ")}`);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ─── Required param count ceiling ────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
test("no planning/completion tool requires more than 10 top-level params", () => {
|
|
332
|
+
const heavyTools = [
|
|
333
|
+
"gsd_slice_complete",
|
|
334
|
+
"gsd_plan_milestone",
|
|
335
|
+
"gsd_task_complete",
|
|
336
|
+
"gsd_complete_milestone",
|
|
337
|
+
"gsd_plan_slice",
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
for (const name of heavyTools) {
|
|
341
|
+
const tool = getTool(name);
|
|
342
|
+
assert.ok(tool, `${name} must be registered`);
|
|
343
|
+
const required = getRequiredProps(tool);
|
|
344
|
+
assert.ok(
|
|
345
|
+
required.length <= 10,
|
|
346
|
+
`${name} has ${required.length} required params (max 10) — required: ${required.join(", ")}`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { execSync } from "node:child_process";
|
|
@@ -57,13 +57,20 @@ function hasRecognizedProjectFiles(basePath: string, existsSyncFn: (p: string) =
|
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/** Simulate the phases.ts Xcode-bundle detection (readdirSync suffix scan). */
|
|
61
|
+
function hasXcodeBundle(basePath: string): boolean {
|
|
62
|
+
try {
|
|
63
|
+
return readdirSync(basePath).some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
|
|
64
|
+
} catch { return false; }
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
import { existsSync } from "node:fs";
|
|
61
68
|
|
|
62
69
|
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
63
70
|
|
|
64
71
|
test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
|
|
65
72
|
assert.ok(Array.isArray(PROJECT_FILES), "PROJECT_FILES is an array");
|
|
66
|
-
assert.ok(PROJECT_FILES.length >=
|
|
73
|
+
assert.ok(PROJECT_FILES.length >= 18, `expected >= 18 entries, got ${PROJECT_FILES.length}`);
|
|
67
74
|
// Spot-check key ecosystems
|
|
68
75
|
assert.ok(PROJECT_FILES.includes("Cargo.toml"), "includes Rust marker");
|
|
69
76
|
assert.ok(PROJECT_FILES.includes("go.mod"), "includes Go marker");
|
|
@@ -140,3 +147,29 @@ describe("health check without git repo", () => {
|
|
|
140
147
|
assert.ok(!wouldPassHealthCheck(dir, existsSync), "no-git directory should fail health check");
|
|
141
148
|
});
|
|
142
149
|
});
|
|
150
|
+
|
|
151
|
+
describe("health check with xcodegen and Xcode bundles", () => {
|
|
152
|
+
let dir: string;
|
|
153
|
+
beforeEach(() => { dir = createGitRepo(); });
|
|
154
|
+
afterEach(() => { rmSync(dir, { recursive: true, force: true }); });
|
|
155
|
+
|
|
156
|
+
test("health check passes for xcodegen project (project.yml, no Package.swift)", () => {
|
|
157
|
+
writeFileSync(join(dir, "project.yml"), "name: MyApp\ntargets:\n MyApp:\n type: application\n");
|
|
158
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "xcodegen project should pass health check");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Regression for the real-world failure in #1882: an iOS project with a
|
|
162
|
+
// project-specific Xcode bundle (Sudokuxyz.xcodeproj/) was blocked because
|
|
163
|
+
// PROJECT_FILES only probes exact filenames, not suffix-based directory names.
|
|
164
|
+
test("Xcode bundle (*.xcodeproj) is not in PROJECT_FILES but detected by suffix scan", () => {
|
|
165
|
+
mkdirSync(join(dir, "Sudokuxyz.xcodeproj"), { recursive: true });
|
|
166
|
+
mkdirSync(join(dir, "Sources", "Sudokuxyz"), { recursive: true });
|
|
167
|
+
writeFileSync(join(dir, "Sources", "Sudokuxyz", "ContentView.swift"), "import SwiftUI\n");
|
|
168
|
+
// PROJECT_FILES uses exact names — cannot match project-specific bundle names
|
|
169
|
+
assert.ok(!hasRecognizedProjectFiles(dir, existsSync), "xcodeproj bundle must NOT be in PROJECT_FILES");
|
|
170
|
+
// The readdirSync suffix scan used in phases.ts detects it
|
|
171
|
+
assert.ok(hasXcodeBundle(dir), "readdirSync suffix scan detects .xcodeproj bundle");
|
|
172
|
+
// Health check passes regardless (only requires .git)
|
|
173
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Xcode bundle project should pass health check");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-health-monorepo.test.ts — #2347
|
|
3
|
+
*
|
|
4
|
+
* The worktree health check in auto/phases.ts falsely rejects monorepos
|
|
5
|
+
* where package.json (or other project markers) is in a parent directory.
|
|
6
|
+
* This test verifies that the health check walks parent directories.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
12
|
+
|
|
13
|
+
const { assertTrue, report } = createTestContext();
|
|
14
|
+
|
|
15
|
+
const srcPath = join(import.meta.dirname, "..", "auto", "phases.ts");
|
|
16
|
+
const src = readFileSync(srcPath, "utf-8");
|
|
17
|
+
|
|
18
|
+
console.log("\n=== #2347: Worktree health check supports monorepos ===");
|
|
19
|
+
|
|
20
|
+
// ── Test 1: The health check region exists ──────────────────────────────
|
|
21
|
+
|
|
22
|
+
const healthCheckIdx = src.indexOf("Worktree health check");
|
|
23
|
+
assertTrue(healthCheckIdx > 0, "auto/phases.ts has worktree health check section");
|
|
24
|
+
|
|
25
|
+
const healthCheckRegion = src.slice(healthCheckIdx, healthCheckIdx + 2000);
|
|
26
|
+
|
|
27
|
+
// ── Test 2: The check walks parent directories for project markers ──────
|
|
28
|
+
|
|
29
|
+
// The fix should check parent directories for project files, not just s.basePath.
|
|
30
|
+
// Look for patterns like: walking up directories, dirname, parent, or a helper
|
|
31
|
+
// function that checks ancestors.
|
|
32
|
+
const checksParentDirs =
|
|
33
|
+
healthCheckRegion.includes("dirname") ||
|
|
34
|
+
healthCheckRegion.includes("parent") ||
|
|
35
|
+
healthCheckRegion.includes("ancestor") ||
|
|
36
|
+
healthCheckRegion.includes("walk") ||
|
|
37
|
+
// Or a helper function that's called with the base path
|
|
38
|
+
/hasProjectFileInAncestor|findProjectRoot|checkParent/i.test(healthCheckRegion);
|
|
39
|
+
|
|
40
|
+
assertTrue(
|
|
41
|
+
checksParentDirs,
|
|
42
|
+
"Health check should walk parent directories for project markers (monorepo support) (#2347)",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// ── Test 3: The parent walk stops at a .git boundary ──────────────────
|
|
46
|
+
|
|
47
|
+
// The parent directory walk must not escape the git repository root.
|
|
48
|
+
// Without this guard, ancestor directories like ~ or /usr/local that
|
|
49
|
+
// happen to contain package.json would cause false positive health checks.
|
|
50
|
+
const hasGitBoundary = healthCheckRegion.includes('.git') &&
|
|
51
|
+
(healthCheckRegion.includes('break') || healthCheckRegion.includes('stop'));
|
|
52
|
+
|
|
53
|
+
assertTrue(
|
|
54
|
+
hasGitBoundary,
|
|
55
|
+
"Parent directory walk must stop at .git repository boundary to prevent false positives",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// ── Test 4: The greenfield warning should only trigger when no parent has markers ─
|
|
59
|
+
|
|
60
|
+
// The original code was:
|
|
61
|
+
// const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
|
|
62
|
+
// The fix should check parents too, so the greenfield warning only fires
|
|
63
|
+
// when NO ancestor directory has project markers either.
|
|
64
|
+
const hasParentCheck = healthCheckRegion.includes("parent") ||
|
|
65
|
+
healthCheckRegion.includes("dirname") ||
|
|
66
|
+
/ancestor|walk.*up/i.test(healthCheckRegion);
|
|
67
|
+
|
|
68
|
+
assertTrue(
|
|
69
|
+
hasParentCheck,
|
|
70
|
+
"Greenfield check should consider parent directories before warning (#2347)",
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
report();
|
|
@@ -550,6 +550,40 @@ test("mergeAndExit failure message tells user worktree and branch are preserved
|
|
|
550
550
|
);
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
+
test("mergeAndExit failure message references /gsd dispatch complete-milestone, not /complete-milestone (#1891)", () => {
|
|
554
|
+
// Regression test: the failure notification previously told users to
|
|
555
|
+
// "retry /complete-milestone" — a command that does not exist. The correct
|
|
556
|
+
// recovery command is "/gsd dispatch complete-milestone".
|
|
557
|
+
const s = makeSession({
|
|
558
|
+
basePath: "/project/.gsd/worktrees/M001",
|
|
559
|
+
originalBasePath: "/project",
|
|
560
|
+
});
|
|
561
|
+
const deps = makeDeps({
|
|
562
|
+
isInAutoWorktree: () => true,
|
|
563
|
+
getIsolationMode: () => "worktree",
|
|
564
|
+
mergeMilestoneToMain: () => {
|
|
565
|
+
throw new Error("dirty working tree");
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
const ctx = makeNotifyCtx();
|
|
569
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
570
|
+
|
|
571
|
+
resolver.mergeAndExit("M001", ctx);
|
|
572
|
+
|
|
573
|
+
const warning = ctx.messages.find((m) => m.level === "warning");
|
|
574
|
+
assert.ok(warning, "a warning message is emitted");
|
|
575
|
+
// Must reference the correct dispatch command
|
|
576
|
+
assert.ok(
|
|
577
|
+
warning!.msg.includes("/gsd dispatch complete-milestone"),
|
|
578
|
+
"warning references /gsd dispatch complete-milestone, not bare /complete-milestone",
|
|
579
|
+
);
|
|
580
|
+
// Must NOT contain the bare (incorrect) command without the dispatch prefix
|
|
581
|
+
assert.ok(
|
|
582
|
+
!warning!.msg.match(/retry\s+\/complete-milestone(?!\S)/),
|
|
583
|
+
"warning must not reference the non-existent /complete-milestone command",
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
|
|
553
587
|
// ─── mergeAndExit Tests (branch mode) ────────────────────────────────────────
|
|
554
588
|
|
|
555
589
|
test("mergeAndExit in branch mode merges when on milestone branch", () => {
|
|
@@ -22,7 +22,7 @@ console.log("\n=== #2337: Worktree teardown preserves submodule state ===");
|
|
|
22
22
|
const removeWorktreeIdx = src.indexOf("export function removeWorktree");
|
|
23
23
|
assertTrue(removeWorktreeIdx > 0, "worktree-manager.ts exports removeWorktree");
|
|
24
24
|
|
|
25
|
-
const fnBody = src.slice(removeWorktreeIdx, removeWorktreeIdx +
|
|
25
|
+
const fnBody = src.slice(removeWorktreeIdx, removeWorktreeIdx + 6000);
|
|
26
26
|
|
|
27
27
|
// ── Test 2: The function checks for submodules before force removal ─────
|
|
28
28
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-teardown-safety.test.ts — Regression test for #2365.
|
|
3
|
+
*
|
|
4
|
+
* Ensures that removeWorktree() and teardownAutoWorktree() never delete
|
|
5
|
+
* directories outside .gsd/worktrees/. The bug: removeWorktree overrides
|
|
6
|
+
* the computed worktree path with whatever `git worktree list` reports.
|
|
7
|
+
* When .gsd/ was (or is) a symlink, git resolves the symlink at creation
|
|
8
|
+
* time, so its registered path can point to an external directory. If that
|
|
9
|
+
* external path happens to be a project data directory, teardown destroys it.
|
|
10
|
+
*
|
|
11
|
+
* The fix adds path validation so rmSync / nativeWorktreeRemove only operate
|
|
12
|
+
* on paths that are actually under .gsd/worktrees/.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
rmSync,
|
|
20
|
+
existsSync,
|
|
21
|
+
realpathSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { tmpdir } from "node:os";
|
|
26
|
+
import { execSync } from "node:child_process";
|
|
27
|
+
import { describe, it, after } from "node:test";
|
|
28
|
+
|
|
29
|
+
import { createWorktree, removeWorktree, worktreePath, isInsideWorktreesDir } from "../worktree-manager.ts";
|
|
30
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
31
|
+
|
|
32
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
33
|
+
|
|
34
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function run(command: string, cwd: string): string {
|
|
37
|
+
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createTempRepo(): string {
|
|
41
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "wt-safety-test-")));
|
|
42
|
+
run("git init", dir);
|
|
43
|
+
run("git config user.email test@test.com", dir);
|
|
44
|
+
run("git config user.name Test", dir);
|
|
45
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
46
|
+
run("git add .", dir);
|
|
47
|
+
run("git commit -m init", dir);
|
|
48
|
+
run("git branch -M main", dir);
|
|
49
|
+
return dir;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Tests ────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
describe("worktree-teardown-safety", () => {
|
|
55
|
+
const dirs: string[] = [];
|
|
56
|
+
|
|
57
|
+
after(() => {
|
|
58
|
+
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
59
|
+
report();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("removeWorktree does not delete sibling data directories", () => {
|
|
63
|
+
const tempDir = createTempRepo();
|
|
64
|
+
dirs.push(tempDir);
|
|
65
|
+
|
|
66
|
+
// Create a project data directory that lives alongside .gsd/
|
|
67
|
+
const dataDir = join(tempDir, "project-data");
|
|
68
|
+
mkdirSync(dataDir, { recursive: true });
|
|
69
|
+
writeFileSync(join(dataDir, "important.db"), "precious data");
|
|
70
|
+
|
|
71
|
+
// Create a worktree normally
|
|
72
|
+
const wt = createWorktree(tempDir, "test-wt");
|
|
73
|
+
assertTrue(existsSync(wt.path), "worktree created successfully");
|
|
74
|
+
|
|
75
|
+
// Remove the worktree
|
|
76
|
+
removeWorktree(tempDir, "test-wt");
|
|
77
|
+
|
|
78
|
+
// The worktree directory should be gone
|
|
79
|
+
assertTrue(!existsSync(wt.path), "worktree directory removed");
|
|
80
|
+
|
|
81
|
+
// The project data directory MUST still exist
|
|
82
|
+
assertTrue(existsSync(dataDir), "project data directory survives teardown");
|
|
83
|
+
assertTrue(
|
|
84
|
+
existsSync(join(dataDir, "important.db")),
|
|
85
|
+
"project data files survive teardown",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("path validation rejects paths outside .gsd/worktrees/", () => {
|
|
90
|
+
const tempDir = createTempRepo();
|
|
91
|
+
dirs.push(tempDir);
|
|
92
|
+
|
|
93
|
+
const externalDir = join(tempDir, "external-state");
|
|
94
|
+
mkdirSync(externalDir, { recursive: true });
|
|
95
|
+
writeFileSync(join(externalDir, "state.json"), '{"critical": true}');
|
|
96
|
+
|
|
97
|
+
// Create and then remove a worktree that has a legitimate path
|
|
98
|
+
const wt2 = createWorktree(tempDir, "safe-wt");
|
|
99
|
+
assertTrue(existsSync(wt2.path), "second worktree created");
|
|
100
|
+
|
|
101
|
+
removeWorktree(tempDir, "safe-wt");
|
|
102
|
+
assertTrue(!existsSync(wt2.path), "second worktree removed cleanly");
|
|
103
|
+
|
|
104
|
+
// External directory must be untouched
|
|
105
|
+
assertTrue(existsSync(externalDir), "external directory survives second teardown");
|
|
106
|
+
assertEq(
|
|
107
|
+
readFileSync(join(externalDir, "state.json"), "utf-8"),
|
|
108
|
+
'{"critical": true}',
|
|
109
|
+
"external directory contents intact after teardown",
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("worktreePath always returns paths under .gsd/worktrees/", () => {
|
|
114
|
+
const tempDir = createTempRepo();
|
|
115
|
+
dirs.push(tempDir);
|
|
116
|
+
|
|
117
|
+
const wtPathResult = worktreePath(tempDir, "anything");
|
|
118
|
+
assertTrue(
|
|
119
|
+
wtPathResult.startsWith(join(tempDir, ".gsd", "worktrees")),
|
|
120
|
+
"worktreePath returns path under .gsd/worktrees/",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("isInsideWorktreesDir rejects path traversal attempts", () => {
|
|
125
|
+
const tempDir = createTempRepo();
|
|
126
|
+
dirs.push(tempDir);
|
|
127
|
+
|
|
128
|
+
assertTrue(
|
|
129
|
+
isInsideWorktreesDir(tempDir, join(tempDir, ".gsd", "worktrees", "my-wt")),
|
|
130
|
+
"path inside .gsd/worktrees/ is accepted",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
assertTrue(
|
|
134
|
+
!isInsideWorktreesDir(tempDir, join(tempDir, "project-data")),
|
|
135
|
+
"path outside .gsd/worktrees/ is rejected",
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
assertTrue(
|
|
139
|
+
!isInsideWorktreesDir(tempDir, join(tempDir, ".gsd", "worktrees", "..", "..", "project-data")),
|
|
140
|
+
"path traversal via .. is rejected",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
assertTrue(
|
|
144
|
+
!isInsideWorktreesDir(tempDir, "/tmp/some-other-dir"),
|
|
145
|
+
"completely external path is rejected",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
});
|