gsd-pi 2.79.0-dev.ece5fd8ba → 2.80.0-dev.4ea7d80e7
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/github-sync/templates.js +90 -74
- package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
- package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +53 -0
- package/dist/resources/extensions/gsd/auto/loop.js +365 -522
- package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +55 -6
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -15
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.js +12 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-iteration.js +24 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.js +33 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.js +26 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-retry.js +49 -0
- package/dist/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.js +25 -0
- package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +48 -0
- package/dist/resources/extensions/gsd/auto/workflow-dispatch-ledger.js +26 -0
- package/dist/resources/extensions/gsd/auto/workflow-iteration-completion.js +10 -0
- package/dist/resources/extensions/gsd/auto/workflow-journal-reporter.js +16 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +263 -0
- package/dist/resources/extensions/gsd/auto/workflow-memory-pressure.js +36 -0
- package/dist/resources/extensions/gsd/auto/workflow-phase-reporter.js +9 -0
- package/dist/resources/extensions/gsd/auto/workflow-session-lock.js +35 -0
- package/dist/resources/extensions/gsd/auto/workflow-sidecar-iteration.js +24 -0
- package/dist/resources/extensions/gsd/auto/workflow-sidecar-queue.js +26 -0
- package/dist/resources/extensions/gsd/auto/workflow-turn-reporter.js +36 -0
- package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +44 -0
- package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +15 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +54 -15
- package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +168 -3
- package/dist/resources/extensions/gsd/auto-recovery.js +198 -59
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
- package/dist/resources/extensions/gsd/auto-start.js +2 -3
- package/dist/resources/extensions/gsd/auto-verification.js +2 -11
- package/dist/resources/extensions/gsd/auto-worktree.js +87 -38
- package/dist/resources/extensions/gsd/auto.js +168 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +24 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +10 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +22 -6
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
- package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
- package/dist/resources/extensions/gsd/commands-ship.js +23 -46
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +12 -7
- package/dist/resources/extensions/gsd/component-loader.js +5 -11
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +25 -1
- package/dist/resources/extensions/gsd/db-adapter.js +47 -0
- package/dist/resources/extensions/gsd/db-base-schema.js +351 -0
- package/dist/resources/extensions/gsd/db-connection-cache.js +31 -0
- package/dist/resources/extensions/gsd/db-coordination-schema.js +104 -0
- package/dist/resources/extensions/gsd/db-decision-requirement-rows.js +71 -0
- package/dist/resources/extensions/gsd/db-gate-rows.js +16 -0
- package/dist/resources/extensions/gsd/db-lightweight-query-rows.js +29 -0
- package/dist/resources/extensions/gsd/db-memory-fts-schema.js +56 -0
- package/dist/resources/extensions/gsd/db-migration-backup.js +22 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +410 -0
- package/dist/resources/extensions/gsd/db-milestone-artifact-rows.js +35 -0
- package/dist/resources/extensions/gsd/db-open-state.js +32 -0
- package/dist/resources/extensions/gsd/db-provider.js +108 -0
- package/dist/resources/extensions/gsd/db-runtime-kv-schema.js +27 -0
- package/dist/resources/extensions/gsd/db-schema-metadata.js +23 -0
- package/dist/resources/extensions/gsd/db-task-slice-rows.js +86 -0
- package/dist/resources/extensions/gsd/db-transaction.js +63 -0
- package/dist/resources/extensions/gsd/db-verification-evidence-rows.js +3 -0
- package/dist/resources/extensions/gsd/db-verification-evidence-schema.js +19 -0
- package/dist/resources/extensions/gsd/escalation.js +2 -0
- package/dist/resources/extensions/gsd/graph.js +9 -3
- package/dist/resources/extensions/gsd/gsd-db.js +316 -1520
- package/dist/resources/extensions/gsd/guided-flow.js +2 -2
- package/dist/resources/extensions/gsd/legacy-telemetry.js +70 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +9 -6
- package/dist/resources/extensions/gsd/notification-widget.js +21 -3
- package/dist/resources/extensions/gsd/post-execution-checks.js +27 -6
- package/dist/resources/extensions/gsd/pr-evidence.js +117 -0
- package/dist/resources/extensions/gsd/pre-execution-checks.js +2 -0
- package/dist/resources/extensions/gsd/process-task-path.js +61 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +9 -5
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +23 -34
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
- package/dist/resources/extensions/gsd/prompts/discuss.md +81 -181
- package/dist/resources/extensions/gsd/prompts/execute-task.md +40 -67
- package/dist/resources/extensions/gsd/prompts/forensics.md +41 -84
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
- package/dist/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +25 -87
- package/dist/resources/extensions/gsd/prompts/queue.md +46 -53
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +23 -23
- package/dist/resources/extensions/gsd/prompts/research-slice.md +23 -23
- package/dist/resources/extensions/gsd/prompts/rethink.md +10 -10
- package/dist/resources/extensions/gsd/prompts/system.md +65 -107
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +15 -15
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
- package/dist/resources/extensions/gsd/state.js +4 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +14 -9
- package/dist/resources/extensions/gsd/tools/complete-task.js +2 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +6 -1
- package/dist/resources/extensions/gsd/unit-context-composer.js +1 -1
- package/dist/resources/extensions/gsd/uok/kernel.js +8 -3
- package/dist/resources/extensions/gsd/uok/plan-v2.js +2 -0
- package/dist/resources/extensions/gsd/workflow-logger.js +13 -13
- package/dist/resources/extensions/gsd/workflow-manifest.js +2 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -0
- package/dist/resources/extensions/gsd/workflow-templates.js +9 -0
- package/dist/resources/extensions/gsd/working-output-messages.js +64 -0
- package/dist/resources/extensions/shared/interview-ui.js +15 -4
- 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 +14 -14
- 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/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/chunks/167.js +2 -0
- 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/dist/web/standalone/package.json +1 -0
- package/dist/web/standalone/server.js +1 -1
- package/package.json +18 -7
- package/packages/contracts/dist/index.d.ts +3 -0
- package/packages/contracts/dist/index.d.ts.map +1 -0
- package/packages/contracts/dist/index.js +5 -0
- package/packages/contracts/dist/index.js.map +1 -0
- package/packages/contracts/dist/rpc.d.ts +549 -0
- package/packages/contracts/dist/rpc.d.ts.map +1 -0
- package/packages/contracts/dist/rpc.js +53 -0
- package/packages/contracts/dist/rpc.js.map +1 -0
- package/packages/contracts/dist/rpc.test.d.ts +2 -0
- package/packages/contracts/dist/rpc.test.d.ts.map +1 -0
- package/packages/contracts/dist/rpc.test.js +47 -0
- package/packages/contracts/dist/rpc.test.js.map +1 -0
- package/packages/contracts/dist/workflow.d.ts +180 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -0
- package/packages/contracts/dist/workflow.js +201 -0
- package/packages/contracts/dist/workflow.js.map +1 -0
- package/packages/contracts/package.json +39 -0
- package/packages/contracts/src/index.ts +5 -0
- package/packages/contracts/src/rpc.test.ts +72 -0
- package/packages/contracts/src/rpc.ts +286 -0
- package/packages/contracts/src/workflow.ts +213 -0
- package/packages/contracts/tsconfig.json +25 -0
- package/packages/daemon/package.json +3 -2
- package/packages/daemon/src/event-bridge.test.ts +2 -1
- package/packages/daemon/src/event-bridge.ts +1 -1
- package/packages/daemon/src/event-formatter.test.ts +1 -2
- package/packages/daemon/src/event-formatter.ts +1 -2
- package/packages/daemon/src/session-manager.ts +2 -2
- package/packages/daemon/src/types.ts +3 -18
- package/packages/mcp-server/dist/server.d.ts +13 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +77 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/session-manager.js +1 -1
- package/packages/mcp-server/dist/session-manager.js.map +1 -1
- package/packages/mcp-server/dist/types.d.ts +3 -11
- package/packages/mcp-server/dist/types.d.ts.map +1 -1
- package/packages/mcp-server/dist/types.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +2 -40
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -2
- package/packages/mcp-server/src/mcp-server.test.ts +138 -0
- package/packages/mcp-server/src/server.ts +99 -1
- package/packages/mcp-server/src/session-manager.ts +2 -2
- package/packages/mcp-server/src/types.ts +7 -18
- package/packages/mcp-server/src/workflow-tools.ts +2 -40
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/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/package.json +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/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 +67 -14
- 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 +27 -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 +112 -18
- 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 +12 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +54 -10
- 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/modes/rpc/rpc-types.d.ts +1 -512
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js +3 -7
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-types.js.map +1 -1
- package/packages/pi-coding-agent/package.json +2 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +87 -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 +100 -16
- 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 +41 -9
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +124 -18
- 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 +75 -9
- 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/modes/rpc/rpc-types.ts +3 -336
- 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.map +1 -1
- package/packages/pi-tui/dist/tui.js +1 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +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 +1 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/README.md +3 -3
- package/packages/rpc-client/dist/index.d.ts +1 -1
- package/packages/rpc-client/dist/index.d.ts.map +1 -1
- package/packages/rpc-client/dist/index.js.map +1 -1
- package/packages/rpc-client/dist/rpc-client.d.ts +2 -6
- package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -1
- package/packages/rpc-client/dist/rpc-client.js.map +1 -1
- package/packages/rpc-client/dist/rpc-types.d.ts +1 -565
- package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -1
- package/packages/rpc-client/dist/rpc-types.js +3 -11
- package/packages/rpc-client/dist/rpc-types.js.map +1 -1
- package/packages/rpc-client/package.json +4 -1
- package/packages/rpc-client/src/index.ts +1 -1
- package/packages/rpc-client/src/rpc-client.ts +3 -6
- package/packages/rpc-client/src/rpc-types.ts +3 -398
- package/packages/rpc-client/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/pkg/package.json +1 -1
- package/src/resources/extensions/github-sync/templates.ts +93 -88
- package/src/resources/extensions/github-sync/tests/inline-code.test.ts +66 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +10 -2
- package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
- package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +72 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
- package/src/resources/extensions/gsd/auto/loop.ts +416 -596
- package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
- package/src/resources/extensions/gsd/auto/phases.ts +82 -8
- package/src/resources/extensions/gsd/auto/run-unit.ts +24 -14
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-dispatch-outcome.ts +28 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-iteration.ts +52 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile-outcome.ts +58 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-reconcile.ts +71 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-retry.ts +90 -0
- package/src/resources/extensions/gsd/auto/workflow-custom-engine-verify-outcome.ts +50 -0
- package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +97 -0
- package/src/resources/extensions/gsd/auto/workflow-dispatch-ledger.ts +45 -0
- package/src/resources/extensions/gsd/auto/workflow-iteration-completion.ts +26 -0
- package/src/resources/extensions/gsd/auto/workflow-journal-reporter.ts +33 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +520 -0
- package/src/resources/extensions/gsd/auto/workflow-memory-pressure.ts +58 -0
- package/src/resources/extensions/gsd/auto/workflow-phase-reporter.ts +22 -0
- package/src/resources/extensions/gsd/auto/workflow-session-lock.ts +68 -0
- package/src/resources/extensions/gsd/auto/workflow-sidecar-iteration.ts +46 -0
- package/src/resources/extensions/gsd/auto/workflow-sidecar-queue.ts +46 -0
- package/src/resources/extensions/gsd/auto/workflow-turn-reporter.ts +68 -0
- package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +89 -0
- package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +38 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +61 -8
- package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +170 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +194 -56
- package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
- package/src/resources/extensions/gsd/auto-start.ts +7 -6
- package/src/resources/extensions/gsd/auto-verification.ts +5 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +85 -36
- package/src/resources/extensions/gsd/auto.ts +179 -2
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +30 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +11 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +30 -6
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
- package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
- package/src/resources/extensions/gsd/commands-ship.ts +24 -51
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +13 -0
- package/src/resources/extensions/gsd/component-loader.ts +5 -11
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +29 -0
- package/src/resources/extensions/gsd/db-adapter.ts +75 -0
- package/src/resources/extensions/gsd/db-base-schema.ts +383 -0
- package/src/resources/extensions/gsd/db-connection-cache.ts +45 -0
- package/src/resources/extensions/gsd/db-coordination-schema.ts +109 -0
- package/src/resources/extensions/gsd/db-decision-requirement-rows.ts +77 -0
- package/src/resources/extensions/gsd/db-gate-rows.ts +19 -0
- package/src/resources/extensions/gsd/db-lightweight-query-rows.ts +50 -0
- package/src/resources/extensions/gsd/db-memory-fts-schema.ts +66 -0
- package/src/resources/extensions/gsd/db-migration-backup.ts +34 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +451 -0
- package/src/resources/extensions/gsd/db-milestone-artifact-rows.ts +70 -0
- package/src/resources/extensions/gsd/db-open-state.ts +47 -0
- package/src/resources/extensions/gsd/db-provider.ts +148 -0
- package/src/resources/extensions/gsd/db-runtime-kv-schema.ts +30 -0
- package/src/resources/extensions/gsd/db-schema-metadata.ts +33 -0
- package/src/resources/extensions/gsd/db-task-slice-rows.ts +146 -0
- package/src/resources/extensions/gsd/db-transaction.ts +76 -0
- package/src/resources/extensions/gsd/db-verification-evidence-rows.ts +14 -0
- package/src/resources/extensions/gsd/db-verification-evidence-schema.ts +22 -0
- package/src/resources/extensions/gsd/escalation.ts +3 -1
- package/src/resources/extensions/gsd/graph.ts +12 -5
- package/src/resources/extensions/gsd/gsd-db.ts +379 -1660
- package/src/resources/extensions/gsd/guided-flow.ts +2 -2
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/legacy-telemetry.ts +99 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +4 -1
- package/src/resources/extensions/gsd/model-router.ts +10 -6
- package/src/resources/extensions/gsd/notification-widget.ts +25 -4
- package/src/resources/extensions/gsd/post-execution-checks.ts +35 -7
- package/src/resources/extensions/gsd/pr-evidence.ts +182 -0
- package/src/resources/extensions/gsd/pre-execution-checks.ts +4 -1
- package/src/resources/extensions/gsd/process-task-path.ts +81 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +9 -5
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +32 -30
- package/src/resources/extensions/gsd/prompts/complete-slice.md +23 -34
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +50 -96
- package/src/resources/extensions/gsd/prompts/discuss.md +81 -181
- package/src/resources/extensions/gsd/prompts/execute-task.md +40 -67
- package/src/resources/extensions/gsd/prompts/forensics.md +41 -84
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +29 -39
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +30 -65
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +25 -52
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +36 -36
- package/src/resources/extensions/gsd/prompts/guided-research-project.md +20 -38
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +45 -59
- package/src/resources/extensions/gsd/prompts/plan-slice.md +25 -87
- package/src/resources/extensions/gsd/prompts/queue.md +46 -53
- package/src/resources/extensions/gsd/prompts/refine-slice.md +23 -23
- package/src/resources/extensions/gsd/prompts/research-slice.md +23 -23
- package/src/resources/extensions/gsd/prompts/rethink.md +10 -10
- package/src/resources/extensions/gsd/prompts/system.md +65 -107
- package/src/resources/extensions/gsd/prompts/triage-captures.md +15 -15
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +24 -24
- package/src/resources/extensions/gsd/prompts/worktree-merge.md +35 -35
- package/src/resources/extensions/gsd/state.ts +6 -3
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/commands-eval-review.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/commands-ship-eval-warn.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/complete-milestone-prompt-rendering.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +19 -5
- package/src/resources/extensions/gsd/tests/component-loader.test.ts +2 -9
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/db-base-schema.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/db-connection-cache.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/db-coordination-schema.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-decision-requirement-rows.test.ts +135 -0
- package/src/resources/extensions/gsd/tests/db-gate-rows.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/db-lightweight-query-rows.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-memory-fts-schema.test.ts +86 -0
- package/src/resources/extensions/gsd/tests/db-migration-backup.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/db-migration-steps.integration.test.ts +428 -0
- package/src/resources/extensions/gsd/tests/db-migration-steps.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/db-milestone-artifact-rows.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/db-open-state.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/db-provider.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/db-runtime-kv-schema.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +115 -0
- package/src/resources/extensions/gsd/tests/db-task-slice-rows.test.ts +128 -0
- package/src/resources/extensions/gsd/tests/db-transaction.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/db-verification-evidence-schema.test.ts +76 -0
- package/src/resources/extensions/gsd/tests/discuss-headless-rendering.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +59 -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/forensics-prompt-rendering.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-requirements-prompt-rendering.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +5 -3
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/legacy-component-format-telemetry.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/legacy-telemetry.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +40 -16
- package/src/resources/extensions/gsd/tests/model-router.test.ts +33 -12
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/notification-widget.test.ts +40 -1
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-milestone-rendering.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +65 -16
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -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/pr-evidence.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/process-task-path.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/queue-prompt-rendering.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +32 -9
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-dispatch-outcome.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-iteration.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile-outcome.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-reconcile.test.ts +146 -0
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-retry.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/workflow-custom-engine-verify-outcome.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/workflow-dispatch-ledger.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/workflow-iteration-completion.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/workflow-journal-reporter.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +607 -0
- package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +20 -4
- package/src/resources/extensions/gsd/tests/workflow-memory-pressure.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/workflow-phase-reporter.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/workflow-session-lock.test.ts +135 -0
- package/src/resources/extensions/gsd/tests/workflow-sidecar-iteration.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/workflow-sidecar-queue.test.ts +116 -0
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/workflow-turn-reporter.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/workflow-unit-dispatch.test.ts +160 -0
- package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/working-output-messages.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +17 -33
- package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +15 -9
- package/src/resources/extensions/gsd/tools/complete-task.ts +4 -1
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +6 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +1 -1
- package/src/resources/extensions/gsd/uok/kernel.ts +10 -3
- package/src/resources/extensions/gsd/uok/plan-v2.ts +5 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +13 -13
- package/src/resources/extensions/gsd/workflow-manifest.ts +6 -15
- package/src/resources/extensions/gsd/workflow-projections.ts +5 -1
- package/src/resources/extensions/gsd/workflow-templates.ts +11 -0
- package/src/resources/extensions/gsd/working-output-messages.ts +120 -0
- package/src/resources/extensions/shared/interview-ui.ts +18 -5
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
- package/dist/web/standalone/.next/server/chunks/6336.js +0 -1
- /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → vIAZSyxIuvqNkCvXt9oqb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TzEVJ1Lh8vbez4n4Q9TqQ → vIAZSyxIuvqNkCvXt9oqb}/_ssgManifest.js +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: GSD database facade, schema, migrations, and single-writer write API.
|
|
1
3
|
// GSD Database Abstraction Layer
|
|
2
4
|
// Provides a SQLite database with provider fallback chain:
|
|
3
5
|
// node:sqlite (built-in) → better-sqlite3 (npm) → null (unavailable)
|
|
@@ -28,315 +30,83 @@ import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
|
28
30
|
import type { GsdWorkspace, MilestoneScope } from "./workspace.js";
|
|
29
31
|
import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
|
|
30
32
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
33
|
+
import { createDbAdapter, type DbAdapter } from "./db-adapter.js";
|
|
34
|
+
import { createBaseSchemaObjects } from "./db-base-schema.js";
|
|
35
|
+
import { createCoordinationTablesV24 } from "./db-coordination-schema.js";
|
|
36
|
+
import { createDbConnectionCache, type DbConnectionCacheEntry } from "./db-connection-cache.js";
|
|
37
|
+
import {
|
|
38
|
+
emptyTaskStatusCounts,
|
|
39
|
+
rowToActiveTaskSummary,
|
|
40
|
+
rowToIdStatusSummary,
|
|
41
|
+
rowToTaskStatusCounts,
|
|
42
|
+
rowsToStringColumn,
|
|
43
|
+
type ActiveTaskSummary,
|
|
44
|
+
type IdStatusSummary,
|
|
45
|
+
type TaskStatusCounts,
|
|
46
|
+
} from "./db-lightweight-query-rows.js";
|
|
47
|
+
import {
|
|
48
|
+
rowToActiveDecision,
|
|
49
|
+
rowToActiveRequirement,
|
|
50
|
+
rowToDecision,
|
|
51
|
+
rowToRequirement,
|
|
52
|
+
rowsToRequirementCounts,
|
|
53
|
+
} from "./db-decision-requirement-rows.js";
|
|
54
|
+
import { rowToGate } from "./db-gate-rows.js";
|
|
55
|
+
import { rowToArtifact, rowToMilestone, type ArtifactRow, type MilestoneRow } from "./db-milestone-artifact-rows.js";
|
|
56
|
+
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
57
|
+
import {
|
|
58
|
+
applyMigrationV2Artifacts,
|
|
59
|
+
applyMigrationV3Memories,
|
|
60
|
+
applyMigrationV4DecisionMadeBy,
|
|
61
|
+
applyMigrationV5HierarchyTables,
|
|
62
|
+
applyMigrationV6SliceSummaries,
|
|
63
|
+
applyMigrationV7Dependencies,
|
|
64
|
+
applyMigrationV8PlanningFields,
|
|
65
|
+
applyMigrationV9Ordering,
|
|
66
|
+
applyMigrationV10ReplanTrigger,
|
|
67
|
+
applyMigrationV11TaskPlanning,
|
|
68
|
+
applyMigrationV12QualityGates,
|
|
69
|
+
applyMigrationV13HotPathIndexes,
|
|
70
|
+
applyMigrationV14SliceDependencies,
|
|
71
|
+
applyMigrationV15AuditTables,
|
|
72
|
+
applyMigrationV16EscalationSource,
|
|
73
|
+
applyMigrationV17TaskEscalation,
|
|
74
|
+
applyMigrationV18MemorySources,
|
|
75
|
+
applyMigrationV19MemoryFts,
|
|
76
|
+
applyMigrationV20MemoryRelations,
|
|
77
|
+
applyMigrationV21StructuredMemories,
|
|
78
|
+
applyMigrationV22QualityGateRepair,
|
|
79
|
+
applyMigrationV23MilestoneQueue,
|
|
80
|
+
applyMigrationV26MilestoneCommitAttributions,
|
|
81
|
+
} from "./db-migration-steps.js";
|
|
82
|
+
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
83
|
+
import { createDbOpenState, type DbOpenPhase } from "./db-open-state.js";
|
|
84
|
+
import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
|
|
85
|
+
import { ensureColumn, getCurrentSchemaVersion, recordSchemaVersion } from "./db-schema-metadata.js";
|
|
86
|
+
import { rowToSlice, rowToTask, type SliceRow, type TaskRow } from "./db-task-slice-rows.js";
|
|
87
|
+
import { createDbTransactionRunner } from "./db-transaction.js";
|
|
88
|
+
import { ensureVerificationEvidenceDedupIndex } from "./db-verification-evidence-schema.js";
|
|
89
|
+
import { createSqliteProviderLoader, suppressSqliteWarning, type DbProviderName, type SqliteFallbackOpen } from "./db-provider.js";
|
|
31
90
|
// Type-only import to avoid a circular runtime dep. The runtime side of
|
|
32
91
|
// workflow-manifest.ts depends on this file, but the StateManifest type is
|
|
33
92
|
// pure structure with no runtime coupling.
|
|
34
93
|
import type { StateManifest } from "./workflow-manifest.js";
|
|
35
94
|
|
|
36
95
|
const _require = createRequire(import.meta.url);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
interface DbStatement {
|
|
40
|
-
run(...params: unknown[]): unknown;
|
|
41
|
-
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
42
|
-
all(...params: unknown[]): Record<string, unknown>[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface DbAdapter {
|
|
46
|
-
exec(sql: string): void;
|
|
47
|
-
prepare(sql: string): DbStatement;
|
|
48
|
-
close(): void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type ProviderName = "node:sqlite" | "better-sqlite3";
|
|
52
|
-
|
|
53
|
-
let providerName: ProviderName | null = null;
|
|
54
|
-
let providerModule: unknown = null;
|
|
55
|
-
let loadAttempted = false;
|
|
56
|
-
|
|
57
|
-
function suppressSqliteWarning(): void {
|
|
58
|
-
const origEmit = process.emit;
|
|
59
|
-
// Override via loose cast: Node's overloaded emit signature is not directly assignable.
|
|
60
|
-
(process as any).emit = function (event: string, ...args: unknown[]): boolean {
|
|
61
|
-
if (
|
|
62
|
-
event === "warning" &&
|
|
63
|
-
args[0] &&
|
|
64
|
-
typeof args[0] === "object" &&
|
|
65
|
-
"name" in args[0] &&
|
|
66
|
-
(args[0] as { name: string }).name === "ExperimentalWarning" &&
|
|
67
|
-
"message" in args[0] &&
|
|
68
|
-
typeof (args[0] as { message: string }).message === "string" &&
|
|
69
|
-
(args[0] as { message: string }).message.includes("SQLite")
|
|
70
|
-
) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return origEmit.apply(process, [event, ...args] as Parameters<typeof process.emit>) as unknown as boolean;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function loadProvider(): void {
|
|
78
|
-
if (loadAttempted) return;
|
|
79
|
-
loadAttempted = true;
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
suppressSqliteWarning();
|
|
83
|
-
const mod = _require("node:sqlite");
|
|
84
|
-
if (mod.DatabaseSync) {
|
|
85
|
-
providerModule = mod;
|
|
86
|
-
providerName = "node:sqlite";
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
// unavailable
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const mod = _require(BETTER_SQLITE3_PACKAGE);
|
|
95
|
-
if (typeof mod === "function" || (mod && mod.default)) {
|
|
96
|
-
providerModule = mod.default || mod;
|
|
97
|
-
providerName = "better-sqlite3";
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
// unavailable
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
105
|
-
const versionHint = nodeMajor < 22
|
|
106
|
-
? ` GSD requires Node >= 22.0.0 (current: v${process.versions.node}). Upgrade Node to fix this.`
|
|
107
|
-
: "";
|
|
108
|
-
process.stderr.write(
|
|
109
|
-
`gsd-db: No SQLite provider available (tried node:sqlite, better-sqlite3).${versionHint}\n`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
96
|
+
type ProviderName = DbProviderName;
|
|
112
97
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return { ...(row as Record<string, unknown>) };
|
|
117
|
-
}
|
|
118
|
-
return row as Record<string, unknown>;
|
|
119
|
-
}
|
|
98
|
+
export type { ArtifactRow, MilestoneRow } from "./db-milestone-artifact-rows.js";
|
|
99
|
+
export type { ActiveTaskSummary, IdStatusSummary, TaskStatusCounts } from "./db-lightweight-query-rows.js";
|
|
100
|
+
export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
|
|
120
101
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
102
|
+
const providerLoader = createSqliteProviderLoader({
|
|
103
|
+
requireModule: (id: string) => _require(id),
|
|
104
|
+
suppressSqliteWarning,
|
|
105
|
+
nodeVersion: process.versions.node,
|
|
106
|
+
writeStderr: (message: string) => process.stderr.write(message),
|
|
107
|
+
});
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
const db = rawDb as {
|
|
127
|
-
exec(sql: string): void;
|
|
128
|
-
prepare(sql: string): {
|
|
129
|
-
run(...args: unknown[]): unknown;
|
|
130
|
-
get(...args: unknown[]): unknown;
|
|
131
|
-
all(...args: unknown[]): unknown[];
|
|
132
|
-
};
|
|
133
|
-
close(): void;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const stmtCache = new Map<string, DbStatement>();
|
|
137
|
-
|
|
138
|
-
function wrapStmt(raw: { run(...a: unknown[]): unknown; get(...a: unknown[]): unknown; all(...a: unknown[]): unknown[] }): DbStatement {
|
|
139
|
-
return {
|
|
140
|
-
run(...params: unknown[]): unknown {
|
|
141
|
-
return raw.run(...params);
|
|
142
|
-
},
|
|
143
|
-
get(...params: unknown[]): Record<string, unknown> | undefined {
|
|
144
|
-
return normalizeRow(raw.get(...params));
|
|
145
|
-
},
|
|
146
|
-
all(...params: unknown[]): Record<string, unknown>[] {
|
|
147
|
-
return normalizeRows(raw.all(...params));
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
exec(sql: string): void {
|
|
154
|
-
db.exec(sql);
|
|
155
|
-
},
|
|
156
|
-
prepare(sql: string): DbStatement {
|
|
157
|
-
let cached = stmtCache.get(sql);
|
|
158
|
-
if (cached) return cached;
|
|
159
|
-
cached = wrapStmt(db.prepare(sql));
|
|
160
|
-
stmtCache.set(sql, cached);
|
|
161
|
-
return cached;
|
|
162
|
-
},
|
|
163
|
-
close(): void {
|
|
164
|
-
stmtCache.clear();
|
|
165
|
-
db.close();
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function openRawDb(path: string): unknown {
|
|
171
|
-
loadProvider();
|
|
172
|
-
if (!providerModule || !providerName) return null;
|
|
173
|
-
|
|
174
|
-
if (providerName === "node:sqlite") {
|
|
175
|
-
const { DatabaseSync } = providerModule as {
|
|
176
|
-
DatabaseSync: new (path: string) => unknown;
|
|
177
|
-
};
|
|
178
|
-
return new DatabaseSync(path);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const Database = providerModule as new (path: string) => unknown;
|
|
182
|
-
return new Database(path);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export const SCHEMA_VERSION = 25;
|
|
186
|
-
|
|
187
|
-
function indexExists(db: DbAdapter, name: string): boolean {
|
|
188
|
-
return !!db.prepare(
|
|
189
|
-
"SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?",
|
|
190
|
-
).get(name);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Create the v24 coordination tables (workers, milestone_leases,
|
|
195
|
-
* unit_dispatches, cancellation_requests, command_queue) and their indexes.
|
|
196
|
-
*
|
|
197
|
-
* Idempotent — uses IF NOT EXISTS throughout. Called from both the
|
|
198
|
-
* fresh-install path and the v24 migration block in migrateSchema().
|
|
199
|
-
*
|
|
200
|
-
* Single-host invariant: these tables coordinate concurrent auto-mode
|
|
201
|
-
* workers via shared SQLite WAL on local disk only. NFS / network
|
|
202
|
-
* filesystems break the coordination semantics — multi-host execution
|
|
203
|
-
* needs a real coordinator (etcd, Postgres) and is out of scope.
|
|
204
|
-
*/
|
|
205
|
-
function createCoordinationTablesV24(db: DbAdapter): void {
|
|
206
|
-
const ddl = [
|
|
207
|
-
`CREATE TABLE IF NOT EXISTS workers (
|
|
208
|
-
worker_id TEXT PRIMARY KEY,
|
|
209
|
-
host TEXT NOT NULL,
|
|
210
|
-
pid INTEGER NOT NULL,
|
|
211
|
-
started_at TEXT NOT NULL,
|
|
212
|
-
version TEXT NOT NULL,
|
|
213
|
-
last_heartbeat_at TEXT NOT NULL,
|
|
214
|
-
status TEXT NOT NULL,
|
|
215
|
-
project_root_realpath TEXT NOT NULL
|
|
216
|
-
)`,
|
|
217
|
-
`CREATE TABLE IF NOT EXISTS milestone_leases (
|
|
218
|
-
milestone_id TEXT PRIMARY KEY,
|
|
219
|
-
worker_id TEXT NOT NULL,
|
|
220
|
-
fencing_token INTEGER NOT NULL,
|
|
221
|
-
acquired_at TEXT NOT NULL,
|
|
222
|
-
expires_at TEXT NOT NULL,
|
|
223
|
-
status TEXT NOT NULL,
|
|
224
|
-
FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
|
|
225
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
226
|
-
)`,
|
|
227
|
-
`CREATE TABLE IF NOT EXISTS unit_dispatches (
|
|
228
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
229
|
-
trace_id TEXT NOT NULL,
|
|
230
|
-
turn_id TEXT,
|
|
231
|
-
worker_id TEXT NOT NULL,
|
|
232
|
-
milestone_lease_token INTEGER NOT NULL,
|
|
233
|
-
milestone_id TEXT NOT NULL,
|
|
234
|
-
slice_id TEXT,
|
|
235
|
-
task_id TEXT,
|
|
236
|
-
unit_type TEXT NOT NULL,
|
|
237
|
-
unit_id TEXT NOT NULL,
|
|
238
|
-
status TEXT NOT NULL,
|
|
239
|
-
attempt_n INTEGER NOT NULL DEFAULT 1,
|
|
240
|
-
started_at TEXT NOT NULL,
|
|
241
|
-
ended_at TEXT,
|
|
242
|
-
exit_reason TEXT,
|
|
243
|
-
error_summary TEXT,
|
|
244
|
-
verification_evidence_id INTEGER,
|
|
245
|
-
next_run_at TEXT,
|
|
246
|
-
retry_after_ms INTEGER,
|
|
247
|
-
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
248
|
-
last_error_code TEXT,
|
|
249
|
-
last_error_at TEXT,
|
|
250
|
-
FOREIGN KEY (worker_id) REFERENCES workers(worker_id),
|
|
251
|
-
FOREIGN KEY (verification_evidence_id) REFERENCES verification_evidence(id)
|
|
252
|
-
)`,
|
|
253
|
-
`CREATE TABLE IF NOT EXISTS cancellation_requests (
|
|
254
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
255
|
-
requested_at TEXT NOT NULL,
|
|
256
|
-
requested_by TEXT NOT NULL,
|
|
257
|
-
scope TEXT NOT NULL,
|
|
258
|
-
scope_id TEXT NOT NULL,
|
|
259
|
-
dispatch_id INTEGER,
|
|
260
|
-
reason TEXT NOT NULL,
|
|
261
|
-
status TEXT NOT NULL,
|
|
262
|
-
acked_at TEXT,
|
|
263
|
-
acked_worker_id TEXT,
|
|
264
|
-
FOREIGN KEY (dispatch_id) REFERENCES unit_dispatches(id),
|
|
265
|
-
FOREIGN KEY (acked_worker_id) REFERENCES workers(worker_id)
|
|
266
|
-
)`,
|
|
267
|
-
`CREATE TABLE IF NOT EXISTS command_queue (
|
|
268
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
269
|
-
target_worker TEXT,
|
|
270
|
-
command TEXT NOT NULL,
|
|
271
|
-
args_json TEXT NOT NULL DEFAULT '{}',
|
|
272
|
-
enqueued_at TEXT NOT NULL,
|
|
273
|
-
claimed_at TEXT,
|
|
274
|
-
claimed_by TEXT,
|
|
275
|
-
completed_at TEXT,
|
|
276
|
-
result_json TEXT
|
|
277
|
-
)`,
|
|
278
|
-
];
|
|
279
|
-
for (const stmt of ddl) db.exec(stmt);
|
|
280
|
-
|
|
281
|
-
// Indexes — created here so both fresh-install and v24-migration paths
|
|
282
|
-
// produce identical structure.
|
|
283
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_active ON unit_dispatches(milestone_id, status)");
|
|
284
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_unit_dispatches_trace ON unit_dispatches(trace_id, turn_id)");
|
|
285
|
-
// Partial unique index — prevents two workers from claiming the same
|
|
286
|
-
// unit concurrently. Codex review MEDIUM B2: enforces double-claim guard
|
|
287
|
-
// at the DB level.
|
|
288
|
-
db.exec(
|
|
289
|
-
"CREATE UNIQUE INDEX IF NOT EXISTS idx_unit_dispatches_active_per_unit "
|
|
290
|
-
+ "ON unit_dispatches(unit_id) WHERE status IN ('claimed','running')",
|
|
291
|
-
);
|
|
292
|
-
// command_queue index — SQLite indexes NULLs in B-trees, so this single
|
|
293
|
-
// index serves both targeted (target_worker = ?) and broadcast
|
|
294
|
-
// (target_worker IS NULL) queries. Codex review LOW B4 documented.
|
|
295
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_command_queue_pending ON command_queue(target_worker, claimed_at)");
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Create the v25 runtime_kv table. Idempotent — uses IF NOT EXISTS.
|
|
300
|
-
*
|
|
301
|
-
* STRICT INVARIANT: runtime_kv is NON-CORRECTNESS-CRITICAL. UI cursors,
|
|
302
|
-
* dashboard caches, last-seen-version markers, resume cursors, and other
|
|
303
|
-
* "soft" state are OK. Anything that drives auto-mode control flow gets
|
|
304
|
-
* typed columns in unit_dispatches / workers / milestone_leases — never
|
|
305
|
-
* a bag of JSON in runtime_kv.
|
|
306
|
-
*
|
|
307
|
-
* Scope partitioning: ('global', '', key) for project-wide values;
|
|
308
|
-
* ('worker', worker_id, key) for per-worker state (resume cursors);
|
|
309
|
-
* ('milestone', milestone_id, key) for per-milestone soft state.
|
|
310
|
-
*/
|
|
311
|
-
function createRuntimeKvTableV25(db: DbAdapter): void {
|
|
312
|
-
db.exec(`
|
|
313
|
-
CREATE TABLE IF NOT EXISTS runtime_kv (
|
|
314
|
-
scope TEXT NOT NULL,
|
|
315
|
-
scope_id TEXT NOT NULL DEFAULT '',
|
|
316
|
-
key TEXT NOT NULL,
|
|
317
|
-
value_json TEXT NOT NULL,
|
|
318
|
-
updated_at TEXT NOT NULL,
|
|
319
|
-
PRIMARY KEY (scope, scope_id, key)
|
|
320
|
-
)
|
|
321
|
-
`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function dedupeVerificationEvidenceRows(db: DbAdapter): void {
|
|
325
|
-
db.exec(`
|
|
326
|
-
DELETE FROM verification_evidence
|
|
327
|
-
WHERE rowid NOT IN (
|
|
328
|
-
SELECT MIN(rowid)
|
|
329
|
-
FROM verification_evidence
|
|
330
|
-
GROUP BY task_id, slice_id, milestone_id, command, verdict
|
|
331
|
-
)
|
|
332
|
-
`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function ensureVerificationEvidenceDedupIndex(db: DbAdapter): void {
|
|
336
|
-
if (indexExists(db, "idx_verification_evidence_dedup")) return;
|
|
337
|
-
dedupeVerificationEvidenceRows(db);
|
|
338
|
-
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
339
|
-
}
|
|
109
|
+
export const SCHEMA_VERSION = 26;
|
|
340
110
|
|
|
341
111
|
function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
342
112
|
if (fileBacked) db.exec("PRAGMA journal_mode=WAL");
|
|
@@ -350,370 +120,10 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
350
120
|
|
|
351
121
|
db.exec("BEGIN");
|
|
352
122
|
try {
|
|
353
|
-
db
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
)
|
|
358
|
-
`);
|
|
359
|
-
|
|
360
|
-
db.exec(`
|
|
361
|
-
CREATE TABLE IF NOT EXISTS decisions (
|
|
362
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
363
|
-
id TEXT NOT NULL UNIQUE,
|
|
364
|
-
when_context TEXT NOT NULL DEFAULT '',
|
|
365
|
-
scope TEXT NOT NULL DEFAULT '',
|
|
366
|
-
decision TEXT NOT NULL DEFAULT '',
|
|
367
|
-
choice TEXT NOT NULL DEFAULT '',
|
|
368
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
369
|
-
revisable TEXT NOT NULL DEFAULT '',
|
|
370
|
-
made_by TEXT NOT NULL DEFAULT 'agent',
|
|
371
|
-
source TEXT NOT NULL DEFAULT 'discussion', -- ADR-011 P2: 'discussion' | 'planning' | 'escalation'
|
|
372
|
-
superseded_by TEXT DEFAULT NULL
|
|
373
|
-
)
|
|
374
|
-
`);
|
|
375
|
-
|
|
376
|
-
db.exec(`
|
|
377
|
-
CREATE TABLE IF NOT EXISTS requirements (
|
|
378
|
-
id TEXT PRIMARY KEY,
|
|
379
|
-
class TEXT NOT NULL DEFAULT '',
|
|
380
|
-
status TEXT NOT NULL DEFAULT '',
|
|
381
|
-
description TEXT NOT NULL DEFAULT '',
|
|
382
|
-
why TEXT NOT NULL DEFAULT '',
|
|
383
|
-
source TEXT NOT NULL DEFAULT '',
|
|
384
|
-
primary_owner TEXT NOT NULL DEFAULT '',
|
|
385
|
-
supporting_slices TEXT NOT NULL DEFAULT '',
|
|
386
|
-
validation TEXT NOT NULL DEFAULT '',
|
|
387
|
-
notes TEXT NOT NULL DEFAULT '',
|
|
388
|
-
full_content TEXT NOT NULL DEFAULT '',
|
|
389
|
-
superseded_by TEXT DEFAULT NULL
|
|
390
|
-
)
|
|
391
|
-
`);
|
|
392
|
-
|
|
393
|
-
db.exec(`
|
|
394
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
395
|
-
path TEXT PRIMARY KEY,
|
|
396
|
-
artifact_type TEXT NOT NULL DEFAULT '',
|
|
397
|
-
milestone_id TEXT DEFAULT NULL,
|
|
398
|
-
slice_id TEXT DEFAULT NULL,
|
|
399
|
-
task_id TEXT DEFAULT NULL,
|
|
400
|
-
full_content TEXT NOT NULL DEFAULT '',
|
|
401
|
-
imported_at TEXT NOT NULL DEFAULT ''
|
|
402
|
-
)
|
|
403
|
-
`);
|
|
404
|
-
|
|
405
|
-
db.exec(`
|
|
406
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
407
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
408
|
-
id TEXT NOT NULL UNIQUE,
|
|
409
|
-
category TEXT NOT NULL,
|
|
410
|
-
content TEXT NOT NULL,
|
|
411
|
-
confidence REAL NOT NULL DEFAULT 0.8,
|
|
412
|
-
source_unit_type TEXT,
|
|
413
|
-
source_unit_id TEXT,
|
|
414
|
-
created_at TEXT NOT NULL,
|
|
415
|
-
updated_at TEXT NOT NULL,
|
|
416
|
-
superseded_by TEXT DEFAULT NULL,
|
|
417
|
-
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
418
|
-
scope TEXT NOT NULL DEFAULT 'project',
|
|
419
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
420
|
-
structured_fields TEXT DEFAULT NULL
|
|
421
|
-
)
|
|
422
|
-
`);
|
|
423
|
-
|
|
424
|
-
db.exec(`
|
|
425
|
-
CREATE TABLE IF NOT EXISTS memory_processed_units (
|
|
426
|
-
unit_key TEXT PRIMARY KEY,
|
|
427
|
-
activity_file TEXT,
|
|
428
|
-
processed_at TEXT NOT NULL
|
|
429
|
-
)
|
|
430
|
-
`);
|
|
431
|
-
|
|
432
|
-
db.exec(`
|
|
433
|
-
CREATE TABLE IF NOT EXISTS memory_sources (
|
|
434
|
-
id TEXT PRIMARY KEY,
|
|
435
|
-
kind TEXT NOT NULL,
|
|
436
|
-
uri TEXT,
|
|
437
|
-
title TEXT,
|
|
438
|
-
content TEXT NOT NULL,
|
|
439
|
-
content_hash TEXT NOT NULL UNIQUE,
|
|
440
|
-
imported_at TEXT NOT NULL,
|
|
441
|
-
scope TEXT NOT NULL DEFAULT 'project',
|
|
442
|
-
tags TEXT NOT NULL DEFAULT '[]'
|
|
443
|
-
)
|
|
444
|
-
`);
|
|
445
|
-
|
|
446
|
-
db.exec(`
|
|
447
|
-
CREATE TABLE IF NOT EXISTS memory_embeddings (
|
|
448
|
-
memory_id TEXT PRIMARY KEY,
|
|
449
|
-
model TEXT NOT NULL,
|
|
450
|
-
dim INTEGER NOT NULL,
|
|
451
|
-
vector BLOB NOT NULL,
|
|
452
|
-
updated_at TEXT NOT NULL
|
|
453
|
-
)
|
|
454
|
-
`);
|
|
455
|
-
|
|
456
|
-
db.exec(`
|
|
457
|
-
CREATE TABLE IF NOT EXISTS memory_relations (
|
|
458
|
-
from_id TEXT NOT NULL,
|
|
459
|
-
to_id TEXT NOT NULL,
|
|
460
|
-
rel TEXT NOT NULL,
|
|
461
|
-
confidence REAL NOT NULL DEFAULT 0.8,
|
|
462
|
-
created_at TEXT NOT NULL,
|
|
463
|
-
PRIMARY KEY (from_id, to_id, rel)
|
|
464
|
-
)
|
|
465
|
-
`);
|
|
466
|
-
|
|
467
|
-
// FTS5 virtual table mirroring memories.content for fast keyword search.
|
|
468
|
-
// Optional — if the SQLite build lacks FTS5, we fall back to LIKE scans.
|
|
469
|
-
tryCreateMemoriesFts(db);
|
|
470
|
-
|
|
471
|
-
db.exec(`
|
|
472
|
-
CREATE TABLE IF NOT EXISTS milestones (
|
|
473
|
-
id TEXT PRIMARY KEY,
|
|
474
|
-
title TEXT NOT NULL DEFAULT '',
|
|
475
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
476
|
-
depends_on TEXT NOT NULL DEFAULT '[]',
|
|
477
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
478
|
-
completed_at TEXT DEFAULT NULL,
|
|
479
|
-
vision TEXT NOT NULL DEFAULT '',
|
|
480
|
-
success_criteria TEXT NOT NULL DEFAULT '[]',
|
|
481
|
-
key_risks TEXT NOT NULL DEFAULT '[]',
|
|
482
|
-
proof_strategy TEXT NOT NULL DEFAULT '[]',
|
|
483
|
-
verification_contract TEXT NOT NULL DEFAULT '',
|
|
484
|
-
verification_integration TEXT NOT NULL DEFAULT '',
|
|
485
|
-
verification_operational TEXT NOT NULL DEFAULT '',
|
|
486
|
-
verification_uat TEXT NOT NULL DEFAULT '',
|
|
487
|
-
definition_of_done TEXT NOT NULL DEFAULT '[]',
|
|
488
|
-
requirement_coverage TEXT NOT NULL DEFAULT '',
|
|
489
|
-
boundary_map_markdown TEXT NOT NULL DEFAULT '',
|
|
490
|
-
sequence INTEGER DEFAULT 0
|
|
491
|
-
)
|
|
492
|
-
`);
|
|
493
|
-
|
|
494
|
-
db.exec(`
|
|
495
|
-
CREATE TABLE IF NOT EXISTS slices (
|
|
496
|
-
milestone_id TEXT NOT NULL,
|
|
497
|
-
id TEXT NOT NULL,
|
|
498
|
-
title TEXT NOT NULL DEFAULT '',
|
|
499
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
500
|
-
risk TEXT NOT NULL DEFAULT 'medium',
|
|
501
|
-
depends TEXT NOT NULL DEFAULT '[]',
|
|
502
|
-
demo TEXT NOT NULL DEFAULT '',
|
|
503
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
504
|
-
completed_at TEXT DEFAULT NULL,
|
|
505
|
-
full_summary_md TEXT NOT NULL DEFAULT '',
|
|
506
|
-
full_uat_md TEXT NOT NULL DEFAULT '',
|
|
507
|
-
goal TEXT NOT NULL DEFAULT '',
|
|
508
|
-
success_criteria TEXT NOT NULL DEFAULT '',
|
|
509
|
-
proof_level TEXT NOT NULL DEFAULT '',
|
|
510
|
-
integration_closure TEXT NOT NULL DEFAULT '',
|
|
511
|
-
observability_impact TEXT NOT NULL DEFAULT '',
|
|
512
|
-
sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
|
|
513
|
-
replan_triggered_at TEXT DEFAULT NULL,
|
|
514
|
-
is_sketch INTEGER NOT NULL DEFAULT 0, -- ADR-011: 1 = slice is a sketch awaiting refinement
|
|
515
|
-
sketch_scope TEXT NOT NULL DEFAULT '', -- ADR-011: 2-3 sentence rough scope from plan-milestone
|
|
516
|
-
PRIMARY KEY (milestone_id, id),
|
|
517
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
518
|
-
)
|
|
519
|
-
`);
|
|
520
|
-
|
|
521
|
-
db.exec(`
|
|
522
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
523
|
-
milestone_id TEXT NOT NULL,
|
|
524
|
-
slice_id TEXT NOT NULL,
|
|
525
|
-
id TEXT NOT NULL,
|
|
526
|
-
title TEXT NOT NULL DEFAULT '',
|
|
527
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
528
|
-
one_liner TEXT NOT NULL DEFAULT '',
|
|
529
|
-
narrative TEXT NOT NULL DEFAULT '',
|
|
530
|
-
verification_result TEXT NOT NULL DEFAULT '',
|
|
531
|
-
duration TEXT NOT NULL DEFAULT '',
|
|
532
|
-
completed_at TEXT DEFAULT NULL,
|
|
533
|
-
blocker_discovered INTEGER DEFAULT 0,
|
|
534
|
-
blocker_source TEXT NOT NULL DEFAULT '', -- ADR-011 P2: provenance for blocker_discovered (e.g. 'reject-escalation')
|
|
535
|
-
escalation_pending INTEGER NOT NULL DEFAULT 0, -- ADR-011 P2: pause-on-escalation flag
|
|
536
|
-
escalation_awaiting_review INTEGER NOT NULL DEFAULT 0, -- ADR-011 P2: artifact exists but continueWithDefault=true (no pause)
|
|
537
|
-
escalation_artifact_path TEXT DEFAULT NULL, -- ADR-011 P2: path to T##-ESCALATION.json
|
|
538
|
-
escalation_override_applied_at TEXT DEFAULT NULL, -- ADR-011 P2: DB claim lock for idempotent override injection
|
|
539
|
-
deviations TEXT NOT NULL DEFAULT '',
|
|
540
|
-
known_issues TEXT NOT NULL DEFAULT '',
|
|
541
|
-
key_files TEXT NOT NULL DEFAULT '[]',
|
|
542
|
-
key_decisions TEXT NOT NULL DEFAULT '[]',
|
|
543
|
-
full_summary_md TEXT NOT NULL DEFAULT '',
|
|
544
|
-
description TEXT NOT NULL DEFAULT '',
|
|
545
|
-
estimate TEXT NOT NULL DEFAULT '',
|
|
546
|
-
files TEXT NOT NULL DEFAULT '[]',
|
|
547
|
-
verify TEXT NOT NULL DEFAULT '',
|
|
548
|
-
inputs TEXT NOT NULL DEFAULT '[]',
|
|
549
|
-
expected_output TEXT NOT NULL DEFAULT '[]',
|
|
550
|
-
observability_impact TEXT NOT NULL DEFAULT '',
|
|
551
|
-
full_plan_md TEXT NOT NULL DEFAULT '',
|
|
552
|
-
sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
|
|
553
|
-
PRIMARY KEY (milestone_id, slice_id, id),
|
|
554
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
555
|
-
)
|
|
556
|
-
`);
|
|
557
|
-
|
|
558
|
-
db.exec(`
|
|
559
|
-
CREATE TABLE IF NOT EXISTS verification_evidence (
|
|
560
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
561
|
-
task_id TEXT NOT NULL DEFAULT '',
|
|
562
|
-
slice_id TEXT NOT NULL DEFAULT '',
|
|
563
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
564
|
-
command TEXT NOT NULL DEFAULT '',
|
|
565
|
-
exit_code INTEGER DEFAULT 0,
|
|
566
|
-
verdict TEXT NOT NULL DEFAULT '',
|
|
567
|
-
duration_ms INTEGER DEFAULT 0,
|
|
568
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
569
|
-
FOREIGN KEY (milestone_id, slice_id, task_id) REFERENCES tasks(milestone_id, slice_id, id)
|
|
570
|
-
)
|
|
571
|
-
`);
|
|
572
|
-
|
|
573
|
-
db.exec(`
|
|
574
|
-
CREATE TABLE IF NOT EXISTS replan_history (
|
|
575
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
576
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
577
|
-
slice_id TEXT DEFAULT NULL,
|
|
578
|
-
task_id TEXT DEFAULT NULL,
|
|
579
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
580
|
-
previous_artifact_path TEXT DEFAULT NULL,
|
|
581
|
-
replacement_artifact_path TEXT DEFAULT NULL,
|
|
582
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
583
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
584
|
-
)
|
|
585
|
-
`);
|
|
586
|
-
|
|
587
|
-
db.exec(`
|
|
588
|
-
CREATE TABLE IF NOT EXISTS assessments (
|
|
589
|
-
path TEXT PRIMARY KEY,
|
|
590
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
591
|
-
slice_id TEXT DEFAULT NULL,
|
|
592
|
-
task_id TEXT DEFAULT NULL,
|
|
593
|
-
status TEXT NOT NULL DEFAULT '',
|
|
594
|
-
scope TEXT NOT NULL DEFAULT '',
|
|
595
|
-
full_content TEXT NOT NULL DEFAULT '',
|
|
596
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
597
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
598
|
-
)
|
|
599
|
-
`);
|
|
600
|
-
|
|
601
|
-
db.exec(`
|
|
602
|
-
CREATE TABLE IF NOT EXISTS quality_gates (
|
|
603
|
-
milestone_id TEXT NOT NULL,
|
|
604
|
-
slice_id TEXT NOT NULL,
|
|
605
|
-
gate_id TEXT NOT NULL,
|
|
606
|
-
scope TEXT NOT NULL DEFAULT 'slice',
|
|
607
|
-
task_id TEXT NOT NULL DEFAULT '',
|
|
608
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
609
|
-
verdict TEXT NOT NULL DEFAULT '',
|
|
610
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
611
|
-
findings TEXT NOT NULL DEFAULT '',
|
|
612
|
-
evaluated_at TEXT DEFAULT NULL,
|
|
613
|
-
PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
|
|
614
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
615
|
-
)
|
|
616
|
-
`);
|
|
617
|
-
|
|
618
|
-
// Slice dependency junction table (v14)
|
|
619
|
-
db.exec(`
|
|
620
|
-
CREATE TABLE IF NOT EXISTS slice_dependencies (
|
|
621
|
-
milestone_id TEXT NOT NULL,
|
|
622
|
-
slice_id TEXT NOT NULL,
|
|
623
|
-
depends_on_slice_id TEXT NOT NULL,
|
|
624
|
-
PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
|
|
625
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
|
|
626
|
-
FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
|
|
627
|
-
)
|
|
628
|
-
`);
|
|
629
|
-
|
|
630
|
-
db.exec(`
|
|
631
|
-
CREATE TABLE IF NOT EXISTS gate_runs (
|
|
632
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
633
|
-
trace_id TEXT NOT NULL,
|
|
634
|
-
turn_id TEXT NOT NULL,
|
|
635
|
-
gate_id TEXT NOT NULL,
|
|
636
|
-
gate_type TEXT NOT NULL DEFAULT '',
|
|
637
|
-
unit_type TEXT DEFAULT NULL,
|
|
638
|
-
unit_id TEXT DEFAULT NULL,
|
|
639
|
-
milestone_id TEXT DEFAULT NULL,
|
|
640
|
-
slice_id TEXT DEFAULT NULL,
|
|
641
|
-
task_id TEXT DEFAULT NULL,
|
|
642
|
-
outcome TEXT NOT NULL DEFAULT 'pass',
|
|
643
|
-
failure_class TEXT NOT NULL DEFAULT 'none',
|
|
644
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
645
|
-
findings TEXT NOT NULL DEFAULT '',
|
|
646
|
-
attempt INTEGER NOT NULL DEFAULT 1,
|
|
647
|
-
max_attempts INTEGER NOT NULL DEFAULT 1,
|
|
648
|
-
retryable INTEGER NOT NULL DEFAULT 0,
|
|
649
|
-
evaluated_at TEXT NOT NULL DEFAULT ''
|
|
650
|
-
)
|
|
651
|
-
`);
|
|
652
|
-
|
|
653
|
-
db.exec(`
|
|
654
|
-
CREATE TABLE IF NOT EXISTS turn_git_transactions (
|
|
655
|
-
trace_id TEXT NOT NULL,
|
|
656
|
-
turn_id TEXT NOT NULL,
|
|
657
|
-
unit_type TEXT DEFAULT NULL,
|
|
658
|
-
unit_id TEXT DEFAULT NULL,
|
|
659
|
-
stage TEXT NOT NULL DEFAULT 'turn-start',
|
|
660
|
-
action TEXT NOT NULL DEFAULT 'status-only',
|
|
661
|
-
push INTEGER NOT NULL DEFAULT 0,
|
|
662
|
-
status TEXT NOT NULL DEFAULT 'ok',
|
|
663
|
-
error TEXT DEFAULT NULL,
|
|
664
|
-
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
665
|
-
updated_at TEXT NOT NULL DEFAULT '',
|
|
666
|
-
PRIMARY KEY (trace_id, turn_id, stage)
|
|
667
|
-
)
|
|
668
|
-
`);
|
|
669
|
-
|
|
670
|
-
db.exec(`
|
|
671
|
-
CREATE TABLE IF NOT EXISTS audit_events (
|
|
672
|
-
event_id TEXT PRIMARY KEY,
|
|
673
|
-
trace_id TEXT NOT NULL,
|
|
674
|
-
turn_id TEXT DEFAULT NULL,
|
|
675
|
-
caused_by TEXT DEFAULT NULL,
|
|
676
|
-
category TEXT NOT NULL,
|
|
677
|
-
type TEXT NOT NULL,
|
|
678
|
-
ts TEXT NOT NULL,
|
|
679
|
-
payload_json TEXT NOT NULL DEFAULT '{}'
|
|
680
|
-
)
|
|
681
|
-
`);
|
|
682
|
-
|
|
683
|
-
db.exec(`
|
|
684
|
-
CREATE TABLE IF NOT EXISTS audit_turn_index (
|
|
685
|
-
trace_id TEXT NOT NULL,
|
|
686
|
-
turn_id TEXT NOT NULL,
|
|
687
|
-
first_ts TEXT NOT NULL,
|
|
688
|
-
last_ts TEXT NOT NULL,
|
|
689
|
-
event_count INTEGER NOT NULL DEFAULT 0,
|
|
690
|
-
PRIMARY KEY (trace_id, turn_id)
|
|
691
|
-
)
|
|
692
|
-
`);
|
|
693
|
-
|
|
694
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
|
|
695
|
-
|
|
696
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
|
|
697
|
-
|
|
698
|
-
// v13 indexes — hot-path dispatch queries
|
|
699
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_active ON tasks(milestone_id, slice_id, status)");
|
|
700
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
|
|
701
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
702
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
703
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
704
|
-
ensureVerificationEvidenceDedupIndex(db);
|
|
705
|
-
|
|
706
|
-
// v14 index — slice dependency lookups
|
|
707
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
|
|
708
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
|
|
709
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
|
|
710
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
|
|
711
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
|
|
712
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
|
|
713
|
-
|
|
714
|
-
db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
|
|
715
|
-
db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
|
|
716
|
-
db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
|
|
123
|
+
createBaseSchemaObjects(db, {
|
|
124
|
+
tryCreateMemoriesFts,
|
|
125
|
+
ensureVerificationEvidenceDedupIndex,
|
|
126
|
+
});
|
|
717
127
|
|
|
718
128
|
const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
|
|
719
129
|
if (existing && (existing["cnt"] as number) === 0) {
|
|
@@ -731,12 +141,7 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
731
141
|
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
|
|
732
142
|
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
|
|
733
143
|
|
|
734
|
-
db
|
|
735
|
-
"INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
|
|
736
|
-
).run({
|
|
737
|
-
":version": SCHEMA_VERSION,
|
|
738
|
-
":applied_at": new Date().toISOString(),
|
|
739
|
-
});
|
|
144
|
+
recordSchemaVersion(db, SCHEMA_VERSION);
|
|
740
145
|
}
|
|
741
146
|
|
|
742
147
|
db.exec("COMMIT");
|
|
@@ -748,11 +153,6 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
748
153
|
migrateSchema(db);
|
|
749
154
|
}
|
|
750
155
|
|
|
751
|
-
function columnExists(db: DbAdapter, table: string, column: string): boolean {
|
|
752
|
-
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
753
|
-
return rows.some((row) => row["name"] === column);
|
|
754
|
-
}
|
|
755
|
-
|
|
756
156
|
/**
|
|
757
157
|
* Create the FTS5 virtual table for memories plus the triggers that keep it
|
|
758
158
|
* in sync with the base table. FTS5 may be unavailable on stripped-down
|
|
@@ -760,317 +160,88 @@ function columnExists(db: DbAdapter, table: string, column: string): boolean {
|
|
|
760
160
|
* to LIKE-based scans in `memory-store.queryMemoriesRanked`.
|
|
761
161
|
*/
|
|
762
162
|
export function tryCreateMemoriesFts(db: DbAdapter): boolean {
|
|
763
|
-
|
|
764
|
-
db
|
|
765
|
-
|
|
766
|
-
USING fts5(content, content='memories', content_rowid='seq', tokenize='porter unicode61')
|
|
767
|
-
`);
|
|
768
|
-
// Triggers mirror inserts / updates / deletes on the base memories table.
|
|
769
|
-
db.exec(`
|
|
770
|
-
CREATE TRIGGER IF NOT EXISTS memories_ai
|
|
771
|
-
AFTER INSERT ON memories BEGIN
|
|
772
|
-
INSERT INTO memories_fts(rowid, content) VALUES (new.seq, new.content);
|
|
773
|
-
END
|
|
774
|
-
`);
|
|
775
|
-
db.exec(`
|
|
776
|
-
CREATE TRIGGER IF NOT EXISTS memories_ad
|
|
777
|
-
AFTER DELETE ON memories BEGIN
|
|
778
|
-
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.seq, old.content);
|
|
779
|
-
END
|
|
780
|
-
`);
|
|
781
|
-
db.exec(`
|
|
782
|
-
CREATE TRIGGER IF NOT EXISTS memories_au
|
|
783
|
-
AFTER UPDATE OF content ON memories BEGIN
|
|
784
|
-
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.seq, old.content);
|
|
785
|
-
INSERT INTO memories_fts(rowid, content) VALUES (new.seq, new.content);
|
|
786
|
-
END
|
|
787
|
-
`);
|
|
788
|
-
return true;
|
|
789
|
-
} catch (err) {
|
|
790
|
-
logWarning("db", `FTS5 unavailable — memory queries will use LIKE fallback: ${(err as Error).message}`);
|
|
791
|
-
return false;
|
|
792
|
-
}
|
|
163
|
+
return tryCreateMemoriesFtsSchema(db, {
|
|
164
|
+
onUnavailable: (message) => logWarning("db", message),
|
|
165
|
+
});
|
|
793
166
|
}
|
|
794
167
|
|
|
795
168
|
export function isMemoriesFtsAvailable(db: DbAdapter): boolean {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
} catch {
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
169
|
+
return isMemoriesFtsAvailableSchema(db);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function backfillMemoriesFts(db: DbAdapter): void {
|
|
173
|
+
db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
|
|
804
174
|
}
|
|
805
175
|
|
|
806
|
-
function
|
|
807
|
-
|
|
176
|
+
function copyQualityGateRowsToRepairedTable(db: DbAdapter): void {
|
|
177
|
+
db.exec(`
|
|
178
|
+
INSERT OR IGNORE INTO quality_gates_new
|
|
179
|
+
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
180
|
+
SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
|
|
181
|
+
FROM quality_gates
|
|
182
|
+
`);
|
|
808
183
|
}
|
|
809
184
|
|
|
810
185
|
function migrateSchema(db: DbAdapter): void {
|
|
811
|
-
const
|
|
812
|
-
const currentVersion = row ? (row["v"] as number) : 0;
|
|
186
|
+
const currentVersion = getCurrentSchemaVersion(db);
|
|
813
187
|
if (currentVersion >= SCHEMA_VERSION) return;
|
|
814
188
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const backupPath = `${currentPath}.backup-v${currentVersion}`;
|
|
821
|
-
if (!existsSync(backupPath)) {
|
|
822
|
-
// Flush WAL to main DB file before copying — without this, the backup
|
|
823
|
-
// may be missing committed data that only exists in the -wal file.
|
|
824
|
-
try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* checkpoint is best-effort */ }
|
|
825
|
-
copyFileSync(currentPath, backupPath);
|
|
826
|
-
}
|
|
827
|
-
} catch (backupErr) {
|
|
828
|
-
// Log but proceed — blocking migration leaves the DB stuck at an old
|
|
829
|
-
// schema version permanently on read-only or full filesystems.
|
|
830
|
-
logWarning("db", `Pre-migration backup failed: ${backupErr instanceof Error ? backupErr.message : String(backupErr)}`);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
189
|
+
backupDatabaseBeforeMigration(db, currentPath, currentVersion, {
|
|
190
|
+
existsSync,
|
|
191
|
+
copyFileSync,
|
|
192
|
+
logWarning,
|
|
193
|
+
});
|
|
833
194
|
|
|
834
195
|
db.exec("BEGIN");
|
|
835
196
|
try {
|
|
836
197
|
if (currentVersion < 2) {
|
|
837
|
-
db
|
|
838
|
-
|
|
839
|
-
path TEXT PRIMARY KEY,
|
|
840
|
-
artifact_type TEXT NOT NULL DEFAULT '',
|
|
841
|
-
milestone_id TEXT DEFAULT NULL,
|
|
842
|
-
slice_id TEXT DEFAULT NULL,
|
|
843
|
-
task_id TEXT DEFAULT NULL,
|
|
844
|
-
full_content TEXT NOT NULL DEFAULT '',
|
|
845
|
-
imported_at TEXT NOT NULL DEFAULT ''
|
|
846
|
-
)
|
|
847
|
-
`);
|
|
848
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
849
|
-
":version": 2,
|
|
850
|
-
":applied_at": new Date().toISOString(),
|
|
851
|
-
});
|
|
198
|
+
applyMigrationV2Artifacts(db);
|
|
199
|
+
recordSchemaVersion(db, 2);
|
|
852
200
|
}
|
|
853
201
|
|
|
854
202
|
if (currentVersion < 3) {
|
|
855
|
-
db
|
|
856
|
-
|
|
857
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
858
|
-
id TEXT NOT NULL UNIQUE,
|
|
859
|
-
category TEXT NOT NULL,
|
|
860
|
-
content TEXT NOT NULL,
|
|
861
|
-
confidence REAL NOT NULL DEFAULT 0.8,
|
|
862
|
-
source_unit_type TEXT,
|
|
863
|
-
source_unit_id TEXT,
|
|
864
|
-
created_at TEXT NOT NULL,
|
|
865
|
-
updated_at TEXT NOT NULL,
|
|
866
|
-
superseded_by TEXT DEFAULT NULL,
|
|
867
|
-
hit_count INTEGER NOT NULL DEFAULT 0
|
|
868
|
-
)
|
|
869
|
-
`);
|
|
870
|
-
db.exec(`
|
|
871
|
-
CREATE TABLE IF NOT EXISTS memory_processed_units (
|
|
872
|
-
unit_key TEXT PRIMARY KEY,
|
|
873
|
-
activity_file TEXT,
|
|
874
|
-
processed_at TEXT NOT NULL
|
|
875
|
-
)
|
|
876
|
-
`);
|
|
877
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)");
|
|
878
|
-
db.exec("DROP VIEW IF EXISTS active_memories");
|
|
879
|
-
db.exec("CREATE VIEW active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL");
|
|
880
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
881
|
-
":version": 3,
|
|
882
|
-
":applied_at": new Date().toISOString(),
|
|
883
|
-
});
|
|
203
|
+
applyMigrationV3Memories(db);
|
|
204
|
+
recordSchemaVersion(db, 3);
|
|
884
205
|
}
|
|
885
206
|
|
|
886
207
|
if (currentVersion < 4) {
|
|
887
|
-
|
|
888
|
-
db
|
|
889
|
-
db.exec("CREATE VIEW active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL");
|
|
890
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
891
|
-
":version": 4,
|
|
892
|
-
":applied_at": new Date().toISOString(),
|
|
893
|
-
});
|
|
208
|
+
applyMigrationV4DecisionMadeBy(db);
|
|
209
|
+
recordSchemaVersion(db, 4);
|
|
894
210
|
}
|
|
895
211
|
|
|
896
212
|
if (currentVersion < 5) {
|
|
897
|
-
db
|
|
898
|
-
|
|
899
|
-
id TEXT PRIMARY KEY,
|
|
900
|
-
title TEXT NOT NULL DEFAULT '',
|
|
901
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
902
|
-
created_at TEXT NOT NULL,
|
|
903
|
-
completed_at TEXT DEFAULT NULL
|
|
904
|
-
)
|
|
905
|
-
`);
|
|
906
|
-
db.exec(`
|
|
907
|
-
CREATE TABLE IF NOT EXISTS slices (
|
|
908
|
-
milestone_id TEXT NOT NULL,
|
|
909
|
-
id TEXT NOT NULL,
|
|
910
|
-
title TEXT NOT NULL DEFAULT '',
|
|
911
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
912
|
-
risk TEXT NOT NULL DEFAULT 'medium',
|
|
913
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
914
|
-
completed_at TEXT DEFAULT NULL,
|
|
915
|
-
PRIMARY KEY (milestone_id, id),
|
|
916
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
917
|
-
)
|
|
918
|
-
`);
|
|
919
|
-
db.exec(`
|
|
920
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
921
|
-
milestone_id TEXT NOT NULL,
|
|
922
|
-
slice_id TEXT NOT NULL,
|
|
923
|
-
id TEXT NOT NULL,
|
|
924
|
-
title TEXT NOT NULL DEFAULT '',
|
|
925
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
926
|
-
one_liner TEXT NOT NULL DEFAULT '',
|
|
927
|
-
narrative TEXT NOT NULL DEFAULT '',
|
|
928
|
-
verification_result TEXT NOT NULL DEFAULT '',
|
|
929
|
-
duration TEXT NOT NULL DEFAULT '',
|
|
930
|
-
completed_at TEXT DEFAULT NULL,
|
|
931
|
-
blocker_discovered INTEGER DEFAULT 0,
|
|
932
|
-
deviations TEXT NOT NULL DEFAULT '',
|
|
933
|
-
known_issues TEXT NOT NULL DEFAULT '',
|
|
934
|
-
key_files TEXT NOT NULL DEFAULT '[]',
|
|
935
|
-
key_decisions TEXT NOT NULL DEFAULT '[]',
|
|
936
|
-
full_summary_md TEXT NOT NULL DEFAULT '',
|
|
937
|
-
PRIMARY KEY (milestone_id, slice_id, id),
|
|
938
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
939
|
-
)
|
|
940
|
-
`);
|
|
941
|
-
db.exec(`
|
|
942
|
-
CREATE TABLE IF NOT EXISTS verification_evidence (
|
|
943
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
944
|
-
task_id TEXT NOT NULL DEFAULT '',
|
|
945
|
-
slice_id TEXT NOT NULL DEFAULT '',
|
|
946
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
947
|
-
command TEXT NOT NULL DEFAULT '',
|
|
948
|
-
exit_code INTEGER DEFAULT 0,
|
|
949
|
-
verdict TEXT NOT NULL DEFAULT '',
|
|
950
|
-
duration_ms INTEGER DEFAULT 0,
|
|
951
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
952
|
-
FOREIGN KEY (milestone_id, slice_id, task_id) REFERENCES tasks(milestone_id, slice_id, id)
|
|
953
|
-
)
|
|
954
|
-
`);
|
|
955
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
956
|
-
":version": 5,
|
|
957
|
-
":applied_at": new Date().toISOString(),
|
|
958
|
-
});
|
|
213
|
+
applyMigrationV5HierarchyTables(db);
|
|
214
|
+
recordSchemaVersion(db, 5);
|
|
959
215
|
}
|
|
960
216
|
|
|
961
217
|
if (currentVersion < 6) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
965
|
-
":version": 6,
|
|
966
|
-
":applied_at": new Date().toISOString(),
|
|
967
|
-
});
|
|
218
|
+
applyMigrationV6SliceSummaries(db);
|
|
219
|
+
recordSchemaVersion(db, 6);
|
|
968
220
|
}
|
|
969
221
|
|
|
970
222
|
if (currentVersion < 7) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
ensureColumn(db, "milestones", "depends_on", `ALTER TABLE milestones ADD COLUMN depends_on TEXT NOT NULL DEFAULT '[]'`);
|
|
974
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
975
|
-
":version": 7,
|
|
976
|
-
":applied_at": new Date().toISOString(),
|
|
977
|
-
});
|
|
223
|
+
applyMigrationV7Dependencies(db);
|
|
224
|
+
recordSchemaVersion(db, 7);
|
|
978
225
|
}
|
|
979
226
|
|
|
980
227
|
if (currentVersion < 8) {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
ensureColumn(db, "milestones", "key_risks", `ALTER TABLE milestones ADD COLUMN key_risks TEXT NOT NULL DEFAULT '[]'`);
|
|
984
|
-
ensureColumn(db, "milestones", "proof_strategy", `ALTER TABLE milestones ADD COLUMN proof_strategy TEXT NOT NULL DEFAULT '[]'`);
|
|
985
|
-
ensureColumn(db, "milestones", "verification_contract", `ALTER TABLE milestones ADD COLUMN verification_contract TEXT NOT NULL DEFAULT ''`);
|
|
986
|
-
ensureColumn(db, "milestones", "verification_integration", `ALTER TABLE milestones ADD COLUMN verification_integration TEXT NOT NULL DEFAULT ''`);
|
|
987
|
-
ensureColumn(db, "milestones", "verification_operational", `ALTER TABLE milestones ADD COLUMN verification_operational TEXT NOT NULL DEFAULT ''`);
|
|
988
|
-
ensureColumn(db, "milestones", "verification_uat", `ALTER TABLE milestones ADD COLUMN verification_uat TEXT NOT NULL DEFAULT ''`);
|
|
989
|
-
ensureColumn(db, "milestones", "definition_of_done", `ALTER TABLE milestones ADD COLUMN definition_of_done TEXT NOT NULL DEFAULT '[]'`);
|
|
990
|
-
ensureColumn(db, "milestones", "requirement_coverage", `ALTER TABLE milestones ADD COLUMN requirement_coverage TEXT NOT NULL DEFAULT ''`);
|
|
991
|
-
ensureColumn(db, "milestones", "boundary_map_markdown", `ALTER TABLE milestones ADD COLUMN boundary_map_markdown TEXT NOT NULL DEFAULT ''`);
|
|
992
|
-
|
|
993
|
-
ensureColumn(db, "slices", "goal", `ALTER TABLE slices ADD COLUMN goal TEXT NOT NULL DEFAULT ''`);
|
|
994
|
-
ensureColumn(db, "slices", "success_criteria", `ALTER TABLE slices ADD COLUMN success_criteria TEXT NOT NULL DEFAULT ''`);
|
|
995
|
-
ensureColumn(db, "slices", "proof_level", `ALTER TABLE slices ADD COLUMN proof_level TEXT NOT NULL DEFAULT ''`);
|
|
996
|
-
ensureColumn(db, "slices", "integration_closure", `ALTER TABLE slices ADD COLUMN integration_closure TEXT NOT NULL DEFAULT ''`);
|
|
997
|
-
ensureColumn(db, "slices", "observability_impact", `ALTER TABLE slices ADD COLUMN observability_impact TEXT NOT NULL DEFAULT ''`);
|
|
998
|
-
|
|
999
|
-
ensureColumn(db, "tasks", "description", `ALTER TABLE tasks ADD COLUMN description TEXT NOT NULL DEFAULT ''`);
|
|
1000
|
-
ensureColumn(db, "tasks", "estimate", `ALTER TABLE tasks ADD COLUMN estimate TEXT NOT NULL DEFAULT ''`);
|
|
1001
|
-
ensureColumn(db, "tasks", "files", `ALTER TABLE tasks ADD COLUMN files TEXT NOT NULL DEFAULT '[]'`);
|
|
1002
|
-
ensureColumn(db, "tasks", "verify", `ALTER TABLE tasks ADD COLUMN verify TEXT NOT NULL DEFAULT ''`);
|
|
1003
|
-
ensureColumn(db, "tasks", "inputs", `ALTER TABLE tasks ADD COLUMN inputs TEXT NOT NULL DEFAULT '[]'`);
|
|
1004
|
-
ensureColumn(db, "tasks", "expected_output", `ALTER TABLE tasks ADD COLUMN expected_output TEXT NOT NULL DEFAULT '[]'`);
|
|
1005
|
-
ensureColumn(db, "tasks", "observability_impact", `ALTER TABLE tasks ADD COLUMN observability_impact TEXT NOT NULL DEFAULT ''`);
|
|
1006
|
-
|
|
1007
|
-
db.exec(`
|
|
1008
|
-
CREATE TABLE IF NOT EXISTS replan_history (
|
|
1009
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1010
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
1011
|
-
slice_id TEXT DEFAULT NULL,
|
|
1012
|
-
task_id TEXT DEFAULT NULL,
|
|
1013
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
1014
|
-
previous_artifact_path TEXT DEFAULT NULL,
|
|
1015
|
-
replacement_artifact_path TEXT DEFAULT NULL,
|
|
1016
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
1017
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
1018
|
-
)
|
|
1019
|
-
`);
|
|
1020
|
-
db.exec(`
|
|
1021
|
-
CREATE TABLE IF NOT EXISTS assessments (
|
|
1022
|
-
path TEXT PRIMARY KEY,
|
|
1023
|
-
milestone_id TEXT NOT NULL DEFAULT '',
|
|
1024
|
-
slice_id TEXT DEFAULT NULL,
|
|
1025
|
-
task_id TEXT DEFAULT NULL,
|
|
1026
|
-
status TEXT NOT NULL DEFAULT '',
|
|
1027
|
-
scope TEXT NOT NULL DEFAULT '',
|
|
1028
|
-
full_content TEXT NOT NULL DEFAULT '',
|
|
1029
|
-
created_at TEXT NOT NULL DEFAULT '',
|
|
1030
|
-
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
|
|
1031
|
-
)
|
|
1032
|
-
`);
|
|
1033
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_replan_history_milestone ON replan_history(milestone_id, created_at)");
|
|
1034
|
-
|
|
1035
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1036
|
-
":version": 8,
|
|
1037
|
-
":applied_at": new Date().toISOString(),
|
|
1038
|
-
});
|
|
228
|
+
applyMigrationV8PlanningFields(db);
|
|
229
|
+
recordSchemaVersion(db, 8);
|
|
1039
230
|
}
|
|
1040
231
|
|
|
1041
232
|
if (currentVersion < 9) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1046
|
-
":version": 9,
|
|
1047
|
-
":applied_at": new Date().toISOString(),
|
|
1048
|
-
});
|
|
233
|
+
applyMigrationV9Ordering(db);
|
|
234
|
+
recordSchemaVersion(db, 9);
|
|
1049
235
|
}
|
|
1050
236
|
|
|
1051
237
|
if (currentVersion < 10) {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1055
|
-
":version": 10,
|
|
1056
|
-
":applied_at": new Date().toISOString(),
|
|
1057
|
-
});
|
|
238
|
+
applyMigrationV10ReplanTrigger(db);
|
|
239
|
+
recordSchemaVersion(db, 10);
|
|
1058
240
|
}
|
|
1059
241
|
|
|
1060
242
|
if (currentVersion < 11) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
// one replan record per blocker task per slice per milestone.
|
|
1064
|
-
db.exec(`
|
|
1065
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_replan_history_unique
|
|
1066
|
-
ON replan_history(milestone_id, slice_id, task_id)
|
|
1067
|
-
WHERE slice_id IS NOT NULL AND task_id IS NOT NULL
|
|
1068
|
-
`);
|
|
1069
|
-
|
|
1070
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1071
|
-
":version": 11,
|
|
1072
|
-
":applied_at": new Date().toISOString(),
|
|
1073
|
-
});
|
|
243
|
+
applyMigrationV11TaskPlanning(db);
|
|
244
|
+
recordSchemaVersion(db, 11);
|
|
1074
245
|
}
|
|
1075
246
|
|
|
1076
247
|
if (currentVersion < 12) {
|
|
@@ -1079,305 +250,68 @@ function migrateSchema(db: DbAdapter): void {
|
|
|
1079
250
|
// DBs that migrate through v12. The corrected DDL uses
|
|
1080
251
|
// task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
|
|
1081
252
|
// were created with the broken DDL are repaired by the v22 migration below.
|
|
1082
|
-
db
|
|
1083
|
-
|
|
1084
|
-
milestone_id TEXT NOT NULL,
|
|
1085
|
-
slice_id TEXT NOT NULL,
|
|
1086
|
-
gate_id TEXT NOT NULL,
|
|
1087
|
-
scope TEXT NOT NULL DEFAULT 'slice',
|
|
1088
|
-
task_id TEXT NOT NULL DEFAULT '',
|
|
1089
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
1090
|
-
verdict TEXT NOT NULL DEFAULT '',
|
|
1091
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
1092
|
-
findings TEXT NOT NULL DEFAULT '',
|
|
1093
|
-
evaluated_at TEXT DEFAULT NULL,
|
|
1094
|
-
PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
|
|
1095
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
1096
|
-
)
|
|
1097
|
-
`);
|
|
1098
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1099
|
-
":version": 12,
|
|
1100
|
-
":applied_at": new Date().toISOString(),
|
|
1101
|
-
});
|
|
253
|
+
applyMigrationV12QualityGates(db);
|
|
254
|
+
recordSchemaVersion(db, 12);
|
|
1102
255
|
}
|
|
1103
256
|
|
|
1104
257
|
if (currentVersion < 13) {
|
|
1105
|
-
|
|
1106
|
-
db
|
|
1107
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_slices_active ON slices(milestone_id, status)");
|
|
1108
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
1109
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
1110
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
1111
|
-
ensureVerificationEvidenceDedupIndex(db);
|
|
1112
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1113
|
-
":version": 13,
|
|
1114
|
-
":applied_at": new Date().toISOString(),
|
|
1115
|
-
});
|
|
258
|
+
applyMigrationV13HotPathIndexes(db, ensureVerificationEvidenceDedupIndex);
|
|
259
|
+
recordSchemaVersion(db, 13);
|
|
1116
260
|
}
|
|
1117
261
|
|
|
1118
262
|
if (currentVersion < 14) {
|
|
1119
|
-
db
|
|
1120
|
-
|
|
1121
|
-
milestone_id TEXT NOT NULL,
|
|
1122
|
-
slice_id TEXT NOT NULL,
|
|
1123
|
-
depends_on_slice_id TEXT NOT NULL,
|
|
1124
|
-
PRIMARY KEY (milestone_id, slice_id, depends_on_slice_id),
|
|
1125
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id),
|
|
1126
|
-
FOREIGN KEY (milestone_id, depends_on_slice_id) REFERENCES slices(milestone_id, id)
|
|
1127
|
-
)
|
|
1128
|
-
`);
|
|
1129
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
|
|
1130
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1131
|
-
":version": 14,
|
|
1132
|
-
":applied_at": new Date().toISOString(),
|
|
1133
|
-
});
|
|
263
|
+
applyMigrationV14SliceDependencies(db);
|
|
264
|
+
recordSchemaVersion(db, 14);
|
|
1134
265
|
}
|
|
1135
266
|
|
|
1136
267
|
if (currentVersion < 15) {
|
|
1137
|
-
db
|
|
1138
|
-
|
|
1139
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1140
|
-
trace_id TEXT NOT NULL,
|
|
1141
|
-
turn_id TEXT NOT NULL,
|
|
1142
|
-
gate_id TEXT NOT NULL,
|
|
1143
|
-
gate_type TEXT NOT NULL DEFAULT '',
|
|
1144
|
-
unit_type TEXT DEFAULT NULL,
|
|
1145
|
-
unit_id TEXT DEFAULT NULL,
|
|
1146
|
-
milestone_id TEXT DEFAULT NULL,
|
|
1147
|
-
slice_id TEXT DEFAULT NULL,
|
|
1148
|
-
task_id TEXT DEFAULT NULL,
|
|
1149
|
-
outcome TEXT NOT NULL DEFAULT 'pass',
|
|
1150
|
-
failure_class TEXT NOT NULL DEFAULT 'none',
|
|
1151
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
1152
|
-
findings TEXT NOT NULL DEFAULT '',
|
|
1153
|
-
attempt INTEGER NOT NULL DEFAULT 1,
|
|
1154
|
-
max_attempts INTEGER NOT NULL DEFAULT 1,
|
|
1155
|
-
retryable INTEGER NOT NULL DEFAULT 0,
|
|
1156
|
-
evaluated_at TEXT NOT NULL DEFAULT ''
|
|
1157
|
-
)
|
|
1158
|
-
`);
|
|
1159
|
-
db.exec(`
|
|
1160
|
-
CREATE TABLE IF NOT EXISTS turn_git_transactions (
|
|
1161
|
-
trace_id TEXT NOT NULL,
|
|
1162
|
-
turn_id TEXT NOT NULL,
|
|
1163
|
-
unit_type TEXT DEFAULT NULL,
|
|
1164
|
-
unit_id TEXT DEFAULT NULL,
|
|
1165
|
-
stage TEXT NOT NULL DEFAULT 'turn-start',
|
|
1166
|
-
action TEXT NOT NULL DEFAULT 'status-only',
|
|
1167
|
-
push INTEGER NOT NULL DEFAULT 0,
|
|
1168
|
-
status TEXT NOT NULL DEFAULT 'ok',
|
|
1169
|
-
error TEXT DEFAULT NULL,
|
|
1170
|
-
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
1171
|
-
updated_at TEXT NOT NULL DEFAULT '',
|
|
1172
|
-
PRIMARY KEY (trace_id, turn_id, stage)
|
|
1173
|
-
)
|
|
1174
|
-
`);
|
|
1175
|
-
db.exec(`
|
|
1176
|
-
CREATE TABLE IF NOT EXISTS audit_events (
|
|
1177
|
-
event_id TEXT PRIMARY KEY,
|
|
1178
|
-
trace_id TEXT NOT NULL,
|
|
1179
|
-
turn_id TEXT DEFAULT NULL,
|
|
1180
|
-
caused_by TEXT DEFAULT NULL,
|
|
1181
|
-
category TEXT NOT NULL,
|
|
1182
|
-
type TEXT NOT NULL,
|
|
1183
|
-
ts TEXT NOT NULL,
|
|
1184
|
-
payload_json TEXT NOT NULL DEFAULT '{}'
|
|
1185
|
-
)
|
|
1186
|
-
`);
|
|
1187
|
-
db.exec(`
|
|
1188
|
-
CREATE TABLE IF NOT EXISTS audit_turn_index (
|
|
1189
|
-
trace_id TEXT NOT NULL,
|
|
1190
|
-
turn_id TEXT NOT NULL,
|
|
1191
|
-
first_ts TEXT NOT NULL,
|
|
1192
|
-
last_ts TEXT NOT NULL,
|
|
1193
|
-
event_count INTEGER NOT NULL DEFAULT 0,
|
|
1194
|
-
PRIMARY KEY (trace_id, turn_id)
|
|
1195
|
-
)
|
|
1196
|
-
`);
|
|
1197
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_turn ON gate_runs(trace_id, turn_id)");
|
|
1198
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_gate_runs_lookup ON gate_runs(milestone_id, slice_id, task_id, gate_id)");
|
|
1199
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_turn_git_tx_turn ON turn_git_transactions(trace_id, turn_id)");
|
|
1200
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events(trace_id, ts)");
|
|
1201
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_audit_events_turn ON audit_events(trace_id, turn_id, ts)");
|
|
1202
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1203
|
-
":version": 15,
|
|
1204
|
-
":applied_at": new Date().toISOString(),
|
|
1205
|
-
});
|
|
268
|
+
applyMigrationV15AuditTables(db);
|
|
269
|
+
recordSchemaVersion(db, 15);
|
|
1206
270
|
}
|
|
1207
271
|
|
|
1208
272
|
if (currentVersion < 16) {
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
ensureColumn(db, "slices", "sketch_scope", `ALTER TABLE slices ADD COLUMN sketch_scope TEXT NOT NULL DEFAULT ''`);
|
|
1212
|
-
// ADR-011 Phase 2: decisions can now be sourced from escalation resolutions.
|
|
1213
|
-
ensureColumn(db, "decisions", "source", `ALTER TABLE decisions ADD COLUMN source TEXT NOT NULL DEFAULT 'discussion'`);
|
|
1214
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1215
|
-
":version": 16,
|
|
1216
|
-
":applied_at": new Date().toISOString(),
|
|
1217
|
-
});
|
|
273
|
+
applyMigrationV16EscalationSource(db);
|
|
274
|
+
recordSchemaVersion(db, 16);
|
|
1218
275
|
}
|
|
1219
276
|
|
|
1220
277
|
if (currentVersion < 17) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
ensureColumn(db, "tasks", "escalation_pending", `ALTER TABLE tasks ADD COLUMN escalation_pending INTEGER NOT NULL DEFAULT 0`);
|
|
1224
|
-
ensureColumn(db, "tasks", "escalation_awaiting_review", `ALTER TABLE tasks ADD COLUMN escalation_awaiting_review INTEGER NOT NULL DEFAULT 0`);
|
|
1225
|
-
ensureColumn(db, "tasks", "escalation_artifact_path", `ALTER TABLE tasks ADD COLUMN escalation_artifact_path TEXT DEFAULT NULL`);
|
|
1226
|
-
ensureColumn(db, "tasks", "escalation_override_applied_at", `ALTER TABLE tasks ADD COLUMN escalation_override_applied_at TEXT DEFAULT NULL`);
|
|
1227
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
|
|
1228
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1229
|
-
":version": 17,
|
|
1230
|
-
":applied_at": new Date().toISOString(),
|
|
1231
|
-
});
|
|
278
|
+
applyMigrationV17TaskEscalation(db);
|
|
279
|
+
recordSchemaVersion(db, 17);
|
|
1232
280
|
}
|
|
1233
281
|
|
|
1234
282
|
if (currentVersion < 18) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
ensureColumn(db, "memories", "scope", `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'`);
|
|
1238
|
-
ensureColumn(db, "memories", "tags", `ALTER TABLE memories ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`);
|
|
1239
|
-
db.exec(`
|
|
1240
|
-
CREATE TABLE IF NOT EXISTS memory_sources (
|
|
1241
|
-
id TEXT PRIMARY KEY,
|
|
1242
|
-
kind TEXT NOT NULL,
|
|
1243
|
-
uri TEXT,
|
|
1244
|
-
title TEXT,
|
|
1245
|
-
content TEXT NOT NULL,
|
|
1246
|
-
content_hash TEXT NOT NULL UNIQUE,
|
|
1247
|
-
imported_at TEXT NOT NULL,
|
|
1248
|
-
scope TEXT NOT NULL DEFAULT 'project',
|
|
1249
|
-
tags TEXT NOT NULL DEFAULT '[]'
|
|
1250
|
-
)
|
|
1251
|
-
`);
|
|
1252
|
-
// If memory_sources already existed before v18 (created by an earlier
|
|
1253
|
-
// version of initSchema that lacked scope/tags), add the missing columns.
|
|
1254
|
-
ensureColumn(db, "memory_sources", "scope", `ALTER TABLE memory_sources ADD COLUMN scope TEXT NOT NULL DEFAULT 'project'`);
|
|
1255
|
-
ensureColumn(db, "memory_sources", "tags", `ALTER TABLE memory_sources ADD COLUMN tags TEXT NOT NULL DEFAULT '[]'`);
|
|
1256
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
|
|
1257
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
|
|
1258
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
|
|
1259
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1260
|
-
":version": 18,
|
|
1261
|
-
":applied_at": new Date().toISOString(),
|
|
1262
|
-
});
|
|
283
|
+
applyMigrationV18MemorySources(db);
|
|
284
|
+
recordSchemaVersion(db, 18);
|
|
1263
285
|
}
|
|
1264
286
|
|
|
1265
287
|
if (currentVersion < 19) {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
dim INTEGER NOT NULL,
|
|
1272
|
-
vector BLOB NOT NULL,
|
|
1273
|
-
updated_at TEXT NOT NULL
|
|
1274
|
-
)
|
|
1275
|
-
`);
|
|
1276
|
-
tryCreateMemoriesFts(db);
|
|
1277
|
-
// Backfill FTS5 with any existing memories (triggers only cover future writes).
|
|
1278
|
-
if (isMemoriesFtsAvailable(db)) {
|
|
1279
|
-
try {
|
|
1280
|
-
db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
|
|
1281
|
-
} catch (err) {
|
|
1282
|
-
logWarning("db", `FTS5 backfill failed: ${(err as Error).message}`);
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1286
|
-
":version": 19,
|
|
1287
|
-
":applied_at": new Date().toISOString(),
|
|
288
|
+
applyMigrationV19MemoryFts(db, {
|
|
289
|
+
tryCreateMemoriesFts,
|
|
290
|
+
isMemoriesFtsAvailable,
|
|
291
|
+
backfillMemoriesFts,
|
|
292
|
+
logWarning,
|
|
1288
293
|
});
|
|
294
|
+
recordSchemaVersion(db, 19);
|
|
1289
295
|
}
|
|
1290
296
|
|
|
1291
297
|
if (currentVersion < 20) {
|
|
1292
|
-
|
|
1293
|
-
db
|
|
1294
|
-
CREATE TABLE IF NOT EXISTS memory_relations (
|
|
1295
|
-
from_id TEXT NOT NULL,
|
|
1296
|
-
to_id TEXT NOT NULL,
|
|
1297
|
-
rel TEXT NOT NULL,
|
|
1298
|
-
confidence REAL NOT NULL DEFAULT 0.8,
|
|
1299
|
-
created_at TEXT NOT NULL,
|
|
1300
|
-
PRIMARY KEY (from_id, to_id, rel)
|
|
1301
|
-
)
|
|
1302
|
-
`);
|
|
1303
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
|
|
1304
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
|
|
1305
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1306
|
-
":version": 20,
|
|
1307
|
-
":applied_at": new Date().toISOString(),
|
|
1308
|
-
});
|
|
298
|
+
applyMigrationV20MemoryRelations(db);
|
|
299
|
+
recordSchemaVersion(db, 20);
|
|
1309
300
|
}
|
|
1310
301
|
|
|
1311
302
|
if (currentVersion < 21) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
// the eventual decisions->memories cutover does not lose schema fidelity.
|
|
1315
|
-
// Nullable JSON column — existing rows stay NULL until backfilled in Step 5.
|
|
1316
|
-
// Use ensureColumn for race-safety (matches v15-v18 pattern; bare ALTER
|
|
1317
|
-
// throws "duplicate column" on the loser of a concurrent open race even
|
|
1318
|
-
// though the transaction wrapper protects the schema_version row).
|
|
1319
|
-
ensureColumn(db, "memories", "structured_fields", "ALTER TABLE memories ADD COLUMN structured_fields TEXT DEFAULT NULL");
|
|
1320
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1321
|
-
":version": 21,
|
|
1322
|
-
":applied_at": new Date().toISOString(),
|
|
1323
|
-
});
|
|
303
|
+
applyMigrationV21StructuredMemories(db);
|
|
304
|
+
recordSchemaVersion(db, 21);
|
|
1324
305
|
}
|
|
1325
306
|
|
|
1326
307
|
if (currentVersion < 22) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
// SQLite DDL). Those DBs have task_id nullable (dflt_value NULL, notnull 0).
|
|
1330
|
-
// Rebuild the table with the correct schema, migrating existing rows via
|
|
1331
|
-
// COALESCE so no data is lost.
|
|
1332
|
-
const qgInfo = db.prepare("PRAGMA table_info(quality_gates)").all() as Array<Record<string, unknown>>;
|
|
1333
|
-
const taskIdCol = qgInfo.find((r) => r["name"] === "task_id");
|
|
1334
|
-
const needsRepair = taskIdCol && (taskIdCol["notnull"] === 0 || taskIdCol["notnull"] === "0");
|
|
1335
|
-
if (needsRepair) {
|
|
1336
|
-
db.exec(`
|
|
1337
|
-
CREATE TABLE quality_gates_new (
|
|
1338
|
-
milestone_id TEXT NOT NULL,
|
|
1339
|
-
slice_id TEXT NOT NULL,
|
|
1340
|
-
gate_id TEXT NOT NULL,
|
|
1341
|
-
scope TEXT NOT NULL DEFAULT 'slice',
|
|
1342
|
-
task_id TEXT NOT NULL DEFAULT '',
|
|
1343
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
1344
|
-
verdict TEXT NOT NULL DEFAULT '',
|
|
1345
|
-
rationale TEXT NOT NULL DEFAULT '',
|
|
1346
|
-
findings TEXT NOT NULL DEFAULT '',
|
|
1347
|
-
evaluated_at TEXT DEFAULT NULL,
|
|
1348
|
-
PRIMARY KEY (milestone_id, slice_id, gate_id, task_id),
|
|
1349
|
-
FOREIGN KEY (milestone_id, slice_id) REFERENCES slices(milestone_id, id)
|
|
1350
|
-
)
|
|
1351
|
-
`);
|
|
1352
|
-
db.exec(`
|
|
1353
|
-
INSERT OR IGNORE INTO quality_gates_new
|
|
1354
|
-
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
1355
|
-
SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
|
|
1356
|
-
FROM quality_gates
|
|
1357
|
-
`);
|
|
1358
|
-
db.exec("DROP TABLE quality_gates");
|
|
1359
|
-
db.exec("ALTER TABLE quality_gates_new RENAME TO quality_gates");
|
|
1360
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
1361
|
-
}
|
|
1362
|
-
// Ensure scope column exists on quality_gates and assessments (guard
|
|
1363
|
-
// against DBs that somehow lack it after a partial migration).
|
|
1364
|
-
ensureColumn(db, "quality_gates", "scope", "ALTER TABLE quality_gates ADD COLUMN scope TEXT NOT NULL DEFAULT 'slice'");
|
|
1365
|
-
ensureColumn(db, "assessments", "scope", "ALTER TABLE assessments ADD COLUMN scope TEXT NOT NULL DEFAULT ''");
|
|
1366
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1367
|
-
":version": 22,
|
|
1368
|
-
":applied_at": new Date().toISOString(),
|
|
1369
|
-
});
|
|
308
|
+
applyMigrationV22QualityGateRepair(db, { copyQualityGateRowsToRepairedTable });
|
|
309
|
+
recordSchemaVersion(db, 22);
|
|
1370
310
|
}
|
|
1371
311
|
|
|
1372
312
|
if (currentVersion < 23) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
// derivation must not read it as authoritative state.
|
|
1376
|
-
ensureColumn(db, "milestones", "sequence", "ALTER TABLE milestones ADD COLUMN sequence INTEGER DEFAULT 0");
|
|
1377
|
-
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
1378
|
-
":version": 23,
|
|
1379
|
-
":applied_at": new Date().toISOString(),
|
|
1380
|
-
});
|
|
313
|
+
applyMigrationV23MilestoneQueue(db);
|
|
314
|
+
recordSchemaVersion(db, 23);
|
|
1381
315
|
}
|
|
1382
316
|
|
|
1383
317
|
if (currentVersion < 24) {
|
|
@@ -1386,20 +320,19 @@ function migrateSchema(db: DbAdapter): void {
|
|
|
1386
320
|
// helper runs in the fresh-install path); for upgraded DBs this is
|
|
1387
321
|
// the only place these tables get created.
|
|
1388
322
|
createCoordinationTablesV24(db);
|
|
1389
|
-
db
|
|
1390
|
-
":version": 24,
|
|
1391
|
-
":applied_at": new Date().toISOString(),
|
|
1392
|
-
});
|
|
323
|
+
recordSchemaVersion(db, 24);
|
|
1393
324
|
}
|
|
1394
325
|
|
|
1395
326
|
if (currentVersion < 25) {
|
|
1396
327
|
// v25: runtime_kv non-correctness-critical key-value storage. See
|
|
1397
328
|
// createRuntimeKvTableV25 for the full schema + invariants.
|
|
1398
329
|
createRuntimeKvTableV25(db);
|
|
1399
|
-
db
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
330
|
+
recordSchemaVersion(db, 25);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (currentVersion < 26) {
|
|
334
|
+
applyMigrationV26MilestoneCommitAttributions(db);
|
|
335
|
+
recordSchemaVersion(db, 26);
|
|
1403
336
|
}
|
|
1404
337
|
|
|
1405
338
|
db.exec("COMMIT");
|
|
@@ -1413,9 +346,7 @@ let currentDb: DbAdapter | null = null;
|
|
|
1413
346
|
let currentPath: string | null = null;
|
|
1414
347
|
let currentPid: number = 0;
|
|
1415
348
|
let _exitHandlerRegistered = false;
|
|
1416
|
-
|
|
1417
|
-
let _lastDbError: Error | null = null;
|
|
1418
|
-
let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
|
|
349
|
+
const _dbOpenState = createDbOpenState();
|
|
1419
350
|
/**
|
|
1420
351
|
* Identity key of the workspace whose connection is currently active
|
|
1421
352
|
* (currentDb). Set by openDatabaseByWorkspace(); null when the active
|
|
@@ -1436,11 +367,29 @@ let _currentIdentityKey: string | null = null;
|
|
|
1436
367
|
* The cache allows fast re-activation of a previously opened connection when
|
|
1437
368
|
* callers switch between known workspaces via openDatabaseByWorkspace().
|
|
1438
369
|
*/
|
|
1439
|
-
const _dbCache =
|
|
370
|
+
const _dbCache = createDbConnectionCache();
|
|
1440
371
|
|
|
1441
372
|
/** Test helper: expose the internal cache for inspection. Not for production use. */
|
|
1442
|
-
export function _getDbCache(): ReadonlyMap<string,
|
|
1443
|
-
return _dbCache;
|
|
373
|
+
export function _getDbCache(): ReadonlyMap<string, DbConnectionCacheEntry> {
|
|
374
|
+
return _dbCache.asReadonlyMap();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function closeCachedConnection(entry: DbConnectionCacheEntry, source: "all" | "workspace"): void {
|
|
378
|
+
try {
|
|
379
|
+
entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
380
|
+
} catch (e) {
|
|
381
|
+
if (source === "workspace") logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`);
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
entry.db.exec("PRAGMA incremental_vacuum(64)");
|
|
385
|
+
} catch (e) {
|
|
386
|
+
if (source === "workspace") logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`);
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
entry.db.close();
|
|
390
|
+
} catch (e) {
|
|
391
|
+
if (source === "workspace") logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`);
|
|
392
|
+
}
|
|
1444
393
|
}
|
|
1445
394
|
|
|
1446
395
|
/**
|
|
@@ -1453,13 +402,7 @@ export function _getDbCache(): ReadonlyMap<string, { dbPath: string; db: DbAdapt
|
|
|
1453
402
|
*/
|
|
1454
403
|
export function closeAllDatabases(): void {
|
|
1455
404
|
// Close all non-active cached connections first.
|
|
1456
|
-
|
|
1457
|
-
if (entry.db === currentDb) continue; // handled by closeDatabase() below
|
|
1458
|
-
_dbCache.delete(key);
|
|
1459
|
-
try { entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch { /* best-effort */ }
|
|
1460
|
-
try { entry.db.exec("PRAGMA incremental_vacuum(64)"); } catch { /* best-effort */ }
|
|
1461
|
-
try { entry.db.close(); } catch { /* best-effort */ }
|
|
1462
|
-
}
|
|
405
|
+
_dbCache.closeNonActive(currentDb, (entry) => closeCachedConnection(entry, "all"));
|
|
1463
406
|
closeDatabase();
|
|
1464
407
|
}
|
|
1465
408
|
|
|
@@ -1489,7 +432,7 @@ export function openDatabaseByWorkspace(workspace: GsdWorkspace): boolean {
|
|
|
1489
432
|
currentDb = cached.db;
|
|
1490
433
|
currentPath = cached.dbPath;
|
|
1491
434
|
currentPid = process.pid;
|
|
1492
|
-
|
|
435
|
+
_dbOpenState.markAttempted();
|
|
1493
436
|
_currentIdentityKey = key;
|
|
1494
437
|
return true;
|
|
1495
438
|
}
|
|
@@ -1581,21 +524,13 @@ export function closeDatabaseByWorkspace(workspace: GsdWorkspace): void {
|
|
|
1581
524
|
closeDatabase();
|
|
1582
525
|
} else {
|
|
1583
526
|
// Connection was displaced by a later open; close the adapter directly.
|
|
1584
|
-
|
|
1585
|
-
cached.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
1586
|
-
} catch (e) { logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`); }
|
|
1587
|
-
try {
|
|
1588
|
-
cached.db.exec("PRAGMA incremental_vacuum(64)");
|
|
1589
|
-
} catch (e) { logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`); }
|
|
1590
|
-
try {
|
|
1591
|
-
cached.db.close();
|
|
1592
|
-
} catch (e) { logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`); }
|
|
527
|
+
closeCachedConnection(cached, "workspace");
|
|
1593
528
|
}
|
|
1594
529
|
}
|
|
1595
530
|
|
|
1596
531
|
export function getDbProvider(): ProviderName | null {
|
|
1597
|
-
|
|
1598
|
-
return
|
|
532
|
+
providerLoader.load();
|
|
533
|
+
return providerLoader.getProviderName();
|
|
1599
534
|
}
|
|
1600
535
|
|
|
1601
536
|
export function isDbAvailable(): boolean {
|
|
@@ -1609,7 +544,7 @@ export function isDbAvailable(): boolean {
|
|
|
1609
544
|
* trigger a false degraded-mode warning.
|
|
1610
545
|
*/
|
|
1611
546
|
export function wasDbOpenAttempted(): boolean {
|
|
1612
|
-
return
|
|
547
|
+
return _dbOpenState.snapshot().attempted;
|
|
1613
548
|
}
|
|
1614
549
|
|
|
1615
550
|
export function getDbStatus(): {
|
|
@@ -1617,56 +552,44 @@ export function getDbStatus(): {
|
|
|
1617
552
|
provider: ProviderName | null;
|
|
1618
553
|
attempted: boolean;
|
|
1619
554
|
lastError: Error | null;
|
|
1620
|
-
lastPhase:
|
|
555
|
+
lastPhase: DbOpenPhase | null;
|
|
1621
556
|
} {
|
|
1622
|
-
|
|
557
|
+
providerLoader.load();
|
|
558
|
+
const openState = _dbOpenState.snapshot();
|
|
1623
559
|
return {
|
|
1624
560
|
available: currentDb !== null,
|
|
1625
|
-
provider:
|
|
1626
|
-
attempted:
|
|
1627
|
-
lastError:
|
|
1628
|
-
lastPhase:
|
|
561
|
+
provider: providerLoader.getProviderName(),
|
|
562
|
+
attempted: openState.attempted,
|
|
563
|
+
lastError: openState.lastError,
|
|
564
|
+
lastPhase: openState.lastPhase,
|
|
1629
565
|
};
|
|
1630
566
|
}
|
|
1631
567
|
|
|
1632
568
|
export function openDatabase(path: string): boolean {
|
|
1633
|
-
|
|
569
|
+
_dbOpenState.markAttempted();
|
|
1634
570
|
if (currentDb && currentPath !== path) closeDatabase();
|
|
1635
571
|
if (currentDb && currentPath === path) return true;
|
|
1636
572
|
|
|
1637
573
|
// Reset error state only when a new open attempt is actually going to run.
|
|
1638
|
-
|
|
1639
|
-
_lastDbPhase = null;
|
|
574
|
+
_dbOpenState.clearError();
|
|
1640
575
|
|
|
1641
576
|
let rawDb: unknown;
|
|
1642
|
-
let
|
|
1643
|
-
let fallbackModule: unknown = null;
|
|
577
|
+
let fallbackOpen: SqliteFallbackOpen | null = null;
|
|
1644
578
|
try {
|
|
1645
|
-
rawDb =
|
|
579
|
+
rawDb = providerLoader.openRaw(path);
|
|
1646
580
|
} catch (primaryErr) {
|
|
1647
|
-
|
|
1648
|
-
_lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
|
|
581
|
+
_dbOpenState.recordError("open", primaryErr);
|
|
1649
582
|
// node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
if (typeof Db === "function") {
|
|
1655
|
-
rawDb = new Db(path);
|
|
1656
|
-
fallbackProvider = "better-sqlite3";
|
|
1657
|
-
fallbackModule = Db;
|
|
1658
|
-
_lastDbError = null;
|
|
1659
|
-
_lastDbPhase = null;
|
|
1660
|
-
}
|
|
1661
|
-
} catch {
|
|
1662
|
-
// fallback unavailable; surface original error
|
|
1663
|
-
}
|
|
583
|
+
fallbackOpen = providerLoader.tryOpenBetterSqliteFallback(path);
|
|
584
|
+
if (fallbackOpen) {
|
|
585
|
+
rawDb = fallbackOpen.rawDb;
|
|
586
|
+
_dbOpenState.clearError();
|
|
1664
587
|
}
|
|
1665
588
|
if (!rawDb) throw primaryErr;
|
|
1666
589
|
}
|
|
1667
590
|
if (!rawDb) return false;
|
|
1668
591
|
|
|
1669
|
-
const adapter =
|
|
592
|
+
const adapter = createDbAdapter(rawDb);
|
|
1670
593
|
const fileBacked = path !== ":memory:";
|
|
1671
594
|
try {
|
|
1672
595
|
initSchema(adapter, fileBacked);
|
|
@@ -1679,24 +602,19 @@ export function openDatabase(path: string): boolean {
|
|
|
1679
602
|
initSchema(adapter, fileBacked);
|
|
1680
603
|
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
1681
604
|
} catch (retryErr) {
|
|
1682
|
-
|
|
1683
|
-
_lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
|
|
605
|
+
_dbOpenState.recordError("vacuum-recovery", retryErr);
|
|
1684
606
|
try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
|
|
1685
607
|
throw retryErr;
|
|
1686
608
|
}
|
|
1687
609
|
} else {
|
|
1688
|
-
|
|
1689
|
-
_lastDbError = err instanceof Error ? err : new Error(String(err));
|
|
610
|
+
_dbOpenState.recordError("initSchema", err);
|
|
1690
611
|
try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
|
|
1691
612
|
throw err;
|
|
1692
613
|
}
|
|
1693
614
|
}
|
|
1694
615
|
|
|
1695
616
|
// Commit fallback provider switch only after open + schema both succeeded.
|
|
1696
|
-
if (
|
|
1697
|
-
providerName = fallbackProvider;
|
|
1698
|
-
providerModule = fallbackModule;
|
|
1699
|
-
}
|
|
617
|
+
if (fallbackOpen) providerLoader.commitFallback(fallbackOpen);
|
|
1700
618
|
|
|
1701
619
|
currentDb = adapter;
|
|
1702
620
|
currentPath = path;
|
|
@@ -1735,9 +653,36 @@ export function closeDatabase(): void {
|
|
|
1735
653
|
}
|
|
1736
654
|
// Reset session-scoped state unconditionally so stale error info from a
|
|
1737
655
|
// failed open doesn't persist into the next open attempt or status check.
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
656
|
+
_dbOpenState.reset();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Re-open the active database connection from disk.
|
|
661
|
+
*
|
|
662
|
+
* Auto-mode can observe artifacts written by a workflow server running in a
|
|
663
|
+
* different process before its long-lived singleton has re-synchronized. The
|
|
664
|
+
* recovery path uses this to force the next state derivation to read from the
|
|
665
|
+
* current on-disk database instead of continuing with a possibly stale handle.
|
|
666
|
+
*/
|
|
667
|
+
export function refreshOpenDatabaseFromDisk(): boolean {
|
|
668
|
+
if (!currentDb || !currentPath) return false;
|
|
669
|
+
if (currentPath === ":memory:") return false;
|
|
670
|
+
|
|
671
|
+
const dbPath = currentPath;
|
|
672
|
+
const identityKey = _currentIdentityKey;
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
closeDatabase();
|
|
676
|
+
const opened = openDatabase(dbPath);
|
|
677
|
+
if (opened && identityKey && currentDb) {
|
|
678
|
+
_dbCache.set(identityKey, { dbPath, db: currentDb });
|
|
679
|
+
_currentIdentityKey = identityKey;
|
|
680
|
+
}
|
|
681
|
+
return opened;
|
|
682
|
+
} catch (e) {
|
|
683
|
+
logWarning("db", `database refresh failed: ${(e as Error).message}`);
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
1741
686
|
}
|
|
1742
687
|
|
|
1743
688
|
/** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
|
|
@@ -1756,7 +701,16 @@ export function checkpointDatabase(): void {
|
|
|
1756
701
|
} catch (e) { logWarning("db", `WAL checkpoint failed: ${(e as Error).message}`); }
|
|
1757
702
|
}
|
|
1758
703
|
|
|
1759
|
-
|
|
704
|
+
const _transactionRunner = createDbTransactionRunner();
|
|
705
|
+
|
|
706
|
+
function createTransactionControls(db: DbAdapter) {
|
|
707
|
+
return {
|
|
708
|
+
begin: () => db.exec("BEGIN"),
|
|
709
|
+
beginRead: () => db.exec("BEGIN DEFERRED"),
|
|
710
|
+
commit: () => db.exec("COMMIT"),
|
|
711
|
+
rollback: () => db.exec("ROLLBACK"),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
1760
714
|
|
|
1761
715
|
/**
|
|
1762
716
|
* Whether the current call is running inside an active SQLite transaction.
|
|
@@ -1765,35 +719,12 @@ let _txDepth = 0;
|
|
|
1765
719
|
* and would mask the original error with a secondary "cannot VACUUM" throw.
|
|
1766
720
|
*/
|
|
1767
721
|
export function isInTransaction(): boolean {
|
|
1768
|
-
return
|
|
722
|
+
return _transactionRunner.isInTransaction();
|
|
1769
723
|
}
|
|
1770
724
|
|
|
1771
725
|
export function transaction<T>(fn: () => T): T {
|
|
1772
726
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1773
|
-
|
|
1774
|
-
// Re-entrant: if already inside a transaction, just run fn() without
|
|
1775
|
-
// starting a new one. SQLite does not support nested BEGIN/COMMIT.
|
|
1776
|
-
if (_txDepth > 0) {
|
|
1777
|
-
_txDepth++;
|
|
1778
|
-
try {
|
|
1779
|
-
return fn();
|
|
1780
|
-
} finally {
|
|
1781
|
-
_txDepth--;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
currentDb.exec("BEGIN");
|
|
1786
|
-
_txDepth++;
|
|
1787
|
-
try {
|
|
1788
|
-
const result = fn();
|
|
1789
|
-
currentDb.exec("COMMIT");
|
|
1790
|
-
return result;
|
|
1791
|
-
} catch (err) {
|
|
1792
|
-
currentDb.exec("ROLLBACK");
|
|
1793
|
-
throw err;
|
|
1794
|
-
} finally {
|
|
1795
|
-
_txDepth--;
|
|
1796
|
-
}
|
|
727
|
+
return _transactionRunner.transaction(createTransactionControls(currentDb), fn);
|
|
1797
728
|
}
|
|
1798
729
|
|
|
1799
730
|
/**
|
|
@@ -1806,36 +737,14 @@ export function transaction<T>(fn: () => T): T {
|
|
|
1806
737
|
export function readTransaction<T>(fn: () => T): T {
|
|
1807
738
|
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1808
739
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
currentDb.exec("BEGIN DEFERRED");
|
|
1819
|
-
_txDepth++;
|
|
1820
|
-
try {
|
|
1821
|
-
const result = fn();
|
|
1822
|
-
currentDb.exec("COMMIT");
|
|
1823
|
-
return result;
|
|
1824
|
-
} catch (err) {
|
|
1825
|
-
try {
|
|
1826
|
-
currentDb.exec("ROLLBACK");
|
|
1827
|
-
} catch (rollbackErr) {
|
|
1828
|
-
// A failed ROLLBACK after a failed read is a split-brain signal —
|
|
1829
|
-
// the transaction is in an indeterminate state. Surface it via the
|
|
1830
|
-
// logger instead of swallowing it.
|
|
1831
|
-
logError("db", "snapshotState ROLLBACK failed", {
|
|
1832
|
-
error: (rollbackErr as Error).message,
|
|
1833
|
-
});
|
|
1834
|
-
}
|
|
1835
|
-
throw err;
|
|
1836
|
-
} finally {
|
|
1837
|
-
_txDepth--;
|
|
1838
|
-
}
|
|
740
|
+
return _transactionRunner.readTransaction(createTransactionControls(currentDb), fn, (rollbackErr) => {
|
|
741
|
+
// A failed ROLLBACK after a failed read is a split-brain signal —
|
|
742
|
+
// the transaction is in an indeterminate state. Surface it via the
|
|
743
|
+
// logger instead of swallowing it.
|
|
744
|
+
logError("db", "snapshotState ROLLBACK failed", {
|
|
745
|
+
error: rollbackErr.message,
|
|
746
|
+
});
|
|
747
|
+
});
|
|
1839
748
|
}
|
|
1840
749
|
|
|
1841
750
|
export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
@@ -1861,37 +770,13 @@ export function getDecisionById(id: string): Decision | null {
|
|
|
1861
770
|
if (!currentDb) return null;
|
|
1862
771
|
const row = currentDb.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
|
|
1863
772
|
if (!row) return null;
|
|
1864
|
-
return
|
|
1865
|
-
seq: row["seq"] as number,
|
|
1866
|
-
id: row["id"] as string,
|
|
1867
|
-
when_context: row["when_context"] as string,
|
|
1868
|
-
scope: row["scope"] as string,
|
|
1869
|
-
decision: row["decision"] as string,
|
|
1870
|
-
choice: row["choice"] as string,
|
|
1871
|
-
rationale: row["rationale"] as string,
|
|
1872
|
-
revisable: row["revisable"] as string,
|
|
1873
|
-
made_by: (row["made_by"] as string as import("./types.js").DecisionMadeBy) ?? "agent",
|
|
1874
|
-
source: (row["source"] as string) ?? "discussion",
|
|
1875
|
-
superseded_by: (row["superseded_by"] as string) ?? null,
|
|
1876
|
-
};
|
|
773
|
+
return rowToDecision(row);
|
|
1877
774
|
}
|
|
1878
775
|
|
|
1879
776
|
export function getActiveDecisions(): Decision[] {
|
|
1880
777
|
if (!currentDb) return [];
|
|
1881
778
|
const rows = currentDb.prepare("SELECT * FROM active_decisions").all();
|
|
1882
|
-
return rows.map(
|
|
1883
|
-
seq: row["seq"] as number,
|
|
1884
|
-
id: row["id"] as string,
|
|
1885
|
-
when_context: row["when_context"] as string,
|
|
1886
|
-
scope: row["scope"] as string,
|
|
1887
|
-
decision: row["decision"] as string,
|
|
1888
|
-
choice: row["choice"] as string,
|
|
1889
|
-
rationale: row["rationale"] as string,
|
|
1890
|
-
revisable: row["revisable"] as string,
|
|
1891
|
-
made_by: (row["made_by"] as string as import("./types.js").DecisionMadeBy) ?? "agent",
|
|
1892
|
-
source: (row["source"] as string) ?? "discussion",
|
|
1893
|
-
superseded_by: null,
|
|
1894
|
-
}));
|
|
779
|
+
return rows.map(rowToActiveDecision);
|
|
1895
780
|
}
|
|
1896
781
|
|
|
1897
782
|
export function insertRequirement(r: Requirement): void {
|
|
@@ -1919,39 +804,13 @@ export function getRequirementById(id: string): Requirement | null {
|
|
|
1919
804
|
if (!currentDb) return null;
|
|
1920
805
|
const row = currentDb.prepare("SELECT * FROM requirements WHERE id = ?").get(id);
|
|
1921
806
|
if (!row) return null;
|
|
1922
|
-
return
|
|
1923
|
-
id: row["id"] as string,
|
|
1924
|
-
class: row["class"] as string,
|
|
1925
|
-
status: row["status"] as string,
|
|
1926
|
-
description: row["description"] as string,
|
|
1927
|
-
why: row["why"] as string,
|
|
1928
|
-
source: row["source"] as string,
|
|
1929
|
-
primary_owner: row["primary_owner"] as string,
|
|
1930
|
-
supporting_slices: row["supporting_slices"] as string,
|
|
1931
|
-
validation: row["validation"] as string,
|
|
1932
|
-
notes: row["notes"] as string,
|
|
1933
|
-
full_content: row["full_content"] as string,
|
|
1934
|
-
superseded_by: (row["superseded_by"] as string) ?? null,
|
|
1935
|
-
};
|
|
807
|
+
return rowToRequirement(row);
|
|
1936
808
|
}
|
|
1937
809
|
|
|
1938
810
|
export function getActiveRequirements(): Requirement[] {
|
|
1939
811
|
if (!currentDb) return [];
|
|
1940
812
|
const rows = currentDb.prepare("SELECT * FROM active_requirements").all();
|
|
1941
|
-
return rows.map(
|
|
1942
|
-
id: row["id"] as string,
|
|
1943
|
-
class: row["class"] as string,
|
|
1944
|
-
status: row["status"] as string,
|
|
1945
|
-
description: row["description"] as string,
|
|
1946
|
-
why: row["why"] as string,
|
|
1947
|
-
source: row["source"] as string,
|
|
1948
|
-
primary_owner: row["primary_owner"] as string,
|
|
1949
|
-
supporting_slices: row["supporting_slices"] as string,
|
|
1950
|
-
validation: row["validation"] as string,
|
|
1951
|
-
notes: row["notes"] as string,
|
|
1952
|
-
full_content: row["full_content"] as string,
|
|
1953
|
-
superseded_by: null,
|
|
1954
|
-
}));
|
|
813
|
+
return rows.map(rowToActiveRequirement);
|
|
1955
814
|
}
|
|
1956
815
|
|
|
1957
816
|
export function getRequirementCounts(): {
|
|
@@ -1968,18 +827,7 @@ export function getRequirementCounts(): {
|
|
|
1968
827
|
const rows = currentDb
|
|
1969
828
|
.prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
|
|
1970
829
|
.all();
|
|
1971
|
-
|
|
1972
|
-
for (const row of rows) {
|
|
1973
|
-
const status = String(row["status"] ?? "");
|
|
1974
|
-
const count = Number(row["count"] ?? 0);
|
|
1975
|
-
counts.total += count;
|
|
1976
|
-
if (status === "active") counts.active += count;
|
|
1977
|
-
else if (status === "validated") counts.validated += count;
|
|
1978
|
-
else if (status === "deferred") counts.deferred += count;
|
|
1979
|
-
else if (status === "out-of-scope" || status === "out_of_scope") counts.outOfScope += count;
|
|
1980
|
-
else if (status === "blocked") counts.blocked += count;
|
|
1981
|
-
}
|
|
1982
|
-
return counts;
|
|
830
|
+
return rowsToRequirementCounts(rows);
|
|
1983
831
|
}
|
|
1984
832
|
|
|
1985
833
|
export function getDbOwnerPid(): number {
|
|
@@ -1995,9 +843,7 @@ export function _getAdapter(): DbAdapter | null {
|
|
|
1995
843
|
}
|
|
1996
844
|
|
|
1997
845
|
export function _resetProvider(): void {
|
|
1998
|
-
|
|
1999
|
-
providerModule = null;
|
|
2000
|
-
providerName = null;
|
|
846
|
+
providerLoader.reset();
|
|
2001
847
|
}
|
|
2002
848
|
|
|
2003
849
|
export function upsertDecision(d: Omit<Decision, "seq">): void {
|
|
@@ -2458,54 +1304,6 @@ export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId:
|
|
|
2458
1304
|
});
|
|
2459
1305
|
}
|
|
2460
1306
|
|
|
2461
|
-
export interface SliceRow {
|
|
2462
|
-
milestone_id: string;
|
|
2463
|
-
id: string;
|
|
2464
|
-
title: string;
|
|
2465
|
-
status: string;
|
|
2466
|
-
risk: string;
|
|
2467
|
-
depends: string[];
|
|
2468
|
-
demo: string;
|
|
2469
|
-
created_at: string;
|
|
2470
|
-
completed_at: string | null;
|
|
2471
|
-
full_summary_md: string;
|
|
2472
|
-
full_uat_md: string;
|
|
2473
|
-
goal: string;
|
|
2474
|
-
success_criteria: string;
|
|
2475
|
-
proof_level: string;
|
|
2476
|
-
integration_closure: string;
|
|
2477
|
-
observability_impact: string;
|
|
2478
|
-
sequence: number;
|
|
2479
|
-
replan_triggered_at: string | null;
|
|
2480
|
-
is_sketch: number;
|
|
2481
|
-
sketch_scope: string;
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
function rowToSlice(row: Record<string, unknown>): SliceRow {
|
|
2485
|
-
return {
|
|
2486
|
-
milestone_id: row["milestone_id"] as string,
|
|
2487
|
-
id: row["id"] as string,
|
|
2488
|
-
title: row["title"] as string,
|
|
2489
|
-
status: row["status"] as string,
|
|
2490
|
-
risk: row["risk"] as string,
|
|
2491
|
-
depends: JSON.parse((row["depends"] as string) || "[]"),
|
|
2492
|
-
demo: (row["demo"] as string) ?? "",
|
|
2493
|
-
created_at: row["created_at"] as string,
|
|
2494
|
-
completed_at: (row["completed_at"] as string) ?? null,
|
|
2495
|
-
full_summary_md: (row["full_summary_md"] as string) ?? "",
|
|
2496
|
-
full_uat_md: (row["full_uat_md"] as string) ?? "",
|
|
2497
|
-
goal: (row["goal"] as string) ?? "",
|
|
2498
|
-
success_criteria: (row["success_criteria"] as string) ?? "",
|
|
2499
|
-
proof_level: (row["proof_level"] as string) ?? "",
|
|
2500
|
-
integration_closure: (row["integration_closure"] as string) ?? "",
|
|
2501
|
-
observability_impact: (row["observability_impact"] as string) ?? "",
|
|
2502
|
-
sequence: (row["sequence"] as number) ?? 0,
|
|
2503
|
-
replan_triggered_at: (row["replan_triggered_at"] as string) ?? null,
|
|
2504
|
-
is_sketch: (row["is_sketch"] as number) ?? 0,
|
|
2505
|
-
sketch_scope: (row["sketch_scope"] as string) ?? "",
|
|
2506
|
-
};
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
1307
|
export function getSlice(milestoneId: string, sliceId: string): SliceRow | null {
|
|
2510
1308
|
if (!currentDb) return null;
|
|
2511
1309
|
const row = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid AND id = :sid").get({ ":mid": milestoneId, ":sid": sliceId });
|
|
@@ -2540,116 +1338,6 @@ export function setSliceSummaryMd(milestoneId: string, sliceId: string, summaryM
|
|
|
2540
1338
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
|
|
2541
1339
|
}
|
|
2542
1340
|
|
|
2543
|
-
export interface TaskRow {
|
|
2544
|
-
milestone_id: string;
|
|
2545
|
-
slice_id: string;
|
|
2546
|
-
id: string;
|
|
2547
|
-
title: string;
|
|
2548
|
-
status: string;
|
|
2549
|
-
one_liner: string;
|
|
2550
|
-
narrative: string;
|
|
2551
|
-
verification_result: string;
|
|
2552
|
-
duration: string;
|
|
2553
|
-
completed_at: string | null;
|
|
2554
|
-
blocker_discovered: boolean;
|
|
2555
|
-
deviations: string;
|
|
2556
|
-
known_issues: string;
|
|
2557
|
-
key_files: string[];
|
|
2558
|
-
key_decisions: string[];
|
|
2559
|
-
full_summary_md: string;
|
|
2560
|
-
description: string;
|
|
2561
|
-
estimate: string;
|
|
2562
|
-
files: string[];
|
|
2563
|
-
verify: string;
|
|
2564
|
-
inputs: string[];
|
|
2565
|
-
expected_output: string[];
|
|
2566
|
-
observability_impact: string;
|
|
2567
|
-
full_plan_md: string;
|
|
2568
|
-
sequence: number;
|
|
2569
|
-
// ADR-011 Phase 2 escalation fields
|
|
2570
|
-
blocker_source: string;
|
|
2571
|
-
escalation_pending: number;
|
|
2572
|
-
escalation_awaiting_review: number;
|
|
2573
|
-
escalation_artifact_path: string | null;
|
|
2574
|
-
escalation_override_applied_at: string | null;
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
function parseTaskArrayColumn(raw: unknown): string[] {
|
|
2578
|
-
if (typeof raw !== "string" || raw.trim() === "") return [];
|
|
2579
|
-
|
|
2580
|
-
try {
|
|
2581
|
-
const parsed = JSON.parse(raw);
|
|
2582
|
-
if (Array.isArray(parsed)) return parsed.map((value) => String(value));
|
|
2583
|
-
if (parsed === null || parsed === undefined || parsed === "") return [];
|
|
2584
|
-
return [String(parsed)];
|
|
2585
|
-
} catch {
|
|
2586
|
-
// Older/corrupt rows may contain comma-separated strings instead of JSON.
|
|
2587
|
-
return raw
|
|
2588
|
-
.split(",")
|
|
2589
|
-
.map((value) => value.trim())
|
|
2590
|
-
.filter(Boolean);
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
function rowToTask(row: Record<string, unknown>): TaskRow {
|
|
2595
|
-
const parseTaskArray = (value: unknown): string[] => {
|
|
2596
|
-
if (Array.isArray(value)) {
|
|
2597
|
-
return value.filter((entry): entry is string => typeof entry === "string");
|
|
2598
|
-
}
|
|
2599
|
-
if (typeof value !== "string") return [];
|
|
2600
|
-
|
|
2601
|
-
const trimmed = value.trim();
|
|
2602
|
-
if (!trimmed) return [];
|
|
2603
|
-
|
|
2604
|
-
try {
|
|
2605
|
-
const parsed = JSON.parse(trimmed);
|
|
2606
|
-
if (Array.isArray(parsed)) {
|
|
2607
|
-
return parsed.filter((entry): entry is string => typeof entry === "string");
|
|
2608
|
-
}
|
|
2609
|
-
if (typeof parsed === "string" && parsed.trim()) {
|
|
2610
|
-
return [parsed.trim()];
|
|
2611
|
-
}
|
|
2612
|
-
} catch {
|
|
2613
|
-
// Older/corrupt DB rows may contain raw comma-separated paths instead of JSON arrays.
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
return trimmed.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2617
|
-
};
|
|
2618
|
-
|
|
2619
|
-
return {
|
|
2620
|
-
milestone_id: row["milestone_id"] as string,
|
|
2621
|
-
slice_id: row["slice_id"] as string,
|
|
2622
|
-
id: row["id"] as string,
|
|
2623
|
-
title: row["title"] as string,
|
|
2624
|
-
status: row["status"] as string,
|
|
2625
|
-
one_liner: row["one_liner"] as string,
|
|
2626
|
-
narrative: row["narrative"] as string,
|
|
2627
|
-
verification_result: row["verification_result"] as string,
|
|
2628
|
-
duration: row["duration"] as string,
|
|
2629
|
-
completed_at: (row["completed_at"] as string) ?? null,
|
|
2630
|
-
blocker_discovered: (row["blocker_discovered"] as number) === 1,
|
|
2631
|
-
deviations: row["deviations"] as string,
|
|
2632
|
-
known_issues: row["known_issues"] as string,
|
|
2633
|
-
key_files: parseTaskArrayColumn(row["key_files"]),
|
|
2634
|
-
key_decisions: parseTaskArrayColumn(row["key_decisions"]),
|
|
2635
|
-
full_summary_md: row["full_summary_md"] as string,
|
|
2636
|
-
description: (row["description"] as string) ?? "",
|
|
2637
|
-
estimate: (row["estimate"] as string) ?? "",
|
|
2638
|
-
files: parseTaskArray(row["files"]),
|
|
2639
|
-
verify: (row["verify"] as string) ?? "",
|
|
2640
|
-
inputs: parseTaskArray(row["inputs"]),
|
|
2641
|
-
expected_output: parseTaskArray(row["expected_output"]),
|
|
2642
|
-
observability_impact: (row["observability_impact"] as string) ?? "",
|
|
2643
|
-
full_plan_md: (row["full_plan_md"] as string) ?? "",
|
|
2644
|
-
sequence: (row["sequence"] as number) ?? 0,
|
|
2645
|
-
blocker_source: (row["blocker_source"] as string) ?? "",
|
|
2646
|
-
escalation_pending: (row["escalation_pending"] as number) ?? 0,
|
|
2647
|
-
escalation_awaiting_review: (row["escalation_awaiting_review"] as number) ?? 0,
|
|
2648
|
-
escalation_artifact_path: (row["escalation_artifact_path"] as string) ?? null,
|
|
2649
|
-
escalation_override_applied_at: (row["escalation_override_applied_at"] as string) ?? null,
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2652
|
-
|
|
2653
1341
|
export function getTask(milestoneId: string, sliceId: string, taskId: string): TaskRow | null {
|
|
2654
1342
|
if (!currentDb) return null;
|
|
2655
1343
|
const row = currentDb.prepare(
|
|
@@ -2667,6 +1355,45 @@ export function getSliceTasks(milestoneId: string, sliceId: string): TaskRow[] {
|
|
|
2667
1355
|
return rows.map(rowToTask);
|
|
2668
1356
|
}
|
|
2669
1357
|
|
|
1358
|
+
export function getCompletedMilestoneTaskFileHints(milestoneId: string): string[] {
|
|
1359
|
+
if (!currentDb) return [];
|
|
1360
|
+
const rows = currentDb.prepare(
|
|
1361
|
+
`SELECT files, key_files
|
|
1362
|
+
FROM tasks
|
|
1363
|
+
WHERE milestone_id = :mid AND status IN ('complete', 'done')`,
|
|
1364
|
+
).all({ ":mid": milestoneId }) as Array<Record<string, unknown>>;
|
|
1365
|
+
|
|
1366
|
+
const hints = new Set<string>();
|
|
1367
|
+
for (const row of rows) {
|
|
1368
|
+
for (const raw of [row["files"], row["key_files"]]) {
|
|
1369
|
+
for (const file of parseStringArrayColumn(raw)) {
|
|
1370
|
+
const normalized = normalizeRepoPath(file);
|
|
1371
|
+
if (normalized) hints.add(normalized);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return [...hints];
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
function parseStringArrayColumn(raw: unknown): string[] {
|
|
1379
|
+
if (Array.isArray(raw)) return raw.filter((entry): entry is string => typeof entry === "string");
|
|
1380
|
+
if (typeof raw !== "string") return [];
|
|
1381
|
+
const trimmed = raw.trim();
|
|
1382
|
+
if (!trimmed) return [];
|
|
1383
|
+
try {
|
|
1384
|
+
const parsed = JSON.parse(trimmed);
|
|
1385
|
+
if (Array.isArray(parsed)) return parsed.filter((entry): entry is string => typeof entry === "string");
|
|
1386
|
+
if (typeof parsed === "string") return [parsed];
|
|
1387
|
+
} catch {
|
|
1388
|
+
return trimmed.split(",");
|
|
1389
|
+
}
|
|
1390
|
+
return [];
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function normalizeRepoPath(file: string): string {
|
|
1394
|
+
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
1395
|
+
}
|
|
1396
|
+
|
|
2670
1397
|
// ─── ADR-011 Phase 2 escalation helpers ──────────────────────────────────
|
|
2671
1398
|
|
|
2672
1399
|
/** Set pause-on-escalation state on a completed task. Mutually exclusive with awaiting_review. */
|
|
@@ -2831,72 +1558,6 @@ export function getVerificationEvidence(milestoneId: string, sliceId: string, ta
|
|
|
2831
1558
|
return rows as unknown as VerificationEvidenceRow[];
|
|
2832
1559
|
}
|
|
2833
1560
|
|
|
2834
|
-
export interface MilestoneRow {
|
|
2835
|
-
id: string;
|
|
2836
|
-
title: string;
|
|
2837
|
-
status: string;
|
|
2838
|
-
depends_on: string[];
|
|
2839
|
-
created_at: string;
|
|
2840
|
-
completed_at: string | null;
|
|
2841
|
-
vision: string;
|
|
2842
|
-
success_criteria: string[];
|
|
2843
|
-
key_risks: Array<{ risk: string; whyItMatters: string }>;
|
|
2844
|
-
proof_strategy: Array<{ riskOrUnknown: string; retireIn: string; whatWillBeProven: string }>;
|
|
2845
|
-
verification_contract: string;
|
|
2846
|
-
verification_integration: string;
|
|
2847
|
-
verification_operational: string;
|
|
2848
|
-
verification_uat: string;
|
|
2849
|
-
definition_of_done: string[];
|
|
2850
|
-
requirement_coverage: string;
|
|
2851
|
-
boundary_map_markdown: string;
|
|
2852
|
-
sequence: number;
|
|
2853
|
-
}
|
|
2854
|
-
|
|
2855
|
-
function rowToMilestone(row: Record<string, unknown>): MilestoneRow {
|
|
2856
|
-
return {
|
|
2857
|
-
id: row["id"] as string,
|
|
2858
|
-
title: row["title"] as string,
|
|
2859
|
-
status: row["status"] as string,
|
|
2860
|
-
depends_on: JSON.parse((row["depends_on"] as string) || "[]"),
|
|
2861
|
-
created_at: row["created_at"] as string,
|
|
2862
|
-
completed_at: (row["completed_at"] as string) ?? null,
|
|
2863
|
-
vision: (row["vision"] as string) ?? "",
|
|
2864
|
-
success_criteria: JSON.parse((row["success_criteria"] as string) || "[]"),
|
|
2865
|
-
key_risks: JSON.parse((row["key_risks"] as string) || "[]"),
|
|
2866
|
-
proof_strategy: JSON.parse((row["proof_strategy"] as string) || "[]"),
|
|
2867
|
-
verification_contract: (row["verification_contract"] as string) ?? "",
|
|
2868
|
-
verification_integration: (row["verification_integration"] as string) ?? "",
|
|
2869
|
-
verification_operational: (row["verification_operational"] as string) ?? "",
|
|
2870
|
-
verification_uat: (row["verification_uat"] as string) ?? "",
|
|
2871
|
-
definition_of_done: JSON.parse((row["definition_of_done"] as string) || "[]"),
|
|
2872
|
-
requirement_coverage: (row["requirement_coverage"] as string) ?? "",
|
|
2873
|
-
boundary_map_markdown: (row["boundary_map_markdown"] as string) ?? "",
|
|
2874
|
-
sequence: Number(row["sequence"] ?? 0),
|
|
2875
|
-
};
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
export interface ArtifactRow {
|
|
2879
|
-
path: string;
|
|
2880
|
-
artifact_type: string;
|
|
2881
|
-
milestone_id: string | null;
|
|
2882
|
-
slice_id: string | null;
|
|
2883
|
-
task_id: string | null;
|
|
2884
|
-
full_content: string;
|
|
2885
|
-
imported_at: string;
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
function rowToArtifact(row: Record<string, unknown>): ArtifactRow {
|
|
2889
|
-
return {
|
|
2890
|
-
path: row["path"] as string,
|
|
2891
|
-
artifact_type: row["artifact_type"] as string,
|
|
2892
|
-
milestone_id: (row["milestone_id"] as string) ?? null,
|
|
2893
|
-
slice_id: (row["slice_id"] as string) ?? null,
|
|
2894
|
-
task_id: (row["task_id"] as string) ?? null,
|
|
2895
|
-
full_content: row["full_content"] as string,
|
|
2896
|
-
imported_at: row["imported_at"] as string,
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
1561
|
export function getAllMilestones(): MilestoneRow[] {
|
|
2901
1562
|
if (!currentDb) return [];
|
|
2902
1563
|
const rows = currentDb.prepare(
|
|
@@ -2996,36 +1657,36 @@ export function getArtifact(path: string): ArtifactRow | null {
|
|
|
2996
1657
|
// ─── Lightweight Query Variants (hot-path optimized) ─────────────────────
|
|
2997
1658
|
|
|
2998
1659
|
/** Fast milestone status check — avoids deserializing JSON planning fields. */
|
|
2999
|
-
export function getActiveMilestoneIdFromDb():
|
|
1660
|
+
export function getActiveMilestoneIdFromDb(): IdStatusSummary | null {
|
|
3000
1661
|
if (!currentDb) return null;
|
|
3001
1662
|
const row = currentDb.prepare(
|
|
3002
1663
|
"SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'parked') ORDER BY id LIMIT 1",
|
|
3003
1664
|
).get();
|
|
3004
1665
|
if (!row) return null;
|
|
3005
|
-
return
|
|
1666
|
+
return rowToIdStatusSummary(row);
|
|
3006
1667
|
}
|
|
3007
1668
|
|
|
3008
1669
|
/** Fast slice status check — avoids deserializing JSON depends/planning fields. */
|
|
3009
|
-
export function getSliceStatusSummary(milestoneId: string):
|
|
1670
|
+
export function getSliceStatusSummary(milestoneId: string): IdStatusSummary[] {
|
|
3010
1671
|
if (!currentDb) return [];
|
|
3011
1672
|
return currentDb.prepare(
|
|
3012
1673
|
"SELECT id, status FROM slices WHERE milestone_id = :mid ORDER BY sequence, id",
|
|
3013
|
-
).all({ ":mid": milestoneId }).map(
|
|
1674
|
+
).all({ ":mid": milestoneId }).map(rowToIdStatusSummary);
|
|
3014
1675
|
}
|
|
3015
1676
|
|
|
3016
1677
|
/** Fast task status check — avoids deserializing JSON arrays and large text fields. */
|
|
3017
|
-
export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string):
|
|
1678
|
+
export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): ActiveTaskSummary | null {
|
|
3018
1679
|
if (!currentDb) return null;
|
|
3019
1680
|
const row = currentDb.prepare(
|
|
3020
1681
|
"SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1",
|
|
3021
1682
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
3022
1683
|
if (!row) return null;
|
|
3023
|
-
return
|
|
1684
|
+
return rowToActiveTaskSummary(row);
|
|
3024
1685
|
}
|
|
3025
1686
|
|
|
3026
1687
|
/** Count tasks by status for a slice — useful for progress reporting without full row load. */
|
|
3027
|
-
export function getSliceTaskCounts(milestoneId: string, sliceId: string):
|
|
3028
|
-
if (!currentDb) return
|
|
1688
|
+
export function getSliceTaskCounts(milestoneId: string, sliceId: string): TaskStatusCounts {
|
|
1689
|
+
if (!currentDb) return emptyTaskStatusCounts();
|
|
3029
1690
|
const row = currentDb.prepare(
|
|
3030
1691
|
`SELECT
|
|
3031
1692
|
COUNT(*) as total,
|
|
@@ -3033,8 +1694,7 @@ export function getSliceTaskCounts(milestoneId: string, sliceId: string): { tota
|
|
|
3033
1694
|
SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
|
|
3034
1695
|
FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
3035
1696
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
3036
|
-
|
|
3037
|
-
return { total: (row["total"] as number) ?? 0, done: (row["done"] as number) ?? 0, pending: (row["pending"] as number) ?? 0 };
|
|
1697
|
+
return rowToTaskStatusCounts(row);
|
|
3038
1698
|
}
|
|
3039
1699
|
|
|
3040
1700
|
// ─── Slice Dependencies (junction table) ─────────────────────────────────
|
|
@@ -3055,9 +1715,10 @@ export function syncSliceDependencies(milestoneId: string, sliceId: string, depe
|
|
|
3055
1715
|
/** Get all slices that depend on a given slice. */
|
|
3056
1716
|
export function getDependentSlices(milestoneId: string, sliceId: string): string[] {
|
|
3057
1717
|
if (!currentDb) return [];
|
|
3058
|
-
|
|
1718
|
+
const rows = currentDb.prepare(
|
|
3059
1719
|
"SELECT slice_id FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid",
|
|
3060
|
-
).all({ ":mid": milestoneId, ":sid": sliceId })
|
|
1720
|
+
).all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1721
|
+
return rowsToStringColumn(rows, "slice_id");
|
|
3061
1722
|
}
|
|
3062
1723
|
|
|
3063
1724
|
// ─── Worktree DB Helpers ──────────────────────────────────────────────────
|
|
@@ -3457,6 +2118,9 @@ export function deleteMilestone(milestoneId: string): void {
|
|
|
3457
2118
|
currentDb!.prepare(
|
|
3458
2119
|
`DELETE FROM artifacts WHERE milestone_id = :mid`,
|
|
3459
2120
|
).run({ ":mid": milestoneId });
|
|
2121
|
+
currentDb!.prepare(
|
|
2122
|
+
`DELETE FROM milestone_commit_attributions WHERE milestone_id = :mid`,
|
|
2123
|
+
).run({ ":mid": milestoneId });
|
|
3460
2124
|
currentDb!.prepare(
|
|
3461
2125
|
`DELETE FROM milestone_leases WHERE milestone_id = :mid`,
|
|
3462
2126
|
).run({ ":mid": milestoneId });
|
|
@@ -3526,21 +2190,6 @@ export function getLatestAssessmentByScope(
|
|
|
3526
2190
|
|
|
3527
2191
|
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
3528
2192
|
|
|
3529
|
-
function rowToGate(row: Record<string, unknown>): GateRow {
|
|
3530
|
-
return {
|
|
3531
|
-
milestone_id: row["milestone_id"] as string,
|
|
3532
|
-
slice_id: row["slice_id"] as string,
|
|
3533
|
-
gate_id: row["gate_id"] as GateId,
|
|
3534
|
-
scope: row["scope"] as GateScope,
|
|
3535
|
-
task_id: (row["task_id"] as string) ?? "",
|
|
3536
|
-
status: row["status"] as GateStatus,
|
|
3537
|
-
verdict: row["status"] === "pending" ? null : (row["verdict"] as GateVerdict),
|
|
3538
|
-
rationale: (row["rationale"] as string) || "",
|
|
3539
|
-
findings: (row["findings"] as string) || "",
|
|
3540
|
-
evaluated_at: (row["evaluated_at"] as string) ?? null,
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
|
|
3544
2193
|
export function insertGateRow(g: {
|
|
3545
2194
|
milestoneId: string;
|
|
3546
2195
|
sliceId: string;
|
|
@@ -3790,6 +2439,75 @@ export function upsertTurnGitTransaction(entry: {
|
|
|
3790
2439
|
});
|
|
3791
2440
|
}
|
|
3792
2441
|
|
|
2442
|
+
export function getMilestoneCommitAttributionShas(milestoneId: string): string[] {
|
|
2443
|
+
if (!currentDb) return [];
|
|
2444
|
+
const rows = currentDb.prepare(
|
|
2445
|
+
`SELECT commit_sha
|
|
2446
|
+
FROM milestone_commit_attributions
|
|
2447
|
+
WHERE milestone_id = :mid
|
|
2448
|
+
ORDER BY created_at, commit_sha`,
|
|
2449
|
+
).all({ ":mid": milestoneId }) as Array<Record<string, unknown>>;
|
|
2450
|
+
return rows
|
|
2451
|
+
.map((row) => typeof row["commit_sha"] === "string" ? row["commit_sha"] : "")
|
|
2452
|
+
.filter(Boolean);
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
export function recordMilestoneCommitAttribution(entry: {
|
|
2456
|
+
commitSha: string;
|
|
2457
|
+
milestoneId: string;
|
|
2458
|
+
sliceId?: string;
|
|
2459
|
+
taskId?: string;
|
|
2460
|
+
source: "recorded" | "backfill";
|
|
2461
|
+
confidence: number;
|
|
2462
|
+
files: string[];
|
|
2463
|
+
createdAt: string;
|
|
2464
|
+
}): void {
|
|
2465
|
+
if (!currentDb) return;
|
|
2466
|
+
transaction(() => {
|
|
2467
|
+
currentDb!.prepare(
|
|
2468
|
+
`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2469
|
+
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2470
|
+
) VALUES (
|
|
2471
|
+
:commit_sha, :milestone_id, :slice_id, :task_id, :source, :confidence, :files_json, :created_at
|
|
2472
|
+
)`,
|
|
2473
|
+
).run({
|
|
2474
|
+
":commit_sha": entry.commitSha,
|
|
2475
|
+
":milestone_id": entry.milestoneId,
|
|
2476
|
+
":slice_id": entry.sliceId ?? null,
|
|
2477
|
+
":task_id": entry.taskId ?? null,
|
|
2478
|
+
":source": entry.source,
|
|
2479
|
+
":confidence": entry.confidence,
|
|
2480
|
+
":files_json": JSON.stringify(entry.files),
|
|
2481
|
+
":created_at": entry.createdAt,
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
currentDb!.prepare(
|
|
2485
|
+
`INSERT OR IGNORE INTO audit_events (
|
|
2486
|
+
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2487
|
+
) VALUES (
|
|
2488
|
+
:event_id, :trace_id, :turn_id, :caused_by, :category, :type, :ts, :payload_json
|
|
2489
|
+
)`,
|
|
2490
|
+
).run({
|
|
2491
|
+
":event_id": `milestone-commit-attribution:${entry.milestoneId}:${entry.commitSha}`,
|
|
2492
|
+
":trace_id": "milestone-commit-attribution",
|
|
2493
|
+
":turn_id": null,
|
|
2494
|
+
":caused_by": null,
|
|
2495
|
+
":category": "git",
|
|
2496
|
+
":type": "milestone-commit-attribution-recorded",
|
|
2497
|
+
":ts": entry.createdAt,
|
|
2498
|
+
":payload_json": JSON.stringify({
|
|
2499
|
+
commitSha: entry.commitSha,
|
|
2500
|
+
milestoneId: entry.milestoneId,
|
|
2501
|
+
sliceId: entry.sliceId ?? null,
|
|
2502
|
+
taskId: entry.taskId ?? null,
|
|
2503
|
+
source: entry.source,
|
|
2504
|
+
confidence: entry.confidence,
|
|
2505
|
+
files: entry.files,
|
|
2506
|
+
}),
|
|
2507
|
+
});
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
|
|
3793
2511
|
export function insertAuditEvent(entry: {
|
|
3794
2512
|
eventId: string;
|
|
3795
2513
|
traceId: string;
|
|
@@ -3893,6 +2611,7 @@ export function clearEngineHierarchy(): void {
|
|
|
3893
2611
|
currentDb!.exec("DELETE FROM slice_dependencies");
|
|
3894
2612
|
currentDb!.exec("DELETE FROM assessments");
|
|
3895
2613
|
currentDb!.exec("DELETE FROM replan_history");
|
|
2614
|
+
currentDb!.exec("DELETE FROM milestone_commit_attributions");
|
|
3896
2615
|
currentDb!.exec("DELETE FROM tasks");
|
|
3897
2616
|
currentDb!.exec("DELETE FROM slices");
|
|
3898
2617
|
currentDb!.exec("DELETE FROM milestone_leases");
|