@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.6ccd27b3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-model-override.d.ts +15 -0
- package/dist/cli-model-override.js +21 -0
- package/dist/cli-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli.js +2 -19
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +14 -34
- package/dist/loader.js +6 -4
- package/dist/mcp-server.js +2 -1
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/register-agent-bundles.d.ts +11 -2
- package/dist/register-agent-bundles.js +18 -4
- package/dist/resource-loader.d.ts +10 -5
- package/dist/resource-loader.js +123 -20
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +5 -4
- package/dist/resources/extensions/ask-user-questions.js +3 -2
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +30 -64
- package/dist/resources/extensions/async-jobs/await-tool.js +80 -12
- package/dist/resources/extensions/async-jobs/index.js +65 -0
- package/dist/resources/extensions/async-jobs/job-manager.js +12 -1
- package/dist/resources/extensions/bg-shell/bg-shell-command.js +6 -6
- package/dist/resources/extensions/bg-shell/bg-shell-tool.js +10 -7
- package/dist/resources/extensions/bg-shell/overlay.js +9 -6
- package/dist/resources/extensions/bg-shell/process-manager.js +54 -25
- package/dist/resources/extensions/bg-shell/readiness-detector.js +11 -0
- package/dist/resources/extensions/bg-shell/utilities.js +5 -2
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +209 -88
- package/dist/resources/extensions/browser-tools/engine/selection.js +73 -5
- package/dist/resources/extensions/browser-tools/index.js +69 -12
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +464 -200
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +33 -1
- package/dist/resources/extensions/gsd/auto/closeout.js +215 -0
- package/dist/resources/extensions/gsd/auto/custom-verify-retry-store.js +17 -2
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +33 -13
- package/dist/resources/extensions/gsd/auto/dispatch-history.js +120 -0
- package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
- package/dist/resources/extensions/gsd/auto/dispatch.js +365 -0
- package/dist/resources/extensions/gsd/auto/finalize.js +347 -0
- package/dist/resources/extensions/gsd/auto/loop.js +7 -1
- package/dist/resources/extensions/gsd/auto/milestone-lease-reclaim.js +56 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +207 -73
- package/dist/resources/extensions/gsd/auto/phase-helpers.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +17 -2324
- package/dist/resources/extensions/gsd/auto/pre-dispatch.js +534 -0
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto/unit-phase.js +694 -0
- package/dist/resources/extensions/gsd/auto/workflow-unit-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto/worktree-safety-phase.js +125 -0
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
- package/dist/resources/extensions/gsd/auto-dispatch.js +50 -58
- package/dist/resources/extensions/gsd/auto-model-selection.js +36 -13
- package/dist/resources/extensions/gsd/auto-post-unit.js +43 -14
- package/dist/resources/extensions/gsd/auto-prompts.js +81 -19
- package/dist/resources/extensions/gsd/auto-start.js +47 -29
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +12 -20
- package/dist/resources/extensions/gsd/auto-verification.js +23 -30
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +50 -354
- package/dist/resources/extensions/gsd/auto.js +49 -22
- package/dist/resources/extensions/gsd/blocked-models.js +28 -0
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +29 -8
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +69 -19
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +229 -36
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +319 -71
- package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
- package/dist/resources/extensions/gsd/browser-daemon-auto-prep.js +83 -0
- package/dist/resources/extensions/gsd/browser-evidence.js +8 -2
- package/dist/resources/extensions/gsd/captures.js +5 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/closeout-wizard.js +92 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/commands/context.js +16 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +46 -3
- package/dist/resources/extensions/gsd/commands-mcp-status.js +2 -2
- package/dist/resources/extensions/gsd/commands-workflow-templates.js +9 -2
- package/dist/resources/extensions/gsd/consent-question.js +353 -0
- package/dist/resources/extensions/gsd/consent-verdict.js +63 -0
- package/dist/resources/extensions/gsd/constants.js +0 -2
- package/dist/resources/extensions/gsd/crash-recovery.js +12 -15
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +428 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- package/dist/resources/extensions/gsd/db-writer.js +8 -17
- package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
- package/dist/resources/extensions/gsd/doctor-environment.js +261 -136
- package/dist/resources/extensions/gsd/doctor-format.js +9 -6
- package/dist/resources/extensions/gsd/doctor-git-checks.js +6 -21
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/error-classifier.js +9 -0
- package/dist/resources/extensions/gsd/exec-sandbox.js +30 -10
- package/dist/resources/extensions/gsd/files.js +33 -19
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-command-home.js +22 -12
- package/dist/resources/extensions/gsd/gsd-db.js +172 -2048
- package/dist/resources/extensions/gsd/guidance.js +158 -0
- package/dist/resources/extensions/gsd/guided-flow.js +144 -9
- package/dist/resources/extensions/gsd/health-widget.js +87 -28
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -0
- package/dist/resources/extensions/gsd/mcp-bridge.js +10 -0
- package/dist/resources/extensions/gsd/mcp-filter.js +2 -19
- package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
- package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
- package/dist/resources/extensions/gsd/milestone-closeout.js +85 -24
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +2 -2
- package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
- package/dist/resources/extensions/gsd/milestone-settlement.js +2 -2
- package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/notifications.js +12 -7
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
- package/dist/resources/extensions/gsd/parsers-legacy.js +16 -4
- package/dist/resources/extensions/gsd/paths.js +37 -24
- package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
- package/dist/resources/extensions/gsd/preferences-models.js +14 -48
- package/dist/resources/extensions/gsd/preferences.js +14 -0
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -2
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +9 -5
- package/dist/resources/extensions/gsd/prompts/system.md +5 -2
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
- package/dist/resources/extensions/gsd/publication.js +87 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +8 -1
- package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
- package/dist/resources/extensions/gsd/roadmap-slices.js +25 -3
- package/dist/resources/extensions/gsd/safety/destructive-confirmation.js +108 -0
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/skill-activation.js +3 -6
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/state.js +12 -22
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/stop-notice.js +57 -0
- package/dist/resources/extensions/gsd/tool-contract.js +14 -3
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +4 -4
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +108 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +46 -55
- package/dist/resources/extensions/gsd/tools/complete-task.js +65 -2
- package/dist/resources/extensions/gsd/tools/exec-tool.js +10 -8
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -8
- package/dist/resources/extensions/gsd/tools/plan-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +2 -2
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +13 -31
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +16 -35
- package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -2
- package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +67 -2
- package/dist/resources/extensions/gsd/uat-policy.js +42 -16
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
- package/dist/resources/extensions/gsd/unit-context-composer.js +74 -1
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +367 -0
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
- package/dist/resources/extensions/gsd/verdict-parser.js +1 -1
- package/dist/resources/extensions/gsd/verification-verdict.js +2 -1
- package/dist/resources/extensions/gsd/web-app-uat.js +45 -8
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +91 -0
- package/dist/resources/extensions/gsd/workflow-event-vocabulary.js +46 -0
- package/dist/resources/extensions/gsd/workflow-events.js +6 -18
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-readiness-cache.js +105 -0
- package/dist/resources/extensions/gsd/workflow-reconcile.js +21 -56
- package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +12 -3
- package/dist/resources/extensions/gsd/worktree-manager.js +52 -29
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +28 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +36 -31
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
- package/dist/resources/extensions/gsd/worktree.js +8 -1
- package/dist/resources/extensions/mcp-client/manager.js +6 -1
- package/dist/resources/extensions/search-the-web/native-search.js +5 -3
- package/dist/resources/extensions/shared/browser-contract.js +59 -0
- package/dist/resources/extensions/shared/gsd-browser-cli.js +116 -6
- package/dist/resources/shared/gsd-browser-path-sync.js +214 -0
- package/dist/resources/shared/package-manager-detection.js +1 -1
- package/dist/resources/shared/package.json +3 -0
- package/dist/resources/skills/create-skill/SKILL.md +3 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +1 -1
- package/dist/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/dist/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/dist/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/runtime-checks.d.ts +10 -0
- package/dist/runtime-checks.js +27 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +20 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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/mcp-connections/route.js.nft.json +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.nft.json +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.nft.json +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.nft.json +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.nft.json +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/shutdown/route.js.nft.json +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.nft.json +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.nft.json +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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js.nft.json +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 +10 -10
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- 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/{796.cf859a427a2cb2ac.js → 796.e0bdc932325d7e03.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{webpack-fbea77b5f9953368.js → webpack-f0285ce91d4ec9ef.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/package.json +1 -1
- package/dist/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -11
- package/package.json +3 -3
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -0
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/dist/workflow.d.ts +4 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +6 -4
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +13 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +55 -6
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +34 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js +8 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +11 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +12 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +4 -4
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +12 -3
- package/packages/mcp-server/dist/cli-runner.d.ts +40 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -0
- package/packages/mcp-server/dist/cli-runner.js +137 -0
- package/packages/mcp-server/dist/cli-runner.js.map +1 -0
- package/packages/mcp-server/dist/cli.js +2 -50
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts +29 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js +50 -0
- package/packages/mcp-server/dist/moonshot-tool-schema.js.map +1 -0
- package/packages/mcp-server/dist/pid-registry.d.ts +46 -0
- package/packages/mcp-server/dist/pid-registry.d.ts.map +1 -0
- package/packages/mcp-server/dist/pid-registry.js +452 -0
- package/packages/mcp-server/dist/pid-registry.js.map +1 -0
- package/packages/mcp-server/dist/probe-mode.d.ts +4 -0
- package/packages/mcp-server/dist/probe-mode.d.ts.map +1 -0
- package/packages/mcp-server/dist/probe-mode.js +10 -0
- package/packages/mcp-server/dist/probe-mode.js.map +1 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +4 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/stdio-watchdog.d.ts +8 -0
- package/packages/mcp-server/dist/stdio-watchdog.d.ts.map +1 -0
- package/packages/mcp-server/dist/stdio-watchdog.js +40 -0
- package/packages/mcp-server/dist/stdio-watchdog.js.map +1 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts +26 -18
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +207 -102
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +43 -2
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts +1 -0
- package/packages/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/harness/env/nodejs.js +34 -3
- package/packages/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
- package/packages/pi-agent-core/dist/index.d.ts +1 -0
- package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/index.js +3 -0
- package/packages/pi-agent-core/dist/index.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/README.md +1 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts +2 -2
- package/packages/pi-ai/dist/image-models.generated.js +6 -6
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/index.d.ts +2 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +158 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +132 -17
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +12 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +5 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +12 -3
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +7 -3
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts +9 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js +34 -0
- package/packages/pi-ai/dist/utils/moonshot-tool-schema.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +6 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +19 -13
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/provider-readiness.js +13 -6
- package/packages/pi-coding-agent/dist/core/provider-readiness.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -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 +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +11 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +53 -11
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +45 -17
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +28 -2
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +56 -10
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/index.d.ts +1 -1
- package/packages/pi-tui/dist/index.d.ts.map +1 -1
- package/packages/pi-tui/dist/index.js +1 -1
- package/packages/pi-tui/dist/index.js.map +1 -1
- package/packages/pi-tui/dist/terminal-image.d.ts +33 -0
- package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal-image.js +54 -2
- package/packages/pi-tui/dist/terminal-image.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts +8 -0
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +72 -18
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/dist/utils.d.ts.map +1 -1
- package/packages/pi-tui/dist/utils.js +110 -36
- package/packages/pi-tui/dist/utils.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +45 -17
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +5 -4
- package/src/resources/extensions/ask-user-questions.ts +7 -2
- package/src/resources/extensions/async-jobs/async-bash-cancel.test.ts +360 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +33 -56
- package/src/resources/extensions/async-jobs/await-tool.test.ts +139 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +82 -12
- package/src/resources/extensions/async-jobs/index.ts +79 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +21 -1
- package/src/resources/extensions/bg-shell/bg-shell-command.ts +6 -6
- package/src/resources/extensions/bg-shell/bg-shell-tool.ts +10 -6
- package/src/resources/extensions/bg-shell/overlay.ts +9 -5
- package/src/resources/extensions/bg-shell/process-manager.ts +50 -25
- package/src/resources/extensions/bg-shell/readiness-detector.ts +12 -0
- package/src/resources/extensions/bg-shell/tests/lifecycle-and-utilities.test.ts +48 -1
- package/src/resources/extensions/bg-shell/utilities.ts +5 -2
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +265 -98
- package/src/resources/extensions/browser-tools/engine/selection.ts +90 -4
- package/src/resources/extensions/browser-tools/index.ts +71 -13
- package/src/resources/extensions/browser-tools/tests/browser-engine-selection.test.mjs +83 -13
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +40 -1
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +136 -0
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +543 -202
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +700 -7
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +38 -1
- package/src/resources/extensions/gsd/auto/closeout.ts +309 -0
- package/src/resources/extensions/gsd/auto/custom-verify-retry-store.ts +21 -3
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +32 -9
- package/src/resources/extensions/gsd/auto/dispatch-history.ts +168 -0
- package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
- package/src/resources/extensions/gsd/auto/dispatch.ts +449 -0
- package/src/resources/extensions/gsd/auto/finalize.ts +445 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +7 -1
- package/src/resources/extensions/gsd/auto/milestone-lease-reclaim.ts +74 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +232 -76
- package/src/resources/extensions/gsd/auto/phase-helpers.ts +199 -0
- package/src/resources/extensions/gsd/auto/phases.ts +58 -3013
- package/src/resources/extensions/gsd/auto/pre-dispatch.ts +704 -0
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto/unit-phase.ts +910 -0
- package/src/resources/extensions/gsd/auto/workflow-unit-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto/worktree-safety-phase.ts +149 -0
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
- package/src/resources/extensions/gsd/auto-dispatch.ts +48 -61
- package/src/resources/extensions/gsd/auto-model-selection.ts +41 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +52 -13
- package/src/resources/extensions/gsd/auto-prompts.ts +118 -35
- package/src/resources/extensions/gsd/auto-start.ts +48 -33
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +14 -21
- package/src/resources/extensions/gsd/auto-verification.ts +26 -28
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +56 -366
- package/src/resources/extensions/gsd/auto.ts +69 -26
- package/src/resources/extensions/gsd/blocked-models.ts +49 -0
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +37 -10
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +89 -18
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +270 -37
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +368 -78
- package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
- package/src/resources/extensions/gsd/browser-daemon-auto-prep.ts +108 -0
- package/src/resources/extensions/gsd/browser-evidence.ts +18 -2
- package/src/resources/extensions/gsd/captures.ts +5 -16
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/closeout-wizard.ts +102 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/commands/context.ts +16 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +46 -3
- package/src/resources/extensions/gsd/commands-mcp-status.ts +2 -2
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +11 -4
- package/src/resources/extensions/gsd/consent-question.ts +431 -0
- package/src/resources/extensions/gsd/consent-verdict.ts +86 -0
- package/src/resources/extensions/gsd/constants.ts +0 -3
- package/src/resources/extensions/gsd/crash-recovery.ts +13 -11
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +519 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- package/src/resources/extensions/gsd/db-writer.ts +11 -19
- package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-environment.ts +272 -155
- package/src/resources/extensions/gsd/doctor-format.ts +12 -7
- package/src/resources/extensions/gsd/doctor-git-checks.ts +5 -22
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/error-classifier.ts +11 -0
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/files.ts +33 -12
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-command-home.ts +13 -3
- package/src/resources/extensions/gsd/gsd-db.ts +176 -2375
- package/src/resources/extensions/gsd/guidance.ts +217 -0
- package/src/resources/extensions/gsd/guided-flow.ts +195 -29
- package/src/resources/extensions/gsd/health-widget.ts +91 -27
- package/src/resources/extensions/gsd/markdown-renderer.ts +11 -0
- package/src/resources/extensions/gsd/mcp-bridge.ts +39 -0
- package/src/resources/extensions/gsd/mcp-filter.ts +2 -23
- package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
- package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
- package/src/resources/extensions/gsd/milestone-closeout.ts +109 -24
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +2 -2
- package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
- package/src/resources/extensions/gsd/milestone-settlement.ts +2 -2
- package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/notifications.ts +13 -6
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
- package/src/resources/extensions/gsd/parsers-legacy.ts +16 -4
- package/src/resources/extensions/gsd/paths.ts +42 -22
- package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
- package/src/resources/extensions/gsd/preferences-models.ts +12 -47
- package/src/resources/extensions/gsd/preferences.ts +18 -0
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -2
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/quick-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/refine-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +9 -5
- package/src/resources/extensions/gsd/prompts/system.md +5 -2
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -1
- package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
- package/src/resources/extensions/gsd/publication.ts +122 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +11 -1
- package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
- package/src/resources/extensions/gsd/roadmap-slices.ts +28 -3
- package/src/resources/extensions/gsd/safety/destructive-confirmation.ts +134 -0
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/skill-activation.ts +3 -6
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/state.ts +16 -22
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/stop-notice.ts +75 -0
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-blocked-remediation-message.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +329 -22
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +97 -1
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +273 -37
- package/src/resources/extensions/gsd/tests/auto-pause-double-entry-guard.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-remote-session-lock-cleanup.test.ts +65 -3
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +236 -0
- package/src/resources/extensions/gsd/tests/auto-unit-closeout.test.ts +169 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/blocked-models.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/browser-automation-contract-fixture.ts +39 -0
- package/src/resources/extensions/gsd/tests/browser-contract.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/browser-daemon-auto-prep.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +141 -5
- package/src/resources/extensions/gsd/tests/consent-question.test.ts +351 -0
- package/src/resources/extensions/gsd/tests/custom-verify-retry-store.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +12 -11
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/discuss-routing-fixes.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +328 -0
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +8 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/dynamic-bash-no-cap.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/engine-hook-contract.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +117 -91
- package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +35 -1
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/gsd-command-home.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/guidance.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +18 -6
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +58 -15
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +89 -59
- package/src/resources/extensions/gsd/tests/integration/doctor-environment-async.test.ts +104 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/integration/gsd-integration-fixture.ts +80 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +217 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +47 -16
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/mcp-readiness-preflight.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/mcp-status.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +95 -4
- package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-report-path.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/milestone-settlement.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/notifications.test.ts +64 -9
- package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +143 -0
- package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/plan-gate-failed-doctor-heal-hint.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -2
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +124 -6
- package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
- package/src/resources/extensions/gsd/tests/remote-notification-from-desktop.test.ts +31 -81
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +27 -2
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -17
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +80 -2
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/tool-unavailable-retry.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/transport-gate-double-complete.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/uat-policy.test.ts +112 -29
- package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +67 -2
- package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
- package/src/resources/extensions/gsd/tests/uok-audit-unified.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-readiness-cache.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +67 -4
- package/src/resources/extensions/gsd/tests/workflow-phase-contract-matrix.test.ts +332 -0
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +275 -40
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety-phase.test.ts +100 -0
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +75 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +24 -2
- package/src/resources/extensions/gsd/tests/worktree.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +109 -1
- package/src/resources/extensions/gsd/tool-contract.ts +38 -3
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +4 -4
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +183 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +45 -70
- package/src/resources/extensions/gsd/tools/complete-task.ts +90 -2
- package/src/resources/extensions/gsd/tools/exec-tool.ts +9 -8
- package/src/resources/extensions/gsd/tools/plan-slice.ts +14 -8
- package/src/resources/extensions/gsd/tools/plan-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +2 -2
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +13 -40
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +16 -44
- package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -2
- package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +81 -2
- package/src/resources/extensions/gsd/uat-policy.ts +62 -16
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
- package/src/resources/extensions/gsd/unit-context-composer.ts +111 -1
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +442 -0
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
- package/src/resources/extensions/gsd/verdict-parser.ts +1 -1
- package/src/resources/extensions/gsd/verification-verdict.ts +4 -2
- package/src/resources/extensions/gsd/web-app-uat.ts +51 -8
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +131 -0
- package/src/resources/extensions/gsd/workflow-event-vocabulary.ts +59 -0
- package/src/resources/extensions/gsd/workflow-events.ts +12 -20
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-readiness-cache.ts +150 -0
- package/src/resources/extensions/gsd/workflow-reconcile.ts +29 -62
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +13 -9
- package/src/resources/extensions/gsd/worktree-manager.ts +53 -29
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +29 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +49 -44
- package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
- package/src/resources/extensions/gsd/worktree.ts +7 -1
- package/src/resources/extensions/mcp-client/manager.ts +7 -1
- package/src/resources/extensions/search-the-web/native-search.ts +5 -3
- package/src/resources/extensions/shared/browser-contract.ts +66 -0
- package/src/resources/extensions/shared/gsd-browser-cli.ts +141 -6
- package/src/resources/shared/gsd-browser-path-sync.ts +273 -0
- package/src/resources/shared/package-manager-detection.ts +1 -1
- package/src/resources/shared/package.json +3 -0
- package/src/resources/skills/create-skill/SKILL.md +3 -0
- package/src/resources/skills/create-skill/references/executable-code.md +1 -1
- package/src/resources/skills/create-skill/references/skill-structure.md +1 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +8 -3
- package/src/resources/skills/create-skill/workflows/add-script.md +4 -2
- package/src/resources/skills/create-skill/workflows/add-template.md +3 -1
- package/src/resources/skills/create-skill/workflows/add-workflow.md +8 -3
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +10 -5
- package/src/resources/skills/create-skill/workflows/verify-skill.md +9 -4
- package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/resources/extensions/gsd/user-input-boundary.js +0 -218
- package/dist/resources/skills/gsd-browser/SKILL.md +0 -41
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -173
- package/src/resources/extensions/gsd/user-input-boundary.ts +0 -216
- package/src/resources/skills/gsd-browser/SKILL.md +0 -41
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → avEtvPrImYTq2gGe-hVNp}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → avEtvPrImYTq2gGe-hVNp}/_ssgManifest.js +0 -0
|
@@ -1,757 +1,44 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
|
-
// File Purpose: GSD
|
|
3
|
-
// GSD Database Abstraction Layer
|
|
4
|
-
// Provides a SQLite database with provider fallback chain:
|
|
5
|
-
// node:sqlite (built-in) → better-sqlite3 (npm) → null (unavailable)
|
|
6
|
-
//
|
|
7
|
-
// Exposes a unified sync API for decisions and requirements storage.
|
|
8
|
-
// Schema is initialized on first open with WAL mode for file-backed DBs.
|
|
2
|
+
// File Purpose: GSD single-writer barrel + write/read wrappers.
|
|
9
3
|
//
|
|
10
4
|
// ─── Single-writer invariant ─────────────────────────────────────────────
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
5
|
+
// Every write-SQL statement against `.gsd/gsd.db` lives behind a typed
|
|
6
|
+
// wrapper in the single-writer layer (this file plus db/writers/*). Connection
|
|
7
|
+
// ownership, lifecycle, schema/migrations and transaction primitives live in
|
|
8
|
+
// db/engine.ts and are re-exported here for backward compatibility, so callers
|
|
9
|
+
// keep importing from "./gsd-db.js".
|
|
16
10
|
//
|
|
17
|
-
// `_getAdapter()` is retained for read-only
|
|
18
|
-
//
|
|
19
|
-
// Do NOT use it for writes — add a wrapper here instead.
|
|
11
|
+
// `_getAdapter()` (re-exported from the engine) is retained for read-only
|
|
12
|
+
// SELECTs in query modules. Do NOT use it for writes — add a wrapper here.
|
|
20
13
|
//
|
|
21
|
-
// The separate `.gsd/unit-claims.db`
|
|
22
|
-
//
|
|
23
|
-
// excluded from this invariant.
|
|
24
|
-
import { createRequire } from "node:module";
|
|
14
|
+
// The separate `.gsd/unit-claims.db` (unit-ownership.ts) is an intentionally
|
|
15
|
+
// independent store and is excluded from this invariant.
|
|
25
16
|
import { createHash } from "node:crypto";
|
|
26
|
-
import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
27
|
-
import { dirname, join } from "node:path";
|
|
28
17
|
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
29
18
|
import { getGateIdsForTurn } from "./gate-registry.js";
|
|
30
|
-
import {
|
|
31
|
-
import { createDbAdapter } from "./db-adapter.js";
|
|
32
|
-
import { createBaseSchemaObjects } from "./db-base-schema.js";
|
|
33
|
-
import { createCoordinationTablesV24 } from "./db-coordination-schema.js";
|
|
34
|
-
import { createDbConnectionCache } from "./db-connection-cache.js";
|
|
35
|
-
import { emptyTaskStatusCounts, rowToActiveTaskSummary, rowToIdStatusSummary, rowToTaskStatusCounts, rowsToStringColumn, } from "./db-lightweight-query-rows.js";
|
|
36
|
-
import { rowToActiveDecision, rowToActiveRequirement, rowToDecision, rowToRequirement, rowsToRequirementCounts, } from "./db-decision-requirement-rows.js";
|
|
37
|
-
import { rowToGate } from "./db-gate-rows.js";
|
|
38
|
-
import { rowToArtifact, rowToMilestone } from "./db-milestone-artifact-rows.js";
|
|
39
|
-
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
19
|
+
import { logWarning } from "./workflow-logger.js";
|
|
40
20
|
import { isClosedStatus } from "./status-guards.js";
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
_gsdRequire = null;
|
|
59
|
-
}
|
|
60
|
-
return _gsdRequire;
|
|
61
|
-
}
|
|
62
|
-
const providerLoader = createSqliteProviderLoader({
|
|
63
|
-
tryRequireNodeSqlite: () => {
|
|
64
|
-
const req = getGsdRequire();
|
|
65
|
-
if (!req)
|
|
66
|
-
throw new Error("unavailable");
|
|
67
|
-
return req("node:sqlite");
|
|
68
|
-
},
|
|
69
|
-
tryRequireBetterSqlite3: () => {
|
|
70
|
-
const req = getGsdRequire();
|
|
71
|
-
if (!req)
|
|
72
|
-
throw new Error("unavailable");
|
|
73
|
-
return req(BETTER_SQLITE3_PACKAGE);
|
|
74
|
-
},
|
|
75
|
-
suppressSqliteWarning,
|
|
76
|
-
nodeVersion: process.versions.node,
|
|
77
|
-
writeStderr: (message) => process.stderr.write(message),
|
|
78
|
-
});
|
|
79
|
-
export const SCHEMA_VERSION = 29;
|
|
80
|
-
const TERMINAL_STATUS_SQL = "'complete', 'done', 'skipped', 'closed'";
|
|
81
|
-
function initSchema(db, fileBacked, dbPath) {
|
|
82
|
-
const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
|
|
83
|
-
if (fileBacked)
|
|
84
|
-
db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
|
|
85
|
-
if (fileBacked)
|
|
86
|
-
db.exec("PRAGMA busy_timeout = 5000");
|
|
87
|
-
if (fileBacked)
|
|
88
|
-
db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
|
|
89
|
-
if (fileBacked)
|
|
90
|
-
db.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
91
|
-
if (fileBacked)
|
|
92
|
-
db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
|
|
93
|
-
if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin")
|
|
94
|
-
db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
|
|
95
|
-
db.exec("PRAGMA temp_store = MEMORY");
|
|
96
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
97
|
-
db.exec("BEGIN");
|
|
98
|
-
try {
|
|
99
|
-
createBaseSchemaObjects(db, {
|
|
100
|
-
tryCreateMemoriesFts,
|
|
101
|
-
ensureVerificationEvidenceDedupIndex,
|
|
102
|
-
});
|
|
103
|
-
const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
|
|
104
|
-
if (existing && existing["cnt"] === 0) {
|
|
105
|
-
createCoordinationTablesV24(db);
|
|
106
|
-
createRuntimeKvTableV25(db);
|
|
107
|
-
// Fresh install — all tables are created above with the full current schema,
|
|
108
|
-
// so it is safe to create all migration-specific indexes here. For existing
|
|
109
|
-
// databases these indexes are created inside the individual migration guards
|
|
110
|
-
// in migrateSchema() after the corresponding columns have been added.
|
|
111
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
|
|
112
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
|
|
113
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
|
|
114
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
|
|
115
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
|
|
116
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
|
|
117
|
-
recordSchemaVersion(db, SCHEMA_VERSION);
|
|
118
|
-
}
|
|
119
|
-
db.exec("COMMIT");
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
db.exec("ROLLBACK");
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
migrateSchema(db);
|
|
126
|
-
}
|
|
127
|
-
export function _isLikelyWslDrvFsPathForTest(dbPath) {
|
|
128
|
-
if (!dbPath || process.platform !== "linux")
|
|
129
|
-
return false;
|
|
130
|
-
const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
|
|
131
|
-
if (drvFsPathPattern.test(dbPath))
|
|
132
|
-
return true;
|
|
133
|
-
try {
|
|
134
|
-
return drvFsPathPattern.test(realpathSync(dbPath));
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Create the FTS5 virtual table for memories plus the triggers that keep it
|
|
142
|
-
* in sync with the base table. FTS5 may be unavailable on stripped-down
|
|
143
|
-
* SQLite builds — callers should treat failure as non-fatal and fall back
|
|
144
|
-
* to LIKE-based scans in `memory-store.queryMemoriesRanked`.
|
|
145
|
-
*/
|
|
146
|
-
export function tryCreateMemoriesFts(db) {
|
|
147
|
-
return tryCreateMemoriesFtsSchema(db, {
|
|
148
|
-
onUnavailable: (message) => logWarning("db", message),
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
export function isMemoriesFtsAvailable(db) {
|
|
152
|
-
return isMemoriesFtsAvailableSchema(db);
|
|
153
|
-
}
|
|
154
|
-
function backfillMemoriesFts(db) {
|
|
155
|
-
db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
|
|
156
|
-
}
|
|
157
|
-
function copyQualityGateRowsToRepairedTable(db) {
|
|
158
|
-
db.exec(`
|
|
159
|
-
INSERT OR IGNORE INTO quality_gates_new
|
|
160
|
-
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
161
|
-
SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
|
|
162
|
-
FROM quality_gates
|
|
163
|
-
`);
|
|
164
|
-
}
|
|
165
|
-
function migrateSchema(db) {
|
|
166
|
-
const currentVersion = getCurrentSchemaVersion(db);
|
|
167
|
-
if (currentVersion >= SCHEMA_VERSION)
|
|
168
|
-
return;
|
|
169
|
-
backupDatabaseBeforeMigration(db, currentPath, currentVersion, {
|
|
170
|
-
existsSync,
|
|
171
|
-
copyFileSync,
|
|
172
|
-
logWarning,
|
|
173
|
-
});
|
|
174
|
-
db.exec("BEGIN");
|
|
175
|
-
try {
|
|
176
|
-
if (currentVersion < 2) {
|
|
177
|
-
applyMigrationV2Artifacts(db);
|
|
178
|
-
recordSchemaVersion(db, 2);
|
|
179
|
-
}
|
|
180
|
-
if (currentVersion < 3) {
|
|
181
|
-
applyMigrationV3Memories(db);
|
|
182
|
-
recordSchemaVersion(db, 3);
|
|
183
|
-
}
|
|
184
|
-
if (currentVersion < 4) {
|
|
185
|
-
applyMigrationV4DecisionMadeBy(db);
|
|
186
|
-
recordSchemaVersion(db, 4);
|
|
187
|
-
}
|
|
188
|
-
if (currentVersion < 5) {
|
|
189
|
-
applyMigrationV5HierarchyTables(db);
|
|
190
|
-
recordSchemaVersion(db, 5);
|
|
191
|
-
}
|
|
192
|
-
if (currentVersion < 6) {
|
|
193
|
-
applyMigrationV6SliceSummaries(db);
|
|
194
|
-
recordSchemaVersion(db, 6);
|
|
195
|
-
}
|
|
196
|
-
if (currentVersion < 7) {
|
|
197
|
-
applyMigrationV7Dependencies(db);
|
|
198
|
-
recordSchemaVersion(db, 7);
|
|
199
|
-
}
|
|
200
|
-
if (currentVersion < 8) {
|
|
201
|
-
applyMigrationV8PlanningFields(db);
|
|
202
|
-
recordSchemaVersion(db, 8);
|
|
203
|
-
}
|
|
204
|
-
if (currentVersion < 9) {
|
|
205
|
-
applyMigrationV9Ordering(db);
|
|
206
|
-
recordSchemaVersion(db, 9);
|
|
207
|
-
}
|
|
208
|
-
if (currentVersion < 10) {
|
|
209
|
-
applyMigrationV10ReplanTrigger(db);
|
|
210
|
-
recordSchemaVersion(db, 10);
|
|
211
|
-
}
|
|
212
|
-
if (currentVersion < 11) {
|
|
213
|
-
applyMigrationV11TaskPlanning(db);
|
|
214
|
-
recordSchemaVersion(db, 11);
|
|
215
|
-
}
|
|
216
|
-
if (currentVersion < 12) {
|
|
217
|
-
// NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
|
|
218
|
-
// expression, which is invalid SQLite syntax and causes startup errors on
|
|
219
|
-
// DBs that migrate through v12. The corrected DDL uses
|
|
220
|
-
// task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
|
|
221
|
-
// were created with the broken DDL are repaired by the v22 migration below.
|
|
222
|
-
applyMigrationV12QualityGates(db);
|
|
223
|
-
recordSchemaVersion(db, 12);
|
|
224
|
-
}
|
|
225
|
-
if (currentVersion < 13) {
|
|
226
|
-
applyMigrationV13HotPathIndexes(db, ensureVerificationEvidenceDedupIndex);
|
|
227
|
-
recordSchemaVersion(db, 13);
|
|
228
|
-
}
|
|
229
|
-
if (currentVersion < 14) {
|
|
230
|
-
applyMigrationV14SliceDependencies(db);
|
|
231
|
-
recordSchemaVersion(db, 14);
|
|
232
|
-
}
|
|
233
|
-
if (currentVersion < 15) {
|
|
234
|
-
applyMigrationV15AuditTables(db);
|
|
235
|
-
recordSchemaVersion(db, 15);
|
|
236
|
-
}
|
|
237
|
-
if (currentVersion < 16) {
|
|
238
|
-
applyMigrationV16EscalationSource(db);
|
|
239
|
-
recordSchemaVersion(db, 16);
|
|
240
|
-
}
|
|
241
|
-
if (currentVersion < 17) {
|
|
242
|
-
applyMigrationV17TaskEscalation(db);
|
|
243
|
-
recordSchemaVersion(db, 17);
|
|
244
|
-
}
|
|
245
|
-
if (currentVersion < 18) {
|
|
246
|
-
applyMigrationV18MemorySources(db);
|
|
247
|
-
recordSchemaVersion(db, 18);
|
|
248
|
-
}
|
|
249
|
-
if (currentVersion < 19) {
|
|
250
|
-
applyMigrationV19MemoryFts(db, {
|
|
251
|
-
tryCreateMemoriesFts,
|
|
252
|
-
isMemoriesFtsAvailable,
|
|
253
|
-
backfillMemoriesFts,
|
|
254
|
-
logWarning,
|
|
255
|
-
});
|
|
256
|
-
recordSchemaVersion(db, 19);
|
|
257
|
-
}
|
|
258
|
-
if (currentVersion < 20) {
|
|
259
|
-
applyMigrationV20MemoryRelations(db);
|
|
260
|
-
recordSchemaVersion(db, 20);
|
|
261
|
-
}
|
|
262
|
-
if (currentVersion < 21) {
|
|
263
|
-
applyMigrationV21StructuredMemories(db);
|
|
264
|
-
recordSchemaVersion(db, 21);
|
|
265
|
-
}
|
|
266
|
-
if (currentVersion < 22) {
|
|
267
|
-
applyMigrationV22QualityGateRepair(db, { copyQualityGateRowsToRepairedTable });
|
|
268
|
-
recordSchemaVersion(db, 22);
|
|
269
|
-
}
|
|
270
|
-
if (currentVersion < 23) {
|
|
271
|
-
applyMigrationV23MilestoneQueue(db);
|
|
272
|
-
recordSchemaVersion(db, 23);
|
|
273
|
-
}
|
|
274
|
-
if (currentVersion < 24) {
|
|
275
|
-
// v24: auto-mode coordination tables. See createCoordinationTablesV24
|
|
276
|
-
// for full schema + invariants. No-op for fresh installs (the same
|
|
277
|
-
// helper runs in the fresh-install path); for upgraded DBs this is
|
|
278
|
-
// the only place these tables get created.
|
|
279
|
-
createCoordinationTablesV24(db);
|
|
280
|
-
recordSchemaVersion(db, 24);
|
|
281
|
-
}
|
|
282
|
-
if (currentVersion < 25) {
|
|
283
|
-
// v25: runtime_kv non-correctness-critical key-value storage. See
|
|
284
|
-
// createRuntimeKvTableV25 for the full schema + invariants.
|
|
285
|
-
createRuntimeKvTableV25(db);
|
|
286
|
-
recordSchemaVersion(db, 25);
|
|
287
|
-
}
|
|
288
|
-
if (currentVersion < 26) {
|
|
289
|
-
applyMigrationV26MilestoneCommitAttributions(db);
|
|
290
|
-
recordSchemaVersion(db, 26);
|
|
291
|
-
}
|
|
292
|
-
if (currentVersion < 27) {
|
|
293
|
-
applyMigrationV27ArtifactHash(db);
|
|
294
|
-
recordSchemaVersion(db, 27);
|
|
295
|
-
}
|
|
296
|
-
if (currentVersion < 28) {
|
|
297
|
-
applyMigrationV28MemoryLastHitAt(db);
|
|
298
|
-
recordSchemaVersion(db, 28);
|
|
299
|
-
}
|
|
300
|
-
if (currentVersion < 29) {
|
|
301
|
-
applyMigrationV29RepositoryTargets(db);
|
|
302
|
-
recordSchemaVersion(db, 29);
|
|
303
|
-
}
|
|
304
|
-
db.exec("COMMIT");
|
|
305
|
-
}
|
|
306
|
-
catch (err) {
|
|
307
|
-
db.exec("ROLLBACK");
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
let currentDb = null;
|
|
312
|
-
let currentPath = null;
|
|
313
|
-
let currentPid = 0;
|
|
314
|
-
let _exitHandlerRegistered = false;
|
|
315
|
-
const _dbOpenState = createDbOpenState();
|
|
316
|
-
/**
|
|
317
|
-
* Identity key of the workspace whose connection is currently active
|
|
318
|
-
* (currentDb). Set by openDatabaseByWorkspace(); null when the active
|
|
319
|
-
* connection was opened via the legacy openDatabase(path) path.
|
|
320
|
-
*/
|
|
321
|
-
let _currentIdentityKey = null;
|
|
322
|
-
/**
|
|
323
|
-
* Workspace-scoped connection cache.
|
|
324
|
-
* Key: GsdWorkspace.identityKey (realpath-normalized project root).
|
|
325
|
-
* Value: the DB path and open adapter for that workspace.
|
|
326
|
-
*
|
|
327
|
-
* Sibling worktrees of the same project share the same identityKey (set by
|
|
328
|
-
* createWorkspace) and therefore reuse the same cached connection, preserving
|
|
329
|
-
* shared-WAL semantics. Different projects get distinct cache entries.
|
|
330
|
-
*
|
|
331
|
-
* NOTE: Only one connection is "active" at a time (currentDb/currentPath).
|
|
332
|
-
* The cache allows fast re-activation of a previously opened connection when
|
|
333
|
-
* callers switch between known workspaces via openDatabaseByWorkspace().
|
|
334
|
-
*/
|
|
335
|
-
const _dbCache = createDbConnectionCache();
|
|
336
|
-
/** Test helper: expose the internal cache for inspection. Not for production use. */
|
|
337
|
-
export function _getDbCache() {
|
|
338
|
-
return _dbCache.asReadonlyMap();
|
|
339
|
-
}
|
|
340
|
-
function closeCachedConnection(entry, source) {
|
|
341
|
-
try {
|
|
342
|
-
entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
343
|
-
}
|
|
344
|
-
catch (e) {
|
|
345
|
-
if (source === "workspace")
|
|
346
|
-
logWarning("db", `WAL checkpoint (byWorkspace) failed: ${e.message}`);
|
|
347
|
-
}
|
|
348
|
-
try {
|
|
349
|
-
entry.db.exec("PRAGMA incremental_vacuum(64)");
|
|
350
|
-
}
|
|
351
|
-
catch (e) {
|
|
352
|
-
if (source === "workspace")
|
|
353
|
-
logWarning("db", `incremental vacuum (byWorkspace) failed: ${e.message}`);
|
|
354
|
-
}
|
|
355
|
-
try {
|
|
356
|
-
entry.db.close();
|
|
357
|
-
}
|
|
358
|
-
catch (e) {
|
|
359
|
-
if (source === "workspace")
|
|
360
|
-
logWarning("db", `database close (byWorkspace) failed: ${e.message}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Close and evict every entry in the workspace connection cache, then call
|
|
365
|
-
* closeDatabase() to close the active connection.
|
|
366
|
-
*
|
|
367
|
-
* Use this for test teardown or process-shutdown paths where every open
|
|
368
|
-
* connection must be flushed. Normal callers should use closeDatabase() or
|
|
369
|
-
* closeDatabaseByWorkspace() instead.
|
|
370
|
-
*/
|
|
371
|
-
export function closeAllDatabases() {
|
|
372
|
-
// Close all non-active cached connections first.
|
|
373
|
-
_dbCache.closeNonActive(currentDb, (entry) => closeCachedConnection(entry, "all"));
|
|
374
|
-
closeDatabase();
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Open (or reuse) the database connection scoped to the given workspace.
|
|
378
|
-
*
|
|
379
|
-
* Uses workspace.identityKey as the cache key, so sibling worktrees of the
|
|
380
|
-
* same project resolve to the same connection. On a cache hit the existing
|
|
381
|
-
* adapter is reactivated as the current connection without re-opening the
|
|
382
|
-
* file. On a cache miss, delegates to openDatabase() for the full
|
|
383
|
-
* open + schema-init + migration flow, then caches the result.
|
|
384
|
-
*
|
|
385
|
-
* When switching to a different workspace, the previously active connection
|
|
386
|
-
* is preserved in the cache (not closed), so callers can switch back to it
|
|
387
|
-
* cheaply via a subsequent openDatabaseByWorkspace() call.
|
|
388
|
-
*
|
|
389
|
-
* @param workspace A GsdWorkspace created by createWorkspace().
|
|
390
|
-
* @returns true if the connection is open and ready, false otherwise.
|
|
391
|
-
*/
|
|
392
|
-
export function openDatabaseByWorkspace(workspace) {
|
|
393
|
-
const key = workspace.identityKey;
|
|
394
|
-
const dbPath = workspace.contract.projectDb;
|
|
395
|
-
const cached = _dbCache.get(key);
|
|
396
|
-
if (cached) {
|
|
397
|
-
// Reactivate the cached connection as the current singleton.
|
|
398
|
-
currentDb = cached.db;
|
|
399
|
-
currentPath = cached.dbPath;
|
|
400
|
-
currentPid = process.pid;
|
|
401
|
-
_dbOpenState.markAttempted();
|
|
402
|
-
_currentIdentityKey = key;
|
|
403
|
-
return true;
|
|
404
|
-
}
|
|
405
|
-
// Cache miss — need to open a new connection.
|
|
406
|
-
//
|
|
407
|
-
// If there is a currently active workspace connection, stash it in the
|
|
408
|
-
// cache under its identity key before calling openDatabase(), because
|
|
409
|
-
// openDatabase() will call closeDatabase() when the path changes (which
|
|
410
|
-
// would destroy the existing adapter). By nulling out currentDb first,
|
|
411
|
-
// we prevent openDatabase() from closing the live adapter.
|
|
412
|
-
let oldDb = null;
|
|
413
|
-
let oldPath = null;
|
|
414
|
-
let oldPid = 0;
|
|
415
|
-
let oldKey = null;
|
|
416
|
-
if (currentDb !== null && _currentIdentityKey !== null) {
|
|
417
|
-
// Snapshot the old globals so we can restore them on failure.
|
|
418
|
-
oldDb = currentDb;
|
|
419
|
-
oldPath = currentPath;
|
|
420
|
-
oldPid = currentPid;
|
|
421
|
-
oldKey = _currentIdentityKey;
|
|
422
|
-
// Save the current connection so it stays alive in the cache.
|
|
423
|
-
_dbCache.set(_currentIdentityKey, {
|
|
424
|
-
dbPath: currentPath,
|
|
425
|
-
db: currentDb,
|
|
426
|
-
});
|
|
427
|
-
// Detach from globals so openDatabase() opens fresh without closing it.
|
|
428
|
-
currentDb = null;
|
|
429
|
-
currentPath = null;
|
|
430
|
-
currentPid = 0;
|
|
431
|
-
_currentIdentityKey = null;
|
|
432
|
-
}
|
|
433
|
-
// Run the full open/schema/migration flow for the new workspace.
|
|
434
|
-
// openDatabase() can throw on corrupt DB or permission error — catch so we
|
|
435
|
-
// can restore the previous connection rather than leaving globals null.
|
|
436
|
-
let opened;
|
|
437
|
-
try {
|
|
438
|
-
opened = openDatabase(dbPath);
|
|
439
|
-
}
|
|
440
|
-
catch (err) {
|
|
441
|
-
// Failed to open the new DB. Restore the previous workspace connection so
|
|
442
|
-
// the caller's workspace remains active (it is still safe in _dbCache).
|
|
443
|
-
if (oldDb !== null) {
|
|
444
|
-
currentDb = oldDb;
|
|
445
|
-
currentPath = oldPath;
|
|
446
|
-
currentPid = oldPid;
|
|
447
|
-
_currentIdentityKey = oldKey;
|
|
448
|
-
}
|
|
449
|
-
throw err;
|
|
450
|
-
}
|
|
451
|
-
if (opened && currentDb) {
|
|
452
|
-
_dbCache.set(key, { dbPath, db: currentDb });
|
|
453
|
-
_currentIdentityKey = key;
|
|
454
|
-
}
|
|
455
|
-
else if (!opened && oldDb !== null) {
|
|
456
|
-
// Restore the previous connection so the caller's workspace remains active.
|
|
457
|
-
// The failed attempt left no live adapter, so the globals stayed null.
|
|
458
|
-
currentDb = oldDb;
|
|
459
|
-
currentPath = oldPath;
|
|
460
|
-
currentPid = oldPid;
|
|
461
|
-
_currentIdentityKey = oldKey;
|
|
462
|
-
}
|
|
463
|
-
return opened;
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Open (or reuse) the database connection scoped to the workspace in a
|
|
467
|
-
* MilestoneScope. Thin delegation to openDatabaseByWorkspace().
|
|
468
|
-
*/
|
|
469
|
-
export function openDatabaseByScope(scope) {
|
|
470
|
-
return openDatabaseByWorkspace(scope.workspace);
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Close the database connection for the given workspace and remove it from
|
|
474
|
-
* the cache. If the workspace's connection is currently active (currentDb),
|
|
475
|
-
* performs a full closeDatabase() including WAL checkpoint. Otherwise only
|
|
476
|
-
* removes the cache entry (the adapter was already replaced by a later open).
|
|
477
|
-
*/
|
|
478
|
-
export function closeDatabaseByWorkspace(workspace) {
|
|
479
|
-
const key = workspace.identityKey;
|
|
480
|
-
const cached = _dbCache.get(key);
|
|
481
|
-
if (!cached)
|
|
482
|
-
return;
|
|
483
|
-
_dbCache.delete(key);
|
|
484
|
-
if (currentDb === cached.db) {
|
|
485
|
-
// This workspace's connection is the active one — full close.
|
|
486
|
-
closeDatabase();
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
// Connection was displaced by a later open; close the adapter directly.
|
|
490
|
-
closeCachedConnection(cached, "workspace");
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
export function getDbProvider() {
|
|
494
|
-
providerLoader.load();
|
|
495
|
-
return providerLoader.getProviderName();
|
|
496
|
-
}
|
|
497
|
-
export function isDbAvailable() {
|
|
498
|
-
return currentDb !== null;
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Returns true if openDatabase() has been called at least once this session.
|
|
502
|
-
* Used to distinguish "DB not yet initialized" from "DB genuinely unavailable"
|
|
503
|
-
* so that early callers (e.g. before_agent_start context injection) don't
|
|
504
|
-
* trigger a false degraded-mode warning.
|
|
505
|
-
*/
|
|
506
|
-
export function wasDbOpenAttempted() {
|
|
507
|
-
return _dbOpenState.snapshot().attempted;
|
|
508
|
-
}
|
|
509
|
-
export function getDbStatus() {
|
|
510
|
-
providerLoader.load();
|
|
511
|
-
const openState = _dbOpenState.snapshot();
|
|
512
|
-
return {
|
|
513
|
-
available: currentDb !== null,
|
|
514
|
-
provider: providerLoader.getProviderName(),
|
|
515
|
-
attempted: openState.attempted,
|
|
516
|
-
lastError: openState.lastError,
|
|
517
|
-
lastPhase: openState.lastPhase,
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
export function openDatabase(path) {
|
|
521
|
-
_dbOpenState.markAttempted();
|
|
522
|
-
if (currentDb && currentPath !== path)
|
|
523
|
-
closeDatabase();
|
|
524
|
-
if (currentDb && currentPath === path)
|
|
525
|
-
return true;
|
|
526
|
-
// Reset error state only when a new open attempt is actually going to run.
|
|
527
|
-
_dbOpenState.clearError();
|
|
528
|
-
let rawDb;
|
|
529
|
-
let fallbackOpen = null;
|
|
530
|
-
try {
|
|
531
|
-
rawDb = providerLoader.openRaw(path);
|
|
532
|
-
}
|
|
533
|
-
catch (primaryErr) {
|
|
534
|
-
_dbOpenState.recordError("open", primaryErr);
|
|
535
|
-
// node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
|
|
536
|
-
fallbackOpen = providerLoader.tryOpenBetterSqliteFallback(path);
|
|
537
|
-
if (fallbackOpen) {
|
|
538
|
-
rawDb = fallbackOpen.rawDb;
|
|
539
|
-
_dbOpenState.clearError();
|
|
540
|
-
}
|
|
541
|
-
if (!rawDb)
|
|
542
|
-
throw primaryErr;
|
|
543
|
-
}
|
|
544
|
-
if (!rawDb)
|
|
545
|
-
return false;
|
|
546
|
-
const adapter = createDbAdapter(rawDb);
|
|
547
|
-
const fileBacked = path !== ":memory:";
|
|
548
|
-
try {
|
|
549
|
-
initSchema(adapter, fileBacked, path);
|
|
550
|
-
}
|
|
551
|
-
catch (err) {
|
|
552
|
-
// Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
|
|
553
|
-
// Attempt VACUUM recovery before giving up (see #2519).
|
|
554
|
-
if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
|
|
555
|
-
try {
|
|
556
|
-
adapter.exec("VACUUM");
|
|
557
|
-
initSchema(adapter, fileBacked, path);
|
|
558
|
-
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
559
|
-
}
|
|
560
|
-
catch (retryErr) {
|
|
561
|
-
_dbOpenState.recordError("vacuum-recovery", retryErr);
|
|
562
|
-
try {
|
|
563
|
-
adapter.close();
|
|
564
|
-
}
|
|
565
|
-
catch (e) {
|
|
566
|
-
logWarning("db", `close after VACUUM failed: ${e.message}`);
|
|
567
|
-
}
|
|
568
|
-
throw retryErr;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
_dbOpenState.recordError("initSchema", err);
|
|
573
|
-
try {
|
|
574
|
-
adapter.close();
|
|
575
|
-
}
|
|
576
|
-
catch (e) {
|
|
577
|
-
logWarning("db", `close after initSchema failed: ${e.message}`);
|
|
578
|
-
}
|
|
579
|
-
throw err;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
// Commit fallback provider switch only after open + schema both succeeded.
|
|
583
|
-
if (fallbackOpen)
|
|
584
|
-
providerLoader.commitFallback(fallbackOpen);
|
|
585
|
-
currentDb = adapter;
|
|
586
|
-
currentPath = path;
|
|
587
|
-
currentPid = process.pid;
|
|
588
|
-
if (!_exitHandlerRegistered) {
|
|
589
|
-
_exitHandlerRegistered = true;
|
|
590
|
-
process.on("exit", () => { try {
|
|
591
|
-
closeDatabase();
|
|
592
|
-
}
|
|
593
|
-
catch (e) {
|
|
594
|
-
logWarning("db", `exit handler close failed: ${e.message}`);
|
|
595
|
-
} });
|
|
596
|
-
}
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
export function closeDatabase() {
|
|
600
|
-
if (currentDb) {
|
|
601
|
-
try {
|
|
602
|
-
currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
603
|
-
}
|
|
604
|
-
catch (e) {
|
|
605
|
-
logWarning("db", `WAL checkpoint failed: ${e.message}`);
|
|
606
|
-
}
|
|
607
|
-
try {
|
|
608
|
-
// Incremental vacuum to reclaim space without blocking
|
|
609
|
-
currentDb.exec('PRAGMA incremental_vacuum(64)');
|
|
610
|
-
}
|
|
611
|
-
catch (e) {
|
|
612
|
-
logWarning("db", `incremental vacuum failed: ${e.message}`);
|
|
613
|
-
}
|
|
614
|
-
try {
|
|
615
|
-
currentDb.close();
|
|
616
|
-
}
|
|
617
|
-
catch (e) {
|
|
618
|
-
logWarning("db", `database close failed: ${e.message}`);
|
|
619
|
-
}
|
|
620
|
-
// If this connection was workspace-tracked, evict it from the cache so
|
|
621
|
-
// subsequent openDatabaseByWorkspace() calls re-open rather than reactivate
|
|
622
|
-
// a closed adapter.
|
|
623
|
-
if (_currentIdentityKey !== null) {
|
|
624
|
-
_dbCache.delete(_currentIdentityKey);
|
|
625
|
-
_currentIdentityKey = null;
|
|
626
|
-
}
|
|
627
|
-
currentDb = null;
|
|
628
|
-
currentPath = null;
|
|
629
|
-
currentPid = 0;
|
|
630
|
-
}
|
|
631
|
-
// Reset session-scoped state unconditionally so stale error info from a
|
|
632
|
-
// failed open doesn't persist into the next open attempt or status check.
|
|
633
|
-
_dbOpenState.reset();
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Re-open the active database connection from disk.
|
|
637
|
-
*
|
|
638
|
-
* Auto-mode can observe artifacts written by a workflow server running in a
|
|
639
|
-
* different process before its long-lived singleton has re-synchronized. The
|
|
640
|
-
* recovery path uses this to force the next state derivation to read from the
|
|
641
|
-
* current on-disk database instead of continuing with a possibly stale handle.
|
|
642
|
-
*/
|
|
643
|
-
export function refreshOpenDatabaseFromDisk() {
|
|
644
|
-
if (!currentDb || !currentPath)
|
|
645
|
-
return false;
|
|
646
|
-
if (currentPath === ":memory:")
|
|
647
|
-
return false;
|
|
648
|
-
const dbPath = currentPath;
|
|
649
|
-
const identityKey = _currentIdentityKey;
|
|
650
|
-
try {
|
|
651
|
-
closeDatabase();
|
|
652
|
-
const opened = openDatabase(dbPath);
|
|
653
|
-
if (opened && identityKey && currentDb) {
|
|
654
|
-
_dbCache.set(identityKey, { dbPath, db: currentDb });
|
|
655
|
-
_currentIdentityKey = identityKey;
|
|
656
|
-
}
|
|
657
|
-
return opened;
|
|
658
|
-
}
|
|
659
|
-
catch (e) {
|
|
660
|
-
logWarning("db", `database refresh failed: ${e.message}`);
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
/** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
|
|
665
|
-
export function vacuumDatabase() {
|
|
666
|
-
if (!currentDb)
|
|
667
|
-
return;
|
|
668
|
-
try {
|
|
669
|
-
currentDb.exec('VACUUM');
|
|
670
|
-
}
|
|
671
|
-
catch (e) {
|
|
672
|
-
logWarning("db", `VACUUM failed: ${e.message}`);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
/** Flush WAL into gsd.db so `git add .gsd/gsd.db` stages current state — safe while DB is open. */
|
|
676
|
-
export function checkpointDatabase() {
|
|
677
|
-
if (!currentDb)
|
|
678
|
-
return;
|
|
679
|
-
try {
|
|
680
|
-
currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
681
|
-
}
|
|
682
|
-
catch (e) {
|
|
683
|
-
logWarning("db", `WAL checkpoint failed: ${e.message}`);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Copy the live database file to `.gsd/backups/<label>-<timestamp>.db` so a
|
|
688
|
-
* destructive operation (e.g. recover, which clears the hierarchy tables) is
|
|
689
|
-
* reversible. Checkpoints the WAL first so the snapshot is complete. Returns
|
|
690
|
-
* the backup path, or null if no DB is open or the copy failed.
|
|
691
|
-
*/
|
|
692
|
-
export function backupDatabaseSnapshot(label) {
|
|
693
|
-
if (!currentPath)
|
|
694
|
-
return null;
|
|
695
|
-
try {
|
|
696
|
-
checkpointDatabase();
|
|
697
|
-
const backupsDir = join(dirname(currentPath), "backups");
|
|
698
|
-
mkdirSync(backupsDir, { recursive: true });
|
|
699
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
700
|
-
const dest = join(backupsDir, `${label}-${stamp}.db`);
|
|
701
|
-
copyFileSync(currentPath, dest);
|
|
702
|
-
return dest;
|
|
703
|
-
}
|
|
704
|
-
catch (e) {
|
|
705
|
-
logWarning("db", `database snapshot failed: ${e.message}`);
|
|
706
|
-
return null;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
const _transactionRunner = createDbTransactionRunner();
|
|
710
|
-
function createTransactionControls(db) {
|
|
711
|
-
return {
|
|
712
|
-
begin: () => db.exec("BEGIN"),
|
|
713
|
-
beginRead: () => db.exec("BEGIN DEFERRED"),
|
|
714
|
-
commit: () => db.exec("COMMIT"),
|
|
715
|
-
rollback: () => db.exec("ROLLBACK"),
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Whether the current call is running inside an active SQLite transaction.
|
|
720
|
-
* Statement-time recovery paths (e.g. VACUUM retry on a malformed memory
|
|
721
|
-
* store) MUST gate on this — SQLite refuses VACUUM inside a transaction
|
|
722
|
-
* and would mask the original error with a secondary "cannot VACUUM" throw.
|
|
723
|
-
*/
|
|
724
|
-
export function isInTransaction() {
|
|
725
|
-
return _transactionRunner.isInTransaction();
|
|
726
|
-
}
|
|
727
|
-
export function transaction(fn) {
|
|
728
|
-
if (!currentDb)
|
|
729
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
730
|
-
return _transactionRunner.transaction(createTransactionControls(currentDb), fn);
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Wrap a block of reads in a DEFERRED transaction so that all SELECTs observe
|
|
734
|
-
* a consistent snapshot of the DB even if a concurrent writer commits between
|
|
735
|
-
* them. Use this for multi-query read flows (e.g. tool executors that query
|
|
736
|
-
* milestone + slices + counts and want one snapshot). Re-entrant — if already
|
|
737
|
-
* inside a transaction, runs fn() without starting a nested one.
|
|
738
|
-
*/
|
|
739
|
-
export function readTransaction(fn) {
|
|
740
|
-
if (!currentDb)
|
|
741
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
742
|
-
return _transactionRunner.readTransaction(createTransactionControls(currentDb), fn, (rollbackErr) => {
|
|
743
|
-
// A failed ROLLBACK after a failed read is a split-brain signal —
|
|
744
|
-
// the transaction is in an indeterminate state. Surface it via the
|
|
745
|
-
// logger instead of swallowing it.
|
|
746
|
-
logError("db", "snapshotState ROLLBACK failed", {
|
|
747
|
-
error: rollbackErr.message,
|
|
748
|
-
});
|
|
749
|
-
});
|
|
750
|
-
}
|
|
21
|
+
// Connection ownership, lifecycle, schema/migrations and transaction
|
|
22
|
+
// primitives now live in the engine; re-export the full public surface so
|
|
23
|
+
// existing `from "./gsd-db.js"` imports keep working.
|
|
24
|
+
export * from "./db/engine.js";
|
|
25
|
+
import { transaction, getDbOrNull } from "./db/engine.js";
|
|
26
|
+
// ─── Single Writer Layer re-exports ──────────────────────────────────────
|
|
27
|
+
// Write subsystems live in db/writers/*; re-exported here so callers keep
|
|
28
|
+
// importing from "./gsd-db.js".
|
|
29
|
+
export * from "./db/writers/memory.js";
|
|
30
|
+
export * from "./db/writers/reconcile.js";
|
|
31
|
+
export * from "./db/writers/import-restore.js";
|
|
32
|
+
// Query Module (read-only seam) — extracted from the single-writer file.
|
|
33
|
+
export * from "./db/queries.js";
|
|
34
|
+
// Domain Write Operations (Hierarchy Status Cascades).
|
|
35
|
+
export * from "./db/writers/cascades.js";
|
|
36
|
+
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
37
|
+
import { applyStatusTransition } from "./db/writers/status.js";
|
|
751
38
|
export function insertDecision(d) {
|
|
752
|
-
if (!
|
|
39
|
+
if (!getDbOrNull())
|
|
753
40
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
754
|
-
|
|
41
|
+
getDbOrNull().prepare(`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
755
42
|
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :source, :superseded_by)`).run({
|
|
756
43
|
":id": d.id,
|
|
757
44
|
":when_context": d.when_context,
|
|
@@ -765,24 +52,10 @@ export function insertDecision(d) {
|
|
|
765
52
|
":superseded_by": d.superseded_by,
|
|
766
53
|
});
|
|
767
54
|
}
|
|
768
|
-
export function getDecisionById(id) {
|
|
769
|
-
if (!currentDb)
|
|
770
|
-
return null;
|
|
771
|
-
const row = currentDb.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
|
|
772
|
-
if (!row)
|
|
773
|
-
return null;
|
|
774
|
-
return rowToDecision(row);
|
|
775
|
-
}
|
|
776
|
-
export function getActiveDecisions() {
|
|
777
|
-
if (!currentDb)
|
|
778
|
-
return [];
|
|
779
|
-
const rows = currentDb.prepare("SELECT * FROM active_decisions").all();
|
|
780
|
-
return rows.map(rowToActiveDecision);
|
|
781
|
-
}
|
|
782
55
|
export function insertRequirement(r) {
|
|
783
|
-
if (!
|
|
56
|
+
if (!getDbOrNull())
|
|
784
57
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
785
|
-
|
|
58
|
+
getDbOrNull().prepare(`INSERT INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
786
59
|
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`).run({
|
|
787
60
|
":id": r.id,
|
|
788
61
|
":class": r.class,
|
|
@@ -798,48 +71,13 @@ export function insertRequirement(r) {
|
|
|
798
71
|
":superseded_by": r.superseded_by,
|
|
799
72
|
});
|
|
800
73
|
}
|
|
801
|
-
export function getRequirementById(id) {
|
|
802
|
-
if (!currentDb)
|
|
803
|
-
return null;
|
|
804
|
-
const row = currentDb.prepare("SELECT * FROM requirements WHERE id = ?").get(id);
|
|
805
|
-
if (!row)
|
|
806
|
-
return null;
|
|
807
|
-
return rowToRequirement(row);
|
|
808
|
-
}
|
|
809
|
-
export function getActiveRequirements() {
|
|
810
|
-
if (!currentDb)
|
|
811
|
-
return [];
|
|
812
|
-
const rows = currentDb.prepare("SELECT * FROM active_requirements").all();
|
|
813
|
-
return rows.map(rowToActiveRequirement);
|
|
814
|
-
}
|
|
815
|
-
export function getRequirementCounts() {
|
|
816
|
-
if (!currentDb) {
|
|
817
|
-
return { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
818
|
-
}
|
|
819
|
-
const rows = currentDb
|
|
820
|
-
.prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
|
|
821
|
-
.all();
|
|
822
|
-
return rowsToRequirementCounts(rows);
|
|
823
|
-
}
|
|
824
|
-
export function getDbOwnerPid() {
|
|
825
|
-
return currentPid;
|
|
826
|
-
}
|
|
827
|
-
export function getDbPath() {
|
|
828
|
-
return currentPath;
|
|
829
|
-
}
|
|
830
|
-
export function _getAdapter() {
|
|
831
|
-
return currentDb;
|
|
832
|
-
}
|
|
833
|
-
export function _resetProvider() {
|
|
834
|
-
providerLoader.reset();
|
|
835
|
-
}
|
|
836
74
|
export function upsertDecision(d) {
|
|
837
|
-
if (!
|
|
75
|
+
if (!getDbOrNull())
|
|
838
76
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
839
77
|
// Use ON CONFLICT DO UPDATE instead of INSERT OR REPLACE to preserve the
|
|
840
78
|
// seq column. INSERT OR REPLACE deletes then reinserts, resetting seq and
|
|
841
79
|
// corrupting decision ordering in DECISIONS.md after reconcile replay.
|
|
842
|
-
|
|
80
|
+
getDbOrNull().prepare(`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
843
81
|
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :source, :superseded_by)
|
|
844
82
|
ON CONFLICT(id) DO UPDATE SET
|
|
845
83
|
when_context = excluded.when_context,
|
|
@@ -864,9 +102,9 @@ export function upsertDecision(d) {
|
|
|
864
102
|
});
|
|
865
103
|
}
|
|
866
104
|
export function upsertRequirement(r) {
|
|
867
|
-
if (!
|
|
105
|
+
if (!getDbOrNull())
|
|
868
106
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
869
|
-
|
|
107
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
870
108
|
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`).run({
|
|
871
109
|
":id": r.id,
|
|
872
110
|
":class": r.class,
|
|
@@ -883,40 +121,40 @@ export function upsertRequirement(r) {
|
|
|
883
121
|
});
|
|
884
122
|
}
|
|
885
123
|
export function clearArtifacts() {
|
|
886
|
-
if (!
|
|
124
|
+
if (!getDbOrNull())
|
|
887
125
|
return;
|
|
888
126
|
try {
|
|
889
|
-
|
|
127
|
+
getDbOrNull().exec("DELETE FROM artifacts");
|
|
890
128
|
}
|
|
891
129
|
catch (e) {
|
|
892
130
|
logWarning("db", `clearArtifacts failed: ${e.message}`);
|
|
893
131
|
}
|
|
894
132
|
}
|
|
895
133
|
export function clearDecisions() {
|
|
896
|
-
if (!
|
|
134
|
+
if (!getDbOrNull())
|
|
897
135
|
return;
|
|
898
136
|
try {
|
|
899
|
-
|
|
137
|
+
getDbOrNull().exec("DELETE FROM decisions");
|
|
900
138
|
}
|
|
901
139
|
catch (e) {
|
|
902
140
|
logWarning("db", `clearDecisions failed: ${e.message}`);
|
|
903
141
|
}
|
|
904
142
|
}
|
|
905
143
|
export function clearRequirements() {
|
|
906
|
-
if (!
|
|
144
|
+
if (!getDbOrNull())
|
|
907
145
|
return;
|
|
908
146
|
try {
|
|
909
|
-
|
|
147
|
+
getDbOrNull().exec("DELETE FROM requirements");
|
|
910
148
|
}
|
|
911
149
|
catch (e) {
|
|
912
150
|
logWarning("db", `clearRequirements failed: ${e.message}`);
|
|
913
151
|
}
|
|
914
152
|
}
|
|
915
153
|
export function insertArtifact(a) {
|
|
916
|
-
if (!
|
|
154
|
+
if (!getDbOrNull())
|
|
917
155
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
918
156
|
const contentHash = createHash("sha256").update(a.full_content).digest("hex");
|
|
919
|
-
|
|
157
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash)
|
|
920
158
|
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at, :content_hash)`).run({
|
|
921
159
|
":path": a.path,
|
|
922
160
|
":artifact_type": a.artifact_type,
|
|
@@ -929,9 +167,9 @@ export function insertArtifact(a) {
|
|
|
929
167
|
});
|
|
930
168
|
}
|
|
931
169
|
export function insertMilestone(m) {
|
|
932
|
-
if (!
|
|
170
|
+
if (!getDbOrNull())
|
|
933
171
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
934
|
-
|
|
172
|
+
const result = getDbOrNull().prepare(`INSERT OR IGNORE INTO milestones (
|
|
935
173
|
id, title, status, depends_on, created_at,
|
|
936
174
|
vision, success_criteria, key_risks, proof_strategy,
|
|
937
175
|
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
@@ -961,11 +199,12 @@ export function insertMilestone(m) {
|
|
|
961
199
|
":requirement_coverage": m.planning?.requirementCoverage ?? "",
|
|
962
200
|
":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
|
|
963
201
|
});
|
|
202
|
+
return (result.changes ?? 0) > 0;
|
|
964
203
|
}
|
|
965
204
|
export function upsertMilestonePlanning(milestoneId, planning) {
|
|
966
|
-
if (!
|
|
205
|
+
if (!getDbOrNull())
|
|
967
206
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
968
|
-
|
|
207
|
+
getDbOrNull().prepare(`UPDATE milestones SET
|
|
969
208
|
title = COALESCE(NULLIF(:title, ''), title),
|
|
970
209
|
status = COALESCE(NULLIF(:status, ''), status),
|
|
971
210
|
depends_on = COALESCE(:depends_on, depends_on),
|
|
@@ -999,14 +238,14 @@ export function upsertMilestonePlanning(milestoneId, planning) {
|
|
|
999
238
|
});
|
|
1000
239
|
}
|
|
1001
240
|
export function insertSlice(s) {
|
|
1002
|
-
if (!
|
|
241
|
+
if (!getDbOrNull())
|
|
1003
242
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1004
243
|
const SLICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
1005
244
|
const invalidDep = (s.depends ?? []).find(d => !SLICE_ID_RE.test(d));
|
|
1006
245
|
if (invalidDep !== undefined) {
|
|
1007
246
|
throw new GSDError(GSD_STALE_STATE, `insertSlice: depends element "${invalidDep}" is not a valid slice ID`);
|
|
1008
247
|
}
|
|
1009
|
-
|
|
248
|
+
getDbOrNull().prepare(`INSERT INTO slices (
|
|
1010
249
|
milestone_id, id, title, status, risk, depends, demo, created_at,
|
|
1011
250
|
goal, success_criteria, proof_level, integration_closure, observability_impact, target_repositories, sequence,
|
|
1012
251
|
is_sketch, sketch_scope
|
|
@@ -1067,26 +306,14 @@ export function insertSlice(s) {
|
|
|
1067
306
|
}
|
|
1068
307
|
// ADR-011: sketch-then-refine helpers
|
|
1069
308
|
export function setSliceSketchFlag(milestoneId, sliceId, isSketch) {
|
|
1070
|
-
if (!
|
|
309
|
+
if (!getDbOrNull())
|
|
1071
310
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
/**
|
|
1075
|
-
* ADR-017 raw primitive: returns slice IDs in a milestone whose is_sketch flag
|
|
1076
|
-
* is still 1. The stale-sketch-flag drift handler at
|
|
1077
|
-
* `state-reconciliation/drift/sketch-flag.ts` composes this with PLAN.md
|
|
1078
|
-
* existence checks to detect drift, then writes via `setSliceSketchFlag`.
|
|
1079
|
-
*/
|
|
1080
|
-
export function getSketchedSliceIds(milestoneId) {
|
|
1081
|
-
if (!currentDb)
|
|
1082
|
-
return [];
|
|
1083
|
-
const rows = currentDb.prepare(`SELECT id FROM slices WHERE milestone_id = :mid AND is_sketch = 1`).all({ ":mid": milestoneId });
|
|
1084
|
-
return rows.map((r) => r.id);
|
|
311
|
+
getDbOrNull().prepare(`UPDATE slices SET is_sketch = :is_sketch WHERE milestone_id = :mid AND id = :sid`).run({ ":is_sketch": isSketch ? 1 : 0, ":mid": milestoneId, ":sid": sliceId });
|
|
1085
312
|
}
|
|
1086
313
|
export function upsertSlicePlanning(milestoneId, sliceId, planning) {
|
|
1087
|
-
if (!
|
|
314
|
+
if (!getDbOrNull())
|
|
1088
315
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1089
|
-
|
|
316
|
+
getDbOrNull().prepare(`UPDATE slices SET
|
|
1090
317
|
goal = COALESCE(:goal, goal),
|
|
1091
318
|
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
1092
319
|
proof_level = COALESCE(:proof_level, proof_level),
|
|
@@ -1105,9 +332,9 @@ export function upsertSlicePlanning(milestoneId, sliceId, planning) {
|
|
|
1105
332
|
});
|
|
1106
333
|
}
|
|
1107
334
|
export function insertTask(t) {
|
|
1108
|
-
if (!
|
|
335
|
+
if (!getDbOrNull())
|
|
1109
336
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1110
|
-
|
|
337
|
+
getDbOrNull().prepare(`INSERT INTO tasks (
|
|
1111
338
|
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
1112
339
|
verification_result, duration, completed_at, blocker_discovered,
|
|
1113
340
|
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
@@ -1179,26 +406,17 @@ export function insertTask(t) {
|
|
|
1179
406
|
});
|
|
1180
407
|
}
|
|
1181
408
|
export function updateTaskStatus(milestoneId, sliceId, taskId, status, completedAt) {
|
|
1182
|
-
|
|
1183
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1184
|
-
currentDb.prepare(`UPDATE tasks SET status = :status, completed_at = :completed_at
|
|
1185
|
-
WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`).run({
|
|
1186
|
-
":status": status,
|
|
1187
|
-
":completed_at": completedAt ?? null,
|
|
1188
|
-
":milestone_id": milestoneId,
|
|
1189
|
-
":slice_id": sliceId,
|
|
1190
|
-
":id": taskId,
|
|
1191
|
-
});
|
|
409
|
+
applyStatusTransition({ entity: "task", milestoneId, sliceId, taskId, status, completedAt });
|
|
1192
410
|
}
|
|
1193
411
|
export function setTaskBlockerDiscovered(milestoneId, sliceId, taskId, discovered) {
|
|
1194
|
-
if (!
|
|
412
|
+
if (!getDbOrNull())
|
|
1195
413
|
return;
|
|
1196
|
-
|
|
414
|
+
getDbOrNull().prepare(`UPDATE tasks SET blocker_discovered = :discovered WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":discovered": discovered ? 1 : 0, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1197
415
|
}
|
|
1198
416
|
export function upsertTaskPlanning(milestoneId, sliceId, taskId, planning) {
|
|
1199
|
-
if (!
|
|
417
|
+
if (!getDbOrNull())
|
|
1200
418
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1201
|
-
|
|
419
|
+
getDbOrNull().prepare(`UPDATE tasks SET
|
|
1202
420
|
title = COALESCE(:title, title),
|
|
1203
421
|
description = COALESCE(:description, description),
|
|
1204
422
|
estimate = COALESCE(:estimate, estimate),
|
|
@@ -1225,96 +443,25 @@ export function upsertTaskPlanning(milestoneId, sliceId, taskId, planning) {
|
|
|
1225
443
|
":target_repositories": planning.targetRepositories ? JSON.stringify(planning.targetRepositories) : null,
|
|
1226
444
|
});
|
|
1227
445
|
}
|
|
1228
|
-
export function getSlice(milestoneId, sliceId) {
|
|
1229
|
-
if (!currentDb)
|
|
1230
|
-
return null;
|
|
1231
|
-
const row = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid AND id = :sid").get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1232
|
-
if (!row)
|
|
1233
|
-
return null;
|
|
1234
|
-
return rowToSlice(row);
|
|
1235
|
-
}
|
|
1236
446
|
export function updateSliceStatus(milestoneId, sliceId, status, completedAt) {
|
|
1237
|
-
|
|
1238
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1239
|
-
currentDb.prepare(`UPDATE slices SET status = :status, completed_at = :completed_at
|
|
1240
|
-
WHERE milestone_id = :milestone_id AND id = :id`).run({
|
|
1241
|
-
":status": status,
|
|
1242
|
-
":completed_at": completedAt ?? null,
|
|
1243
|
-
":milestone_id": milestoneId,
|
|
1244
|
-
":id": sliceId,
|
|
1245
|
-
});
|
|
447
|
+
applyStatusTransition({ entity: "slice", milestoneId, sliceId, status, completedAt });
|
|
1246
448
|
}
|
|
1247
449
|
export function setTaskSummaryMd(milestoneId, sliceId, taskId, md) {
|
|
1248
|
-
if (!
|
|
450
|
+
if (!getDbOrNull())
|
|
1249
451
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1250
|
-
|
|
452
|
+
getDbOrNull().prepare(`UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId, ":md": md });
|
|
1251
453
|
}
|
|
1252
454
|
export function setSliceSummaryMd(milestoneId, sliceId, summaryMd, uatMd) {
|
|
1253
|
-
if (!
|
|
455
|
+
if (!getDbOrNull())
|
|
1254
456
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1257
|
-
export function getTask(milestoneId, sliceId, taskId) {
|
|
1258
|
-
if (!currentDb)
|
|
1259
|
-
return null;
|
|
1260
|
-
const row = currentDb.prepare("SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid").get({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1261
|
-
if (!row)
|
|
1262
|
-
return null;
|
|
1263
|
-
return rowToTask(row);
|
|
1264
|
-
}
|
|
1265
|
-
export function getSliceTasks(milestoneId, sliceId) {
|
|
1266
|
-
if (!currentDb)
|
|
1267
|
-
return [];
|
|
1268
|
-
const rows = currentDb.prepare("SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid ORDER BY sequence, id").all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1269
|
-
return rows.map(rowToTask);
|
|
1270
|
-
}
|
|
1271
|
-
export function getCompletedMilestoneTaskFileHints(milestoneId) {
|
|
1272
|
-
if (!currentDb)
|
|
1273
|
-
return [];
|
|
1274
|
-
const rows = currentDb.prepare(`SELECT files, key_files
|
|
1275
|
-
FROM tasks
|
|
1276
|
-
WHERE milestone_id = :mid AND status IN ('complete', 'done')`).all({ ":mid": milestoneId });
|
|
1277
|
-
const hints = new Set();
|
|
1278
|
-
for (const row of rows) {
|
|
1279
|
-
for (const raw of [row["files"], row["key_files"]]) {
|
|
1280
|
-
for (const file of parseStringArrayColumn(raw)) {
|
|
1281
|
-
const normalized = normalizeRepoPath(file);
|
|
1282
|
-
if (normalized)
|
|
1283
|
-
hints.add(normalized);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
return [...hints];
|
|
1288
|
-
}
|
|
1289
|
-
function parseStringArrayColumn(raw) {
|
|
1290
|
-
if (Array.isArray(raw))
|
|
1291
|
-
return raw.filter((entry) => typeof entry === "string");
|
|
1292
|
-
if (typeof raw !== "string")
|
|
1293
|
-
return [];
|
|
1294
|
-
const trimmed = raw.trim();
|
|
1295
|
-
if (!trimmed)
|
|
1296
|
-
return [];
|
|
1297
|
-
try {
|
|
1298
|
-
const parsed = JSON.parse(trimmed);
|
|
1299
|
-
if (Array.isArray(parsed))
|
|
1300
|
-
return parsed.filter((entry) => typeof entry === "string");
|
|
1301
|
-
if (typeof parsed === "string")
|
|
1302
|
-
return [parsed];
|
|
1303
|
-
}
|
|
1304
|
-
catch {
|
|
1305
|
-
return trimmed.split(",");
|
|
1306
|
-
}
|
|
1307
|
-
return [];
|
|
1308
|
-
}
|
|
1309
|
-
function normalizeRepoPath(file) {
|
|
1310
|
-
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
457
|
+
getDbOrNull().prepare(`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
|
|
1311
458
|
}
|
|
1312
459
|
// ─── ADR-011 Phase 2 escalation helpers ──────────────────────────────────
|
|
1313
460
|
/** Set pause-on-escalation state on a completed task. Mutually exclusive with awaiting_review. */
|
|
1314
461
|
export function setTaskEscalationPending(milestoneId, sliceId, taskId, artifactPath) {
|
|
1315
|
-
if (!
|
|
462
|
+
if (!getDbOrNull())
|
|
1316
463
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1317
|
-
|
|
464
|
+
getDbOrNull().prepare(`UPDATE tasks
|
|
1318
465
|
SET escalation_pending = 1,
|
|
1319
466
|
escalation_awaiting_review = 0,
|
|
1320
467
|
escalation_artifact_path = :path
|
|
@@ -1322,9 +469,9 @@ export function setTaskEscalationPending(milestoneId, sliceId, taskId, artifactP
|
|
|
1322
469
|
}
|
|
1323
470
|
/** Set awaiting-review state (artifact exists and requires explicit user review). Mutually exclusive with pending. */
|
|
1324
471
|
export function setTaskEscalationAwaitingReview(milestoneId, sliceId, taskId, artifactPath) {
|
|
1325
|
-
if (!
|
|
472
|
+
if (!getDbOrNull())
|
|
1326
473
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1327
|
-
|
|
474
|
+
getDbOrNull().prepare(`UPDATE tasks
|
|
1328
475
|
SET escalation_awaiting_review = 1,
|
|
1329
476
|
escalation_pending = 0,
|
|
1330
477
|
escalation_artifact_path = :path
|
|
@@ -1332,9 +479,9 @@ export function setTaskEscalationAwaitingReview(milestoneId, sliceId, taskId, ar
|
|
|
1332
479
|
}
|
|
1333
480
|
/** Clear escalation-pending and awaiting-review flags once the user has resolved it. */
|
|
1334
481
|
export function clearTaskEscalationFlags(milestoneId, sliceId, taskId) {
|
|
1335
|
-
if (!
|
|
482
|
+
if (!getDbOrNull())
|
|
1336
483
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1337
|
-
|
|
484
|
+
getDbOrNull().prepare(`UPDATE tasks
|
|
1338
485
|
SET escalation_pending = 0,
|
|
1339
486
|
escalation_awaiting_review = 0
|
|
1340
487
|
WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
@@ -1345,10 +492,10 @@ export function clearTaskEscalationFlags(milestoneId, sliceId, taskId) {
|
|
|
1345
492
|
* another caller already claimed it (must skip).
|
|
1346
493
|
*/
|
|
1347
494
|
export function claimEscalationOverride(milestoneId, sliceId, sourceTaskId) {
|
|
1348
|
-
if (!
|
|
495
|
+
if (!getDbOrNull())
|
|
1349
496
|
return false;
|
|
1350
497
|
const now = new Date().toISOString();
|
|
1351
|
-
const result =
|
|
498
|
+
const result = getDbOrNull().prepare(`UPDATE tasks
|
|
1352
499
|
SET escalation_override_applied_at = :now
|
|
1353
500
|
WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid
|
|
1354
501
|
AND escalation_override_applied_at IS NULL
|
|
@@ -1357,51 +504,19 @@ export function claimEscalationOverride(milestoneId, sliceId, sourceTaskId) {
|
|
|
1357
504
|
const changes = result.changes ?? 0;
|
|
1358
505
|
return changes > 0;
|
|
1359
506
|
}
|
|
1360
|
-
/** Find the most recent resolved-but-unapplied escalation override in a slice. */
|
|
1361
|
-
export function findUnappliedEscalationOverride(milestoneId, sliceId) {
|
|
1362
|
-
if (!currentDb)
|
|
1363
|
-
return null;
|
|
1364
|
-
// Filter BOTH flags: escalation_pending=0 AND escalation_awaiting_review=0
|
|
1365
|
-
// ensures we only claim overrides the user has explicitly resolved.
|
|
1366
|
-
// Without the awaiting_review filter, continueWithDefault=true artifacts
|
|
1367
|
-
// (not yet responded to) would be prematurely claimed, causing the override
|
|
1368
|
-
// to be lost when the user later resolves (#ADR-011 Phase 2 peer-review Bug 2).
|
|
1369
|
-
const row = currentDb.prepare(`SELECT id, escalation_artifact_path AS path
|
|
1370
|
-
FROM tasks
|
|
1371
|
-
WHERE milestone_id = :mid AND slice_id = :sid
|
|
1372
|
-
AND escalation_artifact_path IS NOT NULL
|
|
1373
|
-
AND escalation_override_applied_at IS NULL
|
|
1374
|
-
AND escalation_pending = 0
|
|
1375
|
-
AND escalation_awaiting_review = 0
|
|
1376
|
-
ORDER BY sequence DESC, id DESC
|
|
1377
|
-
LIMIT 1`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1378
|
-
if (!row || !row.path)
|
|
1379
|
-
return null;
|
|
1380
|
-
return { taskId: row.id, artifactPath: row.path };
|
|
1381
|
-
}
|
|
1382
507
|
/** Set the blocker_source provenance field (used when rejecting an escalation). */
|
|
1383
508
|
export function setTaskBlockerSource(milestoneId, sliceId, taskId, source) {
|
|
1384
|
-
if (!
|
|
509
|
+
if (!getDbOrNull())
|
|
1385
510
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1386
|
-
|
|
511
|
+
getDbOrNull().prepare(`UPDATE tasks
|
|
1387
512
|
SET blocker_discovered = 1,
|
|
1388
513
|
blocker_source = :src
|
|
1389
514
|
WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":src": source, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1390
515
|
}
|
|
1391
|
-
/** List tasks with active escalation artifacts across a milestone (for /gsd escalate list). */
|
|
1392
|
-
export function listEscalationArtifacts(milestoneId, includeResolved = false) {
|
|
1393
|
-
if (!currentDb)
|
|
1394
|
-
return [];
|
|
1395
|
-
const filter = includeResolved
|
|
1396
|
-
? "escalation_artifact_path IS NOT NULL"
|
|
1397
|
-
: "(escalation_pending = 1 OR escalation_awaiting_review = 1) AND escalation_artifact_path IS NOT NULL";
|
|
1398
|
-
const rows = currentDb.prepare(`SELECT * FROM tasks WHERE milestone_id = :mid AND ${filter} ORDER BY slice_id, sequence, id`).all({ ":mid": milestoneId });
|
|
1399
|
-
return rows.map(rowToTask);
|
|
1400
|
-
}
|
|
1401
516
|
export function insertVerificationEvidence(e) {
|
|
1402
|
-
if (!
|
|
517
|
+
if (!getDbOrNull())
|
|
1403
518
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1404
|
-
|
|
519
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
1405
520
|
VALUES (:task_id, :slice_id, :milestone_id, :command, :exit_code, :verdict, :duration_ms, :created_at)`).run({
|
|
1406
521
|
":task_id": e.taskId,
|
|
1407
522
|
":slice_id": e.sliceId,
|
|
@@ -1413,53 +528,33 @@ export function insertVerificationEvidence(e) {
|
|
|
1413
528
|
":created_at": new Date().toISOString(),
|
|
1414
529
|
});
|
|
1415
530
|
}
|
|
1416
|
-
export function getVerificationEvidence(milestoneId, sliceId, taskId) {
|
|
1417
|
-
if (!currentDb)
|
|
1418
|
-
return [];
|
|
1419
|
-
const rows = currentDb.prepare("SELECT * FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid ORDER BY id").all({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1420
|
-
return rows;
|
|
1421
|
-
}
|
|
1422
|
-
export function getAllMilestones() {
|
|
1423
|
-
if (!currentDb)
|
|
1424
|
-
return [];
|
|
1425
|
-
const rows = currentDb.prepare("SELECT * FROM milestones ORDER BY CASE WHEN sequence > 0 THEN 0 ELSE 1 END, sequence, id").all();
|
|
1426
|
-
return rows.map(rowToMilestone);
|
|
1427
|
-
}
|
|
1428
|
-
export function getMilestone(id) {
|
|
1429
|
-
if (!currentDb)
|
|
1430
|
-
return null;
|
|
1431
|
-
const row = currentDb.prepare("SELECT * FROM milestones WHERE id = :id").get({ ":id": id });
|
|
1432
|
-
if (!row)
|
|
1433
|
-
return null;
|
|
1434
|
-
return rowToMilestone(row);
|
|
1435
|
-
}
|
|
1436
531
|
export function setMilestoneQueueOrder(order) {
|
|
1437
|
-
if (!
|
|
532
|
+
if (!getDbOrNull())
|
|
1438
533
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1439
|
-
|
|
534
|
+
getDbOrNull().exec("BEGIN IMMEDIATE");
|
|
1440
535
|
try {
|
|
1441
|
-
|
|
1442
|
-
const stmt =
|
|
536
|
+
getDbOrNull().prepare("UPDATE milestones SET sequence = 0").run();
|
|
537
|
+
const stmt = getDbOrNull().prepare("UPDATE milestones SET sequence = :sequence WHERE id = :id");
|
|
1443
538
|
order.forEach((id, index) => {
|
|
1444
539
|
stmt.run({ ":id": id, ":sequence": index + 1 });
|
|
1445
540
|
});
|
|
1446
|
-
|
|
541
|
+
getDbOrNull().exec("COMMIT");
|
|
1447
542
|
}
|
|
1448
543
|
catch (err) {
|
|
1449
|
-
|
|
544
|
+
getDbOrNull().exec("ROLLBACK");
|
|
1450
545
|
throw err;
|
|
1451
546
|
}
|
|
1452
547
|
}
|
|
1453
548
|
function getMilestoneStatusForUpdate(milestoneId) {
|
|
1454
|
-
if (!
|
|
549
|
+
if (!getDbOrNull())
|
|
1455
550
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1456
|
-
const row =
|
|
551
|
+
const row = getDbOrNull().prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": milestoneId });
|
|
1457
552
|
return typeof row?.["status"] === "string" ? row["status"] : null;
|
|
1458
553
|
}
|
|
1459
554
|
function writeMilestoneStatus(milestoneId, status, completedAt) {
|
|
1460
|
-
if (!
|
|
555
|
+
if (!getDbOrNull())
|
|
1461
556
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1462
|
-
|
|
557
|
+
getDbOrNull().prepare(`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
|
|
1463
558
|
}
|
|
1464
559
|
/**
|
|
1465
560
|
* Update a milestone's status in the database.
|
|
@@ -1469,19 +564,13 @@ function writeMilestoneStatus(milestoneId, status, completedAt) {
|
|
|
1469
564
|
* must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
|
|
1470
565
|
*/
|
|
1471
566
|
export function updateMilestoneStatus(milestoneId, status, completedAt) {
|
|
1472
|
-
|
|
1473
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1474
|
-
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1475
|
-
if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(status)) {
|
|
1476
|
-
throw new Error(`Cannot update closed milestone ${milestoneId} from ${currentStatus} to ${status}; use gsd_milestone_reopen for an explicit reopen.`);
|
|
1477
|
-
}
|
|
1478
|
-
writeMilestoneStatus(milestoneId, status, completedAt);
|
|
567
|
+
applyStatusTransition({ entity: "milestone", milestoneId, status, completedAt });
|
|
1479
568
|
}
|
|
1480
569
|
/**
|
|
1481
570
|
* Explicit closed -> active transition for gsd_milestone_reopen only.
|
|
1482
571
|
*/
|
|
1483
572
|
export function reopenMilestoneStatus(milestoneId) {
|
|
1484
|
-
if (!
|
|
573
|
+
if (!getDbOrNull())
|
|
1485
574
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1486
575
|
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1487
576
|
if (!currentStatus) {
|
|
@@ -1492,562 +581,24 @@ export function reopenMilestoneStatus(milestoneId) {
|
|
|
1492
581
|
}
|
|
1493
582
|
writeMilestoneStatus(milestoneId, "active", null);
|
|
1494
583
|
}
|
|
1495
|
-
export function getActiveMilestoneFromDb() {
|
|
1496
|
-
if (!currentDb)
|
|
1497
|
-
return null;
|
|
1498
|
-
const row = currentDb.prepare("SELECT * FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1").get();
|
|
1499
|
-
if (!row)
|
|
1500
|
-
return null;
|
|
1501
|
-
return rowToMilestone(row);
|
|
1502
|
-
}
|
|
1503
|
-
export function getActiveSliceFromDb(milestoneId) {
|
|
1504
|
-
if (!currentDb)
|
|
1505
|
-
return null;
|
|
1506
|
-
// Single query: find the first non-complete slice whose dependencies are all satisfied.
|
|
1507
|
-
// Uses json_each() to expand the JSON depends array and checks each dep is complete.
|
|
1508
|
-
const row = currentDb.prepare(`SELECT s.* FROM slices s
|
|
1509
|
-
WHERE s.milestone_id = :mid
|
|
1510
|
-
AND s.status NOT IN ('complete', 'done', 'skipped')
|
|
1511
|
-
AND NOT EXISTS (
|
|
1512
|
-
SELECT 1 FROM json_each(s.depends) AS dep
|
|
1513
|
-
WHERE dep.value NOT IN (
|
|
1514
|
-
SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
|
|
1515
|
-
)
|
|
1516
|
-
)
|
|
1517
|
-
ORDER BY s.sequence, s.id
|
|
1518
|
-
LIMIT 1`).get({ ":mid": milestoneId });
|
|
1519
|
-
if (!row)
|
|
1520
|
-
return null;
|
|
1521
|
-
return rowToSlice(row);
|
|
1522
|
-
}
|
|
1523
|
-
export function getActiveTaskFromDb(milestoneId, sliceId) {
|
|
1524
|
-
if (!currentDb)
|
|
1525
|
-
return null;
|
|
1526
|
-
const row = currentDb.prepare("SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1").get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1527
|
-
if (!row)
|
|
1528
|
-
return null;
|
|
1529
|
-
return rowToTask(row);
|
|
1530
|
-
}
|
|
1531
|
-
export function getMilestoneSlices(milestoneId) {
|
|
1532
|
-
if (!currentDb)
|
|
1533
|
-
return [];
|
|
1534
|
-
const rows = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid ORDER BY sequence, id").all({ ":mid": milestoneId });
|
|
1535
|
-
return rows.map(rowToSlice);
|
|
1536
|
-
}
|
|
1537
|
-
export function getArtifact(path) {
|
|
1538
|
-
if (!currentDb)
|
|
1539
|
-
return null;
|
|
1540
|
-
const row = currentDb.prepare("SELECT * FROM artifacts WHERE path = :path").get({ ":path": path });
|
|
1541
|
-
if (!row)
|
|
1542
|
-
return null;
|
|
1543
|
-
return rowToArtifact(row);
|
|
1544
|
-
}
|
|
1545
584
|
// ─── Lightweight Query Variants (hot-path optimized) ─────────────────────
|
|
1546
|
-
/** Fast milestone status check — avoids deserializing JSON planning fields. */
|
|
1547
|
-
export function getActiveMilestoneIdFromDb() {
|
|
1548
|
-
if (!currentDb)
|
|
1549
|
-
return null;
|
|
1550
|
-
const row = currentDb.prepare("SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1").get();
|
|
1551
|
-
if (!row)
|
|
1552
|
-
return null;
|
|
1553
|
-
return rowToIdStatusSummary(row);
|
|
1554
|
-
}
|
|
1555
|
-
/** Fast slice status check — avoids deserializing JSON depends/planning fields. */
|
|
1556
|
-
export function getSliceStatusSummary(milestoneId) {
|
|
1557
|
-
if (!currentDb)
|
|
1558
|
-
return [];
|
|
1559
|
-
return currentDb.prepare("SELECT id, status FROM slices WHERE milestone_id = :mid ORDER BY sequence, id").all({ ":mid": milestoneId }).map(rowToIdStatusSummary);
|
|
1560
|
-
}
|
|
1561
|
-
/** Fast task status check — avoids deserializing JSON arrays and large text fields. */
|
|
1562
|
-
export function getActiveTaskIdFromDb(milestoneId, sliceId) {
|
|
1563
|
-
if (!currentDb)
|
|
1564
|
-
return null;
|
|
1565
|
-
const row = currentDb.prepare("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").get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1566
|
-
if (!row)
|
|
1567
|
-
return null;
|
|
1568
|
-
return rowToActiveTaskSummary(row);
|
|
1569
|
-
}
|
|
1570
|
-
/** Count tasks by status for a slice — useful for progress reporting without full row load. */
|
|
1571
|
-
export function getSliceTaskCounts(milestoneId, sliceId) {
|
|
1572
|
-
if (!currentDb)
|
|
1573
|
-
return emptyTaskStatusCounts();
|
|
1574
|
-
const row = currentDb.prepare(`SELECT
|
|
1575
|
-
COUNT(*) as total,
|
|
1576
|
-
SUM(CASE WHEN status IN ('complete', 'done') THEN 1 ELSE 0 END) as done,
|
|
1577
|
-
SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
|
|
1578
|
-
FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1579
|
-
return rowToTaskStatusCounts(row);
|
|
1580
|
-
}
|
|
1581
585
|
// ─── Slice Dependencies (junction table) ─────────────────────────────────
|
|
1582
586
|
/** Sync the slice_dependencies junction table from a slice's JSON depends array. */
|
|
1583
587
|
export function syncSliceDependencies(milestoneId, sliceId, depends) {
|
|
1584
|
-
if (!
|
|
588
|
+
if (!getDbOrNull())
|
|
1585
589
|
return;
|
|
1586
|
-
|
|
590
|
+
getDbOrNull().prepare("DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid").run({ ":mid": milestoneId, ":sid": sliceId });
|
|
1587
591
|
for (const dep of depends) {
|
|
1588
|
-
|
|
592
|
+
getDbOrNull().prepare("INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (:mid, :sid, :dep)").run({ ":mid": milestoneId, ":sid": sliceId, ":dep": dep });
|
|
1589
593
|
}
|
|
1590
594
|
}
|
|
1591
|
-
/** Get all slices that depend on a given slice. */
|
|
1592
|
-
export function getDependentSlices(milestoneId, sliceId) {
|
|
1593
|
-
if (!currentDb)
|
|
1594
|
-
return [];
|
|
1595
|
-
const rows = currentDb.prepare("SELECT slice_id FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid").all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1596
|
-
return rowsToStringColumn(rows, "slice_id");
|
|
1597
|
-
}
|
|
1598
595
|
// ─── Worktree DB Helpers ──────────────────────────────────────────────────
|
|
1599
|
-
export function copyWorktreeDb(srcDbPath, destDbPath) {
|
|
1600
|
-
try {
|
|
1601
|
-
if (!existsSync(srcDbPath))
|
|
1602
|
-
return false;
|
|
1603
|
-
const destDir = dirname(destDbPath);
|
|
1604
|
-
mkdirSync(destDir, { recursive: true });
|
|
1605
|
-
copyFileSync(srcDbPath, destDbPath);
|
|
1606
|
-
return true;
|
|
1607
|
-
}
|
|
1608
|
-
catch (err) {
|
|
1609
|
-
logError("db", "failed to copy DB to worktree", { error: err.message });
|
|
1610
|
-
return false;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
export function reconcileWorktreeDb(mainDbPath, worktreeDbPath) {
|
|
1614
|
-
const zero = {
|
|
1615
|
-
decisions: 0,
|
|
1616
|
-
requirements: 0,
|
|
1617
|
-
artifacts: 0,
|
|
1618
|
-
milestones: 0,
|
|
1619
|
-
slices: 0,
|
|
1620
|
-
tasks: 0,
|
|
1621
|
-
memories: 0,
|
|
1622
|
-
replan_history: 0,
|
|
1623
|
-
assessments: 0,
|
|
1624
|
-
quality_gates: 0,
|
|
1625
|
-
slice_dependencies: 0,
|
|
1626
|
-
verification_evidence: 0,
|
|
1627
|
-
gate_runs: 0,
|
|
1628
|
-
milestone_commit_attributions: 0,
|
|
1629
|
-
conflicts: [],
|
|
1630
|
-
};
|
|
1631
|
-
if (!existsSync(worktreeDbPath))
|
|
1632
|
-
return zero;
|
|
1633
|
-
// Guard: bail when both paths resolve to the same physical file.
|
|
1634
|
-
// ATTACHing a WAL-mode DB to itself corrupts the WAL (#2823).
|
|
1635
|
-
try {
|
|
1636
|
-
if (realpathSync(mainDbPath) === realpathSync(worktreeDbPath))
|
|
1637
|
-
return zero;
|
|
1638
|
-
}
|
|
1639
|
-
catch (e) {
|
|
1640
|
-
logWarning("db", `realpathSync failed: ${e.message}`);
|
|
1641
|
-
}
|
|
1642
|
-
// Sanitize path: reject any characters that could break ATTACH syntax.
|
|
1643
|
-
// ATTACH DATABASE doesn't support parameterized paths in all providers,
|
|
1644
|
-
// so we use strict allowlist validation instead.
|
|
1645
|
-
if (/['";\x00]/.test(worktreeDbPath)) {
|
|
1646
|
-
logError("db", "worktree DB reconciliation failed: path contains unsafe characters");
|
|
1647
|
-
return zero;
|
|
1648
|
-
}
|
|
1649
|
-
if (!currentDb) {
|
|
1650
|
-
const opened = openDatabase(mainDbPath);
|
|
1651
|
-
if (!opened) {
|
|
1652
|
-
logError("db", "worktree DB reconciliation failed: cannot open main DB");
|
|
1653
|
-
return zero;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
const adapter = currentDb;
|
|
1657
|
-
const conflicts = [];
|
|
1658
|
-
try {
|
|
1659
|
-
adapter.exec(`ATTACH DATABASE '${worktreeDbPath}' AS wt`);
|
|
1660
|
-
try {
|
|
1661
|
-
function countChanges(result) {
|
|
1662
|
-
return typeof result === "object" && result !== null ? (result.changes ?? 0) : 0;
|
|
1663
|
-
}
|
|
1664
|
-
function wtTableInfo(tableName) {
|
|
1665
|
-
return adapter.prepare(`PRAGMA wt.table_info('${tableName}')`).all();
|
|
1666
|
-
}
|
|
1667
|
-
const wtInfo = wtTableInfo("decisions");
|
|
1668
|
-
const hasWtDecisions = wtInfo.length > 0;
|
|
1669
|
-
const hasMadeBy = wtInfo.some((col) => col["name"] === "made_by");
|
|
1670
|
-
// ADR-011: worktree may predate schema v16/v17. For missing columns we
|
|
1671
|
-
// fall through to the main DB's existing value (not a literal default)
|
|
1672
|
-
// so reconcile never silently clears state the main tree has recorded.
|
|
1673
|
-
const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
|
|
1674
|
-
const wtRequirementInfo = wtTableInfo("requirements");
|
|
1675
|
-
const hasWtRequirements = wtRequirementInfo.length > 0;
|
|
1676
|
-
const wtMilestoneInfo = wtTableInfo("milestones");
|
|
1677
|
-
const hasWtMilestones = wtMilestoneInfo.length > 0;
|
|
1678
|
-
const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
|
|
1679
|
-
const wtSliceInfo = wtTableInfo("slices");
|
|
1680
|
-
const hasWtSlices = wtSliceInfo.length > 0;
|
|
1681
|
-
const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
|
|
1682
|
-
const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
|
|
1683
|
-
const hasSliceTargetRepositories = wtSliceInfo.some((col) => col["name"] === "target_repositories");
|
|
1684
|
-
const wtTaskInfo = wtTableInfo("tasks");
|
|
1685
|
-
const hasWtTasks = wtTaskInfo.length > 0;
|
|
1686
|
-
const hasTaskTargetRepositories = wtTaskInfo.some((col) => col["name"] === "target_repositories");
|
|
1687
|
-
const hasBlockerSource = wtTaskInfo.some((col) => col["name"] === "blocker_source");
|
|
1688
|
-
const hasEscalationPending = wtTaskInfo.some((col) => col["name"] === "escalation_pending");
|
|
1689
|
-
const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
|
|
1690
|
-
const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
|
|
1691
|
-
const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
|
|
1692
|
-
const wtArtifactInfo = wtTableInfo("artifacts");
|
|
1693
|
-
const hasWtArtifacts = wtArtifactInfo.length > 0;
|
|
1694
|
-
const wtMemoryInfo = wtTableInfo("memories");
|
|
1695
|
-
const hasWtMemories = wtMemoryInfo.length > 0;
|
|
1696
|
-
const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
|
|
1697
|
-
const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
|
|
1698
|
-
const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
|
|
1699
|
-
const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
|
|
1700
|
-
const hasWtReplanHistory = wtTableInfo("replan_history").length > 0;
|
|
1701
|
-
const hasWtAssessments = wtTableInfo("assessments").length > 0;
|
|
1702
|
-
const hasWtQualityGates = wtTableInfo("quality_gates").length > 0;
|
|
1703
|
-
const hasWtSliceDependencies = wtTableInfo("slice_dependencies").length > 0;
|
|
1704
|
-
const hasWtVerificationEvidence = wtTableInfo("verification_evidence").length > 0;
|
|
1705
|
-
const hasWtGateRuns = wtTableInfo("gate_runs").length > 0;
|
|
1706
|
-
const hasWtMilestoneCommitAttributions = wtTableInfo("milestone_commit_attributions").length > 0;
|
|
1707
|
-
if (hasWtDecisions) {
|
|
1708
|
-
const decConf = adapter.prepare(`SELECT m.id FROM decisions m INNER JOIN wt.decisions w ON m.id = w.id WHERE m.decision != w.decision OR m.choice != w.choice OR m.rationale != w.rationale OR ${hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"} OR m.superseded_by IS NOT w.superseded_by`).all();
|
|
1709
|
-
for (const row of decConf)
|
|
1710
|
-
conflicts.push(`decision ${row["id"]}: modified in both`);
|
|
1711
|
-
}
|
|
1712
|
-
if (hasWtRequirements) {
|
|
1713
|
-
const reqConf = adapter.prepare(`SELECT m.id FROM requirements m INNER JOIN wt.requirements w ON m.id = w.id WHERE m.description != w.description OR m.status != w.status OR m.notes != w.notes OR m.superseded_by IS NOT w.superseded_by`).all();
|
|
1714
|
-
for (const row of reqConf)
|
|
1715
|
-
conflicts.push(`requirement ${row["id"]}: modified in both`);
|
|
1716
|
-
}
|
|
1717
|
-
const merged = {
|
|
1718
|
-
decisions: 0,
|
|
1719
|
-
requirements: 0,
|
|
1720
|
-
artifacts: 0,
|
|
1721
|
-
milestones: 0,
|
|
1722
|
-
slices: 0,
|
|
1723
|
-
tasks: 0,
|
|
1724
|
-
memories: 0,
|
|
1725
|
-
replan_history: 0,
|
|
1726
|
-
assessments: 0,
|
|
1727
|
-
quality_gates: 0,
|
|
1728
|
-
slice_dependencies: 0,
|
|
1729
|
-
verification_evidence: 0,
|
|
1730
|
-
gate_runs: 0,
|
|
1731
|
-
milestone_commit_attributions: 0,
|
|
1732
|
-
};
|
|
1733
|
-
const sliceTargetRepositoriesSql = hasSliceTargetRepositories
|
|
1734
|
-
? `CASE
|
|
1735
|
-
WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
|
|
1736
|
-
THEN m.target_repositories
|
|
1737
|
-
ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
|
|
1738
|
-
END`
|
|
1739
|
-
: "COALESCE(m.target_repositories, '[]')";
|
|
1740
|
-
const taskTargetRepositoriesSql = hasTaskTargetRepositories
|
|
1741
|
-
? `CASE
|
|
1742
|
-
WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
|
|
1743
|
-
THEN m.target_repositories
|
|
1744
|
-
ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
|
|
1745
|
-
END`
|
|
1746
|
-
: "COALESCE(m.target_repositories, '[]')";
|
|
1747
|
-
adapter.exec("BEGIN");
|
|
1748
|
-
try {
|
|
1749
|
-
// Join the target decisions so we can prefer an existing main.source
|
|
1750
|
-
// when the worktree predates v16 — otherwise a write-through reconcile
|
|
1751
|
-
// would clobber 'escalation'-sourced decisions with the literal default.
|
|
1752
|
-
if (hasWtDecisions) {
|
|
1753
|
-
merged.decisions = countChanges(adapter.prepare(`
|
|
1754
|
-
INSERT INTO decisions (
|
|
1755
|
-
id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by
|
|
1756
|
-
)
|
|
1757
|
-
SELECT w.id, w.when_context, w.scope, w.decision, w.choice, w.rationale, w.revisable, ${hasMadeBy ? "w.made_by" : "COALESCE(m.made_by, 'agent')"}, ${hasDecisionSource ? "w.source" : "COALESCE(m.source, 'discussion')"}, w.superseded_by
|
|
1758
|
-
FROM wt.decisions w
|
|
1759
|
-
LEFT JOIN decisions m ON m.id = w.id
|
|
1760
|
-
WHERE true
|
|
1761
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1762
|
-
when_context = excluded.when_context,
|
|
1763
|
-
scope = excluded.scope,
|
|
1764
|
-
decision = excluded.decision,
|
|
1765
|
-
choice = excluded.choice,
|
|
1766
|
-
rationale = excluded.rationale,
|
|
1767
|
-
revisable = excluded.revisable,
|
|
1768
|
-
made_by = excluded.made_by,
|
|
1769
|
-
source = excluded.source,
|
|
1770
|
-
superseded_by = excluded.superseded_by
|
|
1771
|
-
`).run());
|
|
1772
|
-
}
|
|
1773
|
-
if (hasWtRequirements) {
|
|
1774
|
-
merged.requirements = countChanges(adapter.prepare(`
|
|
1775
|
-
INSERT OR REPLACE INTO requirements (
|
|
1776
|
-
id, class, status, description, why, source, primary_owner,
|
|
1777
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
1778
|
-
)
|
|
1779
|
-
SELECT id, class, status, description, why, source, primary_owner,
|
|
1780
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
1781
|
-
FROM wt.requirements
|
|
1782
|
-
`).run());
|
|
1783
|
-
}
|
|
1784
|
-
// Always recompute artifact hashes from the content being merged. Older
|
|
1785
|
-
// worktree DBs may not have content_hash at all, and migrated old DBs can
|
|
1786
|
-
// carry stale default/null hashes after their content changed.
|
|
1787
|
-
if (hasWtArtifacts) {
|
|
1788
|
-
const artifactRows = adapter.prepare(`
|
|
1789
|
-
SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
1790
|
-
FROM wt.artifacts
|
|
1791
|
-
`).all();
|
|
1792
|
-
const artifactStmt = adapter.prepare(`
|
|
1793
|
-
INSERT OR REPLACE INTO artifacts (
|
|
1794
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
1795
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1796
|
-
`);
|
|
1797
|
-
for (const row of artifactRows) {
|
|
1798
|
-
const fullContent = String(row["full_content"] ?? "");
|
|
1799
|
-
merged.artifacts += countChanges(artifactStmt.run(row["path"], row["artifact_type"], row["milestone_id"] ?? null, row["slice_id"] ?? null, row["task_id"] ?? null, fullContent, row["imported_at"], createHash("sha256").update(fullContent).digest("hex")));
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
// Merge milestones — worktree may have updated status/planning fields.
|
|
1803
|
-
// Never downgrade status: complete > active > pre-planning (#4372).
|
|
1804
|
-
// A stale worktree may carry an older 'active' status for a milestone
|
|
1805
|
-
// that the main DB has already marked 'complete'; preserve the higher status.
|
|
1806
|
-
if (hasWtMilestones) {
|
|
1807
|
-
merged.milestones = countChanges(adapter.prepare(`
|
|
1808
|
-
INSERT OR REPLACE INTO milestones (
|
|
1809
|
-
id, title, status, depends_on, created_at, completed_at,
|
|
1810
|
-
vision, success_criteria, key_risks, proof_strategy,
|
|
1811
|
-
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
1812
|
-
definition_of_done, requirement_coverage, boundary_map_markdown, sequence
|
|
1813
|
-
)
|
|
1814
|
-
SELECT w.id, w.title,
|
|
1815
|
-
CASE
|
|
1816
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1817
|
-
THEN m.status ELSE w.status
|
|
1818
|
-
END,
|
|
1819
|
-
w.depends_on,
|
|
1820
|
-
CASE
|
|
1821
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1822
|
-
THEN m.created_at ELSE w.created_at
|
|
1823
|
-
END,
|
|
1824
|
-
CASE
|
|
1825
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1826
|
-
THEN m.completed_at ELSE w.completed_at
|
|
1827
|
-
END,
|
|
1828
|
-
w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
|
|
1829
|
-
w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
|
|
1830
|
-
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
|
|
1831
|
-
${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
|
|
1832
|
-
FROM wt.milestones w
|
|
1833
|
-
LEFT JOIN milestones m ON m.id = w.id
|
|
1834
|
-
`).run());
|
|
1835
|
-
}
|
|
1836
|
-
// Merge slices — preserve worktree progress but never downgrade completed status (#2558).
|
|
1837
|
-
// ADR-011 Phase 1: carry is_sketch + sketch_scope so reconcile doesn't
|
|
1838
|
-
// silently clear sketch metadata. When the worktree predates v16,
|
|
1839
|
-
// fall back to the main DB's existing value rather than a literal 0/''.
|
|
1840
|
-
if (hasWtSlices) {
|
|
1841
|
-
merged.slices = countChanges(adapter.prepare(`
|
|
1842
|
-
INSERT OR REPLACE INTO slices (
|
|
1843
|
-
milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
|
|
1844
|
-
full_summary_md, full_uat_md, goal, success_criteria, proof_level,
|
|
1845
|
-
integration_closure, observability_impact, target_repositories, sequence, replan_triggered_at,
|
|
1846
|
-
is_sketch, sketch_scope
|
|
1847
|
-
)
|
|
1848
|
-
SELECT w.milestone_id, w.id, w.title,
|
|
1849
|
-
CASE
|
|
1850
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1851
|
-
THEN m.status ELSE w.status
|
|
1852
|
-
END,
|
|
1853
|
-
w.risk, w.depends, w.demo, w.created_at,
|
|
1854
|
-
CASE
|
|
1855
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1856
|
-
THEN m.completed_at ELSE w.completed_at
|
|
1857
|
-
END,
|
|
1858
|
-
w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
|
|
1859
|
-
w.integration_closure, w.observability_impact,
|
|
1860
|
-
${sliceTargetRepositoriesSql},
|
|
1861
|
-
w.sequence, w.replan_triggered_at,
|
|
1862
|
-
${hasIsSketch ? "w.is_sketch" : "COALESCE(m.is_sketch, 0)"},
|
|
1863
|
-
${hasSketchScope ? "w.sketch_scope" : "COALESCE(m.sketch_scope, '')"}
|
|
1864
|
-
FROM wt.slices w
|
|
1865
|
-
LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
|
|
1866
|
-
`).run());
|
|
1867
|
-
}
|
|
1868
|
-
// Merge tasks — preserve execution results, never downgrade completed status (#2558).
|
|
1869
|
-
// ADR-011 P2: carry blocker_source + escalation_* columns so worktree reconcile
|
|
1870
|
-
// doesn't silently clear escalation state back to defaults.
|
|
1871
|
-
if (hasWtTasks) {
|
|
1872
|
-
merged.tasks = countChanges(adapter.prepare(`
|
|
1873
|
-
INSERT OR REPLACE INTO tasks (
|
|
1874
|
-
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
1875
|
-
verification_result, duration, completed_at, blocker_discovered,
|
|
1876
|
-
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
1877
|
-
description, estimate, files, verify, inputs, expected_output,
|
|
1878
|
-
observability_impact, full_plan_md, target_repositories, sequence,
|
|
1879
|
-
blocker_source, escalation_pending, escalation_awaiting_review,
|
|
1880
|
-
escalation_artifact_path, escalation_override_applied_at
|
|
1881
|
-
)
|
|
1882
|
-
SELECT w.milestone_id, w.slice_id, w.id, w.title,
|
|
1883
|
-
CASE
|
|
1884
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1885
|
-
THEN m.status ELSE w.status
|
|
1886
|
-
END,
|
|
1887
|
-
w.one_liner, w.narrative,
|
|
1888
|
-
w.verification_result, w.duration,
|
|
1889
|
-
CASE
|
|
1890
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
1891
|
-
THEN m.completed_at ELSE w.completed_at
|
|
1892
|
-
END,
|
|
1893
|
-
w.blocker_discovered,
|
|
1894
|
-
w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
|
|
1895
|
-
w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
|
|
1896
|
-
w.observability_impact, w.full_plan_md,
|
|
1897
|
-
${taskTargetRepositoriesSql},
|
|
1898
|
-
w.sequence,
|
|
1899
|
-
${hasBlockerSource ? "w.blocker_source" : "COALESCE(m.blocker_source, '')"},
|
|
1900
|
-
${hasEscalationPending ? "w.escalation_pending" : "COALESCE(m.escalation_pending, 0)"},
|
|
1901
|
-
${hasEscalationAwaiting ? "w.escalation_awaiting_review" : "COALESCE(m.escalation_awaiting_review, 0)"},
|
|
1902
|
-
${hasEscalationArtifact ? "w.escalation_artifact_path" : "m.escalation_artifact_path"},
|
|
1903
|
-
${hasEscalationOverride ? "w.escalation_override_applied_at" : "m.escalation_override_applied_at"}
|
|
1904
|
-
FROM wt.tasks w
|
|
1905
|
-
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
1906
|
-
`).run());
|
|
1907
|
-
}
|
|
1908
|
-
// Merge memories — keep worktree-learned insights.
|
|
1909
|
-
// V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
|
|
1910
|
-
// column the wt may not yet have (older worktree DB), fall back to the
|
|
1911
|
-
// main DB's existing value via LEFT JOIN so reconcile never silently
|
|
1912
|
-
// resets these fields to defaults on rows that already had them.
|
|
1913
|
-
if (hasWtMemories) {
|
|
1914
|
-
merged.memories = countChanges(adapter.prepare(`
|
|
1915
|
-
INSERT OR REPLACE INTO memories (
|
|
1916
|
-
seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
1917
|
-
created_at, updated_at, superseded_by, hit_count,
|
|
1918
|
-
scope, tags, structured_fields, last_hit_at
|
|
1919
|
-
)
|
|
1920
|
-
SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
|
|
1921
|
-
w.created_at, w.updated_at, w.superseded_by, w.hit_count,
|
|
1922
|
-
${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
|
|
1923
|
-
${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
|
|
1924
|
-
${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
|
|
1925
|
-
${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
|
|
1926
|
-
FROM wt.memories w
|
|
1927
|
-
LEFT JOIN memories m ON m.id = w.id
|
|
1928
|
-
`).run());
|
|
1929
|
-
}
|
|
1930
|
-
if (hasWtReplanHistory) {
|
|
1931
|
-
merged.replan_history = countChanges(adapter.prepare(`
|
|
1932
|
-
INSERT INTO replan_history (
|
|
1933
|
-
milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
|
|
1934
|
-
)
|
|
1935
|
-
SELECT w.milestone_id, w.slice_id, w.task_id, w.summary, w.previous_artifact_path, w.replacement_artifact_path, w.created_at
|
|
1936
|
-
FROM wt.replan_history w
|
|
1937
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
1938
|
-
AND NOT EXISTS (
|
|
1939
|
-
SELECT 1 FROM replan_history m
|
|
1940
|
-
WHERE m.milestone_id = w.milestone_id
|
|
1941
|
-
AND m.slice_id IS w.slice_id
|
|
1942
|
-
AND m.task_id IS w.task_id
|
|
1943
|
-
AND m.summary = w.summary
|
|
1944
|
-
AND m.previous_artifact_path IS w.previous_artifact_path
|
|
1945
|
-
AND m.replacement_artifact_path IS w.replacement_artifact_path
|
|
1946
|
-
)
|
|
1947
|
-
`).run());
|
|
1948
|
-
}
|
|
1949
|
-
if (hasWtAssessments) {
|
|
1950
|
-
merged.assessments = countChanges(adapter.prepare(`
|
|
1951
|
-
INSERT OR REPLACE INTO assessments (
|
|
1952
|
-
path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
|
|
1953
|
-
)
|
|
1954
|
-
SELECT w.path, w.milestone_id, w.slice_id, w.task_id, w.status, w.scope, w.full_content, w.created_at
|
|
1955
|
-
FROM wt.assessments w
|
|
1956
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
1957
|
-
`).run());
|
|
1958
|
-
}
|
|
1959
|
-
if (hasWtQualityGates) {
|
|
1960
|
-
merged.quality_gates = countChanges(adapter.prepare(`
|
|
1961
|
-
INSERT OR REPLACE INTO quality_gates (
|
|
1962
|
-
milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
|
|
1963
|
-
)
|
|
1964
|
-
SELECT w.milestone_id, w.slice_id, w.gate_id, w.scope, COALESCE(w.task_id, ''), w.status, w.verdict, w.rationale, w.findings, w.evaluated_at
|
|
1965
|
-
FROM wt.quality_gates w
|
|
1966
|
-
WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
|
|
1967
|
-
`).run());
|
|
1968
|
-
}
|
|
1969
|
-
if (hasWtSliceDependencies) {
|
|
1970
|
-
merged.slice_dependencies = countChanges(adapter.prepare(`
|
|
1971
|
-
INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id)
|
|
1972
|
-
SELECT w.milestone_id, w.slice_id, w.depends_on_slice_id
|
|
1973
|
-
FROM wt.slice_dependencies w
|
|
1974
|
-
WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
|
|
1975
|
-
AND EXISTS (SELECT 1 FROM slices d WHERE d.milestone_id = w.milestone_id AND d.id = w.depends_on_slice_id)
|
|
1976
|
-
`).run());
|
|
1977
|
-
}
|
|
1978
|
-
// Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
|
|
1979
|
-
if (hasWtVerificationEvidence) {
|
|
1980
|
-
merged.verification_evidence = countChanges(adapter.prepare(`
|
|
1981
|
-
INSERT OR IGNORE INTO verification_evidence (
|
|
1982
|
-
task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
|
|
1983
|
-
)
|
|
1984
|
-
SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
|
|
1985
|
-
FROM wt.verification_evidence
|
|
1986
|
-
`).run());
|
|
1987
|
-
}
|
|
1988
|
-
if (hasWtGateRuns) {
|
|
1989
|
-
merged.gate_runs = countChanges(adapter.prepare(`
|
|
1990
|
-
INSERT INTO gate_runs (
|
|
1991
|
-
trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
|
|
1992
|
-
outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
|
|
1993
|
-
)
|
|
1994
|
-
SELECT w.trace_id, w.turn_id, w.gate_id, w.gate_type, w.unit_type, w.unit_id, w.milestone_id, w.slice_id, w.task_id,
|
|
1995
|
-
w.outcome, w.failure_class, w.rationale, w.findings, w.attempt, w.max_attempts, w.retryable, w.evaluated_at
|
|
1996
|
-
FROM wt.gate_runs w
|
|
1997
|
-
WHERE NOT EXISTS (
|
|
1998
|
-
SELECT 1 FROM gate_runs m
|
|
1999
|
-
WHERE m.trace_id = w.trace_id
|
|
2000
|
-
AND m.turn_id = w.turn_id
|
|
2001
|
-
AND m.gate_id = w.gate_id
|
|
2002
|
-
AND m.attempt = w.attempt
|
|
2003
|
-
AND m.evaluated_at = w.evaluated_at
|
|
2004
|
-
)
|
|
2005
|
-
`).run());
|
|
2006
|
-
}
|
|
2007
|
-
if (hasWtMilestoneCommitAttributions) {
|
|
2008
|
-
merged.milestone_commit_attributions = countChanges(adapter.prepare(`
|
|
2009
|
-
INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2010
|
-
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2011
|
-
)
|
|
2012
|
-
SELECT w.commit_sha, w.milestone_id, w.slice_id, w.task_id, w.source, w.confidence, w.files_json, w.created_at
|
|
2013
|
-
FROM wt.milestone_commit_attributions w
|
|
2014
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
2015
|
-
`).run());
|
|
2016
|
-
}
|
|
2017
|
-
adapter.exec("COMMIT");
|
|
2018
|
-
}
|
|
2019
|
-
catch (txErr) {
|
|
2020
|
-
try {
|
|
2021
|
-
adapter.exec("ROLLBACK");
|
|
2022
|
-
}
|
|
2023
|
-
catch (e) {
|
|
2024
|
-
logWarning("db", `rollback failed: ${e.message}`);
|
|
2025
|
-
}
|
|
2026
|
-
throw txErr;
|
|
2027
|
-
}
|
|
2028
|
-
return { ...merged, conflicts };
|
|
2029
|
-
}
|
|
2030
|
-
finally {
|
|
2031
|
-
try {
|
|
2032
|
-
adapter.exec("DETACH DATABASE wt");
|
|
2033
|
-
}
|
|
2034
|
-
catch (e) {
|
|
2035
|
-
logWarning("db", `detach worktree DB failed: ${e.message}`);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
catch (err) {
|
|
2040
|
-
logError("db", "worktree DB reconciliation failed", { error: err.message });
|
|
2041
|
-
return { ...zero, conflicts };
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
// ─── Replan & Assessment Helpers ──────────────────────────────────────────
|
|
2045
596
|
export function insertReplanHistory(entry) {
|
|
2046
|
-
if (!
|
|
597
|
+
if (!getDbOrNull())
|
|
2047
598
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2048
599
|
// INSERT OR REPLACE: idempotent on (milestone_id, slice_id, task_id) via schema v11 unique index.
|
|
2049
600
|
// Retrying the same replan silently updates summary instead of accumulating duplicate rows.
|
|
2050
|
-
|
|
601
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO replan_history (milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at)
|
|
2051
602
|
VALUES (:milestone_id, :slice_id, :task_id, :summary, :previous_artifact_path, :replacement_artifact_path, :created_at)`).run({
|
|
2052
603
|
":milestone_id": entry.milestoneId,
|
|
2053
604
|
":slice_id": entry.sliceId ?? null,
|
|
@@ -2059,12 +610,12 @@ export function insertReplanHistory(entry) {
|
|
|
2059
610
|
});
|
|
2060
611
|
}
|
|
2061
612
|
export function insertAssessment(entry) {
|
|
2062
|
-
if (!
|
|
613
|
+
if (!getDbOrNull())
|
|
2063
614
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2064
615
|
// Idempotent: PRIMARY KEY is `path`, which is deterministic given (milestone_id, scope) per
|
|
2065
616
|
// the artifact-path resolver. Retrying the same reassess-roadmap silently overwrites the row
|
|
2066
617
|
// instead of accumulating duplicates.
|
|
2067
|
-
|
|
618
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
|
2068
619
|
VALUES (:path, :milestone_id, :slice_id, :task_id, :status, :scope, :full_content, :created_at)`).run({
|
|
2069
620
|
":path": entry.path,
|
|
2070
621
|
":milestone_id": entry.milestoneId,
|
|
@@ -2077,57 +628,57 @@ export function insertAssessment(entry) {
|
|
|
2077
628
|
});
|
|
2078
629
|
}
|
|
2079
630
|
export function deleteAssessmentByScope(milestoneId, scope) {
|
|
2080
|
-
if (!
|
|
631
|
+
if (!getDbOrNull())
|
|
2081
632
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2082
|
-
|
|
633
|
+
getDbOrNull().prepare(`DELETE FROM assessments WHERE milestone_id = :mid AND scope = :scope`).run({ ":mid": milestoneId, ":scope": scope });
|
|
2083
634
|
}
|
|
2084
635
|
export function deleteVerificationEvidence(milestoneId, sliceId, taskId) {
|
|
2085
|
-
if (!
|
|
636
|
+
if (!getDbOrNull())
|
|
2086
637
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2087
|
-
|
|
638
|
+
getDbOrNull().prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2088
639
|
}
|
|
2089
640
|
export function deleteTask(milestoneId, sliceId, taskId) {
|
|
2090
|
-
if (!
|
|
641
|
+
if (!getDbOrNull())
|
|
2091
642
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2092
643
|
transaction(() => {
|
|
2093
644
|
// Must delete verification_evidence first (FK constraint)
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
645
|
+
getDbOrNull().prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
646
|
+
getDbOrNull().prepare(`DELETE FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
647
|
+
getDbOrNull().prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2097
648
|
});
|
|
2098
649
|
}
|
|
2099
650
|
export function deleteSlice(milestoneId, sliceId) {
|
|
2100
|
-
if (!
|
|
651
|
+
if (!getDbOrNull())
|
|
2101
652
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2102
653
|
transaction(() => {
|
|
2103
654
|
// Cascade-style manual deletion: evidence → tasks → dependencies → slice
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
655
|
+
getDbOrNull().prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
656
|
+
getDbOrNull().prepare(`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
657
|
+
getDbOrNull().prepare(`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
658
|
+
getDbOrNull().prepare(`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
659
|
+
getDbOrNull().prepare(`DELETE FROM slices WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2109
660
|
});
|
|
2110
661
|
}
|
|
2111
662
|
export function deleteMilestone(milestoneId) {
|
|
2112
|
-
if (!
|
|
663
|
+
if (!getDbOrNull())
|
|
2113
664
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2114
665
|
transaction(() => {
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
666
|
+
getDbOrNull().prepare(`DELETE FROM verification_evidence WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
667
|
+
getDbOrNull().prepare(`DELETE FROM quality_gates WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
668
|
+
getDbOrNull().prepare(`DELETE FROM gate_runs WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
669
|
+
getDbOrNull().prepare(`DELETE FROM tasks WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
670
|
+
getDbOrNull().prepare(`DELETE FROM slice_dependencies WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
671
|
+
getDbOrNull().prepare(`DELETE FROM slices WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
672
|
+
getDbOrNull().prepare(`DELETE FROM replan_history WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
673
|
+
getDbOrNull().prepare(`DELETE FROM assessments WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
674
|
+
getDbOrNull().prepare(`DELETE FROM artifacts WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
675
|
+
getDbOrNull().prepare(`DELETE FROM milestone_commit_attributions WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
676
|
+
getDbOrNull().prepare(`DELETE FROM milestone_leases WHERE milestone_id = :mid`).run({ ":mid": milestoneId });
|
|
677
|
+
getDbOrNull().prepare(`DELETE FROM milestones WHERE id = :mid`).run({ ":mid": milestoneId });
|
|
2127
678
|
});
|
|
2128
679
|
}
|
|
2129
680
|
export function updateSliceFields(milestoneId, sliceId, fields) {
|
|
2130
|
-
if (!
|
|
681
|
+
if (!getDbOrNull())
|
|
2131
682
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2132
683
|
const SLICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
2133
684
|
if (fields.depends !== undefined) {
|
|
@@ -2136,7 +687,7 @@ export function updateSliceFields(milestoneId, sliceId, fields) {
|
|
|
2136
687
|
throw new GSDError(GSD_STALE_STATE, `updateSliceFields: depends element "${invalidDep}" is not a valid slice ID`);
|
|
2137
688
|
}
|
|
2138
689
|
}
|
|
2139
|
-
|
|
690
|
+
getDbOrNull().prepare(`UPDATE slices SET
|
|
2140
691
|
title = COALESCE(:title, title),
|
|
2141
692
|
risk = COALESCE(:risk, risk),
|
|
2142
693
|
depends = COALESCE(:depends, depends),
|
|
@@ -2150,34 +701,11 @@ export function updateSliceFields(milestoneId, sliceId, fields) {
|
|
|
2150
701
|
":demo": fields.demo ?? null,
|
|
2151
702
|
});
|
|
2152
703
|
}
|
|
2153
|
-
export function getReplanHistory(milestoneId, sliceId) {
|
|
2154
|
-
if (!currentDb)
|
|
2155
|
-
return [];
|
|
2156
|
-
if (sliceId) {
|
|
2157
|
-
return currentDb.prepare(`SELECT * FROM replan_history WHERE milestone_id = :mid AND slice_id = :sid ORDER BY created_at DESC`).all({ ":mid": milestoneId, ":sid": sliceId });
|
|
2158
|
-
}
|
|
2159
|
-
return currentDb.prepare(`SELECT * FROM replan_history WHERE milestone_id = :mid ORDER BY created_at DESC`).all({ ":mid": milestoneId });
|
|
2160
|
-
}
|
|
2161
|
-
export function getAssessment(path) {
|
|
2162
|
-
if (!currentDb)
|
|
2163
|
-
return null;
|
|
2164
|
-
const row = currentDb.prepare(`SELECT * FROM assessments WHERE path = :path`).get({ ":path": path });
|
|
2165
|
-
return row ?? null;
|
|
2166
|
-
}
|
|
2167
|
-
export function getLatestAssessmentByScope(milestoneId, scope) {
|
|
2168
|
-
if (!currentDb)
|
|
2169
|
-
return null;
|
|
2170
|
-
const row = currentDb.prepare(`SELECT * FROM assessments
|
|
2171
|
-
WHERE milestone_id = :mid AND scope = :scope
|
|
2172
|
-
ORDER BY created_at DESC
|
|
2173
|
-
LIMIT 1`).get({ ":mid": milestoneId, ":scope": scope });
|
|
2174
|
-
return row ?? null;
|
|
2175
|
-
}
|
|
2176
704
|
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
2177
705
|
export function insertGateRow(g) {
|
|
2178
|
-
if (!
|
|
706
|
+
if (!getDbOrNull())
|
|
2179
707
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2180
|
-
|
|
708
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO quality_gates (milestone_id, slice_id, gate_id, scope, task_id, status)
|
|
2181
709
|
VALUES (:mid, :sid, :gid, :scope, :tid, :status)`).run({
|
|
2182
710
|
":mid": g.milestoneId,
|
|
2183
711
|
":sid": g.sliceId,
|
|
@@ -2188,10 +716,10 @@ export function insertGateRow(g) {
|
|
|
2188
716
|
});
|
|
2189
717
|
}
|
|
2190
718
|
export function saveGateResult(g) {
|
|
2191
|
-
if (!
|
|
719
|
+
if (!getDbOrNull())
|
|
2192
720
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2193
721
|
const evaluatedAt = new Date().toISOString();
|
|
2194
|
-
const result =
|
|
722
|
+
const result = getDbOrNull().prepare(`UPDATE quality_gates
|
|
2195
723
|
SET status = 'complete', verdict = :verdict, rationale = :rationale,
|
|
2196
724
|
findings = :findings, evaluated_at = :evaluated_at
|
|
2197
725
|
WHERE milestone_id = :mid AND slice_id = :sid AND gate_id = :gid
|
|
@@ -2231,32 +759,10 @@ export function saveGateResult(g) {
|
|
|
2231
759
|
evaluatedAt,
|
|
2232
760
|
});
|
|
2233
761
|
}
|
|
2234
|
-
export function getPendingGates(milestoneId, sliceId, scope) {
|
|
2235
|
-
if (!currentDb)
|
|
2236
|
-
return [];
|
|
2237
|
-
const sql = scope
|
|
2238
|
-
? `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND scope = :scope AND status = 'pending'`
|
|
2239
|
-
: `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'`;
|
|
2240
|
-
const params = { ":mid": milestoneId, ":sid": sliceId };
|
|
2241
|
-
if (scope)
|
|
2242
|
-
params[":scope"] = scope;
|
|
2243
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2244
|
-
}
|
|
2245
|
-
export function getGateResults(milestoneId, sliceId, scope) {
|
|
2246
|
-
if (!currentDb)
|
|
2247
|
-
return [];
|
|
2248
|
-
const sql = scope
|
|
2249
|
-
? `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND scope = :scope`
|
|
2250
|
-
: `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid`;
|
|
2251
|
-
const params = { ":mid": milestoneId, ":sid": sliceId };
|
|
2252
|
-
if (scope)
|
|
2253
|
-
params[":scope"] = scope;
|
|
2254
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2255
|
-
}
|
|
2256
762
|
export function markAllGatesOmitted(milestoneId, sliceId) {
|
|
2257
|
-
if (!
|
|
763
|
+
if (!getDbOrNull())
|
|
2258
764
|
return;
|
|
2259
|
-
|
|
765
|
+
getDbOrNull().prepare(`UPDATE quality_gates SET status = 'complete', verdict = 'omitted', evaluated_at = :now
|
|
2260
766
|
WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'`).run({
|
|
2261
767
|
":mid": milestoneId,
|
|
2262
768
|
":sid": sliceId,
|
|
@@ -2264,7 +770,7 @@ export function markAllGatesOmitted(milestoneId, sliceId) {
|
|
|
2264
770
|
});
|
|
2265
771
|
}
|
|
2266
772
|
export function markPendingGatesOmittedForTurn(milestoneId, sliceId, turn) {
|
|
2267
|
-
if (!
|
|
773
|
+
if (!getDbOrNull())
|
|
2268
774
|
return;
|
|
2269
775
|
const gateIds = [...getGateIdsForTurn(turn)];
|
|
2270
776
|
if (gateIds.length === 0)
|
|
@@ -2278,61 +784,14 @@ export function markPendingGatesOmittedForTurn(milestoneId, sliceId, turn) {
|
|
|
2278
784
|
gateIds.forEach((id, index) => {
|
|
2279
785
|
params[`:gid${index}`] = id;
|
|
2280
786
|
});
|
|
2281
|
-
|
|
787
|
+
getDbOrNull().prepare(`UPDATE quality_gates SET status = 'complete', verdict = 'omitted', evaluated_at = :now
|
|
2282
788
|
WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'
|
|
2283
789
|
AND gate_id IN (${placeholders})`).run(params);
|
|
2284
790
|
}
|
|
2285
|
-
export function getPendingSliceGateCount(milestoneId, sliceId) {
|
|
2286
|
-
if (!currentDb)
|
|
2287
|
-
return 0;
|
|
2288
|
-
const row = currentDb.prepare(`SELECT COUNT(*) as cnt FROM quality_gates
|
|
2289
|
-
WHERE milestone_id = :mid AND slice_id = :sid AND scope = 'slice' AND status = 'pending'`).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
2290
|
-
return row ? row["cnt"] : 0;
|
|
2291
|
-
}
|
|
2292
|
-
/**
|
|
2293
|
-
* Return pending gate rows owned by a specific workflow turn.
|
|
2294
|
-
*
|
|
2295
|
-
* Unlike `getPendingGates(..., scope)`, this filters by the registry's
|
|
2296
|
-
* `ownerTurn` metadata so callers can distinguish Q3/Q4 (owned by
|
|
2297
|
-
* gate-evaluate) from Q8 (owned by complete-slice) even though both are
|
|
2298
|
-
* scope:"slice". Pass `taskId` to narrow task-scoped results to one task.
|
|
2299
|
-
*/
|
|
2300
|
-
export function getPendingGatesForTurn(milestoneId, sliceId, turn, taskId) {
|
|
2301
|
-
if (!currentDb)
|
|
2302
|
-
return [];
|
|
2303
|
-
const ids = getGateIdsForTurn(turn);
|
|
2304
|
-
if (ids.size === 0)
|
|
2305
|
-
return [];
|
|
2306
|
-
const idList = [...ids];
|
|
2307
|
-
const placeholders = idList.map((_, i) => `:gid${i}`).join(",");
|
|
2308
|
-
const params = {
|
|
2309
|
-
":mid": milestoneId,
|
|
2310
|
-
":sid": sliceId,
|
|
2311
|
-
};
|
|
2312
|
-
idList.forEach((id, i) => {
|
|
2313
|
-
params[`:gid${i}`] = id;
|
|
2314
|
-
});
|
|
2315
|
-
let sql = `SELECT * FROM quality_gates
|
|
2316
|
-
WHERE milestone_id = :mid AND slice_id = :sid
|
|
2317
|
-
AND status = 'pending'
|
|
2318
|
-
AND gate_id IN (${placeholders})`;
|
|
2319
|
-
if (taskId !== undefined) {
|
|
2320
|
-
sql += ` AND task_id = :tid`;
|
|
2321
|
-
params[":tid"] = taskId;
|
|
2322
|
-
}
|
|
2323
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2324
|
-
}
|
|
2325
|
-
/**
|
|
2326
|
-
* Count pending gates for a turn. Convenience wrapper used by state
|
|
2327
|
-
* derivation to decide whether a phase transition should pause.
|
|
2328
|
-
*/
|
|
2329
|
-
export function getPendingGateCountForTurn(milestoneId, sliceId, turn) {
|
|
2330
|
-
return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
|
|
2331
|
-
}
|
|
2332
791
|
export function insertGateRun(entry) {
|
|
2333
|
-
if (!
|
|
792
|
+
if (!getDbOrNull())
|
|
2334
793
|
return;
|
|
2335
|
-
|
|
794
|
+
getDbOrNull().prepare(`INSERT INTO gate_runs (
|
|
2336
795
|
trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
|
|
2337
796
|
outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
|
|
2338
797
|
) VALUES (
|
|
@@ -2359,9 +818,9 @@ export function insertGateRun(entry) {
|
|
|
2359
818
|
});
|
|
2360
819
|
}
|
|
2361
820
|
export function upsertTurnGitTransaction(entry) {
|
|
2362
|
-
if (!
|
|
821
|
+
if (!getDbOrNull())
|
|
2363
822
|
return;
|
|
2364
|
-
|
|
823
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO turn_git_transactions (
|
|
2365
824
|
trace_id, turn_id, unit_type, unit_id, stage, action, push, status, error, metadata_json, updated_at
|
|
2366
825
|
) VALUES (
|
|
2367
826
|
:trace_id, :turn_id, :unit_type, :unit_id, :stage, :action, :push, :status, :error, :metadata_json, :updated_at
|
|
@@ -2379,22 +838,11 @@ export function upsertTurnGitTransaction(entry) {
|
|
|
2379
838
|
":updated_at": entry.updatedAt,
|
|
2380
839
|
});
|
|
2381
840
|
}
|
|
2382
|
-
export function getMilestoneCommitAttributionShas(milestoneId) {
|
|
2383
|
-
if (!currentDb)
|
|
2384
|
-
return [];
|
|
2385
|
-
const rows = currentDb.prepare(`SELECT commit_sha
|
|
2386
|
-
FROM milestone_commit_attributions
|
|
2387
|
-
WHERE milestone_id = :mid
|
|
2388
|
-
ORDER BY created_at, commit_sha`).all({ ":mid": milestoneId });
|
|
2389
|
-
return rows
|
|
2390
|
-
.map((row) => typeof row["commit_sha"] === "string" ? row["commit_sha"] : "")
|
|
2391
|
-
.filter(Boolean);
|
|
2392
|
-
}
|
|
2393
841
|
export function recordMilestoneCommitAttribution(entry) {
|
|
2394
|
-
if (!
|
|
842
|
+
if (!getDbOrNull())
|
|
2395
843
|
return;
|
|
2396
844
|
transaction(() => {
|
|
2397
|
-
|
|
845
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2398
846
|
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2399
847
|
) VALUES (
|
|
2400
848
|
:commit_sha, :milestone_id, :slice_id, :task_id, :source, :confidence, :files_json, :created_at
|
|
@@ -2408,7 +856,7 @@ export function recordMilestoneCommitAttribution(entry) {
|
|
|
2408
856
|
":files_json": JSON.stringify(entry.files),
|
|
2409
857
|
":created_at": entry.createdAt,
|
|
2410
858
|
});
|
|
2411
|
-
|
|
859
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO audit_events (
|
|
2412
860
|
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2413
861
|
) VALUES (
|
|
2414
862
|
:event_id, :trace_id, :turn_id, :caused_by, :category, :type, :ts, :payload_json
|
|
@@ -2433,10 +881,10 @@ export function recordMilestoneCommitAttribution(entry) {
|
|
|
2433
881
|
});
|
|
2434
882
|
}
|
|
2435
883
|
export function insertAuditEvent(entry) {
|
|
2436
|
-
if (!
|
|
884
|
+
if (!getDbOrNull())
|
|
2437
885
|
return;
|
|
2438
886
|
transaction(() => {
|
|
2439
|
-
|
|
887
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO audit_events (
|
|
2440
888
|
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2441
889
|
) VALUES (
|
|
2442
890
|
:event_id, :trace_id, :turn_id, :caused_by, :category, :type, :ts, :payload_json
|
|
@@ -2451,14 +899,14 @@ export function insertAuditEvent(entry) {
|
|
|
2451
899
|
":payload_json": JSON.stringify(entry.payload ?? {}),
|
|
2452
900
|
});
|
|
2453
901
|
if (entry.turnId) {
|
|
2454
|
-
const row =
|
|
902
|
+
const row = getDbOrNull().prepare(`SELECT event_count, first_ts, last_ts
|
|
2455
903
|
FROM audit_turn_index
|
|
2456
904
|
WHERE trace_id = :trace_id AND turn_id = :turn_id`).get({
|
|
2457
905
|
":trace_id": entry.traceId,
|
|
2458
906
|
":turn_id": entry.turnId,
|
|
2459
907
|
});
|
|
2460
908
|
if (row) {
|
|
2461
|
-
|
|
909
|
+
getDbOrNull().prepare(`UPDATE audit_turn_index
|
|
2462
910
|
SET first_ts = CASE WHEN :ts < first_ts THEN :ts ELSE first_ts END,
|
|
2463
911
|
last_ts = CASE WHEN :ts > last_ts THEN :ts ELSE last_ts END,
|
|
2464
912
|
event_count = event_count + 1
|
|
@@ -2469,7 +917,7 @@ export function insertAuditEvent(entry) {
|
|
|
2469
917
|
});
|
|
2470
918
|
}
|
|
2471
919
|
else {
|
|
2472
|
-
|
|
920
|
+
getDbOrNull().prepare(`INSERT INTO audit_turn_index (trace_id, turn_id, first_ts, last_ts, event_count)
|
|
2473
921
|
VALUES (:trace_id, :turn_id, :first_ts, :last_ts, :event_count)`).run({
|
|
2474
922
|
":trace_id": entry.traceId,
|
|
2475
923
|
":turn_id": entry.turnId,
|
|
@@ -2489,40 +937,40 @@ export function insertAuditEvent(entry) {
|
|
|
2489
937
|
// add new behavior.
|
|
2490
938
|
/** Delete a decision row by id. Used by db-writer.ts rollback on disk-write failure. */
|
|
2491
939
|
export function deleteDecisionById(id) {
|
|
2492
|
-
if (!
|
|
940
|
+
if (!getDbOrNull())
|
|
2493
941
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2494
|
-
|
|
942
|
+
getDbOrNull().prepare("DELETE FROM decisions WHERE id = :id").run({ ":id": id });
|
|
2495
943
|
}
|
|
2496
944
|
/** Delete a requirement row by id. Used by db-writer.ts rollback on disk-write failure. */
|
|
2497
945
|
export function deleteRequirementById(id) {
|
|
2498
|
-
if (!
|
|
946
|
+
if (!getDbOrNull())
|
|
2499
947
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2500
|
-
|
|
948
|
+
getDbOrNull().prepare("DELETE FROM requirements WHERE id = :id").run({ ":id": id });
|
|
2501
949
|
}
|
|
2502
950
|
/** Delete an artifact row by path. Used by db-writer.ts rollback on disk-write failure. */
|
|
2503
951
|
export function deleteArtifactByPath(path) {
|
|
2504
|
-
if (!
|
|
952
|
+
if (!getDbOrNull())
|
|
2505
953
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2506
|
-
|
|
954
|
+
getDbOrNull().prepare("DELETE FROM artifacts WHERE path = :path").run({ ":path": path });
|
|
2507
955
|
}
|
|
2508
956
|
/**
|
|
2509
957
|
* Drop hierarchy rows in dependency order inside a transaction. Used by
|
|
2510
958
|
* `gsd recover --confirm` to rebuild engine state from markdown.
|
|
2511
959
|
*/
|
|
2512
960
|
export function clearEngineHierarchy() {
|
|
2513
|
-
if (!
|
|
961
|
+
if (!getDbOrNull())
|
|
2514
962
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2515
963
|
transaction(() => {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
964
|
+
getDbOrNull().exec("DELETE FROM verification_evidence");
|
|
965
|
+
getDbOrNull().exec("DELETE FROM quality_gates");
|
|
966
|
+
getDbOrNull().exec("DELETE FROM slice_dependencies");
|
|
967
|
+
getDbOrNull().exec("DELETE FROM assessments");
|
|
968
|
+
getDbOrNull().exec("DELETE FROM replan_history");
|
|
969
|
+
getDbOrNull().exec("DELETE FROM milestone_commit_attributions");
|
|
970
|
+
getDbOrNull().exec("DELETE FROM tasks");
|
|
971
|
+
getDbOrNull().exec("DELETE FROM slices");
|
|
972
|
+
getDbOrNull().exec("DELETE FROM milestone_leases");
|
|
973
|
+
getDbOrNull().exec("DELETE FROM milestones");
|
|
2526
974
|
});
|
|
2527
975
|
}
|
|
2528
976
|
/**
|
|
@@ -2532,9 +980,9 @@ export function clearEngineHierarchy() {
|
|
|
2532
980
|
* slice back to 'pending'.
|
|
2533
981
|
*/
|
|
2534
982
|
export function insertOrIgnoreSlice(args) {
|
|
2535
|
-
if (!
|
|
983
|
+
if (!getDbOrNull())
|
|
2536
984
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2537
|
-
|
|
985
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO slices (milestone_id, id, title, status, created_at)
|
|
2538
986
|
VALUES (:mid, :sid, :title, 'pending', :ts)`).run({
|
|
2539
987
|
":mid": args.milestoneId,
|
|
2540
988
|
":sid": args.sliceId,
|
|
@@ -2547,9 +995,9 @@ export function insertOrIgnoreSlice(args) {
|
|
|
2547
995
|
* Same rationale as `insertOrIgnoreSlice`.
|
|
2548
996
|
*/
|
|
2549
997
|
export function insertOrIgnoreTask(args) {
|
|
2550
|
-
if (!
|
|
998
|
+
if (!getDbOrNull())
|
|
2551
999
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2552
|
-
|
|
1000
|
+
getDbOrNull().prepare(`INSERT OR IGNORE INTO tasks (milestone_id, slice_id, id, title, status, created_at)
|
|
2553
1001
|
VALUES (:mid, :sid, :tid, :title, 'pending', :ts)`).run({
|
|
2554
1002
|
":mid": args.milestoneId,
|
|
2555
1003
|
":sid": args.sliceId,
|
|
@@ -2564,18 +1012,18 @@ export function insertOrIgnoreTask(args) {
|
|
|
2564
1012
|
* trigger via DB in addition to the on-disk REPLAN-TRIGGER.md marker.
|
|
2565
1013
|
*/
|
|
2566
1014
|
export function setSliceReplanTriggeredAt(milestoneId, sliceId, ts) {
|
|
2567
|
-
if (!
|
|
1015
|
+
if (!getDbOrNull())
|
|
2568
1016
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2569
|
-
|
|
1017
|
+
getDbOrNull().prepare("UPDATE slices SET replan_triggered_at = :ts WHERE milestone_id = :mid AND id = :sid").run({ ":ts": ts, ":mid": milestoneId, ":sid": sliceId });
|
|
2570
1018
|
}
|
|
2571
1019
|
/**
|
|
2572
1020
|
* INSERT OR REPLACE a quality_gates row. Used by milestone-validation-gates.ts
|
|
2573
1021
|
* to persist milestone-level (MV*) gate outcomes after validate-milestone runs.
|
|
2574
1022
|
*/
|
|
2575
1023
|
export function upsertQualityGate(g) {
|
|
2576
|
-
if (!
|
|
1024
|
+
if (!getDbOrNull())
|
|
2577
1025
|
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2578
|
-
|
|
1026
|
+
getDbOrNull().prepare(`INSERT OR REPLACE INTO quality_gates
|
|
2579
1027
|
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
2580
1028
|
VALUES (:mid, :sid, :gid, :scope, :tid, :status, :verdict, :rationale, :findings, :evaluated_at)`).run({
|
|
2581
1029
|
":mid": g.milestoneId,
|
|
@@ -2590,327 +1038,3 @@ export function upsertQualityGate(g) {
|
|
|
2590
1038
|
":evaluated_at": g.evaluatedAt,
|
|
2591
1039
|
});
|
|
2592
1040
|
}
|
|
2593
|
-
/**
|
|
2594
|
-
* Atomically replace all workflow state from a manifest. Lifted verbatim from
|
|
2595
|
-
* workflow-manifest.ts so the single-writer invariant holds. Restores
|
|
2596
|
-
* correctness-bearing workflow tables; runtime soft state and append-only audit
|
|
2597
|
-
* streams stay outside this recovery path.
|
|
2598
|
-
*/
|
|
2599
|
-
export function restoreManifest(manifest) {
|
|
2600
|
-
if (!currentDb)
|
|
2601
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2602
|
-
const db = currentDb;
|
|
2603
|
-
transaction(() => {
|
|
2604
|
-
const restoredMilestoneIds = new Set(manifest.milestones.map((m) => m.id));
|
|
2605
|
-
const restoredSliceKeys = new Set(manifest.slices.map((s) => JSON.stringify([s.milestone_id, s.id])));
|
|
2606
|
-
const preservedReplanHistory = manifest.replan_history === undefined
|
|
2607
|
-
? db.prepare("SELECT * FROM replan_history ORDER BY id").all()
|
|
2608
|
-
: [];
|
|
2609
|
-
const preservedAssessments = manifest.assessments === undefined
|
|
2610
|
-
? db.prepare("SELECT * FROM assessments ORDER BY path").all()
|
|
2611
|
-
: [];
|
|
2612
|
-
const preservedQualityGates = manifest.quality_gates === undefined
|
|
2613
|
-
? db.prepare("SELECT * FROM quality_gates ORDER BY milestone_id, slice_id, gate_id, task_id").all()
|
|
2614
|
-
: [];
|
|
2615
|
-
const preservedCommitAttributions = manifest.milestone_commit_attributions === undefined
|
|
2616
|
-
? db.prepare("SELECT * FROM milestone_commit_attributions ORDER BY milestone_id, commit_sha").all()
|
|
2617
|
-
: [];
|
|
2618
|
-
// Clear workflow tables in dependency order.
|
|
2619
|
-
db.exec("DELETE FROM verification_evidence");
|
|
2620
|
-
db.exec("DELETE FROM quality_gates");
|
|
2621
|
-
db.exec("DELETE FROM slice_dependencies");
|
|
2622
|
-
db.exec("DELETE FROM assessments");
|
|
2623
|
-
db.exec("DELETE FROM replan_history");
|
|
2624
|
-
db.exec("DELETE FROM milestone_commit_attributions");
|
|
2625
|
-
db.exec("DELETE FROM tasks");
|
|
2626
|
-
db.exec("DELETE FROM slices");
|
|
2627
|
-
db.exec("DELETE FROM milestone_leases");
|
|
2628
|
-
db.exec("DELETE FROM milestones");
|
|
2629
|
-
db.exec("DELETE FROM decisions WHERE 1=1");
|
|
2630
|
-
db.exec(`DELETE FROM memories WHERE category = 'architecture' AND structured_fields LIKE '%"sourceDecisionId":"%'`);
|
|
2631
|
-
if (manifest.artifacts !== undefined)
|
|
2632
|
-
db.exec("DELETE FROM artifacts");
|
|
2633
|
-
if (manifest.requirements !== undefined)
|
|
2634
|
-
db.exec("DELETE FROM requirements");
|
|
2635
|
-
if (manifest.requirements !== undefined) {
|
|
2636
|
-
const reqStmt = db.prepare(`INSERT INTO requirements (
|
|
2637
|
-
id, class, status, description, why, source, primary_owner,
|
|
2638
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
2639
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2640
|
-
for (const r of manifest.requirements) {
|
|
2641
|
-
reqStmt.run(r.id, r.class, r.status, r.description, r.why, r.source, r.primary_owner, r.supporting_slices, r.validation, r.notes, r.full_content, r.superseded_by);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
if (manifest.artifacts !== undefined) {
|
|
2645
|
-
const artStmt = db.prepare(`INSERT INTO artifacts (
|
|
2646
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
2647
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2648
|
-
for (const a of manifest.artifacts) {
|
|
2649
|
-
const fullContent = a.full_content ?? "";
|
|
2650
|
-
artStmt.run(a.path, a.artifact_type, a.milestone_id, a.slice_id, a.task_id, fullContent, a.imported_at, a.content_hash ?? createHash("sha256").update(fullContent).digest("hex"));
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
// Restore milestones
|
|
2654
|
-
const msStmt = db.prepare(`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
|
|
2655
|
-
vision, success_criteria, key_risks, proof_strategy,
|
|
2656
|
-
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
2657
|
-
definition_of_done, requirement_coverage, boundary_map_markdown, sequence)
|
|
2658
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2659
|
-
for (const m of manifest.milestones) {
|
|
2660
|
-
msStmt.run(m.id, m.title, m.status, JSON.stringify(m.depends_on), m.created_at, m.completed_at, m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks), JSON.stringify(m.proof_strategy), m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat, JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown, m.sequence ?? 0);
|
|
2661
|
-
}
|
|
2662
|
-
// Restore slices (ADR-011 Phase 1: includes is_sketch + sketch_scope)
|
|
2663
|
-
const slStmt = db.prepare(`INSERT INTO slices (milestone_id, id, title, status, risk, depends, demo,
|
|
2664
|
-
created_at, completed_at, full_summary_md, full_uat_md,
|
|
2665
|
-
goal, success_criteria, proof_level, integration_closure, observability_impact,
|
|
2666
|
-
target_repositories, sequence, replan_triggered_at, is_sketch, sketch_scope)
|
|
2667
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2668
|
-
for (const s of manifest.slices) {
|
|
2669
|
-
slStmt.run(s.milestone_id, s.id, s.title, s.status, s.risk, JSON.stringify(s.depends), s.demo, s.created_at, s.completed_at, s.full_summary_md, s.full_uat_md, s.goal, s.success_criteria, s.proof_level, s.integration_closure, s.observability_impact, JSON.stringify(s.target_repositories ?? []), s.sequence, s.replan_triggered_at, s.is_sketch ?? 0, s.sketch_scope ?? "");
|
|
2670
|
-
}
|
|
2671
|
-
const depStmt = db.prepare("INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (?, ?, ?)");
|
|
2672
|
-
for (const s of manifest.slices) {
|
|
2673
|
-
for (const dep of s.depends ?? []) {
|
|
2674
|
-
depStmt.run(s.milestone_id, s.id, dep);
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
// Restore tasks (ADR-011 P2: includes blocker_source + escalation_* columns)
|
|
2678
|
-
const tkStmt = db.prepare(`INSERT INTO tasks (milestone_id, slice_id, id, title, status,
|
|
2679
|
-
one_liner, narrative, verification_result, duration, completed_at,
|
|
2680
|
-
blocker_discovered, deviations, known_issues, key_files, key_decisions,
|
|
2681
|
-
full_summary_md, description, estimate, files, verify,
|
|
2682
|
-
inputs, expected_output, observability_impact, full_plan_md, target_repositories, sequence,
|
|
2683
|
-
blocker_source, escalation_pending, escalation_awaiting_review,
|
|
2684
|
-
escalation_artifact_path, escalation_override_applied_at)
|
|
2685
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2686
|
-
for (const t of manifest.tasks) {
|
|
2687
|
-
tkStmt.run(t.milestone_id, t.slice_id, t.id, t.title, t.status, t.one_liner, t.narrative, t.verification_result, t.duration, t.completed_at, t.blocker_discovered ? 1 : 0, t.deviations, t.known_issues, JSON.stringify(t.key_files), JSON.stringify(t.key_decisions), t.full_summary_md, t.description, t.estimate, JSON.stringify(t.files), t.verify, JSON.stringify(t.inputs), JSON.stringify(t.expected_output), t.observability_impact, t.full_plan_md ?? "", JSON.stringify(t.target_repositories ?? []), t.sequence, t.blocker_source ?? "", t.escalation_pending ?? 0, t.escalation_awaiting_review ?? 0, t.escalation_artifact_path ?? null, t.escalation_override_applied_at ?? null);
|
|
2688
|
-
}
|
|
2689
|
-
// Restore decisions (ADR-011 P2: include source so escalation decisions survive)
|
|
2690
|
-
const dcStmt = db.prepare(`INSERT INTO decisions (seq, id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
2691
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2692
|
-
for (const d of manifest.decisions) {
|
|
2693
|
-
dcStmt.run(d.seq, d.id, d.when_context, d.scope, d.decision, d.choice, d.rationale, d.revisable, d.made_by, d.source ?? "discussion", d.superseded_by);
|
|
2694
|
-
}
|
|
2695
|
-
const replanHistoryRows = manifest.replan_history ?? preservedReplanHistory.filter((r) => restoredMilestoneIds.has(r.milestone_id));
|
|
2696
|
-
if (replanHistoryRows.length > 0) {
|
|
2697
|
-
const replStmt = db.prepare(`INSERT INTO replan_history (
|
|
2698
|
-
id, milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
|
|
2699
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2700
|
-
for (const r of replanHistoryRows) {
|
|
2701
|
-
replStmt.run(r.id, r.milestone_id, r.slice_id, r.task_id, r.summary, r.previous_artifact_path, r.replacement_artifact_path, r.created_at);
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
const assessmentRows = manifest.assessments ?? preservedAssessments.filter((a) => restoredMilestoneIds.has(a.milestone_id));
|
|
2705
|
-
if (assessmentRows.length > 0) {
|
|
2706
|
-
const assessStmt = db.prepare(`INSERT INTO assessments (
|
|
2707
|
-
path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
|
|
2708
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2709
|
-
for (const a of assessmentRows) {
|
|
2710
|
-
assessStmt.run(a.path, a.milestone_id, a.slice_id, a.task_id, a.status, a.scope, a.full_content, a.created_at);
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
const qualityGateRows = manifest.quality_gates ?? preservedQualityGates.filter((g) => (restoredSliceKeys.has(JSON.stringify([g.milestone_id, g.slice_id]))));
|
|
2714
|
-
if (qualityGateRows.length > 0) {
|
|
2715
|
-
const gateStmt = db.prepare(`INSERT INTO quality_gates (
|
|
2716
|
-
milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
|
|
2717
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2718
|
-
for (const g of qualityGateRows) {
|
|
2719
|
-
gateStmt.run(g.milestone_id, g.slice_id, g.gate_id, g.scope, g.task_id, g.status, g.verdict ?? "", g.rationale, g.findings, g.evaluated_at);
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
const commitAttributionRows = manifest.milestone_commit_attributions ??
|
|
2723
|
-
preservedCommitAttributions.filter((a) => restoredMilestoneIds.has(a.milestone_id));
|
|
2724
|
-
if (commitAttributionRows.length > 0) {
|
|
2725
|
-
const attrStmt = db.prepare(`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2726
|
-
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2727
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2728
|
-
for (const a of commitAttributionRows) {
|
|
2729
|
-
attrStmt.run(a.commit_sha, a.milestone_id, a.slice_id, a.task_id, a.source, a.confidence, a.files_json, a.created_at);
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
// Restore verification evidence
|
|
2733
|
-
const evStmt = db.prepare(`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
2734
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
2735
|
-
for (const e of manifest.verification_evidence) {
|
|
2736
|
-
evStmt.run(e.task_id, e.slice_id, e.milestone_id, e.command, e.exit_code, e.verdict, e.duration_ms, e.created_at);
|
|
2737
|
-
}
|
|
2738
|
-
});
|
|
2739
|
-
}
|
|
2740
|
-
/**
|
|
2741
|
-
* Bulk delete + insert a legacy milestone hierarchy for markdown → DB migration.
|
|
2742
|
-
* Used by workflow-migration.ts to populate engine tables from parsed ROADMAP/PLAN
|
|
2743
|
-
* files. All operations run inside a single transaction.
|
|
2744
|
-
*/
|
|
2745
|
-
export function bulkInsertLegacyHierarchy(payload) {
|
|
2746
|
-
if (!currentDb)
|
|
2747
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2748
|
-
const db = currentDb;
|
|
2749
|
-
const { milestones, slices, tasks, clearMilestoneIds, createdAt } = payload;
|
|
2750
|
-
if (clearMilestoneIds.length === 0)
|
|
2751
|
-
return;
|
|
2752
|
-
const placeholders = clearMilestoneIds.map(() => "?").join(",");
|
|
2753
|
-
transaction(() => {
|
|
2754
|
-
db.prepare(`DELETE FROM tasks WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
2755
|
-
db.prepare(`DELETE FROM slices WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
2756
|
-
db.prepare(`DELETE FROM milestone_leases WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
2757
|
-
db.prepare(`DELETE FROM milestones WHERE id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
2758
|
-
const insertMilestone = db.prepare("INSERT INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)");
|
|
2759
|
-
for (const m of milestones) {
|
|
2760
|
-
insertMilestone.run(m.id, m.title, m.status, createdAt);
|
|
2761
|
-
}
|
|
2762
|
-
const insertSliceStmt = db.prepare("INSERT INTO slices (id, milestone_id, title, status, risk, depends, sequence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
2763
|
-
for (const s of slices) {
|
|
2764
|
-
insertSliceStmt.run(s.id, s.milestoneId, s.title, s.status, s.risk, "[]", s.sequence, createdAt);
|
|
2765
|
-
}
|
|
2766
|
-
const insertTaskStmt = db.prepare("INSERT INTO tasks (id, slice_id, milestone_id, title, description, status, estimate, files, sequence) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
2767
|
-
for (const t of tasks) {
|
|
2768
|
-
insertTaskStmt.run(t.id, t.sliceId, t.milestoneId, t.title, "", t.status, "", "[]", t.sequence);
|
|
2769
|
-
}
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
|
-
// ─── Memory store writers ────────────────────────────────────────────────
|
|
2773
|
-
// All memory writes go through gsd-db.ts so the single-writer invariant
|
|
2774
|
-
// holds. These are direct pass-throughs to the SQL previously in
|
|
2775
|
-
// memory-store.ts — same bindings, same behavior.
|
|
2776
|
-
export function insertMemoryRow(args) {
|
|
2777
|
-
if (!currentDb)
|
|
2778
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2779
|
-
currentDb.prepare(`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at, scope, tags, structured_fields)
|
|
2780
|
-
VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at, :scope, :tags, :structured_fields)`).run({
|
|
2781
|
-
":id": args.id,
|
|
2782
|
-
":category": args.category,
|
|
2783
|
-
":content": args.content,
|
|
2784
|
-
":confidence": args.confidence,
|
|
2785
|
-
":source_unit_type": args.sourceUnitType,
|
|
2786
|
-
":source_unit_id": args.sourceUnitId,
|
|
2787
|
-
":created_at": args.createdAt,
|
|
2788
|
-
":updated_at": args.updatedAt,
|
|
2789
|
-
":scope": args.scope ?? "project",
|
|
2790
|
-
":tags": JSON.stringify(args.tags ?? []),
|
|
2791
|
-
":structured_fields": args.structuredFields == null ? null : JSON.stringify(args.structuredFields),
|
|
2792
|
-
});
|
|
2793
|
-
}
|
|
2794
|
-
export function insertMemorySourceRow(args) {
|
|
2795
|
-
if (!currentDb)
|
|
2796
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2797
|
-
currentDb.prepare(`INSERT OR IGNORE INTO memory_sources (id, kind, uri, title, content, content_hash, imported_at, scope, tags)
|
|
2798
|
-
VALUES (:id, :kind, :uri, :title, :content, :content_hash, :imported_at, :scope, :tags)`).run({
|
|
2799
|
-
":id": args.id,
|
|
2800
|
-
":kind": args.kind,
|
|
2801
|
-
":uri": args.uri,
|
|
2802
|
-
":title": args.title,
|
|
2803
|
-
":content": args.content,
|
|
2804
|
-
":content_hash": args.contentHash,
|
|
2805
|
-
":imported_at": args.importedAt,
|
|
2806
|
-
":scope": args.scope ?? "project",
|
|
2807
|
-
":tags": JSON.stringify(args.tags ?? []),
|
|
2808
|
-
});
|
|
2809
|
-
}
|
|
2810
|
-
export function deleteMemorySourceRow(id) {
|
|
2811
|
-
if (!currentDb)
|
|
2812
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2813
|
-
const res = currentDb
|
|
2814
|
-
.prepare("DELETE FROM memory_sources WHERE id = :id")
|
|
2815
|
-
.run({ ":id": id });
|
|
2816
|
-
return (res?.changes ?? 0) > 0;
|
|
2817
|
-
}
|
|
2818
|
-
export function upsertMemoryEmbedding(args) {
|
|
2819
|
-
if (!currentDb)
|
|
2820
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2821
|
-
currentDb.prepare(`INSERT INTO memory_embeddings (memory_id, model, dim, vector, updated_at)
|
|
2822
|
-
VALUES (:memory_id, :model, :dim, :vector, :updated_at)
|
|
2823
|
-
ON CONFLICT(memory_id) DO UPDATE SET
|
|
2824
|
-
model = excluded.model,
|
|
2825
|
-
dim = excluded.dim,
|
|
2826
|
-
vector = excluded.vector,
|
|
2827
|
-
updated_at = excluded.updated_at`).run({
|
|
2828
|
-
":memory_id": args.memoryId,
|
|
2829
|
-
":model": args.model,
|
|
2830
|
-
":dim": args.dim,
|
|
2831
|
-
":vector": args.vector,
|
|
2832
|
-
":updated_at": args.updatedAt,
|
|
2833
|
-
});
|
|
2834
|
-
}
|
|
2835
|
-
export function deleteMemoryEmbedding(memoryId) {
|
|
2836
|
-
if (!currentDb)
|
|
2837
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2838
|
-
const res = currentDb
|
|
2839
|
-
.prepare("DELETE FROM memory_embeddings WHERE memory_id = :id")
|
|
2840
|
-
.run({ ":id": memoryId });
|
|
2841
|
-
return (res?.changes ?? 0) > 0;
|
|
2842
|
-
}
|
|
2843
|
-
export function insertMemoryRelationRow(args) {
|
|
2844
|
-
if (!currentDb)
|
|
2845
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2846
|
-
currentDb.prepare(`INSERT OR REPLACE INTO memory_relations (from_id, to_id, rel, confidence, created_at)
|
|
2847
|
-
VALUES (:from_id, :to_id, :rel, :confidence, :created_at)`).run({
|
|
2848
|
-
":from_id": args.fromId,
|
|
2849
|
-
":to_id": args.toId,
|
|
2850
|
-
":rel": args.rel,
|
|
2851
|
-
":confidence": args.confidence,
|
|
2852
|
-
":created_at": args.createdAt,
|
|
2853
|
-
});
|
|
2854
|
-
}
|
|
2855
|
-
export function deleteMemoryRelationsFor(memoryId) {
|
|
2856
|
-
if (!currentDb)
|
|
2857
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2858
|
-
currentDb
|
|
2859
|
-
.prepare("DELETE FROM memory_relations WHERE from_id = :id OR to_id = :id")
|
|
2860
|
-
.run({ ":id": memoryId });
|
|
2861
|
-
}
|
|
2862
|
-
export function rewriteMemoryId(placeholderId, realId) {
|
|
2863
|
-
if (!currentDb)
|
|
2864
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2865
|
-
currentDb.prepare("UPDATE memories SET id = :real_id WHERE id = :placeholder").run({
|
|
2866
|
-
":real_id": realId,
|
|
2867
|
-
":placeholder": placeholderId,
|
|
2868
|
-
});
|
|
2869
|
-
}
|
|
2870
|
-
export function updateMemoryContentRow(id, content, confidence, updatedAt) {
|
|
2871
|
-
if (!currentDb)
|
|
2872
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2873
|
-
if (confidence != null) {
|
|
2874
|
-
currentDb.prepare("UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id").run({ ":content": content, ":confidence": confidence, ":updated_at": updatedAt, ":id": id });
|
|
2875
|
-
}
|
|
2876
|
-
else {
|
|
2877
|
-
currentDb.prepare("UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id").run({ ":content": content, ":updated_at": updatedAt, ":id": id });
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
export function incrementMemoryHitCount(id, updatedAt) {
|
|
2881
|
-
if (!currentDb)
|
|
2882
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2883
|
-
currentDb.prepare("UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at, last_hit_at = :last_hit_at WHERE id = :id").run({ ":updated_at": updatedAt, ":last_hit_at": updatedAt, ":id": id });
|
|
2884
|
-
}
|
|
2885
|
-
export function supersedeMemoryRow(oldId, newId, updatedAt) {
|
|
2886
|
-
if (!currentDb)
|
|
2887
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2888
|
-
currentDb.prepare("UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id").run({ ":new_id": newId, ":updated_at": updatedAt, ":old_id": oldId });
|
|
2889
|
-
}
|
|
2890
|
-
export function markMemoryUnitProcessed(unitKey, activityFile, processedAt) {
|
|
2891
|
-
if (!currentDb)
|
|
2892
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2893
|
-
currentDb.prepare(`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
|
|
2894
|
-
VALUES (:key, :file, :at)`).run({ ":key": unitKey, ":file": activityFile, ":at": processedAt });
|
|
2895
|
-
}
|
|
2896
|
-
export function decayMemoriesBefore(cutoffTs, now) {
|
|
2897
|
-
if (!currentDb)
|
|
2898
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2899
|
-
currentDb.prepare(`UPDATE memories
|
|
2900
|
-
SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
|
|
2901
|
-
WHERE superseded_by IS NULL
|
|
2902
|
-
AND updated_at < :cutoff
|
|
2903
|
-
AND confidence > 0.1
|
|
2904
|
-
AND (structured_fields IS NULL OR structured_fields NOT LIKE '%"sourceDecisionId"%')`).run({ ":now": now, ":cutoff": cutoffTs });
|
|
2905
|
-
}
|
|
2906
|
-
export function supersedeLowestRankedMemories(limit, now) {
|
|
2907
|
-
if (!currentDb)
|
|
2908
|
-
throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2909
|
-
currentDb.prepare(`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
|
|
2910
|
-
WHERE id IN (
|
|
2911
|
-
SELECT id FROM memories
|
|
2912
|
-
WHERE superseded_by IS NULL
|
|
2913
|
-
ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
|
|
2914
|
-
LIMIT :limit
|
|
2915
|
-
)`).run({ ":now": now, ":limit": limit });
|
|
2916
|
-
}
|