gsd-pi 2.80.0-dev.b62439b9f → 2.80.0-dev.c5c38454b
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/{cli/cli-web-branch.d.ts → cli-web-branch.d.ts} +1 -1
- package/dist/{cli/cli-web-branch.js → cli-web-branch.js} +3 -3
- package/dist/{cli/cli.js → cli.js} +25 -25
- package/dist/{extension-runtime/extension-registry.js → extension-registry.js} +1 -1
- package/dist/{headless/headless-query.d.ts → headless-query.d.ts} +1 -1
- package/dist/{headless/headless-query.js → headless-query.js} +2 -3
- package/dist/{headless/headless-recover.js → headless-recover.js} +2 -3
- package/dist/{headless/headless.js → headless.js} +3 -3
- package/dist/loader.js +10 -10
- package/dist/{providers/models-resolver.js → models-resolver.js} +1 -1
- package/dist/{onboarding/onboarding.js → onboarding.js} +8 -8
- package/dist/{providers/remote-questions-config.js → remote-questions-config.js} +1 -1
- package/dist/{resource-runtime/resource-loader.d.ts → resource-loader.d.ts} +1 -1
- package/dist/{resource-runtime/resource-loader.js → resource-loader.js} +6 -6
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/github-sync/templates.js +39 -8
- package/dist/resources/extensions/gsd/auto/loop.js +13 -9
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -15
- package/dist/resources/extensions/gsd/auto-dashboard.js +51 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +154 -8
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +6 -4
- package/dist/resources/extensions/gsd/auto.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +15 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
- package/dist/resources/extensions/gsd/gsd-db.js +102 -2
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/pr-evidence.js +57 -16
- package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
- package/dist/{security/security-overrides.js → security-overrides.js} +1 -1
- package/dist/{startup/startup-model-validation.js → startup-model-validation.js} +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/{update/update-check.js → update-check.js} +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- 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/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
- package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
- 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/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{8336.6f6f30e410419aff.js → 8336.631939fb583761fa.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-d82dbee6356c1733.js → webpack-0481f1221120a7c6.js} +1 -1
- package/dist/{cli/web-mode.js → web-mode.js} +3 -3
- package/dist/{onboarding/welcome-screen.js → welcome-screen.js} +1 -1
- package/dist/{worktrees/worktree-cli.js → worktree-cli.js} +2 -3
- package/package.json +7 -3
- package/packages/pi-ai/dist/models/fake-model.d.ts +12 -0
- package/packages/pi-ai/dist/models/fake-model.d.ts.map +1 -0
- package/packages/pi-ai/dist/models/fake-model.js +27 -0
- package/packages/pi-ai/dist/models/fake-model.js.map +1 -0
- package/packages/pi-ai/dist/models/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/models/index.js +8 -0
- package/packages/pi-ai/dist/models/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/fake.d.ts +42 -0
- package/packages/pi-ai/dist/providers/fake.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/fake.js +319 -0
- package/packages/pi-ai/dist/providers/fake.js.map +1 -0
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +24 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/src/models/fake-model.ts +30 -0
- package/packages/pi-ai/src/models/index.ts +9 -0
- package/packages/pi-ai/src/providers/fake.ts +376 -0
- package/packages/pi-ai/src/providers/register-builtins.ts +23 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +14 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +97 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.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 +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +54 -15
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts +26 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +112 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js +51 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +10 -9
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +16 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -17
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.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 +58 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +40 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
- 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 +23 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +20 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js +79 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js +13 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +18 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts +11 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js +18 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +48 -0
- package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +51 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +108 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +16 -1
- package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +12 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +78 -15
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.test.ts +59 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +160 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +10 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +118 -17
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +43 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +73 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +25 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.test.ts +95 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +24 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.ts +13 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +32 -2
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +65 -0
- package/packages/pi-coding-agent/src/modes/interactive/tui-mode.ts +29 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/style.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/style.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/style.test.js +63 -0
- package/packages/pi-tui/dist/__tests__/style.test.js.map +1 -0
- package/packages/pi-tui/dist/__tests__/tui.test.js +1 -2
- package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
- package/packages/pi-tui/dist/index.d.ts +1 -0
- package/packages/pi-tui/dist/index.d.ts.map +1 -1
- package/packages/pi-tui/dist/index.js +2 -0
- package/packages/pi-tui/dist/index.js.map +1 -1
- package/packages/pi-tui/dist/style.d.ts +41 -0
- package/packages/pi-tui/dist/style.d.ts.map +1 -0
- package/packages/pi-tui/dist/style.js +158 -0
- package/packages/pi-tui/dist/style.js.map +1 -0
- package/packages/pi-tui/dist/tui.d.ts +1 -3
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +2 -3
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/__tests__/style.test.ts +76 -0
- package/packages/pi-tui/src/__tests__/tui.test.ts +1 -2
- package/packages/pi-tui/src/index.ts +9 -0
- package/packages/pi-tui/src/style.ts +225 -0
- package/packages/pi-tui/src/tui.ts +2 -3
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/pkg/dist/modes/interactive/theme/theme-schema.d.ts +12 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme-schema.js +13 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts +1 -1
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +18 -1
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/themes.js +36 -27
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/src/resources/extensions/github-sync/templates.ts +38 -8
- package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -10
- package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
- package/src/resources/extensions/gsd/auto-dashboard.ts +57 -8
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
- package/src/resources/extensions/gsd/auto-start.ts +7 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +6 -4
- package/src/resources/extensions/gsd/auto.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +16 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
- package/src/resources/extensions/gsd/gsd-db.ts +119 -1
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/pr-evidence.ts +63 -5
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +7 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +83 -4
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
- package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-basic.md +52 -0
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/commands-ship-empty-optionals.md +42 -0
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-no-blockers.md +55 -0
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +60 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/headless-answers.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/pr-evidence-equivalence.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/pr-evidence-hardening.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +7 -8
- package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
- package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
- package/dist/web/standalone/.next/server/chunks/7020.js +0 -12
- /package/dist/{app/app-paths.d.ts → app-paths.d.ts} +0 -0
- /package/dist/{app/app-paths.js → app-paths.js} +0 -0
- /package/dist/{extension-runtime/bundled-extension-paths.d.ts → bundled-extension-paths.d.ts} +0 -0
- /package/dist/{extension-runtime/bundled-extension-paths.js → bundled-extension-paths.js} +0 -0
- /package/dist/{extension-runtime/bundled-resource-path.d.ts → bundled-resource-path.d.ts} +0 -0
- /package/dist/{extension-runtime/bundled-resource-path.js → bundled-resource-path.js} +0 -0
- /package/dist/{startup/claude-cli-check.d.ts → claude-cli-check.d.ts} +0 -0
- /package/dist/{startup/claude-cli-check.js → claude-cli-check.js} +0 -0
- /package/dist/{cli/cli-auto-routing.d.ts → cli-auto-routing.d.ts} +0 -0
- /package/dist/{cli/cli-auto-routing.js → cli-auto-routing.js} +0 -0
- /package/dist/{cli/cli-policy.d.ts → cli-policy.d.ts} +0 -0
- /package/dist/{cli/cli-policy.js → cli-policy.js} +0 -0
- /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
- /package/dist/{extension-runtime/extension-discovery.d.ts → extension-discovery.d.ts} +0 -0
- /package/dist/{extension-runtime/extension-discovery.js → extension-discovery.js} +0 -0
- /package/dist/{extension-runtime/extension-registry.d.ts → extension-registry.d.ts} +0 -0
- /package/dist/{extension-runtime/extension-sort.d.ts → extension-sort.d.ts} +0 -0
- /package/dist/{extension-runtime/extension-sort.js → extension-sort.js} +0 -0
- /package/dist/{extension-runtime/extension-validator.d.ts → extension-validator.d.ts} +0 -0
- /package/dist/{extension-runtime/extension-validator.js → extension-validator.js} +0 -0
- /package/dist/{headless/headless-answers.d.ts → headless-answers.d.ts} +0 -0
- /package/dist/{headless/headless-answers.js → headless-answers.js} +0 -0
- /package/dist/{headless/headless-context.d.ts → headless-context.d.ts} +0 -0
- /package/dist/{headless/headless-context.js → headless-context.js} +0 -0
- /package/dist/{headless/headless-events.d.ts → headless-events.d.ts} +0 -0
- /package/dist/{headless/headless-events.js → headless-events.js} +0 -0
- /package/dist/{headless/headless-recover.d.ts → headless-recover.d.ts} +0 -0
- /package/dist/{headless/headless-types.d.ts → headless-types.d.ts} +0 -0
- /package/dist/{headless/headless-types.js → headless-types.js} +0 -0
- /package/dist/{headless/headless-ui.d.ts → headless-ui.d.ts} +0 -0
- /package/dist/{headless/headless-ui.js → headless-ui.js} +0 -0
- /package/dist/{headless/headless.d.ts → headless.d.ts} +0 -0
- /package/dist/{cli/help-text.d.ts → help-text.d.ts} +0 -0
- /package/dist/{cli/help-text.js → help-text.js} +0 -0
- /package/dist/{cli/logo.d.ts → logo.d.ts} +0 -0
- /package/dist/{cli/logo.js → logo.js} +0 -0
- /package/dist/{mcp/mcp-server.d.ts → mcp-server.d.ts} +0 -0
- /package/dist/{mcp/mcp-server.js → mcp-server.js} +0 -0
- /package/dist/{providers/models-resolver.d.ts → models-resolver.d.ts} +0 -0
- /package/dist/{onboarding/onboarding.d.ts → onboarding.d.ts} +0 -0
- /package/dist/{providers/pi-migration.d.ts → pi-migration.d.ts} +0 -0
- /package/dist/{providers/pi-migration.js → pi-migration.js} +0 -0
- /package/dist/{app/project-sessions.d.ts → project-sessions.d.ts} +0 -0
- /package/dist/{app/project-sessions.js → project-sessions.js} +0 -0
- /package/dist/{providers/provider-migrations.d.ts → provider-migrations.d.ts} +0 -0
- /package/dist/{providers/provider-migrations.js → provider-migrations.js} +0 -0
- /package/dist/{providers/remote-questions-config.d.ts → remote-questions-config.d.ts} +0 -0
- /package/dist/{rtk/rtk-shared.d.ts → rtk-shared.d.ts} +0 -0
- /package/dist/{rtk/rtk-shared.js → rtk-shared.js} +0 -0
- /package/dist/{rtk/rtk.d.ts → rtk.d.ts} +0 -0
- /package/dist/{rtk/rtk.js → rtk.js} +0 -0
- /package/dist/{startup/runtime-checks.d.ts → runtime-checks.d.ts} +0 -0
- /package/dist/{startup/runtime-checks.js → runtime-checks.js} +0 -0
- /package/dist/{security/security-overrides.d.ts → security-overrides.d.ts} +0 -0
- /package/dist/{startup/startup-model-validation.d.ts → startup-model-validation.d.ts} +0 -0
- /package/dist/{startup/startup-timings.d.ts → startup-timings.d.ts} +0 -0
- /package/dist/{startup/startup-timings.js → startup-timings.js} +0 -0
- /package/dist/{app/tool-bootstrap.d.ts → tool-bootstrap.d.ts} +0 -0
- /package/dist/{app/tool-bootstrap.js → tool-bootstrap.js} +0 -0
- /package/dist/{update/update-check.d.ts → update-check.d.ts} +0 -0
- /package/dist/{update/update-cmd.d.ts → update-cmd.d.ts} +0 -0
- /package/dist/{update/update-cmd.js → update-cmd.js} +0 -0
- /package/dist/web/standalone/.next/static/{-WDXZiiJHGZDbYH75Mu6f → TCSim36ZpcPu2WgeoC45g}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-WDXZiiJHGZDbYH75Mu6f → TCSim36ZpcPu2WgeoC45g}/_ssgManifest.js +0 -0
- /package/dist/{cli/web-mode.d.ts → web-mode.d.ts} +0 -0
- /package/dist/{onboarding/welcome-screen.d.ts → welcome-screen.d.ts} +0 -0
- /package/dist/{onboarding/wizard.d.ts → wizard.d.ts} +0 -0
- /package/dist/{onboarding/wizard.js → wizard.js} +0 -0
- /package/dist/{worktrees/worktree-cli.d.ts → worktree-cli.d.ts} +0 -0
- /package/dist/{worktrees/worktree-name-gen.d.ts → worktree-name-gen.d.ts} +0 -0
- /package/dist/{worktrees/worktree-name-gen.js → worktree-name-gen.js} +0 -0
- /package/dist/{worktrees/worktree-status-banner.d.ts → worktree-status-banner.d.ts} +0 -0
- /package/dist/{worktrees/worktree-status-banner.js → worktree-status-banner.js} +0 -0
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Auto-mode Dashboard — progress widget rendering, elapsed time formatting,
|
|
3
|
-
* unit description helpers, and slice progress caching.
|
|
4
|
-
*
|
|
5
|
-
* Pure functions that accept specific parameters — no module-level globals
|
|
6
|
-
* or AutoContext dependency. State accessors are passed as callbacks.
|
|
7
|
-
*/
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/auto-dashboard.ts - Auto-mode progress widget rendering and dashboard helpers.
|
|
8
2
|
import { getCurrentBranch } from "./worktree.js";
|
|
9
3
|
import { getActiveHook } from "./post-unit-hooks.js";
|
|
10
4
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
@@ -25,6 +19,7 @@ import { formatRtkSavingsLabel, getRtkSessionSavings, } from "../shared/rtk-sess
|
|
|
25
19
|
import { logWarning } from "./workflow-logger.js";
|
|
26
20
|
import { formattedShortcutPair } from "./shortcut-defs.js";
|
|
27
21
|
import { homedir } from "node:os";
|
|
22
|
+
import { readUnitRuntimeRecord } from "./unit-runtime.js";
|
|
28
23
|
// ─── UAT Slice Extraction ─────────────────────────────────────────────────────
|
|
29
24
|
/**
|
|
30
25
|
* Extract the target slice ID from a run-uat unit ID (e.g. "M001/S01" → "S01").
|
|
@@ -165,6 +160,30 @@ export function formatWidgetTokens(count) {
|
|
|
165
160
|
return `${(count / 1000000).toFixed(1)}M`;
|
|
166
161
|
return `${Math.round(count / 1000000)}M`;
|
|
167
162
|
}
|
|
163
|
+
export function formatRuntimeHealthSignal(record, now = Date.now()) {
|
|
164
|
+
if (!record)
|
|
165
|
+
return null;
|
|
166
|
+
const idleMs = Math.max(0, now - record.lastProgressAt);
|
|
167
|
+
const idleMinutes = Math.floor(idleMs / 60_000);
|
|
168
|
+
if ((record.recoveryAttempts ?? 0) > 0 || record.phase === "recovered" || record.lastProgressKind.includes("recovery")) {
|
|
169
|
+
return {
|
|
170
|
+
level: "yellow",
|
|
171
|
+
summary: "Recovering",
|
|
172
|
+
detail: `retry ${record.recoveryAttempts ?? 1} after ${record.lastRecoveryReason ?? "idle"} stall`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (record.progressCount === 0 && idleMs >= 60_000) {
|
|
176
|
+
return {
|
|
177
|
+
level: "yellow",
|
|
178
|
+
summary: "Waiting on provider",
|
|
179
|
+
detail: `no output for ${idleMinutes}m`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
export function shouldRenderRoadmapProgress(progress) {
|
|
185
|
+
return !!progress && progress.total > 0;
|
|
186
|
+
}
|
|
168
187
|
// ─── ETA Estimation ──────────────────────────────────────────────────────────
|
|
169
188
|
/**
|
|
170
189
|
* Estimate remaining time based on average unit duration from the metrics ledger.
|
|
@@ -499,6 +518,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
499
518
|
let cachedLines;
|
|
500
519
|
let cachedWidth;
|
|
501
520
|
let cachedRtkLabel;
|
|
521
|
+
let cachedRuntimeRecord = null;
|
|
502
522
|
const refreshRtkLabel = () => {
|
|
503
523
|
try {
|
|
504
524
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
@@ -510,7 +530,16 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
510
530
|
cachedRtkLabel = null;
|
|
511
531
|
}
|
|
512
532
|
};
|
|
533
|
+
const refreshRuntimeRecord = () => {
|
|
534
|
+
try {
|
|
535
|
+
cachedRuntimeRecord = readUnitRuntimeRecord(accessors.getBasePath(), unitType, unitId);
|
|
536
|
+
}
|
|
537
|
+
catch {
|
|
538
|
+
cachedRuntimeRecord = null;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
513
541
|
refreshRtkLabel();
|
|
542
|
+
refreshRuntimeRecord();
|
|
514
543
|
const pulseTimer = setInterval(() => {
|
|
515
544
|
pulseBright = !pulseBright;
|
|
516
545
|
cachedLines = undefined;
|
|
@@ -526,6 +555,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
526
555
|
updateSliceProgressCache(accessors.getBasePath(), mid.id, slice?.id);
|
|
527
556
|
}
|
|
528
557
|
refreshRtkLabel();
|
|
558
|
+
refreshRuntimeRecord();
|
|
529
559
|
cachedLines = undefined;
|
|
530
560
|
}
|
|
531
561
|
catch (err) { /* non-fatal */
|
|
@@ -555,13 +585,16 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
555
585
|
const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
|
|
556
586
|
// Health indicator in header
|
|
557
587
|
const score = computeProgressScore();
|
|
558
|
-
const
|
|
559
|
-
|
|
588
|
+
const runtimeSignal = formatRuntimeHealthSignal(cachedRuntimeRecord);
|
|
589
|
+
const healthLevel = runtimeSignal?.level ?? score.level;
|
|
590
|
+
const healthSummary = runtimeSignal?.summary ?? score.summary;
|
|
591
|
+
const healthColor = healthLevel === "green" ? "success"
|
|
592
|
+
: healthLevel === "yellow" ? "warning"
|
|
560
593
|
: "error";
|
|
561
|
-
const healthIcon =
|
|
562
|
-
:
|
|
594
|
+
const healthIcon = healthLevel === "green" ? GLYPH.statusActive
|
|
595
|
+
: healthLevel === "yellow" ? "!"
|
|
563
596
|
: "x";
|
|
564
|
-
const healthStr = ` ${theme.fg(healthColor, healthIcon)} ${theme.fg(healthColor,
|
|
597
|
+
const healthStr = ` ${theme.fg(healthColor, healthIcon)} ${theme.fg(healthColor, healthSummary)}`;
|
|
565
598
|
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}${healthStr}`;
|
|
566
599
|
// ETA in header right, after elapsed
|
|
567
600
|
const eta = estimateTimeRemaining();
|
|
@@ -573,7 +606,10 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
573
606
|
: "";
|
|
574
607
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
575
608
|
// Show health signal details when degraded (yellow/red)
|
|
576
|
-
if (
|
|
609
|
+
if (runtimeSignal?.detail && widgetMode !== "min") {
|
|
610
|
+
lines.push(`${pad} ${theme.fg("dim", runtimeSignal.detail)}`);
|
|
611
|
+
}
|
|
612
|
+
else if (score.level !== "green" && score.signals.length > 0 && widgetMode !== "min") {
|
|
577
613
|
// Show up to 3 most relevant signals in compact form
|
|
578
614
|
const topSignals = score.signals
|
|
579
615
|
.filter(s => s.kind === "negative")
|
|
@@ -645,7 +681,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
645
681
|
lines.push(rightAlign(actionLeft, theme.fg("dim", phaseLabel), width));
|
|
646
682
|
// Progress bar
|
|
647
683
|
const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
|
|
648
|
-
if (roadmapSlices) {
|
|
684
|
+
if (shouldRenderRoadmapProgress(roadmapSlices)) {
|
|
649
685
|
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
650
686
|
const barWidth = Math.max(6, Math.min(18, Math.floor(width * 0.25)));
|
|
651
687
|
const pct = total > 0 ? done / total : 0;
|
|
@@ -706,7 +742,7 @@ export function updateProgressWidget(ctx, unitType, unitId, state, accessors, ti
|
|
|
706
742
|
? Math.floor(width * (width >= 100 ? 0.45 : 0.50))
|
|
707
743
|
: width;
|
|
708
744
|
const leftLines = [];
|
|
709
|
-
if (roadmapSlices) {
|
|
745
|
+
if (shouldRenderRoadmapProgress(roadmapSlices)) {
|
|
710
746
|
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
711
747
|
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
712
748
|
const pct = total > 0 ? done / total : 0;
|
|
@@ -93,6 +93,16 @@ export function hasPendingDeepStage(prefs, basePath) {
|
|
|
93
93
|
const gate = getDeepStageGate(prefs, basePath);
|
|
94
94
|
return gate.status === "pending" || gate.status === "blocked";
|
|
95
95
|
}
|
|
96
|
+
export function shouldRunDeepProjectSetup(state, prefs, basePath, options = {}) {
|
|
97
|
+
if (options.hasSurvivorBranch === true)
|
|
98
|
+
return false;
|
|
99
|
+
if (state.phase !== "pre-planning" &&
|
|
100
|
+
state.phase !== "needs-discussion" &&
|
|
101
|
+
state.phase !== "planning") {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return hasPendingDeepStage(prefs, basePath);
|
|
105
|
+
}
|
|
96
106
|
function missingSliceStop(mid, phase) {
|
|
97
107
|
return {
|
|
98
108
|
action: "stop",
|
|
@@ -12,7 +12,7 @@ import { appendEvent } from "./workflow-events.js";
|
|
|
12
12
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
13
|
import { clearParseCache } from "./files.js";
|
|
14
14
|
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
|
15
|
-
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
|
|
15
|
+
import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
|
|
16
16
|
import { isValidationTerminal } from "./state.js";
|
|
17
17
|
import { getErrorMessage } from "./error-utils.js";
|
|
18
18
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
@@ -110,17 +110,32 @@ export function hasImplementationArtifacts(basePath, milestoneId) {
|
|
|
110
110
|
// milestone commits instead of treating the self-diff as proof of no work.
|
|
111
111
|
if (changedFiles.length === 0) {
|
|
112
112
|
if (milestoneId && currentBranch === integrationBranch) {
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
113
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
114
|
+
if (!milestoneEvidence.ok)
|
|
115
115
|
return "unknown";
|
|
116
|
-
if (
|
|
117
|
-
return classifyImplementationFiles(
|
|
116
|
+
if (milestoneEvidence.matched)
|
|
117
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
118
118
|
}
|
|
119
119
|
if (currentBranch && currentBranch !== "HEAD")
|
|
120
120
|
return "absent";
|
|
121
121
|
return "unknown";
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
const branchClassification = classifyImplementationFiles(changedFiles);
|
|
124
|
+
if (branchClassification === "present")
|
|
125
|
+
return "present";
|
|
126
|
+
// A completing milestone branch can have a non-empty diff containing only
|
|
127
|
+
// .gsd/ closeout files after implementation commits already landed on the
|
|
128
|
+
// recorded integration branch. In that topology, the branch diff alone is
|
|
129
|
+
// insufficient; use the same milestone-tagged evidence fallback as the
|
|
130
|
+
// self-diff retry path before declaring the milestone implementation-free.
|
|
131
|
+
if (milestoneId) {
|
|
132
|
+
const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
|
|
133
|
+
if (!milestoneEvidence.ok)
|
|
134
|
+
return "unknown";
|
|
135
|
+
if (milestoneEvidence.matched)
|
|
136
|
+
return classifyImplementationFiles(milestoneEvidence.files);
|
|
137
|
+
}
|
|
138
|
+
return "absent";
|
|
124
139
|
}
|
|
125
140
|
catch (e) {
|
|
126
141
|
// Non-fatal — if git operations fail, return unknown so callers can decide
|
|
@@ -148,6 +163,9 @@ function classifyImplementationFiles(files) {
|
|
|
148
163
|
function isImplementationPath(file) {
|
|
149
164
|
return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
|
|
150
165
|
}
|
|
166
|
+
function normalizeRepoPath(file) {
|
|
167
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
168
|
+
}
|
|
151
169
|
/**
|
|
152
170
|
* Detect the main/master branch name.
|
|
153
171
|
*/
|
|
@@ -217,7 +235,7 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
217
235
|
]);
|
|
218
236
|
if (!scoped.ok)
|
|
219
237
|
return scoped;
|
|
220
|
-
if (scoped.matched)
|
|
238
|
+
if (scoped.matched && classifyImplementationFiles(scoped.files) === "present")
|
|
221
239
|
return scoped;
|
|
222
240
|
// Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
|
|
223
241
|
// path-scoped scan matches no commits even though GSD-tagged commits
|
|
@@ -228,9 +246,137 @@ function getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId) {
|
|
|
228
246
|
// Intentionally unbounded — symmetric with the primary scan, and avoids
|
|
229
247
|
// reintroducing the rolling-depth failure class removed in #4699 where
|
|
230
248
|
// milestone evidence aged out behind unrelated activity.
|
|
231
|
-
|
|
249
|
+
const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
|
|
232
250
|
"log", "--format=%H%x1f%B%x1e", "HEAD",
|
|
233
251
|
]);
|
|
252
|
+
if (!unscoped.ok)
|
|
253
|
+
return scoped.matched ? scoped : unscoped;
|
|
254
|
+
if (!unscoped.matched)
|
|
255
|
+
return scoped;
|
|
256
|
+
return {
|
|
257
|
+
ok: true,
|
|
258
|
+
matched: true,
|
|
259
|
+
files: [...new Set([...scoped.files, ...unscoped.files])],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function getChangedFilesFromMilestoneEvidence(basePath, milestoneId) {
|
|
263
|
+
const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
|
|
264
|
+
if (!tagged.ok)
|
|
265
|
+
return tagged;
|
|
266
|
+
if (tagged.matched && classifyImplementationFiles(tagged.files) === "present")
|
|
267
|
+
return tagged;
|
|
268
|
+
const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
|
|
269
|
+
if (!attributed.ok)
|
|
270
|
+
return tagged.matched ? tagged : attributed;
|
|
271
|
+
if (attributed.matched && classifyImplementationFiles(attributed.files) === "present")
|
|
272
|
+
return attributed;
|
|
273
|
+
const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
|
|
274
|
+
if (!backfilled.ok)
|
|
275
|
+
return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
|
|
276
|
+
if (!backfilled.matched) {
|
|
277
|
+
if (tagged.matched)
|
|
278
|
+
return tagged;
|
|
279
|
+
return attributed.matched ? attributed : backfilled;
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
ok: true,
|
|
283
|
+
matched: true,
|
|
284
|
+
files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId) {
|
|
288
|
+
try {
|
|
289
|
+
const shas = getMilestoneCommitAttributionShas(milestoneId);
|
|
290
|
+
if (shas.length === 0)
|
|
291
|
+
return { ok: true, matched: false, files: [] };
|
|
292
|
+
const files = new Set();
|
|
293
|
+
let matched = false;
|
|
294
|
+
for (const sha of shas) {
|
|
295
|
+
if (!isFullCommitSha(sha))
|
|
296
|
+
continue;
|
|
297
|
+
const commitFiles = getChangedFilesForCommit(basePath, sha);
|
|
298
|
+
if (commitFiles.length === 0)
|
|
299
|
+
continue;
|
|
300
|
+
matched = true;
|
|
301
|
+
for (const file of commitFiles)
|
|
302
|
+
files.add(file);
|
|
303
|
+
}
|
|
304
|
+
return { ok: true, matched, files: [...files] };
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
logWarning("recovery", `milestone attribution scan failed: ${e.message}`);
|
|
308
|
+
return { ok: false, matched: false, files: [] };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId) {
|
|
312
|
+
try {
|
|
313
|
+
const milestone = getMilestone(milestoneId);
|
|
314
|
+
const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
|
|
315
|
+
if (!Number.isFinite(milestoneStartedAt))
|
|
316
|
+
return { ok: true, matched: false, files: [] };
|
|
317
|
+
const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
|
|
318
|
+
if (taskFileHints.length === 0)
|
|
319
|
+
return { ok: true, matched: false, files: [] };
|
|
320
|
+
const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
|
|
321
|
+
if (hintSet.size === 0)
|
|
322
|
+
return { ok: true, matched: false, files: [] };
|
|
323
|
+
const records = getCommitRecords(basePath);
|
|
324
|
+
const files = new Set();
|
|
325
|
+
let matched = false;
|
|
326
|
+
for (const record of records) {
|
|
327
|
+
if (!isFullCommitSha(record.hash))
|
|
328
|
+
continue;
|
|
329
|
+
if (Date.parse(record.committedAt) < milestoneStartedAt)
|
|
330
|
+
continue;
|
|
331
|
+
if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1)
|
|
332
|
+
continue;
|
|
333
|
+
if (commitMessageHasGsdTrailer(record.message))
|
|
334
|
+
continue;
|
|
335
|
+
const commitFiles = getChangedFilesForCommit(basePath, record.hash);
|
|
336
|
+
const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
|
|
337
|
+
if (implementationFiles.length === 0)
|
|
338
|
+
continue;
|
|
339
|
+
if (!implementationFiles.some((file) => hintSet.has(file)))
|
|
340
|
+
continue;
|
|
341
|
+
matched = true;
|
|
342
|
+
for (const file of implementationFiles)
|
|
343
|
+
files.add(file);
|
|
344
|
+
recordMilestoneCommitAttribution({
|
|
345
|
+
commitSha: record.hash,
|
|
346
|
+
milestoneId,
|
|
347
|
+
source: "backfill",
|
|
348
|
+
confidence: 0.8,
|
|
349
|
+
files: implementationFiles,
|
|
350
|
+
createdAt: new Date().toISOString(),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return { ok: true, matched, files: [...files] };
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
logWarning("recovery", `milestone attribution backfill failed: ${e.message}`);
|
|
357
|
+
return { ok: false, matched: false, files: [] };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function getCommitRecords(basePath) {
|
|
361
|
+
const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
|
|
362
|
+
cwd: basePath,
|
|
363
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
364
|
+
encoding: "utf-8",
|
|
365
|
+
});
|
|
366
|
+
return logOutput
|
|
367
|
+
.split("\x1e")
|
|
368
|
+
.map((record) => record.trim())
|
|
369
|
+
.filter(Boolean)
|
|
370
|
+
.flatMap((record) => {
|
|
371
|
+
const parts = record.split("\x1f");
|
|
372
|
+
if (parts.length < 4)
|
|
373
|
+
return [];
|
|
374
|
+
const [hash, parents, committedAt, ...messageParts] = parts;
|
|
375
|
+
return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function isFullCommitSha(value) {
|
|
379
|
+
return /^[0-9a-f]{40}$/i.test(value);
|
|
234
380
|
}
|
|
235
381
|
function scanGsdTaggedCommits(basePath, milestoneId, gitArgs) {
|
|
236
382
|
try {
|
|
@@ -487,9 +487,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
487
487
|
hasSurvivorBranch = false;
|
|
488
488
|
}
|
|
489
489
|
const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
: false;
|
|
490
|
+
const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
|
|
491
|
+
const deepProjectStagePending = shouldRunDeepProjectSetup(state, effectivePrefs, base, { hasSurvivorBranch });
|
|
493
492
|
if (deepProjectStagePending) {
|
|
494
493
|
// Deep project-level setup runs before the first milestone exists. Let
|
|
495
494
|
// the auto loop dispatch workflow-preferences / project / requirements
|
|
@@ -212,14 +212,16 @@ function gitPathspecForWorktreePath(basePath, targetPath) {
|
|
|
212
212
|
try {
|
|
213
213
|
base = realpathSync.native(basePath);
|
|
214
214
|
}
|
|
215
|
-
catch
|
|
216
|
-
|
|
215
|
+
catch {
|
|
216
|
+
/* keep original */
|
|
217
|
+
void base;
|
|
217
218
|
}
|
|
218
219
|
try {
|
|
219
220
|
target = realpathSync.native(targetPath);
|
|
220
221
|
}
|
|
221
|
-
catch
|
|
222
|
-
|
|
222
|
+
catch {
|
|
223
|
+
/* keep original */
|
|
224
|
+
void target;
|
|
223
225
|
}
|
|
224
226
|
const rel = relative(base, target);
|
|
225
227
|
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
@@ -1547,15 +1547,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1547
1547
|
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
1548
1548
|
// bundled extension updates before resume-time verification/state logic runs.
|
|
1549
1549
|
// GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
|
|
1550
|
-
// The relative import ("../../../resource-
|
|
1550
|
+
// The relative import ("../../../resource-loader.js") only works from the source
|
|
1551
1551
|
// tree; deployed extensions live at ~/.gsd/agent/extensions/gsd/ where the
|
|
1552
1552
|
// relative path resolves to ~/.gsd/agent/resource-loader.js which doesn't exist.
|
|
1553
1553
|
// Using GSD_PKG_ROOT constructs a correct absolute path in both contexts (#3949).
|
|
1554
1554
|
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome(), "agent");
|
|
1555
1555
|
const pkgRoot = process.env.GSD_PKG_ROOT;
|
|
1556
1556
|
const resourceLoaderPath = pkgRoot
|
|
1557
|
-
? pathToFileURL(join(pkgRoot, "dist", "resource-
|
|
1558
|
-
: new URL("../../../resource-
|
|
1557
|
+
? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href
|
|
1558
|
+
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1559
1559
|
const { initResources } = await import(resourceLoaderPath);
|
|
1560
1560
|
initResources(agentDir);
|
|
1561
1561
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
// GSD-2 + src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts - Handles provider and agent-end recovery for GSD auto-mode.
|
|
1
2
|
import { logWarning } from "../workflow-logger.js";
|
|
2
3
|
import { checkDeepProjectSetupAfterTurn, checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
|
|
3
4
|
import { clearPathCache } from "../paths.js";
|
|
4
5
|
import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
|
|
5
6
|
import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
|
|
6
7
|
import { pauseAutoForProviderError } from "../provider-error-pause.js";
|
|
7
|
-
import { isSessionSwitchInFlight, resolveAgentEnd } from "../auto/resolve.js";
|
|
8
|
+
import { isSessionSwitchInFlight, resolveAgentEnd, resolveAgentEndCancelled } from "../auto/resolve.js";
|
|
8
9
|
import { resolveModelId } from "../auto-model-selection.js";
|
|
9
10
|
import { resolveProjectRoot } from "../worktree.js";
|
|
10
11
|
import { clearDiscussionFlowState } from "./write-gate.js";
|
|
@@ -47,6 +48,11 @@ export function _buildAbortedPauseContext(lastMsg) {
|
|
|
47
48
|
isTransient: true,
|
|
48
49
|
};
|
|
49
50
|
}
|
|
51
|
+
export function isUserInitiatedAbortMessage(message) {
|
|
52
|
+
if (!message)
|
|
53
|
+
return false;
|
|
54
|
+
return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
|
|
55
|
+
}
|
|
50
56
|
async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
|
|
51
57
|
retryState.consecutiveTransientCount += 1;
|
|
52
58
|
const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
|
|
@@ -150,6 +156,14 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
150
156
|
// is in the assistant message text content. Fall back to content when
|
|
151
157
|
// errorMessage looks uninformative.
|
|
152
158
|
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
159
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
160
|
+
resolveAgentEndCancelled({
|
|
161
|
+
message: rawErrorMsg,
|
|
162
|
+
category: "aborted",
|
|
163
|
+
isTransient: false,
|
|
164
|
+
});
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
153
167
|
const isUseless = !rawErrorMsg || /^(success|ok|true|error|unknown)$/i.test(rawErrorMsg.trim());
|
|
154
168
|
// #3588: When errorMessage is uninformative, extract the real error from
|
|
155
169
|
// the assistant message text content for display purposes only.
|
|
@@ -2,7 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
3
3
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
4
4
|
import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
5
|
-
import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
5
|
+
import { canonicalToolName, clearDiscussionFlowState, isDepthConfirmationAnswer, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
6
6
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
7
7
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
8
8
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
@@ -432,6 +432,16 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
432
432
|
return planningGuard;
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
|
+
// ── Worktree-isolation write gate (#5199) ────────────────────────────
|
|
436
|
+
// Block planning-write tools from landing code at the project root when
|
|
437
|
+
// git.isolation=worktree but auto-mode hasn't created the milestone
|
|
438
|
+
// worktree yet. Without this, writes silently orphan outside git history.
|
|
439
|
+
if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
|
|
440
|
+
const wtBasePath = resolveWorktreeProjectRoot(dash.basePath ?? discussionBasePath);
|
|
441
|
+
const wtGuard = shouldBlockWorktreeWrite(event.toolName, event.input.path, wtBasePath, isAutoActive(), dash.currentUnit?.type);
|
|
442
|
+
if (wtGuard.block)
|
|
443
|
+
return wtGuard;
|
|
444
|
+
}
|
|
435
445
|
// ── Single-writer engine: block direct writes to STATE.md ──────────
|
|
436
446
|
// Covers write, edit, and bash tools to prevent bypass vectors.
|
|
437
447
|
if (isToolCallEventType("write", event)) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { minimatch } from "minimatch";
|
|
4
|
+
import { getIsolationMode } from "../preferences.js";
|
|
4
5
|
import { logWarning } from "../workflow-logger.js";
|
|
6
|
+
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
5
7
|
/**
|
|
6
8
|
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
7
9
|
* and unique M001-abc123 formats. Exported so regex-hardening tests
|
|
@@ -728,3 +730,129 @@ export function shouldBlockPlanningUnit(toolName, pathOrCommand, basePath, unitT
|
|
|
728
730
|
// avoids breaking gsd_* MCP tools or future safe additions.
|
|
729
731
|
return { block: false };
|
|
730
732
|
}
|
|
733
|
+
// ─── Worktree isolation write gate (#5199) ────────────────────────────────
|
|
734
|
+
//
|
|
735
|
+
// When `git.isolation: worktree` is configured, the per-unit commit pipeline
|
|
736
|
+
// only runs inside the auto-mode loop (`auto-post-unit.ts`). If the LLM
|
|
737
|
+
// authors code at the project root before auto-mode is started, those writes
|
|
738
|
+
// land in the working tree but never reach a commit — they're silently
|
|
739
|
+
// orphaned outside git history. This guard blocks those writes at the
|
|
740
|
+
// tool_call seam so the agent receives a clear error instead.
|
|
741
|
+
const WORKTREE_GATE_BOOTSTRAP_UNITS = new Set([
|
|
742
|
+
"discuss-milestone",
|
|
743
|
+
"plan-milestone",
|
|
744
|
+
"init",
|
|
745
|
+
]);
|
|
746
|
+
function realpathOrResolve(p) {
|
|
747
|
+
const abs = resolve(p);
|
|
748
|
+
try {
|
|
749
|
+
return realpathSync(abs);
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
// Path doesn't exist (yet) — realpath the deepest existing ancestor so
|
|
753
|
+
// platforms where /tmp -> /private/tmp don't break containment checks.
|
|
754
|
+
let dir = abs;
|
|
755
|
+
const tail = [];
|
|
756
|
+
while (dir && dir !== resolve(dir, "..")) {
|
|
757
|
+
try {
|
|
758
|
+
const real = realpathSync(dir);
|
|
759
|
+
return tail.length ? join(real, ...tail.reverse()) : real;
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
const idx = dir.lastIndexOf(sep);
|
|
763
|
+
if (idx <= 0)
|
|
764
|
+
break;
|
|
765
|
+
tail.push(dir.slice(idx + 1));
|
|
766
|
+
dir = dir.slice(0, idx) || sep;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return abs;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function isPathContained(target, container) {
|
|
773
|
+
if (target === container)
|
|
774
|
+
return true;
|
|
775
|
+
return target.startsWith(container.endsWith(sep) ? container : container + sep);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Block planning-write tool calls that would land code at the project root
|
|
779
|
+
* while `git.isolation: worktree` is in effect and auto-mode hasn't created
|
|
780
|
+
* (or flipped cwd into) the milestone worktree.
|
|
781
|
+
*
|
|
782
|
+
* Pure / unit-testable. Callers in `register-hooks.ts` supply the resolved
|
|
783
|
+
* project root and current auto liveness; this function does no I/O beyond
|
|
784
|
+
* realpath resolution.
|
|
785
|
+
*
|
|
786
|
+
* Allow rules (in order):
|
|
787
|
+
* 1. Tool isn't a planning-write (write/edit/multi_edit/notebook_edit).
|
|
788
|
+
* 2. `GSD_DISABLE_WORKTREE_WRITE_GUARD=1` self-hosting bypass.
|
|
789
|
+
* 3. Isolation mode is not "worktree".
|
|
790
|
+
* 4. Active unit is a bootstrap unit (discuss-milestone/plan-milestone/init).
|
|
791
|
+
* 5. Target is inside `<projectRoot>/.gsd/worktrees/` (a real worktree).
|
|
792
|
+
* 6. Target is inside `<projectRoot>/.gsd/` and isn't masquerading as a
|
|
793
|
+
* worktrees sibling (rejects the `.gsd/worktrees-extra/…` prefix trick).
|
|
794
|
+
* 7. Auto is live AND `effectiveBasePath` is itself a `.gsd/worktrees/…` path.
|
|
795
|
+
*
|
|
796
|
+
* Otherwise: block with a message that points the agent at `/gsd` to start
|
|
797
|
+
* auto-mode.
|
|
798
|
+
*/
|
|
799
|
+
export function shouldBlockWorktreeWrite(toolName, targetPath, effectiveBasePath, isAutoLive, currentUnitType) {
|
|
800
|
+
const tool = canonicalToolName(toolName);
|
|
801
|
+
if (!PLANNING_WRITE_TOOLS.has(tool))
|
|
802
|
+
return { block: false };
|
|
803
|
+
if (process.env.GSD_DISABLE_WORKTREE_WRITE_GUARD === "1")
|
|
804
|
+
return { block: false };
|
|
805
|
+
if (getIsolationMode(effectiveBasePath) !== "worktree")
|
|
806
|
+
return { block: false };
|
|
807
|
+
if (currentUnitType && WORKTREE_GATE_BOOTSTRAP_UNITS.has(currentUnitType))
|
|
808
|
+
return { block: false };
|
|
809
|
+
if (!targetPath) {
|
|
810
|
+
return {
|
|
811
|
+
block: true,
|
|
812
|
+
reason: [
|
|
813
|
+
`HARD BLOCK: ${tool} called with empty path while \`git.isolation: worktree\` is configured`,
|
|
814
|
+
`and auto-mode is not active. Refusing to allow writes that cannot be located.`,
|
|
815
|
+
].join(" "),
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
// Resolve the target relative to the project root, then realpath to defeat
|
|
819
|
+
// symlink-based escapes and prefix tricks (e.g. .gsd/worktrees-extra/).
|
|
820
|
+
const projectRoot = resolveWorktreeProjectRoot(effectiveBasePath);
|
|
821
|
+
const absTarget = isAbsolute(targetPath) ? targetPath : resolve(projectRoot, targetPath);
|
|
822
|
+
const realTarget = realpathOrResolve(absTarget);
|
|
823
|
+
const realRoot = realpathOrResolve(projectRoot);
|
|
824
|
+
const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
|
|
825
|
+
const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
|
|
826
|
+
// Allow writes inside the legitimate worktrees subtree.
|
|
827
|
+
if (isPathContained(realTarget, realWorktreesDir))
|
|
828
|
+
return { block: false };
|
|
829
|
+
// Allow writes to .gsd/ planning artifacts, but reject siblings whose name
|
|
830
|
+
// starts with "worktrees" (the worktrees-extra prefix trick — case 4).
|
|
831
|
+
if (isPathContained(realTarget, realGsd)) {
|
|
832
|
+
const rel = relative(realGsd, realTarget);
|
|
833
|
+
const firstSeg = rel.split(/[\/\\]/)[0] ?? "";
|
|
834
|
+
if (!firstSeg.startsWith("worktrees"))
|
|
835
|
+
return { block: false };
|
|
836
|
+
// fall through: looks like worktrees<something> sibling — block
|
|
837
|
+
}
|
|
838
|
+
// Auto is live and the caller is operating inside a worktree path —
|
|
839
|
+
// host tool's write happens in worktree context; let it through.
|
|
840
|
+
if (isAutoLive && isGsdWorktreePath(effectiveBasePath))
|
|
841
|
+
return { block: false };
|
|
842
|
+
// Block. Provide enough context that the agent can self-correct.
|
|
843
|
+
const displayTarget = isPathContained(realTarget, realRoot)
|
|
844
|
+
? relative(realRoot, realTarget) || "."
|
|
845
|
+
: realTarget;
|
|
846
|
+
return {
|
|
847
|
+
block: true,
|
|
848
|
+
reason: [
|
|
849
|
+
`HARD BLOCK: Worktree isolation is configured (\`git.isolation: worktree\`) but auto-mode is`,
|
|
850
|
+
`not running and the target "${displayTarget}" is not inside \`.gsd/worktrees/<MID>/\`.`,
|
|
851
|
+
`Code edits at the project root would be lost — only the auto-mode commit pipeline`,
|
|
852
|
+
`(auto-post-unit) commits work, and it never runs outside the loop.`,
|
|
853
|
+
`Required action: start auto-mode with \`/gsd\` so the milestone worktree is created,`,
|
|
854
|
+
`then write inside it. To disable this guard for self-hosting development, set`,
|
|
855
|
+
`GSD_DISABLE_WORKTREE_WRITE_GUARD=1.`,
|
|
856
|
+
].join(" "),
|
|
857
|
+
};
|
|
858
|
+
}
|