@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.72e3af2a
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/extensions/claude-code-cli/stream-adapter.js +11 -2
- package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
- package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +20 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -11
- package/dist/resources/extensions/gsd/auto-recovery.js +6 -507
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
- package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +10 -19
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +68 -10
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-context.js +19 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
- package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
- package/dist/resources/extensions/gsd/db/queries.js +60 -0
- package/dist/resources/extensions/gsd/db-workspace.js +55 -3
- package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
- package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
- package/dist/resources/extensions/gsd/forensics.js +2 -32
- package/dist/resources/extensions/gsd/git-service.js +4 -4
- package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
- package/dist/resources/extensions/gsd/health-widget.js +55 -29
- package/dist/resources/extensions/gsd/layout-policy.js +3 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
- package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
- package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
- package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/dist/resources/extensions/gsd/quick.js +45 -2
- package/dist/resources/extensions/gsd/session-forensics.js +11 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
- package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
- package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
- package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
- package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
- package/dist/resources/extensions/gsd/state.js +12 -611
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
- package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
- package/dist/resources/extensions/gsd/unit-registry.js +32 -4
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
- package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
- 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 +10 -10
- 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 +9 -9
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +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/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- 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/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- 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/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +1 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +2 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
- package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/agent-session.js +3 -0
- package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
- package/packages/gsd-agent-core/dist/index.d.ts +2 -0
- package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/index.js +2 -0
- package/packages/gsd-agent-core/dist/index.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
- package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
- package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
- package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +3 -3
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +34 -20
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/README.md +3 -2
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme-schema.d.ts +75 -75
- package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
- package/pkg/dist/theme/theme-schema.js +1 -1
- package/pkg/dist/theme/theme-schema.js.map +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +11 -7
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
- package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -23
- package/src/resources/extensions/gsd/auto-prompts.ts +38 -12
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -508
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
- package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +43 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -19
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +74 -10
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-context.ts +18 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
- package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
- package/src/resources/extensions/gsd/db/queries.ts +79 -0
- package/src/resources/extensions/gsd/db-workspace.ts +61 -3
- package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/forensics.ts +2 -33
- package/src/resources/extensions/gsd/git-service.ts +5 -5
- package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
- package/src/resources/extensions/gsd/health-widget.ts +69 -32
- package/src/resources/extensions/gsd/layout-policy.ts +2 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
- package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
- package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
- package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/src/resources/extensions/gsd/quick.ts +43 -2
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
- package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
- package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
- package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
- package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
- package/src/resources/extensions/gsd/state.ts +32 -732
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
- package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
- package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
- package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
- package/src/resources/extensions/gsd/unit-registry.ts +32 -4
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
- package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
- package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
- package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: DB-backed GSD state derivation pipeline stage.
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { isClosedStatus, isDeferredStatus } from '../../status-guards.js';
|
|
6
|
+
import { buildMilestoneFileName, resolveFile, resolveMilestonePath, } from '../../paths.js';
|
|
7
|
+
import { getAllMilestones, getLatestAssessmentByScope, getPendingGateCountForTurn, getReplanHistory, getRequirementCounts, getSlice, getSliceTasks, getSlicesByMilestoneIds, } from '../../gsd-db.js';
|
|
8
|
+
import { classifyMilestoneReadiness, readinessNeedsDiscussion, } from '../../milestone-readiness.js';
|
|
9
|
+
import { needsAttentionBlockerGuidance as formatNeedsAttentionBlocker, needsRemediationBlockerGuidance as formatNeedsRemediationBlocker, } from '../../guidance.js';
|
|
10
|
+
import { detectPendingEscalation } from '../../escalation.js';
|
|
11
|
+
import { countUnmappedActiveRequirements, formatCompletePhaseNextAction } from '../../requirements-backlog.js';
|
|
12
|
+
import { logWarning } from '../../workflow-logger.js';
|
|
13
|
+
import { buildDbUnavailableState, ensureExistingWorkflowDbOpen, getRequestedMilestoneLock, } from './db-open.js';
|
|
14
|
+
const isStatusDone = isClosedStatus;
|
|
15
|
+
function stripMilestonePrefix(title) {
|
|
16
|
+
return title.replace(/^M\d+(?:-[a-z0-9]{6})?[^:]*:\s*/, '') || title;
|
|
17
|
+
}
|
|
18
|
+
function buildCompletenessSet(basePath, milestones) {
|
|
19
|
+
const completeMilestoneIds = new Set();
|
|
20
|
+
const parkedMilestoneIds = new Set();
|
|
21
|
+
// DB-authoritative: a milestone is only "complete" when its DB row says so.
|
|
22
|
+
// SUMMARY-file presence is NOT a completion signal here — an orphan SUMMARY
|
|
23
|
+
// (crashed complete-milestone turn, partial merge, manual edit) must not
|
|
24
|
+
// flip derived state to complete and cascade into a false auto-merge (#4179).
|
|
25
|
+
for (const m of milestones) {
|
|
26
|
+
if (m.status === 'parked') {
|
|
27
|
+
parkedMilestoneIds.add(m.id);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (isStatusDone(m.status)) {
|
|
31
|
+
completeMilestoneIds.add(m.id);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { completeMilestoneIds, parkedMilestoneIds };
|
|
36
|
+
}
|
|
37
|
+
function milestoneArtifactExistsInResolvedDir(milestoneDir, milestoneId, suffix) {
|
|
38
|
+
if (!milestoneDir)
|
|
39
|
+
return false;
|
|
40
|
+
const flatPath = join(milestoneDir, buildMilestoneFileName(milestoneId, suffix));
|
|
41
|
+
return existsSync(flatPath) || resolveFile(milestoneDir, milestoneId, suffix) !== null;
|
|
42
|
+
}
|
|
43
|
+
async function buildRegistryAndFindActive(basePath, milestones, completeMilestoneIds, parkedMilestoneIds) {
|
|
44
|
+
const registry = [];
|
|
45
|
+
let activeMilestone = null;
|
|
46
|
+
let activeMilestoneSlices = [];
|
|
47
|
+
let activeMilestoneFound = false;
|
|
48
|
+
let activeMilestoneHasDraft = false;
|
|
49
|
+
let firstDeferredQueuedShell = null;
|
|
50
|
+
const activeMilestoneIds = milestones
|
|
51
|
+
.filter((m) => !parkedMilestoneIds.has(m.id))
|
|
52
|
+
.map((m) => m.id);
|
|
53
|
+
const slicesByMilestone = getSlicesByMilestoneIds(activeMilestoneIds);
|
|
54
|
+
for (const m of milestones) {
|
|
55
|
+
if (parkedMilestoneIds.has(m.id)) {
|
|
56
|
+
registry.push({ id: m.id, title: stripMilestonePrefix(m.title) || m.id, status: 'parked' });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const slices = slicesByMilestone.get(m.id) ?? [];
|
|
60
|
+
// DB-authoritative completeness (#4179): only trust completeMilestoneIds,
|
|
61
|
+
// which is itself derived from DB status. SUMMARY-file presence alone must
|
|
62
|
+
// not imply completion.
|
|
63
|
+
if (completeMilestoneIds.has(m.id)) {
|
|
64
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
65
|
+
registry.push({ id: m.id, title, status: 'complete' });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
|
|
69
|
+
const title = stripMilestonePrefix(m.title) || m.id;
|
|
70
|
+
const milestoneDir = resolveMilestonePath(basePath, m.id);
|
|
71
|
+
const hasContext = milestoneArtifactExistsInResolvedDir(milestoneDir, m.id, "CONTEXT");
|
|
72
|
+
const hasDraftContext = !hasContext && milestoneArtifactExistsInResolvedDir(milestoneDir, m.id, "CONTEXT-DRAFT");
|
|
73
|
+
const readiness = classifyMilestoneReadiness({
|
|
74
|
+
status: m.status,
|
|
75
|
+
hasContext,
|
|
76
|
+
hasDraftContext,
|
|
77
|
+
sliceCount: slices.length,
|
|
78
|
+
});
|
|
79
|
+
if (!activeMilestoneFound) {
|
|
80
|
+
const deps = m.depends_on;
|
|
81
|
+
const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
|
|
82
|
+
if (depsUnmet) {
|
|
83
|
+
registry.push({ id: m.id, title, status: 'pending', dependsOn: deps });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (readiness.kind === 'queued-shell') {
|
|
87
|
+
if (!firstDeferredQueuedShell) {
|
|
88
|
+
firstDeferredQueuedShell = { id: m.id, title, deps, hasDraftContext: readiness.hasDraftContext };
|
|
89
|
+
}
|
|
90
|
+
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (allSlicesDone) {
|
|
94
|
+
activeMilestone = { id: m.id, title };
|
|
95
|
+
activeMilestoneSlices = slices;
|
|
96
|
+
activeMilestoneFound = true;
|
|
97
|
+
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (readinessNeedsDiscussion(readiness))
|
|
101
|
+
activeMilestoneHasDraft = true;
|
|
102
|
+
activeMilestone = { id: m.id, title };
|
|
103
|
+
activeMilestoneSlices = slices;
|
|
104
|
+
activeMilestoneFound = true;
|
|
105
|
+
registry.push({ id: m.id, title, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const deps = m.depends_on;
|
|
109
|
+
registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!activeMilestoneFound && firstDeferredQueuedShell) {
|
|
113
|
+
const shell = firstDeferredQueuedShell;
|
|
114
|
+
activeMilestone = { id: shell.id, title: shell.title };
|
|
115
|
+
activeMilestoneSlices = [];
|
|
116
|
+
activeMilestoneFound = true;
|
|
117
|
+
if (shell.hasDraftContext)
|
|
118
|
+
activeMilestoneHasDraft = true;
|
|
119
|
+
const entry = registry.find(e => e.id === shell.id);
|
|
120
|
+
if (entry)
|
|
121
|
+
entry.status = 'active';
|
|
122
|
+
}
|
|
123
|
+
return { registry, activeMilestone, activeMilestoneSlices, activeMilestoneHasDraft };
|
|
124
|
+
}
|
|
125
|
+
function handleNoActiveMilestone(registry, requirements, milestoneProgress) {
|
|
126
|
+
const pendingEntries = registry.filter(e => e.status === 'pending');
|
|
127
|
+
const parkedEntries = registry.filter(e => e.status === 'parked');
|
|
128
|
+
if (pendingEntries.length > 0) {
|
|
129
|
+
const blockerDetails = pendingEntries
|
|
130
|
+
.filter(e => e.dependsOn && e.dependsOn.length > 0)
|
|
131
|
+
.map(e => `${e.id} is waiting on unmet deps: ${e.dependsOn.join(', ')}`);
|
|
132
|
+
return {
|
|
133
|
+
activeMilestone: null, activeSlice: null, activeTask: null,
|
|
134
|
+
phase: 'blocked',
|
|
135
|
+
recentDecisions: [], blockers: blockerDetails.length > 0
|
|
136
|
+
? blockerDetails
|
|
137
|
+
: ['All remaining milestones are dep-blocked but no deps listed — check CONTEXT.md files'],
|
|
138
|
+
nextAction: 'Resolve milestone dependencies before proceeding.',
|
|
139
|
+
registry, requirements,
|
|
140
|
+
progress: { milestones: milestoneProgress },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (parkedEntries.length > 0) {
|
|
144
|
+
const parkedIds = parkedEntries.map(e => e.id).join(', ');
|
|
145
|
+
return {
|
|
146
|
+
activeMilestone: null, activeSlice: null, activeTask: null,
|
|
147
|
+
phase: 'pre-planning',
|
|
148
|
+
recentDecisions: [], blockers: [],
|
|
149
|
+
nextAction: `All remaining milestones are parked (${parkedIds}). Run /gsd unpark <id> or create a new milestone.`,
|
|
150
|
+
registry, requirements,
|
|
151
|
+
progress: { milestones: milestoneProgress },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (registry.length === 0) {
|
|
155
|
+
return {
|
|
156
|
+
activeMilestone: null, activeSlice: null, activeTask: null,
|
|
157
|
+
phase: 'pre-planning',
|
|
158
|
+
recentDecisions: [], blockers: [],
|
|
159
|
+
nextAction: 'No milestones found. Run /gsd to create one.',
|
|
160
|
+
registry: [], requirements,
|
|
161
|
+
progress: { milestones: { done: 0, total: 0 } },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const lastEntry = registry[registry.length - 1];
|
|
165
|
+
const unmappedActive = countUnmappedActiveRequirements();
|
|
166
|
+
const completionNote = formatCompletePhaseNextAction(unmappedActive);
|
|
167
|
+
return {
|
|
168
|
+
activeMilestone: null,
|
|
169
|
+
lastCompletedMilestone: lastEntry ? { id: lastEntry.id, title: lastEntry.title } : null,
|
|
170
|
+
activeSlice: null, activeTask: null,
|
|
171
|
+
phase: 'complete',
|
|
172
|
+
recentDecisions: [], blockers: [],
|
|
173
|
+
nextAction: completionNote,
|
|
174
|
+
registry, requirements,
|
|
175
|
+
progress: { milestones: milestoneProgress },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress) {
|
|
179
|
+
const validation = getLatestAssessmentByScope(activeMilestone.id, "milestone-validation");
|
|
180
|
+
const verdict = typeof validation?.status === "string" ? validation.status : undefined;
|
|
181
|
+
const validationTerminal = verdict != null && verdict !== "";
|
|
182
|
+
if (!validationTerminal) {
|
|
183
|
+
return {
|
|
184
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
185
|
+
phase: 'validating-milestone',
|
|
186
|
+
recentDecisions: [], blockers: [],
|
|
187
|
+
nextAction: `Validate milestone ${activeMilestone.id} before completion.`,
|
|
188
|
+
registry, requirements,
|
|
189
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// All roadmap slices are done (enforced by caller) and verdict is
|
|
193
|
+
// needs-remediation — remediation cannot progress without new slices.
|
|
194
|
+
// Return blocked instead of re-dispatching validate-milestone (#4506).
|
|
195
|
+
if (verdict === 'needs-attention') {
|
|
196
|
+
return {
|
|
197
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
198
|
+
phase: 'blocked',
|
|
199
|
+
recentDecisions: [],
|
|
200
|
+
blockers: [formatNeedsAttentionBlocker(activeMilestone.id)],
|
|
201
|
+
nextAction: `Resolve ${activeMilestone.id} validation attention before proceeding.`,
|
|
202
|
+
registry, requirements,
|
|
203
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (verdict === 'needs-remediation') {
|
|
207
|
+
return {
|
|
208
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
209
|
+
phase: 'blocked',
|
|
210
|
+
recentDecisions: [],
|
|
211
|
+
blockers: [formatNeedsRemediationBlocker(activeMilestone.id)],
|
|
212
|
+
nextAction: `Resolve ${activeMilestone.id} remediation before proceeding.`,
|
|
213
|
+
registry, requirements,
|
|
214
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
219
|
+
phase: 'completing-milestone',
|
|
220
|
+
recentDecisions: [], blockers: [],
|
|
221
|
+
nextAction: `All slices complete in ${activeMilestone.id}. Write milestone summary.`,
|
|
222
|
+
registry, requirements,
|
|
223
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function resolveSliceDependencies(activeMilestoneSlices) {
|
|
227
|
+
const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
|
|
228
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
229
|
+
if (sliceLock) {
|
|
230
|
+
const lockedSlice = activeMilestoneSlices.find(s => s.id === sliceLock);
|
|
231
|
+
if (lockedSlice) {
|
|
232
|
+
return { activeSlice: { id: lockedSlice.id, title: lockedSlice.title }, activeSliceRow: lockedSlice };
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
logWarning("state", `GSD_SLICE_LOCK=${sliceLock} not found in active slices — worker has no assigned work`);
|
|
236
|
+
return { activeSlice: null, activeSliceRow: null };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const s of activeMilestoneSlices) {
|
|
240
|
+
if (isStatusDone(s.status))
|
|
241
|
+
continue;
|
|
242
|
+
if (isDeferredStatus(s.status))
|
|
243
|
+
continue;
|
|
244
|
+
if (s.depends.every(dep => doneSliceIds.has(dep))) {
|
|
245
|
+
return { activeSlice: { id: s.id, title: s.title }, activeSliceRow: s };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { activeSlice: null, activeSliceRow: null };
|
|
249
|
+
}
|
|
250
|
+
async function detectBlockers(basePath, milestoneId, sliceId, tasks) {
|
|
251
|
+
const completedTasks = tasks.filter(t => isStatusDone(t.status));
|
|
252
|
+
for (const ct of completedTasks) {
|
|
253
|
+
if (ct.blocker_discovered) {
|
|
254
|
+
return ct.id;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
function checkReplanTrigger(basePath, milestoneId, sliceId) {
|
|
260
|
+
const sliceRow = getSlice(milestoneId, sliceId);
|
|
261
|
+
return !!sliceRow?.replan_triggered_at;
|
|
262
|
+
}
|
|
263
|
+
export async function deriveStateFromDb(basePath, artifactReadRoot = basePath) {
|
|
264
|
+
if (!ensureExistingWorkflowDbOpen(basePath)) {
|
|
265
|
+
return buildDbUnavailableState();
|
|
266
|
+
}
|
|
267
|
+
const requirements = getRequirementCounts();
|
|
268
|
+
const allMilestones = getAllMilestones();
|
|
269
|
+
const milestoneLock = getRequestedMilestoneLock();
|
|
270
|
+
const milestones = milestoneLock
|
|
271
|
+
? allMilestones.filter(m => m.id === milestoneLock)
|
|
272
|
+
: allMilestones;
|
|
273
|
+
if (milestones.length === 0) {
|
|
274
|
+
return {
|
|
275
|
+
activeMilestone: null, activeSlice: null, activeTask: null,
|
|
276
|
+
phase: 'pre-planning', recentDecisions: [], blockers: [],
|
|
277
|
+
nextAction: 'No milestones found. Run /gsd to create one.',
|
|
278
|
+
registry: [], requirements,
|
|
279
|
+
progress: { milestones: { done: 0, total: 0 } },
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const { completeMilestoneIds, parkedMilestoneIds } = buildCompletenessSet(basePath, milestones);
|
|
283
|
+
const registryContext = await buildRegistryAndFindActive(basePath, milestones, completeMilestoneIds, parkedMilestoneIds);
|
|
284
|
+
const { registry, activeMilestone, activeMilestoneSlices, activeMilestoneHasDraft } = registryContext;
|
|
285
|
+
const milestoneProgress = {
|
|
286
|
+
done: registry.filter(e => e.status === 'complete').length,
|
|
287
|
+
total: registry.length,
|
|
288
|
+
};
|
|
289
|
+
if (!activeMilestone) {
|
|
290
|
+
return handleNoActiveMilestone(registry, requirements, milestoneProgress);
|
|
291
|
+
}
|
|
292
|
+
if (activeMilestoneSlices.length === 0) {
|
|
293
|
+
const phase = activeMilestoneHasDraft ? 'needs-discussion' : 'pre-planning';
|
|
294
|
+
const nextAction = activeMilestoneHasDraft
|
|
295
|
+
? `Discuss draft context for milestone ${activeMilestone.id}.`
|
|
296
|
+
: `Plan milestone ${activeMilestone.id}.`;
|
|
297
|
+
return {
|
|
298
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
299
|
+
phase, recentDecisions: [], blockers: [],
|
|
300
|
+
nextAction, registry, requirements,
|
|
301
|
+
progress: { milestones: milestoneProgress },
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const allSlicesDone = activeMilestoneSlices.every(s => isStatusDone(s.status));
|
|
305
|
+
const sliceProgress = {
|
|
306
|
+
done: activeMilestoneSlices.filter(s => isStatusDone(s.status)).length,
|
|
307
|
+
total: activeMilestoneSlices.length,
|
|
308
|
+
};
|
|
309
|
+
if (allSlicesDone) {
|
|
310
|
+
return handleAllSlicesDone(basePath, activeMilestone, registry, requirements, milestoneProgress, sliceProgress);
|
|
311
|
+
}
|
|
312
|
+
const activeSliceContext = resolveSliceDependencies(activeMilestoneSlices);
|
|
313
|
+
if (!activeSliceContext.activeSlice) {
|
|
314
|
+
// If locked slice wasn't found, it returns null but logs warning, we need to return 'blocked'
|
|
315
|
+
const sliceLock = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_SLICE_LOCK : undefined;
|
|
316
|
+
if (sliceLock) {
|
|
317
|
+
return {
|
|
318
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
319
|
+
phase: 'blocked', recentDecisions: [], blockers: [`GSD_SLICE_LOCK=${sliceLock} not found in active milestone slices`],
|
|
320
|
+
nextAction: 'Slice lock references a non-existent slice — check orchestrator dispatch.',
|
|
321
|
+
registry, requirements,
|
|
322
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
activeMilestone, activeSlice: null, activeTask: null,
|
|
327
|
+
phase: 'blocked', recentDecisions: [], blockers: ['No slice eligible — check dependency ordering'],
|
|
328
|
+
nextAction: 'Resolve dependency blockers or plan next slice.',
|
|
329
|
+
registry, requirements,
|
|
330
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const { activeSlice } = activeSliceContext;
|
|
334
|
+
const activeSliceRow = activeSliceContext.activeSliceRow;
|
|
335
|
+
// ADR-011: DB slice metadata is authoritative for sketch refinement.
|
|
336
|
+
// Stale sketch flags (PLAN on disk but is_sketch=1) are repaired by
|
|
337
|
+
// sketchFlagHandler via reconcileBeforeDispatch — not during derivation.
|
|
338
|
+
// PLAN.md and preference flags are projections/configuration and are
|
|
339
|
+
// deliberately not used to infer whether the slice itself is a sketch.
|
|
340
|
+
if (activeSliceRow?.is_sketch === 1) {
|
|
341
|
+
return {
|
|
342
|
+
activeMilestone, activeSlice, activeTask: null,
|
|
343
|
+
phase: 'refining', recentDecisions: [], blockers: [],
|
|
344
|
+
nextAction: `Refine sketch slice ${activeSlice.id} (${activeSlice.title}) using prior slice context.`,
|
|
345
|
+
registry, requirements,
|
|
346
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress },
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
|
|
350
|
+
const taskProgress = {
|
|
351
|
+
done: tasks.filter(t => isStatusDone(t.status)).length,
|
|
352
|
+
total: tasks.length,
|
|
353
|
+
};
|
|
354
|
+
const activeTaskRow = tasks.find(t => !isStatusDone(t.status));
|
|
355
|
+
if (!activeTaskRow && tasks.length > 0) {
|
|
356
|
+
return {
|
|
357
|
+
activeMilestone, activeSlice, activeTask: null,
|
|
358
|
+
phase: 'summarizing', recentDecisions: [], blockers: [],
|
|
359
|
+
nextAction: `All tasks done in ${activeSlice.id}. Write slice summary and complete slice.`,
|
|
360
|
+
registry, requirements,
|
|
361
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (!activeTaskRow) {
|
|
365
|
+
return {
|
|
366
|
+
activeMilestone, activeSlice, activeTask: null,
|
|
367
|
+
phase: 'planning', recentDecisions: [], blockers: [],
|
|
368
|
+
nextAction: `Slice ${activeSlice.id} has no DB tasks. Plan slice tasks before execution.`,
|
|
369
|
+
registry, requirements,
|
|
370
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const activeTask = { id: activeTaskRow.id, title: activeTaskRow.title };
|
|
374
|
+
// ── Quality gate evaluation check ──────────────────────────────────
|
|
375
|
+
// Pause before execution only when gates owned by the `gate-evaluate`
|
|
376
|
+
// turn (Q3/Q4) are still pending. Q8 is also `scope:"slice"` but is
|
|
377
|
+
// owned by `complete-slice`, so it must NOT block the evaluating-gates
|
|
378
|
+
// phase — otherwise auto-loop stalls forever waiting for a gate that
|
|
379
|
+
// this turn never evaluates. See gate-registry.ts for the ownership map.
|
|
380
|
+
// Slices with zero gate rows (pre-feature or simple) skip straight through.
|
|
381
|
+
const pendingGateCount = getPendingGateCountForTurn(activeMilestone.id, activeSlice.id, "gate-evaluate");
|
|
382
|
+
if (pendingGateCount > 0) {
|
|
383
|
+
return {
|
|
384
|
+
activeMilestone, activeSlice, activeTask: null,
|
|
385
|
+
phase: 'evaluating-gates', recentDecisions: [], blockers: [],
|
|
386
|
+
nextAction: `Evaluate ${pendingGateCount} quality gate(s) for ${activeSlice.id} before execution.`,
|
|
387
|
+
registry, requirements,
|
|
388
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const blockerTaskId = await detectBlockers(basePath, activeMilestone.id, activeSlice.id, tasks);
|
|
392
|
+
if (blockerTaskId) {
|
|
393
|
+
const replanHistory = getReplanHistory(activeMilestone.id, activeSlice.id);
|
|
394
|
+
if (replanHistory.length === 0) {
|
|
395
|
+
return {
|
|
396
|
+
activeMilestone, activeSlice, activeTask,
|
|
397
|
+
phase: 'replanning-slice', recentDecisions: [],
|
|
398
|
+
blockers: [`Task ${blockerTaskId} discovered a blocker requiring slice replan`],
|
|
399
|
+
nextAction: `Task ${blockerTaskId} reported blocker_discovered. Replan slice ${activeSlice.id} before continuing.`,
|
|
400
|
+
activeWorkspace: undefined,
|
|
401
|
+
registry, requirements,
|
|
402
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// ADR-011 Phase 2: pause-on-escalation takes precedence over dispatching the
|
|
407
|
+
// next task. `awaiting_review` tasks (continueWithDefault=true) still pause
|
|
408
|
+
// here so silence is never treated as consent.
|
|
409
|
+
//
|
|
410
|
+
// We do NOT gate this on `phases.mid_execution_escalation` — creation of
|
|
411
|
+
// new escalations is gated at the write site (tools/complete-task.ts:315),
|
|
412
|
+
// but any escalation_pending row already persisted in the DB must be
|
|
413
|
+
// honored even if the user later toggles the flag off. Otherwise those
|
|
414
|
+
// rows would silently orphan, the loop would advance past the paused task,
|
|
415
|
+
// and the user's prior resolution never lands.
|
|
416
|
+
const escalatingTaskId = detectPendingEscalation(tasks, basePath);
|
|
417
|
+
if (escalatingTaskId) {
|
|
418
|
+
return {
|
|
419
|
+
activeMilestone, activeSlice, activeTask,
|
|
420
|
+
phase: 'escalating-task', recentDecisions: [],
|
|
421
|
+
blockers: [`Task ${escalatingTaskId} requires a user decision before the loop can proceed`],
|
|
422
|
+
nextAction: `Run /gsd escalate show ${escalatingTaskId} to review, then /gsd escalate resolve ${escalatingTaskId} <choice> to proceed.`,
|
|
423
|
+
activeWorkspace: undefined,
|
|
424
|
+
registry, requirements,
|
|
425
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (!blockerTaskId) {
|
|
429
|
+
const isTriggered = checkReplanTrigger(basePath, activeMilestone.id, activeSlice.id);
|
|
430
|
+
if (isTriggered) {
|
|
431
|
+
const replanHistory = getReplanHistory(activeMilestone.id, activeSlice.id);
|
|
432
|
+
if (replanHistory.length === 0) {
|
|
433
|
+
return {
|
|
434
|
+
activeMilestone, activeSlice, activeTask,
|
|
435
|
+
phase: 'replanning-slice', recentDecisions: [],
|
|
436
|
+
blockers: ['Triage replan trigger detected — slice replan required'],
|
|
437
|
+
nextAction: `Triage replan triggered for slice ${activeSlice.id}. Replan before continuing.`,
|
|
438
|
+
activeWorkspace: undefined,
|
|
439
|
+
registry, requirements,
|
|
440
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
activeMilestone, activeSlice, activeTask,
|
|
447
|
+
phase: 'executing', recentDecisions: [], blockers: [],
|
|
448
|
+
nextAction: `Execute ${activeTask.id}: ${activeTask.title} in slice ${activeSlice.id}.`,
|
|
449
|
+
registry, requirements,
|
|
450
|
+
progress: { milestones: milestoneProgress, slices: sliceProgress, tasks: taskProgress },
|
|
451
|
+
};
|
|
452
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: deriveState orchestrator — cache, DB open, pure DB projection.
|
|
3
|
+
import { loadFile } from '../../files.js';
|
|
4
|
+
import { resolveGsdRootFile } from '../../paths.js';
|
|
5
|
+
import { isDbAvailable } from '../../gsd-db.js';
|
|
6
|
+
import { wasWorkflowDatabaseOpenAttempted } from '../../db-workspace.js';
|
|
7
|
+
import { debugCount, debugTime } from '../../debug-logger.js';
|
|
8
|
+
import { logWarning } from '../../workflow-logger.js';
|
|
9
|
+
import { getDeriveTelemetry, incrementDbDeriveCount, invalidateStateCache, readCachedDeriveState, resetDeriveTelemetry, writeCachedDeriveState, } from './cache.js';
|
|
10
|
+
import { buildDbUnavailableState, ensureExistingWorkflowDbOpen, } from './db-open.js';
|
|
11
|
+
import { deriveStateFromDb } from './from-db.js';
|
|
12
|
+
export { getDeriveTelemetry, invalidateStateCache, resetDeriveTelemetry, };
|
|
13
|
+
async function loadRecentDecisions(basePath) {
|
|
14
|
+
const decisionsPath = resolveGsdRootFile(basePath, "DECISIONS");
|
|
15
|
+
const content = await loadFile(decisionsPath);
|
|
16
|
+
if (!content)
|
|
17
|
+
return [];
|
|
18
|
+
const fromTable = content
|
|
19
|
+
.split("\n")
|
|
20
|
+
.map((line) => line.trim())
|
|
21
|
+
.filter((line) => line.startsWith("|"))
|
|
22
|
+
.map((line) => {
|
|
23
|
+
const cells = line
|
|
24
|
+
.split("|")
|
|
25
|
+
.map((cell) => cell.trim())
|
|
26
|
+
.filter((cell) => cell.length > 0);
|
|
27
|
+
if (cells.length < 6)
|
|
28
|
+
return null;
|
|
29
|
+
const id = cells[0];
|
|
30
|
+
if (!/^D\d+$/i.test(id))
|
|
31
|
+
return null;
|
|
32
|
+
const whenContext = cells[1];
|
|
33
|
+
const decision = cells[3];
|
|
34
|
+
const choice = cells[4];
|
|
35
|
+
if (!decision || !choice)
|
|
36
|
+
return null;
|
|
37
|
+
return `${id} (${whenContext}): ${decision} -> ${choice}`;
|
|
38
|
+
})
|
|
39
|
+
.filter((value) => value != null);
|
|
40
|
+
if (fromTable.length > 0)
|
|
41
|
+
return fromTable.slice(-5);
|
|
42
|
+
const fromBullets = content
|
|
43
|
+
.split("\n")
|
|
44
|
+
.map((line) => line.trim())
|
|
45
|
+
.filter((line) => /^-\s+/.test(line))
|
|
46
|
+
.map((line) => line.replace(/^-+\s+/, ""))
|
|
47
|
+
.filter((line) => /^D\d+\b/i.test(line));
|
|
48
|
+
return fromBullets.slice(-5);
|
|
49
|
+
}
|
|
50
|
+
export async function deriveState(basePath, opts) {
|
|
51
|
+
const cacheKey = opts?.projectRootForReads ?? basePath;
|
|
52
|
+
const cached = readCachedDeriveState(cacheKey);
|
|
53
|
+
if (cached)
|
|
54
|
+
return cached;
|
|
55
|
+
const stopTimer = debugTime("derive-state-impl");
|
|
56
|
+
let result;
|
|
57
|
+
ensureExistingWorkflowDbOpen(basePath);
|
|
58
|
+
if (isDbAvailable()) {
|
|
59
|
+
const stopDbTimer = debugTime("derive-state-db");
|
|
60
|
+
result = await deriveStateFromDb(basePath, opts?.projectRootForReads ?? basePath);
|
|
61
|
+
stopDbTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
62
|
+
incrementDbDeriveCount();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (wasWorkflowDatabaseOpenAttempted()) {
|
|
66
|
+
logWarning("state", "DB unavailable — refusing implicit markdown state derivation");
|
|
67
|
+
}
|
|
68
|
+
result = buildDbUnavailableState();
|
|
69
|
+
}
|
|
70
|
+
result.recentDecisions = await loadRecentDecisions(cacheKey);
|
|
71
|
+
stopTimer({ phase: result.phase, milestone: result.activeMilestone?.id });
|
|
72
|
+
debugCount("deriveStateCalls");
|
|
73
|
+
writeCachedDeriveState(cacheKey, result);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Detect interrupted slice work from CONTINUE artifacts (legacy path).
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { loadFile } from '../../files.js';
|
|
5
|
+
import { resolveSliceFile, resolveSlicePath } from '../../paths.js';
|
|
6
|
+
export async function detectInterruptedWork(basePath, milestoneId, sliceId) {
|
|
7
|
+
const sliceDir = resolveSlicePath(basePath, milestoneId, sliceId);
|
|
8
|
+
const continueFile = sliceDir
|
|
9
|
+
? resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE")
|
|
10
|
+
: null;
|
|
11
|
+
if (continueFile && await loadFile(continueFile))
|
|
12
|
+
return true;
|
|
13
|
+
if (sliceDir && await loadFile(join(sliceDir, "continue.md")))
|
|
14
|
+
return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
export function interruptedWorkNextAction(taskId, taskTitle, sliceId, interrupted) {
|
|
18
|
+
return interrupted
|
|
19
|
+
? `Resume interrupted work on ${taskId}: ${taskTitle} in slice ${sliceId}. Read continue.md first.`
|
|
20
|
+
: `Execute ${taskId}: ${taskTitle} in slice ${sliceId}.`;
|
|
21
|
+
}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
// had zero callers in production code — wiring it through
|
|
7
7
|
// reconcileBeforeDispatch closes that gap.
|
|
8
8
|
import { detectStaleRenders, renderPlanCheckboxes, renderRoadmapFromDb, renderSliceSummary, renderTaskSummary, } from "../../markdown-renderer.js";
|
|
9
|
-
import { getMilestone, getMilestoneSlices, getSlice, getSliceTasks, setSliceSummaryMd } from "../../gsd-db.js";
|
|
9
|
+
import { getMilestone, getMilestoneSlices, getSlice, getSliceTasks, setSliceSummaryMd, } from "../../gsd-db.js";
|
|
10
|
+
import { ensureWorkflowDbForBase, } from "../../db-workspace.js";
|
|
10
11
|
import { resolveSliceFile } from "../../paths.js";
|
|
11
12
|
import { logWarning } from "../../workflow-logger.js";
|
|
13
|
+
const VALIDATION_BLOCK_RE = /milestone validation returned needs-(?:attention|remediation)|validation verdict is needs-(?:attention|remediation)/i;
|
|
12
14
|
// ─── Core (basePath-only — usable by both drift API and legacy wrapper) ──────
|
|
13
15
|
function detectStaleRenderDriftFromBasePath(basePath) {
|
|
14
16
|
const entries = detectStaleRenders(basePath);
|
|
@@ -38,6 +40,17 @@ function isRepairableStaleRenderReason(reason) {
|
|
|
38
40
|
(reason.includes("SUMMARY.md missing") && /^S\d+/.test(reason)) ||
|
|
39
41
|
reason.includes("UAT.md missing"));
|
|
40
42
|
}
|
|
43
|
+
function validationBlocker(state) {
|
|
44
|
+
if (state.phase !== "blocked")
|
|
45
|
+
return null;
|
|
46
|
+
return state.blockers.find((blocker) => VALIDATION_BLOCK_RE.test(blocker)) ?? null;
|
|
47
|
+
}
|
|
48
|
+
function isMilestoneSummaryMissing(record) {
|
|
49
|
+
const normPath = record.renderPath.replace(/\\/g, "/");
|
|
50
|
+
return (record.reason.includes("SUMMARY.md missing") &&
|
|
51
|
+
(/^M\d+(?:\b|[-_:])/.test(record.reason) ||
|
|
52
|
+
/(?:^|\/)M\d+(?:-[a-z0-9]+)?-SUMMARY\.md$/i.test(normPath)));
|
|
53
|
+
}
|
|
41
54
|
function canonicalizeMilestoneId(dirSegment) {
|
|
42
55
|
if (getMilestone(dirSegment))
|
|
43
56
|
return dirSegment;
|
|
@@ -82,7 +95,16 @@ function resolveRoadmapMilestoneIdFromPath(normPath) {
|
|
|
82
95
|
}
|
|
83
96
|
return fileMatch?.[1] ?? milestoneMatch[1];
|
|
84
97
|
}
|
|
98
|
+
function ensureDbForStaleRenderRepair(basePath) {
|
|
99
|
+
return ensureWorkflowDbForBase(basePath);
|
|
100
|
+
}
|
|
101
|
+
function retryDbForStaleRenderRepair(basePath) {
|
|
102
|
+
return ensureWorkflowDbForBase(basePath, { refresh: true });
|
|
103
|
+
}
|
|
85
104
|
async function repairStaleRenderFromBasePath(record, basePath) {
|
|
105
|
+
if (!ensureDbForStaleRenderRepair(basePath)) {
|
|
106
|
+
throw new Error(`stale-render drift: database unavailable for repair (${basePath})`);
|
|
107
|
+
}
|
|
86
108
|
const normPath = record.renderPath.replace(/\\/g, "/");
|
|
87
109
|
const reason = record.reason;
|
|
88
110
|
if (reason.includes("in roadmap")) {
|
|
@@ -101,7 +123,18 @@ async function repairStaleRenderFromBasePath(record, basePath) {
|
|
|
101
123
|
const sliceId = pathMatch[2] && pathMatch[3] && /^\d+$/.test(pathMatch[2])
|
|
102
124
|
? `S${String(parseInt(pathMatch[3], 10)).padStart(2, "0")}`
|
|
103
125
|
: pathMatch[2];
|
|
104
|
-
|
|
126
|
+
let wrote = false;
|
|
127
|
+
try {
|
|
128
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if (!retryDbForStaleRenderRepair(basePath))
|
|
132
|
+
throw err;
|
|
133
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
134
|
+
}
|
|
135
|
+
if (!wrote && retryDbForStaleRenderRepair(basePath)) {
|
|
136
|
+
wrote = await renderPlanCheckboxes(basePath, milestoneId, sliceId, record.renderPath);
|
|
137
|
+
}
|
|
105
138
|
if (!wrote) {
|
|
106
139
|
throw new Error(`stale-render drift: plan re-render wrote nothing for ${milestoneId}/${pathMatch[2]} ` +
|
|
107
140
|
`(${record.renderPath}); slice has no tasks or its path is unresolvable`);
|
|
@@ -227,9 +260,19 @@ export function detectStaleRenderDrift(_state, ctx) {
|
|
|
227
260
|
export async function repairStaleRender(record, ctx) {
|
|
228
261
|
await repairStaleRenderFromBasePath(record, ctx.basePath);
|
|
229
262
|
}
|
|
263
|
+
export function staleRenderBlocker(record, ctx) {
|
|
264
|
+
const blocker = validationBlocker(ctx.state);
|
|
265
|
+
if (!blocker || !isMilestoneSummaryMissing(record))
|
|
266
|
+
return null;
|
|
267
|
+
return [
|
|
268
|
+
`Stale milestone summary render at ${record.renderPath} is blocked by milestone validation.`,
|
|
269
|
+
blocker,
|
|
270
|
+
].join("\n");
|
|
271
|
+
}
|
|
230
272
|
export const staleRenderHandler = {
|
|
231
273
|
kind: "stale-render",
|
|
232
274
|
detect: detectStaleRenderDrift,
|
|
275
|
+
blocker: staleRenderBlocker,
|
|
233
276
|
repair: repairStaleRender,
|
|
234
277
|
};
|
|
235
278
|
// ─── Legacy entry point ──────────────────────────────────────────────────────
|