gsd-pi 2.80.0-dev.c5f2443b3 → 2.80.0-dev.e51d2c88c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/github-sync/templates.js +39 -8
- package/dist/resources/extensions/gsd/auto/loop.js +48 -10
- package/dist/resources/extensions/gsd/auto/phases.js +66 -45
- package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
- package/dist/resources/extensions/gsd/auto/run-unit.js +32 -16
- 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-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +124 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +197 -9
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
- package/dist/resources/extensions/gsd/auto.js +75 -5
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +36 -3
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
- package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
- package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/context-budget.js +37 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +22 -2
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +18 -2
- package/dist/resources/extensions/gsd/db-migration-steps.js +22 -0
- package/dist/resources/extensions/gsd/detection.js +106 -0
- package/dist/resources/extensions/gsd/graph.js +9 -3
- package/dist/resources/extensions/gsd/gsd-db.js +146 -13
- package/dist/resources/extensions/gsd/guided-flow.js +82 -16
- package/dist/resources/extensions/gsd/memory-store.js +69 -12
- package/dist/resources/extensions/gsd/migrate/command.js +40 -1
- package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
- package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
- package/dist/resources/extensions/gsd/pr-evidence.js +57 -16
- package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/dist/resources/extensions/gsd/quick.js +34 -2
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
- package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
- package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
- package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
- package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
- package/dist/resources/extensions/gsd/unit-runtime.js +22 -0
- package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
- package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
- package/dist/tsconfig.extensions.tsbuildinfo +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/onboarding/route.js +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 +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/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/package.json +12 -8
- package/packages/contracts/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +22 -17
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
- package/packages/mcp-server/src/workflow-tools.ts +30 -16
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- 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/agent-session-abort-order.test.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
- package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
- package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
- package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -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 +17 -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 +99 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.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 +24 -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 +33 -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 +17 -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 +109 -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 +69 -2
- 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 +93 -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 +26 -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/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
- package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
- package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +4 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +51 -1
- 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 +122 -17
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +99 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +92 -3
- 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 +28 -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/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
- 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 +24 -3
- 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 +0 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +21 -16
- 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 +29 -3
- 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 +23 -16
- 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/GSD-WORKFLOW.md +2 -2
- 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-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/loop.ts +67 -18
- package/src/resources/extensions/gsd/auto/phases.ts +77 -48
- package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +42 -15
- 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-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +133 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +207 -7
- package/src/resources/extensions/gsd/auto-start.ts +7 -6
- package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
- package/src/resources/extensions/gsd/auto.ts +90 -4
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +37 -2
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
- package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
- package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/context-budget.ts +44 -2
- package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +24 -1
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +19 -2
- package/src/resources/extensions/gsd/db-migration-steps.ts +25 -0
- package/src/resources/extensions/gsd/detection.ts +128 -0
- package/src/resources/extensions/gsd/graph.ts +12 -5
- package/src/resources/extensions/gsd/gsd-db.ts +168 -13
- package/src/resources/extensions/gsd/guided-flow.ts +98 -16
- package/src/resources/extensions/gsd/memory-store.ts +77 -12
- package/src/resources/extensions/gsd/migrate/command.ts +47 -1
- package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
- package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
- package/src/resources/extensions/gsd/pr-evidence.ts +63 -5
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +19 -19
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
- package/src/resources/extensions/gsd/quick.ts +37 -2
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
- 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 +155 -5
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +184 -2
- package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +14 -1
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
- package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +112 -6
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +40 -2
- 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/detection.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
- 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/graph-operations.test.ts +10 -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/journal-integration.test.ts +234 -0
- package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
- 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/prompt-path-audit.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +101 -2
- package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
- package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
- package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
- package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
- package/src/resources/extensions/gsd/unit-runtime.ts +25 -0
- package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
- package/packages/contracts/tsconfig.tsbuildinfo +0 -1
- /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → 8F5YpnZNBaooIWGF4GBV3}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → 8F5YpnZNBaooIWGF4GBV3}/_ssgManifest.js +0 -0
|
@@ -24,7 +24,7 @@ let _currentSigtermHandler = null;
|
|
|
24
24
|
*
|
|
25
25
|
* Returns the new handler so the caller can store and deregister it later.
|
|
26
26
|
*/
|
|
27
|
-
export function registerSigtermHandler(currentBasePath, previousHandler) {
|
|
27
|
+
export function registerSigtermHandler(currentBasePath, previousHandler, onSignalCleanup) {
|
|
28
28
|
// Remove the explicitly-passed previous handler
|
|
29
29
|
if (previousHandler) {
|
|
30
30
|
for (const sig of CLEANUP_SIGNALS)
|
|
@@ -37,6 +37,13 @@ export function registerSigtermHandler(currentBasePath, previousHandler) {
|
|
|
37
37
|
process.off(sig, _currentSigtermHandler);
|
|
38
38
|
}
|
|
39
39
|
const handler = () => {
|
|
40
|
+
try {
|
|
41
|
+
onSignalCleanup?.();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
void 0;
|
|
45
|
+
// Signal cleanup is best-effort; lock cleanup and process exit still run.
|
|
46
|
+
}
|
|
40
47
|
clearLock(currentBasePath);
|
|
41
48
|
releaseSessionLock(currentBasePath);
|
|
42
49
|
process.exit(0);
|
|
@@ -35,13 +35,13 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
|
|
|
35
35
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
36
36
|
recovery: status,
|
|
37
37
|
});
|
|
38
|
-
const durableComplete = status.summaryExists && status.taskChecked && status.nextActionAdvanced;
|
|
38
|
+
const durableComplete = status.dbComplete || (status.summaryExists && status.taskChecked && status.nextActionAdvanced);
|
|
39
39
|
if (durableComplete) {
|
|
40
40
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnitStartedAt, {
|
|
41
41
|
phase: "finalized",
|
|
42
42
|
recovery: status,
|
|
43
43
|
});
|
|
44
|
-
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed
|
|
44
|
+
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed. Continuing auto-mode. (attempt ${attemptNumber})`, "info");
|
|
45
45
|
unitRecoveryCount.delete(recoveryKey);
|
|
46
46
|
bumpAndResolveSynthetic(`timeout-recovery:${reason}:${unitType}/${unitId}`);
|
|
47
47
|
return "recovered";
|
|
@@ -20,7 +20,7 @@ import { gsdRoot, resolveMilestoneFile, resolveMilestonePath, resolveDir, milest
|
|
|
20
20
|
import { invalidateAllCaches } from "./cache.js";
|
|
21
21
|
import { clearActivityLogState } from "./activity-log.js";
|
|
22
22
|
import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
|
|
23
|
-
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, } from "./crash-recovery.js";
|
|
23
|
+
import { writeLock, clearLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, emitOpenUnitEndForUnit, } from "./crash-recovery.js";
|
|
24
24
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
25
25
|
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
26
26
|
import { sendDesktopNotification } from "./notifications.js";
|
|
@@ -61,6 +61,8 @@ import { isClosedStatus } from "./status-guards.js";
|
|
|
61
61
|
import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
|
|
62
62
|
import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
|
|
63
63
|
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
|
64
|
+
import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
|
|
65
|
+
import { writeUnitRuntimeRecord } from "./unit-runtime.js";
|
|
64
66
|
import { countPendingCaptures } from "./captures.js";
|
|
65
67
|
import { CMUX_CHANNELS } from "../shared/cmux-events.js";
|
|
66
68
|
import { ensureDbOpen } from "./bootstrap/dynamic-tools.js";
|
|
@@ -85,6 +87,16 @@ import { validateDirectory } from "./validate-directory.js";
|
|
|
85
87
|
import { createAutoOrchestrator } from "./auto/orchestrator.js";
|
|
86
88
|
import { WorktreeResolver, } from "./worktree-resolver.js";
|
|
87
89
|
import { reorderForCaching } from "./prompt-ordering.js";
|
|
90
|
+
import { initTokenCounter } from "./token-counter.js";
|
|
91
|
+
// Warm the tiktoken encoder at extension startup so context-budget computations
|
|
92
|
+
// can use accurate token counts via countTokensSync without paying the load
|
|
93
|
+
// cost mid-prompt-build. Fire-and-forget — failure falls back to the
|
|
94
|
+
// provider-aware char-ratio estimator already used by getCharsPerToken().
|
|
95
|
+
// Catch rejections explicitly: an unhandled rejection at module-import time
|
|
96
|
+
// can destabilize startup before the engine logger is configured.
|
|
97
|
+
void initTokenCounter().catch((err) => {
|
|
98
|
+
logWarning("engine", `token counter warm-up failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
99
|
+
});
|
|
88
100
|
export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
|
|
89
101
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
90
102
|
import { gsdHome } from "./gsd-home.js";
|
|
@@ -112,11 +124,12 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
|
112
124
|
* the DB is unavailable (e.g. fresh project before init) we skip registration
|
|
113
125
|
* silently rather than blocking session start.
|
|
114
126
|
*/
|
|
115
|
-
function registerAutoWorkerForSession(session) {
|
|
127
|
+
function registerAutoWorkerForSession(session, projectRootOverride) {
|
|
116
128
|
if (session.workerId)
|
|
117
129
|
return; // already registered (e.g. resume re-runs)
|
|
118
130
|
try {
|
|
119
|
-
const projectRootRealpath = normalizeRealPath(
|
|
131
|
+
const projectRootRealpath = normalizeRealPath(projectRootOverride
|
|
132
|
+
?? session.scope?.workspace.projectRoot
|
|
120
133
|
?? (session.originalBasePath || session.basePath));
|
|
121
134
|
session.workerId = registerAutoWorker({ projectRootRealpath });
|
|
122
135
|
}
|
|
@@ -225,8 +238,16 @@ function synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile
|
|
|
225
238
|
export function _synthesizePausedSessionRecoveryForTest(basePath, unitType, unitId, sessionFile) {
|
|
226
239
|
return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
|
|
227
240
|
}
|
|
241
|
+
const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
|
|
242
|
+
function withDetachedAutoKeepalive(run) {
|
|
243
|
+
const keepAlive = setInterval(() => { }, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
|
|
244
|
+
return run.finally(() => {
|
|
245
|
+
clearInterval(keepAlive);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
|
|
228
249
|
export function startAutoDetached(ctx, pi, base, verboseMode, options) {
|
|
229
|
-
void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
|
|
250
|
+
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
|
|
230
251
|
const message = getErrorMessage(err);
|
|
231
252
|
ctx.ui.notify(`Auto-start failed: ${message}`, "error");
|
|
232
253
|
logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
|
|
@@ -264,9 +285,50 @@ export function shouldUseWorktreeIsolation(basePath) {
|
|
|
264
285
|
*/
|
|
265
286
|
// Re-export budget utilities for external consumers
|
|
266
287
|
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, } from "./auto-budget.js";
|
|
288
|
+
function closeOutSignalInterruptedUnit(currentBasePath) {
|
|
289
|
+
const currentUnit = s.currentUnit;
|
|
290
|
+
if (!currentUnit)
|
|
291
|
+
return;
|
|
292
|
+
const reason = "Auto-mode process received a termination signal";
|
|
293
|
+
const errorContext = {
|
|
294
|
+
message: reason,
|
|
295
|
+
category: "aborted",
|
|
296
|
+
isTransient: false,
|
|
297
|
+
};
|
|
298
|
+
const basePath = s.basePath || currentBasePath;
|
|
299
|
+
try {
|
|
300
|
+
emitOpenUnitEndForUnit(basePath, currentUnit.type, currentUnit.id, "cancelled", errorContext);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
logWarning("engine", `signal unit-end cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
writeUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id, currentUnit.startedAt, {
|
|
307
|
+
phase: "crashed",
|
|
308
|
+
lastProgressAt: Date.now(),
|
|
309
|
+
lastProgressKind: "signal",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
logWarning("engine", `signal runtime cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
if (s.workerId)
|
|
317
|
+
markLatestActiveForWorkerCanceled(s.workerId, "signal-exit");
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
logWarning("engine", `signal dispatch cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
resolveAgentEndCancelled(errorContext);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
logWarning("engine", `signal resolve cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
267
329
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
268
330
|
function registerSigtermHandler(currentBasePath) {
|
|
269
|
-
s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler);
|
|
331
|
+
s.sigtermHandler = _registerSigtermHandler(currentBasePath, s.sigtermHandler, () => closeOutSignalInterruptedUnit(currentBasePath));
|
|
270
332
|
}
|
|
271
333
|
/** Wrapper: deregister SIGTERM handler and clear reference. */
|
|
272
334
|
function deregisterSigtermHandler() {
|
|
@@ -1558,6 +1620,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1558
1620
|
: new URL("../../../resource-loader.js", import.meta.url).href;
|
|
1559
1621
|
const { initResources } = await import(resourceLoaderPath);
|
|
1560
1622
|
initResources(agentDir);
|
|
1623
|
+
// initResources() uses synchronous fs APIs, so the prompt-template cache
|
|
1624
|
+
// can be primed immediately — no need for the legacy 1s setTimeout deferral.
|
|
1625
|
+
const { primeCache } = await import("./prompt-loader.js");
|
|
1626
|
+
primeCache();
|
|
1561
1627
|
// Open the project DB before rebuild/derive so resume uses DB-backed
|
|
1562
1628
|
// state instead of falling back to stale markdown parsing (#2940).
|
|
1563
1629
|
await openProjectDbIfPresent(s.basePath);
|
|
@@ -1623,6 +1689,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1623
1689
|
lockBase,
|
|
1624
1690
|
buildResolver,
|
|
1625
1691
|
};
|
|
1692
|
+
// Register the worker before bootstrap enters a milestone worktree.
|
|
1693
|
+
// This ensures enterMilestone can claim a lease and seed dispatch claims
|
|
1694
|
+
// for crash-recovery fidelity (#5405).
|
|
1695
|
+
registerAutoWorkerForSession(s, base);
|
|
1626
1696
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1627
1697
|
if (!ready)
|
|
1628
1698
|
return;
|
|
@@ -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;
|
|
@@ -112,9 +118,28 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
112
118
|
return;
|
|
113
119
|
if (!isAutoActive())
|
|
114
120
|
return;
|
|
115
|
-
if (isSessionSwitchInFlight())
|
|
116
|
-
return;
|
|
117
121
|
const lastMsg = event.messages[event.messages.length - 1];
|
|
122
|
+
if (isSessionSwitchInFlight()) {
|
|
123
|
+
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "error") {
|
|
124
|
+
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
125
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
126
|
+
resolveAgentEndCancelled({
|
|
127
|
+
message: rawErrorMsg,
|
|
128
|
+
category: "aborted",
|
|
129
|
+
isTransient: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
134
|
+
const content = "content" in lastMsg ? lastMsg.content : undefined;
|
|
135
|
+
const hasEmptyContent = Array.isArray(content) && content.length === 0;
|
|
136
|
+
const hasErrorMessage = "errorMessage" in lastMsg && !!lastMsg.errorMessage;
|
|
137
|
+
if (!hasEmptyContent || hasErrorMessage) {
|
|
138
|
+
resolveAgentEndCancelled(_buildAbortedPauseContext(lastMsg));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
118
143
|
if (lastMsg && "stopReason" in lastMsg && lastMsg.stopReason === "aborted") {
|
|
119
144
|
// Empty content with aborted stopReason is a non-fatal agent stop (the LLM
|
|
120
145
|
// chose to end without producing output). Only pause on genuine fatal aborts
|
|
@@ -150,6 +175,14 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
150
175
|
// is in the assistant message text content. Fall back to content when
|
|
151
176
|
// errorMessage looks uninformative.
|
|
152
177
|
const rawErrorMsg = ("errorMessage" in lastMsg && lastMsg.errorMessage) ? String(lastMsg.errorMessage) : "";
|
|
178
|
+
if (isUserInitiatedAbortMessage(rawErrorMsg)) {
|
|
179
|
+
resolveAgentEndCancelled({
|
|
180
|
+
message: rawErrorMsg,
|
|
181
|
+
category: "aborted",
|
|
182
|
+
isTransient: false,
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
153
186
|
const isUseless = !rawErrorMsg || /^(success|ok|true|error|unknown)$/i.test(rawErrorMsg.trim());
|
|
154
187
|
// #3588: When errorMessage is uninformative, extract the real error from
|
|
155
188
|
// the assistant message text content for display purposes only.
|
|
@@ -3,21 +3,34 @@
|
|
|
3
3
|
// Exposes the Context Mode runtime tools in-process. Default-on; opt out with
|
|
4
4
|
// `context_mode.enabled: false` in preferences.
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
6
|
+
async function loadContextModePreferences(baseDir) {
|
|
7
|
+
const [{ loadEffectiveGSDPreferences }, { logWarning }] = await Promise.all([
|
|
8
|
+
import("../preferences.js"),
|
|
9
|
+
import("../workflow-logger.js"),
|
|
10
|
+
]);
|
|
11
|
+
try {
|
|
12
|
+
return loadEffectiveGSDPreferences(baseDir)?.preferences ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
logWarning("tool", `Context Mode tool could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
6
19
|
export function registerExecTools(pi) {
|
|
7
20
|
pi.registerTool({
|
|
8
21
|
name: "gsd_exec",
|
|
9
22
|
label: "Exec (Sandboxed)",
|
|
10
|
-
description: "Run a short script (bash/node/python) in a subprocess.
|
|
23
|
+
description: "Run a short script (bash/node/python) in a subprocess. Capped stdout/stderr and metadata persist to " +
|
|
11
24
|
".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
|
|
12
25
|
"this instead of reading many files or emitting large tool outputs — e.g. have the script " +
|
|
13
26
|
"count/grep/summarize and log the finding. Enabled by default; opt out via " +
|
|
14
27
|
"preferences.context_mode.enabled=false.",
|
|
15
|
-
promptSnippet: "Run a bash/node/python script in a sandbox;
|
|
28
|
+
promptSnippet: "Run a bash/node/python script in a sandbox; capped output is saved to disk and only a digest returns",
|
|
16
29
|
promptGuidelines: [
|
|
17
30
|
"Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
|
|
18
31
|
"Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
|
|
19
32
|
"The digest is the last ~300 chars of stdout — size your log output accordingly.",
|
|
20
|
-
"Need
|
|
33
|
+
"Need persisted output? Read the stdout_path returned in details (file on local disk).",
|
|
21
34
|
],
|
|
22
35
|
parameters: Type.Object({
|
|
23
36
|
runtime: Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." }),
|
|
@@ -30,21 +43,11 @@ export function registerExecTools(pi) {
|
|
|
30
43
|
})),
|
|
31
44
|
}),
|
|
32
45
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
import("../preferences.js"),
|
|
36
|
-
import("../workflow-logger.js"),
|
|
37
|
-
]);
|
|
38
|
-
let prefs = null;
|
|
39
|
-
try {
|
|
40
|
-
prefs = loadEffectiveGSDPreferences();
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
-
}
|
|
46
|
+
const { executeGsdExec } = await import("../tools/exec-tool.js");
|
|
47
|
+
const baseDir = process.cwd();
|
|
45
48
|
return executeGsdExec(params, {
|
|
46
|
-
baseDir
|
|
47
|
-
preferences:
|
|
49
|
+
baseDir,
|
|
50
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
48
51
|
});
|
|
49
52
|
},
|
|
50
53
|
});
|
|
@@ -56,7 +59,7 @@ export function registerExecTools(pi) {
|
|
|
56
59
|
promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
|
|
57
60
|
promptGuidelines: [
|
|
58
61
|
"Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
|
|
59
|
-
"The preview shows the trailing ~300 chars of stdout; read stdout_path for
|
|
62
|
+
"The preview shows the trailing ~300 chars of stdout; read stdout_path for persisted output.",
|
|
60
63
|
],
|
|
61
64
|
parameters: Type.Object({
|
|
62
65
|
query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
|
|
@@ -68,8 +71,10 @@ export function registerExecTools(pi) {
|
|
|
68
71
|
}),
|
|
69
72
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
73
|
const { executeExecSearch } = await import("../tools/exec-search-tool.js");
|
|
74
|
+
const baseDir = process.cwd();
|
|
71
75
|
return executeExecSearch(params, {
|
|
72
|
-
baseDir
|
|
76
|
+
baseDir,
|
|
77
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
73
78
|
});
|
|
74
79
|
},
|
|
75
80
|
});
|
|
@@ -87,8 +92,10 @@ export function registerExecTools(pi) {
|
|
|
87
92
|
parameters: Type.Object({}),
|
|
88
93
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
89
94
|
const { executeResume } = await import("../tools/resume-tool.js");
|
|
95
|
+
const baseDir = process.cwd();
|
|
90
96
|
return executeResume(params, {
|
|
91
|
-
baseDir
|
|
97
|
+
baseDir,
|
|
98
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
92
99
|
});
|
|
93
100
|
},
|
|
94
101
|
});
|
|
@@ -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";
|
|
@@ -52,6 +52,25 @@ async function applyDisabledModelProviderPolicy(ctx) {
|
|
|
52
52
|
// Non-fatal: keep default provider visibility if preferences cannot be loaded.
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Bridge `context_management.compaction_threshold_percent` from GSD preferences
|
|
57
|
+
* into the agent's runtime compaction settings (#5475). The preference is
|
|
58
|
+
* validated to (0.5, 0.95) at load time, but defense-in-depth normalization
|
|
59
|
+
* here protects against a stale or hand-edited prefs file. Calling with
|
|
60
|
+
* `undefined` clears any prior override so a removed preference does not leak.
|
|
61
|
+
*/
|
|
62
|
+
async function applyCompactionThresholdOverride(ctx) {
|
|
63
|
+
try {
|
|
64
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
65
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
66
|
+
const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
|
|
67
|
+
const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
|
|
68
|
+
ctx.setCompactionThresholdOverride(value);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Non-fatal: leave any existing override in place.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
export function resolveNotificationStoreBasePath(cwd = process.cwd()) {
|
|
56
75
|
return resolveWorktreeProjectRoot(cwd);
|
|
57
76
|
}
|
|
@@ -101,6 +120,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
101
120
|
await resetAskUserQuestionsTurnCache();
|
|
102
121
|
await syncServiceTierStatus(ctx);
|
|
103
122
|
await applyDisabledModelProviderPolicy(ctx);
|
|
123
|
+
await applyCompactionThresholdOverride(ctx);
|
|
104
124
|
// Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
|
|
105
125
|
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
106
126
|
if (!isInAutoWorktree(process.cwd())) {
|
|
@@ -149,6 +169,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
149
169
|
clearDiscussionFlowState(process.cwd());
|
|
150
170
|
await syncServiceTierStatus(ctx);
|
|
151
171
|
await applyDisabledModelProviderPolicy(ctx);
|
|
172
|
+
await applyCompactionThresholdOverride(ctx);
|
|
152
173
|
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
153
174
|
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
154
175
|
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
@@ -432,6 +453,16 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
432
453
|
return planningGuard;
|
|
433
454
|
}
|
|
434
455
|
}
|
|
456
|
+
// ── Worktree-isolation write gate (#5199) ────────────────────────────
|
|
457
|
+
// Block planning-write tools from landing code at the project root when
|
|
458
|
+
// git.isolation=worktree but auto-mode hasn't created the milestone
|
|
459
|
+
// worktree yet. Without this, writes silently orphan outside git history.
|
|
460
|
+
if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
|
|
461
|
+
const wtBasePath = resolveWorktreeProjectRoot(dash.basePath ?? discussionBasePath);
|
|
462
|
+
const wtGuard = shouldBlockWorktreeWrite(event.toolName, event.input.path, wtBasePath, isAutoActive(), dash.currentUnit?.type);
|
|
463
|
+
if (wtGuard.block)
|
|
464
|
+
return wtGuard;
|
|
465
|
+
}
|
|
435
466
|
// ── Single-writer engine: block direct writes to STATE.md ──────────
|
|
436
467
|
// Covers write, edit, and bash tools to prevent bypass vectors.
|
|
437
468
|
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
|
+
}
|
|
@@ -16,6 +16,31 @@ import { execFileSync } from "node:child_process";
|
|
|
16
16
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
17
17
|
import { logWarning } from "./workflow-logger.js";
|
|
18
18
|
import { nativeHasChanges } from "./native-git-bridge.js";
|
|
19
|
+
function findPreflightStashRef(basePath, milestoneId, stashMarker) {
|
|
20
|
+
const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
|
|
21
|
+
let fallbackRef = null;
|
|
22
|
+
try {
|
|
23
|
+
const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
|
|
24
|
+
cwd: basePath,
|
|
25
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
env: GIT_NO_PROMPT_ENV,
|
|
28
|
+
});
|
|
29
|
+
for (const line of list.split("\n")) {
|
|
30
|
+
const [ref, subject] = line.split("\x00");
|
|
31
|
+
if (!ref || !subject)
|
|
32
|
+
continue;
|
|
33
|
+
if (stashMarker && subject.includes(stashMarker))
|
|
34
|
+
return ref;
|
|
35
|
+
if (!fallbackRef && subject.includes(markerPrefix))
|
|
36
|
+
fallbackRef = ref;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logWarning("preflight", `stash list failed before restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
|
+
}
|
|
42
|
+
return fallbackRef;
|
|
43
|
+
}
|
|
19
44
|
/**
|
|
20
45
|
* Check the working tree for dirty files before a milestone merge.
|
|
21
46
|
*
|
|
@@ -47,7 +72,8 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
47
72
|
notify(warnMsg, "warning");
|
|
48
73
|
// Push the stash
|
|
49
74
|
try {
|
|
50
|
-
|
|
75
|
+
const stashMarker = `gsd-preflight-stash:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
76
|
+
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd-preflight-stash [${stashMarker}]`], {
|
|
51
77
|
cwd: basePath,
|
|
52
78
|
stdio: ["ignore", "pipe", "pipe"],
|
|
53
79
|
encoding: "utf-8",
|
|
@@ -55,6 +81,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
55
81
|
});
|
|
56
82
|
return {
|
|
57
83
|
stashPushed: true,
|
|
84
|
+
stashMarker,
|
|
58
85
|
summary: `Stashed uncommitted changes before merge (milestone ${milestoneId}).`,
|
|
59
86
|
};
|
|
60
87
|
}
|
|
@@ -73,9 +100,17 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
|
|
|
73
100
|
* Any pop error (e.g. conflict) is logged and notified but does NOT throw —
|
|
74
101
|
* the merge already completed successfully.
|
|
75
102
|
*/
|
|
76
|
-
export function postflightPopStash(basePath, milestoneId, notify) {
|
|
103
|
+
export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
|
|
104
|
+
let stashRef = null;
|
|
77
105
|
try {
|
|
78
|
-
|
|
106
|
+
stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
|
|
107
|
+
if (!stashRef) {
|
|
108
|
+
const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
|
|
109
|
+
logWarning("preflight", msg);
|
|
110
|
+
notify(msg, "warning");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
execFileSync("git", ["stash", "pop", stashRef], {
|
|
79
114
|
cwd: basePath,
|
|
80
115
|
stdio: ["ignore", "pipe", "pipe"],
|
|
81
116
|
encoding: "utf-8",
|
|
@@ -86,7 +121,10 @@ export function postflightPopStash(basePath, milestoneId, notify) {
|
|
|
86
121
|
catch (err) {
|
|
87
122
|
// Pop conflicts mean the merged code collides with the stashed changes.
|
|
88
123
|
// Log a warning — the user needs to resolve manually, but the merge succeeded.
|
|
89
|
-
const
|
|
124
|
+
const restoreHint = stashRef
|
|
125
|
+
? `Run "git stash pop ${stashRef}" or "git stash apply ${stashRef}" manually to restore the correct stash.`
|
|
126
|
+
: `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
|
|
127
|
+
const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
|
|
90
128
|
logWarning("preflight", msg);
|
|
91
129
|
notify(msg, "warning");
|
|
92
130
|
}
|
|
@@ -33,5 +33,10 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
33
33
|
}
|
|
34
34
|
if (handled)
|
|
35
35
|
return;
|
|
36
|
+
if (trimmed.includes(" ")) {
|
|
37
|
+
const { handleDo } = await import("../commands-do.js");
|
|
38
|
+
await handleDo(trimmed, ctx, pi);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
37
42
|
}
|