gsd-pi 2.51.0 → 2.52.0-dev.585e355
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 +4 -4
- package/dist/headless-events.d.ts +18 -0
- package/dist/headless-events.js +36 -0
- 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/gsd/auto/phases.js +6 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +18 -0
- 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 +21 -0
- package/dist/resources/extensions/gsd/auto.js +8 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +105 -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/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/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 +33 -19
- 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 +5 -3
- 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 +15 -15
- 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 +15 -15
- 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.21054f459af5cc78.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-024d82be84800e52.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/gsd/auto/phases.ts +6 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +19 -0
- 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 +21 -0
- package/src/resources/extensions/gsd/auto.ts +10 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -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/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/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 +34 -20
- package/src/resources/extensions/gsd/status-guards.ts +13 -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/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/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 +37 -4
- 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/none-mode-gates.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/park-db-sync.test.ts +85 -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/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/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/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 +12 -3
- 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 → KTe1kB5nPLQFIIFz2OcmI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vkr67v-utm1dgZnbrBWQh → KTe1kB5nPLQFIIFz2OcmI}/_ssgManifest.js +0 -0
- /package/src/resources/extensions/gsd/templates/{preferences.md → PREFERENCES.md} +0 -0
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// GSD Extension — Workflow Logger Tests
|
|
2
2
|
// Tests for the centralized warning/error accumulator.
|
|
3
3
|
|
|
4
|
-
import { describe, test, beforeEach } from "node:test";
|
|
4
|
+
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { makeTempDir, cleanup } from "./test-utils.ts";
|
|
6
9
|
import {
|
|
7
10
|
logWarning,
|
|
8
11
|
logError,
|
|
@@ -14,6 +17,7 @@ import {
|
|
|
14
17
|
hasAnyIssues,
|
|
15
18
|
summarizeLogs,
|
|
16
19
|
formatForNotification,
|
|
20
|
+
setLogBasePath,
|
|
17
21
|
_resetLogs,
|
|
18
22
|
} from "../workflow-logger.ts";
|
|
19
23
|
|
|
@@ -222,6 +226,44 @@ describe("workflow-logger", () => {
|
|
|
222
226
|
});
|
|
223
227
|
});
|
|
224
228
|
|
|
229
|
+
describe("audit log persistence", () => {
|
|
230
|
+
let dir: string;
|
|
231
|
+
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
dir = makeTempDir("wl-audit-");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
afterEach(() => {
|
|
237
|
+
setLogBasePath("");
|
|
238
|
+
cleanup(dir);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
|
|
242
|
+
setLogBasePath(dir);
|
|
243
|
+
logWarning("engine", "audit test entry");
|
|
244
|
+
|
|
245
|
+
const auditPath = join(dir, ".gsd", "audit-log.jsonl");
|
|
246
|
+
assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
|
|
247
|
+
const content = readFileSync(auditPath, "utf-8");
|
|
248
|
+
const entry = JSON.parse(content.trim());
|
|
249
|
+
assert.equal(entry.severity, "warn");
|
|
250
|
+
assert.equal(entry.component, "engine");
|
|
251
|
+
assert.equal(entry.message, "audit test entry");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("_resetLogs does not clear the audit base path", () => {
|
|
255
|
+
setLogBasePath(dir);
|
|
256
|
+
_resetLogs();
|
|
257
|
+
logWarning("engine", "post-reset entry");
|
|
258
|
+
|
|
259
|
+
const auditPath = join(dir, ".gsd", "audit-log.jsonl");
|
|
260
|
+
assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
|
|
261
|
+
const content = readFileSync(auditPath, "utf-8");
|
|
262
|
+
const entry = JSON.parse(content.trim());
|
|
263
|
+
assert.equal(entry.message, "post-reset entry");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
225
267
|
describe("buffer limit", () => {
|
|
226
268
|
test("caps at MAX_BUFFER entries, dropping oldest", () => {
|
|
227
269
|
const OVER = 110;
|
|
@@ -237,6 +279,44 @@ describe("workflow-logger", () => {
|
|
|
237
279
|
});
|
|
238
280
|
});
|
|
239
281
|
|
|
282
|
+
describe("audit log persistence", () => {
|
|
283
|
+
let dir: string;
|
|
284
|
+
|
|
285
|
+
beforeEach(() => {
|
|
286
|
+
dir = makeTempDir("wl-audit-");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
afterEach(() => {
|
|
290
|
+
setLogBasePath("");
|
|
291
|
+
cleanup(dir);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
|
|
295
|
+
setLogBasePath(dir);
|
|
296
|
+
logWarning("engine", "audit test entry");
|
|
297
|
+
|
|
298
|
+
const auditPath = join(dir, ".gsd", "audit-log.jsonl");
|
|
299
|
+
assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
|
|
300
|
+
const content = readFileSync(auditPath, "utf-8");
|
|
301
|
+
const entry = JSON.parse(content.trim());
|
|
302
|
+
assert.equal(entry.severity, "warn");
|
|
303
|
+
assert.equal(entry.component, "engine");
|
|
304
|
+
assert.equal(entry.message, "audit test entry");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("_resetLogs does not clear the audit base path", () => {
|
|
308
|
+
setLogBasePath(dir);
|
|
309
|
+
_resetLogs();
|
|
310
|
+
logWarning("engine", "post-reset entry");
|
|
311
|
+
|
|
312
|
+
const auditPath = join(dir, ".gsd", "audit-log.jsonl");
|
|
313
|
+
assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
|
|
314
|
+
const content = readFileSync(auditPath, "utf-8");
|
|
315
|
+
const entry = JSON.parse(content.trim());
|
|
316
|
+
assert.equal(entry.message, "post-reset entry");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
240
320
|
describe("stderr output", () => {
|
|
241
321
|
test("writes WARN prefix to stderr for warnings", (t) => {
|
|
242
322
|
const written: string[] = [];
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-preferences-sync.test.ts — Regression test for #2684.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that preferences.md is seeded into auto-mode worktrees:
|
|
5
|
+
*
|
|
6
|
+
* 1. copyPlanningArtifacts() copies preferences.md on initial worktree creation
|
|
7
|
+
* 2. syncGsdStateToWorktree() forward-syncs preferences.md (additive only)
|
|
8
|
+
* 3. syncWorktreeStateBack() does NOT overwrite project root preferences.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import {
|
|
14
|
+
existsSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
syncGsdStateToWorktree,
|
|
26
|
+
syncWorktreeStateBack,
|
|
27
|
+
} from "../auto-worktree.ts";
|
|
28
|
+
|
|
29
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function makeTempDir(prefix: string): string {
|
|
32
|
+
return mkdtempSync(join(tmpdir(), `gsd-prefs-test-${prefix}-`));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cleanup(...dirs: string[]): void {
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
rmSync(dir, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeFile(dir: string, relativePath: string, content: string): void {
|
|
42
|
+
const fullPath = join(dir, relativePath);
|
|
43
|
+
mkdirSync(join(fullPath, ".."), { recursive: true });
|
|
44
|
+
writeFileSync(fullPath, content, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Tests ───────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
const PREFS_CONTENT = [
|
|
50
|
+
"# Preferences",
|
|
51
|
+
"",
|
|
52
|
+
"post_unit_hooks:",
|
|
53
|
+
" - npm run lint",
|
|
54
|
+
"",
|
|
55
|
+
"skill_rules:",
|
|
56
|
+
' - use: "frontend-design"',
|
|
57
|
+
].join("\n");
|
|
58
|
+
|
|
59
|
+
test("#2684: syncGsdStateToWorktree forward-syncs preferences.md when missing from worktree", (t) => {
|
|
60
|
+
const mainBase = makeTempDir("main");
|
|
61
|
+
const wtBase = makeTempDir("wt");
|
|
62
|
+
t.after(() => cleanup(mainBase, wtBase));
|
|
63
|
+
|
|
64
|
+
// Project root has preferences.md
|
|
65
|
+
writeFile(mainBase, ".gsd/preferences.md", PREFS_CONTENT);
|
|
66
|
+
|
|
67
|
+
// Worktree has .gsd/ but no preferences.md
|
|
68
|
+
mkdirSync(join(wtBase, ".gsd"), { recursive: true });
|
|
69
|
+
|
|
70
|
+
const result = syncGsdStateToWorktree(mainBase, wtBase);
|
|
71
|
+
|
|
72
|
+
assert.ok(
|
|
73
|
+
existsSync(join(wtBase, ".gsd", "preferences.md")),
|
|
74
|
+
"preferences.md should be copied to worktree",
|
|
75
|
+
);
|
|
76
|
+
assert.equal(
|
|
77
|
+
readFileSync(join(wtBase, ".gsd", "preferences.md"), "utf-8"),
|
|
78
|
+
PREFS_CONTENT,
|
|
79
|
+
"preferences.md content should match source",
|
|
80
|
+
);
|
|
81
|
+
assert.ok(
|
|
82
|
+
result.synced.includes("preferences.md"),
|
|
83
|
+
"preferences.md should appear in synced list",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("#2684: syncGsdStateToWorktree does NOT overwrite existing worktree preferences.md", (t) => {
|
|
88
|
+
const mainBase = makeTempDir("main");
|
|
89
|
+
const wtBase = makeTempDir("wt");
|
|
90
|
+
t.after(() => cleanup(mainBase, wtBase));
|
|
91
|
+
|
|
92
|
+
const rootPrefs = "# Root preferences\nold: true";
|
|
93
|
+
const wtPrefs = "# Worktree preferences\nmodified: true";
|
|
94
|
+
|
|
95
|
+
writeFile(mainBase, ".gsd/preferences.md", rootPrefs);
|
|
96
|
+
writeFile(wtBase, ".gsd/preferences.md", wtPrefs);
|
|
97
|
+
|
|
98
|
+
syncGsdStateToWorktree(mainBase, wtBase);
|
|
99
|
+
|
|
100
|
+
assert.equal(
|
|
101
|
+
readFileSync(join(wtBase, ".gsd", "preferences.md"), "utf-8"),
|
|
102
|
+
wtPrefs,
|
|
103
|
+
"existing worktree preferences.md must not be overwritten",
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("#2684: syncWorktreeStateBack does NOT overwrite project root preferences.md", (t) => {
|
|
108
|
+
const mainBase = makeTempDir("main");
|
|
109
|
+
const wtBase = makeTempDir("wt");
|
|
110
|
+
const mid = "M001";
|
|
111
|
+
t.after(() => cleanup(mainBase, wtBase));
|
|
112
|
+
|
|
113
|
+
const rootPrefs = "# Root preferences\nauthoritative: true";
|
|
114
|
+
const wtPrefs = "# Worktree preferences\nstale-copy: true";
|
|
115
|
+
|
|
116
|
+
writeFile(mainBase, ".gsd/preferences.md", rootPrefs);
|
|
117
|
+
writeFile(wtBase, ".gsd/preferences.md", wtPrefs);
|
|
118
|
+
|
|
119
|
+
// Worktree needs at least a milestone dir for the function to proceed
|
|
120
|
+
mkdirSync(join(wtBase, ".gsd", "milestones", mid), { recursive: true });
|
|
121
|
+
mkdirSync(join(mainBase, ".gsd", "milestones"), { recursive: true });
|
|
122
|
+
|
|
123
|
+
syncWorktreeStateBack(mainBase, wtBase, mid);
|
|
124
|
+
|
|
125
|
+
assert.equal(
|
|
126
|
+
readFileSync(join(mainBase, ".gsd", "preferences.md"), "utf-8"),
|
|
127
|
+
rootPrefs,
|
|
128
|
+
"project root preferences.md must NOT be overwritten by worktree copy",
|
|
129
|
+
);
|
|
130
|
+
});
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
getMilestone,
|
|
15
15
|
getMilestoneSlices,
|
|
16
16
|
getSliceTasks,
|
|
17
|
-
|
|
17
|
+
updateMilestoneStatus,
|
|
18
18
|
} from "../gsd-db.js";
|
|
19
19
|
import { resolveMilestonePath, clearPathCache } from "../paths.js";
|
|
20
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
20
21
|
import { saveFile, clearParseCache } from "../files.js";
|
|
21
22
|
import { invalidateStateCache } from "../state.js";
|
|
22
23
|
import { renderAllProjections } from "../workflow-projections.js";
|
|
@@ -134,7 +135,7 @@ export async function handleCompleteMilestone(
|
|
|
134
135
|
guardError = `milestone not found: ${params.milestoneId}`;
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
|
-
if (milestone.status
|
|
138
|
+
if (isClosedStatus(milestone.status)) {
|
|
138
139
|
guardError = `milestone ${params.milestoneId} is already complete`;
|
|
139
140
|
return;
|
|
140
141
|
}
|
|
@@ -146,7 +147,7 @@ export async function handleCompleteMilestone(
|
|
|
146
147
|
return;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
const incompleteSlices = slices.filter(s => s.status
|
|
150
|
+
const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
|
|
150
151
|
if (incompleteSlices.length > 0) {
|
|
151
152
|
const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
|
|
152
153
|
guardError = `incomplete slices: ${incompleteIds}`;
|
|
@@ -156,7 +157,7 @@ export async function handleCompleteMilestone(
|
|
|
156
157
|
// Deep check: verify all tasks in all slices are complete
|
|
157
158
|
for (const slice of slices) {
|
|
158
159
|
const tasks = getSliceTasks(params.milestoneId, slice.id);
|
|
159
|
-
const incompleteTasks = tasks.filter(t => t.status
|
|
160
|
+
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
160
161
|
if (incompleteTasks.length > 0) {
|
|
161
162
|
const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
162
163
|
guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
|
|
@@ -165,13 +166,7 @@ export async function handleCompleteMilestone(
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
// All guards passed — perform write
|
|
168
|
-
|
|
169
|
-
adapter.prepare(
|
|
170
|
-
`UPDATE milestones SET status = 'complete', completed_at = :completed_at WHERE id = :mid`,
|
|
171
|
-
).run({
|
|
172
|
-
":completed_at": completedAt,
|
|
173
|
-
":mid": params.milestoneId,
|
|
174
|
-
});
|
|
169
|
+
updateMilestoneStatus(params.milestoneId, 'complete', completedAt);
|
|
175
170
|
});
|
|
176
171
|
|
|
177
172
|
if (guardError) {
|
|
@@ -199,12 +194,7 @@ export async function handleCompleteMilestone(
|
|
|
199
194
|
process.stderr.write(
|
|
200
195
|
`gsd-db: complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
|
|
201
196
|
);
|
|
202
|
-
|
|
203
|
-
if (rollbackAdapter) {
|
|
204
|
-
rollbackAdapter.prepare(
|
|
205
|
-
`UPDATE milestones SET status = 'active', completed_at = NULL WHERE id = :mid`,
|
|
206
|
-
).run({ ":mid": params.milestoneId });
|
|
207
|
-
}
|
|
197
|
+
updateMilestoneStatus(params.milestoneId, 'active', null);
|
|
208
198
|
invalidateStateCache();
|
|
209
199
|
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
210
200
|
}
|
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import { mkdirSync } from "node:fs";
|
|
12
12
|
|
|
13
13
|
import type { CompleteSliceParams } from "../types.js";
|
|
14
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
14
15
|
import {
|
|
15
16
|
transaction,
|
|
16
17
|
insertMilestone,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
getSliceTasks,
|
|
20
21
|
getMilestone,
|
|
21
22
|
updateSliceStatus,
|
|
22
|
-
|
|
23
|
+
setSliceSummaryMd,
|
|
23
24
|
} from "../gsd-db.js";
|
|
24
25
|
import { resolveSliceFile, resolveSlicePath, clearPathCache } from "../paths.js";
|
|
25
26
|
import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
|
|
@@ -225,13 +226,13 @@ export async function handleCompleteSlice(
|
|
|
225
226
|
// Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
|
|
226
227
|
// Only block if they exist and are closed.
|
|
227
228
|
const milestone = getMilestone(params.milestoneId);
|
|
228
|
-
if (milestone && (milestone.status
|
|
229
|
+
if (milestone && isClosedStatus(milestone.status)) {
|
|
229
230
|
guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
230
231
|
return;
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
234
|
-
if (slice && (slice.status
|
|
235
|
+
if (slice && isClosedStatus(slice.status)) {
|
|
235
236
|
guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
@@ -243,7 +244,7 @@ export async function handleCompleteSlice(
|
|
|
243
244
|
return;
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
const incompleteTasks = tasks.filter(t => t.status
|
|
247
|
+
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
247
248
|
if (incompleteTasks.length > 0) {
|
|
248
249
|
const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
|
|
249
250
|
guardError = `incomplete tasks: ${incompleteIds}`;
|
|
@@ -299,31 +300,13 @@ export async function handleCompleteSlice(
|
|
|
299
300
|
process.stderr.write(
|
|
300
301
|
`gsd-db: complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
|
|
301
302
|
);
|
|
302
|
-
|
|
303
|
-
if (rollbackAdapter) {
|
|
304
|
-
rollbackAdapter.prepare(
|
|
305
|
-
`UPDATE slices SET status = 'pending' WHERE milestone_id = :mid AND id = :sid`,
|
|
306
|
-
).run({
|
|
307
|
-
":mid": params.milestoneId,
|
|
308
|
-
":sid": params.sliceId,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
303
|
+
updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
|
|
311
304
|
invalidateStateCache();
|
|
312
305
|
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
313
306
|
}
|
|
314
307
|
|
|
315
308
|
// Store rendered markdown in DB for D004 recovery
|
|
316
|
-
|
|
317
|
-
if (adapter) {
|
|
318
|
-
adapter.prepare(
|
|
319
|
-
`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`,
|
|
320
|
-
).run({
|
|
321
|
-
":summary_md": summaryMd,
|
|
322
|
-
":uat_md": uatMd,
|
|
323
|
-
":mid": params.milestoneId,
|
|
324
|
-
":sid": params.sliceId,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
309
|
+
setSliceSummaryMd(params.milestoneId, params.sliceId, summaryMd, uatMd);
|
|
327
310
|
|
|
328
311
|
// Invalidate all caches
|
|
329
312
|
invalidateStateCache();
|
|
@@ -11,6 +11,7 @@ import { join } from "node:path";
|
|
|
11
11
|
import { mkdirSync, existsSync } from "node:fs";
|
|
12
12
|
|
|
13
13
|
import type { CompleteTaskParams } from "../types.js";
|
|
14
|
+
import { isClosedStatus } from "../status-guards.js";
|
|
14
15
|
import {
|
|
15
16
|
transaction,
|
|
16
17
|
insertMilestone,
|
|
@@ -20,7 +21,9 @@ import {
|
|
|
20
21
|
getMilestone,
|
|
21
22
|
getSlice,
|
|
22
23
|
getTask,
|
|
23
|
-
|
|
24
|
+
updateTaskStatus,
|
|
25
|
+
setTaskSummaryMd,
|
|
26
|
+
deleteVerificationEvidence,
|
|
24
27
|
} from "../gsd-db.js";
|
|
25
28
|
import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
|
|
26
29
|
import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
|
|
@@ -157,19 +160,19 @@ export async function handleCompleteTask(
|
|
|
157
160
|
// Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
|
|
158
161
|
// Only block if they exist and are closed.
|
|
159
162
|
const milestone = getMilestone(params.milestoneId);
|
|
160
|
-
if (milestone && (milestone.status
|
|
163
|
+
if (milestone && isClosedStatus(milestone.status)) {
|
|
161
164
|
guardError = `cannot complete task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
|
|
162
165
|
return;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
166
|
-
if (slice && (slice.status
|
|
169
|
+
if (slice && isClosedStatus(slice.status)) {
|
|
167
170
|
guardError = `cannot complete task in a closed slice: ${params.sliceId} (status: ${slice.status})`;
|
|
168
171
|
return;
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
|
|
172
|
-
if (existingTask && (existingTask.status
|
|
175
|
+
if (existingTask && isClosedStatus(existingTask.status)) {
|
|
173
176
|
guardError = `task ${params.taskId} is already complete — use gsd_task_reopen first if you need to redo it`;
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
@@ -248,32 +251,17 @@ export async function handleCompleteTask(
|
|
|
248
251
|
process.stderr.write(
|
|
249
252
|
`gsd-db: complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
|
|
250
253
|
);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
":mid": params.milestoneId,
|
|
257
|
-
":sid": params.sliceId,
|
|
258
|
-
":tid": params.taskId,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
254
|
+
// Delete orphaned verification_evidence rows first (FK constraint
|
|
255
|
+
// references tasks, so evidence must go before status change).
|
|
256
|
+
// Without this, retries accumulate duplicate evidence rows (#2724).
|
|
257
|
+
deleteVerificationEvidence(params.milestoneId, params.sliceId, params.taskId);
|
|
258
|
+
updateTaskStatus(params.milestoneId, params.sliceId, params.taskId, 'pending');
|
|
261
259
|
invalidateStateCache();
|
|
262
260
|
return { error: `disk render failed: ${(renderErr as Error).message}` };
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
// Store rendered markdown in DB for D004 recovery
|
|
266
|
-
|
|
267
|
-
if (adapter) {
|
|
268
|
-
adapter.prepare(
|
|
269
|
-
`UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
|
|
270
|
-
).run({
|
|
271
|
-
":md": summaryMd,
|
|
272
|
-
":mid": params.milestoneId,
|
|
273
|
-
":sid": params.sliceId,
|
|
274
|
-
":tid": params.taskId,
|
|
275
|
-
});
|
|
276
|
-
}
|
|
264
|
+
setTaskSummaryMd(params.milestoneId, params.sliceId, params.taskId, summaryMd);
|
|
277
265
|
|
|
278
266
|
// Invalidate all caches
|
|
279
267
|
invalidateStateCache();
|