gsd-pi 2.51.0 → 2.52.0-dev.655ad8a
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 +59 -36
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- package/dist/headless-query.js +1 -1
- package/dist/headless-types.d.ts +28 -0
- package/dist/headless-types.js +7 -0
- package/dist/headless.d.ts +8 -3
- package/dist/headless.js +47 -16
- package/dist/help-text.js +16 -5
- package/dist/onboarding.js +5 -4
- package/dist/remote-questions-config.js +1 -1
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +29 -17
- package/dist/resources/extensions/async-jobs/job-manager.js +4 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +18 -19
- package/dist/resources/extensions/get-secrets-from-user.js +7 -0
- package/dist/resources/extensions/gsd/auto/phases.js +34 -8
- package/dist/resources/extensions/gsd/auto-dispatch.js +23 -1
- package/dist/resources/extensions/gsd/auto-start.js +2 -0
- package/dist/resources/extensions/gsd/auto-timers.js +24 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +25 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +91 -14
- package/dist/resources/extensions/gsd/auto.js +30 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +99 -70
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +12 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
- package/dist/resources/extensions/gsd/claude-import.js +60 -9
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +69 -6
- package/dist/resources/extensions/gsd/commands-config.js +10 -5
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +4 -4
- package/dist/resources/extensions/gsd/detection.js +6 -6
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/dist/resources/extensions/gsd/error-classifier.js +105 -0
- package/dist/resources/extensions/gsd/git-service.js +4 -3
- package/dist/resources/extensions/gsd/gitignore.js +7 -7
- package/dist/resources/extensions/gsd/gsd-db.js +298 -45
- package/dist/resources/extensions/gsd/guided-flow.js +4 -3
- package/dist/resources/extensions/gsd/init-wizard.js +2 -2
- package/dist/resources/extensions/gsd/key-manager.js +7 -16
- package/dist/resources/extensions/gsd/markdown-renderer.js +5 -4
- package/dist/resources/extensions/gsd/memory-store.js +28 -13
- package/dist/resources/extensions/gsd/milestone-actions.js +19 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.js +18 -2
- package/dist/resources/extensions/gsd/preferences-models.js +1 -13
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences.js +13 -13
- package/dist/resources/extensions/gsd/prompts/system.md +1 -1
- package/dist/resources/extensions/gsd/provider-error-pause.js +0 -44
- package/dist/resources/extensions/gsd/rule-registry.js +1 -1
- package/dist/resources/extensions/gsd/service-tier.js +13 -2
- package/dist/resources/extensions/gsd/state.js +38 -30
- package/dist/resources/extensions/gsd/status-guards.js +12 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +7 -13
- package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -20
- package/dist/resources/extensions/gsd/tools/complete-task.js +11 -21
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +28 -29
- package/dist/resources/extensions/gsd/tools/plan-slice.js +27 -26
- package/dist/resources/extensions/gsd/tools/plan-task.js +23 -23
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +50 -41
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +4 -3
- package/dist/resources/extensions/gsd/tools/reopen-task.js +5 -4
- package/dist/resources/extensions/gsd/tools/replan-slice.js +51 -41
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +23 -16
- package/dist/resources/extensions/gsd/validation.js +21 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +0 -1
- package/dist/resources/extensions/remote-questions/config.js +1 -1
- package/dist/resources/extensions/remote-questions/remote-command.js +1 -1
- package/dist/resources/extensions/search-the-web/native-search.js +1 -1
- package/dist/resources/extensions/search-the-web/provider.js +1 -1
- package/dist/resources/extensions/shared/rtk.js +14 -4
- package/dist/rtk.js +3 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +4 -4
- 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 +4 -4
- 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 +2 -2
- package/dist/web/standalone/.next/server/app/api/boot/route.js +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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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 +5 -5
- 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 +5 -5
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +11 -11
- package/dist/web/standalone/.next/server/chunks/2229.js +3 -3
- 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-react-loadable-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/4024.87fd909ae0110f50.js +9 -0
- 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-b950e4e384cc62b3.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/.next/static/chunks/{webpack-cfc9a116e6450a6b.js → webpack-bca0e732db0dcec3.js} +1 -1
- package/dist/web/standalone/.next/static/css/a58ef8a151aa0493.css +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/wizard.js +4 -1
- package/package.json +2 -2
- package/packages/mcp-server/README.md +202 -0
- package/packages/mcp-server/package.json +36 -0
- package/packages/mcp-server/src/cli.ts +68 -0
- package/packages/mcp-server/src/index.ts +14 -0
- package/packages/mcp-server/src/mcp-server.test.ts +628 -0
- package/packages/mcp-server/src/server.ts +278 -0
- package/packages/mcp-server/src/session-manager.ts +328 -0
- package/packages/mcp-server/src/types.ts +107 -0
- package/packages/mcp-server/tsconfig.json +24 -0
- package/packages/pi-ai/dist/models.d.ts +14 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.js +53 -10
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +102 -1
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +30 -0
- 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/models.test.ts +114 -1
- package/packages/pi-ai/src/models.ts +70 -13
- package/packages/pi-ai/src/types.ts +31 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts +2 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +3 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js +5 -1
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +9 -4
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts +19 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js +83 -0
- package/packages/pi-coding-agent/dist/core/tools/bash-spawn-windows.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +5 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +5 -3
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +0 -2
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts +28 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js +49 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +114 -6
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts +9 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js +831 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-protocol-v2.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts +66 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +0 -1
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +4 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +5 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +10 -3
- package/packages/pi-coding-agent/src/core/tools/bash-spawn-windows.test.ts +101 -0
- package/packages/pi-coding-agent/src/core/tools/bash.ts +5 -1
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/main.ts +5 -3
- package/packages/pi-coding-agent/src/modes/index.ts +8 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +54 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +124 -6
- package/packages/pi-coding-agent/src/modes/rpc/rpc-protocol-v2.test.ts +971 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-types.ts +61 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +0 -1
- package/packages/rpc-client/package.json +20 -0
- package/pkg/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +36 -8
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +22 -11
- package/src/resources/extensions/async-jobs/job-manager.ts +4 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +19 -20
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +21 -0
- package/src/resources/extensions/get-secrets-from-user.ts +8 -0
- package/src/resources/extensions/gsd/auto/phases.ts +44 -7
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -1
- package/src/resources/extensions/gsd/auto-start.ts +2 -0
- package/src/resources/extensions/gsd/auto-timers.ts +25 -1
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +30 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +94 -14
- package/src/resources/extensions/gsd/auto.ts +31 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +118 -73
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +11 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
- package/src/resources/extensions/gsd/claude-import.ts +58 -9
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +73 -6
- package/src/resources/extensions/gsd/commands-config.ts +11 -5
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -4
- package/src/resources/extensions/gsd/detection.ts +6 -6
- package/src/resources/extensions/gsd/docs/preferences-reference.md +5 -5
- package/src/resources/extensions/gsd/error-classifier.ts +139 -0
- package/src/resources/extensions/gsd/git-service.ts +4 -3
- package/src/resources/extensions/gsd/gitignore.ts +7 -7
- package/src/resources/extensions/gsd/gsd-db.ts +355 -63
- package/src/resources/extensions/gsd/guided-flow.ts +4 -3
- package/src/resources/extensions/gsd/init-wizard.ts +2 -2
- package/src/resources/extensions/gsd/key-manager.ts +7 -16
- package/src/resources/extensions/gsd/markdown-renderer.ts +5 -4
- package/src/resources/extensions/gsd/memory-store.ts +29 -18
- package/src/resources/extensions/gsd/milestone-actions.ts +17 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +23 -1
- package/src/resources/extensions/gsd/preferences-models.ts +1 -13
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/preferences.ts +12 -13
- package/src/resources/extensions/gsd/prompts/system.md +1 -1
- package/src/resources/extensions/gsd/provider-error-pause.ts +0 -57
- package/src/resources/extensions/gsd/rule-registry.ts +1 -1
- package/src/resources/extensions/gsd/service-tier.ts +14 -2
- package/src/resources/extensions/gsd/state.ts +39 -30
- package/src/resources/extensions/gsd/status-guards.ts +13 -0
- package/src/resources/extensions/gsd/tests/active-milestone-id-guard.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +61 -0
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/claude-import-marketplace-discovery.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/commands-config.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +35 -7
- package/src/resources/extensions/gsd/tests/detection.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/empty-db-reconciliation.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +65 -31
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/interactive-tool-idle-exemption.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/parallel-orchestrator-zombie-cleanup.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +103 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +77 -70
- package/src/resources/extensions/gsd/tests/rate-limit-model-fallback.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +42 -31
- package/src/resources/extensions/gsd/tests/token-cost-display.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/vacuous-truth-slices.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/validation-gate-patterns.test.ts +124 -0
- package/src/resources/extensions/gsd/tests/validation.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +81 -1
- package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +130 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +7 -17
- package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -24
- package/src/resources/extensions/gsd/tools/complete-task.ts +13 -25
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +30 -32
- package/src/resources/extensions/gsd/tools/plan-slice.ts +30 -30
- package/src/resources/extensions/gsd/tools/plan-task.ts +26 -26
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +57 -46
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +4 -3
- package/src/resources/extensions/gsd/tools/reopen-task.ts +5 -4
- package/src/resources/extensions/gsd/tools/replan-slice.ts +55 -44
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +26 -20
- package/src/resources/extensions/gsd/validation.ts +23 -0
- package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
- package/src/resources/extensions/remote-questions/config.ts +1 -1
- package/src/resources/extensions/remote-questions/remote-command.ts +1 -1
- package/src/resources/extensions/search-the-web/native-search.ts +1 -1
- package/src/resources/extensions/search-the-web/provider.ts +1 -1
- package/src/resources/extensions/shared/rtk.ts +22 -4
- package/dist/web/standalone/.next/static/chunks/4024.9ad5def014d90ce4.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.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/css/de141508b083f922.css +0 -1
- /package/dist/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → zpvUPKoW5jRAMB_fWHlPi}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -1,73 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* terminated-transient.test.ts — Regression test for #2309.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* classifyError should treat 'terminated' errors (process killed,
|
|
5
5
|
* connection reset) as transient with auto-resume, not permanent.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import test from "node:test";
|
|
9
9
|
import assert from "node:assert/strict";
|
|
10
|
-
import {
|
|
10
|
+
import { classifyError, isTransient } from "../error-classifier.ts";
|
|
11
11
|
|
|
12
12
|
test("#2309: 'terminated' errors should be classified as transient", () => {
|
|
13
|
-
const result =
|
|
14
|
-
assert.equal(result
|
|
15
|
-
assert.equal(result.
|
|
16
|
-
assert.ok(result.
|
|
13
|
+
const result = classifyError("terminated");
|
|
14
|
+
assert.equal(isTransient(result), true, "'terminated' should be transient");
|
|
15
|
+
assert.equal(result.kind, "connection", "'terminated' matches connection");
|
|
16
|
+
assert.ok("retryAfterMs" in result && result.retryAfterMs > 0, "'terminated' should have a retry delay");
|
|
17
|
+
assert.equal("retryAfterMs" in result && result.retryAfterMs, 15_000, "'terminated' should use 15s backoff");
|
|
17
18
|
});
|
|
18
19
|
|
|
19
|
-
test("#2309: 'connection reset' errors should be classified as transient", () => {
|
|
20
|
-
const result =
|
|
21
|
-
assert.equal(result
|
|
20
|
+
test("#2309: 'connection reset by peer' errors should be classified as transient (network)", () => {
|
|
21
|
+
const result = classifyError("connection reset by peer");
|
|
22
|
+
assert.equal(isTransient(result), true, "'connection reset by peer' should be transient");
|
|
23
|
+
assert.equal(result.kind, "network", "'connection reset by peer' matches NETWORK_RE (connection.*reset) before CONNECTION_RE");
|
|
24
|
+
assert.equal("retryAfterMs" in result && result.retryAfterMs, 3_000, "network errors use 3s backoff");
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
test("#2309: 'other side closed' errors should be classified as transient", () => {
|
|
25
|
-
const result =
|
|
26
|
-
assert.equal(result
|
|
28
|
+
const result = classifyError("other side closed the connection");
|
|
29
|
+
assert.equal(isTransient(result), true, "'other side closed' should be transient");
|
|
30
|
+
assert.equal(result.kind, "connection", "'other side closed' matches CONNECTION_RE");
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
test("#2309: 'fetch failed' errors should be classified as transient", () => {
|
|
30
|
-
const result =
|
|
31
|
-
assert.equal(result
|
|
34
|
+
const result = classifyError("fetch failed: network error");
|
|
35
|
+
assert.equal(isTransient(result), true, "'fetch failed' should be transient");
|
|
36
|
+
assert.equal(result.kind, "network", "'fetch failed' matches NETWORK_RE");
|
|
37
|
+
assert.equal("retryAfterMs" in result && result.retryAfterMs, 3_000, "network errors use 3s backoff");
|
|
32
38
|
});
|
|
33
39
|
|
|
34
40
|
test("#2309: 'connection refused' errors should be classified as transient", () => {
|
|
35
|
-
const result =
|
|
36
|
-
assert.equal(result
|
|
41
|
+
const result = classifyError("ECONNREFUSED: connection refused");
|
|
42
|
+
assert.equal(isTransient(result), true, "'connection refused' should be transient");
|
|
43
|
+
assert.equal(result.kind, "network", "'ECONNREFUSED' matches NETWORK_RE (same-model retry)");
|
|
37
44
|
});
|
|
38
45
|
|
|
39
46
|
test("#2309: permanent errors are still permanent", () => {
|
|
40
|
-
const authResult =
|
|
41
|
-
assert.equal(authResult
|
|
42
|
-
assert.equal(authResult.
|
|
47
|
+
const authResult = classifyError("unauthorized: invalid API key");
|
|
48
|
+
assert.equal(isTransient(authResult), false, "auth errors should stay permanent");
|
|
49
|
+
assert.equal(authResult.kind, "permanent", "auth errors are permanent");
|
|
50
|
+
assert.equal("retryAfterMs" in authResult, false, "permanent errors have no retryAfterMs");
|
|
43
51
|
});
|
|
44
52
|
|
|
45
53
|
test("#2309: rate limits are still transient", () => {
|
|
46
|
-
const rlResult =
|
|
47
|
-
assert.equal(rlResult
|
|
48
|
-
assert.equal(rlResult.
|
|
54
|
+
const rlResult = classifyError("rate limit exceeded (429)");
|
|
55
|
+
assert.equal(isTransient(rlResult), true, "rate limits are still transient");
|
|
56
|
+
assert.equal(rlResult.kind, "rate-limit", "rate limits are flagged as rate-limit kind");
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
// --- #2572: stream-truncation JSON parse errors should be transient ---
|
|
52
60
|
|
|
53
61
|
test("#2572: 'Expected double-quoted property name' (truncated stream) is transient", () => {
|
|
54
|
-
const result =
|
|
55
|
-
assert.equal(result
|
|
56
|
-
assert.equal(result.
|
|
57
|
-
assert.equal(result.
|
|
62
|
+
const result = classifyError("Expected double-quoted property name in JSON at position 23 (line 1 column 24)");
|
|
63
|
+
assert.equal(isTransient(result), true, "truncated-stream JSON parse error should be transient");
|
|
64
|
+
assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
|
|
65
|
+
assert.equal("retryAfterMs" in result && result.retryAfterMs, 15_000, "should use 15s backoff");
|
|
58
66
|
});
|
|
59
67
|
|
|
60
68
|
test("#2572: 'Unexpected end of JSON input' (truncated stream) is transient", () => {
|
|
61
|
-
const result =
|
|
62
|
-
assert.equal(result
|
|
69
|
+
const result = classifyError("Unexpected end of JSON input");
|
|
70
|
+
assert.equal(isTransient(result), true, "'Unexpected end of JSON input' should be transient");
|
|
71
|
+
assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
|
|
63
72
|
});
|
|
64
73
|
|
|
65
74
|
test("#2572: 'Unexpected token' in JSON (truncated stream) is transient", () => {
|
|
66
|
-
const result =
|
|
67
|
-
assert.equal(result
|
|
75
|
+
const result = classifyError("Unexpected token < in JSON at position 0");
|
|
76
|
+
assert.equal(isTransient(result), true, "'Unexpected token in JSON' should be transient");
|
|
77
|
+
assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
|
|
68
78
|
});
|
|
69
79
|
|
|
70
80
|
test("#2572: 'SyntaxError' with JSON context (truncated stream) is transient", () => {
|
|
71
|
-
const result =
|
|
72
|
-
assert.equal(result
|
|
81
|
+
const result = classifyError("SyntaxError: JSON.parse: unexpected character at line 1 column 1");
|
|
82
|
+
assert.equal(isTransient(result), true, "'SyntaxError...JSON' should be transient");
|
|
83
|
+
assert.equal(result.kind, "stream", "JSON parse errors are stream kind");
|
|
73
84
|
});
|
|
@@ -63,13 +63,13 @@ test("show_token_cost defaults to undefined (disabled) when not set", () => {
|
|
|
63
63
|
assert.equal(preferences.show_token_cost, undefined);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
test("empty
|
|
66
|
+
test("empty PREFERENCES.md does not enable show_token_cost", () => {
|
|
67
67
|
const prefs = parsePreferencesMarkdown("---\nversion: 1\n---\n");
|
|
68
68
|
assert.ok(prefs);
|
|
69
69
|
assert.equal(prefs.show_token_cost, undefined);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
test("
|
|
72
|
+
test("PREFERENCES.md with show_token_cost: true enables the preference", () => {
|
|
73
73
|
const prefs = parsePreferencesMarkdown("---\nshow_token_cost: true\n---\n");
|
|
74
74
|
assert.ok(prefs);
|
|
75
75
|
assert.equal(prefs.show_token_cost, true);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #2667: deriveStateFromDb must NOT treat an empty
|
|
3
|
+
* slice array as "all slices done" due to JavaScript's vacuous-truth
|
|
4
|
+
* behavior of Array.prototype.every on an empty array.
|
|
5
|
+
*
|
|
6
|
+
* [].every(predicate) === true in JavaScript. Without a length > 0 guard,
|
|
7
|
+
* this causes a premature phase transition to validating-milestone when
|
|
8
|
+
* the DB returns 0 slices (e.g. after a worktree DB wipe).
|
|
9
|
+
*/
|
|
10
|
+
import { test } from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
|
|
16
|
+
import { deriveStateFromDb, invalidateStateCache } from "../state.ts";
|
|
17
|
+
import {
|
|
18
|
+
openDatabase,
|
|
19
|
+
closeDatabase,
|
|
20
|
+
insertMilestone,
|
|
21
|
+
insertSlice,
|
|
22
|
+
} from "../gsd-db.ts";
|
|
23
|
+
|
|
24
|
+
test("deriveStateFromDb does NOT skip to validating when slice array is empty (#2667)", async () => {
|
|
25
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-vacuous-truth-"));
|
|
26
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Set up a milestone with a roadmap that references slices,
|
|
30
|
+
// but the DB has NO slice rows (simulating a worktree DB wipe)
|
|
31
|
+
writeFileSync(
|
|
32
|
+
join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
33
|
+
[
|
|
34
|
+
"# M001: Test Milestone",
|
|
35
|
+
"",
|
|
36
|
+
"## Slices",
|
|
37
|
+
"",
|
|
38
|
+
"### S01 — First Slice",
|
|
39
|
+
"Do something.",
|
|
40
|
+
"",
|
|
41
|
+
"### S02 — Second Slice",
|
|
42
|
+
"Do another thing.",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
openDatabase(":memory:");
|
|
47
|
+
// Milestone exists but NO slices inserted — simulates DB wipe
|
|
48
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
|
|
49
|
+
|
|
50
|
+
invalidateStateCache();
|
|
51
|
+
const state = await deriveStateFromDb(base);
|
|
52
|
+
|
|
53
|
+
// The phase must NOT be "validating-milestone" or "completing-milestone"
|
|
54
|
+
// because no slices have been executed — the empty array should not
|
|
55
|
+
// trigger the "all slices done" code path.
|
|
56
|
+
assert.notEqual(
|
|
57
|
+
state.phase,
|
|
58
|
+
"validating-milestone",
|
|
59
|
+
"empty slice array must not trigger validating-milestone (vacuous truth)",
|
|
60
|
+
);
|
|
61
|
+
assert.notEqual(
|
|
62
|
+
state.phase,
|
|
63
|
+
"completing-milestone",
|
|
64
|
+
"empty slice array must not trigger completing-milestone (vacuous truth)",
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
closeDatabase();
|
|
68
|
+
} finally {
|
|
69
|
+
closeDatabase();
|
|
70
|
+
rmSync(base, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("deriveStateFromDb correctly reaches validating when all slices are done (#2667 guard)", async () => {
|
|
75
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-vacuous-truth-"));
|
|
76
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
writeFileSync(
|
|
80
|
+
join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
81
|
+
[
|
|
82
|
+
"# M001: Test Milestone",
|
|
83
|
+
"",
|
|
84
|
+
"## Slices",
|
|
85
|
+
"",
|
|
86
|
+
"### S01 — First Slice",
|
|
87
|
+
"Do something.",
|
|
88
|
+
].join("\n"),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Write a slice summary so the filesystem recognizes it as complete
|
|
92
|
+
writeFileSync(
|
|
93
|
+
join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md"),
|
|
94
|
+
"# S01 Summary\n\nDone.",
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
openDatabase(":memory:");
|
|
98
|
+
insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
|
|
99
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First Slice", status: "complete", risk: "low", depends: [] });
|
|
100
|
+
|
|
101
|
+
invalidateStateCache();
|
|
102
|
+
const state = await deriveStateFromDb(base);
|
|
103
|
+
|
|
104
|
+
// With one slice that IS complete, phase should advance
|
|
105
|
+
assert.ok(
|
|
106
|
+
state.phase === "validating-milestone" || state.phase === "completing-milestone",
|
|
107
|
+
`expected validating or completing phase, got "${state.phase}"`,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
closeDatabase();
|
|
111
|
+
} finally {
|
|
112
|
+
closeDatabase();
|
|
113
|
+
rmSync(base, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, existsSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
import { handleValidateMilestone } from "../tools/validate-milestone.js";
|
|
9
|
+
import { openDatabase, closeDatabase, _getAdapter, insertMilestone } from "../gsd-db.js";
|
|
10
|
+
import { clearPathCache } from "../paths.js";
|
|
11
|
+
import { clearParseCache } from "../files.js";
|
|
12
|
+
|
|
13
|
+
function makeTmpBase(): string {
|
|
14
|
+
const base = join(tmpdir(), `gsd-val-handler-${randomUUID()}`);
|
|
15
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
16
|
+
return base;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const VALID_PARAMS = {
|
|
20
|
+
milestoneId: "M001",
|
|
21
|
+
verdict: "pass" as const,
|
|
22
|
+
remediationRound: 0,
|
|
23
|
+
successCriteriaChecklist: "- [x] All pass",
|
|
24
|
+
sliceDeliveryAudit: "| S01 | delivered |",
|
|
25
|
+
crossSliceIntegration: "No issues",
|
|
26
|
+
requirementCoverage: "All covered",
|
|
27
|
+
verdictRationale: "Everything checks out",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("handleValidateMilestone write ordering (#2725)", () => {
|
|
31
|
+
let base: string;
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
clearPathCache();
|
|
35
|
+
clearParseCache();
|
|
36
|
+
try { closeDatabase(); } catch { /* */ }
|
|
37
|
+
if (base) {
|
|
38
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("writes DB row and disk file on success", async () => {
|
|
43
|
+
base = makeTmpBase();
|
|
44
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
45
|
+
openDatabase(dbPath);
|
|
46
|
+
insertMilestone({ id: "M001" });
|
|
47
|
+
|
|
48
|
+
const result = await handleValidateMilestone(VALID_PARAMS, base);
|
|
49
|
+
assert.ok(!("error" in result), `unexpected error: ${"error" in result ? result.error : ""}`);
|
|
50
|
+
|
|
51
|
+
// DB row exists
|
|
52
|
+
const adapter = _getAdapter()!;
|
|
53
|
+
const row = adapter.prepare(
|
|
54
|
+
`SELECT status, scope FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
|
|
55
|
+
).get() as { status: string; scope: string } | undefined;
|
|
56
|
+
assert.ok(row, "assessment row should exist in DB");
|
|
57
|
+
assert.equal(row!.status, "pass");
|
|
58
|
+
|
|
59
|
+
// Disk file exists
|
|
60
|
+
const filePath = join(base, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
|
|
61
|
+
assert.ok(existsSync(filePath), "VALIDATION.md should exist on disk");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("rolls back DB row when disk write fails", async () => {
|
|
65
|
+
base = makeTmpBase();
|
|
66
|
+
const dbPath = join(base, ".gsd", "gsd.db");
|
|
67
|
+
openDatabase(dbPath);
|
|
68
|
+
insertMilestone({ id: "M001" });
|
|
69
|
+
|
|
70
|
+
// Force disk write failure by replacing the milestone directory with a
|
|
71
|
+
// regular file. saveFile() will fail because it cannot write inside a
|
|
72
|
+
// non-directory. This works cross-platform (chmod is ignored on Windows).
|
|
73
|
+
const milestoneDir = join(base, ".gsd", "milestones", "M001");
|
|
74
|
+
rmSync(milestoneDir, { recursive: true, force: true });
|
|
75
|
+
writeFileSync(milestoneDir, "not-a-directory");
|
|
76
|
+
|
|
77
|
+
const result = await handleValidateMilestone(VALID_PARAMS, base);
|
|
78
|
+
|
|
79
|
+
// Should return error
|
|
80
|
+
assert.ok("error" in result, "should return error when disk write fails");
|
|
81
|
+
assert.ok(result.error.includes("disk render failed"));
|
|
82
|
+
|
|
83
|
+
// DB row should have been rolled back (deleted)
|
|
84
|
+
const adapter = _getAdapter()!;
|
|
85
|
+
const row = adapter.prepare(
|
|
86
|
+
`SELECT * FROM assessments WHERE milestone_id = 'M001' AND scope = 'milestone-validation'`,
|
|
87
|
+
).get();
|
|
88
|
+
assert.equal(row, undefined, "assessment row should be deleted after disk-write rollback");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -110,6 +110,16 @@ test("isValidationTerminal returns true for verdict: passed (#1429)", () => {
|
|
|
110
110
|
assert.equal(isValidationTerminal(content), true);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
test("isValidationTerminal returns true for verdict: fail (#2769)", () => {
|
|
114
|
+
const content = "---\nverdict: fail\nremediation_round: 1\n---\n\n# Validation";
|
|
115
|
+
assert.equal(isValidationTerminal(content), true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("isValidationTerminal returns true for any arbitrary verdict string (#2769)", () => {
|
|
119
|
+
const content = "---\nverdict: custom-verdict\nremediation_round: 0\n---\n\n# Validation";
|
|
120
|
+
assert.equal(isValidationTerminal(content), true);
|
|
121
|
+
});
|
|
122
|
+
|
|
113
123
|
test("isValidationTerminal returns false for missing frontmatter", () => {
|
|
114
124
|
const content = "# Validation\nNo frontmatter here.";
|
|
115
125
|
assert.equal(isValidationTerminal(content), false);
|
|
@@ -327,14 +337,14 @@ test("verifyExpectedArtifact rejects VALIDATION with missing verdict field", ()
|
|
|
327
337
|
}
|
|
328
338
|
});
|
|
329
339
|
|
|
330
|
-
test("verifyExpectedArtifact
|
|
340
|
+
test("verifyExpectedArtifact accepts VALIDATION with any extracted verdict", () => {
|
|
331
341
|
const base = makeTmpBase();
|
|
332
342
|
try {
|
|
333
343
|
writeValidation(base, "M001", "---\nverdict: unknown-value\nremediation_round: 0\n---\n\n# Validation");
|
|
334
344
|
clearPathCache();
|
|
335
345
|
clearParseCache();
|
|
336
346
|
const result = verifyExpectedArtifact("validate-milestone", "M001", base);
|
|
337
|
-
assert.equal(result,
|
|
347
|
+
assert.equal(result, true, "VALIDATION with any extracted verdict should pass verification");
|
|
338
348
|
} finally {
|
|
339
349
|
cleanup(base);
|
|
340
350
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the milestone completion validation gate pattern matching.
|
|
3
|
+
*
|
|
4
|
+
* The gate in auto-dispatch accepts two evidence formats:
|
|
5
|
+
* 1. Structured template: content contains "Operational" AND ("MET" or "N/A")
|
|
6
|
+
* 2. Prose evidence: matches /[Oo]perational[\s:][^\n]*(?:pass|verified|...)/i
|
|
7
|
+
*
|
|
8
|
+
* These tests exercise the exact same expressions used in auto-dispatch.ts
|
|
9
|
+
* to ensure both formats are correctly recognized, and that content without
|
|
10
|
+
* operational evidence is properly rejected.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import test from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
|
|
16
|
+
// ─── Replicate the gate matching logic from auto-dispatch.ts ─────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns true when validation content contains acceptable operational
|
|
20
|
+
* verification evidence (structured or prose). Mirrors the inline checks
|
|
21
|
+
* in the "execute → complete-milestone" dispatch rule.
|
|
22
|
+
*/
|
|
23
|
+
function hasOperationalEvidence(validationContent: string): boolean {
|
|
24
|
+
const structuredMatch =
|
|
25
|
+
validationContent.includes("Operational") &&
|
|
26
|
+
(validationContent.includes("MET") || validationContent.includes("N/A"));
|
|
27
|
+
const proseMatch =
|
|
28
|
+
/[Oo]perational[\s:][^\n]*(?:pass|verified|confirmed|met|complete|true|yes|addressed|covered|n\/a|not\s+applicable)/i.test(
|
|
29
|
+
validationContent,
|
|
30
|
+
);
|
|
31
|
+
return structuredMatch || proseMatch;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Structured format ───────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
test("structured: Operational + MET passes", () => {
|
|
37
|
+
const content = `| Criteria | Status |
|
|
38
|
+
| Operational | MET |
|
|
39
|
+
| Functional | MET |`;
|
|
40
|
+
assert.ok(hasOperationalEvidence(content));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("structured: Operational + N/A passes", () => {
|
|
44
|
+
const content = `| Criteria | Status |
|
|
45
|
+
| Operational | N/A |
|
|
46
|
+
| Functional | MET |`;
|
|
47
|
+
assert.ok(hasOperationalEvidence(content));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("structured: Operational present with MET on another row still passes (includes is content-wide)", () => {
|
|
51
|
+
// The structured check uses .includes() across the entire content,
|
|
52
|
+
// so "MET" on the Functional row satisfies the condition alongside
|
|
53
|
+
// "Operational" anywhere in the document.
|
|
54
|
+
const content = `| Criteria | Status |
|
|
55
|
+
| Operational | PENDING |
|
|
56
|
+
| Functional | MET |`;
|
|
57
|
+
assert.ok(hasOperationalEvidence(content));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("structured: Operational alone without any MET or N/A anywhere fails", () => {
|
|
61
|
+
const content = `| Criteria | Status |
|
|
62
|
+
| Operational | PENDING |
|
|
63
|
+
| Functional | PENDING |`;
|
|
64
|
+
assert.ok(!hasOperationalEvidence(content));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ─── Prose format ────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
test('prose: "Operational: verified" passes', () => {
|
|
70
|
+
const content = `## Validation Report
|
|
71
|
+
Operational: verified — all endpoints responsive.
|
|
72
|
+
Functional: tests pass.`;
|
|
73
|
+
assert.ok(hasOperationalEvidence(content));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('prose: "Operational checks confirmed" passes', () => {
|
|
77
|
+
const content = `## Validation Report
|
|
78
|
+
Operational checks confirmed by smoke test suite.`;
|
|
79
|
+
assert.ok(hasOperationalEvidence(content));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('prose: "Operational — pass" passes', () => {
|
|
83
|
+
const content = `Operational — pass (all services healthy)`;
|
|
84
|
+
assert.ok(hasOperationalEvidence(content));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('prose: "operational: addressed" passes (case-insensitive)', () => {
|
|
88
|
+
const content = `operational: addressed in CI pipeline run #42.`;
|
|
89
|
+
assert.ok(hasOperationalEvidence(content));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('prose: "Operational: not applicable" passes', () => {
|
|
93
|
+
const content = `Operational: not applicable for this library-only change.`;
|
|
94
|
+
assert.ok(hasOperationalEvidence(content));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('prose: "Operational: n/a" passes', () => {
|
|
98
|
+
const content = `Operational: n/a — no runtime components.`;
|
|
99
|
+
assert.ok(hasOperationalEvidence(content));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('prose: "Operational: complete" passes', () => {
|
|
103
|
+
const content = `Operational: complete — all health checks green.`;
|
|
104
|
+
assert.ok(hasOperationalEvidence(content));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ─── Rejection cases ─────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
test("no operational evidence: unrelated content fails", () => {
|
|
110
|
+
const content = `## Validation Report
|
|
111
|
+
All functional tests pass.
|
|
112
|
+
Code coverage at 92%.`;
|
|
113
|
+
assert.ok(!hasOperationalEvidence(content));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("no operational evidence: word 'operational' buried without qualifying keyword fails", () => {
|
|
117
|
+
const content = `## Validation Report
|
|
118
|
+
The operational aspects were not evaluated in this round.`;
|
|
119
|
+
assert.ok(!hasOperationalEvidence(content));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("no operational evidence: empty content fails", () => {
|
|
123
|
+
assert.ok(!hasOperationalEvidence(""));
|
|
124
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// GSD — validation unit tests
|
|
2
|
+
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
|
|
6
|
+
import { isNonEmptyString, validateStringArray } from '../validation.ts';
|
|
7
|
+
|
|
8
|
+
// ─── isNonEmptyString ────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
test('isNonEmptyString: "hello" returns true', () => {
|
|
11
|
+
assert.equal(isNonEmptyString('hello'), true);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('isNonEmptyString: " " (whitespace only) returns false', () => {
|
|
15
|
+
assert.equal(isNonEmptyString(' '), false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('isNonEmptyString: "" (empty string) returns false', () => {
|
|
19
|
+
assert.equal(isNonEmptyString(''), false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('isNonEmptyString: null returns false', () => {
|
|
23
|
+
assert.equal(isNonEmptyString(null), false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('isNonEmptyString: undefined returns false', () => {
|
|
27
|
+
assert.equal(isNonEmptyString(undefined), false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('isNonEmptyString: 42 (number) returns false', () => {
|
|
31
|
+
assert.equal(isNonEmptyString(42), false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ─── validateStringArray ─────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
test('validateStringArray: ["a", "b"] returns ["a", "b"]', () => {
|
|
37
|
+
assert.deepEqual(validateStringArray(['a', 'b'], 'items'), ['a', 'b']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('validateStringArray: [] (empty array) returns []', () => {
|
|
41
|
+
assert.deepEqual(validateStringArray([], 'items'), []);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('validateStringArray: "not an array" throws with "must be an array"', () => {
|
|
45
|
+
assert.throws(
|
|
46
|
+
() => validateStringArray('not an array', 'items'),
|
|
47
|
+
(err: Error) => {
|
|
48
|
+
assert.ok(err.message.includes('must be an array'));
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('validateStringArray: ["a", 42] throws with "must contain only non-empty strings"', () => {
|
|
55
|
+
assert.throws(
|
|
56
|
+
() => validateStringArray(['a', 42], 'items'),
|
|
57
|
+
(err: Error) => {
|
|
58
|
+
assert.ok(err.message.includes('must contain only non-empty strings'));
|
|
59
|
+
return true;
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('validateStringArray: ["a", ""] throws with "must contain only non-empty strings"', () => {
|
|
65
|
+
assert.throws(
|
|
66
|
+
() => validateStringArray(['a', ''], 'items'),
|
|
67
|
+
(err: Error) => {
|
|
68
|
+
assert.ok(err.message.includes('must contain only non-empty strings'));
|
|
69
|
+
return true;
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
});
|