gsd-pi 2.80.0-dev.c5f2443b3 → 2.80.0-dev.e146beb20
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/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 +37 -30
- 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-post-unit.js +10 -10
- package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
- 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.js +9 -1
- 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/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/crash-recovery.js +56 -10
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +22 -2
- 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/detection.js +106 -0
- package/dist/resources/extensions/gsd/graph.js +9 -3
- package/dist/resources/extensions/gsd/gsd-db.js +102 -2
- package/dist/resources/extensions/gsd/guided-flow.js +49 -12
- 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/prompts/complete-milestone.md +7 -8
- 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/safety/evidence-collector.js +10 -2
- 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/unit-runtime.js +11 -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 +16 -16
- 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 +16 -16
- 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 +10 -6
- package/packages/contracts/package.json +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/chat-controller-ordering.test.js +74 -0
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- 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 +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 +60 -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/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/chat-controller-ordering.test.ts +87 -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 +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 +75 -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/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 +3 -8
- 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 +3 -8
- 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 +42 -28
- 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-post-unit.ts +10 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
- 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.ts +12 -1
- 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/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/crash-recovery.ts +67 -10
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +24 -1
- 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/detection.ts +128 -0
- package/src/resources/extensions/gsd/graph.ts +12 -5
- package/src/resources/extensions/gsd/gsd-db.ts +119 -1
- package/src/resources/extensions/gsd/guided-flow.ts +49 -12
- 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/prompts/complete-milestone.md +7 -8
- 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/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 +84 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
- 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/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/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/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/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/smart-entry-complete.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- 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/plan-slice.ts +13 -0
- package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -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 → y73quA-XdLo9n41nxphjW}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{bQDK5_LtkGVS64AirQgQG → y73quA-XdLo9n41nxphjW}/_ssgManifest.js +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
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
|
|
|
4
4
|
import { minimatch } from "minimatch";
|
|
5
5
|
|
|
6
|
+
import { getIsolationMode } from "../preferences.js";
|
|
6
7
|
import type { ToolsPolicy } from "../unit-context-manifest.js";
|
|
7
8
|
import { logWarning } from "../workflow-logger.js";
|
|
9
|
+
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
@@ -882,3 +884,135 @@ export function shouldBlockPlanningUnit(
|
|
|
882
884
|
// avoids breaking gsd_* MCP tools or future safe additions.
|
|
883
885
|
return { block: false };
|
|
884
886
|
}
|
|
887
|
+
|
|
888
|
+
// ─── Worktree isolation write gate (#5199) ────────────────────────────────
|
|
889
|
+
//
|
|
890
|
+
// When `git.isolation: worktree` is configured, the per-unit commit pipeline
|
|
891
|
+
// only runs inside the auto-mode loop (`auto-post-unit.ts`). If the LLM
|
|
892
|
+
// authors code at the project root before auto-mode is started, those writes
|
|
893
|
+
// land in the working tree but never reach a commit — they're silently
|
|
894
|
+
// orphaned outside git history. This guard blocks those writes at the
|
|
895
|
+
// tool_call seam so the agent receives a clear error instead.
|
|
896
|
+
|
|
897
|
+
const WORKTREE_GATE_BOOTSTRAP_UNITS = new Set([
|
|
898
|
+
"discuss-milestone",
|
|
899
|
+
"plan-milestone",
|
|
900
|
+
"init",
|
|
901
|
+
]);
|
|
902
|
+
|
|
903
|
+
function realpathOrResolve(p: string): string {
|
|
904
|
+
const abs = resolve(p);
|
|
905
|
+
try {
|
|
906
|
+
return realpathSync(abs);
|
|
907
|
+
} catch {
|
|
908
|
+
// Path doesn't exist (yet) — realpath the deepest existing ancestor so
|
|
909
|
+
// platforms where /tmp -> /private/tmp don't break containment checks.
|
|
910
|
+
let dir = abs;
|
|
911
|
+
const tail: string[] = [];
|
|
912
|
+
while (dir && dir !== resolve(dir, "..")) {
|
|
913
|
+
try {
|
|
914
|
+
const real = realpathSync(dir);
|
|
915
|
+
return tail.length ? join(real, ...tail.reverse()) : real;
|
|
916
|
+
} catch {
|
|
917
|
+
const idx = dir.lastIndexOf(sep);
|
|
918
|
+
if (idx <= 0) break;
|
|
919
|
+
tail.push(dir.slice(idx + 1));
|
|
920
|
+
dir = dir.slice(0, idx) || sep;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return abs;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function isPathContained(target: string, container: string): boolean {
|
|
928
|
+
if (target === container) return true;
|
|
929
|
+
return target.startsWith(container.endsWith(sep) ? container : container + sep);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Block planning-write tool calls that would land code at the project root
|
|
934
|
+
* while `git.isolation: worktree` is in effect and auto-mode hasn't created
|
|
935
|
+
* (or flipped cwd into) the milestone worktree.
|
|
936
|
+
*
|
|
937
|
+
* Pure / unit-testable. Callers in `register-hooks.ts` supply the resolved
|
|
938
|
+
* project root and current auto liveness; this function does no I/O beyond
|
|
939
|
+
* realpath resolution.
|
|
940
|
+
*
|
|
941
|
+
* Allow rules (in order):
|
|
942
|
+
* 1. Tool isn't a planning-write (write/edit/multi_edit/notebook_edit).
|
|
943
|
+
* 2. `GSD_DISABLE_WORKTREE_WRITE_GUARD=1` self-hosting bypass.
|
|
944
|
+
* 3. Isolation mode is not "worktree".
|
|
945
|
+
* 4. Active unit is a bootstrap unit (discuss-milestone/plan-milestone/init).
|
|
946
|
+
* 5. Target is inside `<projectRoot>/.gsd/worktrees/` (a real worktree).
|
|
947
|
+
* 6. Target is inside `<projectRoot>/.gsd/` and isn't masquerading as a
|
|
948
|
+
* worktrees sibling (rejects the `.gsd/worktrees-extra/…` prefix trick).
|
|
949
|
+
* 7. Auto is live AND `effectiveBasePath` is itself a `.gsd/worktrees/…` path.
|
|
950
|
+
*
|
|
951
|
+
* Otherwise: block with a message that points the agent at `/gsd` to start
|
|
952
|
+
* auto-mode.
|
|
953
|
+
*/
|
|
954
|
+
export function shouldBlockWorktreeWrite(
|
|
955
|
+
toolName: string,
|
|
956
|
+
targetPath: string,
|
|
957
|
+
effectiveBasePath: string,
|
|
958
|
+
isAutoLive: boolean,
|
|
959
|
+
currentUnitType?: string | null,
|
|
960
|
+
): { block: boolean; reason?: string } {
|
|
961
|
+
const tool = canonicalToolName(toolName);
|
|
962
|
+
if (!PLANNING_WRITE_TOOLS.has(tool)) return { block: false };
|
|
963
|
+
if (process.env.GSD_DISABLE_WORKTREE_WRITE_GUARD === "1") return { block: false };
|
|
964
|
+
if (getIsolationMode(effectiveBasePath) !== "worktree") return { block: false };
|
|
965
|
+
if (currentUnitType && WORKTREE_GATE_BOOTSTRAP_UNITS.has(currentUnitType)) return { block: false };
|
|
966
|
+
|
|
967
|
+
if (!targetPath) {
|
|
968
|
+
return {
|
|
969
|
+
block: true,
|
|
970
|
+
reason: [
|
|
971
|
+
`HARD BLOCK: ${tool} called with empty path while \`git.isolation: worktree\` is configured`,
|
|
972
|
+
`and auto-mode is not active. Refusing to allow writes that cannot be located.`,
|
|
973
|
+
].join(" "),
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Resolve the target relative to the project root, then realpath to defeat
|
|
978
|
+
// symlink-based escapes and prefix tricks (e.g. .gsd/worktrees-extra/).
|
|
979
|
+
const projectRoot = resolveWorktreeProjectRoot(effectiveBasePath);
|
|
980
|
+
const absTarget = isAbsolute(targetPath) ? targetPath : resolve(projectRoot, targetPath);
|
|
981
|
+
const realTarget = realpathOrResolve(absTarget);
|
|
982
|
+
const realRoot = realpathOrResolve(projectRoot);
|
|
983
|
+
const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
|
|
984
|
+
const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
|
|
985
|
+
|
|
986
|
+
// Allow writes inside the legitimate worktrees subtree.
|
|
987
|
+
if (isPathContained(realTarget, realWorktreesDir)) return { block: false };
|
|
988
|
+
|
|
989
|
+
// Allow writes to .gsd/ planning artifacts, but reject siblings whose name
|
|
990
|
+
// starts with "worktrees" (the worktrees-extra prefix trick — case 4).
|
|
991
|
+
if (isPathContained(realTarget, realGsd)) {
|
|
992
|
+
const rel = relative(realGsd, realTarget);
|
|
993
|
+
const firstSeg = rel.split(/[\/\\]/)[0] ?? "";
|
|
994
|
+
if (!firstSeg.startsWith("worktrees")) return { block: false };
|
|
995
|
+
// fall through: looks like worktrees<something> sibling — block
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Auto is live and the caller is operating inside a worktree path —
|
|
999
|
+
// host tool's write happens in worktree context; let it through.
|
|
1000
|
+
if (isAutoLive && isGsdWorktreePath(effectiveBasePath)) return { block: false };
|
|
1001
|
+
|
|
1002
|
+
// Block. Provide enough context that the agent can self-correct.
|
|
1003
|
+
const displayTarget = isPathContained(realTarget, realRoot)
|
|
1004
|
+
? relative(realRoot, realTarget) || "."
|
|
1005
|
+
: realTarget;
|
|
1006
|
+
return {
|
|
1007
|
+
block: true,
|
|
1008
|
+
reason: [
|
|
1009
|
+
`HARD BLOCK: Worktree isolation is configured (\`git.isolation: worktree\`) but auto-mode is`,
|
|
1010
|
+
`not running and the target "${displayTarget}" is not inside \`.gsd/worktrees/<MID>/\`.`,
|
|
1011
|
+
`Code edits at the project root would be lost — only the auto-mode commit pipeline`,
|
|
1012
|
+
`(auto-post-unit) commits work, and it never runs outside the loop.`,
|
|
1013
|
+
`Required action: start auto-mode with \`/gsd\` so the milestone worktree is created,`,
|
|
1014
|
+
`then write inside it. To disable this guard for self-hosting development, set`,
|
|
1015
|
+
`GSD_DISABLE_WORKTREE_WRITE_GUARD=1.`,
|
|
1016
|
+
].join(" "),
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
@@ -21,10 +21,34 @@ import { nativeHasChanges } from "./native-git-bridge.js";
|
|
|
21
21
|
export interface PreflightResult {
|
|
22
22
|
/** true when a stash was pushed and postflightPopStash should be called */
|
|
23
23
|
stashPushed: boolean;
|
|
24
|
+
/** Unique marker embedded in the stash message for targeted restoration */
|
|
25
|
+
stashMarker?: string;
|
|
24
26
|
/** human-readable summary of what happened (empty string for clean trees) */
|
|
25
27
|
summary: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
function findPreflightStashRef(basePath: string, milestoneId: string, stashMarker?: string): string | null {
|
|
31
|
+
const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
|
|
32
|
+
let fallbackRef: string | null = null;
|
|
33
|
+
try {
|
|
34
|
+
const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
|
|
35
|
+
cwd: basePath,
|
|
36
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
37
|
+
encoding: "utf-8",
|
|
38
|
+
env: GIT_NO_PROMPT_ENV,
|
|
39
|
+
});
|
|
40
|
+
for (const line of list.split("\n")) {
|
|
41
|
+
const [ref, subject] = line.split("\x00");
|
|
42
|
+
if (!ref || !subject) continue;
|
|
43
|
+
if (stashMarker && subject.includes(stashMarker)) return ref;
|
|
44
|
+
if (!fallbackRef && subject.includes(markerPrefix)) fallbackRef = ref;
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
logWarning("preflight", `stash list failed before restore: ${err instanceof Error ? err.message : String(err)}`);
|
|
48
|
+
}
|
|
49
|
+
return fallbackRef;
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
/**
|
|
29
53
|
* Check the working tree for dirty files before a milestone merge.
|
|
30
54
|
*
|
|
@@ -62,7 +86,8 @@ export function preflightCleanRoot(
|
|
|
62
86
|
|
|
63
87
|
// Push the stash
|
|
64
88
|
try {
|
|
65
|
-
|
|
89
|
+
const stashMarker = `gsd-preflight-stash:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
90
|
+
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd-preflight-stash [${stashMarker}]`], {
|
|
66
91
|
cwd: basePath,
|
|
67
92
|
stdio: ["ignore", "pipe", "pipe"],
|
|
68
93
|
encoding: "utf-8",
|
|
@@ -70,6 +95,7 @@ export function preflightCleanRoot(
|
|
|
70
95
|
});
|
|
71
96
|
return {
|
|
72
97
|
stashPushed: true,
|
|
98
|
+
stashMarker,
|
|
73
99
|
summary: `Stashed uncommitted changes before merge (milestone ${milestoneId}).`,
|
|
74
100
|
};
|
|
75
101
|
} catch (err) {
|
|
@@ -91,10 +117,19 @@ export function preflightCleanRoot(
|
|
|
91
117
|
export function postflightPopStash(
|
|
92
118
|
basePath: string,
|
|
93
119
|
milestoneId: string,
|
|
120
|
+
stashMarker: string | undefined,
|
|
94
121
|
notify: (message: string, level: "info" | "warning" | "error") => void,
|
|
95
122
|
): void {
|
|
123
|
+
let stashRef: string | null = null;
|
|
96
124
|
try {
|
|
97
|
-
|
|
125
|
+
stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
|
|
126
|
+
if (!stashRef) {
|
|
127
|
+
const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
|
|
128
|
+
logWarning("preflight", msg);
|
|
129
|
+
notify(msg, "warning");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
execFileSync("git", ["stash", "pop", stashRef], {
|
|
98
133
|
cwd: basePath,
|
|
99
134
|
stdio: ["ignore", "pipe", "pipe"],
|
|
100
135
|
encoding: "utf-8",
|
|
@@ -104,7 +139,10 @@ export function postflightPopStash(
|
|
|
104
139
|
} catch (err) {
|
|
105
140
|
// Pop conflicts mean the merged code collides with the stashed changes.
|
|
106
141
|
// Log a warning — the user needs to resolve manually, but the merge succeeded.
|
|
107
|
-
const
|
|
142
|
+
const restoreHint = stashRef
|
|
143
|
+
? `Run "git stash pop ${stashRef}" or "git stash apply ${stashRef}" manually to restore the correct stash.`
|
|
144
|
+
: `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
|
|
145
|
+
const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
|
|
108
146
|
logWarning("preflight", msg);
|
|
109
147
|
notify(msg, "warning");
|
|
110
148
|
}
|
|
@@ -45,5 +45,11 @@ export async function handleGSDCommand(
|
|
|
45
45
|
|
|
46
46
|
if (handled) return;
|
|
47
47
|
|
|
48
|
+
if (trimmed.includes(" ")) {
|
|
49
|
+
const { handleDo } = await import("../commands-do.js");
|
|
50
|
+
await handleDo(trimmed, ctx, pi);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
|
|
49
55
|
}
|
|
@@ -222,14 +222,19 @@ Using the \`write\` tool, persist the full structured report to
|
|
|
222
222
|
LEARNINGS.md is the full, cited audit trail. Write it first — subsequent steps
|
|
223
223
|
feed from its content.
|
|
224
224
|
|
|
225
|
-
### Step 3 —
|
|
225
|
+
### Step 3 — Run one bounded duplicate check
|
|
226
226
|
|
|
227
|
-
Before persisting
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
Before persisting extracted items in Steps 4–6, do at most one duplicate-check
|
|
228
|
+
pass for the durable Decisions, Lessons, and Patterns from this milestone. Use
|
|
229
|
+
the already-written LEARNINGS.md content and call \`memory_query\` once with a
|
|
230
|
+
compact keyword summary for the whole batch. If that result clearly shows a
|
|
231
|
+
semantically equivalent high-confidence memory for an item, mark only that item
|
|
232
|
+
as already captured and skip it in its respective persistence step.
|
|
233
|
+
|
|
234
|
+
Do not re-read milestone artefacts or repeat memory queries category-by-category
|
|
235
|
+
after this point. The memory store is the single source of truth for
|
|
236
|
+
cross-session durable knowledge — no other persistence call is part of this
|
|
237
|
+
flow.
|
|
233
238
|
|
|
234
239
|
### Step 4 — Persist Patterns via \`capture_thought\`
|
|
235
240
|
|
|
@@ -268,11 +273,11 @@ later projection back to a human-visible decisions register stays lossless
|
|
|
268
273
|
|
|
269
274
|
### Step 7 — Deduplication rule (applies to Steps 4, 5, 6)
|
|
270
275
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
over creating a second slightly-different row
|
|
275
|
-
signal.
|
|
276
|
+
Use only the duplicate-check result from Step 3. If that bounded check returned
|
|
277
|
+
a semantically equivalent memory at high confidence for an extracted item, skip
|
|
278
|
+
the capture entirely. Otherwise, persist the item once via \`capture_thought\`.
|
|
279
|
+
Prefer skipping a near-duplicate over creating a second slightly-different row
|
|
280
|
+
— redundancy degrades the signal.
|
|
276
281
|
|
|
277
282
|
### Step 8 — Surprises stay only in LEARNINGS.md
|
|
278
283
|
|
|
@@ -38,6 +38,7 @@ import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
|
38
38
|
import { gsdRoot, normalizeRealPath } from "./paths.js";
|
|
39
39
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
40
40
|
import { effectiveLockFile } from "./session-lock.js";
|
|
41
|
+
import { isInFlightRuntimePhase, listUnitRuntimeRecords, type AutoUnitRuntimeRecord } from "./unit-runtime.js";
|
|
41
42
|
|
|
42
43
|
export interface LockData {
|
|
43
44
|
pid: number;
|
|
@@ -103,10 +104,40 @@ function getLatestDispatchForWorker(workerId: string):
|
|
|
103
104
|
return row ?? null;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
function
|
|
107
|
+
function latestInFlightRuntimeRecord(basePath: string): AutoUnitRuntimeRecord | null {
|
|
108
|
+
const records = listUnitRuntimeRecords(basePath).filter((record) =>
|
|
109
|
+
isInFlightRuntimePhase(record.phase),
|
|
110
|
+
);
|
|
111
|
+
if (records.length === 0) return null;
|
|
112
|
+
return records.sort((a, b) => {
|
|
113
|
+
const bTime = b.updatedAt || b.startedAt || 0;
|
|
114
|
+
const aTime = a.updatedAt || a.startedAt || 0;
|
|
115
|
+
return bTime - aTime;
|
|
116
|
+
})[0] ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function runtimeRecordToLockData(worker: AutoWorkerRow, record: AutoUnitRuntimeRecord, sessionFile?: string): LockData {
|
|
120
|
+
const startedAt = Number.isFinite(record.startedAt)
|
|
121
|
+
? new Date(record.startedAt).toISOString()
|
|
122
|
+
: worker.started_at;
|
|
123
|
+
return {
|
|
124
|
+
pid: worker.pid,
|
|
125
|
+
startedAt: worker.started_at,
|
|
126
|
+
unitType: record.unitType,
|
|
127
|
+
unitId: record.unitId,
|
|
128
|
+
unitStartedAt: startedAt,
|
|
129
|
+
sessionFile,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function workerToLockData(basePath: string, worker: AutoWorkerRow): LockData {
|
|
107
134
|
const dispatch = getLatestDispatchForWorker(worker.worker_id);
|
|
108
135
|
const sessionFile =
|
|
109
136
|
getRuntimeKv<string>("worker", worker.worker_id, SESSION_FILE_KV_KEY) ?? undefined;
|
|
137
|
+
if (!dispatch) {
|
|
138
|
+
const runtimeRecord = latestInFlightRuntimeRecord(basePath);
|
|
139
|
+
if (runtimeRecord) return runtimeRecordToLockData(worker, runtimeRecord, sessionFile);
|
|
140
|
+
}
|
|
110
141
|
return {
|
|
111
142
|
pid: worker.pid,
|
|
112
143
|
startedAt: worker.started_at,
|
|
@@ -204,7 +235,7 @@ export function readCrashLock(basePath: string): LockData | null {
|
|
|
204
235
|
try {
|
|
205
236
|
const projectRoot = normalizeRealPath(basePath);
|
|
206
237
|
const stale = findStaleWorkerForProject(projectRoot);
|
|
207
|
-
if (stale) return workerToLockData(stale);
|
|
238
|
+
if (stale) return workerToLockData(basePath, stale);
|
|
208
239
|
} catch {
|
|
209
240
|
// Fall through to the legacy lock-file compatibility path.
|
|
210
241
|
}
|
|
@@ -260,25 +291,48 @@ export function formatCrashInfo(lock: LockData): string {
|
|
|
260
291
|
*/
|
|
261
292
|
export function emitCrashRecoveredUnitEnd(basePath: string, lock: LockData): void {
|
|
262
293
|
if (!lock.unitType || !lock.unitId || lock.unitType === "starting") return;
|
|
294
|
+
emitOpenUnitEndForUnit(basePath, lock.unitType, lock.unitId, "crash-recovered");
|
|
295
|
+
}
|
|
263
296
|
|
|
297
|
+
export function emitOpenUnitEndForUnit(
|
|
298
|
+
basePath: string,
|
|
299
|
+
unitType: string,
|
|
300
|
+
unitId: string,
|
|
301
|
+
status: string,
|
|
302
|
+
errorContext?: { message: string; category: string; stopReason?: string; isTransient?: boolean; retryAfterMs?: number },
|
|
303
|
+
): boolean {
|
|
264
304
|
try {
|
|
265
305
|
const all = queryJournal(basePath);
|
|
266
306
|
|
|
267
307
|
const starts = all.filter(
|
|
268
|
-
(e) =>
|
|
308
|
+
(e) =>
|
|
309
|
+
e.eventType === "unit-start" &&
|
|
310
|
+
e.data?.unitType === unitType &&
|
|
311
|
+
e.data?.unitId === unitId,
|
|
269
312
|
);
|
|
270
|
-
if (starts.length === 0) return;
|
|
313
|
+
if (starts.length === 0) return false;
|
|
271
314
|
|
|
272
|
-
const lastStart =
|
|
315
|
+
const lastStart = [...starts].reverse().find((start) => {
|
|
316
|
+
return !all.some(
|
|
317
|
+
(e) =>
|
|
318
|
+
e.eventType === "unit-end" &&
|
|
319
|
+
e.data?.unitType === unitType &&
|
|
320
|
+
e.data?.unitId === unitId &&
|
|
321
|
+
e.causedBy?.flowId === start.flowId &&
|
|
322
|
+
e.causedBy?.seq === start.seq,
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
if (!lastStart) return false;
|
|
273
326
|
|
|
274
327
|
const alreadyClosed = all.some(
|
|
275
328
|
(e) =>
|
|
276
329
|
e.eventType === "unit-end" &&
|
|
277
|
-
e.data?.
|
|
330
|
+
e.data?.unitType === unitType &&
|
|
331
|
+
e.data?.unitId === unitId &&
|
|
278
332
|
e.causedBy?.flowId === lastStart.flowId &&
|
|
279
333
|
e.causedBy?.seq === lastStart.seq,
|
|
280
334
|
);
|
|
281
|
-
if (alreadyClosed) return;
|
|
335
|
+
if (alreadyClosed) return false;
|
|
282
336
|
|
|
283
337
|
const maxSeq = all
|
|
284
338
|
.filter((e) => e.flowId === lastStart.flowId)
|
|
@@ -290,15 +344,18 @@ export function emitCrashRecoveredUnitEnd(basePath: string, lock: LockData): voi
|
|
|
290
344
|
seq: maxSeq + 1,
|
|
291
345
|
eventType: "unit-end",
|
|
292
346
|
data: {
|
|
293
|
-
unitType
|
|
294
|
-
unitId
|
|
295
|
-
status
|
|
347
|
+
unitType,
|
|
348
|
+
unitId,
|
|
349
|
+
status,
|
|
296
350
|
artifactVerified: false,
|
|
351
|
+
...(errorContext ? { errorContext } : {}),
|
|
297
352
|
},
|
|
298
353
|
causedBy: { flowId: lastStart.flowId, seq: lastStart.seq },
|
|
299
354
|
});
|
|
355
|
+
return true;
|
|
300
356
|
} catch {
|
|
301
357
|
// Never throw from crash recovery path.
|
|
358
|
+
return false;
|
|
302
359
|
}
|
|
303
360
|
}
|
|
304
361
|
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
markStepActive,
|
|
30
30
|
markStepComplete,
|
|
31
31
|
expandIteration,
|
|
32
|
+
isTerminalStepStatus,
|
|
32
33
|
type WorkflowGraph,
|
|
33
34
|
} from "./graph.js";
|
|
34
35
|
import { injectContext } from "./context-injector.js";
|
|
@@ -40,6 +41,24 @@ import { withFileLock } from "./file-lock.js";
|
|
|
40
41
|
// Re-export for downstream consumers
|
|
41
42
|
export { readFrozenDefinition } from "./definition-io.js";
|
|
42
43
|
|
|
44
|
+
function formatBlockedWorkflowReason(graph: WorkflowGraph): string {
|
|
45
|
+
const statusById = new Map(graph.steps.map((step) => [step.id, step.status]));
|
|
46
|
+
const blockedSteps = graph.steps
|
|
47
|
+
.filter((step) => step.status === "pending")
|
|
48
|
+
.map((step) => {
|
|
49
|
+
const blockers = step.dependsOn
|
|
50
|
+
.filter((depId) => !isTerminalStepStatus(statusById.get(depId)))
|
|
51
|
+
.map((depId) => `${depId} (${statusById.get(depId) ?? "missing"})`);
|
|
52
|
+
return blockers.length > 0
|
|
53
|
+
? `${step.id} waiting on ${blockers.join(", ")}`
|
|
54
|
+
: `${step.id} has no runnable dependency path`;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return blockedSteps.length > 0
|
|
58
|
+
? `Workflow blocked: no pending steps are ready. Blocked steps: ${blockedSteps.join("; ")}`
|
|
59
|
+
: "Workflow blocked: no pending steps are ready.";
|
|
60
|
+
}
|
|
61
|
+
|
|
43
62
|
export class CustomWorkflowEngine implements WorkflowEngine {
|
|
44
63
|
readonly engineId = "custom";
|
|
45
64
|
private readonly runDir: string;
|
|
@@ -114,7 +133,11 @@ export class CustomWorkflowEngine implements WorkflowEngine {
|
|
|
114
133
|
(step) => step.status === "complete" || step.status === "expanded",
|
|
115
134
|
);
|
|
116
135
|
if (!allDone) {
|
|
117
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
action: "stop",
|
|
138
|
+
reason: formatBlockedWorkflowReason(graph),
|
|
139
|
+
level: "error",
|
|
140
|
+
};
|
|
118
141
|
}
|
|
119
142
|
return {
|
|
120
143
|
action: "stop",
|
|
@@ -323,6 +323,20 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
|
|
|
323
323
|
)
|
|
324
324
|
`);
|
|
325
325
|
|
|
326
|
+
db.exec(`
|
|
327
|
+
CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
|
|
328
|
+
commit_sha TEXT NOT NULL,
|
|
329
|
+
milestone_id TEXT NOT NULL,
|
|
330
|
+
slice_id TEXT DEFAULT NULL,
|
|
331
|
+
task_id TEXT DEFAULT NULL,
|
|
332
|
+
source TEXT NOT NULL DEFAULT 'recorded',
|
|
333
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
334
|
+
files_json TEXT NOT NULL DEFAULT '[]',
|
|
335
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
336
|
+
PRIMARY KEY (commit_sha, milestone_id)
|
|
337
|
+
)
|
|
338
|
+
`);
|
|
339
|
+
|
|
326
340
|
db.exec(`
|
|
327
341
|
CREATE TABLE IF NOT EXISTS audit_events (
|
|
328
342
|
event_id TEXT PRIMARY KEY,
|
|
@@ -359,6 +373,7 @@ export function createBaseSchemaObjects(db: DbAdapter, hooks: BaseSchemaHooks):
|
|
|
359
373
|
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
|
|
360
374
|
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
|
|
361
375
|
db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
|
|
376
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
362
377
|
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
|
|
363
378
|
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
|
|
364
379
|
|
|
@@ -399,6 +399,23 @@ export function applyMigrationV23MilestoneQueue(db: DbAdapter): void {
|
|
|
399
399
|
ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
export function applyMigrationV26MilestoneCommitAttributions(db: DbAdapter): void {
|
|
403
|
+
db.exec(`
|
|
404
|
+
CREATE TABLE IF NOT EXISTS milestone_commit_attributions (
|
|
405
|
+
commit_sha TEXT NOT NULL,
|
|
406
|
+
milestone_id TEXT NOT NULL,
|
|
407
|
+
slice_id TEXT DEFAULT NULL,
|
|
408
|
+
task_id TEXT DEFAULT NULL,
|
|
409
|
+
source TEXT NOT NULL DEFAULT 'recorded',
|
|
410
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
411
|
+
files_json TEXT NOT NULL DEFAULT '[]',
|
|
412
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
413
|
+
PRIMARY KEY (commit_sha, milestone_id)
|
|
414
|
+
)
|
|
415
|
+
`);
|
|
416
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_milestone_commit_attr_milestone ON milestone_commit_attributions(milestone_id)");
|
|
417
|
+
}
|
|
418
|
+
|
|
402
419
|
export interface MigrationV22Hooks {
|
|
403
420
|
copyQualityGateRowsToRepairedTable(db: DbAdapter): void;
|
|
404
421
|
}
|