@cleocode/core 2026.9.8 → 2026.9.9
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/audit/reconstruct.d.ts +13 -43
- package/dist/audit/reconstruct.d.ts.map +1 -1
- package/dist/audit/reconstruct.js +295 -290
- package/dist/audit/reconstruct.js.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +8 -2
- package/dist/bootstrap.js.map +1 -1
- package/dist/dispatch/mutate-projection.d.ts.map +1 -1
- package/dist/dispatch/mutate-projection.js +12 -0
- package/dist/dispatch/mutate-projection.js.map +1 -1
- package/dist/dispatch/mvi-projection.d.ts +29 -6
- package/dist/dispatch/mvi-projection.d.ts.map +1 -1
- package/dist/dispatch/mvi-projection.js +109 -139
- package/dist/dispatch/mvi-projection.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +3 -2
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +4 -3
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/export-document.js +5100 -2297
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/projection.d.ts +67 -0
- package/dist/docs/projection.d.ts.map +1 -0
- package/dist/docs/projection.js +370 -0
- package/dist/docs/projection.js.map +1 -0
- package/dist/doctor/acceptance-drift.d.ts +6 -2
- package/dist/doctor/acceptance-drift.d.ts.map +1 -1
- package/dist/doctor/acceptance-drift.js +43 -5
- package/dist/doctor/acceptance-drift.js.map +1 -1
- package/dist/doctor/knowledge-summary.d.ts +41 -0
- package/dist/doctor/knowledge-summary.d.ts.map +1 -0
- package/dist/doctor/knowledge-summary.js +81 -0
- package/dist/doctor/knowledge-summary.js.map +1 -0
- package/dist/doctor/knowledge.d.ts +192 -0
- package/dist/doctor/knowledge.d.ts.map +1 -0
- package/dist/doctor/knowledge.js +1864 -0
- package/dist/doctor/knowledge.js.map +1 -0
- package/dist/git/work-tree.d.ts +64 -0
- package/dist/git/work-tree.d.ts.map +1 -0
- package/dist/git/work-tree.js +111 -0
- package/dist/git/work-tree.js.map +1 -0
- package/dist/hooks/handlers/task-hooks.d.ts +8 -5
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +15 -60
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/identity/cleo-identity.d.ts +13 -0
- package/dist/identity/cleo-identity.d.ts.map +1 -1
- package/dist/identity/cleo-identity.js +20 -6
- package/dist/identity/cleo-identity.js.map +1 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +29 -13
- package/dist/injection.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.js +124 -120
- package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
- package/dist/llm/plugin-facade.js +4018 -1726
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/brain-backfill.d.ts +27 -12
- package/dist/memory/brain-backfill.d.ts.map +1 -1
- package/dist/memory/brain-backfill.js +325 -134
- package/dist/memory/brain-backfill.js.map +1 -1
- package/dist/memory/brain-doctor.d.ts +21 -0
- package/dist/memory/brain-doctor.d.ts.map +1 -1
- package/dist/memory/brain-doctor.js +111 -53
- package/dist/memory/brain-doctor.js.map +1 -1
- package/dist/memory/brain-links.d.ts.map +1 -1
- package/dist/memory/brain-links.js +31 -4
- package/dist/memory/brain-links.js.map +1 -1
- package/dist/memory/brain-reasoning.d.ts.map +1 -1
- package/dist/memory/brain-reasoning.js +28 -2
- package/dist/memory/brain-reasoning.js.map +1 -1
- package/dist/memory/brain-reconciler.d.ts +14 -19
- package/dist/memory/brain-reconciler.d.ts.map +1 -1
- package/dist/memory/brain-reconciler.js +70 -97
- package/dist/memory/brain-reconciler.js.map +1 -1
- package/dist/memory/brain-search.d.ts +2 -0
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-search.js +59 -29
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/brain-similarity.d.ts.map +1 -1
- package/dist/memory/brain-similarity.js +5 -4
- package/dist/memory/brain-similarity.js.map +1 -1
- package/dist/memory/brain-stub-prune.d.ts +31 -9
- package/dist/memory/brain-stub-prune.d.ts.map +1 -1
- package/dist/memory/brain-stub-prune.js +65 -18
- package/dist/memory/brain-stub-prune.js.map +1 -1
- package/dist/memory/brain-writer-handlers.d.ts +10 -1
- package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
- package/dist/memory/brain-writer-handlers.js +23 -3
- package/dist/memory/brain-writer-handlers.js.map +1 -1
- package/dist/memory/brain-writer-thread.d.ts +11 -1
- package/dist/memory/brain-writer-thread.d.ts.map +1 -1
- package/dist/memory/brain-writer-thread.js +87 -20
- package/dist/memory/brain-writer-thread.js.map +1 -1
- package/dist/memory/brain-writer-worker.js +8 -1
- package/dist/memory/brain-writer-worker.js.map +1 -1
- package/dist/memory/decision-cross-link.d.ts +39 -0
- package/dist/memory/decision-cross-link.d.ts.map +1 -1
- package/dist/memory/decision-cross-link.js +316 -0
- package/dist/memory/decision-cross-link.js.map +1 -1
- package/dist/memory/decisions.d.ts +4 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +7 -24
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/dispatch-trace.d.ts +12 -5
- package/dist/memory/dispatch-trace.d.ts.map +1 -1
- package/dist/memory/dispatch-trace.js +17 -7
- package/dist/memory/dispatch-trace.js.map +1 -1
- package/dist/memory/eligibility.d.ts +51 -0
- package/dist/memory/eligibility.d.ts.map +1 -0
- package/dist/memory/eligibility.js +77 -0
- package/dist/memory/eligibility.js.map +1 -0
- package/dist/memory/engine-compat.d.ts +2 -0
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +10 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/extraction-gate.d.ts +21 -7
- package/dist/memory/extraction-gate.d.ts.map +1 -1
- package/dist/memory/extraction-gate.js +178 -216
- package/dist/memory/extraction-gate.js.map +1 -1
- package/dist/memory/graph-auto-populate.d.ts +38 -5
- package/dist/memory/graph-auto-populate.d.ts.map +1 -1
- package/dist/memory/graph-auto-populate.js +182 -60
- package/dist/memory/graph-auto-populate.js.map +1 -1
- package/dist/memory/graph-memory-bridge.d.ts +14 -1
- package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
- package/dist/memory/graph-memory-bridge.js +66 -28
- package/dist/memory/graph-memory-bridge.js.map +1 -1
- package/dist/memory/index.d.ts +1 -19
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -50
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/learnings.d.ts.map +1 -1
- package/dist/memory/learnings.js +2 -23
- package/dist/memory/learnings.js.map +1 -1
- package/dist/memory/manifest-filter.d.ts +21 -0
- package/dist/memory/manifest-filter.d.ts.map +1 -0
- package/dist/memory/manifest-filter.js +51 -0
- package/dist/memory/manifest-filter.js.map +1 -0
- package/dist/memory/manifest-ingestion.d.ts +28 -30
- package/dist/memory/manifest-ingestion.d.ts.map +1 -1
- package/dist/memory/manifest-ingestion.js +104 -235
- package/dist/memory/manifest-ingestion.js.map +1 -1
- package/dist/memory/observer-reflector.d.ts +2 -0
- package/dist/memory/observer-reflector.d.ts.map +1 -1
- package/dist/memory/observer-reflector.js +6 -49
- package/dist/memory/observer-reflector.js.map +1 -1
- package/dist/memory/patterns.d.ts +10 -0
- package/dist/memory/patterns.d.ts.map +1 -1
- package/dist/memory/patterns.js +25 -33
- package/dist/memory/patterns.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +72 -9
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +508 -412
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.js +16 -15
- package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
- package/dist/memory/retrieval/fetch.d.ts +7 -0
- package/dist/memory/retrieval/fetch.d.ts.map +1 -1
- package/dist/memory/retrieval/fetch.js +14 -11
- package/dist/memory/retrieval/fetch.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts +7 -1
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +58 -4
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/retrieval/search.d.ts +14 -1
- package/dist/memory/retrieval/search.d.ts.map +1 -1
- package/dist/memory/retrieval/search.js +133 -69
- package/dist/memory/retrieval/search.js.map +1 -1
- package/dist/memory/retrieval/telemetry.d.ts +31 -0
- package/dist/memory/retrieval/telemetry.d.ts.map +1 -0
- package/dist/memory/retrieval/telemetry.js +53 -0
- package/dist/memory/retrieval/telemetry.js.map +1 -0
- package/dist/nexus/analyze-orchestrator.d.ts +41 -9
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +257 -58
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/api-contracts.d.ts +2 -6
- package/dist/nexus/api-contracts.d.ts.map +1 -1
- package/dist/nexus/api-contracts.js +11 -2
- package/dist/nexus/api-contracts.js.map +1 -1
- package/dist/nexus/context.d.ts.map +1 -1
- package/dist/nexus/context.js +14 -37
- package/dist/nexus/context.js.map +1 -1
- package/dist/nexus/diff.d.ts.map +1 -1
- package/dist/nexus/diff.js +2 -1
- package/dist/nexus/diff.js.map +1 -1
- package/dist/nexus/identity.d.ts +29 -3
- package/dist/nexus/identity.d.ts.map +1 -1
- package/dist/nexus/identity.js +63 -12
- package/dist/nexus/identity.js.map +1 -1
- package/dist/nexus/impact.d.ts +21 -19
- package/dist/nexus/impact.d.ts.map +1 -1
- package/dist/nexus/impact.js +96 -192
- package/dist/nexus/impact.js.map +1 -1
- package/dist/nexus/knowledge.d.ts +80 -0
- package/dist/nexus/knowledge.d.ts.map +1 -0
- package/dist/nexus/knowledge.js +786 -0
- package/dist/nexus/knowledge.js.map +1 -0
- package/dist/nexus/living-brain.d.ts +8 -5
- package/dist/nexus/living-brain.d.ts.map +1 -1
- package/dist/nexus/living-brain.js +252 -188
- package/dist/nexus/living-brain.js.map +1 -1
- package/dist/nexus/registry.d.ts +11 -2
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +144 -128
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/source-roots.d.ts +16 -0
- package/dist/nexus/source-roots.d.ts.map +1 -0
- package/dist/nexus/source-roots.js +174 -0
- package/dist/nexus/source-roots.js.map +1 -0
- package/dist/nexus/task-evidence.d.ts +22 -0
- package/dist/nexus/task-evidence.d.ts.map +1 -0
- package/dist/nexus/task-evidence.js +210 -0
- package/dist/nexus/task-evidence.js.map +1 -0
- package/dist/nexus/tasks-bridge.d.ts +14 -7
- package/dist/nexus/tasks-bridge.d.ts.map +1 -1
- package/dist/nexus/tasks-bridge.js +81 -40
- package/dist/nexus/tasks-bridge.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +170 -120
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts +6 -7
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +509 -532
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestration/bootstrap.d.ts +15 -1
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/bootstrap.js +21 -10
- package/dist/orchestration/bootstrap.js.map +1 -1
- package/dist/orchestration/index.d.ts +35 -3
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +64 -35
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +24 -1
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +31 -11
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +16 -41
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/unblock.d.ts +15 -1
- package/dist/orchestration/unblock.d.ts.map +1 -1
- package/dist/orchestration/unblock.js +23 -12
- package/dist/orchestration/unblock.js.map +1 -1
- package/dist/orchestration/validate-spawn.d.ts.map +1 -1
- package/dist/orchestration/validate-spawn.js +3 -3
- package/dist/orchestration/validate-spawn.js.map +1 -1
- package/dist/orchestration/waves.d.ts +29 -13
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +43 -55
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/paths.d.ts +17 -163
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +145 -496
- package/dist/paths.js.map +1 -1
- package/dist/project-info.d.ts +2 -11
- package/dist/project-info.d.ts.map +1 -1
- package/dist/project-info.js +3 -31
- package/dist/project-info.js.map +1 -1
- package/dist/project-scope.d.ts +239 -0
- package/dist/project-scope.d.ts.map +1 -0
- package/dist/project-scope.js +473 -0
- package/dist/project-scope.js.map +1 -0
- package/dist/release/pr-evidence.d.ts +16 -3
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +53 -7
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/render/nexus/graph/index.d.ts.map +1 -1
- package/dist/render/nexus/graph/index.js +4 -2
- package/dist/render/nexus/graph/index.js.map +1 -1
- package/dist/render/orchestration/audit-reconstruct.d.ts +11 -0
- package/dist/render/orchestration/audit-reconstruct.d.ts.map +1 -1
- package/dist/render/orchestration/audit-reconstruct.js +21 -6
- package/dist/render/orchestration/audit-reconstruct.js.map +1 -1
- package/dist/render/tasks/show.js +2 -1
- package/dist/render/tasks/show.js.map +1 -1
- package/dist/resources/spawn-wrapper.d.ts +82 -14
- package/dist/resources/spawn-wrapper.d.ts.map +1 -1
- package/dist/resources/spawn-wrapper.js +585 -27
- package/dist/resources/spawn-wrapper.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +4026 -1734
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/index.js +76 -68
- package/dist/sequence/index.js.map +1 -1
- package/dist/session/canon-lint.d.ts +24 -3
- package/dist/session/canon-lint.d.ts.map +1 -1
- package/dist/session/canon-lint.js +72 -31
- package/dist/session/canon-lint.js.map +1 -1
- package/dist/session/engine-ops.d.ts +1 -4
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +6 -2
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +11 -1
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +76 -8
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/handoff.d.ts +3 -1
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/handoff.js +2 -0
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/shutdown-deadline.d.ts +24 -33
- package/dist/shutdown-deadline.d.ts.map +1 -1
- package/dist/shutdown-deadline.js +71 -14
- package/dist/shutdown-deadline.js.map +1 -1
- package/dist/shutdown.d.ts +21 -10
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +89 -42
- package/dist/shutdown.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/orchestrator/validator.d.ts +87 -20
- package/dist/skills/orchestrator/validator.d.ts.map +1 -1
- package/dist/skills/orchestrator/validator.js +330 -241
- package/dist/skills/orchestrator/validator.js.map +1 -1
- package/dist/store/agent-resolver.d.ts.map +1 -1
- package/dist/store/agent-resolver.js +16 -9
- package/dist/store/agent-resolver.js.map +1 -1
- package/dist/store/attachment-store.d.ts +4 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +20 -3
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/background-jobs.d.ts +426 -105
- package/dist/store/background-jobs.d.ts.map +1 -1
- package/dist/store/background-jobs.js +1208 -252
- package/dist/store/background-jobs.js.map +1 -1
- package/dist/store/background-ops.d.ts +145 -8
- package/dist/store/background-ops.d.ts.map +1 -1
- package/dist/store/background-ops.js +459 -15
- package/dist/store/background-ops.js.map +1 -1
- package/dist/store/backup-inspect.d.ts +48 -4
- package/dist/store/backup-inspect.d.ts.map +1 -1
- package/dist/store/backup-inspect.js +384 -4
- package/dist/store/backup-inspect.js.map +1 -1
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +19 -2
- package/dist/store/converters.js.map +1 -1
- package/dist/store/data-accessor.d.ts +9 -1
- package/dist/store/data-accessor.d.ts.map +1 -1
- package/dist/store/data-accessor.js +22 -5
- package/dist/store/data-accessor.js.map +1 -1
- package/dist/store/data-safety-central.d.ts.map +1 -1
- package/dist/store/data-safety-central.js +272 -232
- package/dist/store/data-safety-central.js.map +1 -1
- package/dist/store/db-helpers.d.ts +19 -3
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +42 -58
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +63 -4
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +166 -19
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/archive.d.ts +21 -7
- package/dist/store/exodus/archive.d.ts.map +1 -1
- package/dist/store/exodus/archive.js +60 -18
- package/dist/store/exodus/archive.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +27 -23
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts +16 -27
- package/dist/store/exodus/on-open.d.ts.map +1 -1
- package/dist/store/exodus/on-open.js +121 -169
- package/dist/store/exodus/on-open.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts +90 -0
- package/dist/store/exodus/recovery.d.ts.map +1 -0
- package/dist/store/exodus/recovery.js +434 -0
- package/dist/store/exodus/recovery.js.map +1 -0
- package/dist/store/exodus/types.d.ts +15 -0
- package/dist/store/exodus/types.d.ts.map +1 -1
- package/dist/store/memory-accessor.d.ts +52 -3
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +122 -7
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +24 -6
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.js +39 -20
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/ports/domain-binding.d.ts.map +1 -1
- package/dist/store/ports/domain-binding.js +27 -5
- package/dist/store/ports/domain-binding.js.map +1 -1
- package/dist/store/schema/background-jobs.d.ts +181 -2
- package/dist/store/schema/background-jobs.d.ts.map +1 -1
- package/dist/store/schema/background-jobs.js +27 -3
- package/dist/store/schema/background-jobs.js.map +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +2 -2
- package/dist/store/schema/cleo-project/lifecycle.d.ts +2 -2
- package/dist/store/schema/cleo-project/runtime.d.ts +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +1 -1
- package/dist/store/schema/lifecycle.d.ts +2 -2
- package/dist/store/schema/skills-schema.d.ts +2 -2
- package/dist/store/schema/tasks.d.ts +1 -1
- package/dist/store/session-store.d.ts +3 -1
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +252 -207
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +39 -4
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +689 -527
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts +3 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +309 -279
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +8 -8
- package/dist/store/writer-lease.d.ts +80 -8
- package/dist/store/writer-lease.d.ts.map +1 -1
- package/dist/store/writer-lease.js +503 -134
- package/dist/store/writer-lease.js.map +1 -1
- package/dist/system/audit.d.ts +2 -1
- package/dist/system/audit.d.ts.map +1 -1
- package/dist/system/audit.js +118 -71
- package/dist/system/audit.js.map +1 -1
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +5 -5
- package/dist/system/backup.js.map +1 -1
- package/dist/system/inject-generate.js +1 -1
- package/dist/task-work/index.d.ts +3 -0
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +34 -37
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +4 -0
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/ac-immutability.d.ts +7 -4
- package/dist/tasks/ac-immutability.d.ts.map +1 -1
- package/dist/tasks/ac-immutability.js +19 -17
- package/dist/tasks/ac-immutability.js.map +1 -1
- package/dist/tasks/ac-table.d.ts +5 -5
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +26 -12
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/acceptance-input.d.ts +52 -0
- package/dist/tasks/acceptance-input.d.ts.map +1 -0
- package/dist/tasks/acceptance-input.js +259 -0
- package/dist/tasks/acceptance-input.js.map +1 -0
- package/dist/tasks/add.d.ts +67 -29
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +255 -97
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts +16 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +152 -55
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/decompose.d.ts +103 -0
- package/dist/tasks/decompose.d.ts.map +1 -0
- package/dist/tasks/decompose.js +224 -0
- package/dist/tasks/decompose.js.map +1 -0
- package/dist/tasks/delete.d.ts +5 -8
- package/dist/tasks/delete.d.ts.map +1 -1
- package/dist/tasks/delete.js +4 -3
- package/dist/tasks/delete.js.map +1 -1
- package/dist/tasks/dependency-check.d.ts +59 -0
- package/dist/tasks/dependency-check.d.ts.map +1 -1
- package/dist/tasks/dependency-check.js +73 -0
- package/dist/tasks/dependency-check.js.map +1 -1
- package/dist/tasks/deps-ready.d.ts +13 -1
- package/dist/tasks/deps-ready.d.ts.map +1 -1
- package/dist/tasks/deps-ready.js +16 -5
- package/dist/tasks/deps-ready.js.map +1 -1
- package/dist/tasks/engine-converters.d.ts +7 -0
- package/dist/tasks/engine-converters.d.ts.map +1 -1
- package/dist/tasks/engine-converters.js +8 -1
- package/dist/tasks/engine-converters.js.map +1 -1
- package/dist/tasks/evidence.d.ts +54 -26
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +292 -40
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/find.d.ts +16 -12
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +107 -93
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +88 -14
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +596 -253
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/hierarchy-policy.d.ts +8 -0
- package/dist/tasks/hierarchy-policy.d.ts.map +1 -1
- package/dist/tasks/hierarchy-policy.js +9 -2
- package/dist/tasks/hierarchy-policy.js.map +1 -1
- package/dist/tasks/hierarchy.d.ts +67 -1
- package/dist/tasks/hierarchy.d.ts.map +1 -1
- package/dist/tasks/hierarchy.js +81 -1
- package/dist/tasks/hierarchy.js.map +1 -1
- package/dist/tasks/index.d.ts +3 -0
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +3 -0
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/infer-add-params.d.ts +1 -20
- package/dist/tasks/infer-add-params.d.ts.map +1 -1
- package/dist/tasks/infer-add-params.js +2 -203
- package/dist/tasks/infer-add-params.js.map +1 -1
- package/dist/tasks/list.d.ts +7 -2
- package/dist/tasks/list.d.ts.map +1 -1
- package/dist/tasks/list.js +24 -36
- package/dist/tasks/list.js.map +1 -1
- package/dist/tasks/ops.d.ts +6 -61
- package/dist/tasks/ops.d.ts.map +1 -1
- package/dist/tasks/ops.js +5 -53
- package/dist/tasks/ops.js.map +1 -1
- package/dist/tasks/plan.d.ts +15 -1
- package/dist/tasks/plan.d.ts.map +1 -1
- package/dist/tasks/plan.js +23 -25
- package/dist/tasks/plan.js.map +1 -1
- package/dist/tasks/population.d.ts +46 -0
- package/dist/tasks/population.d.ts.map +1 -0
- package/dist/tasks/population.js +88 -0
- package/dist/tasks/population.js.map +1 -0
- package/dist/tasks/reconcile-scope.d.ts +157 -0
- package/dist/tasks/reconcile-scope.d.ts.map +1 -0
- package/dist/tasks/reconcile-scope.js +242 -0
- package/dist/tasks/reconcile-scope.js.map +1 -0
- package/dist/tasks/req.d.ts +51 -12
- package/dist/tasks/req.d.ts.map +1 -1
- package/dist/tasks/req.js +125 -69
- package/dist/tasks/req.js.map +1 -1
- package/dist/tasks/session-scope.d.ts +6 -31
- package/dist/tasks/session-scope.d.ts.map +1 -1
- package/dist/tasks/session-scope.js +3 -17
- package/dist/tasks/session-scope.js.map +1 -1
- package/dist/tasks/severity-attestation.d.ts +39 -10
- package/dist/tasks/severity-attestation.d.ts.map +1 -1
- package/dist/tasks/severity-attestation.js +75 -36
- package/dist/tasks/severity-attestation.js.map +1 -1
- package/dist/tasks/task-next.d.ts +5 -0
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +10 -9
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/tasks/task-reparent.d.ts.map +1 -1
- package/dist/tasks/task-reparent.js +3 -13
- package/dist/tasks/task-reparent.js.map +1 -1
- package/dist/tasks/update.d.ts +20 -43
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +64 -49
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +17 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +149 -11
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/workgraph/reparent-preflight.d.ts +1 -1
- package/dist/workgraph/reparent-preflight.d.ts.map +1 -1
- package/dist/workgraph/reparent-preflight.js +6 -1
- package/dist/workgraph/reparent-preflight.js.map +1 -1
- package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
- package/dist/workgraph/scaffold-apply.js +81 -76
- package/dist/workgraph/scaffold-apply.js.map +1 -1
- package/dist/workgraph/structure-validation.d.ts +1 -1
- package/dist/workgraph/structure-validation.d.ts.map +1 -1
- package/dist/workgraph/structure-validation.js +6 -1
- package/dist/workgraph/structure-validation.js.map +1 -1
- package/migrations/drizzle-tasks/20260919180000_t12263-job-ownership-fences/migration.sql +34 -0
- package/migrations/drizzle-tasks/20260919184500_t12265-durable-pending-proposals/migration.sql +4 -0
- package/package.json +18 -13
- package/schemas/config.schema.json +2 -2
- package/templates/CLEO-INJECTION.md +103 -101
|
@@ -10,11 +10,16 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @epic T4454
|
|
12
12
|
*/
|
|
13
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
14
|
+
import { resolve } from 'node:path';
|
|
13
15
|
import { ARCHIVE_REASON_TOMBSTONE, } from '@cleocode/contracts';
|
|
14
|
-
import { and, eq, inArray, isNull, like, ne, notInArray, or, sql } from 'drizzle-orm';
|
|
16
|
+
import { and, eq, getTableColumns, inArray, isNull, like, ne, notInArray, or, sql, } from 'drizzle-orm';
|
|
17
|
+
import { captureProjectScope as captureTaskAccessorScope, getProjectRoot, worktreeScope, } from '../project-scope.js';
|
|
18
|
+
import { withBackgroundOpCommitBoundary } from './background-ops.js';
|
|
15
19
|
import { archivedTaskToRow, rowToSession, rowToTask, taskToRow } from './converters.js';
|
|
16
20
|
import { cleanupBrainRefsOnSessionDelete } from './cross-db-cleanup.js';
|
|
17
|
-
import { batchUpdateDependencies, loadDependenciesForTasks, loadRelationsForTasks, updateDependencies, upsertSession, upsertTask, } from './db-helpers.js';
|
|
21
|
+
import { batchUpdateDependencies, loadDependenciesForTasks, loadRelationsForTasks, parseLabels, updateDependencies, updateTaskLabels, upsertSession, upsertTask, } from './db-helpers.js';
|
|
22
|
+
import { tasksAuditLog } from './schema/cleo-project/audit.js';
|
|
18
23
|
import { resolveCurrentSession } from './session-store.js';
|
|
19
24
|
import { closeDb, getDb, getNativeTasksDb } from './sqlite.js';
|
|
20
25
|
import { TERMINAL_TASK_STATUSES } from './status-registry.js';
|
|
@@ -36,46 +41,6 @@ function generateAuditLogId() {
|
|
|
36
41
|
const rand = Math.random().toString(36).slice(2, 8);
|
|
37
42
|
return `log-${epoch}-${rand}`;
|
|
38
43
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Run `fn` inside an outermost `BEGIN IMMEDIATE` transaction, wrapped
|
|
41
|
-
* in {@link withWriteRetry} so SQLITE_BUSY contention is absorbed at the
|
|
42
|
-
* transaction boundary rather than mid-statement.
|
|
43
|
-
*
|
|
44
|
-
* - Acquires a RESERVED lock immediately so concurrent writers queue
|
|
45
|
-
* politely (or fall through to retry after `busy_timeout` expires).
|
|
46
|
-
* - Commits on success; rolls back on any thrown error before
|
|
47
|
-
* propagating it. If the thrown error is SQLITE_BUSY,
|
|
48
|
-
* {@link withWriteRetry} re-runs the WHOLE function (BEGIN included)
|
|
49
|
-
* after backoff — better-sqlite3 / node:sqlite forbids nesting
|
|
50
|
-
* `BEGIN IMMEDIATE` inside an open transaction, so retry MUST start
|
|
51
|
-
* from this outer boundary.
|
|
52
|
-
*
|
|
53
|
-
* DO NOT call this from inside an already-open transaction. For nested
|
|
54
|
-
* writes use `accessor.transaction()` which uses SAVEPOINTs (T9814).
|
|
55
|
-
*
|
|
56
|
-
* @bug gh-391 — SQLITE_BUSY contention on parallel writes.
|
|
57
|
-
* @task T9839
|
|
58
|
-
*/
|
|
59
|
-
async function runInImmediateTx(nativeDb, fn) {
|
|
60
|
-
return withWriteRetry(async () => {
|
|
61
|
-
nativeDb.prepare('BEGIN IMMEDIATE').run();
|
|
62
|
-
try {
|
|
63
|
-
const result = await fn();
|
|
64
|
-
nativeDb.prepare('COMMIT').run();
|
|
65
|
-
return result;
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
try {
|
|
69
|
-
nativeDb.prepare('ROLLBACK').run();
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
// Secondary rollback failures are non-fatal — propagate the original
|
|
73
|
-
// error which captures the actual cause.
|
|
74
|
-
}
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
44
|
// ---- Schema meta helpers ----
|
|
80
45
|
/** Read a JSON blob from the schema_meta table by key. */
|
|
81
46
|
async function getMetaValue(cwd, key) {
|
|
@@ -94,8 +59,13 @@ async function getMetaValue(cwd, key) {
|
|
|
94
59
|
return null;
|
|
95
60
|
}
|
|
96
61
|
}
|
|
97
|
-
/** Write
|
|
62
|
+
/** Write metadata through the same ownership queue as task mutations. */
|
|
98
63
|
export async function setMetaValue(cwd, key, value) {
|
|
64
|
+
const accessor = await createSqliteDataAccessor(cwd);
|
|
65
|
+
await accessor.setMetaValue(key, value);
|
|
66
|
+
}
|
|
67
|
+
/** Write metadata while the caller owns the transaction. */
|
|
68
|
+
async function writeMetaValue(cwd, key, value) {
|
|
99
69
|
const db = await getDb(cwd);
|
|
100
70
|
const json = JSON.stringify(value);
|
|
101
71
|
await db
|
|
@@ -107,17 +77,125 @@ export async function setMetaValue(cwd, key, value) {
|
|
|
107
77
|
})
|
|
108
78
|
.run();
|
|
109
79
|
}
|
|
80
|
+
// One queue per shared native handle, independent of accessor identity. These
|
|
81
|
+
// hold coordination state only; the ProjectStore remains the owner of handles.
|
|
82
|
+
const taskTransactionQueue = new WeakMap();
|
|
83
|
+
/** One lexical transaction lifetime; its queue serializes sibling savepoints. */
|
|
84
|
+
class TaskTransactionScope {
|
|
85
|
+
native;
|
|
86
|
+
parent;
|
|
87
|
+
active = true;
|
|
88
|
+
pending = Promise.resolve();
|
|
89
|
+
constructor(native, parent) {
|
|
90
|
+
this.native = native;
|
|
91
|
+
this.parent = parent;
|
|
92
|
+
}
|
|
93
|
+
assertActive() {
|
|
94
|
+
if (!this.active)
|
|
95
|
+
throw new Error('Transaction scope has ended');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const taskTransactionContext = new AsyncLocalStorage();
|
|
99
|
+
function activeTransactionScope(native) {
|
|
100
|
+
let scope = taskTransactionContext.getStore();
|
|
101
|
+
while (scope) {
|
|
102
|
+
if (!scope.active)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (scope.native === native)
|
|
105
|
+
return scope;
|
|
106
|
+
scope = scope.parent;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/** Serialize committed effects with foreground writers without opening a SQL transaction. */
|
|
111
|
+
async function scheduleTaskBackground(native, work) {
|
|
112
|
+
const previous = taskTransactionQueue.get(native) ?? Promise.resolve();
|
|
113
|
+
const released = Promise.withResolvers();
|
|
114
|
+
const tail = previous.then(() => released.promise);
|
|
115
|
+
taskTransactionQueue.set(native, tail);
|
|
116
|
+
await previous;
|
|
117
|
+
const scope = new TaskTransactionScope(native);
|
|
118
|
+
try {
|
|
119
|
+
return await taskTransactionContext.run(scope, work);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
await scope.pending;
|
|
123
|
+
scope.active = false;
|
|
124
|
+
released.resolve();
|
|
125
|
+
if (taskTransactionQueue.get(native) === tail)
|
|
126
|
+
taskTransactionQueue.delete(native);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compatible task-accessor name for the shared project ownership capture policy.
|
|
131
|
+
* @remarks This is the same function as `captureProjectScope`; identity validation
|
|
132
|
+
* and routing policy live in the dependency-leaf project scope module.
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const scope = captureTaskAccessorScope(getProjectRoot(), worktreeScope.getStore());
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export { captureProjectScope as captureTaskAccessorScope } from '../project-scope.js';
|
|
139
|
+
/**
|
|
140
|
+
* Bind all asynchronous accessor methods, including transaction ports, to one project.
|
|
141
|
+
* @typeParam T - Concrete accessor contract retained by the scoped wrapper.
|
|
142
|
+
* @param accessor - Canonical, safety-wrapped, or transaction accessor to bind.
|
|
143
|
+
* @param scope - Immutable project scope captured at construction.
|
|
144
|
+
* @returns An accessor preserving method arguments, results and receiver semantics.
|
|
145
|
+
* @remarks The wrapper changes only worktree path scope. The existing transaction
|
|
146
|
+
* AsyncLocalStorage, savepoint queues and postcommit result semantics remain authoritative.
|
|
147
|
+
* A newly inherited execution cannot bypass a captured operation's cancellation or ownership.
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* const bound = bindTaskAccessorScope(accessor, scope);
|
|
151
|
+
* await bound.upsertSingleTask(task);
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function bindTaskAccessorScope(accessor, scope) {
|
|
155
|
+
return new Proxy(accessor, {
|
|
156
|
+
get(target, property) {
|
|
157
|
+
const value = target[property];
|
|
158
|
+
if (typeof value !== 'function')
|
|
159
|
+
return value;
|
|
160
|
+
return new Proxy(value, {
|
|
161
|
+
async apply(method, _receiver, args) {
|
|
162
|
+
const activeExecution = worktreeScope.getStore()?.execution;
|
|
163
|
+
if (scope.execution && activeExecution && scope.execution !== activeExecution)
|
|
164
|
+
throw new Error('Task accessor cannot replace another captured execution authority.');
|
|
165
|
+
const invocationScope = captureTaskAccessorScope(scope.worktreeRoot, {
|
|
166
|
+
...scope,
|
|
167
|
+
execution: scope.execution ?? activeExecution,
|
|
168
|
+
});
|
|
169
|
+
return worktreeScope.run(invocationScope, () => Reflect.apply(method, target, args));
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
110
175
|
// ---- Accessor factory ----
|
|
111
176
|
/**
|
|
112
177
|
* Create a SQLite-backed DataAccessor.
|
|
113
178
|
*
|
|
114
|
-
* Opens (or creates) the SQLite database at `.cleo/
|
|
179
|
+
* Opens (or creates) the consolidated SQLite database at `.cleo/cleo.db` and returns
|
|
115
180
|
* a DataAccessor that materializes/dematerializes whole-file structures
|
|
116
181
|
* from the relational tables.
|
|
117
182
|
*
|
|
118
|
-
* @param cwd -
|
|
183
|
+
* @param cwd - Explicit project root, or canonical root captured before initialization.
|
|
184
|
+
* @returns An accessor whose methods and transaction ports retain captured ownership.
|
|
185
|
+
* @remarks Later ambient scope changes cannot redirect this accessor's I/O. Active
|
|
186
|
+
* execution ownership is checked against persisted identity before method entry.
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* const accessor = await createSqliteDataAccessor('/projects/example');
|
|
190
|
+
* const task = await accessor.loadSingleTask('T001');
|
|
191
|
+
* ```
|
|
119
192
|
*/
|
|
120
193
|
export async function createSqliteDataAccessor(cwd) {
|
|
194
|
+
const scope = captureTaskAccessorScope(resolve(cwd ?? getProjectRoot()), worktreeScope.getStore());
|
|
195
|
+
return worktreeScope.run(scope, async () => bindTaskAccessorScope(await createOwnedSqliteDataAccessor(scope.worktreeRoot, scope), scope));
|
|
196
|
+
}
|
|
197
|
+
/** Construct the raw implementation inside its already captured project scope. */
|
|
198
|
+
async function createOwnedSqliteDataAccessor(cwd, accessorScope) {
|
|
121
199
|
// Eagerly initialize the database to ensure tables exist
|
|
122
200
|
await getDb(cwd);
|
|
123
201
|
/** Load all task IDs for cross-task dependency validation. */
|
|
@@ -126,12 +204,13 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
126
204
|
const rows = await db.select({ id: schema.tasks.id }).from(schema.tasks).all();
|
|
127
205
|
return new Set(rows.map((r) => r.id));
|
|
128
206
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
207
|
+
async function requireNativeDb() {
|
|
208
|
+
await getDb(cwd);
|
|
209
|
+
const nativeDb = getNativeTasksDb(cwd);
|
|
210
|
+
if (!nativeDb)
|
|
211
|
+
throw new Error('Native database not initialized');
|
|
212
|
+
return nativeDb;
|
|
213
|
+
}
|
|
135
214
|
const accessor = {
|
|
136
215
|
engine: 'sqlite',
|
|
137
216
|
// ---- loadArchive ----
|
|
@@ -180,22 +259,16 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
180
259
|
},
|
|
181
260
|
// ---- saveArchive ----
|
|
182
261
|
async saveArchive(data) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// Retry on SQLITE_BUSY contention via runInImmediateTx (gh#391).
|
|
194
|
-
const nativeDb = getNativeTasksDb();
|
|
195
|
-
if (!nativeDb) {
|
|
196
|
-
throw new Error('Native database not initialized');
|
|
197
|
-
}
|
|
198
|
-
await runInImmediateTx(nativeDb, async () => {
|
|
262
|
+
return accessor.transaction(async () => {
|
|
263
|
+
const db = await getDb(cwd);
|
|
264
|
+
// Pre-compute archive IDs + active task IDs for dependency validation
|
|
265
|
+
const archiveIds = new Set(data.archivedTasks.map((t) => t.id));
|
|
266
|
+
const activeRows = await db
|
|
267
|
+
.select({ id: schema.tasks.id })
|
|
268
|
+
.from(schema.tasks)
|
|
269
|
+
.where(ne(schema.tasks.status, 'archived'))
|
|
270
|
+
.all();
|
|
271
|
+
const validDepIds = new Set([...archiveIds, ...activeRows.map((r) => r.id)]);
|
|
199
272
|
// Collect dependency data for batch update
|
|
200
273
|
const depBatch = [];
|
|
201
274
|
for (const task of data.archivedTasks) {
|
|
@@ -227,67 +300,74 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
227
300
|
},
|
|
228
301
|
// ---- saveSessions ----
|
|
229
302
|
async saveSessions(sessions) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
303
|
+
return accessor.transaction(async () => {
|
|
304
|
+
const db = await getDb(cwd);
|
|
305
|
+
// Get existing session IDs
|
|
306
|
+
const existingRows = await db
|
|
307
|
+
.select({ id: schema.sessions.id })
|
|
308
|
+
.from(schema.sessions)
|
|
309
|
+
.all();
|
|
310
|
+
const existingIds = new Set(existingRows.map((r) => r.id));
|
|
311
|
+
const incomingIds = new Set(sessions.map((s) => s.id));
|
|
312
|
+
// Delete sessions that are no longer in the data
|
|
313
|
+
for (const eid of existingIds) {
|
|
314
|
+
if (!incomingIds.has(eid)) {
|
|
315
|
+
await db.delete(schema.sessions).where(eq(schema.sessions.id, eid)).run();
|
|
316
|
+
}
|
|
239
317
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
318
|
+
// Upsert all sessions
|
|
319
|
+
for (const session of sessions) {
|
|
320
|
+
await upsertSession(db, session);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
245
323
|
},
|
|
246
324
|
// ---- appendLog ----
|
|
247
325
|
async appendLog(entry) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
326
|
+
return accessor.transaction(async () => {
|
|
327
|
+
const db = await getDb(cwd);
|
|
328
|
+
// gh#391: every task mutation appends one audit row; retry on contention.
|
|
329
|
+
await withWriteRetry(() => db
|
|
330
|
+
.insert(tasksAuditLog)
|
|
331
|
+
.values({
|
|
332
|
+
id: entry.id ?? generateAuditLogId(),
|
|
333
|
+
timestamp: entry.timestamp ?? new Date().toISOString(),
|
|
334
|
+
action: entry.action ?? entry.operation ?? 'unknown',
|
|
335
|
+
taskId: entry.taskId ?? 'unknown',
|
|
336
|
+
actor: entry.actor ?? 'system',
|
|
337
|
+
detailsJson: entry.details ? JSON.stringify(entry.details) : '{}',
|
|
338
|
+
beforeJson: entry.before ? JSON.stringify(entry.before) : null,
|
|
339
|
+
afterJson: entry.after ? JSON.stringify(entry.after) : null,
|
|
340
|
+
})
|
|
341
|
+
.run());
|
|
342
|
+
});
|
|
263
343
|
},
|
|
264
344
|
async queryAuditLog(query) {
|
|
265
345
|
const db = await getDb(cwd);
|
|
266
346
|
const conditions = [];
|
|
267
347
|
if (query.taskIds && query.taskIds.length > 0) {
|
|
268
|
-
conditions.push(inArray(
|
|
348
|
+
conditions.push(inArray(tasksAuditLog.taskId, [...query.taskIds]));
|
|
269
349
|
}
|
|
270
350
|
if (query.actions && query.actions.length > 0) {
|
|
271
|
-
conditions.push(inArray(
|
|
351
|
+
conditions.push(inArray(tasksAuditLog.action, [...query.actions]));
|
|
272
352
|
}
|
|
273
353
|
if (query.since) {
|
|
274
|
-
conditions.push(sql `${
|
|
354
|
+
conditions.push(sql `${tasksAuditLog.timestamp} >= ${query.since}`);
|
|
275
355
|
}
|
|
276
356
|
const limit = Math.max(1, Math.floor(query.limit ?? 100));
|
|
277
357
|
const rows = await db
|
|
278
358
|
.select({
|
|
279
|
-
id:
|
|
280
|
-
timestamp:
|
|
281
|
-
action:
|
|
282
|
-
taskId:
|
|
283
|
-
actor:
|
|
284
|
-
detailsJson:
|
|
285
|
-
beforeJson:
|
|
286
|
-
afterJson:
|
|
359
|
+
id: tasksAuditLog.id,
|
|
360
|
+
timestamp: tasksAuditLog.timestamp,
|
|
361
|
+
action: tasksAuditLog.action,
|
|
362
|
+
taskId: tasksAuditLog.taskId,
|
|
363
|
+
actor: tasksAuditLog.actor,
|
|
364
|
+
detailsJson: tasksAuditLog.detailsJson,
|
|
365
|
+
beforeJson: tasksAuditLog.beforeJson,
|
|
366
|
+
afterJson: tasksAuditLog.afterJson,
|
|
287
367
|
})
|
|
288
|
-
.from(
|
|
368
|
+
.from(tasksAuditLog)
|
|
289
369
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
290
|
-
.orderBy(sql `${
|
|
370
|
+
.orderBy(sql `${tasksAuditLog.timestamp} DESC`)
|
|
291
371
|
.limit(limit)
|
|
292
372
|
.all();
|
|
293
373
|
return rows.map((row) => ({
|
|
@@ -303,51 +383,12 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
303
383
|
},
|
|
304
384
|
// ---- Fine-grained task operations (T5034) ----
|
|
305
385
|
async upsertSingleTask(task) {
|
|
306
|
-
|
|
307
|
-
const row = taskToRow(task);
|
|
308
|
-
// gh#391: wrap multi-statement write (task row + dependency rows) in
|
|
309
|
-
// a retry loop. SQLite implicitly auto-commits each statement, but
|
|
310
|
-
// concurrent writers can still observe SQLITE_BUSY mid-sequence.
|
|
311
|
-
await withWriteRetry(async () => {
|
|
312
|
-
await upsertTask(db, row);
|
|
313
|
-
await updateDependencies(db, task.id, task.depends ?? []);
|
|
314
|
-
});
|
|
386
|
+
await accessor.transaction((tx) => tx.upsertSingleTask(task));
|
|
315
387
|
},
|
|
316
388
|
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
'related',
|
|
321
|
-
'blocks',
|
|
322
|
-
'duplicates',
|
|
323
|
-
'absorbs',
|
|
324
|
-
'fixes',
|
|
325
|
-
'extends',
|
|
326
|
-
'supersedes',
|
|
327
|
-
'groups', // ADR-073: Saga member linkage
|
|
328
|
-
];
|
|
329
|
-
if (!validTypes.includes(relationType)) {
|
|
330
|
-
throw new Error(`Invalid relation type: ${relationType}. Valid types: ${validTypes.join(', ')}`);
|
|
331
|
-
}
|
|
332
|
-
// gh#391: retry on SQLITE_BUSY contention.
|
|
333
|
-
await withWriteRetry(() => db
|
|
334
|
-
.insert(schema.taskRelations)
|
|
335
|
-
.values({
|
|
336
|
-
taskId,
|
|
337
|
-
relatedTo,
|
|
338
|
-
relationType: relationType,
|
|
339
|
-
reason: reason ?? null,
|
|
340
|
-
})
|
|
341
|
-
.onConflictDoNothing()
|
|
342
|
-
.run());
|
|
343
|
-
},
|
|
344
|
-
async removeRelation(taskId, relatedTo, relationType) {
|
|
345
|
-
const db = await getDb(cwd);
|
|
346
|
-
const conditions = [
|
|
347
|
-
eq(schema.taskRelations.taskId, taskId),
|
|
348
|
-
eq(schema.taskRelations.relatedTo, relatedTo),
|
|
349
|
-
];
|
|
350
|
-
if (relationType !== undefined) {
|
|
389
|
+
return accessor.transaction(async () => {
|
|
390
|
+
const db = await getDb(cwd);
|
|
391
|
+
// Validate relation type - throw on invalid (T5168)
|
|
351
392
|
const validTypes = [
|
|
352
393
|
'related',
|
|
353
394
|
'blocks',
|
|
@@ -361,13 +402,48 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
361
402
|
if (!validTypes.includes(relationType)) {
|
|
362
403
|
throw new Error(`Invalid relation type: ${relationType}. Valid types: ${validTypes.join(', ')}`);
|
|
363
404
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
405
|
+
// gh#391: retry on SQLITE_BUSY contention.
|
|
406
|
+
await withWriteRetry(() => db
|
|
407
|
+
.insert(schema.taskRelations)
|
|
408
|
+
.values({
|
|
409
|
+
taskId,
|
|
410
|
+
relatedTo,
|
|
411
|
+
relationType: relationType,
|
|
412
|
+
reason: reason ?? null,
|
|
413
|
+
})
|
|
414
|
+
.onConflictDoNothing()
|
|
415
|
+
.run());
|
|
416
|
+
});
|
|
417
|
+
},
|
|
418
|
+
async removeRelation(taskId, relatedTo, relationType) {
|
|
419
|
+
return accessor.transaction(async () => {
|
|
420
|
+
const db = await getDb(cwd);
|
|
421
|
+
const conditions = [
|
|
422
|
+
eq(schema.taskRelations.taskId, taskId),
|
|
423
|
+
eq(schema.taskRelations.relatedTo, relatedTo),
|
|
424
|
+
];
|
|
425
|
+
if (relationType !== undefined) {
|
|
426
|
+
const validTypes = [
|
|
427
|
+
'related',
|
|
428
|
+
'blocks',
|
|
429
|
+
'duplicates',
|
|
430
|
+
'absorbs',
|
|
431
|
+
'fixes',
|
|
432
|
+
'extends',
|
|
433
|
+
'supersedes',
|
|
434
|
+
'groups', // ADR-073: Saga member linkage
|
|
435
|
+
];
|
|
436
|
+
if (!validTypes.includes(relationType)) {
|
|
437
|
+
throw new Error(`Invalid relation type: ${relationType}. Valid types: ${validTypes.join(', ')}`);
|
|
438
|
+
}
|
|
439
|
+
conditions.push(eq(schema.taskRelations.relationType, relationType));
|
|
440
|
+
}
|
|
441
|
+
// gh#391: retry on SQLITE_BUSY contention.
|
|
442
|
+
await withWriteRetry(() => db
|
|
443
|
+
.delete(schema.taskRelations)
|
|
444
|
+
.where(and(...conditions))
|
|
445
|
+
.run());
|
|
446
|
+
});
|
|
371
447
|
},
|
|
372
448
|
// ---- AC rows (T10508) ----
|
|
373
449
|
async getAcRows(taskId) {
|
|
@@ -429,46 +505,50 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
429
505
|
}));
|
|
430
506
|
},
|
|
431
507
|
async archiveSingleTask(taskId, fields) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
508
|
+
return accessor.transaction(async () => {
|
|
509
|
+
const db = await getDb(cwd);
|
|
510
|
+
// Verify the task exists before archiving
|
|
511
|
+
const rows = await db
|
|
512
|
+
.select({ id: schema.tasks.id })
|
|
513
|
+
.from(schema.tasks)
|
|
514
|
+
.where(eq(schema.tasks.id, taskId))
|
|
515
|
+
.all();
|
|
516
|
+
if (rows.length === 0)
|
|
517
|
+
return;
|
|
518
|
+
// gh#391: retry on SQLITE_BUSY contention.
|
|
519
|
+
await withWriteRetry(() => db
|
|
520
|
+
.update(schema.tasks)
|
|
521
|
+
.set({
|
|
522
|
+
status: 'archived',
|
|
523
|
+
archivedAt: fields.archivedAt ?? new Date().toISOString(),
|
|
524
|
+
archiveReason: fields.archiveReason ?? ARCHIVE_REASON_TOMBSTONE,
|
|
525
|
+
cycleTimeDays: fields.cycleTimeDays ?? null,
|
|
526
|
+
updatedAt: new Date().toISOString(),
|
|
527
|
+
})
|
|
528
|
+
.where(eq(schema.tasks.id, taskId))
|
|
529
|
+
.run());
|
|
530
|
+
});
|
|
453
531
|
},
|
|
454
532
|
async removeSingleTask(taskId) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
533
|
+
return accessor.transaction(async () => {
|
|
534
|
+
const db = await getDb(cwd);
|
|
535
|
+
// gh#391: wrap the three-statement delete sequence in one retry
|
|
536
|
+
// boundary. SQLITE_BUSY mid-sequence would leak dangling task_dependencies
|
|
537
|
+
// rows; retrying the whole sequence keeps the cleanup atomic at the
|
|
538
|
+
// application level (foreign-key cascade catches edge cases regardless).
|
|
539
|
+
await withWriteRetry(async () => {
|
|
540
|
+
// Delete dependencies first (both directions)
|
|
541
|
+
await db
|
|
542
|
+
.delete(schema.taskDependencies)
|
|
543
|
+
.where(eq(schema.taskDependencies.taskId, taskId))
|
|
544
|
+
.run();
|
|
545
|
+
await db
|
|
546
|
+
.delete(schema.taskDependencies)
|
|
547
|
+
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
548
|
+
.run();
|
|
549
|
+
// Delete the task itself
|
|
550
|
+
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
551
|
+
});
|
|
472
552
|
});
|
|
473
553
|
},
|
|
474
554
|
async loadSingleTask(taskId) {
|
|
@@ -509,14 +589,18 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
509
589
|
return resolveCurrentSession(cwd);
|
|
510
590
|
},
|
|
511
591
|
async upsertSingleSession(session) {
|
|
512
|
-
|
|
513
|
-
|
|
592
|
+
return accessor.transaction(async () => {
|
|
593
|
+
const db = await getDb(cwd);
|
|
594
|
+
await upsertSession(db, session);
|
|
595
|
+
});
|
|
514
596
|
},
|
|
515
597
|
async removeSingleSession(sessionId) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
598
|
+
return accessor.transaction(async () => {
|
|
599
|
+
const db = await getDb(cwd);
|
|
600
|
+
await db.delete(schema.sessions).where(eq(schema.sessions.id, sessionId)).run();
|
|
601
|
+
// Best-effort cross-db cleanup: nullify brain.db references to this session
|
|
602
|
+
void cleanupBrainRefsOnSessionDelete(sessionId, cwd);
|
|
603
|
+
});
|
|
520
604
|
},
|
|
521
605
|
// ---- Targeted query methods (Phase 2 modernization) ----
|
|
522
606
|
async queryTasks(filters) {
|
|
@@ -685,16 +769,28 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
685
769
|
return result?.count ?? 0;
|
|
686
770
|
},
|
|
687
771
|
async getAncestorChain(taskId) {
|
|
688
|
-
const nativeDb =
|
|
689
|
-
if (!nativeDb)
|
|
690
|
-
return [];
|
|
772
|
+
const nativeDb = await requireNativeDb();
|
|
691
773
|
const idRows = nativeDb
|
|
692
|
-
.prepare(
|
|
693
|
-
|
|
774
|
+
.prepare(
|
|
775
|
+
// T12307: the `path` column is a cycle guard, not decoration. `depth`
|
|
776
|
+
// increments on every iteration, so with UNION ALL — and even with a
|
|
777
|
+
// plain UNION — (id, depth) is distinct on every revisit and a
|
|
778
|
+
// self-parented or cyclic row recurses forever. SQLite materialises
|
|
779
|
+
// that in NATIVE memory, which `--max-old-space-size` does not bound,
|
|
780
|
+
// so the symptom is a silent VM death rather than a heap error: one
|
|
781
|
+
// seeded `parentId === id` fixture row killed five CI runs and OOM-froze
|
|
782
|
+
// a workstation three times before the mechanism was found. A depth cap
|
|
783
|
+
// would also terminate, but it silently truncates a legitimately deep
|
|
784
|
+
// chain — unacceptable in the subsystem whose whole job is depth.
|
|
785
|
+
`WITH RECURSIVE ancestor_ids(id, depth, path) AS (
|
|
786
|
+
SELECT parent_id, 0, '/' || id || '/' || parent_id || '/'
|
|
787
|
+
FROM tasks_tasks WHERE id = ? AND parent_id IS NOT NULL
|
|
694
788
|
UNION ALL
|
|
695
|
-
SELECT t.parent_id, a.depth + 1
|
|
696
|
-
|
|
697
|
-
|
|
789
|
+
SELECT t.parent_id, a.depth + 1, a.path || t.parent_id || '/'
|
|
790
|
+
FROM tasks_tasks t
|
|
791
|
+
JOIN ancestor_ids a ON t.id = a.id
|
|
792
|
+
WHERE t.parent_id IS NOT NULL
|
|
793
|
+
AND a.path NOT LIKE '%/' || t.parent_id || '/%'
|
|
698
794
|
)
|
|
699
795
|
SELECT id FROM ancestor_ids ORDER BY depth DESC`)
|
|
700
796
|
.all(taskId);
|
|
@@ -719,14 +815,17 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
719
815
|
return tasks;
|
|
720
816
|
},
|
|
721
817
|
async getSubtree(rootId) {
|
|
722
|
-
const nativeDb =
|
|
723
|
-
if (!nativeDb)
|
|
724
|
-
return [];
|
|
818
|
+
const nativeDb = await requireNativeDb();
|
|
725
819
|
// Get IDs from the CTE, then load via Drizzle for proper conversion
|
|
726
820
|
const idRows = nativeDb
|
|
727
|
-
.prepare(
|
|
821
|
+
.prepare(
|
|
822
|
+
// T12307: UNION, not UNION ALL. Every column here repeats verbatim on
|
|
823
|
+
// a revisit, so the dedupe terminates a self-parented or cyclic row;
|
|
824
|
+
// UNION ALL recurses forever in native memory. A parent edge is
|
|
825
|
+
// single-valued, so no legitimate row is ever deduped away.
|
|
826
|
+
`WITH RECURSIVE subtree AS (
|
|
728
827
|
SELECT id FROM tasks_tasks WHERE id = ?
|
|
729
|
-
UNION
|
|
828
|
+
UNION
|
|
730
829
|
SELECT t.id FROM tasks_tasks t
|
|
731
830
|
JOIN subtree s ON t.parent_id = s.id
|
|
732
831
|
)
|
|
@@ -770,9 +869,7 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
770
869
|
return tasks;
|
|
771
870
|
},
|
|
772
871
|
async getDependencyChain(taskId) {
|
|
773
|
-
const nativeDb =
|
|
774
|
-
if (!nativeDb)
|
|
775
|
-
return [];
|
|
872
|
+
const nativeDb = await requireNativeDb();
|
|
776
873
|
const rows = nativeDb
|
|
777
874
|
.prepare(`WITH RECURSIVE dep_chain(id) AS (
|
|
778
875
|
SELECT depends_on FROM tasks_task_dependencies WHERE task_id = ?
|
|
@@ -813,7 +910,7 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
813
910
|
},
|
|
814
911
|
// ---- Position helpers (T024/T025) ----
|
|
815
912
|
async getNextPosition(parentId) {
|
|
816
|
-
const nativeDb =
|
|
913
|
+
const nativeDb = await requireNativeDb();
|
|
817
914
|
if (!nativeDb) {
|
|
818
915
|
throw new Error('Native database not initialized');
|
|
819
916
|
}
|
|
@@ -827,66 +924,46 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
827
924
|
return row?.next_pos ?? 1;
|
|
828
925
|
},
|
|
829
926
|
async shiftPositions(parentId, fromPosition, delta) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
927
|
+
return accessor.transaction(async () => {
|
|
928
|
+
const nativeDb = await requireNativeDb();
|
|
929
|
+
if (!nativeDb) {
|
|
930
|
+
throw new Error('Native database not initialized');
|
|
931
|
+
}
|
|
932
|
+
if (parentId === null) {
|
|
933
|
+
nativeDb
|
|
934
|
+
.prepare(`UPDATE tasks_tasks SET position = position + ?, position_version = position_version + 1, updated_at = ? WHERE parent_id IS NULL AND position >= ? AND status != 'archived'`)
|
|
935
|
+
.run(delta, new Date().toISOString(), fromPosition);
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
nativeDb
|
|
939
|
+
.prepare(`UPDATE tasks_tasks SET position = position + ?, position_version = position_version + 1, updated_at = ? WHERE parent_id = ? AND position >= ? AND status != 'archived'`)
|
|
940
|
+
.run(delta, new Date().toISOString(), parentId, fromPosition);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
844
943
|
},
|
|
845
944
|
// ---- Targeted write methods ----
|
|
846
945
|
async updateTaskFields(taskId, fields) {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
['priority', 'priority'],
|
|
857
|
-
['type', 'type'],
|
|
858
|
-
['parentId', 'parentId'],
|
|
859
|
-
['phase', 'phase'],
|
|
860
|
-
['size', 'size'],
|
|
861
|
-
['position', 'position'],
|
|
862
|
-
['positionVersion', 'positionVersion'],
|
|
863
|
-
['labelsJson', 'labelsJson'],
|
|
864
|
-
['notesJson', 'notesJson'],
|
|
865
|
-
['acceptanceJson', 'acceptanceJson'],
|
|
866
|
-
['filesJson', 'filesJson'],
|
|
867
|
-
['origin', 'origin'],
|
|
868
|
-
['blockedBy', 'blockedBy'],
|
|
869
|
-
['epicLifecycle', 'epicLifecycle'],
|
|
870
|
-
['noAutoComplete', 'noAutoComplete'],
|
|
871
|
-
['completedAt', 'completedAt'],
|
|
872
|
-
['cancelledAt', 'cancelledAt'],
|
|
873
|
-
['cancellationReason', 'cancellationReason'],
|
|
874
|
-
['verificationJson', 'verificationJson'],
|
|
875
|
-
['createdBy', 'createdBy'],
|
|
876
|
-
['modifiedBy', 'modifiedBy'],
|
|
877
|
-
['sessionId', 'sessionId'],
|
|
878
|
-
['assignee', 'assignee'],
|
|
879
|
-
['pipelineStage', 'pipelineStage'],
|
|
880
|
-
];
|
|
881
|
-
for (const [key, col] of fieldMap) {
|
|
882
|
-
if (fields[key] !== undefined) {
|
|
883
|
-
updateRow[col] = fields[key];
|
|
946
|
+
return accessor.transaction(async () => {
|
|
947
|
+
const db = await getDb(cwd);
|
|
948
|
+
// TaskFieldUpdates uses the schema's field names. Reject unsupported runtime
|
|
949
|
+
// input before Drizzle can silently omit it; no second field map can drift.
|
|
950
|
+
const columns = getTableColumns(schema.tasks);
|
|
951
|
+
for (const key of Object.keys(fields)) {
|
|
952
|
+
if (!Object.hasOwn(columns, key) || key === 'id' || key === 'createdAt') {
|
|
953
|
+
throw new Error(`Unsupported task update field: ${key}`);
|
|
954
|
+
}
|
|
884
955
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
956
|
+
const updateRow = { ...fields, updatedAt: fields.updatedAt ?? new Date().toISOString() };
|
|
957
|
+
// gh#391: this is the chokepoint for `cleo update <id> --add-labels`.
|
|
958
|
+
// Parallel invocations from a single shell used to lose ~50% of writes
|
|
959
|
+
// to SQLITE_BUSY; withWriteRetry recovers them.
|
|
960
|
+
const result = await withWriteRetry(() => db.update(schema.tasks).set(updateRow).where(eq(schema.tasks.id, taskId)).run());
|
|
961
|
+
if (Number(result.changes) !== 1)
|
|
962
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
963
|
+
if (fields.labelsJson !== undefined) {
|
|
964
|
+
await updateTaskLabels(db, taskId, parseLabels(fields.labelsJson));
|
|
965
|
+
}
|
|
966
|
+
});
|
|
890
967
|
},
|
|
891
968
|
async transaction(fn) {
|
|
892
969
|
// Await getDb first so _nativeDb is guaranteed to be the correct instance
|
|
@@ -898,225 +975,306 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
898
975
|
if (!nativeDb) {
|
|
899
976
|
throw new Error('Native database not initialized');
|
|
900
977
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
nativeDb.prepare(`SAVEPOINT ${spName}`).run();
|
|
913
|
-
}
|
|
914
|
-
_txDepth++;
|
|
978
|
+
const context = activeTransactionScope(nativeDb);
|
|
979
|
+
const previous = context?.pending ?? taskTransactionQueue.get(nativeDb) ?? Promise.resolve();
|
|
980
|
+
const released = Promise.withResolvers();
|
|
981
|
+
const tail = previous.then(() => released.promise);
|
|
982
|
+
if (context)
|
|
983
|
+
context.pending = tail;
|
|
984
|
+
else
|
|
985
|
+
taskTransactionQueue.set(nativeDb, tail);
|
|
986
|
+
await previous;
|
|
915
987
|
try {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
988
|
+
if (context && !context.active)
|
|
989
|
+
return accessor.transaction(fn);
|
|
990
|
+
const execute = async () => {
|
|
991
|
+
if (!context && nativeDb.isTransaction) {
|
|
992
|
+
throw new Error('Cannot join a transaction without an active task-accessor owner');
|
|
993
|
+
}
|
|
994
|
+
const inherited = taskTransactionContext.getStore();
|
|
995
|
+
const scope = new TaskTransactionScope(nativeDb, inherited?.active ? inherited : undefined);
|
|
996
|
+
return taskTransactionContext.run(scope, async () => {
|
|
997
|
+
const isOuter = !nativeDb.isTransaction;
|
|
998
|
+
const spName = isOuter ? null : `_cleo_tx_${(_txSavepointCounter++).toString(36)}`;
|
|
999
|
+
if (isOuter) {
|
|
1000
|
+
nativeDb.prepare('BEGIN IMMEDIATE').run();
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
nativeDb.prepare(`SAVEPOINT ${spName}`).run();
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const tx = {
|
|
1007
|
+
async upsertSingleTask(task) {
|
|
1008
|
+
scope.assertActive();
|
|
1009
|
+
return accessor.transaction(async () => {
|
|
1010
|
+
scope.assertActive();
|
|
1011
|
+
const row = taskToRow(task);
|
|
1012
|
+
await upsertTask(db, row);
|
|
1013
|
+
await updateDependencies(db, task.id, task.depends ?? []);
|
|
1014
|
+
});
|
|
1015
|
+
},
|
|
1016
|
+
async archiveSingleTask(taskId, fields) {
|
|
1017
|
+
scope.assertActive();
|
|
1018
|
+
return accessor.transaction(async () => {
|
|
1019
|
+
scope.assertActive();
|
|
1020
|
+
await db
|
|
1021
|
+
.update(schema.tasks)
|
|
1022
|
+
.set({
|
|
1023
|
+
status: 'archived',
|
|
1024
|
+
archivedAt: fields.archivedAt ?? new Date().toISOString(),
|
|
1025
|
+
archiveReason: fields.archiveReason ?? ARCHIVE_REASON_TOMBSTONE,
|
|
1026
|
+
cycleTimeDays: fields.cycleTimeDays ?? null,
|
|
1027
|
+
updatedAt: new Date().toISOString(),
|
|
1028
|
+
})
|
|
1029
|
+
.where(eq(schema.tasks.id, taskId))
|
|
1030
|
+
.run();
|
|
1031
|
+
});
|
|
1032
|
+
},
|
|
1033
|
+
async removeSingleTask(taskId) {
|
|
1034
|
+
scope.assertActive();
|
|
1035
|
+
return accessor.transaction(async () => {
|
|
1036
|
+
scope.assertActive();
|
|
1037
|
+
await db
|
|
1038
|
+
.delete(schema.taskDependencies)
|
|
1039
|
+
.where(eq(schema.taskDependencies.taskId, taskId))
|
|
1040
|
+
.run();
|
|
1041
|
+
await db
|
|
1042
|
+
.delete(schema.taskDependencies)
|
|
1043
|
+
.where(eq(schema.taskDependencies.dependsOn, taskId))
|
|
1044
|
+
.run();
|
|
1045
|
+
await db.delete(schema.tasks).where(eq(schema.tasks.id, taskId)).run();
|
|
1046
|
+
});
|
|
1047
|
+
},
|
|
1048
|
+
async setMetaValue(key, value) {
|
|
1049
|
+
scope.assertActive();
|
|
1050
|
+
return accessor.transaction(async () => {
|
|
1051
|
+
scope.assertActive();
|
|
1052
|
+
await writeMetaValue(cwd, key, value);
|
|
1053
|
+
});
|
|
1054
|
+
},
|
|
1055
|
+
async updateTaskFields(taskId, flds) {
|
|
1056
|
+
scope.assertActive();
|
|
1057
|
+
return accessor.transaction(async () => {
|
|
1058
|
+
scope.assertActive();
|
|
1059
|
+
// Delegate to the outer accessor's implementation
|
|
1060
|
+
await accessor.updateTaskFields(taskId, flds);
|
|
1061
|
+
});
|
|
1062
|
+
},
|
|
1063
|
+
async getChildren(parentId) {
|
|
1064
|
+
scope.assertActive();
|
|
1065
|
+
const rows = await db
|
|
1066
|
+
.select()
|
|
1067
|
+
.from(schema.tasks)
|
|
1068
|
+
.where(and(eq(schema.tasks.parentId, parentId), ne(schema.tasks.status, 'archived')))
|
|
1069
|
+
.orderBy(sql `${schema.tasks.position} ASC, ${schema.tasks.createdAt} ASC`)
|
|
1070
|
+
.all();
|
|
1071
|
+
return rows.map(rowToTask);
|
|
1072
|
+
},
|
|
1073
|
+
async appendLog(entry) {
|
|
1074
|
+
scope.assertActive();
|
|
1075
|
+
return accessor.transaction(async () => {
|
|
1076
|
+
scope.assertActive();
|
|
1077
|
+
await accessor.appendLog(entry);
|
|
1078
|
+
});
|
|
1079
|
+
},
|
|
1080
|
+
// T9514: persist relates mutations inside the transaction
|
|
1081
|
+
async addRelation(taskId, relatedTo, relationType, reason) {
|
|
1082
|
+
scope.assertActive();
|
|
1083
|
+
return accessor.transaction(async () => {
|
|
1084
|
+
scope.assertActive();
|
|
1085
|
+
await accessor.addRelation(taskId, relatedTo, relationType, reason);
|
|
1086
|
+
});
|
|
1087
|
+
},
|
|
1088
|
+
async removeRelation(taskId, relatedTo, relationType) {
|
|
1089
|
+
scope.assertActive();
|
|
1090
|
+
return accessor.transaction(async () => {
|
|
1091
|
+
scope.assertActive();
|
|
1092
|
+
await accessor.removeRelation(taskId, relatedTo, relationType);
|
|
1093
|
+
});
|
|
1094
|
+
},
|
|
1095
|
+
async clearRelations(taskId) {
|
|
1096
|
+
scope.assertActive();
|
|
1097
|
+
return accessor.transaction(async () => {
|
|
1098
|
+
scope.assertActive();
|
|
1099
|
+
await db
|
|
1100
|
+
.delete(schema.taskRelations)
|
|
1101
|
+
.where(eq(schema.taskRelations.taskId, taskId))
|
|
1102
|
+
.run();
|
|
1103
|
+
});
|
|
1104
|
+
},
|
|
1105
|
+
// ---- AC rows (T10508) ----
|
|
1106
|
+
async insertAcRows(rows) {
|
|
1107
|
+
scope.assertActive();
|
|
1108
|
+
return accessor.transaction(async () => {
|
|
1109
|
+
scope.assertActive();
|
|
1110
|
+
if (rows.length === 0)
|
|
1111
|
+
return;
|
|
1112
|
+
await db
|
|
1113
|
+
.insert(schema.taskAcceptanceCriteria)
|
|
1114
|
+
.values(rows.map((r) => ({
|
|
1115
|
+
id: r.id,
|
|
1116
|
+
taskId: r.taskId,
|
|
1117
|
+
ordinal: r.ordinal,
|
|
1118
|
+
kind: r.kind ?? 'text',
|
|
1119
|
+
sourceKey: r.sourceKey ?? `text:${r.ordinal}`,
|
|
1120
|
+
targetTaskId: r.targetTaskId ?? null,
|
|
1121
|
+
projection: r.projection ?? 'legacy',
|
|
1122
|
+
text: r.text,
|
|
1123
|
+
contentHash: r.contentHash ?? null,
|
|
1124
|
+
})))
|
|
1125
|
+
.run();
|
|
1126
|
+
});
|
|
1127
|
+
},
|
|
1128
|
+
async getAcRows(taskId) {
|
|
1129
|
+
scope.assertActive();
|
|
1130
|
+
const out = await db
|
|
1131
|
+
.select({
|
|
1132
|
+
id: schema.taskAcceptanceCriteria.id,
|
|
1133
|
+
taskId: schema.taskAcceptanceCriteria.taskId,
|
|
1134
|
+
ordinal: schema.taskAcceptanceCriteria.ordinal,
|
|
1135
|
+
kind: schema.taskAcceptanceCriteria.kind,
|
|
1136
|
+
sourceKey: schema.taskAcceptanceCriteria.sourceKey,
|
|
1137
|
+
targetTaskId: schema.taskAcceptanceCriteria.targetTaskId,
|
|
1138
|
+
projection: schema.taskAcceptanceCriteria.projection,
|
|
1139
|
+
text: schema.taskAcceptanceCriteria.text,
|
|
1140
|
+
createdAt: schema.taskAcceptanceCriteria.createdAt,
|
|
1141
|
+
updatedAt: schema.taskAcceptanceCriteria.updatedAt,
|
|
1142
|
+
contentHash: schema.taskAcceptanceCriteria.contentHash,
|
|
1143
|
+
})
|
|
1144
|
+
.from(schema.taskAcceptanceCriteria)
|
|
1145
|
+
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
1146
|
+
.orderBy(schema.taskAcceptanceCriteria.ordinal)
|
|
1147
|
+
.all();
|
|
1148
|
+
return out.map((r) => ({
|
|
1149
|
+
id: r.id,
|
|
1150
|
+
taskId: r.taskId,
|
|
1151
|
+
ordinal: r.ordinal,
|
|
1152
|
+
kind: r.kind,
|
|
1153
|
+
sourceKey: r.sourceKey ?? `text:${r.ordinal}`,
|
|
1154
|
+
targetTaskId: r.targetTaskId ?? null,
|
|
1155
|
+
projection: r.projection,
|
|
1156
|
+
text: r.text,
|
|
1157
|
+
createdAt: r.createdAt,
|
|
1158
|
+
updatedAt: r.updatedAt ?? null,
|
|
1159
|
+
contentHash: r.contentHash ?? null,
|
|
1160
|
+
}));
|
|
1161
|
+
},
|
|
1162
|
+
async deleteAcRowsForTask(taskId) {
|
|
1163
|
+
scope.assertActive();
|
|
1164
|
+
return accessor.transaction(async () => {
|
|
1165
|
+
scope.assertActive();
|
|
1166
|
+
await db
|
|
1167
|
+
.delete(schema.taskAcceptanceCriteria)
|
|
1168
|
+
.where(eq(schema.taskAcceptanceCriteria.taskId, taskId))
|
|
1169
|
+
.run();
|
|
1170
|
+
});
|
|
1171
|
+
},
|
|
1172
|
+
async appendAcHistory(rows) {
|
|
1173
|
+
scope.assertActive();
|
|
1174
|
+
return accessor.transaction(async () => {
|
|
1175
|
+
scope.assertActive();
|
|
1176
|
+
if (rows.length === 0)
|
|
1177
|
+
return;
|
|
1178
|
+
await db
|
|
1179
|
+
.insert(schema.taskAcceptanceCriteriaHistory)
|
|
1180
|
+
.values(rows.map((r) => ({
|
|
1181
|
+
acId: r.acId,
|
|
1182
|
+
previousText: r.previousText,
|
|
1183
|
+
reason: r.reason,
|
|
1184
|
+
})))
|
|
1185
|
+
.run();
|
|
1186
|
+
});
|
|
1187
|
+
},
|
|
1188
|
+
// ---- AC bindings (T10509 — AC-coverage gate) ----
|
|
1189
|
+
async getAcBindings(acIds) {
|
|
1190
|
+
scope.assertActive();
|
|
1191
|
+
if (acIds.length === 0)
|
|
1192
|
+
return [];
|
|
1193
|
+
const out = await db
|
|
1194
|
+
.select({
|
|
1195
|
+
id: schema.evidenceAcBindings.id,
|
|
1196
|
+
evidenceAtomId: schema.evidenceAcBindings.evidenceAtomId,
|
|
1197
|
+
acId: schema.evidenceAcBindings.acId,
|
|
1198
|
+
bindingType: schema.evidenceAcBindings.bindingType,
|
|
1199
|
+
createdAt: schema.evidenceAcBindings.createdAt,
|
|
1200
|
+
})
|
|
1201
|
+
.from(schema.evidenceAcBindings)
|
|
1202
|
+
.where(inArray(schema.evidenceAcBindings.acId, acIds))
|
|
1203
|
+
.all();
|
|
1204
|
+
return out.map((r) => ({
|
|
1205
|
+
id: r.id,
|
|
1206
|
+
evidenceAtomId: r.evidenceAtomId,
|
|
1207
|
+
acId: r.acId,
|
|
1208
|
+
bindingType: r.bindingType,
|
|
1209
|
+
createdAt: r.createdAt,
|
|
1210
|
+
}));
|
|
1211
|
+
},
|
|
1212
|
+
// ---- AC bindings — writer (T10511, Validator SDK tools) ----
|
|
1213
|
+
async insertAcBindings(rows) {
|
|
1214
|
+
scope.assertActive();
|
|
1215
|
+
return accessor.transaction(async () => {
|
|
1216
|
+
scope.assertActive();
|
|
1217
|
+
if (rows.length === 0)
|
|
1218
|
+
return;
|
|
1219
|
+
// Idempotent insert — UNIQUE (evidence_atom_id, ac_id, binding_type)
|
|
1220
|
+
// index collapses re-inserts of the same triple via DO NOTHING.
|
|
1221
|
+
await db
|
|
1222
|
+
.insert(schema.evidenceAcBindings)
|
|
1223
|
+
.values(rows.map((r) => ({
|
|
1224
|
+
id: r.id,
|
|
1225
|
+
evidenceAtomId: r.evidenceAtomId,
|
|
1226
|
+
acId: r.acId,
|
|
1227
|
+
bindingType: r.bindingType,
|
|
1228
|
+
})))
|
|
1229
|
+
.onConflictDoNothing({
|
|
1230
|
+
target: [
|
|
1231
|
+
schema.evidenceAcBindings.evidenceAtomId,
|
|
1232
|
+
schema.evidenceAcBindings.acId,
|
|
1233
|
+
schema.evidenceAcBindings.bindingType,
|
|
1234
|
+
],
|
|
1235
|
+
})
|
|
1236
|
+
.run();
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
};
|
|
1240
|
+
const result = await fn(bindTaskAccessorScope(tx, accessorScope));
|
|
1241
|
+
await scope.pending;
|
|
1242
|
+
if (isOuter) {
|
|
1243
|
+
nativeDb.prepare('COMMIT').run();
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
|
|
1247
|
+
}
|
|
1248
|
+
return result;
|
|
1249
|
+
}
|
|
1250
|
+
catch (err) {
|
|
1251
|
+
await scope.pending;
|
|
1252
|
+
try {
|
|
1253
|
+
if (isOuter) {
|
|
1254
|
+
nativeDb.prepare('ROLLBACK').run();
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
nativeDb.prepare(`ROLLBACK TO SAVEPOINT ${spName}`).run();
|
|
1258
|
+
nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
catch {
|
|
1262
|
+
/* ignore secondary rollback errors */
|
|
1263
|
+
}
|
|
1264
|
+
throw err;
|
|
1265
|
+
}
|
|
1266
|
+
finally {
|
|
1267
|
+
scope.active = false;
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1094
1270
|
};
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
if (isOuter) {
|
|
1098
|
-
nativeDb.prepare('COMMIT').run();
|
|
1099
|
-
}
|
|
1100
|
-
else {
|
|
1101
|
-
nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
|
|
1102
|
-
}
|
|
1103
|
-
return result;
|
|
1271
|
+
const commit = () => withBackgroundOpCommitBoundary(nativeDb, execute, worktreeScope.getStore()?.execution, (work) => scheduleTaskBackground(nativeDb, work));
|
|
1272
|
+
return await (context ? commit() : withWriteRetry(commit));
|
|
1104
1273
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
nativeDb.prepare('ROLLBACK').run();
|
|
1110
|
-
}
|
|
1111
|
-
else {
|
|
1112
|
-
nativeDb.prepare(`ROLLBACK TO SAVEPOINT ${spName}`).run();
|
|
1113
|
-
nativeDb.prepare(`RELEASE SAVEPOINT ${spName}`).run();
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
catch {
|
|
1117
|
-
/* ignore secondary rollback errors */
|
|
1118
|
-
}
|
|
1119
|
-
throw err;
|
|
1274
|
+
finally {
|
|
1275
|
+
released.resolve();
|
|
1276
|
+
if (!context && taskTransactionQueue.get(nativeDb) === tail)
|
|
1277
|
+
taskTransactionQueue.delete(nativeDb);
|
|
1120
1278
|
}
|
|
1121
1279
|
},
|
|
1122
1280
|
// ---- close ----
|
|
@@ -1128,7 +1286,7 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
1128
1286
|
return getMetaValue(cwd, key);
|
|
1129
1287
|
},
|
|
1130
1288
|
async setMetaValue(key, value) {
|
|
1131
|
-
return setMetaValue(
|
|
1289
|
+
return accessor.transaction((tx) => tx.setMetaValue(key, value));
|
|
1132
1290
|
},
|
|
1133
1291
|
async getSchemaVersion() {
|
|
1134
1292
|
const meta = await getMetaValue(cwd, 'file_meta');
|
|
@@ -1146,44 +1304,48 @@ export async function createSqliteDataAccessor(cwd) {
|
|
|
1146
1304
|
},
|
|
1147
1305
|
// ---- Agent task claiming ----
|
|
1148
1306
|
async claimTask(taskId, agentId) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
.get(taskId);
|
|
1157
|
-
if (!existsRow) {
|
|
1158
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
1159
|
-
}
|
|
1160
|
-
// Atomic claim: only succeeds if assignee IS NULL or already claimed by this agent.
|
|
1161
|
-
// This prevents race conditions between concurrent agents.
|
|
1162
|
-
const result = nativeDb
|
|
1163
|
-
.prepare('UPDATE tasks_tasks SET assignee = ?, updated_at = ? WHERE id = ? AND (assignee IS NULL OR assignee = ?)')
|
|
1164
|
-
.run(agentId, new Date().toISOString(), taskId, agentId);
|
|
1165
|
-
if (result.changes === 0) {
|
|
1166
|
-
// Row was not updated — task is claimed by a different agent
|
|
1167
|
-
const currentRow = nativeDb
|
|
1307
|
+
return accessor.transaction(async () => {
|
|
1308
|
+
const nativeDb = await requireNativeDb();
|
|
1309
|
+
if (!nativeDb) {
|
|
1310
|
+
throw new Error('Native database not initialized');
|
|
1311
|
+
}
|
|
1312
|
+
// Verify the task exists first
|
|
1313
|
+
const existsRow = nativeDb
|
|
1168
1314
|
.prepare('SELECT assignee FROM tasks_tasks WHERE id = ?')
|
|
1169
1315
|
.get(taskId);
|
|
1170
|
-
|
|
1171
|
-
|
|
1316
|
+
if (!existsRow) {
|
|
1317
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1318
|
+
}
|
|
1319
|
+
// Atomic claim: only succeeds if assignee IS NULL or already claimed by this agent.
|
|
1320
|
+
// This prevents race conditions between concurrent agents.
|
|
1321
|
+
const result = nativeDb
|
|
1322
|
+
.prepare('UPDATE tasks_tasks SET assignee = ?, updated_at = ? WHERE id = ? AND (assignee IS NULL OR assignee = ?)')
|
|
1323
|
+
.run(agentId, new Date().toISOString(), taskId, agentId);
|
|
1324
|
+
if (result.changes === 0) {
|
|
1325
|
+
// Row was not updated — task is claimed by a different agent
|
|
1326
|
+
const currentRow = nativeDb
|
|
1327
|
+
.prepare('SELECT assignee FROM tasks_tasks WHERE id = ?')
|
|
1328
|
+
.get(taskId);
|
|
1329
|
+
throw new Error(`Task ${taskId} is already claimed by agent: ${currentRow?.assignee ?? 'unknown'}`);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1172
1332
|
},
|
|
1173
1333
|
async unclaimTask(taskId) {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1334
|
+
return accessor.transaction(async () => {
|
|
1335
|
+
const nativeDb = await requireNativeDb();
|
|
1336
|
+
if (!nativeDb) {
|
|
1337
|
+
throw new Error('Native database not initialized');
|
|
1338
|
+
}
|
|
1339
|
+
// Verify the task exists
|
|
1340
|
+
const existsRow = nativeDb.prepare('SELECT id FROM tasks_tasks WHERE id = ?').get(taskId);
|
|
1341
|
+
if (!existsRow) {
|
|
1342
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1343
|
+
}
|
|
1344
|
+
// Clear the assignee — no-op if already null
|
|
1345
|
+
nativeDb
|
|
1346
|
+
.prepare('UPDATE tasks_tasks SET assignee = NULL, updated_at = ? WHERE id = ?')
|
|
1347
|
+
.run(new Date().toISOString(), taskId);
|
|
1348
|
+
});
|
|
1187
1349
|
},
|
|
1188
1350
|
};
|
|
1189
1351
|
return accessor;
|