@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.0b870afa
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-style.d.ts +17 -0
- package/dist/cli-style.js +28 -0
- package/dist/cli-web-branch.d.ts +2 -0
- package/dist/cli-web-branch.js +9 -2
- package/dist/cli.js +1 -1
- package/dist/headless-events.d.ts +4 -2
- package/dist/headless-events.js +14 -34
- package/dist/help-text.js +5 -0
- package/dist/mcp-server.js +2 -1
- package/dist/models-resolver.d.ts +3 -13
- package/dist/models-resolver.js +3 -22
- package/dist/project-sessions.js +4 -2
- package/dist/resource-loader.js +2 -14
- 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 +78 -23
- 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 +139 -243
- package/dist/resources/extensions/claude-code-cli/turn-assembler.js +224 -0
- package/dist/resources/extensions/github-sync/templates.js +3 -3
- package/dist/resources/extensions/gsd/artifact-projection.js +14 -0
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- 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 +105 -0
- package/dist/resources/extensions/gsd/auto/dispatch-key.js +37 -0
- package/dist/resources/extensions/gsd/auto/loop.js +77 -56
- package/dist/resources/extensions/gsd/auto/orchestrator.js +860 -96
- package/dist/resources/extensions/gsd/auto/phases.js +81 -8
- package/dist/resources/extensions/gsd/auto/run-unit.js +2 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +16 -4
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -34
- package/dist/resources/extensions/gsd/auto-dispatch.js +56 -63
- package/dist/resources/extensions/gsd/auto-model-selection.js +44 -13
- package/dist/resources/extensions/gsd/auto-post-unit.js +47 -17
- package/dist/resources/extensions/gsd/auto-prompts.js +271 -27
- package/dist/resources/extensions/gsd/auto-recovery.js +48 -49
- package/dist/resources/extensions/gsd/auto-runtime-state.js +17 -0
- package/dist/resources/extensions/gsd/auto-start.js +36 -49
- package/dist/resources/extensions/gsd/auto-timers.js +16 -2
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +55 -0
- package/dist/resources/extensions/gsd/auto-unit-closeout.js +45 -21
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +33 -37
- package/dist/resources/extensions/gsd/auto-verification.js +30 -37
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +67 -375
- package/dist/resources/extensions/gsd/auto.js +112 -486
- 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/db-tools.js +28 -37
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +43 -49
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +19 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +319 -161
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +365 -58
- 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-consistency-gate.js +21 -4
- 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/codebase-generator.js +8 -4
- 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/auto.js +3 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +66 -3
- package/dist/resources/extensions/gsd/commands-inspect.js +4 -8
- package/dist/resources/extensions/gsd/commands-maintenance.js +61 -41
- package/dist/resources/extensions/gsd/commands-ship.js +2 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +12 -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 +398 -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-workspace.js +103 -0
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/delegation-policy.js +2 -10
- package/dist/resources/extensions/gsd/discussion-handoff.js +218 -0
- package/dist/resources/extensions/gsd/dispatch-guard.js +10 -35
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +5 -5
- package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
- 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-proactive.js +7 -2
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
- package/dist/resources/extensions/gsd/doctor.js +16 -9
- package/dist/resources/extensions/gsd/engine-hook-contract.js +70 -0
- package/dist/resources/extensions/gsd/error-classifier.js +10 -1
- 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-conflict-state.js +16 -1
- 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 +184 -2048
- package/dist/resources/extensions/gsd/guidance.js +158 -0
- package/dist/resources/extensions/gsd/guided-flow.js +91 -476
- package/dist/resources/extensions/gsd/guided-unit-completion.js +225 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +43 -33
- package/dist/resources/extensions/gsd/mcp-filter.js +10 -20
- package/dist/resources/extensions/gsd/mcp-tool-name.js +18 -0
- package/dist/resources/extensions/gsd/md-importer.js +4 -3
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
- package/dist/resources/extensions/gsd/migrate/safety.js +22 -11
- package/dist/resources/extensions/gsd/migration-auto-check.js +27 -5
- package/dist/resources/extensions/gsd/milestone-closeout-proof.js +72 -0
- package/dist/resources/extensions/gsd/milestone-closeout.js +97 -28
- package/dist/resources/extensions/gsd/milestone-merge-transaction.js +10 -0
- package/dist/resources/extensions/gsd/milestone-planning-persistence.js +156 -0
- package/dist/resources/extensions/gsd/milestone-readiness.js +77 -0
- package/dist/resources/extensions/gsd/milestone-reopen-events.js +3 -5
- package/dist/resources/extensions/gsd/milestone-settlement.js +50 -0
- package/dist/resources/extensions/gsd/milestone-validation-evidence.js +73 -0
- package/dist/resources/extensions/gsd/milestone-validation-verdict.js +57 -0
- 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/native-git-bridge.js +45 -0
- package/dist/resources/extensions/gsd/notification-store.js +11 -4
- package/dist/resources/extensions/gsd/parallel-eligibility.js +3 -6
- 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/parallel-orchestrator.js +3 -2
- 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-diagnostics.js +67 -0
- package/dist/resources/extensions/gsd/preferences-models.js +14 -48
- package/dist/resources/extensions/gsd/preferences.js +161 -29
- package/dist/resources/extensions/gsd/projection-flush.js +7 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -3
- package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -3
- 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 +3 -2
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +3 -3
- 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 +7 -5
- package/dist/resources/extensions/gsd/prompts/system.md +6 -3
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +6 -4
- package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
- package/dist/resources/extensions/gsd/provider-payload-policy.js +83 -0
- 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/pull-request-process.js +13 -0
- package/dist/resources/extensions/gsd/quality-gate-closure.js +109 -0
- package/dist/resources/extensions/gsd/question-transport.js +86 -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 +33 -5
- 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/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/session-lock.js +1 -1
- package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +3 -2
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/state.js +19 -25
- 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/templates/plan.md +7 -0
- package/dist/resources/extensions/gsd/templates/project.md +1 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +1 -1
- package/dist/resources/extensions/gsd/templates/uat.md +5 -1
- package/dist/resources/extensions/gsd/tool-contract.js +66 -11
- package/dist/resources/extensions/gsd/tool-presentation-plan.js +17 -36
- package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
- package/dist/resources/extensions/gsd/tool-surface-snapshot.js +17 -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 +3 -2
- package/dist/resources/extensions/gsd/tools/exec-tool.js +10 -8
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +15 -143
- 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 +41 -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/validate-milestone.js +15 -78
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +236 -22
- package/dist/resources/extensions/gsd/uat-policy.js +57 -25
- package/dist/resources/extensions/gsd/uat-run.js +9 -14
- 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 +114 -21
- package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
- package/dist/resources/extensions/gsd/unit-registry.js +337 -0
- package/dist/resources/extensions/gsd/unit-runtime.js +3 -2
- package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -181
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- 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 +117 -0
- 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.js +15 -102
- package/dist/resources/extensions/gsd/workflow-reconcile.js +25 -59
- package/dist/resources/extensions/gsd/workflow-tool-surface.js +46 -0
- package/dist/resources/extensions/gsd/workspace-git-guard.js +2 -0
- 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 +45 -28
- 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 +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
- package/dist/resources/extensions/gsd/worktree-state-projection.js +33 -4
- package/dist/resources/extensions/gsd/worktree-telemetry.js +12 -0
- 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/extensions/shared/interview-ui.js +2 -2
- package/dist/resources/shared/claude-runtime-floor.js +182 -0
- 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/references/executable-code.md +1 -1
- 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/gsd-browser/SKILL.md +1 -1
- package/dist/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/update-check.d.ts +2 -0
- package/dist/update-check.js +24 -1
- package/dist/update-cmd.js +40 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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 +8 -8
- 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 +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 +5 -5
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/5942.js +2 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/2659.b7b129ee6a769448.js +1 -0
- package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{3616.4113d484a994e411.js → 3616.3c60753b8ffcbd2e.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/4283.e4873b058df143a1.js +2 -0
- package/dist/web/standalone/.next/static/chunks/5826.a46ecdd1cfe8dabc.js +1 -0
- package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +10 -0
- package/dist/web/standalone/.next/static/chunks/8785.2e5a118797fb2dd2.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-dda80a1ef5587410.js → webpack-f0285ce91d4ec9ef.js} +1 -1
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
- package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
- package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
- package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
- package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
- package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
- package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
- package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
- package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
- package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
- package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
- package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
- package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
- package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
- package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
- package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
- package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
- package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
- package/dist/web/standalone/node_modules/postcss/package.json +48 -48
- package/dist/web/standalone/package.json +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +20 -8
- package/dist/worktree-cli.js +3 -6
- package/dist/worktree-status-banner.js +7 -11
- package/package.json +17 -11
- 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/session/agent-session-extensions.d.ts +2 -0
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +14 -0
- package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +5 -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 +8 -0
- 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.js +1 -1
- 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 +113 -40
- 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-extension-widgets.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.js +6 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-extension-widgets.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/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/dist/cli.js +9 -1
- 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/server.d.ts +10 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +12 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +49 -0
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +147 -60
- 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/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/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 +183 -110
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +205 -158
- 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 +29 -15
- 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/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/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/components/input.js +1 -1
- package/packages/pi-tui/dist/components/input.js.map +1 -1
- package/packages/pi-tui/dist/keys.d.ts.map +1 -1
- package/packages/pi-tui/dist/keys.js +39 -30
- package/packages/pi-tui/dist/keys.js.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
- package/packages/pi-tui/dist/stdin-buffer.js +22 -0
- package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +9 -0
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/scripts/link-workspace-packages.cjs +7 -40
- package/src/resources/GSD-WORKFLOW.md +5 -4
- package/src/resources/extensions/ask-user-questions.ts +87 -24
- 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 +166 -293
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +270 -2
- package/src/resources/extensions/claude-code-cli/turn-assembler.ts +287 -0
- package/src/resources/extensions/github-sync/templates.ts +3 -3
- package/src/resources/extensions/github-sync/tests/templates.test.ts +2 -2
- package/src/resources/extensions/gsd/artifact-projection.ts +31 -0
- package/src/resources/extensions/gsd/auto/contracts.ts +40 -121
- 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 +152 -0
- package/src/resources/extensions/gsd/auto/dispatch-key.ts +39 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +3 -1
- package/src/resources/extensions/gsd/auto/loop.ts +86 -61
- package/src/resources/extensions/gsd/auto/orchestrator.ts +1023 -98
- package/src/resources/extensions/gsd/auto/phases.ts +108 -28
- package/src/resources/extensions/gsd/auto/run-unit.ts +2 -1
- package/src/resources/extensions/gsd/auto/session.ts +7 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +18 -4
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +18 -48
- package/src/resources/extensions/gsd/auto-dispatch.ts +68 -68
- package/src/resources/extensions/gsd/auto-model-selection.ts +49 -12
- package/src/resources/extensions/gsd/auto-post-unit.ts +56 -16
- package/src/resources/extensions/gsd/auto-prompts.ts +338 -44
- package/src/resources/extensions/gsd/auto-recovery.ts +50 -50
- package/src/resources/extensions/gsd/auto-runtime-state.ts +30 -0
- package/src/resources/extensions/gsd/auto-start.ts +41 -49
- package/src/resources/extensions/gsd/auto-timers.ts +16 -2
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +59 -0
- package/src/resources/extensions/gsd/auto-unit-closeout.ts +83 -28
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +43 -38
- package/src/resources/extensions/gsd/auto-verification.ts +33 -36
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +83 -391
- package/src/resources/extensions/gsd/auto.ts +151 -524
- 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/db-tools.ts +29 -37
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +43 -49
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +379 -177
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +452 -58
- 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-consistency-gate.ts +27 -5
- 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/codebase-generator.ts +9 -5
- 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/auto.ts +3 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +64 -3
- package/src/resources/extensions/gsd/commands-inspect.ts +7 -8
- package/src/resources/extensions/gsd/commands-maintenance.ts +74 -40
- package/src/resources/extensions/gsd/commands-ship.ts +2 -2
- package/src/resources/extensions/gsd/commands-verdict.ts +19 -2
- 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 +490 -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-workspace.ts +170 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/delegation-policy.ts +3 -11
- package/src/resources/extensions/gsd/discussion-handoff.ts +276 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +8 -31
- package/src/resources/extensions/gsd/docs/preferences-reference.md +9 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +5 -4
- package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
- 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-proactive.ts +8 -2
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
- package/src/resources/extensions/gsd/doctor.ts +15 -5
- package/src/resources/extensions/gsd/engine-hook-contract.ts +79 -0
- package/src/resources/extensions/gsd/error-classifier.ts +12 -1
- 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-conflict-state.ts +17 -1
- 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 +188 -2375
- package/src/resources/extensions/gsd/guidance.ts +217 -0
- package/src/resources/extensions/gsd/guided-flow.ts +118 -589
- package/src/resources/extensions/gsd/guided-unit-completion.ts +275 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +51 -20
- package/src/resources/extensions/gsd/mcp-filter.ts +11 -24
- package/src/resources/extensions/gsd/mcp-tool-name.ts +30 -0
- package/src/resources/extensions/gsd/md-importer.ts +3 -3
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
- package/src/resources/extensions/gsd/migrate/safety.ts +20 -9
- package/src/resources/extensions/gsd/migration-auto-check.ts +30 -5
- package/src/resources/extensions/gsd/milestone-closeout-proof.ts +131 -0
- package/src/resources/extensions/gsd/milestone-closeout.ts +121 -28
- package/src/resources/extensions/gsd/milestone-merge-transaction.ts +47 -0
- package/src/resources/extensions/gsd/milestone-planning-persistence.ts +224 -0
- package/src/resources/extensions/gsd/milestone-readiness.ts +125 -0
- package/src/resources/extensions/gsd/milestone-reopen-events.ts +3 -6
- package/src/resources/extensions/gsd/milestone-settlement.ts +81 -0
- package/src/resources/extensions/gsd/milestone-validation-evidence.ts +95 -0
- package/src/resources/extensions/gsd/milestone-validation-verdict.ts +80 -0
- 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/native-git-bridge.ts +48 -0
- package/src/resources/extensions/gsd/notification-store.ts +26 -3
- package/src/resources/extensions/gsd/parallel-eligibility.ts +4 -5
- 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/parallel-orchestrator.ts +6 -2
- 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-diagnostics.ts +98 -0
- package/src/resources/extensions/gsd/preferences-models.ts +12 -47
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences.ts +191 -28
- package/src/resources/extensions/gsd/projection-flush.ts +20 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +6 -6
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -3
- 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 +3 -2
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +3 -3
- 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 +7 -5
- package/src/resources/extensions/gsd/prompts/system.md +6 -3
- package/src/resources/extensions/gsd/prompts/triage-captures.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +6 -4
- package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
- package/src/resources/extensions/gsd/provider-payload-policy.ts +140 -0
- 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/pull-request-process.ts +41 -0
- package/src/resources/extensions/gsd/quality-gate-closure.ts +140 -0
- package/src/resources/extensions/gsd/question-transport.ts +138 -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 +36 -5
- 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/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/session-lock.ts +1 -1
- package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +6 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/state.ts +24 -26
- 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/templates/plan.md +7 -0
- package/src/resources/extensions/gsd/templates/project.md +1 -0
- package/src/resources/extensions/gsd/templates/roadmap.md +1 -1
- package/src/resources/extensions/gsd/templates/uat.md +5 -1
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/ask-user-questions-render.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +444 -5
- package/src/resources/extensions/gsd/tests/auto-milestone-target.test.ts +23 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +91 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +894 -858
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +41 -11
- 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-runtime-state.test.ts +34 -0
- 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/canonical-milestone-root.test.ts +20 -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/codebase-generator.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +46 -8
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +42 -0
- 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/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +34 -3
- package/src/resources/extensions/gsd/tests/dispatch-history.test.ts +273 -0
- package/src/resources/extensions/gsd/tests/dispatch-run-uat-browser-tools.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/doctor-git-checks-terminal.test.ts +73 -0
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +18 -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/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/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +1 -0
- 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/fixtures/pr-body/swarm-lane-no-blockers.md +1 -5
- package/src/resources/extensions/gsd/tests/fixtures/pr-body/swarm-lane-with-blockers.md +1 -5
- package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +48 -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 +2 -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 +74 -59
- 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/merge-strategy-regular.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/mcp-tool-name.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +143 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout-proof.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +120 -4
- package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/milestone-readiness.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/milestone-validation-evidence.test.ts +41 -0
- package/src/resources/extensions/gsd/tests/milestone-validation-verdict.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/model-unittype-mapping.test.ts +32 -1
- package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/oauth-api-model-routing.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +138 -0
- package/src/resources/extensions/gsd/tests/phases-terminal-complete-idempotent.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/planning-crossval.test.ts +45 -0
- 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/preferences-diagnostics.test.ts +67 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +183 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +75 -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 +9 -0
- package/src/resources/extensions/gsd/tests/provider-payload-policy.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pull-request-process.test.ts +47 -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 +342 -1
- package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
- package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +44 -1
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +80 -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 +144 -7
- package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +21 -6
- 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-notice.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +114 -0
- package/src/resources/extensions/gsd/tests/tool-availability-audit.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +35 -42
- package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -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 -6
- package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +114 -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/validate-milestone-stuck-guard.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/verification-verdict.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/web-app-uat.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/workflow-events.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +128 -11
- package/src/resources/extensions/gsd/tests/workflow-reconcile.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +275 -40
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-projection-writers.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +27 -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 +2 -2
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tests/write-gate-seam.test.ts +358 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +188 -1
- package/src/resources/extensions/gsd/tool-contract.ts +124 -11
- package/src/resources/extensions/gsd/tool-presentation-plan.ts +18 -35
- package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
- package/src/resources/extensions/gsd/tool-surface-snapshot.ts +47 -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 +3 -2
- package/src/resources/extensions/gsd/tools/exec-tool.ts +9 -8
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +19 -160
- 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 +45 -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/validate-milestone.ts +25 -84
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +264 -23
- package/src/resources/extensions/gsd/uat-policy.ts +80 -25
- package/src/resources/extensions/gsd/uat-run.ts +10 -14
- 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 +196 -21
- package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
- package/src/resources/extensions/gsd/unit-registry.ts +412 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +3 -2
- package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -191
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- 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 +144 -0
- 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.ts +22 -110
- package/src/resources/extensions/gsd/workflow-reconcile.ts +32 -65
- package/src/resources/extensions/gsd/workflow-tool-surface.ts +76 -0
- package/src/resources/extensions/gsd/workspace-git-guard.ts +1 -0
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +20 -25
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- 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 +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
- package/src/resources/extensions/gsd/worktree-state-projection.ts +55 -7
- package/src/resources/extensions/gsd/worktree-telemetry.ts +16 -0
- 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/extensions/shared/interview-ui.ts +15 -2
- package/src/resources/shared/claude-runtime-floor.ts +248 -0
- 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/references/executable-code.md +1 -1
- 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/gsd-browser/SKILL.md +1 -1
- package/src/resources/skills/spike-wrap-up/SKILL.md +9 -9
- package/dist/resources/extensions/gsd/user-input-boundary.js +0 -157
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- package/dist/web/standalone/.next/static/chunks/2659.feb6499ca863ebfc.js +0 -1
- package/dist/web/standalone/.next/static/chunks/2772.151789db0edea835.js +0 -1
- package/dist/web/standalone/.next/static/chunks/4283.10a065467b5340d8.js +0 -2
- package/dist/web/standalone/.next/static/chunks/5826.960dc4634cc9b0d3.js +0 -1
- package/dist/web/standalone/.next/static/chunks/796.46f811c0fac23aab.js +0 -10
- package/dist/web/standalone/.next/static/chunks/8785.d32f7a61f55c1600.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
- package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
- package/scripts/ensure-workspace-builds.cjs +0 -129
- package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +0 -26
- package/src/resources/extensions/gsd/user-input-boundary.ts +0 -166
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → T-LTxEw5wir5Lm5T3qEVd}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → T-LTxEw5wir5Lm5T3qEVd}/_ssgManifest.js +0 -0
|
@@ -1,40 +1,25 @@
|
|
|
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
|
-
|
|
25
|
-
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.
|
|
26
16
|
import { createHash } from "node:crypto";
|
|
27
|
-
import {
|
|
28
|
-
import { dirname, join } from "node:path";
|
|
17
|
+
import { join } from "node:path";
|
|
29
18
|
import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
|
|
30
19
|
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
31
|
-
import type { GsdWorkspace, MilestoneScope } from "./workspace.js";
|
|
32
20
|
import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
|
|
33
21
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
34
|
-
import {
|
|
35
|
-
import { createBaseSchemaObjects } from "./db-base-schema.js";
|
|
36
|
-
import { createCoordinationTablesV24 } from "./db-coordination-schema.js";
|
|
37
|
-
import { createDbConnectionCache, type DbConnectionCacheEntry } from "./db-connection-cache.js";
|
|
22
|
+
import { type DbAdapter } from "./db-adapter.js";
|
|
38
23
|
import {
|
|
39
24
|
emptyTaskStatusCounts,
|
|
40
25
|
rowToActiveTaskSummary,
|
|
@@ -54,783 +39,36 @@ import {
|
|
|
54
39
|
} from "./db-decision-requirement-rows.js";
|
|
55
40
|
import { rowToGate } from "./db-gate-rows.js";
|
|
56
41
|
import { rowToArtifact, rowToMilestone, type ArtifactRow, type MilestoneRow } from "./db-milestone-artifact-rows.js";
|
|
57
|
-
import { backupDatabaseBeforeMigration } from "./db-migration-backup.js";
|
|
58
42
|
import { isClosedStatus } from "./status-guards.js";
|
|
59
|
-
import {
|
|
60
|
-
applyMigrationV2Artifacts,
|
|
61
|
-
applyMigrationV3Memories,
|
|
62
|
-
applyMigrationV4DecisionMadeBy,
|
|
63
|
-
applyMigrationV5HierarchyTables,
|
|
64
|
-
applyMigrationV6SliceSummaries,
|
|
65
|
-
applyMigrationV7Dependencies,
|
|
66
|
-
applyMigrationV8PlanningFields,
|
|
67
|
-
applyMigrationV9Ordering,
|
|
68
|
-
applyMigrationV10ReplanTrigger,
|
|
69
|
-
applyMigrationV11TaskPlanning,
|
|
70
|
-
applyMigrationV12QualityGates,
|
|
71
|
-
applyMigrationV13HotPathIndexes,
|
|
72
|
-
applyMigrationV14SliceDependencies,
|
|
73
|
-
applyMigrationV15AuditTables,
|
|
74
|
-
applyMigrationV16EscalationSource,
|
|
75
|
-
applyMigrationV17TaskEscalation,
|
|
76
|
-
applyMigrationV18MemorySources,
|
|
77
|
-
applyMigrationV19MemoryFts,
|
|
78
|
-
applyMigrationV20MemoryRelations,
|
|
79
|
-
applyMigrationV21StructuredMemories,
|
|
80
|
-
applyMigrationV22QualityGateRepair,
|
|
81
|
-
applyMigrationV23MilestoneQueue,
|
|
82
|
-
applyMigrationV26MilestoneCommitAttributions,
|
|
83
|
-
applyMigrationV27ArtifactHash,
|
|
84
|
-
applyMigrationV28MemoryLastHitAt,
|
|
85
|
-
applyMigrationV29RepositoryTargets,
|
|
86
|
-
} from "./db-migration-steps.js";
|
|
87
|
-
import { isMemoriesFtsAvailableSchema, tryCreateMemoriesFtsSchema } from "./db-memory-fts-schema.js";
|
|
88
|
-
import { createDbOpenState, type DbOpenPhase } from "./db-open-state.js";
|
|
89
|
-
import { createRuntimeKvTableV25 } from "./db-runtime-kv-schema.js";
|
|
90
|
-
import { ensureColumn, getCurrentSchemaVersion, recordSchemaVersion } from "./db-schema-metadata.js";
|
|
91
43
|
import { rowToSlice, rowToTask, type SliceRow, type TaskRow } from "./db-task-slice-rows.js";
|
|
92
|
-
import { createDbTransactionRunner } from "./db-transaction.js";
|
|
93
|
-
import { ensureVerificationEvidenceDedupIndex } from "./db-verification-evidence-schema.js";
|
|
94
|
-
import {
|
|
95
|
-
BETTER_SQLITE3_PACKAGE,
|
|
96
|
-
createSqliteProviderLoader,
|
|
97
|
-
suppressSqliteWarning,
|
|
98
|
-
type DbProviderName,
|
|
99
|
-
type SqliteFallbackOpen,
|
|
100
|
-
} from "./db-provider.js";
|
|
101
|
-
// Type-only import to avoid a circular runtime dep. The runtime side of
|
|
102
|
-
// workflow-manifest.ts depends on this file, but the StateManifest type is
|
|
103
|
-
// pure structure with no runtime coupling.
|
|
104
|
-
import type { StateManifest } from "./workflow-manifest.js";
|
|
105
|
-
|
|
106
|
-
let _gsdRequire: ReturnType<typeof createRequire> | null | undefined;
|
|
107
|
-
|
|
108
|
-
function getGsdRequire(): ReturnType<typeof createRequire> | null {
|
|
109
|
-
if (_gsdRequire !== undefined) return _gsdRequire;
|
|
110
|
-
try {
|
|
111
|
-
_gsdRequire = createRequire(import.meta.url);
|
|
112
|
-
} catch {
|
|
113
|
-
_gsdRequire = null;
|
|
114
|
-
}
|
|
115
|
-
return _gsdRequire;
|
|
116
|
-
}
|
|
117
44
|
|
|
118
|
-
|
|
45
|
+
// Connection ownership, lifecycle, schema/migrations and transaction
|
|
46
|
+
// primitives now live in the engine; re-export the full public surface so
|
|
47
|
+
// existing `from "./gsd-db.js"` imports keep working.
|
|
48
|
+
export * from "./db/engine.js";
|
|
49
|
+
import { transaction, getDb, getDbOrNull } from "./db/engine.js";
|
|
50
|
+
|
|
51
|
+
// ─── Single Writer Layer re-exports ──────────────────────────────────────
|
|
52
|
+
// Write subsystems live in db/writers/*; re-exported here so callers keep
|
|
53
|
+
// importing from "./gsd-db.js".
|
|
54
|
+
export * from "./db/writers/memory.js";
|
|
55
|
+
export * from "./db/writers/reconcile.js";
|
|
56
|
+
export * from "./db/writers/import-restore.js";
|
|
57
|
+
// Query Module (read-only seam) — extracted from the single-writer file.
|
|
58
|
+
export * from "./db/queries.js";
|
|
59
|
+
// Domain Write Operations (Hierarchy Status Cascades).
|
|
60
|
+
export * from "./db/writers/cascades.js";
|
|
119
61
|
|
|
120
62
|
export type { ArtifactRow, MilestoneRow } from "./db-milestone-artifact-rows.js";
|
|
121
63
|
export type { ActiveTaskSummary, IdStatusSummary, TaskStatusCounts } from "./db-lightweight-query-rows.js";
|
|
122
64
|
export type { SliceRow, TaskRow } from "./db-task-slice-rows.js";
|
|
123
65
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const req = getGsdRequire();
|
|
127
|
-
if (!req) throw new Error("unavailable");
|
|
128
|
-
return req("node:sqlite");
|
|
129
|
-
},
|
|
130
|
-
tryRequireBetterSqlite3: () => {
|
|
131
|
-
const req = getGsdRequire();
|
|
132
|
-
if (!req) throw new Error("unavailable");
|
|
133
|
-
return req(BETTER_SQLITE3_PACKAGE);
|
|
134
|
-
},
|
|
135
|
-
suppressSqliteWarning,
|
|
136
|
-
nodeVersion: process.versions.node,
|
|
137
|
-
writeStderr: (message: string) => process.stderr.write(message),
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
export const SCHEMA_VERSION = 29;
|
|
141
|
-
const TERMINAL_STATUS_SQL = "'complete', 'done', 'skipped', 'closed'";
|
|
142
|
-
|
|
143
|
-
function initSchema(db: DbAdapter, fileBacked: boolean, dbPath: string | null): void {
|
|
144
|
-
const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
|
|
145
|
-
if (fileBacked) db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
|
|
146
|
-
if (fileBacked) db.exec("PRAGMA busy_timeout = 5000");
|
|
147
|
-
if (fileBacked) db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
|
|
148
|
-
if (fileBacked) db.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
149
|
-
if (fileBacked) db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
|
|
150
|
-
if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin") db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
|
|
151
|
-
db.exec("PRAGMA temp_store = MEMORY");
|
|
152
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
153
|
-
|
|
154
|
-
db.exec("BEGIN");
|
|
155
|
-
try {
|
|
156
|
-
createBaseSchemaObjects(db, {
|
|
157
|
-
tryCreateMemoriesFts,
|
|
158
|
-
ensureVerificationEvidenceDedupIndex,
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const existing = db.prepare("SELECT count(*) as cnt FROM schema_version").get();
|
|
162
|
-
if (existing && (existing["cnt"] as number) === 0) {
|
|
163
|
-
createCoordinationTablesV24(db);
|
|
164
|
-
createRuntimeKvTableV25(db);
|
|
165
|
-
|
|
166
|
-
// Fresh install — all tables are created above with the full current schema,
|
|
167
|
-
// so it is safe to create all migration-specific indexes here. For existing
|
|
168
|
-
// databases these indexes are created inside the individual migration guards
|
|
169
|
-
// in migrateSchema() after the corresponding columns have been added.
|
|
170
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_escalation_pending ON tasks(milestone_id, slice_id, escalation_pending)");
|
|
171
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope)");
|
|
172
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_kind ON memory_sources(kind)");
|
|
173
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_sources_scope ON memory_sources(scope)");
|
|
174
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_from ON memory_relations(from_id)");
|
|
175
|
-
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_relations_to ON memory_relations(to_id)");
|
|
176
|
-
|
|
177
|
-
recordSchemaVersion(db, SCHEMA_VERSION);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
db.exec("COMMIT");
|
|
181
|
-
} catch (err) {
|
|
182
|
-
db.exec("ROLLBACK");
|
|
183
|
-
throw err;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
migrateSchema(db);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function _isLikelyWslDrvFsPathForTest(dbPath: string | null): boolean {
|
|
190
|
-
if (!dbPath || process.platform !== "linux") return false;
|
|
191
|
-
const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
|
|
192
|
-
if (drvFsPathPattern.test(dbPath)) return true;
|
|
193
|
-
try {
|
|
194
|
-
return drvFsPathPattern.test(realpathSync(dbPath));
|
|
195
|
-
} catch {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Create the FTS5 virtual table for memories plus the triggers that keep it
|
|
202
|
-
* in sync with the base table. FTS5 may be unavailable on stripped-down
|
|
203
|
-
* SQLite builds — callers should treat failure as non-fatal and fall back
|
|
204
|
-
* to LIKE-based scans in `memory-store.queryMemoriesRanked`.
|
|
205
|
-
*/
|
|
206
|
-
export function tryCreateMemoriesFts(db: DbAdapter): boolean {
|
|
207
|
-
return tryCreateMemoriesFtsSchema(db, {
|
|
208
|
-
onUnavailable: (message) => logWarning("db", message),
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function isMemoriesFtsAvailable(db: DbAdapter): boolean {
|
|
213
|
-
return isMemoriesFtsAvailableSchema(db);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function backfillMemoriesFts(db: DbAdapter): void {
|
|
217
|
-
db.exec(`INSERT INTO memories_fts(rowid, content) SELECT seq, content FROM memories`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function copyQualityGateRowsToRepairedTable(db: DbAdapter): void {
|
|
221
|
-
db.exec(`
|
|
222
|
-
INSERT OR IGNORE INTO quality_gates_new
|
|
223
|
-
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
224
|
-
SELECT milestone_id, slice_id, gate_id, scope, COALESCE(task_id, ''), status, verdict, rationale, findings, evaluated_at
|
|
225
|
-
FROM quality_gates
|
|
226
|
-
`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function migrateSchema(db: DbAdapter): void {
|
|
230
|
-
const currentVersion = getCurrentSchemaVersion(db);
|
|
231
|
-
if (currentVersion >= SCHEMA_VERSION) return;
|
|
232
|
-
|
|
233
|
-
backupDatabaseBeforeMigration(db, currentPath, currentVersion, {
|
|
234
|
-
existsSync,
|
|
235
|
-
copyFileSync,
|
|
236
|
-
logWarning,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
db.exec("BEGIN");
|
|
240
|
-
try {
|
|
241
|
-
if (currentVersion < 2) {
|
|
242
|
-
applyMigrationV2Artifacts(db);
|
|
243
|
-
recordSchemaVersion(db, 2);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (currentVersion < 3) {
|
|
247
|
-
applyMigrationV3Memories(db);
|
|
248
|
-
recordSchemaVersion(db, 3);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (currentVersion < 4) {
|
|
252
|
-
applyMigrationV4DecisionMadeBy(db);
|
|
253
|
-
recordSchemaVersion(db, 4);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (currentVersion < 5) {
|
|
257
|
-
applyMigrationV5HierarchyTables(db);
|
|
258
|
-
recordSchemaVersion(db, 5);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (currentVersion < 6) {
|
|
262
|
-
applyMigrationV6SliceSummaries(db);
|
|
263
|
-
recordSchemaVersion(db, 6);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (currentVersion < 7) {
|
|
267
|
-
applyMigrationV7Dependencies(db);
|
|
268
|
-
recordSchemaVersion(db, 7);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (currentVersion < 8) {
|
|
272
|
-
applyMigrationV8PlanningFields(db);
|
|
273
|
-
recordSchemaVersion(db, 8);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (currentVersion < 9) {
|
|
277
|
-
applyMigrationV9Ordering(db);
|
|
278
|
-
recordSchemaVersion(db, 9);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (currentVersion < 10) {
|
|
282
|
-
applyMigrationV10ReplanTrigger(db);
|
|
283
|
-
recordSchemaVersion(db, 10);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (currentVersion < 11) {
|
|
287
|
-
applyMigrationV11TaskPlanning(db);
|
|
288
|
-
recordSchemaVersion(db, 11);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (currentVersion < 12) {
|
|
292
|
-
// NOTE: The original DDL used COALESCE(task_id, '') in the PRIMARY KEY
|
|
293
|
-
// expression, which is invalid SQLite syntax and causes startup errors on
|
|
294
|
-
// DBs that migrate through v12. The corrected DDL uses
|
|
295
|
-
// task_id TEXT NOT NULL DEFAULT '' with a plain column list PK. DBs that
|
|
296
|
-
// were created with the broken DDL are repaired by the v22 migration below.
|
|
297
|
-
applyMigrationV12QualityGates(db);
|
|
298
|
-
recordSchemaVersion(db, 12);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (currentVersion < 13) {
|
|
302
|
-
applyMigrationV13HotPathIndexes(db, ensureVerificationEvidenceDedupIndex);
|
|
303
|
-
recordSchemaVersion(db, 13);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (currentVersion < 14) {
|
|
307
|
-
applyMigrationV14SliceDependencies(db);
|
|
308
|
-
recordSchemaVersion(db, 14);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (currentVersion < 15) {
|
|
312
|
-
applyMigrationV15AuditTables(db);
|
|
313
|
-
recordSchemaVersion(db, 15);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (currentVersion < 16) {
|
|
317
|
-
applyMigrationV16EscalationSource(db);
|
|
318
|
-
recordSchemaVersion(db, 16);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (currentVersion < 17) {
|
|
322
|
-
applyMigrationV17TaskEscalation(db);
|
|
323
|
-
recordSchemaVersion(db, 17);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (currentVersion < 18) {
|
|
327
|
-
applyMigrationV18MemorySources(db);
|
|
328
|
-
recordSchemaVersion(db, 18);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (currentVersion < 19) {
|
|
332
|
-
applyMigrationV19MemoryFts(db, {
|
|
333
|
-
tryCreateMemoriesFts,
|
|
334
|
-
isMemoriesFtsAvailable,
|
|
335
|
-
backfillMemoriesFts,
|
|
336
|
-
logWarning,
|
|
337
|
-
});
|
|
338
|
-
recordSchemaVersion(db, 19);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (currentVersion < 20) {
|
|
342
|
-
applyMigrationV20MemoryRelations(db);
|
|
343
|
-
recordSchemaVersion(db, 20);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (currentVersion < 21) {
|
|
347
|
-
applyMigrationV21StructuredMemories(db);
|
|
348
|
-
recordSchemaVersion(db, 21);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (currentVersion < 22) {
|
|
352
|
-
applyMigrationV22QualityGateRepair(db, { copyQualityGateRowsToRepairedTable });
|
|
353
|
-
recordSchemaVersion(db, 22);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (currentVersion < 23) {
|
|
357
|
-
applyMigrationV23MilestoneQueue(db);
|
|
358
|
-
recordSchemaVersion(db, 23);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
if (currentVersion < 24) {
|
|
362
|
-
// v24: auto-mode coordination tables. See createCoordinationTablesV24
|
|
363
|
-
// for full schema + invariants. No-op for fresh installs (the same
|
|
364
|
-
// helper runs in the fresh-install path); for upgraded DBs this is
|
|
365
|
-
// the only place these tables get created.
|
|
366
|
-
createCoordinationTablesV24(db);
|
|
367
|
-
recordSchemaVersion(db, 24);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (currentVersion < 25) {
|
|
371
|
-
// v25: runtime_kv non-correctness-critical key-value storage. See
|
|
372
|
-
// createRuntimeKvTableV25 for the full schema + invariants.
|
|
373
|
-
createRuntimeKvTableV25(db);
|
|
374
|
-
recordSchemaVersion(db, 25);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (currentVersion < 26) {
|
|
378
|
-
applyMigrationV26MilestoneCommitAttributions(db);
|
|
379
|
-
recordSchemaVersion(db, 26);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (currentVersion < 27) {
|
|
383
|
-
applyMigrationV27ArtifactHash(db);
|
|
384
|
-
recordSchemaVersion(db, 27);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (currentVersion < 28) {
|
|
388
|
-
applyMigrationV28MemoryLastHitAt(db);
|
|
389
|
-
recordSchemaVersion(db, 28);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (currentVersion < 29) {
|
|
393
|
-
applyMigrationV29RepositoryTargets(db);
|
|
394
|
-
recordSchemaVersion(db, 29);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
db.exec("COMMIT");
|
|
398
|
-
} catch (err) {
|
|
399
|
-
db.exec("ROLLBACK");
|
|
400
|
-
throw err;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
let currentDb: DbAdapter | null = null;
|
|
405
|
-
let currentPath: string | null = null;
|
|
406
|
-
let currentPid: number = 0;
|
|
407
|
-
let _exitHandlerRegistered = false;
|
|
408
|
-
const _dbOpenState = createDbOpenState();
|
|
409
|
-
/**
|
|
410
|
-
* Identity key of the workspace whose connection is currently active
|
|
411
|
-
* (currentDb). Set by openDatabaseByWorkspace(); null when the active
|
|
412
|
-
* connection was opened via the legacy openDatabase(path) path.
|
|
413
|
-
*/
|
|
414
|
-
let _currentIdentityKey: string | null = null;
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Workspace-scoped connection cache.
|
|
418
|
-
* Key: GsdWorkspace.identityKey (realpath-normalized project root).
|
|
419
|
-
* Value: the DB path and open adapter for that workspace.
|
|
420
|
-
*
|
|
421
|
-
* Sibling worktrees of the same project share the same identityKey (set by
|
|
422
|
-
* createWorkspace) and therefore reuse the same cached connection, preserving
|
|
423
|
-
* shared-WAL semantics. Different projects get distinct cache entries.
|
|
424
|
-
*
|
|
425
|
-
* NOTE: Only one connection is "active" at a time (currentDb/currentPath).
|
|
426
|
-
* The cache allows fast re-activation of a previously opened connection when
|
|
427
|
-
* callers switch between known workspaces via openDatabaseByWorkspace().
|
|
428
|
-
*/
|
|
429
|
-
const _dbCache = createDbConnectionCache();
|
|
430
|
-
|
|
431
|
-
/** Test helper: expose the internal cache for inspection. Not for production use. */
|
|
432
|
-
export function _getDbCache(): ReadonlyMap<string, DbConnectionCacheEntry> {
|
|
433
|
-
return _dbCache.asReadonlyMap();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function closeCachedConnection(entry: DbConnectionCacheEntry, source: "all" | "workspace"): void {
|
|
437
|
-
try {
|
|
438
|
-
entry.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
439
|
-
} catch (e) {
|
|
440
|
-
if (source === "workspace") logWarning("db", `WAL checkpoint (byWorkspace) failed: ${(e as Error).message}`);
|
|
441
|
-
}
|
|
442
|
-
try {
|
|
443
|
-
entry.db.exec("PRAGMA incremental_vacuum(64)");
|
|
444
|
-
} catch (e) {
|
|
445
|
-
if (source === "workspace") logWarning("db", `incremental vacuum (byWorkspace) failed: ${(e as Error).message}`);
|
|
446
|
-
}
|
|
447
|
-
try {
|
|
448
|
-
entry.db.close();
|
|
449
|
-
} catch (e) {
|
|
450
|
-
if (source === "workspace") logWarning("db", `database close (byWorkspace) failed: ${(e as Error).message}`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Close and evict every entry in the workspace connection cache, then call
|
|
456
|
-
* closeDatabase() to close the active connection.
|
|
457
|
-
*
|
|
458
|
-
* Use this for test teardown or process-shutdown paths where every open
|
|
459
|
-
* connection must be flushed. Normal callers should use closeDatabase() or
|
|
460
|
-
* closeDatabaseByWorkspace() instead.
|
|
461
|
-
*/
|
|
462
|
-
export function closeAllDatabases(): void {
|
|
463
|
-
// Close all non-active cached connections first.
|
|
464
|
-
_dbCache.closeNonActive(currentDb, (entry) => closeCachedConnection(entry, "all"));
|
|
465
|
-
closeDatabase();
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Open (or reuse) the database connection scoped to the given workspace.
|
|
470
|
-
*
|
|
471
|
-
* Uses workspace.identityKey as the cache key, so sibling worktrees of the
|
|
472
|
-
* same project resolve to the same connection. On a cache hit the existing
|
|
473
|
-
* adapter is reactivated as the current connection without re-opening the
|
|
474
|
-
* file. On a cache miss, delegates to openDatabase() for the full
|
|
475
|
-
* open + schema-init + migration flow, then caches the result.
|
|
476
|
-
*
|
|
477
|
-
* When switching to a different workspace, the previously active connection
|
|
478
|
-
* is preserved in the cache (not closed), so callers can switch back to it
|
|
479
|
-
* cheaply via a subsequent openDatabaseByWorkspace() call.
|
|
480
|
-
*
|
|
481
|
-
* @param workspace A GsdWorkspace created by createWorkspace().
|
|
482
|
-
* @returns true if the connection is open and ready, false otherwise.
|
|
483
|
-
*/
|
|
484
|
-
export function openDatabaseByWorkspace(workspace: GsdWorkspace): boolean {
|
|
485
|
-
const key = workspace.identityKey;
|
|
486
|
-
const dbPath = workspace.contract.projectDb;
|
|
487
|
-
|
|
488
|
-
const cached = _dbCache.get(key);
|
|
489
|
-
if (cached) {
|
|
490
|
-
// Reactivate the cached connection as the current singleton.
|
|
491
|
-
currentDb = cached.db;
|
|
492
|
-
currentPath = cached.dbPath;
|
|
493
|
-
currentPid = process.pid;
|
|
494
|
-
_dbOpenState.markAttempted();
|
|
495
|
-
_currentIdentityKey = key;
|
|
496
|
-
return true;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Cache miss — need to open a new connection.
|
|
500
|
-
//
|
|
501
|
-
// If there is a currently active workspace connection, stash it in the
|
|
502
|
-
// cache under its identity key before calling openDatabase(), because
|
|
503
|
-
// openDatabase() will call closeDatabase() when the path changes (which
|
|
504
|
-
// would destroy the existing adapter). By nulling out currentDb first,
|
|
505
|
-
// we prevent openDatabase() from closing the live adapter.
|
|
506
|
-
let oldDb: typeof currentDb = null;
|
|
507
|
-
let oldPath: typeof currentPath = null;
|
|
508
|
-
let oldPid: typeof currentPid = 0;
|
|
509
|
-
let oldKey: typeof _currentIdentityKey = null;
|
|
510
|
-
|
|
511
|
-
if (currentDb !== null && _currentIdentityKey !== null) {
|
|
512
|
-
// Snapshot the old globals so we can restore them on failure.
|
|
513
|
-
oldDb = currentDb;
|
|
514
|
-
oldPath = currentPath;
|
|
515
|
-
oldPid = currentPid;
|
|
516
|
-
oldKey = _currentIdentityKey;
|
|
517
|
-
// Save the current connection so it stays alive in the cache.
|
|
518
|
-
_dbCache.set(_currentIdentityKey, {
|
|
519
|
-
dbPath: currentPath!,
|
|
520
|
-
db: currentDb,
|
|
521
|
-
});
|
|
522
|
-
// Detach from globals so openDatabase() opens fresh without closing it.
|
|
523
|
-
currentDb = null;
|
|
524
|
-
currentPath = null;
|
|
525
|
-
currentPid = 0;
|
|
526
|
-
_currentIdentityKey = null;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Run the full open/schema/migration flow for the new workspace.
|
|
530
|
-
// openDatabase() can throw on corrupt DB or permission error — catch so we
|
|
531
|
-
// can restore the previous connection rather than leaving globals null.
|
|
532
|
-
let opened: boolean;
|
|
533
|
-
try {
|
|
534
|
-
opened = openDatabase(dbPath);
|
|
535
|
-
} catch (err) {
|
|
536
|
-
// Failed to open the new DB. Restore the previous workspace connection so
|
|
537
|
-
// the caller's workspace remains active (it is still safe in _dbCache).
|
|
538
|
-
if (oldDb !== null) {
|
|
539
|
-
currentDb = oldDb;
|
|
540
|
-
currentPath = oldPath;
|
|
541
|
-
currentPid = oldPid;
|
|
542
|
-
_currentIdentityKey = oldKey;
|
|
543
|
-
}
|
|
544
|
-
throw err;
|
|
545
|
-
}
|
|
546
|
-
if (opened && currentDb) {
|
|
547
|
-
_dbCache.set(key, { dbPath, db: currentDb });
|
|
548
|
-
_currentIdentityKey = key;
|
|
549
|
-
} else if (!opened && oldDb !== null) {
|
|
550
|
-
// Restore the previous connection so the caller's workspace remains active.
|
|
551
|
-
// The failed attempt left no live adapter, so the globals stayed null.
|
|
552
|
-
currentDb = oldDb;
|
|
553
|
-
currentPath = oldPath;
|
|
554
|
-
currentPid = oldPid;
|
|
555
|
-
_currentIdentityKey = oldKey;
|
|
556
|
-
}
|
|
557
|
-
return opened;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Open (or reuse) the database connection scoped to the workspace in a
|
|
562
|
-
* MilestoneScope. Thin delegation to openDatabaseByWorkspace().
|
|
563
|
-
*/
|
|
564
|
-
export function openDatabaseByScope(scope: MilestoneScope): boolean {
|
|
565
|
-
return openDatabaseByWorkspace(scope.workspace);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Close the database connection for the given workspace and remove it from
|
|
570
|
-
* the cache. If the workspace's connection is currently active (currentDb),
|
|
571
|
-
* performs a full closeDatabase() including WAL checkpoint. Otherwise only
|
|
572
|
-
* removes the cache entry (the adapter was already replaced by a later open).
|
|
573
|
-
*/
|
|
574
|
-
export function closeDatabaseByWorkspace(workspace: GsdWorkspace): void {
|
|
575
|
-
const key = workspace.identityKey;
|
|
576
|
-
const cached = _dbCache.get(key);
|
|
577
|
-
if (!cached) return;
|
|
578
|
-
|
|
579
|
-
_dbCache.delete(key);
|
|
580
|
-
|
|
581
|
-
if (currentDb === cached.db) {
|
|
582
|
-
// This workspace's connection is the active one — full close.
|
|
583
|
-
closeDatabase();
|
|
584
|
-
} else {
|
|
585
|
-
// Connection was displaced by a later open; close the adapter directly.
|
|
586
|
-
closeCachedConnection(cached, "workspace");
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
export function getDbProvider(): ProviderName | null {
|
|
591
|
-
providerLoader.load();
|
|
592
|
-
return providerLoader.getProviderName();
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
export function isDbAvailable(): boolean {
|
|
596
|
-
return currentDb !== null;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Returns true if openDatabase() has been called at least once this session.
|
|
601
|
-
* Used to distinguish "DB not yet initialized" from "DB genuinely unavailable"
|
|
602
|
-
* so that early callers (e.g. before_agent_start context injection) don't
|
|
603
|
-
* trigger a false degraded-mode warning.
|
|
604
|
-
*/
|
|
605
|
-
export function wasDbOpenAttempted(): boolean {
|
|
606
|
-
return _dbOpenState.snapshot().attempted;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
export function getDbStatus(): {
|
|
610
|
-
available: boolean;
|
|
611
|
-
provider: ProviderName | null;
|
|
612
|
-
attempted: boolean;
|
|
613
|
-
lastError: Error | null;
|
|
614
|
-
lastPhase: DbOpenPhase | null;
|
|
615
|
-
} {
|
|
616
|
-
providerLoader.load();
|
|
617
|
-
const openState = _dbOpenState.snapshot();
|
|
618
|
-
return {
|
|
619
|
-
available: currentDb !== null,
|
|
620
|
-
provider: providerLoader.getProviderName(),
|
|
621
|
-
attempted: openState.attempted,
|
|
622
|
-
lastError: openState.lastError,
|
|
623
|
-
lastPhase: openState.lastPhase,
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
export function openDatabase(path: string): boolean {
|
|
628
|
-
_dbOpenState.markAttempted();
|
|
629
|
-
if (currentDb && currentPath !== path) closeDatabase();
|
|
630
|
-
if (currentDb && currentPath === path) return true;
|
|
631
|
-
|
|
632
|
-
// Reset error state only when a new open attempt is actually going to run.
|
|
633
|
-
_dbOpenState.clearError();
|
|
634
|
-
|
|
635
|
-
let rawDb: unknown;
|
|
636
|
-
let fallbackOpen: SqliteFallbackOpen | null = null;
|
|
637
|
-
try {
|
|
638
|
-
rawDb = providerLoader.openRaw(path);
|
|
639
|
-
} catch (primaryErr) {
|
|
640
|
-
_dbOpenState.recordError("open", primaryErr);
|
|
641
|
-
// node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
|
|
642
|
-
fallbackOpen = providerLoader.tryOpenBetterSqliteFallback(path);
|
|
643
|
-
if (fallbackOpen) {
|
|
644
|
-
rawDb = fallbackOpen.rawDb;
|
|
645
|
-
_dbOpenState.clearError();
|
|
646
|
-
}
|
|
647
|
-
if (!rawDb) throw primaryErr;
|
|
648
|
-
}
|
|
649
|
-
if (!rawDb) return false;
|
|
650
|
-
|
|
651
|
-
const adapter = createDbAdapter(rawDb);
|
|
652
|
-
const fileBacked = path !== ":memory:";
|
|
653
|
-
try {
|
|
654
|
-
initSchema(adapter, fileBacked, path);
|
|
655
|
-
} catch (err) {
|
|
656
|
-
// Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
|
|
657
|
-
// Attempt VACUUM recovery before giving up (see #2519).
|
|
658
|
-
if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
|
|
659
|
-
try {
|
|
660
|
-
adapter.exec("VACUUM");
|
|
661
|
-
initSchema(adapter, fileBacked, path);
|
|
662
|
-
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
663
|
-
} catch (retryErr) {
|
|
664
|
-
_dbOpenState.recordError("vacuum-recovery", retryErr);
|
|
665
|
-
try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
|
|
666
|
-
throw retryErr;
|
|
667
|
-
}
|
|
668
|
-
} else {
|
|
669
|
-
_dbOpenState.recordError("initSchema", err);
|
|
670
|
-
try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
|
|
671
|
-
throw err;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Commit fallback provider switch only after open + schema both succeeded.
|
|
676
|
-
if (fallbackOpen) providerLoader.commitFallback(fallbackOpen);
|
|
677
|
-
|
|
678
|
-
currentDb = adapter;
|
|
679
|
-
currentPath = path;
|
|
680
|
-
currentPid = process.pid;
|
|
681
|
-
|
|
682
|
-
if (!_exitHandlerRegistered) {
|
|
683
|
-
_exitHandlerRegistered = true;
|
|
684
|
-
process.on("exit", () => { try { closeDatabase(); } catch (e) { logWarning("db", `exit handler close failed: ${(e as Error).message}`); } });
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return true;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
export function closeDatabase(): void {
|
|
691
|
-
if (currentDb) {
|
|
692
|
-
try {
|
|
693
|
-
currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
694
|
-
} catch (e) { logWarning("db", `WAL checkpoint failed: ${(e as Error).message}`); }
|
|
695
|
-
try {
|
|
696
|
-
// Incremental vacuum to reclaim space without blocking
|
|
697
|
-
currentDb.exec('PRAGMA incremental_vacuum(64)');
|
|
698
|
-
} catch (e) { logWarning("db", `incremental vacuum failed: ${(e as Error).message}`); }
|
|
699
|
-
try {
|
|
700
|
-
currentDb.close();
|
|
701
|
-
} catch (e) { logWarning("db", `database close failed: ${(e as Error).message}`); }
|
|
702
|
-
// If this connection was workspace-tracked, evict it from the cache so
|
|
703
|
-
// subsequent openDatabaseByWorkspace() calls re-open rather than reactivate
|
|
704
|
-
// a closed adapter.
|
|
705
|
-
if (_currentIdentityKey !== null) {
|
|
706
|
-
_dbCache.delete(_currentIdentityKey);
|
|
707
|
-
_currentIdentityKey = null;
|
|
708
|
-
}
|
|
709
|
-
currentDb = null;
|
|
710
|
-
currentPath = null;
|
|
711
|
-
currentPid = 0;
|
|
712
|
-
}
|
|
713
|
-
// Reset session-scoped state unconditionally so stale error info from a
|
|
714
|
-
// failed open doesn't persist into the next open attempt or status check.
|
|
715
|
-
_dbOpenState.reset();
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/**
|
|
719
|
-
* Re-open the active database connection from disk.
|
|
720
|
-
*
|
|
721
|
-
* Auto-mode can observe artifacts written by a workflow server running in a
|
|
722
|
-
* different process before its long-lived singleton has re-synchronized. The
|
|
723
|
-
* recovery path uses this to force the next state derivation to read from the
|
|
724
|
-
* current on-disk database instead of continuing with a possibly stale handle.
|
|
725
|
-
*/
|
|
726
|
-
export function refreshOpenDatabaseFromDisk(): boolean {
|
|
727
|
-
if (!currentDb || !currentPath) return false;
|
|
728
|
-
if (currentPath === ":memory:") return false;
|
|
729
|
-
|
|
730
|
-
const dbPath = currentPath;
|
|
731
|
-
const identityKey = _currentIdentityKey;
|
|
732
|
-
|
|
733
|
-
try {
|
|
734
|
-
closeDatabase();
|
|
735
|
-
const opened = openDatabase(dbPath);
|
|
736
|
-
if (opened && identityKey && currentDb) {
|
|
737
|
-
_dbCache.set(identityKey, { dbPath, db: currentDb });
|
|
738
|
-
_currentIdentityKey = identityKey;
|
|
739
|
-
}
|
|
740
|
-
return opened;
|
|
741
|
-
} catch (e) {
|
|
742
|
-
logWarning("db", `database refresh failed: ${(e as Error).message}`);
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
|
|
748
|
-
export function vacuumDatabase(): void {
|
|
749
|
-
if (!currentDb) return;
|
|
750
|
-
try {
|
|
751
|
-
currentDb.exec('VACUUM');
|
|
752
|
-
} catch (e) { logWarning("db", `VACUUM failed: ${(e as Error).message}`); }
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/** Flush WAL into gsd.db so `git add .gsd/gsd.db` stages current state — safe while DB is open. */
|
|
756
|
-
export function checkpointDatabase(): void {
|
|
757
|
-
if (!currentDb) return;
|
|
758
|
-
try {
|
|
759
|
-
currentDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
760
|
-
} catch (e) { logWarning("db", `WAL checkpoint failed: ${(e as Error).message}`); }
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Copy the live database file to `.gsd/backups/<label>-<timestamp>.db` so a
|
|
765
|
-
* destructive operation (e.g. recover, which clears the hierarchy tables) is
|
|
766
|
-
* reversible. Checkpoints the WAL first so the snapshot is complete. Returns
|
|
767
|
-
* the backup path, or null if no DB is open or the copy failed.
|
|
768
|
-
*/
|
|
769
|
-
export function backupDatabaseSnapshot(label: string): string | null {
|
|
770
|
-
if (!currentPath) return null;
|
|
771
|
-
try {
|
|
772
|
-
checkpointDatabase();
|
|
773
|
-
const backupsDir = join(dirname(currentPath), "backups");
|
|
774
|
-
mkdirSync(backupsDir, { recursive: true });
|
|
775
|
-
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
776
|
-
const dest = join(backupsDir, `${label}-${stamp}.db`);
|
|
777
|
-
copyFileSync(currentPath, dest);
|
|
778
|
-
return dest;
|
|
779
|
-
} catch (e) {
|
|
780
|
-
logWarning("db", `database snapshot failed: ${(e as Error).message}`);
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
const _transactionRunner = createDbTransactionRunner();
|
|
786
|
-
|
|
787
|
-
function createTransactionControls(db: DbAdapter) {
|
|
788
|
-
return {
|
|
789
|
-
begin: () => db.exec("BEGIN"),
|
|
790
|
-
beginRead: () => db.exec("BEGIN DEFERRED"),
|
|
791
|
-
commit: () => db.exec("COMMIT"),
|
|
792
|
-
rollback: () => db.exec("ROLLBACK"),
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Whether the current call is running inside an active SQLite transaction.
|
|
798
|
-
* Statement-time recovery paths (e.g. VACUUM retry on a malformed memory
|
|
799
|
-
* store) MUST gate on this — SQLite refuses VACUUM inside a transaction
|
|
800
|
-
* and would mask the original error with a secondary "cannot VACUUM" throw.
|
|
801
|
-
*/
|
|
802
|
-
export function isInTransaction(): boolean {
|
|
803
|
-
return _transactionRunner.isInTransaction();
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
export function transaction<T>(fn: () => T): T {
|
|
807
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
808
|
-
return _transactionRunner.transaction(createTransactionControls(currentDb), fn);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* Wrap a block of reads in a DEFERRED transaction so that all SELECTs observe
|
|
813
|
-
* a consistent snapshot of the DB even if a concurrent writer commits between
|
|
814
|
-
* them. Use this for multi-query read flows (e.g. tool executors that query
|
|
815
|
-
* milestone + slices + counts and want one snapshot). Re-entrant — if already
|
|
816
|
-
* inside a transaction, runs fn() without starting a nested one.
|
|
817
|
-
*/
|
|
818
|
-
export function readTransaction<T>(fn: () => T): T {
|
|
819
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
820
|
-
|
|
821
|
-
return _transactionRunner.readTransaction(createTransactionControls(currentDb), fn, (rollbackErr) => {
|
|
822
|
-
// A failed ROLLBACK after a failed read is a split-brain signal —
|
|
823
|
-
// the transaction is in an indeterminate state. Surface it via the
|
|
824
|
-
// logger instead of swallowing it.
|
|
825
|
-
logError("db", "snapshotState ROLLBACK failed", {
|
|
826
|
-
error: rollbackErr.message,
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
}
|
|
66
|
+
import { TERMINAL_STATUS_SQL } from "./db/sql-constants.js";
|
|
67
|
+
import { applyStatusTransition } from "./db/writers/status.js";
|
|
830
68
|
|
|
831
69
|
export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
832
|
-
if (!
|
|
833
|
-
|
|
70
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
71
|
+
getDbOrNull()!.prepare(
|
|
834
72
|
`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
835
73
|
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :source, :superseded_by)`,
|
|
836
74
|
).run({
|
|
@@ -847,22 +85,11 @@ export function insertDecision(d: Omit<Decision, "seq">): void {
|
|
|
847
85
|
});
|
|
848
86
|
}
|
|
849
87
|
|
|
850
|
-
export function getDecisionById(id: string): Decision | null {
|
|
851
|
-
if (!currentDb) return null;
|
|
852
|
-
const row = currentDb.prepare("SELECT * FROM decisions WHERE id = ?").get(id);
|
|
853
|
-
if (!row) return null;
|
|
854
|
-
return rowToDecision(row);
|
|
855
|
-
}
|
|
856
88
|
|
|
857
|
-
export function getActiveDecisions(): Decision[] {
|
|
858
|
-
if (!currentDb) return [];
|
|
859
|
-
const rows = currentDb.prepare("SELECT * FROM active_decisions").all();
|
|
860
|
-
return rows.map(rowToActiveDecision);
|
|
861
|
-
}
|
|
862
89
|
|
|
863
90
|
export function insertRequirement(r: Requirement): void {
|
|
864
|
-
if (!
|
|
865
|
-
|
|
91
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
92
|
+
getDbOrNull()!.prepare(
|
|
866
93
|
`INSERT INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
867
94
|
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`,
|
|
868
95
|
).run({
|
|
@@ -881,58 +108,15 @@ export function insertRequirement(r: Requirement): void {
|
|
|
881
108
|
});
|
|
882
109
|
}
|
|
883
110
|
|
|
884
|
-
export function getRequirementById(id: string): Requirement | null {
|
|
885
|
-
if (!currentDb) return null;
|
|
886
|
-
const row = currentDb.prepare("SELECT * FROM requirements WHERE id = ?").get(id);
|
|
887
|
-
if (!row) return null;
|
|
888
|
-
return rowToRequirement(row);
|
|
889
|
-
}
|
|
890
111
|
|
|
891
|
-
export function getActiveRequirements(): Requirement[] {
|
|
892
|
-
if (!currentDb) return [];
|
|
893
|
-
const rows = currentDb.prepare("SELECT * FROM active_requirements").all();
|
|
894
|
-
return rows.map(rowToActiveRequirement);
|
|
895
|
-
}
|
|
896
112
|
|
|
897
|
-
export function getRequirementCounts(): {
|
|
898
|
-
active: number;
|
|
899
|
-
validated: number;
|
|
900
|
-
deferred: number;
|
|
901
|
-
outOfScope: number;
|
|
902
|
-
blocked: number;
|
|
903
|
-
total: number;
|
|
904
|
-
} {
|
|
905
|
-
if (!currentDb) {
|
|
906
|
-
return { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 };
|
|
907
|
-
}
|
|
908
|
-
const rows = currentDb
|
|
909
|
-
.prepare("SELECT lower(status) as status, COUNT(*) as count FROM requirements GROUP BY lower(status)")
|
|
910
|
-
.all();
|
|
911
|
-
return rowsToRequirementCounts(rows);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export function getDbOwnerPid(): number {
|
|
915
|
-
return currentPid;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
export function getDbPath(): string | null {
|
|
919
|
-
return currentPath;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
export function _getAdapter(): DbAdapter | null {
|
|
923
|
-
return currentDb;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
export function _resetProvider(): void {
|
|
927
|
-
providerLoader.reset();
|
|
928
|
-
}
|
|
929
113
|
|
|
930
114
|
export function upsertDecision(d: Omit<Decision, "seq">): void {
|
|
931
|
-
if (!
|
|
115
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
932
116
|
// Use ON CONFLICT DO UPDATE instead of INSERT OR REPLACE to preserve the
|
|
933
117
|
// seq column. INSERT OR REPLACE deletes then reinserts, resetting seq and
|
|
934
118
|
// corrupting decision ordering in DECISIONS.md after reconcile replay.
|
|
935
|
-
|
|
119
|
+
getDbOrNull()!.prepare(
|
|
936
120
|
`INSERT INTO decisions (id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
937
121
|
VALUES (:id, :when_context, :scope, :decision, :choice, :rationale, :revisable, :made_by, :source, :superseded_by)
|
|
938
122
|
ON CONFLICT(id) DO UPDATE SET
|
|
@@ -960,8 +144,8 @@ export function upsertDecision(d: Omit<Decision, "seq">): void {
|
|
|
960
144
|
}
|
|
961
145
|
|
|
962
146
|
export function upsertRequirement(r: Requirement): void {
|
|
963
|
-
if (!
|
|
964
|
-
|
|
147
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
148
|
+
getDbOrNull()!.prepare(
|
|
965
149
|
`INSERT OR REPLACE INTO requirements (id, class, status, description, why, source, primary_owner, supporting_slices, validation, notes, full_content, superseded_by)
|
|
966
150
|
VALUES (:id, :class, :status, :description, :why, :source, :primary_owner, :supporting_slices, :validation, :notes, :full_content, :superseded_by)`,
|
|
967
151
|
).run({
|
|
@@ -981,18 +165,18 @@ export function upsertRequirement(r: Requirement): void {
|
|
|
981
165
|
}
|
|
982
166
|
|
|
983
167
|
export function clearArtifacts(): void {
|
|
984
|
-
if (!
|
|
985
|
-
try {
|
|
168
|
+
if (!getDbOrNull()!) return;
|
|
169
|
+
try { getDbOrNull()!.exec("DELETE FROM artifacts"); } catch (e) { logWarning("db", `clearArtifacts failed: ${(e as Error).message}`); }
|
|
986
170
|
}
|
|
987
171
|
|
|
988
172
|
export function clearDecisions(): void {
|
|
989
|
-
if (!
|
|
990
|
-
try {
|
|
173
|
+
if (!getDbOrNull()!) return;
|
|
174
|
+
try { getDbOrNull()!.exec("DELETE FROM decisions"); } catch (e) { logWarning("db", `clearDecisions failed: ${(e as Error).message}`); }
|
|
991
175
|
}
|
|
992
176
|
|
|
993
177
|
export function clearRequirements(): void {
|
|
994
|
-
if (!
|
|
995
|
-
try {
|
|
178
|
+
if (!getDbOrNull()!) return;
|
|
179
|
+
try { getDbOrNull()!.exec("DELETE FROM requirements"); } catch (e) { logWarning("db", `clearRequirements failed: ${(e as Error).message}`); }
|
|
996
180
|
}
|
|
997
181
|
|
|
998
182
|
export function insertArtifact(a: {
|
|
@@ -1003,9 +187,9 @@ export function insertArtifact(a: {
|
|
|
1003
187
|
task_id: string | null;
|
|
1004
188
|
full_content: string;
|
|
1005
189
|
}): void {
|
|
1006
|
-
if (!
|
|
190
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1007
191
|
const contentHash = createHash("sha256").update(a.full_content).digest("hex");
|
|
1008
|
-
|
|
192
|
+
getDbOrNull()!.prepare(
|
|
1009
193
|
`INSERT OR REPLACE INTO artifacts (path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash)
|
|
1010
194
|
VALUES (:path, :artifact_type, :milestone_id, :slice_id, :task_id, :full_content, :imported_at, :content_hash)`,
|
|
1011
195
|
).run({
|
|
@@ -1062,9 +246,9 @@ export function insertMilestone(m: {
|
|
|
1062
246
|
status?: string;
|
|
1063
247
|
depends_on?: string[];
|
|
1064
248
|
planning?: Partial<MilestonePlanningRecord>;
|
|
1065
|
-
}):
|
|
1066
|
-
if (!
|
|
1067
|
-
|
|
249
|
+
}): boolean {
|
|
250
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
251
|
+
const result = getDbOrNull()!.prepare(
|
|
1068
252
|
`INSERT OR IGNORE INTO milestones (
|
|
1069
253
|
id, title, status, depends_on, created_at,
|
|
1070
254
|
vision, success_criteria, key_risks, proof_strategy,
|
|
@@ -1095,12 +279,13 @@ export function insertMilestone(m: {
|
|
|
1095
279
|
":definition_of_done": JSON.stringify(m.planning?.definitionOfDone ?? []),
|
|
1096
280
|
":requirement_coverage": m.planning?.requirementCoverage ?? "",
|
|
1097
281
|
":boundary_map_markdown": m.planning?.boundaryMapMarkdown ?? "",
|
|
1098
|
-
});
|
|
282
|
+
}) as { changes?: number };
|
|
283
|
+
return (result.changes ?? 0) > 0;
|
|
1099
284
|
}
|
|
1100
285
|
|
|
1101
286
|
export function upsertMilestonePlanning(milestoneId: string, planning: Partial<MilestonePlanningRecord> & { title?: string; status?: string; depends_on?: string[] }): void {
|
|
1102
|
-
if (!
|
|
1103
|
-
|
|
287
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
288
|
+
getDbOrNull()!.prepare(
|
|
1104
289
|
`UPDATE milestones SET
|
|
1105
290
|
title = COALESCE(NULLIF(:title, ''), title),
|
|
1106
291
|
status = COALESCE(NULLIF(:status, ''), status),
|
|
@@ -1149,8 +334,13 @@ export function insertSlice(s: {
|
|
|
1149
334
|
sketchScope?: string;
|
|
1150
335
|
planning?: Partial<SlicePlanningRecord>;
|
|
1151
336
|
}): void {
|
|
1152
|
-
if (!
|
|
1153
|
-
|
|
337
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
338
|
+
const SLICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
339
|
+
const invalidDep = (s.depends ?? []).find(d => !SLICE_ID_RE.test(d));
|
|
340
|
+
if (invalidDep !== undefined) {
|
|
341
|
+
throw new GSDError(GSD_STALE_STATE, `insertSlice: depends element "${invalidDep}" is not a valid slice ID`);
|
|
342
|
+
}
|
|
343
|
+
getDbOrNull()!.prepare(
|
|
1154
344
|
`INSERT INTO slices (
|
|
1155
345
|
milestone_id, id, title, status, risk, depends, demo, created_at,
|
|
1156
346
|
goal, success_criteria, proof_level, integration_closure, observability_impact, target_repositories, sequence,
|
|
@@ -1214,29 +404,16 @@ export function insertSlice(s: {
|
|
|
1214
404
|
|
|
1215
405
|
// ADR-011: sketch-then-refine helpers
|
|
1216
406
|
export function setSliceSketchFlag(milestoneId: string, sliceId: string, isSketch: boolean): void {
|
|
1217
|
-
if (!
|
|
1218
|
-
|
|
407
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
408
|
+
getDbOrNull()!.prepare(
|
|
1219
409
|
`UPDATE slices SET is_sketch = :is_sketch WHERE milestone_id = :mid AND id = :sid`,
|
|
1220
410
|
).run({ ":is_sketch": isSketch ? 1 : 0, ":mid": milestoneId, ":sid": sliceId });
|
|
1221
411
|
}
|
|
1222
412
|
|
|
1223
|
-
/**
|
|
1224
|
-
* ADR-017 raw primitive: returns slice IDs in a milestone whose is_sketch flag
|
|
1225
|
-
* is still 1. The stale-sketch-flag drift handler at
|
|
1226
|
-
* `state-reconciliation/drift/sketch-flag.ts` composes this with PLAN.md
|
|
1227
|
-
* existence checks to detect drift, then writes via `setSliceSketchFlag`.
|
|
1228
|
-
*/
|
|
1229
|
-
export function getSketchedSliceIds(milestoneId: string): string[] {
|
|
1230
|
-
if (!currentDb) return [];
|
|
1231
|
-
const rows = currentDb.prepare(
|
|
1232
|
-
`SELECT id FROM slices WHERE milestone_id = :mid AND is_sketch = 1`,
|
|
1233
|
-
).all({ ":mid": milestoneId }) as Array<{ id: string }>;
|
|
1234
|
-
return rows.map((r) => r.id);
|
|
1235
|
-
}
|
|
1236
413
|
|
|
1237
414
|
export function upsertSlicePlanning(milestoneId: string, sliceId: string, planning: Partial<SlicePlanningRecord>): void {
|
|
1238
|
-
if (!
|
|
1239
|
-
|
|
415
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
416
|
+
getDbOrNull()!.prepare(
|
|
1240
417
|
`UPDATE slices SET
|
|
1241
418
|
goal = COALESCE(:goal, goal),
|
|
1242
419
|
success_criteria = COALESCE(:success_criteria, success_criteria),
|
|
@@ -1276,8 +453,8 @@ export function insertTask(t: {
|
|
|
1276
453
|
sequence?: number;
|
|
1277
454
|
planning?: Partial<TaskPlanningRecord>;
|
|
1278
455
|
}): void {
|
|
1279
|
-
if (!
|
|
1280
|
-
|
|
456
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
457
|
+
getDbOrNull()!.prepare(
|
|
1281
458
|
`INSERT INTO tasks (
|
|
1282
459
|
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
1283
460
|
verification_result, duration, completed_at, blocker_discovered,
|
|
@@ -1353,29 +530,19 @@ export function insertTask(t: {
|
|
|
1353
530
|
}
|
|
1354
531
|
|
|
1355
532
|
export function updateTaskStatus(milestoneId: string, sliceId: string, taskId: string, status: string, completedAt?: string): void {
|
|
1356
|
-
|
|
1357
|
-
currentDb.prepare(
|
|
1358
|
-
`UPDATE tasks SET status = :status, completed_at = :completed_at
|
|
1359
|
-
WHERE milestone_id = :milestone_id AND slice_id = :slice_id AND id = :id`,
|
|
1360
|
-
).run({
|
|
1361
|
-
":status": status,
|
|
1362
|
-
":completed_at": completedAt ?? null,
|
|
1363
|
-
":milestone_id": milestoneId,
|
|
1364
|
-
":slice_id": sliceId,
|
|
1365
|
-
":id": taskId,
|
|
1366
|
-
});
|
|
533
|
+
applyStatusTransition({ entity: "task", milestoneId, sliceId, taskId, status, completedAt });
|
|
1367
534
|
}
|
|
1368
535
|
|
|
1369
536
|
export function setTaskBlockerDiscovered(milestoneId: string, sliceId: string, taskId: string, discovered: boolean): void {
|
|
1370
|
-
if (!
|
|
1371
|
-
|
|
537
|
+
if (!getDbOrNull()!) return;
|
|
538
|
+
getDbOrNull()!.prepare(
|
|
1372
539
|
`UPDATE tasks SET blocker_discovered = :discovered WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
|
|
1373
540
|
).run({ ":discovered": discovered ? 1 : 0, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1374
541
|
}
|
|
1375
542
|
|
|
1376
543
|
export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId: string, planning: Partial<TaskPlanningRecord>): void {
|
|
1377
|
-
if (!
|
|
1378
|
-
|
|
544
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
545
|
+
getDbOrNull()!.prepare(
|
|
1379
546
|
`UPDATE tasks SET
|
|
1380
547
|
title = COALESCE(:title, title),
|
|
1381
548
|
description = COALESCE(:description, description),
|
|
@@ -1405,95 +572,28 @@ export function upsertTaskPlanning(milestoneId: string, sliceId: string, taskId:
|
|
|
1405
572
|
});
|
|
1406
573
|
}
|
|
1407
574
|
|
|
1408
|
-
export function getSlice(milestoneId: string, sliceId: string): SliceRow | null {
|
|
1409
|
-
if (!currentDb) return null;
|
|
1410
|
-
const row = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid AND id = :sid").get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1411
|
-
if (!row) return null;
|
|
1412
|
-
return rowToSlice(row);
|
|
1413
|
-
}
|
|
1414
575
|
|
|
1415
576
|
export function updateSliceStatus(milestoneId: string, sliceId: string, status: string, completedAt?: string): void {
|
|
1416
|
-
|
|
1417
|
-
currentDb.prepare(
|
|
1418
|
-
`UPDATE slices SET status = :status, completed_at = :completed_at
|
|
1419
|
-
WHERE milestone_id = :milestone_id AND id = :id`,
|
|
1420
|
-
).run({
|
|
1421
|
-
":status": status,
|
|
1422
|
-
":completed_at": completedAt ?? null,
|
|
1423
|
-
":milestone_id": milestoneId,
|
|
1424
|
-
":id": sliceId,
|
|
1425
|
-
});
|
|
577
|
+
applyStatusTransition({ entity: "slice", milestoneId, sliceId, status, completedAt });
|
|
1426
578
|
}
|
|
1427
579
|
|
|
1428
580
|
export function setTaskSummaryMd(milestoneId: string, sliceId: string, taskId: string, md: string): void {
|
|
1429
|
-
if (!
|
|
1430
|
-
|
|
581
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
582
|
+
getDbOrNull()!.prepare(
|
|
1431
583
|
`UPDATE tasks SET full_summary_md = :md WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
|
|
1432
584
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId, ":md": md });
|
|
1433
585
|
}
|
|
1434
586
|
|
|
1435
587
|
export function setSliceSummaryMd(milestoneId: string, sliceId: string, summaryMd: string, uatMd: string): void {
|
|
1436
|
-
if (!
|
|
1437
|
-
|
|
588
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
589
|
+
getDbOrNull()!.prepare(
|
|
1438
590
|
`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`,
|
|
1439
591
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
|
|
1440
592
|
}
|
|
1441
593
|
|
|
1442
|
-
export function getTask(milestoneId: string, sliceId: string, taskId: string): TaskRow | null {
|
|
1443
|
-
if (!currentDb) return null;
|
|
1444
|
-
const row = currentDb.prepare(
|
|
1445
|
-
"SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid",
|
|
1446
|
-
).get({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1447
|
-
if (!row) return null;
|
|
1448
|
-
return rowToTask(row);
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
export function getSliceTasks(milestoneId: string, sliceId: string): TaskRow[] {
|
|
1452
|
-
if (!currentDb) return [];
|
|
1453
|
-
const rows = currentDb.prepare(
|
|
1454
|
-
"SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid ORDER BY sequence, id",
|
|
1455
|
-
).all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1456
|
-
return rows.map(rowToTask);
|
|
1457
|
-
}
|
|
1458
594
|
|
|
1459
|
-
export function getCompletedMilestoneTaskFileHints(milestoneId: string): string[] {
|
|
1460
|
-
if (!currentDb) return [];
|
|
1461
|
-
const rows = currentDb.prepare(
|
|
1462
|
-
`SELECT files, key_files
|
|
1463
|
-
FROM tasks
|
|
1464
|
-
WHERE milestone_id = :mid AND status IN ('complete', 'done')`,
|
|
1465
|
-
).all({ ":mid": milestoneId }) as Array<Record<string, unknown>>;
|
|
1466
|
-
|
|
1467
|
-
const hints = new Set<string>();
|
|
1468
|
-
for (const row of rows) {
|
|
1469
|
-
for (const raw of [row["files"], row["key_files"]]) {
|
|
1470
|
-
for (const file of parseStringArrayColumn(raw)) {
|
|
1471
|
-
const normalized = normalizeRepoPath(file);
|
|
1472
|
-
if (normalized) hints.add(normalized);
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
return [...hints];
|
|
1477
|
-
}
|
|
1478
595
|
|
|
1479
|
-
function parseStringArrayColumn(raw: unknown): string[] {
|
|
1480
|
-
if (Array.isArray(raw)) return raw.filter((entry): entry is string => typeof entry === "string");
|
|
1481
|
-
if (typeof raw !== "string") return [];
|
|
1482
|
-
const trimmed = raw.trim();
|
|
1483
|
-
if (!trimmed) return [];
|
|
1484
|
-
try {
|
|
1485
|
-
const parsed = JSON.parse(trimmed);
|
|
1486
|
-
if (Array.isArray(parsed)) return parsed.filter((entry): entry is string => typeof entry === "string");
|
|
1487
|
-
if (typeof parsed === "string") return [parsed];
|
|
1488
|
-
} catch {
|
|
1489
|
-
return trimmed.split(",");
|
|
1490
|
-
}
|
|
1491
|
-
return [];
|
|
1492
|
-
}
|
|
1493
596
|
|
|
1494
|
-
function normalizeRepoPath(file: string): string {
|
|
1495
|
-
return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
1496
|
-
}
|
|
1497
597
|
|
|
1498
598
|
// ─── ADR-011 Phase 2 escalation helpers ──────────────────────────────────
|
|
1499
599
|
|
|
@@ -1502,8 +602,8 @@ export function setTaskEscalationPending(
|
|
|
1502
602
|
milestoneId: string, sliceId: string, taskId: string,
|
|
1503
603
|
artifactPath: string,
|
|
1504
604
|
): void {
|
|
1505
|
-
if (!
|
|
1506
|
-
|
|
605
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
606
|
+
getDbOrNull()!.prepare(
|
|
1507
607
|
`UPDATE tasks
|
|
1508
608
|
SET escalation_pending = 1,
|
|
1509
609
|
escalation_awaiting_review = 0,
|
|
@@ -1517,8 +617,8 @@ export function setTaskEscalationAwaitingReview(
|
|
|
1517
617
|
milestoneId: string, sliceId: string, taskId: string,
|
|
1518
618
|
artifactPath: string,
|
|
1519
619
|
): void {
|
|
1520
|
-
if (!
|
|
1521
|
-
|
|
620
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
621
|
+
getDbOrNull()!.prepare(
|
|
1522
622
|
`UPDATE tasks
|
|
1523
623
|
SET escalation_awaiting_review = 1,
|
|
1524
624
|
escalation_pending = 0,
|
|
@@ -1531,8 +631,8 @@ export function setTaskEscalationAwaitingReview(
|
|
|
1531
631
|
export function clearTaskEscalationFlags(
|
|
1532
632
|
milestoneId: string, sliceId: string, taskId: string,
|
|
1533
633
|
): void {
|
|
1534
|
-
if (!
|
|
1535
|
-
|
|
634
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
635
|
+
getDbOrNull()!.prepare(
|
|
1536
636
|
`UPDATE tasks
|
|
1537
637
|
SET escalation_pending = 0,
|
|
1538
638
|
escalation_awaiting_review = 0
|
|
@@ -1548,9 +648,9 @@ export function clearTaskEscalationFlags(
|
|
|
1548
648
|
export function claimEscalationOverride(
|
|
1549
649
|
milestoneId: string, sliceId: string, sourceTaskId: string,
|
|
1550
650
|
): boolean {
|
|
1551
|
-
if (!
|
|
651
|
+
if (!getDbOrNull()!) return false;
|
|
1552
652
|
const now = new Date().toISOString();
|
|
1553
|
-
const result =
|
|
653
|
+
const result = getDbOrNull()!.prepare(
|
|
1554
654
|
`UPDATE tasks
|
|
1555
655
|
SET escalation_override_applied_at = :now
|
|
1556
656
|
WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid
|
|
@@ -1562,39 +662,13 @@ export function claimEscalationOverride(
|
|
|
1562
662
|
return changes > 0;
|
|
1563
663
|
}
|
|
1564
664
|
|
|
1565
|
-
/** Find the most recent resolved-but-unapplied escalation override in a slice. */
|
|
1566
|
-
export function findUnappliedEscalationOverride(
|
|
1567
|
-
milestoneId: string, sliceId: string,
|
|
1568
|
-
): { taskId: string; artifactPath: string } | null {
|
|
1569
|
-
if (!currentDb) return null;
|
|
1570
|
-
// Filter BOTH flags: escalation_pending=0 AND escalation_awaiting_review=0
|
|
1571
|
-
// ensures we only claim overrides the user has explicitly resolved.
|
|
1572
|
-
// Without the awaiting_review filter, continueWithDefault=true artifacts
|
|
1573
|
-
// (not yet responded to) would be prematurely claimed, causing the override
|
|
1574
|
-
// to be lost when the user later resolves (#ADR-011 Phase 2 peer-review Bug 2).
|
|
1575
|
-
const row = currentDb.prepare(
|
|
1576
|
-
`SELECT id, escalation_artifact_path AS path
|
|
1577
|
-
FROM tasks
|
|
1578
|
-
WHERE milestone_id = :mid AND slice_id = :sid
|
|
1579
|
-
AND escalation_artifact_path IS NOT NULL
|
|
1580
|
-
AND escalation_override_applied_at IS NULL
|
|
1581
|
-
AND escalation_pending = 0
|
|
1582
|
-
AND escalation_awaiting_review = 0
|
|
1583
|
-
ORDER BY sequence DESC, id DESC
|
|
1584
|
-
LIMIT 1`,
|
|
1585
|
-
).get({ ":mid": milestoneId, ":sid": sliceId }) as
|
|
1586
|
-
| { id: string; path: string | null }
|
|
1587
|
-
| undefined;
|
|
1588
|
-
if (!row || !row.path) return null;
|
|
1589
|
-
return { taskId: row.id, artifactPath: row.path };
|
|
1590
|
-
}
|
|
1591
665
|
|
|
1592
666
|
/** Set the blocker_source provenance field (used when rejecting an escalation). */
|
|
1593
667
|
export function setTaskBlockerSource(
|
|
1594
668
|
milestoneId: string, sliceId: string, taskId: string, source: string,
|
|
1595
669
|
): void {
|
|
1596
|
-
if (!
|
|
1597
|
-
|
|
670
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
671
|
+
getDbOrNull()!.prepare(
|
|
1598
672
|
`UPDATE tasks
|
|
1599
673
|
SET blocker_discovered = 1,
|
|
1600
674
|
blocker_source = :src
|
|
@@ -1602,17 +676,6 @@ export function setTaskBlockerSource(
|
|
|
1602
676
|
).run({ ":src": source, ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1603
677
|
}
|
|
1604
678
|
|
|
1605
|
-
/** List tasks with active escalation artifacts across a milestone (for /gsd escalate list). */
|
|
1606
|
-
export function listEscalationArtifacts(milestoneId: string, includeResolved: boolean = false): TaskRow[] {
|
|
1607
|
-
if (!currentDb) return [];
|
|
1608
|
-
const filter = includeResolved
|
|
1609
|
-
? "escalation_artifact_path IS NOT NULL"
|
|
1610
|
-
: "(escalation_pending = 1 OR escalation_awaiting_review = 1) AND escalation_artifact_path IS NOT NULL";
|
|
1611
|
-
const rows = currentDb.prepare(
|
|
1612
|
-
`SELECT * FROM tasks WHERE milestone_id = :mid AND ${filter} ORDER BY slice_id, sequence, id`,
|
|
1613
|
-
).all({ ":mid": milestoneId });
|
|
1614
|
-
return rows.map(rowToTask);
|
|
1615
|
-
}
|
|
1616
679
|
|
|
1617
680
|
export function insertVerificationEvidence(e: {
|
|
1618
681
|
taskId: string;
|
|
@@ -1623,8 +686,8 @@ export function insertVerificationEvidence(e: {
|
|
|
1623
686
|
verdict: string;
|
|
1624
687
|
durationMs: number;
|
|
1625
688
|
}): void {
|
|
1626
|
-
if (!
|
|
1627
|
-
|
|
689
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
690
|
+
getDbOrNull()!.prepare(
|
|
1628
691
|
`INSERT OR IGNORE INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
1629
692
|
VALUES (:task_id, :slice_id, :milestone_id, :command, :exit_code, :verdict, :duration_ms, :created_at)`,
|
|
1630
693
|
).run({
|
|
@@ -1639,66 +702,35 @@ export function insertVerificationEvidence(e: {
|
|
|
1639
702
|
});
|
|
1640
703
|
}
|
|
1641
704
|
|
|
1642
|
-
export interface VerificationEvidenceRow {
|
|
1643
|
-
id: number;
|
|
1644
|
-
task_id: string;
|
|
1645
|
-
slice_id: string;
|
|
1646
|
-
milestone_id: string;
|
|
1647
|
-
command: string;
|
|
1648
|
-
exit_code: number;
|
|
1649
|
-
verdict: string;
|
|
1650
|
-
duration_ms: number;
|
|
1651
|
-
created_at: string;
|
|
1652
|
-
}
|
|
1653
705
|
|
|
1654
|
-
export function getVerificationEvidence(milestoneId: string, sliceId: string, taskId: string): VerificationEvidenceRow[] {
|
|
1655
|
-
if (!currentDb) return [];
|
|
1656
|
-
const rows = currentDb.prepare(
|
|
1657
|
-
"SELECT * FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid ORDER BY id",
|
|
1658
|
-
).all({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
1659
|
-
return rows as unknown as VerificationEvidenceRow[];
|
|
1660
|
-
}
|
|
1661
706
|
|
|
1662
|
-
export function getAllMilestones(): MilestoneRow[] {
|
|
1663
|
-
if (!currentDb) return [];
|
|
1664
|
-
const rows = currentDb.prepare(
|
|
1665
|
-
"SELECT * FROM milestones ORDER BY CASE WHEN sequence > 0 THEN 0 ELSE 1 END, sequence, id",
|
|
1666
|
-
).all();
|
|
1667
|
-
return rows.map(rowToMilestone);
|
|
1668
|
-
}
|
|
1669
707
|
|
|
1670
|
-
export function getMilestone(id: string): MilestoneRow | null {
|
|
1671
|
-
if (!currentDb) return null;
|
|
1672
|
-
const row = currentDb.prepare("SELECT * FROM milestones WHERE id = :id").get({ ":id": id });
|
|
1673
|
-
if (!row) return null;
|
|
1674
|
-
return rowToMilestone(row);
|
|
1675
|
-
}
|
|
1676
708
|
|
|
1677
709
|
export function setMilestoneQueueOrder(order: string[]): void {
|
|
1678
|
-
if (!
|
|
1679
|
-
|
|
710
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
711
|
+
getDbOrNull()!.exec("BEGIN IMMEDIATE");
|
|
1680
712
|
try {
|
|
1681
|
-
|
|
1682
|
-
const stmt =
|
|
713
|
+
getDbOrNull()!.prepare("UPDATE milestones SET sequence = 0").run();
|
|
714
|
+
const stmt = getDbOrNull()!.prepare("UPDATE milestones SET sequence = :sequence WHERE id = :id");
|
|
1683
715
|
order.forEach((id, index) => {
|
|
1684
716
|
stmt.run({ ":id": id, ":sequence": index + 1 });
|
|
1685
717
|
});
|
|
1686
|
-
|
|
718
|
+
getDbOrNull()!.exec("COMMIT");
|
|
1687
719
|
} catch (err) {
|
|
1688
|
-
|
|
720
|
+
getDbOrNull()!.exec("ROLLBACK");
|
|
1689
721
|
throw err;
|
|
1690
722
|
}
|
|
1691
723
|
}
|
|
1692
724
|
|
|
1693
725
|
function getMilestoneStatusForUpdate(milestoneId: string): string | null {
|
|
1694
|
-
if (!
|
|
1695
|
-
const row =
|
|
726
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
727
|
+
const row = getDbOrNull()!.prepare("SELECT status FROM milestones WHERE id = :id").get({ ":id": milestoneId });
|
|
1696
728
|
return typeof row?.["status"] === "string" ? row["status"] : null;
|
|
1697
729
|
}
|
|
1698
730
|
|
|
1699
731
|
function writeMilestoneStatus(milestoneId: string, status: string, completedAt?: string | null): void {
|
|
1700
|
-
if (!
|
|
1701
|
-
|
|
732
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
733
|
+
getDbOrNull()!.prepare(
|
|
1702
734
|
`UPDATE milestones SET status = :status, completed_at = :completed_at WHERE id = :id`,
|
|
1703
735
|
).run({ ":status": status, ":completed_at": completedAt ?? null, ":id": milestoneId });
|
|
1704
736
|
}
|
|
@@ -1711,21 +743,14 @@ function writeMilestoneStatus(milestoneId: string, status: string, completedAt?:
|
|
|
1711
743
|
* must use reopenMilestoneStatus(), which is reserved for gsd_milestone_reopen.
|
|
1712
744
|
*/
|
|
1713
745
|
export function updateMilestoneStatus(milestoneId: string, status: string, completedAt?: string | null): void {
|
|
1714
|
-
|
|
1715
|
-
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1716
|
-
if (currentStatus && isClosedStatus(currentStatus) && !isClosedStatus(status)) {
|
|
1717
|
-
throw new Error(
|
|
1718
|
-
`Cannot update closed milestone ${milestoneId} from ${currentStatus} to ${status}; use gsd_milestone_reopen for an explicit reopen.`,
|
|
1719
|
-
);
|
|
1720
|
-
}
|
|
1721
|
-
writeMilestoneStatus(milestoneId, status, completedAt);
|
|
746
|
+
applyStatusTransition({ entity: "milestone", milestoneId, status, completedAt });
|
|
1722
747
|
}
|
|
1723
748
|
|
|
1724
749
|
/**
|
|
1725
750
|
* Explicit closed -> active transition for gsd_milestone_reopen only.
|
|
1726
751
|
*/
|
|
1727
752
|
export function reopenMilestoneStatus(milestoneId: string): void {
|
|
1728
|
-
if (!
|
|
753
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1729
754
|
const currentStatus = getMilestoneStatusForUpdate(milestoneId);
|
|
1730
755
|
if (!currentStatus) {
|
|
1731
756
|
throw new Error(`Cannot reopen missing milestone ${milestoneId}`);
|
|
@@ -1736,615 +761,35 @@ export function reopenMilestoneStatus(milestoneId: string): void {
|
|
|
1736
761
|
writeMilestoneStatus(milestoneId, "active", null);
|
|
1737
762
|
}
|
|
1738
763
|
|
|
1739
|
-
export function getActiveMilestoneFromDb(): MilestoneRow | null {
|
|
1740
|
-
if (!currentDb) return null;
|
|
1741
|
-
const row = currentDb.prepare(
|
|
1742
|
-
"SELECT * FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1",
|
|
1743
|
-
).get();
|
|
1744
|
-
if (!row) return null;
|
|
1745
|
-
return rowToMilestone(row);
|
|
1746
|
-
}
|
|
1747
764
|
|
|
1748
|
-
export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
|
|
1749
|
-
if (!currentDb) return null;
|
|
1750
|
-
|
|
1751
|
-
// Single query: find the first non-complete slice whose dependencies are all satisfied.
|
|
1752
|
-
// Uses json_each() to expand the JSON depends array and checks each dep is complete.
|
|
1753
|
-
const row = currentDb.prepare(
|
|
1754
|
-
`SELECT s.* FROM slices s
|
|
1755
|
-
WHERE s.milestone_id = :mid
|
|
1756
|
-
AND s.status NOT IN ('complete', 'done', 'skipped')
|
|
1757
|
-
AND NOT EXISTS (
|
|
1758
|
-
SELECT 1 FROM json_each(s.depends) AS dep
|
|
1759
|
-
WHERE dep.value NOT IN (
|
|
1760
|
-
SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
|
|
1761
|
-
)
|
|
1762
|
-
)
|
|
1763
|
-
ORDER BY s.sequence, s.id
|
|
1764
|
-
LIMIT 1`,
|
|
1765
|
-
).get({ ":mid": milestoneId });
|
|
1766
|
-
if (!row) return null;
|
|
1767
|
-
return rowToSlice(row);
|
|
1768
|
-
}
|
|
1769
765
|
|
|
1770
|
-
export function getActiveTaskFromDb(milestoneId: string, sliceId: string): TaskRow | null {
|
|
1771
|
-
if (!currentDb) return null;
|
|
1772
|
-
const row = currentDb.prepare(
|
|
1773
|
-
"SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1",
|
|
1774
|
-
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1775
|
-
if (!row) return null;
|
|
1776
|
-
return rowToTask(row);
|
|
1777
|
-
}
|
|
1778
766
|
|
|
1779
|
-
export function getMilestoneSlices(milestoneId: string): SliceRow[] {
|
|
1780
|
-
if (!currentDb) return [];
|
|
1781
|
-
const rows = currentDb.prepare("SELECT * FROM slices WHERE milestone_id = :mid ORDER BY sequence, id").all({ ":mid": milestoneId });
|
|
1782
|
-
return rows.map(rowToSlice);
|
|
1783
|
-
}
|
|
1784
767
|
|
|
1785
|
-
export function getArtifact(path: string): ArtifactRow | null {
|
|
1786
|
-
if (!currentDb) return null;
|
|
1787
|
-
const row = currentDb.prepare("SELECT * FROM artifacts WHERE path = :path").get({ ":path": path });
|
|
1788
|
-
if (!row) return null;
|
|
1789
|
-
return rowToArtifact(row);
|
|
1790
|
-
}
|
|
1791
768
|
|
|
1792
769
|
// ─── Lightweight Query Variants (hot-path optimized) ─────────────────────
|
|
1793
770
|
|
|
1794
|
-
/** Fast milestone status check — avoids deserializing JSON planning fields. */
|
|
1795
|
-
export function getActiveMilestoneIdFromDb(): IdStatusSummary | null {
|
|
1796
|
-
if (!currentDb) return null;
|
|
1797
|
-
const row = currentDb.prepare(
|
|
1798
|
-
"SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1",
|
|
1799
|
-
).get();
|
|
1800
|
-
if (!row) return null;
|
|
1801
|
-
return rowToIdStatusSummary(row);
|
|
1802
|
-
}
|
|
1803
771
|
|
|
1804
|
-
/** Fast slice status check — avoids deserializing JSON depends/planning fields. */
|
|
1805
|
-
export function getSliceStatusSummary(milestoneId: string): IdStatusSummary[] {
|
|
1806
|
-
if (!currentDb) return [];
|
|
1807
|
-
return currentDb.prepare(
|
|
1808
|
-
"SELECT id, status FROM slices WHERE milestone_id = :mid ORDER BY sequence, id",
|
|
1809
|
-
).all({ ":mid": milestoneId }).map(rowToIdStatusSummary);
|
|
1810
|
-
}
|
|
1811
772
|
|
|
1812
|
-
/** Fast task status check — avoids deserializing JSON arrays and large text fields. */
|
|
1813
|
-
export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): ActiveTaskSummary | null {
|
|
1814
|
-
if (!currentDb) return null;
|
|
1815
|
-
const row = currentDb.prepare(
|
|
1816
|
-
"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",
|
|
1817
|
-
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1818
|
-
if (!row) return null;
|
|
1819
|
-
return rowToActiveTaskSummary(row);
|
|
1820
|
-
}
|
|
1821
773
|
|
|
1822
|
-
/** Count tasks by status for a slice — useful for progress reporting without full row load. */
|
|
1823
|
-
export function getSliceTaskCounts(milestoneId: string, sliceId: string): TaskStatusCounts {
|
|
1824
|
-
if (!currentDb) return emptyTaskStatusCounts();
|
|
1825
|
-
const row = currentDb.prepare(
|
|
1826
|
-
`SELECT
|
|
1827
|
-
COUNT(*) as total,
|
|
1828
|
-
SUM(CASE WHEN status IN ('complete', 'done') THEN 1 ELSE 0 END) as done,
|
|
1829
|
-
SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
|
|
1830
|
-
FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
1831
|
-
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
1832
|
-
return rowToTaskStatusCounts(row);
|
|
1833
|
-
}
|
|
1834
774
|
|
|
1835
775
|
// ─── Slice Dependencies (junction table) ─────────────────────────────────
|
|
1836
776
|
|
|
1837
777
|
/** Sync the slice_dependencies junction table from a slice's JSON depends array. */
|
|
1838
778
|
export function syncSliceDependencies(milestoneId: string, sliceId: string, depends: string[]): void {
|
|
1839
|
-
if (!
|
|
1840
|
-
|
|
779
|
+
if (!getDbOrNull()!) return;
|
|
780
|
+
getDbOrNull()!.prepare(
|
|
1841
781
|
"DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid",
|
|
1842
782
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
1843
783
|
for (const dep of depends) {
|
|
1844
|
-
|
|
784
|
+
getDbOrNull()!.prepare(
|
|
1845
785
|
"INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (:mid, :sid, :dep)",
|
|
1846
786
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":dep": dep });
|
|
1847
787
|
}
|
|
1848
788
|
}
|
|
1849
789
|
|
|
1850
|
-
/** Get all slices that depend on a given slice. */
|
|
1851
|
-
export function getDependentSlices(milestoneId: string, sliceId: string): string[] {
|
|
1852
|
-
if (!currentDb) return [];
|
|
1853
|
-
const rows = currentDb.prepare(
|
|
1854
|
-
"SELECT slice_id FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid",
|
|
1855
|
-
).all({ ":mid": milestoneId, ":sid": sliceId });
|
|
1856
|
-
return rowsToStringColumn(rows, "slice_id");
|
|
1857
|
-
}
|
|
1858
790
|
|
|
1859
791
|
// ─── Worktree DB Helpers ──────────────────────────────────────────────────
|
|
1860
792
|
|
|
1861
|
-
export function copyWorktreeDb(srcDbPath: string, destDbPath: string): boolean {
|
|
1862
|
-
try {
|
|
1863
|
-
if (!existsSync(srcDbPath)) return false;
|
|
1864
|
-
const destDir = dirname(destDbPath);
|
|
1865
|
-
mkdirSync(destDir, { recursive: true });
|
|
1866
|
-
copyFileSync(srcDbPath, destDbPath);
|
|
1867
|
-
return true;
|
|
1868
|
-
} catch (err) {
|
|
1869
|
-
logError("db", "failed to copy DB to worktree", { error: (err as Error).message });
|
|
1870
|
-
return false;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
export interface ReconcileResult {
|
|
1875
|
-
decisions: number;
|
|
1876
|
-
requirements: number;
|
|
1877
|
-
artifacts: number;
|
|
1878
|
-
milestones: number;
|
|
1879
|
-
slices: number;
|
|
1880
|
-
tasks: number;
|
|
1881
|
-
memories: number;
|
|
1882
|
-
replan_history: number;
|
|
1883
|
-
assessments: number;
|
|
1884
|
-
quality_gates: number;
|
|
1885
|
-
slice_dependencies: number;
|
|
1886
|
-
verification_evidence: number;
|
|
1887
|
-
gate_runs: number;
|
|
1888
|
-
milestone_commit_attributions: number;
|
|
1889
|
-
conflicts: string[];
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
export function reconcileWorktreeDb(
|
|
1893
|
-
mainDbPath: string,
|
|
1894
|
-
worktreeDbPath: string,
|
|
1895
|
-
): ReconcileResult {
|
|
1896
|
-
const zero: ReconcileResult = {
|
|
1897
|
-
decisions: 0,
|
|
1898
|
-
requirements: 0,
|
|
1899
|
-
artifacts: 0,
|
|
1900
|
-
milestones: 0,
|
|
1901
|
-
slices: 0,
|
|
1902
|
-
tasks: 0,
|
|
1903
|
-
memories: 0,
|
|
1904
|
-
replan_history: 0,
|
|
1905
|
-
assessments: 0,
|
|
1906
|
-
quality_gates: 0,
|
|
1907
|
-
slice_dependencies: 0,
|
|
1908
|
-
verification_evidence: 0,
|
|
1909
|
-
gate_runs: 0,
|
|
1910
|
-
milestone_commit_attributions: 0,
|
|
1911
|
-
conflicts: [],
|
|
1912
|
-
};
|
|
1913
|
-
if (!existsSync(worktreeDbPath)) return zero;
|
|
1914
|
-
// Guard: bail when both paths resolve to the same physical file.
|
|
1915
|
-
// ATTACHing a WAL-mode DB to itself corrupts the WAL (#2823).
|
|
1916
|
-
try {
|
|
1917
|
-
if (realpathSync(mainDbPath) === realpathSync(worktreeDbPath)) return zero;
|
|
1918
|
-
} catch (e) { logWarning("db", `realpathSync failed: ${(e as Error).message}`); }
|
|
1919
|
-
// Sanitize path: reject any characters that could break ATTACH syntax.
|
|
1920
|
-
// ATTACH DATABASE doesn't support parameterized paths in all providers,
|
|
1921
|
-
// so we use strict allowlist validation instead.
|
|
1922
|
-
if (/['";\x00]/.test(worktreeDbPath)) {
|
|
1923
|
-
logError("db", "worktree DB reconciliation failed: path contains unsafe characters");
|
|
1924
|
-
return zero;
|
|
1925
|
-
}
|
|
1926
|
-
if (!currentDb) {
|
|
1927
|
-
const opened = openDatabase(mainDbPath);
|
|
1928
|
-
if (!opened) {
|
|
1929
|
-
logError("db", "worktree DB reconciliation failed: cannot open main DB");
|
|
1930
|
-
return zero;
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
const adapter = currentDb!;
|
|
1934
|
-
const conflicts: string[] = [];
|
|
1935
|
-
try {
|
|
1936
|
-
adapter.exec(`ATTACH DATABASE '${worktreeDbPath}' AS wt`);
|
|
1937
|
-
try {
|
|
1938
|
-
function countChanges(result: unknown): number {
|
|
1939
|
-
return typeof result === "object" && result !== null ? ((result as { changes?: number }).changes ?? 0) : 0;
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
function wtTableInfo(tableName: string): Array<Record<string, unknown>> {
|
|
1943
|
-
return adapter.prepare(`PRAGMA wt.table_info('${tableName}')`).all() as Array<Record<string, unknown>>;
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
const wtInfo = wtTableInfo("decisions");
|
|
1947
|
-
const hasWtDecisions = wtInfo.length > 0;
|
|
1948
|
-
const hasMadeBy = wtInfo.some((col) => col["name"] === "made_by");
|
|
1949
|
-
// ADR-011: worktree may predate schema v16/v17. For missing columns we
|
|
1950
|
-
// fall through to the main DB's existing value (not a literal default)
|
|
1951
|
-
// so reconcile never silently clears state the main tree has recorded.
|
|
1952
|
-
const hasDecisionSource = wtInfo.some((col) => col["name"] === "source");
|
|
1953
|
-
const wtRequirementInfo = wtTableInfo("requirements");
|
|
1954
|
-
const hasWtRequirements = wtRequirementInfo.length > 0;
|
|
1955
|
-
const wtMilestoneInfo = wtTableInfo("milestones");
|
|
1956
|
-
const hasWtMilestones = wtMilestoneInfo.length > 0;
|
|
1957
|
-
const hasMilestoneSequence = wtMilestoneInfo.some((col) => col["name"] === "sequence");
|
|
1958
|
-
const wtSliceInfo = wtTableInfo("slices");
|
|
1959
|
-
const hasWtSlices = wtSliceInfo.length > 0;
|
|
1960
|
-
const hasIsSketch = wtSliceInfo.some((col) => col["name"] === "is_sketch");
|
|
1961
|
-
const hasSketchScope = wtSliceInfo.some((col) => col["name"] === "sketch_scope");
|
|
1962
|
-
const hasSliceTargetRepositories = wtSliceInfo.some((col) => col["name"] === "target_repositories");
|
|
1963
|
-
const wtTaskInfo = wtTableInfo("tasks");
|
|
1964
|
-
const hasWtTasks = wtTaskInfo.length > 0;
|
|
1965
|
-
const hasTaskTargetRepositories = wtTaskInfo.some((col) => col["name"] === "target_repositories");
|
|
1966
|
-
const hasBlockerSource = wtTaskInfo.some((col) => col["name"] === "blocker_source");
|
|
1967
|
-
const hasEscalationPending = wtTaskInfo.some((col) => col["name"] === "escalation_pending");
|
|
1968
|
-
const hasEscalationAwaiting = wtTaskInfo.some((col) => col["name"] === "escalation_awaiting_review");
|
|
1969
|
-
const hasEscalationArtifact = wtTaskInfo.some((col) => col["name"] === "escalation_artifact_path");
|
|
1970
|
-
const hasEscalationOverride = wtTaskInfo.some((col) => col["name"] === "escalation_override_applied_at");
|
|
1971
|
-
const wtArtifactInfo = wtTableInfo("artifacts");
|
|
1972
|
-
const hasWtArtifacts = wtArtifactInfo.length > 0;
|
|
1973
|
-
const wtMemoryInfo = wtTableInfo("memories");
|
|
1974
|
-
const hasWtMemories = wtMemoryInfo.length > 0;
|
|
1975
|
-
const hasMemoryScope = wtMemoryInfo.some((col) => col["name"] === "scope");
|
|
1976
|
-
const hasMemoryTags = wtMemoryInfo.some((col) => col["name"] === "tags");
|
|
1977
|
-
const hasMemoryStructuredFields = wtMemoryInfo.some((col) => col["name"] === "structured_fields");
|
|
1978
|
-
const hasMemoryLastHitAt = wtMemoryInfo.some((col) => col["name"] === "last_hit_at");
|
|
1979
|
-
const hasWtReplanHistory = wtTableInfo("replan_history").length > 0;
|
|
1980
|
-
const hasWtAssessments = wtTableInfo("assessments").length > 0;
|
|
1981
|
-
const hasWtQualityGates = wtTableInfo("quality_gates").length > 0;
|
|
1982
|
-
const hasWtSliceDependencies = wtTableInfo("slice_dependencies").length > 0;
|
|
1983
|
-
const hasWtVerificationEvidence = wtTableInfo("verification_evidence").length > 0;
|
|
1984
|
-
const hasWtGateRuns = wtTableInfo("gate_runs").length > 0;
|
|
1985
|
-
const hasWtMilestoneCommitAttributions = wtTableInfo("milestone_commit_attributions").length > 0;
|
|
1986
|
-
|
|
1987
|
-
if (hasWtDecisions) {
|
|
1988
|
-
const decConf = adapter.prepare(
|
|
1989
|
-
`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 ${
|
|
1990
|
-
hasMadeBy ? "m.made_by != w.made_by" : "'agent' != 'agent'"
|
|
1991
|
-
} OR m.superseded_by IS NOT w.superseded_by`,
|
|
1992
|
-
).all();
|
|
1993
|
-
for (const row of decConf) conflicts.push(`decision ${(row as Record<string, unknown>)["id"]}: modified in both`);
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
if (hasWtRequirements) {
|
|
1997
|
-
const reqConf = adapter.prepare(
|
|
1998
|
-
`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`,
|
|
1999
|
-
).all();
|
|
2000
|
-
for (const row of reqConf) conflicts.push(`requirement ${(row as Record<string, unknown>)["id"]}: modified in both`);
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
const merged: Omit<ReconcileResult, "conflicts"> = {
|
|
2004
|
-
decisions: 0,
|
|
2005
|
-
requirements: 0,
|
|
2006
|
-
artifacts: 0,
|
|
2007
|
-
milestones: 0,
|
|
2008
|
-
slices: 0,
|
|
2009
|
-
tasks: 0,
|
|
2010
|
-
memories: 0,
|
|
2011
|
-
replan_history: 0,
|
|
2012
|
-
assessments: 0,
|
|
2013
|
-
quality_gates: 0,
|
|
2014
|
-
slice_dependencies: 0,
|
|
2015
|
-
verification_evidence: 0,
|
|
2016
|
-
gate_runs: 0,
|
|
2017
|
-
milestone_commit_attributions: 0,
|
|
2018
|
-
};
|
|
2019
|
-
const sliceTargetRepositoriesSql = hasSliceTargetRepositories
|
|
2020
|
-
? `CASE
|
|
2021
|
-
WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
|
|
2022
|
-
THEN m.target_repositories
|
|
2023
|
-
ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
|
|
2024
|
-
END`
|
|
2025
|
-
: "COALESCE(m.target_repositories, '[]')";
|
|
2026
|
-
const taskTargetRepositoriesSql = hasTaskTargetRepositories
|
|
2027
|
-
? `CASE
|
|
2028
|
-
WHEN w.target_repositories = '[]' AND COALESCE(m.target_repositories, '[]') <> '[]'
|
|
2029
|
-
THEN m.target_repositories
|
|
2030
|
-
ELSE COALESCE(w.target_repositories, m.target_repositories, '[]')
|
|
2031
|
-
END`
|
|
2032
|
-
: "COALESCE(m.target_repositories, '[]')";
|
|
2033
|
-
|
|
2034
|
-
adapter.exec("BEGIN");
|
|
2035
|
-
try {
|
|
2036
|
-
// Join the target decisions so we can prefer an existing main.source
|
|
2037
|
-
// when the worktree predates v16 — otherwise a write-through reconcile
|
|
2038
|
-
// would clobber 'escalation'-sourced decisions with the literal default.
|
|
2039
|
-
if (hasWtDecisions) {
|
|
2040
|
-
merged.decisions = countChanges(adapter.prepare(`
|
|
2041
|
-
INSERT INTO decisions (
|
|
2042
|
-
id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by
|
|
2043
|
-
)
|
|
2044
|
-
SELECT w.id, w.when_context, w.scope, w.decision, w.choice, w.rationale, w.revisable, ${
|
|
2045
|
-
hasMadeBy ? "w.made_by" : "COALESCE(m.made_by, 'agent')"
|
|
2046
|
-
}, ${
|
|
2047
|
-
hasDecisionSource ? "w.source" : "COALESCE(m.source, 'discussion')"
|
|
2048
|
-
}, w.superseded_by
|
|
2049
|
-
FROM wt.decisions w
|
|
2050
|
-
LEFT JOIN decisions m ON m.id = w.id
|
|
2051
|
-
WHERE true
|
|
2052
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
2053
|
-
when_context = excluded.when_context,
|
|
2054
|
-
scope = excluded.scope,
|
|
2055
|
-
decision = excluded.decision,
|
|
2056
|
-
choice = excluded.choice,
|
|
2057
|
-
rationale = excluded.rationale,
|
|
2058
|
-
revisable = excluded.revisable,
|
|
2059
|
-
made_by = excluded.made_by,
|
|
2060
|
-
source = excluded.source,
|
|
2061
|
-
superseded_by = excluded.superseded_by
|
|
2062
|
-
`).run());
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
if (hasWtRequirements) {
|
|
2066
|
-
merged.requirements = countChanges(adapter.prepare(`
|
|
2067
|
-
INSERT OR REPLACE INTO requirements (
|
|
2068
|
-
id, class, status, description, why, source, primary_owner,
|
|
2069
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
2070
|
-
)
|
|
2071
|
-
SELECT id, class, status, description, why, source, primary_owner,
|
|
2072
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
2073
|
-
FROM wt.requirements
|
|
2074
|
-
`).run());
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
// Always recompute artifact hashes from the content being merged. Older
|
|
2078
|
-
// worktree DBs may not have content_hash at all, and migrated old DBs can
|
|
2079
|
-
// carry stale default/null hashes after their content changed.
|
|
2080
|
-
if (hasWtArtifacts) {
|
|
2081
|
-
const artifactRows = adapter.prepare(`
|
|
2082
|
-
SELECT path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at
|
|
2083
|
-
FROM wt.artifacts
|
|
2084
|
-
`).all() as Array<Record<string, unknown>>;
|
|
2085
|
-
const artifactStmt = adapter.prepare(`
|
|
2086
|
-
INSERT OR REPLACE INTO artifacts (
|
|
2087
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
2088
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
2089
|
-
`);
|
|
2090
|
-
for (const row of artifactRows) {
|
|
2091
|
-
const fullContent = String(row["full_content"] ?? "");
|
|
2092
|
-
merged.artifacts += countChanges(artifactStmt.run(
|
|
2093
|
-
row["path"],
|
|
2094
|
-
row["artifact_type"],
|
|
2095
|
-
row["milestone_id"] ?? null,
|
|
2096
|
-
row["slice_id"] ?? null,
|
|
2097
|
-
row["task_id"] ?? null,
|
|
2098
|
-
fullContent,
|
|
2099
|
-
row["imported_at"],
|
|
2100
|
-
createHash("sha256").update(fullContent).digest("hex"),
|
|
2101
|
-
));
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
// Merge milestones — worktree may have updated status/planning fields.
|
|
2106
|
-
// Never downgrade status: complete > active > pre-planning (#4372).
|
|
2107
|
-
// A stale worktree may carry an older 'active' status for a milestone
|
|
2108
|
-
// that the main DB has already marked 'complete'; preserve the higher status.
|
|
2109
|
-
if (hasWtMilestones) {
|
|
2110
|
-
merged.milestones = countChanges(adapter.prepare(`
|
|
2111
|
-
INSERT OR REPLACE INTO milestones (
|
|
2112
|
-
id, title, status, depends_on, created_at, completed_at,
|
|
2113
|
-
vision, success_criteria, key_risks, proof_strategy,
|
|
2114
|
-
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
2115
|
-
definition_of_done, requirement_coverage, boundary_map_markdown, sequence
|
|
2116
|
-
)
|
|
2117
|
-
SELECT w.id, w.title,
|
|
2118
|
-
CASE
|
|
2119
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2120
|
-
THEN m.status ELSE w.status
|
|
2121
|
-
END,
|
|
2122
|
-
w.depends_on,
|
|
2123
|
-
CASE
|
|
2124
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2125
|
-
THEN m.created_at ELSE w.created_at
|
|
2126
|
-
END,
|
|
2127
|
-
CASE
|
|
2128
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2129
|
-
THEN m.completed_at ELSE w.completed_at
|
|
2130
|
-
END,
|
|
2131
|
-
w.vision, w.success_criteria, w.key_risks, w.proof_strategy,
|
|
2132
|
-
w.verification_contract, w.verification_integration, w.verification_operational, w.verification_uat,
|
|
2133
|
-
w.definition_of_done, w.requirement_coverage, w.boundary_map_markdown,
|
|
2134
|
-
${hasMilestoneSequence ? "COALESCE(w.sequence, 0)" : "COALESCE(m.sequence, 0)"}
|
|
2135
|
-
FROM wt.milestones w
|
|
2136
|
-
LEFT JOIN milestones m ON m.id = w.id
|
|
2137
|
-
`).run());
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// Merge slices — preserve worktree progress but never downgrade completed status (#2558).
|
|
2141
|
-
// ADR-011 Phase 1: carry is_sketch + sketch_scope so reconcile doesn't
|
|
2142
|
-
// silently clear sketch metadata. When the worktree predates v16,
|
|
2143
|
-
// fall back to the main DB's existing value rather than a literal 0/''.
|
|
2144
|
-
if (hasWtSlices) {
|
|
2145
|
-
merged.slices = countChanges(adapter.prepare(`
|
|
2146
|
-
INSERT OR REPLACE INTO slices (
|
|
2147
|
-
milestone_id, id, title, status, risk, depends, demo, created_at, completed_at,
|
|
2148
|
-
full_summary_md, full_uat_md, goal, success_criteria, proof_level,
|
|
2149
|
-
integration_closure, observability_impact, target_repositories, sequence, replan_triggered_at,
|
|
2150
|
-
is_sketch, sketch_scope
|
|
2151
|
-
)
|
|
2152
|
-
SELECT w.milestone_id, w.id, w.title,
|
|
2153
|
-
CASE
|
|
2154
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2155
|
-
THEN m.status ELSE w.status
|
|
2156
|
-
END,
|
|
2157
|
-
w.risk, w.depends, w.demo, w.created_at,
|
|
2158
|
-
CASE
|
|
2159
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2160
|
-
THEN m.completed_at ELSE w.completed_at
|
|
2161
|
-
END,
|
|
2162
|
-
w.full_summary_md, w.full_uat_md, w.goal, w.success_criteria, w.proof_level,
|
|
2163
|
-
w.integration_closure, w.observability_impact,
|
|
2164
|
-
${sliceTargetRepositoriesSql},
|
|
2165
|
-
w.sequence, w.replan_triggered_at,
|
|
2166
|
-
${hasIsSketch ? "w.is_sketch" : "COALESCE(m.is_sketch, 0)"},
|
|
2167
|
-
${hasSketchScope ? "w.sketch_scope" : "COALESCE(m.sketch_scope, '')"}
|
|
2168
|
-
FROM wt.slices w
|
|
2169
|
-
LEFT JOIN slices m ON m.milestone_id = w.milestone_id AND m.id = w.id
|
|
2170
|
-
`).run());
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
// Merge tasks — preserve execution results, never downgrade completed status (#2558).
|
|
2174
|
-
// ADR-011 P2: carry blocker_source + escalation_* columns so worktree reconcile
|
|
2175
|
-
// doesn't silently clear escalation state back to defaults.
|
|
2176
|
-
if (hasWtTasks) {
|
|
2177
|
-
merged.tasks = countChanges(adapter.prepare(`
|
|
2178
|
-
INSERT OR REPLACE INTO tasks (
|
|
2179
|
-
milestone_id, slice_id, id, title, status, one_liner, narrative,
|
|
2180
|
-
verification_result, duration, completed_at, blocker_discovered,
|
|
2181
|
-
deviations, known_issues, key_files, key_decisions, full_summary_md,
|
|
2182
|
-
description, estimate, files, verify, inputs, expected_output,
|
|
2183
|
-
observability_impact, full_plan_md, target_repositories, sequence,
|
|
2184
|
-
blocker_source, escalation_pending, escalation_awaiting_review,
|
|
2185
|
-
escalation_artifact_path, escalation_override_applied_at
|
|
2186
|
-
)
|
|
2187
|
-
SELECT w.milestone_id, w.slice_id, w.id, w.title,
|
|
2188
|
-
CASE
|
|
2189
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2190
|
-
THEN m.status ELSE w.status
|
|
2191
|
-
END,
|
|
2192
|
-
w.one_liner, w.narrative,
|
|
2193
|
-
w.verification_result, w.duration,
|
|
2194
|
-
CASE
|
|
2195
|
-
WHEN m.status IN (${TERMINAL_STATUS_SQL}) AND w.status NOT IN (${TERMINAL_STATUS_SQL})
|
|
2196
|
-
THEN m.completed_at ELSE w.completed_at
|
|
2197
|
-
END,
|
|
2198
|
-
w.blocker_discovered,
|
|
2199
|
-
w.deviations, w.known_issues, w.key_files, w.key_decisions, w.full_summary_md,
|
|
2200
|
-
w.description, w.estimate, w.files, w.verify, w.inputs, w.expected_output,
|
|
2201
|
-
w.observability_impact, w.full_plan_md,
|
|
2202
|
-
${taskTargetRepositoriesSql},
|
|
2203
|
-
w.sequence,
|
|
2204
|
-
${hasBlockerSource ? "w.blocker_source" : "COALESCE(m.blocker_source, '')"},
|
|
2205
|
-
${hasEscalationPending ? "w.escalation_pending" : "COALESCE(m.escalation_pending, 0)"},
|
|
2206
|
-
${hasEscalationAwaiting ? "w.escalation_awaiting_review" : "COALESCE(m.escalation_awaiting_review, 0)"},
|
|
2207
|
-
${hasEscalationArtifact ? "w.escalation_artifact_path" : "m.escalation_artifact_path"},
|
|
2208
|
-
${hasEscalationOverride ? "w.escalation_override_applied_at" : "m.escalation_override_applied_at"}
|
|
2209
|
-
FROM wt.tasks w
|
|
2210
|
-
LEFT JOIN tasks m ON m.milestone_id = w.milestone_id AND m.slice_id = w.slice_id AND m.id = w.id
|
|
2211
|
-
`).run());
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
// Merge memories — keep worktree-learned insights.
|
|
2215
|
-
// V18 (scope, tags), V21 (structured_fields), V28 (last_hit_at): for each
|
|
2216
|
-
// column the wt may not yet have (older worktree DB), fall back to the
|
|
2217
|
-
// main DB's existing value via LEFT JOIN so reconcile never silently
|
|
2218
|
-
// resets these fields to defaults on rows that already had them.
|
|
2219
|
-
if (hasWtMemories) {
|
|
2220
|
-
merged.memories = countChanges(adapter.prepare(`
|
|
2221
|
-
INSERT OR REPLACE INTO memories (
|
|
2222
|
-
seq, id, category, content, confidence, source_unit_type, source_unit_id,
|
|
2223
|
-
created_at, updated_at, superseded_by, hit_count,
|
|
2224
|
-
scope, tags, structured_fields, last_hit_at
|
|
2225
|
-
)
|
|
2226
|
-
SELECT w.seq, w.id, w.category, w.content, w.confidence, w.source_unit_type, w.source_unit_id,
|
|
2227
|
-
w.created_at, w.updated_at, w.superseded_by, w.hit_count,
|
|
2228
|
-
${hasMemoryScope ? "w.scope" : "COALESCE(m.scope, 'project')"},
|
|
2229
|
-
${hasMemoryTags ? "w.tags" : "COALESCE(m.tags, '[]')"},
|
|
2230
|
-
${hasMemoryStructuredFields ? "w.structured_fields" : "m.structured_fields"},
|
|
2231
|
-
${hasMemoryLastHitAt ? "w.last_hit_at" : "m.last_hit_at"}
|
|
2232
|
-
FROM wt.memories w
|
|
2233
|
-
LEFT JOIN memories m ON m.id = w.id
|
|
2234
|
-
`).run());
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
if (hasWtReplanHistory) {
|
|
2238
|
-
merged.replan_history = countChanges(adapter.prepare(`
|
|
2239
|
-
INSERT INTO replan_history (
|
|
2240
|
-
milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
|
|
2241
|
-
)
|
|
2242
|
-
SELECT w.milestone_id, w.slice_id, w.task_id, w.summary, w.previous_artifact_path, w.replacement_artifact_path, w.created_at
|
|
2243
|
-
FROM wt.replan_history w
|
|
2244
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
2245
|
-
AND NOT EXISTS (
|
|
2246
|
-
SELECT 1 FROM replan_history m
|
|
2247
|
-
WHERE m.milestone_id = w.milestone_id
|
|
2248
|
-
AND m.slice_id IS w.slice_id
|
|
2249
|
-
AND m.task_id IS w.task_id
|
|
2250
|
-
AND m.summary = w.summary
|
|
2251
|
-
AND m.previous_artifact_path IS w.previous_artifact_path
|
|
2252
|
-
AND m.replacement_artifact_path IS w.replacement_artifact_path
|
|
2253
|
-
)
|
|
2254
|
-
`).run());
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
if (hasWtAssessments) {
|
|
2258
|
-
merged.assessments = countChanges(adapter.prepare(`
|
|
2259
|
-
INSERT OR REPLACE INTO assessments (
|
|
2260
|
-
path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
|
|
2261
|
-
)
|
|
2262
|
-
SELECT w.path, w.milestone_id, w.slice_id, w.task_id, w.status, w.scope, w.full_content, w.created_at
|
|
2263
|
-
FROM wt.assessments w
|
|
2264
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
2265
|
-
`).run());
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
if (hasWtQualityGates) {
|
|
2269
|
-
merged.quality_gates = countChanges(adapter.prepare(`
|
|
2270
|
-
INSERT OR REPLACE INTO quality_gates (
|
|
2271
|
-
milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
|
|
2272
|
-
)
|
|
2273
|
-
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
|
|
2274
|
-
FROM wt.quality_gates w
|
|
2275
|
-
WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
|
|
2276
|
-
`).run());
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
if (hasWtSliceDependencies) {
|
|
2280
|
-
merged.slice_dependencies = countChanges(adapter.prepare(`
|
|
2281
|
-
INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id)
|
|
2282
|
-
SELECT w.milestone_id, w.slice_id, w.depends_on_slice_id
|
|
2283
|
-
FROM wt.slice_dependencies w
|
|
2284
|
-
WHERE EXISTS (SELECT 1 FROM slices s WHERE s.milestone_id = w.milestone_id AND s.id = w.slice_id)
|
|
2285
|
-
AND EXISTS (SELECT 1 FROM slices d WHERE d.milestone_id = w.milestone_id AND d.id = w.depends_on_slice_id)
|
|
2286
|
-
`).run());
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
// Merge verification evidence — append-only, use INSERT OR IGNORE to avoid duplicates
|
|
2290
|
-
if (hasWtVerificationEvidence) {
|
|
2291
|
-
merged.verification_evidence = countChanges(adapter.prepare(`
|
|
2292
|
-
INSERT OR IGNORE INTO verification_evidence (
|
|
2293
|
-
task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
|
|
2294
|
-
)
|
|
2295
|
-
SELECT task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at
|
|
2296
|
-
FROM wt.verification_evidence
|
|
2297
|
-
`).run());
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
if (hasWtGateRuns) {
|
|
2301
|
-
merged.gate_runs = countChanges(adapter.prepare(`
|
|
2302
|
-
INSERT INTO gate_runs (
|
|
2303
|
-
trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
|
|
2304
|
-
outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
|
|
2305
|
-
)
|
|
2306
|
-
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,
|
|
2307
|
-
w.outcome, w.failure_class, w.rationale, w.findings, w.attempt, w.max_attempts, w.retryable, w.evaluated_at
|
|
2308
|
-
FROM wt.gate_runs w
|
|
2309
|
-
WHERE NOT EXISTS (
|
|
2310
|
-
SELECT 1 FROM gate_runs m
|
|
2311
|
-
WHERE m.trace_id = w.trace_id
|
|
2312
|
-
AND m.turn_id = w.turn_id
|
|
2313
|
-
AND m.gate_id = w.gate_id
|
|
2314
|
-
AND m.attempt = w.attempt
|
|
2315
|
-
AND m.evaluated_at = w.evaluated_at
|
|
2316
|
-
)
|
|
2317
|
-
`).run());
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
if (hasWtMilestoneCommitAttributions) {
|
|
2321
|
-
merged.milestone_commit_attributions = countChanges(adapter.prepare(`
|
|
2322
|
-
INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2323
|
-
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2324
|
-
)
|
|
2325
|
-
SELECT w.commit_sha, w.milestone_id, w.slice_id, w.task_id, w.source, w.confidence, w.files_json, w.created_at
|
|
2326
|
-
FROM wt.milestone_commit_attributions w
|
|
2327
|
-
WHERE EXISTS (SELECT 1 FROM milestones m WHERE m.id = w.milestone_id)
|
|
2328
|
-
`).run());
|
|
2329
|
-
}
|
|
2330
|
-
|
|
2331
|
-
adapter.exec("COMMIT");
|
|
2332
|
-
} catch (txErr) {
|
|
2333
|
-
try { adapter.exec("ROLLBACK"); } catch (e) { logWarning("db", `rollback failed: ${(e as Error).message}`); }
|
|
2334
|
-
throw txErr;
|
|
2335
|
-
}
|
|
2336
|
-
return { ...merged, conflicts };
|
|
2337
|
-
} finally {
|
|
2338
|
-
try { adapter.exec("DETACH DATABASE wt"); } catch (e) { logWarning("db", `detach worktree DB failed: ${(e as Error).message}`); }
|
|
2339
|
-
}
|
|
2340
|
-
} catch (err) {
|
|
2341
|
-
logError("db", "worktree DB reconciliation failed", { error: (err as Error).message });
|
|
2342
|
-
return { ...zero, conflicts };
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
|
|
2346
|
-
// ─── Replan & Assessment Helpers ──────────────────────────────────────────
|
|
2347
|
-
|
|
2348
793
|
export function insertReplanHistory(entry: {
|
|
2349
794
|
milestoneId: string;
|
|
2350
795
|
sliceId?: string | null;
|
|
@@ -2353,10 +798,10 @@ export function insertReplanHistory(entry: {
|
|
|
2353
798
|
previousArtifactPath?: string | null;
|
|
2354
799
|
replacementArtifactPath?: string | null;
|
|
2355
800
|
}): void {
|
|
2356
|
-
if (!
|
|
801
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2357
802
|
// INSERT OR REPLACE: idempotent on (milestone_id, slice_id, task_id) via schema v11 unique index.
|
|
2358
803
|
// Retrying the same replan silently updates summary instead of accumulating duplicate rows.
|
|
2359
|
-
|
|
804
|
+
getDbOrNull()!.prepare(
|
|
2360
805
|
`INSERT OR REPLACE INTO replan_history (milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at)
|
|
2361
806
|
VALUES (:milestone_id, :slice_id, :task_id, :summary, :previous_artifact_path, :replacement_artifact_path, :created_at)`,
|
|
2362
807
|
).run({
|
|
@@ -2379,11 +824,11 @@ export function insertAssessment(entry: {
|
|
|
2379
824
|
scope: string;
|
|
2380
825
|
fullContent: string;
|
|
2381
826
|
}): void {
|
|
2382
|
-
if (!
|
|
827
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2383
828
|
// Idempotent: PRIMARY KEY is `path`, which is deterministic given (milestone_id, scope) per
|
|
2384
829
|
// the artifact-path resolver. Retrying the same reassess-roadmap silently overwrites the row
|
|
2385
830
|
// instead of accumulating duplicates.
|
|
2386
|
-
|
|
831
|
+
getDbOrNull()!.prepare(
|
|
2387
832
|
`INSERT OR REPLACE INTO assessments (path, milestone_id, slice_id, task_id, status, scope, full_content, created_at)
|
|
2388
833
|
VALUES (:path, :milestone_id, :slice_id, :task_id, :status, :scope, :full_content, :created_at)`,
|
|
2389
834
|
).run({
|
|
@@ -2399,94 +844,94 @@ export function insertAssessment(entry: {
|
|
|
2399
844
|
}
|
|
2400
845
|
|
|
2401
846
|
export function deleteAssessmentByScope(milestoneId: string, scope: string): void {
|
|
2402
|
-
if (!
|
|
2403
|
-
|
|
847
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
848
|
+
getDbOrNull()!.prepare(
|
|
2404
849
|
`DELETE FROM assessments WHERE milestone_id = :mid AND scope = :scope`,
|
|
2405
850
|
).run({ ":mid": milestoneId, ":scope": scope });
|
|
2406
851
|
}
|
|
2407
852
|
|
|
2408
853
|
export function deleteVerificationEvidence(milestoneId: string, sliceId: string, taskId: string): void {
|
|
2409
|
-
if (!
|
|
2410
|
-
|
|
854
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
855
|
+
getDbOrNull()!.prepare(
|
|
2411
856
|
`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`,
|
|
2412
857
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2413
858
|
}
|
|
2414
859
|
|
|
2415
860
|
export function deleteTask(milestoneId: string, sliceId: string, taskId: string): void {
|
|
2416
|
-
if (!
|
|
861
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2417
862
|
transaction(() => {
|
|
2418
863
|
// Must delete verification_evidence first (FK constraint)
|
|
2419
|
-
|
|
864
|
+
getDbOrNull()!!.prepare(
|
|
2420
865
|
`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`,
|
|
2421
866
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2422
|
-
|
|
867
|
+
getDbOrNull()!!.prepare(
|
|
2423
868
|
`DELETE FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND task_id = :tid`,
|
|
2424
869
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2425
|
-
|
|
870
|
+
getDbOrNull()!!.prepare(
|
|
2426
871
|
`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND id = :tid`,
|
|
2427
872
|
).run({ ":mid": milestoneId, ":sid": sliceId, ":tid": taskId });
|
|
2428
873
|
});
|
|
2429
874
|
}
|
|
2430
875
|
|
|
2431
876
|
export function deleteSlice(milestoneId: string, sliceId: string): void {
|
|
2432
|
-
if (!
|
|
877
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2433
878
|
transaction(() => {
|
|
2434
879
|
// Cascade-style manual deletion: evidence → tasks → dependencies → slice
|
|
2435
|
-
|
|
880
|
+
getDbOrNull()!!.prepare(
|
|
2436
881
|
`DELETE FROM verification_evidence WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
2437
882
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2438
|
-
|
|
883
|
+
getDbOrNull()!!.prepare(
|
|
2439
884
|
`DELETE FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
2440
885
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2441
|
-
|
|
886
|
+
getDbOrNull()!!.prepare(
|
|
2442
887
|
`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND slice_id = :sid`,
|
|
2443
888
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2444
|
-
|
|
889
|
+
getDbOrNull()!!.prepare(
|
|
2445
890
|
`DELETE FROM slice_dependencies WHERE milestone_id = :mid AND depends_on_slice_id = :sid`,
|
|
2446
891
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2447
|
-
|
|
892
|
+
getDbOrNull()!!.prepare(
|
|
2448
893
|
`DELETE FROM slices WHERE milestone_id = :mid AND id = :sid`,
|
|
2449
894
|
).run({ ":mid": milestoneId, ":sid": sliceId });
|
|
2450
895
|
});
|
|
2451
896
|
}
|
|
2452
897
|
|
|
2453
898
|
export function deleteMilestone(milestoneId: string): void {
|
|
2454
|
-
if (!
|
|
899
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2455
900
|
transaction(() => {
|
|
2456
|
-
|
|
901
|
+
getDbOrNull()!!.prepare(
|
|
2457
902
|
`DELETE FROM verification_evidence WHERE milestone_id = :mid`,
|
|
2458
903
|
).run({ ":mid": milestoneId });
|
|
2459
|
-
|
|
904
|
+
getDbOrNull()!!.prepare(
|
|
2460
905
|
`DELETE FROM quality_gates WHERE milestone_id = :mid`,
|
|
2461
906
|
).run({ ":mid": milestoneId });
|
|
2462
|
-
|
|
907
|
+
getDbOrNull()!!.prepare(
|
|
2463
908
|
`DELETE FROM gate_runs WHERE milestone_id = :mid`,
|
|
2464
909
|
).run({ ":mid": milestoneId });
|
|
2465
|
-
|
|
910
|
+
getDbOrNull()!!.prepare(
|
|
2466
911
|
`DELETE FROM tasks WHERE milestone_id = :mid`,
|
|
2467
912
|
).run({ ":mid": milestoneId });
|
|
2468
|
-
|
|
913
|
+
getDbOrNull()!!.prepare(
|
|
2469
914
|
`DELETE FROM slice_dependencies WHERE milestone_id = :mid`,
|
|
2470
915
|
).run({ ":mid": milestoneId });
|
|
2471
|
-
|
|
916
|
+
getDbOrNull()!!.prepare(
|
|
2472
917
|
`DELETE FROM slices WHERE milestone_id = :mid`,
|
|
2473
918
|
).run({ ":mid": milestoneId });
|
|
2474
|
-
|
|
919
|
+
getDbOrNull()!!.prepare(
|
|
2475
920
|
`DELETE FROM replan_history WHERE milestone_id = :mid`,
|
|
2476
921
|
).run({ ":mid": milestoneId });
|
|
2477
|
-
|
|
922
|
+
getDbOrNull()!!.prepare(
|
|
2478
923
|
`DELETE FROM assessments WHERE milestone_id = :mid`,
|
|
2479
924
|
).run({ ":mid": milestoneId });
|
|
2480
|
-
|
|
925
|
+
getDbOrNull()!!.prepare(
|
|
2481
926
|
`DELETE FROM artifacts WHERE milestone_id = :mid`,
|
|
2482
927
|
).run({ ":mid": milestoneId });
|
|
2483
|
-
|
|
928
|
+
getDbOrNull()!!.prepare(
|
|
2484
929
|
`DELETE FROM milestone_commit_attributions WHERE milestone_id = :mid`,
|
|
2485
930
|
).run({ ":mid": milestoneId });
|
|
2486
|
-
|
|
931
|
+
getDbOrNull()!!.prepare(
|
|
2487
932
|
`DELETE FROM milestone_leases WHERE milestone_id = :mid`,
|
|
2488
933
|
).run({ ":mid": milestoneId });
|
|
2489
|
-
|
|
934
|
+
getDbOrNull()!!.prepare(
|
|
2490
935
|
`DELETE FROM milestones WHERE id = :mid`,
|
|
2491
936
|
).run({ ":mid": milestoneId });
|
|
2492
937
|
});
|
|
@@ -2498,8 +943,15 @@ export function updateSliceFields(milestoneId: string, sliceId: string, fields:
|
|
|
2498
943
|
depends?: string[];
|
|
2499
944
|
demo?: string;
|
|
2500
945
|
}): void {
|
|
2501
|
-
if (!
|
|
2502
|
-
|
|
946
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
947
|
+
const SLICE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
948
|
+
if (fields.depends !== undefined) {
|
|
949
|
+
const invalidDep = fields.depends.find(d => !SLICE_ID_RE.test(d));
|
|
950
|
+
if (invalidDep !== undefined) {
|
|
951
|
+
throw new GSDError(GSD_STALE_STATE, `updateSliceFields: depends element "${invalidDep}" is not a valid slice ID`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
getDbOrNull()!.prepare(
|
|
2503
955
|
`UPDATE slices SET
|
|
2504
956
|
title = COALESCE(:title, title),
|
|
2505
957
|
risk = COALESCE(:risk, risk),
|
|
@@ -2516,39 +968,8 @@ export function updateSliceFields(milestoneId: string, sliceId: string, fields:
|
|
|
2516
968
|
});
|
|
2517
969
|
}
|
|
2518
970
|
|
|
2519
|
-
export function getReplanHistory(milestoneId: string, sliceId?: string): Array<Record<string, unknown>> {
|
|
2520
|
-
if (!currentDb) return [];
|
|
2521
|
-
if (sliceId) {
|
|
2522
|
-
return currentDb.prepare(
|
|
2523
|
-
`SELECT * FROM replan_history WHERE milestone_id = :mid AND slice_id = :sid ORDER BY created_at DESC`,
|
|
2524
|
-
).all({ ":mid": milestoneId, ":sid": sliceId });
|
|
2525
|
-
}
|
|
2526
|
-
return currentDb.prepare(
|
|
2527
|
-
`SELECT * FROM replan_history WHERE milestone_id = :mid ORDER BY created_at DESC`,
|
|
2528
|
-
).all({ ":mid": milestoneId });
|
|
2529
|
-
}
|
|
2530
971
|
|
|
2531
|
-
export function getAssessment(path: string): Record<string, unknown> | null {
|
|
2532
|
-
if (!currentDb) return null;
|
|
2533
|
-
const row = currentDb.prepare(
|
|
2534
|
-
`SELECT * FROM assessments WHERE path = :path`,
|
|
2535
|
-
).get({ ":path": path });
|
|
2536
|
-
return row ?? null;
|
|
2537
|
-
}
|
|
2538
972
|
|
|
2539
|
-
export function getLatestAssessmentByScope(
|
|
2540
|
-
milestoneId: string,
|
|
2541
|
-
scope: string,
|
|
2542
|
-
): Record<string, unknown> | null {
|
|
2543
|
-
if (!currentDb) return null;
|
|
2544
|
-
const row = currentDb.prepare(
|
|
2545
|
-
`SELECT * FROM assessments
|
|
2546
|
-
WHERE milestone_id = :mid AND scope = :scope
|
|
2547
|
-
ORDER BY created_at DESC
|
|
2548
|
-
LIMIT 1`,
|
|
2549
|
-
).get({ ":mid": milestoneId, ":scope": scope });
|
|
2550
|
-
return row ?? null;
|
|
2551
|
-
}
|
|
2552
973
|
|
|
2553
974
|
// ─── Quality Gates ───────────────────────────────────────────────────────
|
|
2554
975
|
|
|
@@ -2560,8 +981,8 @@ export function insertGateRow(g: {
|
|
|
2560
981
|
taskId?: string | null;
|
|
2561
982
|
status?: GateStatus;
|
|
2562
983
|
}): void {
|
|
2563
|
-
if (!
|
|
2564
|
-
|
|
984
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
985
|
+
getDbOrNull()!.prepare(
|
|
2565
986
|
`INSERT OR IGNORE INTO quality_gates (milestone_id, slice_id, gate_id, scope, task_id, status)
|
|
2566
987
|
VALUES (:mid, :sid, :gid, :scope, :tid, :status)`,
|
|
2567
988
|
).run({
|
|
@@ -2583,9 +1004,9 @@ export function saveGateResult(g: {
|
|
|
2583
1004
|
rationale: string;
|
|
2584
1005
|
findings: string;
|
|
2585
1006
|
}): void {
|
|
2586
|
-
if (!
|
|
1007
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
2587
1008
|
const evaluatedAt = new Date().toISOString();
|
|
2588
|
-
const result =
|
|
1009
|
+
const result = getDbOrNull()!.prepare(
|
|
2589
1010
|
`UPDATE quality_gates
|
|
2590
1011
|
SET status = 'complete', verdict = :verdict, rationale = :rationale,
|
|
2591
1012
|
findings = :findings, evaluated_at = :evaluated_at
|
|
@@ -2634,29 +1055,11 @@ export function saveGateResult(g: {
|
|
|
2634
1055
|
});
|
|
2635
1056
|
}
|
|
2636
1057
|
|
|
2637
|
-
export function getPendingGates(milestoneId: string, sliceId: string, scope?: GateScope): GateRow[] {
|
|
2638
|
-
if (!currentDb) return [];
|
|
2639
|
-
const sql = scope
|
|
2640
|
-
? `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND scope = :scope AND status = 'pending'`
|
|
2641
|
-
: `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'`;
|
|
2642
|
-
const params: Record<string, unknown> = { ":mid": milestoneId, ":sid": sliceId };
|
|
2643
|
-
if (scope) params[":scope"] = scope;
|
|
2644
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2645
|
-
}
|
|
2646
1058
|
|
|
2647
|
-
export function getGateResults(milestoneId: string, sliceId: string, scope?: GateScope): GateRow[] {
|
|
2648
|
-
if (!currentDb) return [];
|
|
2649
|
-
const sql = scope
|
|
2650
|
-
? `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid AND scope = :scope`
|
|
2651
|
-
: `SELECT * FROM quality_gates WHERE milestone_id = :mid AND slice_id = :sid`;
|
|
2652
|
-
const params: Record<string, unknown> = { ":mid": milestoneId, ":sid": sliceId };
|
|
2653
|
-
if (scope) params[":scope"] = scope;
|
|
2654
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2655
|
-
}
|
|
2656
1059
|
|
|
2657
1060
|
export function markAllGatesOmitted(milestoneId: string, sliceId: string): void {
|
|
2658
|
-
if (!
|
|
2659
|
-
|
|
1061
|
+
if (!getDbOrNull()!) return;
|
|
1062
|
+
getDbOrNull()!.prepare(
|
|
2660
1063
|
`UPDATE quality_gates SET status = 'complete', verdict = 'omitted', evaluated_at = :now
|
|
2661
1064
|
WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'`,
|
|
2662
1065
|
).run({
|
|
@@ -2671,7 +1074,7 @@ export function markPendingGatesOmittedForTurn(
|
|
|
2671
1074
|
sliceId: string,
|
|
2672
1075
|
turn: OwnerTurn,
|
|
2673
1076
|
): void {
|
|
2674
|
-
if (!
|
|
1077
|
+
if (!getDbOrNull()!) return;
|
|
2675
1078
|
const gateIds = [...getGateIdsForTurn(turn)];
|
|
2676
1079
|
if (gateIds.length === 0) return;
|
|
2677
1080
|
const placeholders = gateIds.map((_, i) => `:gid${i}`).join(",");
|
|
@@ -2683,71 +1086,15 @@ export function markPendingGatesOmittedForTurn(
|
|
|
2683
1086
|
gateIds.forEach((id, index) => {
|
|
2684
1087
|
params[`:gid${index}`] = id;
|
|
2685
1088
|
});
|
|
2686
|
-
|
|
1089
|
+
getDbOrNull()!.prepare(
|
|
2687
1090
|
`UPDATE quality_gates SET status = 'complete', verdict = 'omitted', evaluated_at = :now
|
|
2688
1091
|
WHERE milestone_id = :mid AND slice_id = :sid AND status = 'pending'
|
|
2689
1092
|
AND gate_id IN (${placeholders})`,
|
|
2690
1093
|
).run(params);
|
|
2691
1094
|
}
|
|
2692
1095
|
|
|
2693
|
-
export function getPendingSliceGateCount(milestoneId: string, sliceId: string): number {
|
|
2694
|
-
if (!currentDb) return 0;
|
|
2695
|
-
const row = currentDb.prepare(
|
|
2696
|
-
`SELECT COUNT(*) as cnt FROM quality_gates
|
|
2697
|
-
WHERE milestone_id = :mid AND slice_id = :sid AND scope = 'slice' AND status = 'pending'`,
|
|
2698
|
-
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
2699
|
-
return row ? (row["cnt"] as number) : 0;
|
|
2700
|
-
}
|
|
2701
1096
|
|
|
2702
|
-
/**
|
|
2703
|
-
* Return pending gate rows owned by a specific workflow turn.
|
|
2704
|
-
*
|
|
2705
|
-
* Unlike `getPendingGates(..., scope)`, this filters by the registry's
|
|
2706
|
-
* `ownerTurn` metadata so callers can distinguish Q3/Q4 (owned by
|
|
2707
|
-
* gate-evaluate) from Q8 (owned by complete-slice) even though both are
|
|
2708
|
-
* scope:"slice". Pass `taskId` to narrow task-scoped results to one task.
|
|
2709
|
-
*/
|
|
2710
|
-
export function getPendingGatesForTurn(
|
|
2711
|
-
milestoneId: string,
|
|
2712
|
-
sliceId: string,
|
|
2713
|
-
turn: OwnerTurn,
|
|
2714
|
-
taskId?: string,
|
|
2715
|
-
): GateRow[] {
|
|
2716
|
-
if (!currentDb) return [];
|
|
2717
|
-
const ids = getGateIdsForTurn(turn);
|
|
2718
|
-
if (ids.size === 0) return [];
|
|
2719
|
-
const idList = [...ids];
|
|
2720
|
-
const placeholders = idList.map((_, i) => `:gid${i}`).join(",");
|
|
2721
|
-
const params: Record<string, unknown> = {
|
|
2722
|
-
":mid": milestoneId,
|
|
2723
|
-
":sid": sliceId,
|
|
2724
|
-
};
|
|
2725
|
-
idList.forEach((id, i) => {
|
|
2726
|
-
params[`:gid${i}`] = id;
|
|
2727
|
-
});
|
|
2728
|
-
let sql =
|
|
2729
|
-
`SELECT * FROM quality_gates
|
|
2730
|
-
WHERE milestone_id = :mid AND slice_id = :sid
|
|
2731
|
-
AND status = 'pending'
|
|
2732
|
-
AND gate_id IN (${placeholders})`;
|
|
2733
|
-
if (taskId !== undefined) {
|
|
2734
|
-
sql += ` AND task_id = :tid`;
|
|
2735
|
-
params[":tid"] = taskId;
|
|
2736
|
-
}
|
|
2737
|
-
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2738
|
-
}
|
|
2739
1097
|
|
|
2740
|
-
/**
|
|
2741
|
-
* Count pending gates for a turn. Convenience wrapper used by state
|
|
2742
|
-
* derivation to decide whether a phase transition should pause.
|
|
2743
|
-
*/
|
|
2744
|
-
export function getPendingGateCountForTurn(
|
|
2745
|
-
milestoneId: string,
|
|
2746
|
-
sliceId: string,
|
|
2747
|
-
turn: OwnerTurn,
|
|
2748
|
-
): number {
|
|
2749
|
-
return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
|
|
2750
|
-
}
|
|
2751
1098
|
|
|
2752
1099
|
export function insertGateRun(entry: {
|
|
2753
1100
|
traceId: string;
|
|
@@ -2768,8 +1115,8 @@ export function insertGateRun(entry: {
|
|
|
2768
1115
|
retryable: boolean;
|
|
2769
1116
|
evaluatedAt: string;
|
|
2770
1117
|
}): void {
|
|
2771
|
-
if (!
|
|
2772
|
-
|
|
1118
|
+
if (!getDbOrNull()!) return;
|
|
1119
|
+
getDbOrNull()!.prepare(
|
|
2773
1120
|
`INSERT INTO gate_runs (
|
|
2774
1121
|
trace_id, turn_id, gate_id, gate_type, unit_type, unit_id, milestone_id, slice_id, task_id,
|
|
2775
1122
|
outcome, failure_class, rationale, findings, attempt, max_attempts, retryable, evaluated_at
|
|
@@ -2811,8 +1158,8 @@ export function upsertTurnGitTransaction(entry: {
|
|
|
2811
1158
|
metadata?: Record<string, unknown>;
|
|
2812
1159
|
updatedAt: string;
|
|
2813
1160
|
}): void {
|
|
2814
|
-
if (!
|
|
2815
|
-
|
|
1161
|
+
if (!getDbOrNull()!) return;
|
|
1162
|
+
getDbOrNull()!.prepare(
|
|
2816
1163
|
`INSERT OR REPLACE INTO turn_git_transactions (
|
|
2817
1164
|
trace_id, turn_id, unit_type, unit_id, stage, action, push, status, error, metadata_json, updated_at
|
|
2818
1165
|
) VALUES (
|
|
@@ -2833,18 +1180,6 @@ export function upsertTurnGitTransaction(entry: {
|
|
|
2833
1180
|
});
|
|
2834
1181
|
}
|
|
2835
1182
|
|
|
2836
|
-
export function getMilestoneCommitAttributionShas(milestoneId: string): string[] {
|
|
2837
|
-
if (!currentDb) return [];
|
|
2838
|
-
const rows = currentDb.prepare(
|
|
2839
|
-
`SELECT commit_sha
|
|
2840
|
-
FROM milestone_commit_attributions
|
|
2841
|
-
WHERE milestone_id = :mid
|
|
2842
|
-
ORDER BY created_at, commit_sha`,
|
|
2843
|
-
).all({ ":mid": milestoneId }) as Array<Record<string, unknown>>;
|
|
2844
|
-
return rows
|
|
2845
|
-
.map((row) => typeof row["commit_sha"] === "string" ? row["commit_sha"] : "")
|
|
2846
|
-
.filter(Boolean);
|
|
2847
|
-
}
|
|
2848
1183
|
|
|
2849
1184
|
export function recordMilestoneCommitAttribution(entry: {
|
|
2850
1185
|
commitSha: string;
|
|
@@ -2856,9 +1191,9 @@ export function recordMilestoneCommitAttribution(entry: {
|
|
|
2856
1191
|
files: string[];
|
|
2857
1192
|
createdAt: string;
|
|
2858
1193
|
}): void {
|
|
2859
|
-
if (!
|
|
1194
|
+
if (!getDbOrNull()!) return;
|
|
2860
1195
|
transaction(() => {
|
|
2861
|
-
|
|
1196
|
+
getDbOrNull()!!.prepare(
|
|
2862
1197
|
`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
2863
1198
|
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
2864
1199
|
) VALUES (
|
|
@@ -2875,7 +1210,7 @@ export function recordMilestoneCommitAttribution(entry: {
|
|
|
2875
1210
|
":created_at": entry.createdAt,
|
|
2876
1211
|
});
|
|
2877
1212
|
|
|
2878
|
-
|
|
1213
|
+
getDbOrNull()!!.prepare(
|
|
2879
1214
|
`INSERT OR IGNORE INTO audit_events (
|
|
2880
1215
|
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2881
1216
|
) VALUES (
|
|
@@ -2912,9 +1247,9 @@ export function insertAuditEvent(entry: {
|
|
|
2912
1247
|
ts: string;
|
|
2913
1248
|
payload: Record<string, unknown>;
|
|
2914
1249
|
}): void {
|
|
2915
|
-
if (!
|
|
1250
|
+
if (!getDbOrNull()!) return;
|
|
2916
1251
|
transaction(() => {
|
|
2917
|
-
|
|
1252
|
+
getDbOrNull()!!.prepare(
|
|
2918
1253
|
`INSERT OR IGNORE INTO audit_events (
|
|
2919
1254
|
event_id, trace_id, turn_id, caused_by, category, type, ts, payload_json
|
|
2920
1255
|
) VALUES (
|
|
@@ -2932,7 +1267,7 @@ export function insertAuditEvent(entry: {
|
|
|
2932
1267
|
});
|
|
2933
1268
|
|
|
2934
1269
|
if (entry.turnId) {
|
|
2935
|
-
const row =
|
|
1270
|
+
const row = getDbOrNull()!!.prepare(
|
|
2936
1271
|
`SELECT event_count, first_ts, last_ts
|
|
2937
1272
|
FROM audit_turn_index
|
|
2938
1273
|
WHERE trace_id = :trace_id AND turn_id = :turn_id`,
|
|
@@ -2941,7 +1276,7 @@ export function insertAuditEvent(entry: {
|
|
|
2941
1276
|
":turn_id": entry.turnId,
|
|
2942
1277
|
});
|
|
2943
1278
|
if (row) {
|
|
2944
|
-
|
|
1279
|
+
getDbOrNull()!!.prepare(
|
|
2945
1280
|
`UPDATE audit_turn_index
|
|
2946
1281
|
SET first_ts = CASE WHEN :ts < first_ts THEN :ts ELSE first_ts END,
|
|
2947
1282
|
last_ts = CASE WHEN :ts > last_ts THEN :ts ELSE last_ts END,
|
|
@@ -2953,7 +1288,7 @@ export function insertAuditEvent(entry: {
|
|
|
2953
1288
|
":ts": entry.ts,
|
|
2954
1289
|
});
|
|
2955
1290
|
} else {
|
|
2956
|
-
|
|
1291
|
+
getDbOrNull()!!.prepare(
|
|
2957
1292
|
`INSERT INTO audit_turn_index (trace_id, turn_id, first_ts, last_ts, event_count)
|
|
2958
1293
|
VALUES (:trace_id, :turn_id, :first_ts, :last_ts, :event_count)`,
|
|
2959
1294
|
).run({
|
|
@@ -2977,20 +1312,20 @@ export function insertAuditEvent(entry: {
|
|
|
2977
1312
|
|
|
2978
1313
|
/** Delete a decision row by id. Used by db-writer.ts rollback on disk-write failure. */
|
|
2979
1314
|
export function deleteDecisionById(id: string): void {
|
|
2980
|
-
if (!
|
|
2981
|
-
|
|
1315
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1316
|
+
getDbOrNull()!.prepare("DELETE FROM decisions WHERE id = :id").run({ ":id": id });
|
|
2982
1317
|
}
|
|
2983
1318
|
|
|
2984
1319
|
/** Delete a requirement row by id. Used by db-writer.ts rollback on disk-write failure. */
|
|
2985
1320
|
export function deleteRequirementById(id: string): void {
|
|
2986
|
-
if (!
|
|
2987
|
-
|
|
1321
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1322
|
+
getDbOrNull()!.prepare("DELETE FROM requirements WHERE id = :id").run({ ":id": id });
|
|
2988
1323
|
}
|
|
2989
1324
|
|
|
2990
1325
|
/** Delete an artifact row by path. Used by db-writer.ts rollback on disk-write failure. */
|
|
2991
1326
|
export function deleteArtifactByPath(path: string): void {
|
|
2992
|
-
if (!
|
|
2993
|
-
|
|
1327
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1328
|
+
getDbOrNull()!.prepare("DELETE FROM artifacts WHERE path = :path").run({ ":path": path });
|
|
2994
1329
|
}
|
|
2995
1330
|
|
|
2996
1331
|
/**
|
|
@@ -2998,18 +1333,18 @@ export function deleteArtifactByPath(path: string): void {
|
|
|
2998
1333
|
* `gsd recover --confirm` to rebuild engine state from markdown.
|
|
2999
1334
|
*/
|
|
3000
1335
|
export function clearEngineHierarchy(): void {
|
|
3001
|
-
if (!
|
|
1336
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3002
1337
|
transaction(() => {
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
1338
|
+
getDbOrNull()!!.exec("DELETE FROM verification_evidence");
|
|
1339
|
+
getDbOrNull()!!.exec("DELETE FROM quality_gates");
|
|
1340
|
+
getDbOrNull()!!.exec("DELETE FROM slice_dependencies");
|
|
1341
|
+
getDbOrNull()!!.exec("DELETE FROM assessments");
|
|
1342
|
+
getDbOrNull()!!.exec("DELETE FROM replan_history");
|
|
1343
|
+
getDbOrNull()!!.exec("DELETE FROM milestone_commit_attributions");
|
|
1344
|
+
getDbOrNull()!!.exec("DELETE FROM tasks");
|
|
1345
|
+
getDbOrNull()!!.exec("DELETE FROM slices");
|
|
1346
|
+
getDbOrNull()!!.exec("DELETE FROM milestone_leases");
|
|
1347
|
+
getDbOrNull()!!.exec("DELETE FROM milestones");
|
|
3013
1348
|
});
|
|
3014
1349
|
}
|
|
3015
1350
|
|
|
@@ -3025,8 +1360,8 @@ export function insertOrIgnoreSlice(args: {
|
|
|
3025
1360
|
title: string;
|
|
3026
1361
|
createdAt: string;
|
|
3027
1362
|
}): void {
|
|
3028
|
-
if (!
|
|
3029
|
-
|
|
1363
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1364
|
+
getDbOrNull()!.prepare(
|
|
3030
1365
|
`INSERT OR IGNORE INTO slices (milestone_id, id, title, status, created_at)
|
|
3031
1366
|
VALUES (:mid, :sid, :title, 'pending', :ts)`,
|
|
3032
1367
|
).run({
|
|
@@ -3048,8 +1383,8 @@ export function insertOrIgnoreTask(args: {
|
|
|
3048
1383
|
title: string;
|
|
3049
1384
|
createdAt: string;
|
|
3050
1385
|
}): void {
|
|
3051
|
-
if (!
|
|
3052
|
-
|
|
1386
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1387
|
+
getDbOrNull()!.prepare(
|
|
3053
1388
|
`INSERT OR IGNORE INTO tasks (milestone_id, slice_id, id, title, status, created_at)
|
|
3054
1389
|
VALUES (:mid, :sid, :tid, :title, 'pending', :ts)`,
|
|
3055
1390
|
).run({
|
|
@@ -3067,8 +1402,8 @@ export function insertOrIgnoreTask(args: {
|
|
|
3067
1402
|
* trigger via DB in addition to the on-disk REPLAN-TRIGGER.md marker.
|
|
3068
1403
|
*/
|
|
3069
1404
|
export function setSliceReplanTriggeredAt(milestoneId: string, sliceId: string, ts: string): void {
|
|
3070
|
-
if (!
|
|
3071
|
-
|
|
1405
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1406
|
+
getDbOrNull()!.prepare(
|
|
3072
1407
|
"UPDATE slices SET replan_triggered_at = :ts WHERE milestone_id = :mid AND id = :sid",
|
|
3073
1408
|
).run({ ":ts": ts, ":mid": milestoneId, ":sid": sliceId });
|
|
3074
1409
|
}
|
|
@@ -3089,8 +1424,8 @@ export function upsertQualityGate(g: {
|
|
|
3089
1424
|
findings: string;
|
|
3090
1425
|
evaluatedAt: string;
|
|
3091
1426
|
}): void {
|
|
3092
|
-
if (!
|
|
3093
|
-
|
|
1427
|
+
if (!getDbOrNull()!) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
1428
|
+
getDbOrNull()!.prepare(
|
|
3094
1429
|
`INSERT OR REPLACE INTO quality_gates
|
|
3095
1430
|
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
3096
1431
|
VALUES (:mid, :sid, :gid, :scope, :tid, :status, :verdict, :rationale, :findings, :evaluated_at)`,
|
|
@@ -3108,525 +1443,3 @@ export function upsertQualityGate(g: {
|
|
|
3108
1443
|
});
|
|
3109
1444
|
}
|
|
3110
1445
|
|
|
3111
|
-
/**
|
|
3112
|
-
* Atomically replace all workflow state from a manifest. Lifted verbatim from
|
|
3113
|
-
* workflow-manifest.ts so the single-writer invariant holds. Restores
|
|
3114
|
-
* correctness-bearing workflow tables; runtime soft state and append-only audit
|
|
3115
|
-
* streams stay outside this recovery path.
|
|
3116
|
-
*/
|
|
3117
|
-
export function restoreManifest(manifest: StateManifest): void {
|
|
3118
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3119
|
-
const db = currentDb;
|
|
3120
|
-
|
|
3121
|
-
transaction(() => {
|
|
3122
|
-
const restoredMilestoneIds = new Set(manifest.milestones.map((m) => m.id));
|
|
3123
|
-
const restoredSliceKeys = new Set(manifest.slices.map((s) => JSON.stringify([s.milestone_id, s.id])));
|
|
3124
|
-
const preservedReplanHistory = manifest.replan_history === undefined
|
|
3125
|
-
? db.prepare("SELECT * FROM replan_history ORDER BY id").all() as unknown as NonNullable<StateManifest["replan_history"]>
|
|
3126
|
-
: [];
|
|
3127
|
-
const preservedAssessments = manifest.assessments === undefined
|
|
3128
|
-
? db.prepare("SELECT * FROM assessments ORDER BY path").all() as unknown as NonNullable<StateManifest["assessments"]>
|
|
3129
|
-
: [];
|
|
3130
|
-
const preservedQualityGates = manifest.quality_gates === undefined
|
|
3131
|
-
? db.prepare("SELECT * FROM quality_gates ORDER BY milestone_id, slice_id, gate_id, task_id").all() as unknown as NonNullable<StateManifest["quality_gates"]>
|
|
3132
|
-
: [];
|
|
3133
|
-
const preservedCommitAttributions = manifest.milestone_commit_attributions === undefined
|
|
3134
|
-
? db.prepare("SELECT * FROM milestone_commit_attributions ORDER BY milestone_id, commit_sha").all() as unknown as NonNullable<StateManifest["milestone_commit_attributions"]>
|
|
3135
|
-
: [];
|
|
3136
|
-
|
|
3137
|
-
// Clear workflow tables in dependency order.
|
|
3138
|
-
db.exec("DELETE FROM verification_evidence");
|
|
3139
|
-
db.exec("DELETE FROM quality_gates");
|
|
3140
|
-
db.exec("DELETE FROM slice_dependencies");
|
|
3141
|
-
db.exec("DELETE FROM assessments");
|
|
3142
|
-
db.exec("DELETE FROM replan_history");
|
|
3143
|
-
db.exec("DELETE FROM milestone_commit_attributions");
|
|
3144
|
-
db.exec("DELETE FROM tasks");
|
|
3145
|
-
db.exec("DELETE FROM slices");
|
|
3146
|
-
db.exec("DELETE FROM milestone_leases");
|
|
3147
|
-
db.exec("DELETE FROM milestones");
|
|
3148
|
-
db.exec("DELETE FROM decisions WHERE 1=1");
|
|
3149
|
-
db.exec(`DELETE FROM memories WHERE category = 'architecture' AND structured_fields LIKE '%"sourceDecisionId":"%'`);
|
|
3150
|
-
if (manifest.artifacts !== undefined) db.exec("DELETE FROM artifacts");
|
|
3151
|
-
if (manifest.requirements !== undefined) db.exec("DELETE FROM requirements");
|
|
3152
|
-
|
|
3153
|
-
if (manifest.requirements !== undefined) {
|
|
3154
|
-
const reqStmt = db.prepare(
|
|
3155
|
-
`INSERT INTO requirements (
|
|
3156
|
-
id, class, status, description, why, source, primary_owner,
|
|
3157
|
-
supporting_slices, validation, notes, full_content, superseded_by
|
|
3158
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3159
|
-
);
|
|
3160
|
-
for (const r of manifest.requirements) {
|
|
3161
|
-
reqStmt.run(
|
|
3162
|
-
r.id, r.class, r.status, r.description, r.why, r.source, r.primary_owner,
|
|
3163
|
-
r.supporting_slices, r.validation, r.notes, r.full_content, r.superseded_by,
|
|
3164
|
-
);
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
|
-
|
|
3168
|
-
if (manifest.artifacts !== undefined) {
|
|
3169
|
-
const artStmt = db.prepare(
|
|
3170
|
-
`INSERT INTO artifacts (
|
|
3171
|
-
path, artifact_type, milestone_id, slice_id, task_id, full_content, imported_at, content_hash
|
|
3172
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3173
|
-
);
|
|
3174
|
-
for (const a of manifest.artifacts) {
|
|
3175
|
-
const fullContent = a.full_content ?? "";
|
|
3176
|
-
artStmt.run(
|
|
3177
|
-
a.path, a.artifact_type, a.milestone_id, a.slice_id, a.task_id,
|
|
3178
|
-
fullContent, a.imported_at, a.content_hash ?? createHash("sha256").update(fullContent).digest("hex"),
|
|
3179
|
-
);
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
|
|
3183
|
-
// Restore milestones
|
|
3184
|
-
const msStmt = db.prepare(
|
|
3185
|
-
`INSERT INTO milestones (id, title, status, depends_on, created_at, completed_at,
|
|
3186
|
-
vision, success_criteria, key_risks, proof_strategy,
|
|
3187
|
-
verification_contract, verification_integration, verification_operational, verification_uat,
|
|
3188
|
-
definition_of_done, requirement_coverage, boundary_map_markdown, sequence)
|
|
3189
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3190
|
-
);
|
|
3191
|
-
for (const m of manifest.milestones) {
|
|
3192
|
-
msStmt.run(
|
|
3193
|
-
m.id, m.title, m.status,
|
|
3194
|
-
JSON.stringify(m.depends_on), m.created_at, m.completed_at,
|
|
3195
|
-
m.vision, JSON.stringify(m.success_criteria), JSON.stringify(m.key_risks),
|
|
3196
|
-
JSON.stringify(m.proof_strategy),
|
|
3197
|
-
m.verification_contract, m.verification_integration, m.verification_operational, m.verification_uat,
|
|
3198
|
-
JSON.stringify(m.definition_of_done), m.requirement_coverage, m.boundary_map_markdown, m.sequence ?? 0,
|
|
3199
|
-
);
|
|
3200
|
-
}
|
|
3201
|
-
|
|
3202
|
-
// Restore slices (ADR-011 Phase 1: includes is_sketch + sketch_scope)
|
|
3203
|
-
const slStmt = db.prepare(
|
|
3204
|
-
`INSERT INTO slices (milestone_id, id, title, status, risk, depends, demo,
|
|
3205
|
-
created_at, completed_at, full_summary_md, full_uat_md,
|
|
3206
|
-
goal, success_criteria, proof_level, integration_closure, observability_impact,
|
|
3207
|
-
target_repositories, sequence, replan_triggered_at, is_sketch, sketch_scope)
|
|
3208
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3209
|
-
);
|
|
3210
|
-
for (const s of manifest.slices) {
|
|
3211
|
-
slStmt.run(
|
|
3212
|
-
s.milestone_id, s.id, s.title, s.status, s.risk,
|
|
3213
|
-
JSON.stringify(s.depends), s.demo,
|
|
3214
|
-
s.created_at, s.completed_at, s.full_summary_md, s.full_uat_md,
|
|
3215
|
-
s.goal, s.success_criteria, s.proof_level, s.integration_closure, s.observability_impact,
|
|
3216
|
-
JSON.stringify(s.target_repositories ?? []),
|
|
3217
|
-
s.sequence, s.replan_triggered_at,
|
|
3218
|
-
s.is_sketch ?? 0,
|
|
3219
|
-
s.sketch_scope ?? "",
|
|
3220
|
-
);
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
const depStmt = db.prepare(
|
|
3224
|
-
"INSERT OR IGNORE INTO slice_dependencies (milestone_id, slice_id, depends_on_slice_id) VALUES (?, ?, ?)",
|
|
3225
|
-
);
|
|
3226
|
-
for (const s of manifest.slices) {
|
|
3227
|
-
for (const dep of s.depends ?? []) {
|
|
3228
|
-
depStmt.run(s.milestone_id, s.id, dep);
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
// Restore tasks (ADR-011 P2: includes blocker_source + escalation_* columns)
|
|
3233
|
-
const tkStmt = db.prepare(
|
|
3234
|
-
`INSERT INTO tasks (milestone_id, slice_id, id, title, status,
|
|
3235
|
-
one_liner, narrative, verification_result, duration, completed_at,
|
|
3236
|
-
blocker_discovered, deviations, known_issues, key_files, key_decisions,
|
|
3237
|
-
full_summary_md, description, estimate, files, verify,
|
|
3238
|
-
inputs, expected_output, observability_impact, full_plan_md, target_repositories, sequence,
|
|
3239
|
-
blocker_source, escalation_pending, escalation_awaiting_review,
|
|
3240
|
-
escalation_artifact_path, escalation_override_applied_at)
|
|
3241
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3242
|
-
);
|
|
3243
|
-
for (const t of manifest.tasks) {
|
|
3244
|
-
tkStmt.run(
|
|
3245
|
-
t.milestone_id, t.slice_id, t.id, t.title, t.status,
|
|
3246
|
-
t.one_liner, t.narrative, t.verification_result, t.duration, t.completed_at,
|
|
3247
|
-
t.blocker_discovered ? 1 : 0, t.deviations, t.known_issues,
|
|
3248
|
-
JSON.stringify(t.key_files), JSON.stringify(t.key_decisions),
|
|
3249
|
-
t.full_summary_md, t.description, t.estimate, JSON.stringify(t.files), t.verify,
|
|
3250
|
-
JSON.stringify(t.inputs), JSON.stringify(t.expected_output),
|
|
3251
|
-
t.observability_impact, t.full_plan_md ?? "", JSON.stringify(t.target_repositories ?? []), t.sequence,
|
|
3252
|
-
t.blocker_source ?? "",
|
|
3253
|
-
t.escalation_pending ?? 0,
|
|
3254
|
-
t.escalation_awaiting_review ?? 0,
|
|
3255
|
-
t.escalation_artifact_path ?? null,
|
|
3256
|
-
t.escalation_override_applied_at ?? null,
|
|
3257
|
-
);
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
// Restore decisions (ADR-011 P2: include source so escalation decisions survive)
|
|
3261
|
-
const dcStmt = db.prepare(
|
|
3262
|
-
`INSERT INTO decisions (seq, id, when_context, scope, decision, choice, rationale, revisable, made_by, source, superseded_by)
|
|
3263
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3264
|
-
);
|
|
3265
|
-
for (const d of manifest.decisions) {
|
|
3266
|
-
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);
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
const replanHistoryRows = manifest.replan_history ?? preservedReplanHistory.filter((r) => restoredMilestoneIds.has(r.milestone_id));
|
|
3270
|
-
if (replanHistoryRows.length > 0) {
|
|
3271
|
-
const replStmt = db.prepare(
|
|
3272
|
-
`INSERT INTO replan_history (
|
|
3273
|
-
id, milestone_id, slice_id, task_id, summary, previous_artifact_path, replacement_artifact_path, created_at
|
|
3274
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3275
|
-
);
|
|
3276
|
-
for (const r of replanHistoryRows) {
|
|
3277
|
-
replStmt.run(
|
|
3278
|
-
r.id, r.milestone_id, r.slice_id, r.task_id, r.summary,
|
|
3279
|
-
r.previous_artifact_path, r.replacement_artifact_path, r.created_at,
|
|
3280
|
-
);
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
const assessmentRows = manifest.assessments ?? preservedAssessments.filter((a) => restoredMilestoneIds.has(a.milestone_id));
|
|
3285
|
-
if (assessmentRows.length > 0) {
|
|
3286
|
-
const assessStmt = db.prepare(
|
|
3287
|
-
`INSERT INTO assessments (
|
|
3288
|
-
path, milestone_id, slice_id, task_id, status, scope, full_content, created_at
|
|
3289
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3290
|
-
);
|
|
3291
|
-
for (const a of assessmentRows) {
|
|
3292
|
-
assessStmt.run(
|
|
3293
|
-
a.path, a.milestone_id, a.slice_id, a.task_id,
|
|
3294
|
-
a.status, a.scope, a.full_content, a.created_at,
|
|
3295
|
-
);
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
|
|
3299
|
-
const qualityGateRows = manifest.quality_gates ?? preservedQualityGates.filter((g) => (
|
|
3300
|
-
restoredSliceKeys.has(JSON.stringify([g.milestone_id, g.slice_id]))
|
|
3301
|
-
));
|
|
3302
|
-
if (qualityGateRows.length > 0) {
|
|
3303
|
-
const gateStmt = db.prepare(
|
|
3304
|
-
`INSERT INTO quality_gates (
|
|
3305
|
-
milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at
|
|
3306
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3307
|
-
);
|
|
3308
|
-
for (const g of qualityGateRows) {
|
|
3309
|
-
gateStmt.run(
|
|
3310
|
-
g.milestone_id, g.slice_id, g.gate_id, g.scope, g.task_id,
|
|
3311
|
-
g.status, g.verdict ?? "", g.rationale, g.findings, g.evaluated_at,
|
|
3312
|
-
);
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
const commitAttributionRows = manifest.milestone_commit_attributions ??
|
|
3317
|
-
preservedCommitAttributions.filter((a) => restoredMilestoneIds.has(a.milestone_id));
|
|
3318
|
-
if (commitAttributionRows.length > 0) {
|
|
3319
|
-
const attrStmt = db.prepare(
|
|
3320
|
-
`INSERT OR REPLACE INTO milestone_commit_attributions (
|
|
3321
|
-
commit_sha, milestone_id, slice_id, task_id, source, confidence, files_json, created_at
|
|
3322
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3323
|
-
);
|
|
3324
|
-
for (const a of commitAttributionRows) {
|
|
3325
|
-
attrStmt.run(
|
|
3326
|
-
a.commit_sha, a.milestone_id, a.slice_id, a.task_id,
|
|
3327
|
-
a.source, a.confidence, a.files_json, a.created_at,
|
|
3328
|
-
);
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
|
-
// Restore verification evidence
|
|
3333
|
-
const evStmt = db.prepare(
|
|
3334
|
-
`INSERT INTO verification_evidence (task_id, slice_id, milestone_id, command, exit_code, verdict, duration_ms, created_at)
|
|
3335
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3336
|
-
);
|
|
3337
|
-
for (const e of manifest.verification_evidence) {
|
|
3338
|
-
evStmt.run(e.task_id, e.slice_id, e.milestone_id, e.command, e.exit_code, e.verdict, e.duration_ms, e.created_at);
|
|
3339
|
-
}
|
|
3340
|
-
});
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
// ─── Legacy markdown → DB bulk migration ─────────────────────────────────
|
|
3344
|
-
|
|
3345
|
-
export interface LegacyMilestoneInsert {
|
|
3346
|
-
id: string;
|
|
3347
|
-
title: string;
|
|
3348
|
-
status: string;
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
export interface LegacySliceInsert {
|
|
3352
|
-
id: string;
|
|
3353
|
-
milestoneId: string;
|
|
3354
|
-
title: string;
|
|
3355
|
-
status: string;
|
|
3356
|
-
risk: string;
|
|
3357
|
-
sequence: number;
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
|
-
export interface LegacyTaskInsert {
|
|
3361
|
-
id: string;
|
|
3362
|
-
sliceId: string;
|
|
3363
|
-
milestoneId: string;
|
|
3364
|
-
title: string;
|
|
3365
|
-
status: string;
|
|
3366
|
-
sequence: number;
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
/**
|
|
3370
|
-
* Bulk delete + insert a legacy milestone hierarchy for markdown → DB migration.
|
|
3371
|
-
* Used by workflow-migration.ts to populate engine tables from parsed ROADMAP/PLAN
|
|
3372
|
-
* files. All operations run inside a single transaction.
|
|
3373
|
-
*/
|
|
3374
|
-
export function bulkInsertLegacyHierarchy(payload: {
|
|
3375
|
-
milestones: LegacyMilestoneInsert[];
|
|
3376
|
-
slices: LegacySliceInsert[];
|
|
3377
|
-
tasks: LegacyTaskInsert[];
|
|
3378
|
-
clearMilestoneIds: string[];
|
|
3379
|
-
createdAt: string;
|
|
3380
|
-
}): void {
|
|
3381
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3382
|
-
const db = currentDb;
|
|
3383
|
-
const { milestones, slices, tasks, clearMilestoneIds, createdAt } = payload;
|
|
3384
|
-
|
|
3385
|
-
if (clearMilestoneIds.length === 0) return;
|
|
3386
|
-
const placeholders = clearMilestoneIds.map(() => "?").join(",");
|
|
3387
|
-
|
|
3388
|
-
transaction(() => {
|
|
3389
|
-
db.prepare(`DELETE FROM tasks WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
3390
|
-
db.prepare(`DELETE FROM slices WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
3391
|
-
db.prepare(`DELETE FROM milestone_leases WHERE milestone_id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
3392
|
-
db.prepare(`DELETE FROM milestones WHERE id IN (${placeholders})`).run(...clearMilestoneIds);
|
|
3393
|
-
|
|
3394
|
-
const insertMilestone = db.prepare(
|
|
3395
|
-
"INSERT INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)",
|
|
3396
|
-
);
|
|
3397
|
-
for (const m of milestones) {
|
|
3398
|
-
insertMilestone.run(m.id, m.title, m.status, createdAt);
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
const insertSliceStmt = db.prepare(
|
|
3402
|
-
"INSERT INTO slices (id, milestone_id, title, status, risk, depends, sequence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3403
|
-
);
|
|
3404
|
-
for (const s of slices) {
|
|
3405
|
-
insertSliceStmt.run(s.id, s.milestoneId, s.title, s.status, s.risk, "[]", s.sequence, createdAt);
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
const insertTaskStmt = db.prepare(
|
|
3409
|
-
"INSERT INTO tasks (id, slice_id, milestone_id, title, description, status, estimate, files, sequence) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
3410
|
-
);
|
|
3411
|
-
for (const t of tasks) {
|
|
3412
|
-
insertTaskStmt.run(t.id, t.sliceId, t.milestoneId, t.title, "", t.status, "", "[]", t.sequence);
|
|
3413
|
-
}
|
|
3414
|
-
});
|
|
3415
|
-
}
|
|
3416
|
-
|
|
3417
|
-
// ─── Memory store writers ────────────────────────────────────────────────
|
|
3418
|
-
// All memory writes go through gsd-db.ts so the single-writer invariant
|
|
3419
|
-
// holds. These are direct pass-throughs to the SQL previously in
|
|
3420
|
-
// memory-store.ts — same bindings, same behavior.
|
|
3421
|
-
|
|
3422
|
-
export function insertMemoryRow(args: {
|
|
3423
|
-
id: string;
|
|
3424
|
-
category: string;
|
|
3425
|
-
content: string;
|
|
3426
|
-
confidence: number;
|
|
3427
|
-
sourceUnitType: string | null;
|
|
3428
|
-
sourceUnitId: string | null;
|
|
3429
|
-
createdAt: string;
|
|
3430
|
-
updatedAt: string;
|
|
3431
|
-
scope?: string;
|
|
3432
|
-
tags?: string[];
|
|
3433
|
-
/**
|
|
3434
|
-
* ADR-013 Step 2: optional structured payload preserved alongside the flat
|
|
3435
|
-
* `content` field. Used to retain gsd_save_decision-style fields (scope,
|
|
3436
|
-
* decision, choice, rationale, made_by, revisable) on architecture-category
|
|
3437
|
-
* memories so the cutover in Step 6 is lossless. Schema is intentionally
|
|
3438
|
-
* open inside the JSON; documented per category in ADR-013.
|
|
3439
|
-
*/
|
|
3440
|
-
structuredFields?: Record<string, unknown> | null;
|
|
3441
|
-
}): void {
|
|
3442
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3443
|
-
currentDb.prepare(
|
|
3444
|
-
`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at, scope, tags, structured_fields)
|
|
3445
|
-
VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at, :scope, :tags, :structured_fields)`,
|
|
3446
|
-
).run({
|
|
3447
|
-
":id": args.id,
|
|
3448
|
-
":category": args.category,
|
|
3449
|
-
":content": args.content,
|
|
3450
|
-
":confidence": args.confidence,
|
|
3451
|
-
":source_unit_type": args.sourceUnitType,
|
|
3452
|
-
":source_unit_id": args.sourceUnitId,
|
|
3453
|
-
":created_at": args.createdAt,
|
|
3454
|
-
":updated_at": args.updatedAt,
|
|
3455
|
-
":scope": args.scope ?? "project",
|
|
3456
|
-
":tags": JSON.stringify(args.tags ?? []),
|
|
3457
|
-
":structured_fields": args.structuredFields == null ? null : JSON.stringify(args.structuredFields),
|
|
3458
|
-
});
|
|
3459
|
-
}
|
|
3460
|
-
|
|
3461
|
-
export function insertMemorySourceRow(args: {
|
|
3462
|
-
id: string;
|
|
3463
|
-
kind: string;
|
|
3464
|
-
uri: string | null;
|
|
3465
|
-
title: string | null;
|
|
3466
|
-
content: string;
|
|
3467
|
-
contentHash: string;
|
|
3468
|
-
importedAt: string;
|
|
3469
|
-
scope?: string;
|
|
3470
|
-
tags?: string[];
|
|
3471
|
-
}): void {
|
|
3472
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3473
|
-
currentDb.prepare(
|
|
3474
|
-
`INSERT OR IGNORE INTO memory_sources (id, kind, uri, title, content, content_hash, imported_at, scope, tags)
|
|
3475
|
-
VALUES (:id, :kind, :uri, :title, :content, :content_hash, :imported_at, :scope, :tags)`,
|
|
3476
|
-
).run({
|
|
3477
|
-
":id": args.id,
|
|
3478
|
-
":kind": args.kind,
|
|
3479
|
-
":uri": args.uri,
|
|
3480
|
-
":title": args.title,
|
|
3481
|
-
":content": args.content,
|
|
3482
|
-
":content_hash": args.contentHash,
|
|
3483
|
-
":imported_at": args.importedAt,
|
|
3484
|
-
":scope": args.scope ?? "project",
|
|
3485
|
-
":tags": JSON.stringify(args.tags ?? []),
|
|
3486
|
-
});
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
|
-
export function deleteMemorySourceRow(id: string): boolean {
|
|
3490
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3491
|
-
const res = currentDb
|
|
3492
|
-
.prepare("DELETE FROM memory_sources WHERE id = :id")
|
|
3493
|
-
.run({ ":id": id }) as { changes?: number };
|
|
3494
|
-
return (res?.changes ?? 0) > 0;
|
|
3495
|
-
}
|
|
3496
|
-
|
|
3497
|
-
export function upsertMemoryEmbedding(args: {
|
|
3498
|
-
memoryId: string;
|
|
3499
|
-
model: string;
|
|
3500
|
-
dim: number;
|
|
3501
|
-
vector: Uint8Array;
|
|
3502
|
-
updatedAt: string;
|
|
3503
|
-
}): void {
|
|
3504
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3505
|
-
currentDb.prepare(
|
|
3506
|
-
`INSERT INTO memory_embeddings (memory_id, model, dim, vector, updated_at)
|
|
3507
|
-
VALUES (:memory_id, :model, :dim, :vector, :updated_at)
|
|
3508
|
-
ON CONFLICT(memory_id) DO UPDATE SET
|
|
3509
|
-
model = excluded.model,
|
|
3510
|
-
dim = excluded.dim,
|
|
3511
|
-
vector = excluded.vector,
|
|
3512
|
-
updated_at = excluded.updated_at`,
|
|
3513
|
-
).run({
|
|
3514
|
-
":memory_id": args.memoryId,
|
|
3515
|
-
":model": args.model,
|
|
3516
|
-
":dim": args.dim,
|
|
3517
|
-
":vector": args.vector,
|
|
3518
|
-
":updated_at": args.updatedAt,
|
|
3519
|
-
});
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
export function deleteMemoryEmbedding(memoryId: string): boolean {
|
|
3523
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3524
|
-
const res = currentDb
|
|
3525
|
-
.prepare("DELETE FROM memory_embeddings WHERE memory_id = :id")
|
|
3526
|
-
.run({ ":id": memoryId }) as { changes?: number };
|
|
3527
|
-
return (res?.changes ?? 0) > 0;
|
|
3528
|
-
}
|
|
3529
|
-
|
|
3530
|
-
export function insertMemoryRelationRow(args: {
|
|
3531
|
-
fromId: string;
|
|
3532
|
-
toId: string;
|
|
3533
|
-
rel: string;
|
|
3534
|
-
confidence: number;
|
|
3535
|
-
createdAt: string;
|
|
3536
|
-
}): void {
|
|
3537
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3538
|
-
currentDb.prepare(
|
|
3539
|
-
`INSERT OR REPLACE INTO memory_relations (from_id, to_id, rel, confidence, created_at)
|
|
3540
|
-
VALUES (:from_id, :to_id, :rel, :confidence, :created_at)`,
|
|
3541
|
-
).run({
|
|
3542
|
-
":from_id": args.fromId,
|
|
3543
|
-
":to_id": args.toId,
|
|
3544
|
-
":rel": args.rel,
|
|
3545
|
-
":confidence": args.confidence,
|
|
3546
|
-
":created_at": args.createdAt,
|
|
3547
|
-
});
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
export function deleteMemoryRelationsFor(memoryId: string): void {
|
|
3551
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3552
|
-
currentDb
|
|
3553
|
-
.prepare("DELETE FROM memory_relations WHERE from_id = :id OR to_id = :id")
|
|
3554
|
-
.run({ ":id": memoryId });
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
export function rewriteMemoryId(placeholderId: string, realId: string): void {
|
|
3558
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3559
|
-
currentDb.prepare("UPDATE memories SET id = :real_id WHERE id = :placeholder").run({
|
|
3560
|
-
":real_id": realId,
|
|
3561
|
-
":placeholder": placeholderId,
|
|
3562
|
-
});
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
export function updateMemoryContentRow(
|
|
3566
|
-
id: string,
|
|
3567
|
-
content: string,
|
|
3568
|
-
confidence: number | undefined,
|
|
3569
|
-
updatedAt: string,
|
|
3570
|
-
): void {
|
|
3571
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3572
|
-
if (confidence != null) {
|
|
3573
|
-
currentDb.prepare(
|
|
3574
|
-
"UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id",
|
|
3575
|
-
).run({ ":content": content, ":confidence": confidence, ":updated_at": updatedAt, ":id": id });
|
|
3576
|
-
} else {
|
|
3577
|
-
currentDb.prepare(
|
|
3578
|
-
"UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id",
|
|
3579
|
-
).run({ ":content": content, ":updated_at": updatedAt, ":id": id });
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
|
|
3583
|
-
export function incrementMemoryHitCount(id: string, updatedAt: string): void {
|
|
3584
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3585
|
-
currentDb.prepare(
|
|
3586
|
-
"UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at, last_hit_at = :last_hit_at WHERE id = :id",
|
|
3587
|
-
).run({ ":updated_at": updatedAt, ":last_hit_at": updatedAt, ":id": id });
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
export function supersedeMemoryRow(oldId: string, newId: string, updatedAt: string): void {
|
|
3591
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3592
|
-
currentDb.prepare(
|
|
3593
|
-
"UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id",
|
|
3594
|
-
).run({ ":new_id": newId, ":updated_at": updatedAt, ":old_id": oldId });
|
|
3595
|
-
}
|
|
3596
|
-
|
|
3597
|
-
export function markMemoryUnitProcessed(
|
|
3598
|
-
unitKey: string,
|
|
3599
|
-
activityFile: string,
|
|
3600
|
-
processedAt: string,
|
|
3601
|
-
): void {
|
|
3602
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3603
|
-
currentDb.prepare(
|
|
3604
|
-
`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
|
|
3605
|
-
VALUES (:key, :file, :at)`,
|
|
3606
|
-
).run({ ":key": unitKey, ":file": activityFile, ":at": processedAt });
|
|
3607
|
-
}
|
|
3608
|
-
|
|
3609
|
-
export function decayMemoriesBefore(cutoffTs: string, now: string): void {
|
|
3610
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3611
|
-
currentDb.prepare(
|
|
3612
|
-
`UPDATE memories
|
|
3613
|
-
SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
|
|
3614
|
-
WHERE superseded_by IS NULL
|
|
3615
|
-
AND updated_at < :cutoff
|
|
3616
|
-
AND confidence > 0.1
|
|
3617
|
-
AND (structured_fields IS NULL OR structured_fields NOT LIKE '%"sourceDecisionId"%')`,
|
|
3618
|
-
).run({ ":now": now, ":cutoff": cutoffTs });
|
|
3619
|
-
}
|
|
3620
|
-
|
|
3621
|
-
export function supersedeLowestRankedMemories(limit: number, now: string): void {
|
|
3622
|
-
if (!currentDb) throw new GSDError(GSD_STALE_STATE, "gsd-db: No database open");
|
|
3623
|
-
currentDb.prepare(
|
|
3624
|
-
`UPDATE memories SET superseded_by = 'CAP_EXCEEDED', updated_at = :now
|
|
3625
|
-
WHERE id IN (
|
|
3626
|
-
SELECT id FROM memories
|
|
3627
|
-
WHERE superseded_by IS NULL
|
|
3628
|
-
ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
|
|
3629
|
-
LIMIT :limit
|
|
3630
|
-
)`,
|
|
3631
|
-
).run({ ":now": now, ":limit": limit });
|
|
3632
|
-
}
|