gsd-pi 2.48.0 → 2.49.0-dev.9e177e9
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/dist/headless-ui.js +12 -2
- package/dist/headless.js +29 -13
- package/dist/resources/extensions/gsd/auto/infra-errors.js +1 -0
- package/dist/resources/extensions/gsd/auto/phases.js +11 -11
- package/dist/resources/extensions/gsd/auto/resolve.js +2 -2
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -2
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +8 -10
- package/dist/resources/extensions/gsd/auto-dashboard.js +6 -3
- package/dist/resources/extensions/gsd/auto-dispatch.js +34 -7
- package/dist/resources/extensions/gsd/auto-post-unit.js +34 -27
- package/dist/resources/extensions/gsd/auto-prompts.js +102 -21
- package/dist/resources/extensions/gsd/auto-recovery.js +62 -184
- package/dist/resources/extensions/gsd/auto-start.js +4 -31
- package/dist/resources/extensions/gsd/auto-timers.js +2 -2
- package/dist/resources/extensions/gsd/auto-verification.js +4 -7
- package/dist/resources/extensions/gsd/auto-worktree.js +262 -115
- package/dist/resources/extensions/gsd/auto.js +7 -5
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +89 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +8 -1
- package/dist/resources/extensions/gsd/branch-patterns.js +13 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +43 -3
- package/dist/resources/extensions/gsd/doctor-checks.js +5 -1234
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +168 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +28 -7
- package/dist/resources/extensions/gsd/doctor-git-checks.js +405 -0
- package/dist/resources/extensions/gsd/doctor-global-checks.js +74 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +600 -0
- package/dist/resources/extensions/gsd/doctor.js +9 -1
- package/dist/resources/extensions/gsd/extension-manifest.json +1 -1
- package/dist/resources/extensions/gsd/git-service.js +20 -20
- package/dist/resources/extensions/gsd/gsd-db.js +124 -1
- package/dist/resources/extensions/gsd/guided-flow-queue.js +10 -11
- package/dist/resources/extensions/gsd/markdown-renderer.js +33 -5
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/dist/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/dist/resources/extensions/gsd/repo-identity.js +29 -0
- package/dist/resources/extensions/gsd/roadmap-slices.js +2 -2
- package/dist/resources/extensions/gsd/session-forensics.js +6 -11
- package/dist/resources/extensions/gsd/session-lock.js +67 -56
- package/dist/resources/extensions/gsd/state.js +34 -7
- package/dist/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/dist/resources/extensions/gsd/templates/plan.md +16 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/dist/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -1
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -3
- package/dist/resources/extensions/gsd/verdict-parser.js +84 -0
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
- package/dist/resources/extensions/gsd/worktree.js +3 -2
- package/dist/resources/extensions/remote-questions/config.js +3 -5
- package/dist/resources/extensions/search-the-web/native-search.js +8 -3
- package/dist/resources/extensions/search-the-web/tool-search.js +19 -2
- package/dist/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_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/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 +5 -5
- 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 +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/chunks/471.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.7c75ac378de0f2b5.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-6654a8cca61a3d1c.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-0a4cd455ec4197d2.js → webpack-2473ce2c3879fff4.js} +1 -1
- 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/worktree-cli.js +1 -1
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +4 -1
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +4 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +39 -10
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +39 -8
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.js +8 -3
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +9 -2
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -32
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js +5 -0
- package/packages/pi-coding-agent/dist/modes/rpc/jsonl.js.map +1 -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 +0 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-client.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/blob-store.ts +6 -3
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +9 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +7 -32
- package/packages/pi-coding-agent/src/modes/rpc/jsonl.ts +6 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-client.ts +0 -2
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +8 -4
- package/src/resources/extensions/gsd/auto/infra-errors.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +10 -11
- package/src/resources/extensions/gsd/auto/resolve.ts +3 -3
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -2
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto/types.ts +13 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +19 -21
- package/src/resources/extensions/gsd/auto-dashboard.ts +5 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +40 -5
- package/src/resources/extensions/gsd/auto-loop.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +36 -31
- package/src/resources/extensions/gsd/auto-prompts.ts +113 -19
- package/src/resources/extensions/gsd/auto-recovery.ts +65 -199
- package/src/resources/extensions/gsd/auto-start.ts +7 -27
- package/src/resources/extensions/gsd/auto-timers.ts +2 -2
- package/src/resources/extensions/gsd/auto-verification.ts +4 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +309 -110
- package/src/resources/extensions/gsd/auto.ts +11 -10
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +93 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/branch-patterns.ts +16 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +46 -3
- package/src/resources/extensions/gsd/doctor-checks.ts +5 -1291
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +182 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +30 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +415 -0
- package/src/resources/extensions/gsd/doctor-global-checks.ts +84 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +626 -0
- package/src/resources/extensions/gsd/doctor.ts +9 -1
- package/src/resources/extensions/gsd/extension-manifest.json +1 -1
- package/src/resources/extensions/gsd/git-service.ts +19 -26
- package/src/resources/extensions/gsd/gsd-db.ts +150 -2
- package/src/resources/extensions/gsd/guided-flow-queue.ts +11 -12
- package/src/resources/extensions/gsd/markdown-renderer.ts +37 -4
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +37 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +27 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +9 -8
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/src/resources/extensions/gsd/prompts/execute-task.md +16 -13
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/prompts/gate-evaluate.md +32 -0
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +8 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/repo-identity.ts +28 -0
- package/src/resources/extensions/gsd/roadmap-slices.ts +2 -2
- package/src/resources/extensions/gsd/session-forensics.ts +6 -11
- package/src/resources/extensions/gsd/session-lock.ts +92 -64
- package/src/resources/extensions/gsd/state.ts +38 -5
- package/src/resources/extensions/gsd/templates/milestone-summary.md +8 -0
- package/src/resources/extensions/gsd/templates/plan.md +16 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +13 -0
- package/src/resources/extensions/gsd/templates/slice-summary.md +9 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +24 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +1 -81
- package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +14 -12
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +9 -12
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +65 -1
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/gate-storage.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +68 -9
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/infra-error.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +39 -0
- 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-transition-worktree.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +347 -0
- package/src/resources/extensions/gsd/tests/queue-completed-milestone-perf.test.ts +155 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +87 -15
- package/src/resources/extensions/gsd/tests/session-lock-transient-read.test.ts +223 -0
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +44 -4
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +0 -16
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +204 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -3
- package/src/resources/extensions/gsd/types.ts +30 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +95 -0
- package/src/resources/extensions/gsd/verification-gate.ts +0 -2
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- package/src/resources/extensions/gsd/worktree-resolver.ts +31 -0
- package/src/resources/extensions/gsd/worktree.ts +3 -2
- package/src/resources/extensions/remote-questions/config.ts +3 -5
- package/src/resources/extensions/search-the-web/native-search.ts +8 -3
- package/src/resources/extensions/search-the-web/tool-search.ts +22 -2
- package/src/resources/skills/github-workflows/references/gh/SKILL.md +22 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +0 -191
- package/dist/resources/extensions/gsd/resource-version.js +0 -97
- package/dist/web/standalone/.next/static/chunks/4024.11ca5c01938e5948.js +0 -9
- package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.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/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -234
- package/src/resources/extensions/gsd/resource-version.ts +0 -101
- /package/dist/web/standalone/.next/static/{zGWUKFTfjCQerNgsPpAbF → vNN0h0emdEi8l_npi8poE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{zGWUKFTfjCQerNgsPpAbF → vNN0h0emdEi8l_npi8poE}/_ssgManifest.js +0 -0
|
@@ -378,6 +378,34 @@ export function ensureGsdSymlink(projectPath: string): string {
|
|
|
378
378
|
return localGsd;
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
// Guard: If projectPath is a plain subdirectory (not a worktree) of a git
|
|
382
|
+
// repo that already has a .gsd at the git root, do not create a duplicate
|
|
383
|
+
// symlink in the subdirectory — that causes `.gsd 2` collision variants on
|
|
384
|
+
// macOS (#2380). Worktrees are excluded because they legitimately need their
|
|
385
|
+
// own .gsd symlink pointing at the shared external state dir.
|
|
386
|
+
if (!inWorktree) {
|
|
387
|
+
try {
|
|
388
|
+
const gitRoot = resolveGitRoot(projectPath);
|
|
389
|
+
const normalizedProject = canonicalizeExistingPath(projectPath);
|
|
390
|
+
const normalizedRoot = canonicalizeExistingPath(gitRoot);
|
|
391
|
+
if (normalizedProject !== normalizedRoot) {
|
|
392
|
+
const rootGsd = join(gitRoot, ".gsd");
|
|
393
|
+
if (existsSync(rootGsd)) {
|
|
394
|
+
try {
|
|
395
|
+
const rootStat = lstatSync(rootGsd);
|
|
396
|
+
if (rootStat.isSymbolicLink() || rootStat.isDirectory()) {
|
|
397
|
+
return rootStat.isSymbolicLink() ? realpathSync(rootGsd) : rootGsd;
|
|
398
|
+
}
|
|
399
|
+
} catch {
|
|
400
|
+
// Fall through to normal logic if we can't stat root .gsd
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
// If git root detection fails, fall through to normal logic
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
381
409
|
// Clean up macOS numbered collision variants (.gsd 2, .gsd 3, etc.) before
|
|
382
410
|
// any existence checks — otherwise they accumulate and confuse state (#2205).
|
|
383
411
|
cleanNumberedGsdVariants(projectPath);
|
|
@@ -41,8 +41,8 @@ export function expandDependencies(deps: string[]): string[] {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function extractSlicesSection(content: string): string {
|
|
44
|
-
// Match "## Slices", "## Slice Overview", "## Slice Table", etc.
|
|
45
|
-
const headingMatch = /^## Slice(?:s| Overview| Table| Summary| Status)\b.*$/m.exec(content);
|
|
44
|
+
// Match "## Slices", "## Slice Overview", "## Slice Table", "## Slice Roadmap", etc.
|
|
45
|
+
const headingMatch = /^## Slice(?:s| Overview| Table| Summary| Status| Roadmap)\b.*$/m.exec(content);
|
|
46
46
|
if (!headingMatch || headingMatch.index == null) return "";
|
|
47
47
|
|
|
48
48
|
const start = headingMatch.index + headingMatch[0].length;
|
|
@@ -25,7 +25,6 @@ import { truncateWithEllipsis } from "../shared/format-utils.js";
|
|
|
25
25
|
import { nativeParseJsonlTail } from "./native-parser-bridge.js";
|
|
26
26
|
import { MAX_JSONL_BYTES, parseJSONL } from "./jsonl-utils.js";
|
|
27
27
|
import { nativeWorkingTreeStatus, nativeDiffStat } from "./native-git-bridge.js";
|
|
28
|
-
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
29
28
|
|
|
30
29
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
31
30
|
|
|
@@ -295,17 +294,13 @@ export function synthesizeCrashRecovery(
|
|
|
295
294
|
* Deep diagnostic from any JSONL source (activity log or session file).
|
|
296
295
|
* Replaces the old shallow getLastActivityDiagnostic().
|
|
297
296
|
*/
|
|
298
|
-
export function getDeepDiagnostic(basePath: string): string | null {
|
|
299
|
-
// Try worktree activity logs first if
|
|
297
|
+
export function getDeepDiagnostic(basePath: string, worktreePath?: string): string | null {
|
|
298
|
+
// Try worktree activity logs first if a worktree path is provided
|
|
300
299
|
let trace: ExecutionTrace | null = null;
|
|
301
300
|
try {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (wtPath) {
|
|
306
|
-
const wtActivityDir = join(gsdRoot(wtPath), "activity");
|
|
307
|
-
trace = readLastActivityLog(wtActivityDir);
|
|
308
|
-
}
|
|
301
|
+
if (worktreePath) {
|
|
302
|
+
const wtActivityDir = join(gsdRoot(worktreePath), "activity");
|
|
303
|
+
trace = readLastActivityLog(wtActivityDir);
|
|
309
304
|
}
|
|
310
305
|
} catch { /* non-fatal — fall through to root */ }
|
|
311
306
|
|
|
@@ -323,7 +318,7 @@ export function getDeepDiagnostic(basePath: string): string | null {
|
|
|
323
318
|
* Read the active milestone ID directly from STATE.md without async deriveState().
|
|
324
319
|
* Looks for `**Active Milestone:** M001` pattern.
|
|
325
320
|
*/
|
|
326
|
-
function readActiveMilestoneId(basePath: string): string | null {
|
|
321
|
+
export function readActiveMilestoneId(basePath: string): string | null {
|
|
327
322
|
try {
|
|
328
323
|
const statePath = join(gsdRoot(basePath), "STATE.md");
|
|
329
324
|
if (!existsSync(statePath)) return null;
|
|
@@ -167,6 +167,56 @@ function ensureExitHandler(_gsdDir: string): void {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// ─── Lock Acquisition Helpers ───────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Create the onCompromised callback for proper-lockfile.
|
|
174
|
+
*
|
|
175
|
+
* proper-lockfile fires onCompromised when it detects mtime drift (system sleep,
|
|
176
|
+
* event loop stall, etc.). The default handler throws inside setTimeout — an
|
|
177
|
+
* uncaught exception that crashes or corrupts process state.
|
|
178
|
+
*
|
|
179
|
+
* False-positive suppression (#1362): If we're still within the stale window
|
|
180
|
+
* (30 min since acquisition), the mtime mismatch is from an event loop stall
|
|
181
|
+
* during a long LLM call — not a real takeover. Log and continue.
|
|
182
|
+
*
|
|
183
|
+
* PID ownership check (#1578): Past the stale window, check if the lock file
|
|
184
|
+
* still contains our PID before declaring compromise. Retry reads tolerate
|
|
185
|
+
* transient filesystem hiccups (NFS/CIFS latency, APFS snapshots, etc.) (#2324).
|
|
186
|
+
*/
|
|
187
|
+
function createLockCompromisedHandler(lockFilePath: string): () => void {
|
|
188
|
+
return () => {
|
|
189
|
+
const elapsed = Date.now() - _lockAcquiredAt;
|
|
190
|
+
if (elapsed < 1_800_000) {
|
|
191
|
+
process.stderr.write(
|
|
192
|
+
`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`,
|
|
193
|
+
);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const existing = readExistingLockDataWithRetry(lockFilePath);
|
|
197
|
+
if (existing && existing.pid === process.pid) {
|
|
198
|
+
process.stderr.write(
|
|
199
|
+
`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — lock file still owned by PID ${process.pid}, treating as false positive.\n`,
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
_lockCompromised = true;
|
|
204
|
+
_releaseFunction = null;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Assign module-level lock state after a successful lock acquisition.
|
|
210
|
+
*/
|
|
211
|
+
function assignLockState(basePath: string, release: () => void, lockFilePath: string): void {
|
|
212
|
+
_releaseFunction = release;
|
|
213
|
+
_lockedPath = basePath;
|
|
214
|
+
_lockPid = process.pid;
|
|
215
|
+
_lockCompromised = false;
|
|
216
|
+
_lockAcquiredAt = Date.now();
|
|
217
|
+
_snapshotLockPath = lockFilePath;
|
|
218
|
+
}
|
|
219
|
+
|
|
170
220
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
171
221
|
|
|
172
222
|
/**
|
|
@@ -226,43 +276,10 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
226
276
|
realpath: false,
|
|
227
277
|
stale: 1_800_000, // 30 minutes — safe for laptop sleep / long event loop stalls
|
|
228
278
|
update: 10_000, // Update lock mtime every 10s to prove liveness
|
|
229
|
-
onCompromised: ()
|
|
230
|
-
// proper-lockfile detected mtime drift (system sleep, event loop stall, etc.).
|
|
231
|
-
// Default handler throws inside setTimeout — an uncaught exception that crashes
|
|
232
|
-
// or corrupts process state.
|
|
233
|
-
//
|
|
234
|
-
// False-positive suppression (#1362): If we're still within the stale window
|
|
235
|
-
// (30 min since acquisition), the mtime mismatch is from an event loop stall
|
|
236
|
-
// during a long LLM call — not a real takeover. Log and continue.
|
|
237
|
-
const elapsed = Date.now() - _lockAcquiredAt;
|
|
238
|
-
if (elapsed < 1_800_000) {
|
|
239
|
-
process.stderr.write(
|
|
240
|
-
`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`,
|
|
241
|
-
);
|
|
242
|
-
return; // Suppress false positive
|
|
243
|
-
}
|
|
244
|
-
// Past the stale window — check if the lock file still belongs to us before
|
|
245
|
-
// declaring compromise (#1578). If our PID still owns the metadata, this is
|
|
246
|
-
// a false positive from a very long event loop stall (e.g. subagent execution).
|
|
247
|
-
const existing = readExistingLockData(lp);
|
|
248
|
-
if (existing && existing.pid === process.pid) {
|
|
249
|
-
process.stderr.write(
|
|
250
|
-
`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — lock file still owned by PID ${process.pid}, treating as false positive.\n`,
|
|
251
|
-
);
|
|
252
|
-
return; // Our PID still owns the lock file — no real takeover
|
|
253
|
-
}
|
|
254
|
-
// Lock file is gone or owned by another PID — real compromise
|
|
255
|
-
_lockCompromised = true;
|
|
256
|
-
_releaseFunction = null;
|
|
257
|
-
},
|
|
279
|
+
onCompromised: createLockCompromisedHandler(lp),
|
|
258
280
|
});
|
|
259
281
|
|
|
260
|
-
|
|
261
|
-
_lockedPath = basePath;
|
|
262
|
-
_lockPid = process.pid;
|
|
263
|
-
_lockCompromised = false;
|
|
264
|
-
_lockAcquiredAt = Date.now();
|
|
265
|
-
_snapshotLockPath = lp; // Snapshot the resolved path for consistent access (#1363)
|
|
282
|
+
assignLockState(basePath, release, lp);
|
|
266
283
|
|
|
267
284
|
// Safety net: clean up lock dir on process exit if _releaseFunction
|
|
268
285
|
// wasn't called (e.g., normal exit after clean completion) (#1245).
|
|
@@ -290,35 +307,9 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
290
307
|
realpath: false,
|
|
291
308
|
stale: 1_800_000, // 30 minutes — match primary lock settings
|
|
292
309
|
update: 10_000,
|
|
293
|
-
onCompromised: ()
|
|
294
|
-
// Same false-positive suppression as the primary lock (#1512).
|
|
295
|
-
// Without this, the retry path fires _lockCompromised unconditionally
|
|
296
|
-
// on benign mtime drift (laptop sleep, heavy LLM event loop stalls).
|
|
297
|
-
const elapsed = Date.now() - _lockAcquiredAt;
|
|
298
|
-
if (elapsed < 1_800_000) {
|
|
299
|
-
process.stderr.write(
|
|
300
|
-
`[gsd] Lock heartbeat caught up after ${Math.round(elapsed / 1000)}s — long LLM call, no action needed.\n`,
|
|
301
|
-
);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
// Check PID ownership before declaring compromise (#1578)
|
|
305
|
-
const existing = readExistingLockData(lp);
|
|
306
|
-
if (existing && existing.pid === process.pid) {
|
|
307
|
-
process.stderr.write(
|
|
308
|
-
`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — lock file still owned by PID ${process.pid}, treating as false positive.\n`,
|
|
309
|
-
);
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
_lockCompromised = true;
|
|
313
|
-
_releaseFunction = null;
|
|
314
|
-
},
|
|
310
|
+
onCompromised: createLockCompromisedHandler(lp),
|
|
315
311
|
});
|
|
316
|
-
|
|
317
|
-
_lockedPath = basePath;
|
|
318
|
-
_lockPid = process.pid;
|
|
319
|
-
_lockCompromised = false;
|
|
320
|
-
_lockAcquiredAt = Date.now();
|
|
321
|
-
_snapshotLockPath = lp; // Snapshot for retry path too (#1363)
|
|
312
|
+
assignLockState(basePath, release, lp);
|
|
322
313
|
|
|
323
314
|
// Safety net — uses centralized handler to avoid double-registration
|
|
324
315
|
ensureExitHandler(gsdDir);
|
|
@@ -413,7 +404,8 @@ export function getSessionLockStatus(basePath: string): SessionLockStatus {
|
|
|
413
404
|
// onCompromised fired from benign mtime drift (laptop sleep, event loop stall
|
|
414
405
|
// beyond the stale window). Attempt re-acquisition instead of giving up.
|
|
415
406
|
const lp = lockPath(basePath);
|
|
416
|
-
|
|
407
|
+
// Retry reads to tolerate transient filesystem hiccups (#2324).
|
|
408
|
+
const existing = readExistingLockDataWithRetry(lp);
|
|
417
409
|
if (existing && existing.pid === process.pid) {
|
|
418
410
|
// Lock file still ours — try to re-acquire the OS lock
|
|
419
411
|
try {
|
|
@@ -565,6 +557,42 @@ function readExistingLockData(lp: string): SessionLockData | null {
|
|
|
565
557
|
}
|
|
566
558
|
}
|
|
567
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Retry-tolerant variant of readExistingLockData for use in onCompromised and
|
|
562
|
+
* other paths where a transient filesystem hiccup (NFS/CIFS latency, macOS APFS
|
|
563
|
+
* snapshot, concurrent process briefly holding the file) should NOT be treated
|
|
564
|
+
* as "lock file gone" (#2324).
|
|
565
|
+
*
|
|
566
|
+
* Retries up to `maxAttempts` times with `delayMs` between each attempt.
|
|
567
|
+
* Only returns null when ALL retries fail to read valid data.
|
|
568
|
+
*/
|
|
569
|
+
export interface RetryOptions {
|
|
570
|
+
maxAttempts?: number;
|
|
571
|
+
delayMs?: number;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function readExistingLockDataWithRetry(
|
|
575
|
+
lp: string,
|
|
576
|
+
options?: RetryOptions,
|
|
577
|
+
): SessionLockData | null {
|
|
578
|
+
const maxAttempts = options?.maxAttempts ?? 3;
|
|
579
|
+
const delayMs = options?.delayMs ?? 200;
|
|
580
|
+
|
|
581
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
582
|
+
const data = readExistingLockData(lp);
|
|
583
|
+
if (data !== null) return data;
|
|
584
|
+
if (attempt < maxAttempts) {
|
|
585
|
+
// Synchronous busy-wait — onCompromised runs in a sync callback context
|
|
586
|
+
// and the delays are short (200ms default).
|
|
587
|
+
const start = Date.now();
|
|
588
|
+
while (Date.now() - start < delayMs) {
|
|
589
|
+
// busy-wait
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
|
|
568
596
|
function isPidAlive(pid: number): boolean {
|
|
569
597
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
570
598
|
if (pid === process.pid) return false;
|
|
@@ -40,6 +40,7 @@ import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-
|
|
|
40
40
|
import { join, resolve } from 'path';
|
|
41
41
|
import { existsSync, readdirSync } from 'node:fs';
|
|
42
42
|
import { debugCount, debugTime } from './debug-logger.js';
|
|
43
|
+
import { extractVerdict } from './verdict-parser.js';
|
|
43
44
|
|
|
44
45
|
import {
|
|
45
46
|
isDbAvailable,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
getSlice,
|
|
51
52
|
insertMilestone,
|
|
52
53
|
updateTaskStatus,
|
|
54
|
+
getPendingSliceGateCount,
|
|
53
55
|
type MilestoneRow,
|
|
54
56
|
type SliceRow,
|
|
55
57
|
type TaskRow,
|
|
@@ -91,11 +93,8 @@ export function isMilestoneComplete(roadmap: Roadmap): boolean {
|
|
|
91
93
|
* after remediation slices are executed.
|
|
92
94
|
*/
|
|
93
95
|
export function isValidationTerminal(validationContent: string): boolean {
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
96
|
-
const verdict = match[1].match(/verdict:\s*(\S+)/);
|
|
97
|
-
if (!verdict) return false;
|
|
98
|
-
const v = verdict[1] === 'passed' ? 'pass' : verdict[1];
|
|
96
|
+
const v = extractVerdict(validationContent);
|
|
97
|
+
if (!v) return false;
|
|
99
98
|
// 'pass' and 'needs-attention' are always terminal.
|
|
100
99
|
// 'needs-remediation' is treated as terminal to prevent infinite loops
|
|
101
100
|
// when no remediation slices exist in the roadmap (#832). The validation
|
|
@@ -711,6 +710,22 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
|
|
|
711
710
|
}
|
|
712
711
|
}
|
|
713
712
|
|
|
713
|
+
// ── Quality gate evaluation check ──────────────────────────────────
|
|
714
|
+
// If slice-scoped gates (Q3/Q4) are still pending, pause before execution
|
|
715
|
+
// so the gate-evaluate dispatch rule can run parallel sub-agents.
|
|
716
|
+
// Slices with zero gate rows (pre-feature or simple) skip straight through.
|
|
717
|
+
const pendingGateCount = getPendingSliceGateCount(activeMilestone.id, activeSlice.id);
|
|
718
|
+
if (pendingGateCount > 0) {
|
|
719
|
+
return {
|
|
720
|
+
activeMilestone, activeSlice, activeTask: null,
|
|
721
|
+
phase: 'evaluating-gates',
|
|
722
|
+
recentDecisions: [], blockers: [],
|
|
723
|
+
nextAction: `Evaluate ${pendingGateCount} quality gate(s) for ${activeSlice.id} before execution.`,
|
|
724
|
+
registry, requirements,
|
|
725
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
714
729
|
// ── Blocker detection: check completed tasks for blocker_discovered ──
|
|
715
730
|
const completedTasks = tasks.filter(t => isStatusDone(t.status));
|
|
716
731
|
let blockerTaskId: string | null = null;
|
|
@@ -1280,6 +1295,24 @@ export async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
1280
1295
|
}
|
|
1281
1296
|
|
|
1282
1297
|
const slicePlan = parsePlan(slicePlanContent);
|
|
1298
|
+
|
|
1299
|
+
// ── Reconcile stale task status for filesystem-based projects (#2514) ──
|
|
1300
|
+
// Heading-style tasks (### T01:) are always parsed as done=false by
|
|
1301
|
+
// parsePlan because the heading syntax has no checkbox. When the agent
|
|
1302
|
+
// writes a SUMMARY file but the plan's heading isn't converted to a
|
|
1303
|
+
// checkbox, the task appears incomplete forever — causing infinite
|
|
1304
|
+
// re-dispatch. Reconcile by checking SUMMARY files on disk.
|
|
1305
|
+
for (const t of slicePlan.tasks) {
|
|
1306
|
+
if (t.done) continue;
|
|
1307
|
+
const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
|
|
1308
|
+
if (summaryPath && existsSync(summaryPath)) {
|
|
1309
|
+
t.done = true;
|
|
1310
|
+
process.stderr.write(
|
|
1311
|
+
`gsd-reconcile: task ${activeMilestone.id}/${activeSlice.id}/${t.id} has SUMMARY on disk but plan shows incomplete — marking done (#2514)\n`,
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1283
1316
|
const taskProgress = {
|
|
1284
1317
|
done: slicePlan.tasks.filter(t => t.done).length,
|
|
1285
1318
|
total: slicePlan.tasks.length,
|
|
@@ -49,6 +49,14 @@ completed_at: {{date}}
|
|
|
49
49
|
|
|
50
50
|
- {{requirementId}}: {{fromStatus}} → {{toStatus}} — {{evidence}}
|
|
51
51
|
|
|
52
|
+
## Decision Re-evaluation
|
|
53
|
+
|
|
54
|
+
<!-- Review decisions from this milestone. OMIT if no decisions need re-evaluation. -->
|
|
55
|
+
|
|
56
|
+
| Decision | Original Rationale | Still Valid? | Action |
|
|
57
|
+
|----------|-------------------|-------------|--------|
|
|
58
|
+
| {{decisionId}} | {{originalRationale}} | {{yes/no/partially}} | {{keep/revise/supersede}} |
|
|
59
|
+
|
|
52
60
|
## Forward Intelligence
|
|
53
61
|
|
|
54
62
|
<!-- Write what you wish you'd known at the start of this milestone.
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
- {{mustHave}}
|
|
9
9
|
- {{mustHave}}
|
|
10
10
|
|
|
11
|
+
## Threat Surface
|
|
12
|
+
|
|
13
|
+
<!-- Q3: How can this be exploited? OMIT ENTIRELY for simple slices with no auth, user input, or data exposure. -->
|
|
14
|
+
|
|
15
|
+
- **Abuse**: {{abuseScenarios — parameter tampering, replay, privilege escalation, or N/A}}
|
|
16
|
+
- **Data exposure**: {{sensitiveDataAccessible — PII, tokens, secrets, or none}}
|
|
17
|
+
- **Input trust**: {{untrustedInput — user input reaching DB/API/filesystem, or none}}
|
|
18
|
+
|
|
19
|
+
## Requirement Impact
|
|
20
|
+
|
|
21
|
+
<!-- Q4: What existing promises does this break? OMIT ENTIRELY if no existing requirements are affected. -->
|
|
22
|
+
|
|
23
|
+
- **Requirements touched**: {{requirementIds — e.g. R001, R003, or none}}
|
|
24
|
+
- **Re-verify**: {{whatMustBeRetested — e.g. login flow, API contract, or N/A}}
|
|
25
|
+
- **Decisions revisited**: {{decisionIds — e.g. D002, or none}}
|
|
26
|
+
|
|
11
27
|
## Proof Level
|
|
12
28
|
|
|
13
29
|
<!-- Omit this section entirely for simple slices where the answer is trivially obvious. -->
|
|
@@ -92,6 +92,19 @@ This milestone is complete only when all are true:
|
|
|
92
92
|
- Each "After this" line must be truthful about proof level: if only fixtures or tests prove it, say so; do not imply the user can already perform the live end-to-end behavior unless that has actually been exercised
|
|
93
93
|
-->
|
|
94
94
|
|
|
95
|
+
## Horizontal Checklist
|
|
96
|
+
|
|
97
|
+
<!-- Cross-cutting concerns across all slices. Check each that was considered.
|
|
98
|
+
OMIT ENTIRELY for trivial milestones. -->
|
|
99
|
+
|
|
100
|
+
- [ ] Every active R### re-read against new code — still fully satisfied?
|
|
101
|
+
- [ ] Every D### from prior milestones re-evaluated — still valid at new scope?
|
|
102
|
+
- [ ] Graceful shutdown / cleanup on termination verified
|
|
103
|
+
- [ ] Revenue / billing path impact assessed (or N/A)
|
|
104
|
+
- [ ] Auth boundary documented — what's protected vs public
|
|
105
|
+
- [ ] Shared resource budget confirmed — connection pools, caches, rate limits hold under peak
|
|
106
|
+
- [ ] Reconnection / retry strategy verified for every external dependency
|
|
107
|
+
|
|
95
108
|
## Boundary Map
|
|
96
109
|
|
|
97
110
|
<!-- Be specific. Name concrete outputs: API endpoints, event payloads, shared types/interfaces,
|
|
@@ -57,6 +57,15 @@ completed_at: {{date}}
|
|
|
57
57
|
|
|
58
58
|
- {{requirementIdOr_none}} — {{what changed}}
|
|
59
59
|
|
|
60
|
+
## Operational Readiness
|
|
61
|
+
|
|
62
|
+
<!-- Q8: How will ops know it's healthy/broken? OMIT ENTIRELY for simple slices with no runtime concerns. -->
|
|
63
|
+
|
|
64
|
+
- **Health signal**: {{howToConfirmHealthy — health endpoint, heartbeat log, metric, or N/A}}
|
|
65
|
+
- **Failure signal**: {{howToDetectBroken — error rate spike, alert, log pattern, or N/A}}
|
|
66
|
+
- **Recovery**: {{selfRecoverOrRestart — auto-reconnect, circuit breaker, manual restart, or N/A}}
|
|
67
|
+
- **Monitoring gaps**: {{silentFailureModes — background jobs, cache eviction, memory pressure, or none}}
|
|
68
|
+
|
|
60
69
|
## Deviations
|
|
61
70
|
|
|
62
71
|
<!-- Deviations are unplanned changes to the written plan, not ordinary debugging inside the plan's intended scope. -->
|
|
@@ -17,6 +17,30 @@ skills_used:
|
|
|
17
17
|
|
|
18
18
|
{{description}}
|
|
19
19
|
|
|
20
|
+
## Failure Modes
|
|
21
|
+
|
|
22
|
+
<!-- Q5: What breaks when dependencies fail? OMIT ENTIRELY for tasks with no external dependencies. -->
|
|
23
|
+
|
|
24
|
+
| Dependency | On error | On timeout | On malformed response |
|
|
25
|
+
|------------|----------|-----------|----------------------|
|
|
26
|
+
| {{dependency}} | {{errorStrategy}} | {{timeoutStrategy}} | {{malformedStrategy}} |
|
|
27
|
+
|
|
28
|
+
## Load Profile
|
|
29
|
+
|
|
30
|
+
<!-- Q6: What breaks at 10x load? OMIT ENTIRELY for tasks with no shared resources or scaling concerns. -->
|
|
31
|
+
|
|
32
|
+
- **Shared resources**: {{sharedResources — DB connections, caches, rate limiters, or none}}
|
|
33
|
+
- **Per-operation cost**: {{perOpCost — N API calls, M DB queries, K bytes, or trivial}}
|
|
34
|
+
- **10x breakpoint**: {{whatBreaksFirst — pool exhaustion, rate limit, memory, or N/A}}
|
|
35
|
+
|
|
36
|
+
## Negative Tests
|
|
37
|
+
|
|
38
|
+
<!-- Q7: What negative tests prove robustness? OMIT ENTIRELY for trivial tasks. -->
|
|
39
|
+
|
|
40
|
+
- **Malformed inputs**: {{malformedInputTests — empty string, null, oversized, wrong type}}
|
|
41
|
+
- **Error paths**: {{errorPathTests — network timeout, auth failure, 5xx, invalid JSON}}
|
|
42
|
+
- **Boundary conditions**: {{boundaryTests — empty list, max length, zero, off-by-one}}
|
|
43
|
+
|
|
20
44
|
## Steps
|
|
21
45
|
|
|
22
46
|
1. {{step}}
|
|
@@ -116,7 +116,7 @@ test("auto-timers.ts idle watchdog catch calls resolveAgentEndCancelled", () =>
|
|
|
116
116
|
// Check that resolveAgentEndCancelled is called near this catch
|
|
117
117
|
const catchRegion = source.slice(Math.max(0, idleCatchIdx - 200), idleCatchIdx + 200);
|
|
118
118
|
assert.ok(
|
|
119
|
-
catchRegion.includes("resolveAgentEndCancelled(
|
|
119
|
+
catchRegion.includes("resolveAgentEndCancelled("),
|
|
120
120
|
"idle watchdog catch block must call resolveAgentEndCancelled",
|
|
121
121
|
);
|
|
122
122
|
});
|
|
@@ -129,7 +129,7 @@ test("auto-timers.ts hard timeout catch calls resolveAgentEndCancelled", () => {
|
|
|
129
129
|
assert.ok(hardCatchIdx > -1, "hard timeout catch block must exist");
|
|
130
130
|
const catchRegion = source.slice(Math.max(0, hardCatchIdx - 200), hardCatchIdx + 200);
|
|
131
131
|
assert.ok(
|
|
132
|
-
catchRegion.includes("resolveAgentEndCancelled(
|
|
132
|
+
catchRegion.includes("resolveAgentEndCancelled("),
|
|
133
133
|
"hard timeout catch block must call resolveAgentEndCancelled",
|
|
134
134
|
);
|
|
135
135
|
});
|
|
@@ -183,8 +183,8 @@ test("single milestone worktree is merged to main when all complete (#962)", (t)
|
|
|
183
183
|
"milestone branch should be deleted",
|
|
184
184
|
);
|
|
185
185
|
|
|
186
|
-
// Verify squash commit on main
|
|
187
|
-
const log = run("git log
|
|
186
|
+
// Verify squash commit on main (milestone ID is in trailer, not subject)
|
|
187
|
+
const log = run("git log -3", tempDir);
|
|
188
188
|
assert.ok(
|
|
189
189
|
log.includes("M001"),
|
|
190
190
|
"squash commit on main should reference M001",
|
|
@@ -1745,6 +1745,41 @@ test("resolveAgentEndCancelled prevents orphaned promise after abort path", asyn
|
|
|
1745
1745
|
assert.equal(result.status, "cancelled");
|
|
1746
1746
|
});
|
|
1747
1747
|
|
|
1748
|
+
test("resolveAgentEndCancelled with errorContext passes it through to resolved promise", async () => {
|
|
1749
|
+
_resetPendingResolve();
|
|
1750
|
+
|
|
1751
|
+
const { _setCurrentResolve } = await import("../auto/resolve.js");
|
|
1752
|
+
|
|
1753
|
+
const p = new Promise<UnitResult>((r) => {
|
|
1754
|
+
_setCurrentResolve(r);
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
resolveAgentEndCancelled({ message: "test timeout", category: "timeout", isTransient: true });
|
|
1758
|
+
|
|
1759
|
+
const resolved = await p;
|
|
1760
|
+
assert.equal(resolved.status, "cancelled");
|
|
1761
|
+
assert.ok(resolved.errorContext, "errorContext must be present");
|
|
1762
|
+
assert.equal(resolved.errorContext!.category, "timeout");
|
|
1763
|
+
assert.equal(resolved.errorContext!.message, "test timeout");
|
|
1764
|
+
assert.equal(resolved.errorContext!.isTransient, true);
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
test("resolveAgentEndCancelled without args produces no errorContext field", async () => {
|
|
1768
|
+
_resetPendingResolve();
|
|
1769
|
+
|
|
1770
|
+
const { _setCurrentResolve } = await import("../auto/resolve.js");
|
|
1771
|
+
|
|
1772
|
+
const p = new Promise<UnitResult>((r) => {
|
|
1773
|
+
_setCurrentResolve(r);
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
resolveAgentEndCancelled();
|
|
1777
|
+
|
|
1778
|
+
const resolved = await p;
|
|
1779
|
+
assert.equal(resolved.status, "cancelled");
|
|
1780
|
+
assert.equal(resolved.errorContext, undefined, "errorContext must not be present when no args passed");
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1748
1783
|
// ─── #1571: artifact verification retry ──────────────────────────────────────
|
|
1749
1784
|
|
|
1750
1785
|
test("autoLoop re-iterates when postUnitPreVerification returns retry (#1571)", async () => {
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
verifyExpectedArtifact,
|
|
11
11
|
diagnoseExpectedArtifact,
|
|
12
12
|
buildLoopRemediationSteps,
|
|
13
|
-
selfHealRuntimeRecords,
|
|
14
13
|
hasImplementationArtifacts,
|
|
15
14
|
} from "../auto-recovery.ts";
|
|
16
15
|
import { parseRoadmap, parsePlan } from "../parsers-legacy.ts";
|
|
@@ -112,7 +111,7 @@ test("resolveExpectedArtifactPath returns correct path for all slice-level types
|
|
|
112
111
|
|
|
113
112
|
const uatResult = resolveExpectedArtifactPath("run-uat", "M001/S01", base);
|
|
114
113
|
assert.ok(uatResult);
|
|
115
|
-
assert.ok(uatResult!.includes("UAT
|
|
114
|
+
assert.ok(uatResult!.includes("UAT"));
|
|
116
115
|
});
|
|
117
116
|
|
|
118
117
|
// ─── diagnoseExpectedArtifact ─────────────────────────────────────────────
|
|
@@ -572,85 +571,6 @@ test("verifyExpectedArtifact plan-slice fails after deleting a rendered task pla
|
|
|
572
571
|
}
|
|
573
572
|
});
|
|
574
573
|
|
|
575
|
-
// ─── selfHealRuntimeRecords — worktree base path (#769) ──────────────────
|
|
576
|
-
|
|
577
|
-
test("selfHealRuntimeRecords clears stale dispatched records (#769)", async (t) => {
|
|
578
|
-
// selfHealRuntimeRecords now only clears stale dispatched records (>1h).
|
|
579
|
-
// No completedKeySet parameter — deriveState is sole authority.
|
|
580
|
-
const worktreeBase = makeTmpBase();
|
|
581
|
-
const mainBase = makeTmpBase();
|
|
582
|
-
t.after(() => {
|
|
583
|
-
cleanup(worktreeBase);
|
|
584
|
-
cleanup(mainBase);
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
588
|
-
|
|
589
|
-
// Write a stale runtime record in the worktree .gsd/runtime/units/
|
|
590
|
-
writeUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
591
|
-
phase: "dispatched",
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
// Verify the runtime record exists before heal
|
|
595
|
-
const before = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
596
|
-
assert.ok(before, "runtime record should exist before heal");
|
|
597
|
-
|
|
598
|
-
// Mock ExtensionContext with minimal notify
|
|
599
|
-
const notifications: string[] = [];
|
|
600
|
-
const mockCtx = {
|
|
601
|
-
ui: { notify: (msg: string) => { notifications.push(msg); } },
|
|
602
|
-
} as any;
|
|
603
|
-
|
|
604
|
-
// Call selfHeal with worktreeBase — should clear the stale record
|
|
605
|
-
await selfHealRuntimeRecords(worktreeBase, mockCtx);
|
|
606
|
-
|
|
607
|
-
// The stale record should be cleared
|
|
608
|
-
const after = readUnitRuntimeRecord(worktreeBase, "run-uat", "M001/S01");
|
|
609
|
-
assert.equal(after, null, "runtime record should be cleared after heal");
|
|
610
|
-
assert.ok(notifications.some(n => n.includes("Self-heal")), "should emit self-heal notification");
|
|
611
|
-
|
|
612
|
-
// Write a stale record at mainBase
|
|
613
|
-
writeUnitRuntimeRecord(mainBase, "run-uat", "M001/S01", Date.now() - 7200_000, {
|
|
614
|
-
phase: "dispatched",
|
|
615
|
-
});
|
|
616
|
-
await selfHealRuntimeRecords(mainBase, mockCtx);
|
|
617
|
-
|
|
618
|
-
// The record at mainBase should also be cleared by the stale timeout (>1h)
|
|
619
|
-
const afterMain = readUnitRuntimeRecord(mainBase, "run-uat", "M001/S01");
|
|
620
|
-
assert.equal(afterMain, null, "stale record at main base should be cleared by timeout");
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// ─── #1625: selfHealRuntimeRecords on resume clears paused-session leftovers ──
|
|
624
|
-
|
|
625
|
-
test("selfHealRuntimeRecords clears recently-paused dispatched records on resume (#1625)", async (t) => {
|
|
626
|
-
// When pauseAuto closes out a unit but clearUnitRuntimeRecord silently fails
|
|
627
|
-
// (e.g. permission error), selfHealRuntimeRecords on resume should still
|
|
628
|
-
// clean up stale dispatched records that are >1h old.
|
|
629
|
-
const base = makeTmpBase();
|
|
630
|
-
t.after(() => cleanup(base));
|
|
631
|
-
|
|
632
|
-
const { writeUnitRuntimeRecord, readUnitRuntimeRecord } = await import("../unit-runtime.ts");
|
|
633
|
-
|
|
634
|
-
// Simulate a record left behind after a pause — aged >1h to be considered stale
|
|
635
|
-
writeUnitRuntimeRecord(base, "execute-task", "M001/S01/T01", Date.now() - 3700_000, {
|
|
636
|
-
phase: "dispatched",
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
const before = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
|
|
640
|
-
assert.ok(before, "dispatched record should exist before resume heal");
|
|
641
|
-
assert.equal(before!.phase, "dispatched");
|
|
642
|
-
|
|
643
|
-
const notifications: string[] = [];
|
|
644
|
-
const mockCtx = {
|
|
645
|
-
ui: { notify: (msg: string) => { notifications.push(msg); } },
|
|
646
|
-
} as any;
|
|
647
|
-
|
|
648
|
-
await selfHealRuntimeRecords(base, mockCtx);
|
|
649
|
-
|
|
650
|
-
const after = readUnitRuntimeRecord(base, "execute-task", "M001/S01/T01");
|
|
651
|
-
assert.equal(after, null, "stale dispatched record should be cleared on resume (#1625)");
|
|
652
|
-
});
|
|
653
|
-
|
|
654
574
|
// ─── #793: invalidateAllCaches unblocks skip-loop ─────────────────────────
|
|
655
575
|
// When the skip-loop breaker fires, it must call invalidateAllCaches() (not
|
|
656
576
|
// just invalidateStateCache()) to clear path/parse caches that deriveState
|
|
@@ -76,7 +76,7 @@ test("#2151 bug 1: auto-stash unblocks merge when unrelated files are dirty", ()
|
|
|
76
76
|
|
|
77
77
|
// Should succeed — the dirty README.md is auto-stashed before merge.
|
|
78
78
|
const result = mergeMilestoneToMain(repo, "M200", roadmap);
|
|
79
|
-
assert.ok(result.commitMessage.includes("feat(M200
|
|
79
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M200"), "merge succeeds with dirty unrelated file");
|
|
80
80
|
assert.ok(existsSync(join(repo, "stash-test.ts")), "milestone code merged to main");
|
|
81
81
|
|
|
82
82
|
// Verify the dirty file was restored (stash popped).
|