@cleocode/core 2026.9.20 → 2026.9.22
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/adapters/discovery.d.ts.map +1 -1
- package/dist/adapters/discovery.js +3 -10
- package/dist/adapters/discovery.js.map +1 -1
- package/dist/adrs/link-pipeline.js +2 -2
- package/dist/adrs/link-pipeline.js.map +1 -1
- package/dist/adrs/parse.js +3 -3
- package/dist/adrs/parse.js.map +1 -1
- package/dist/agents/invoke-meta-agent.d.ts.map +1 -1
- package/dist/agents/invoke-meta-agent.js +8 -4
- package/dist/agents/invoke-meta-agent.js.map +1 -1
- package/dist/agents/scaffold.d.ts.map +1 -1
- package/dist/agents/scaffold.js +0 -1
- package/dist/agents/scaffold.js.map +1 -1
- package/dist/bootstrap.d.ts +5 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +45 -21
- package/dist/bootstrap.js.map +1 -1
- package/dist/cloud/crypto.d.ts +47 -0
- package/dist/cloud/crypto.d.ts.map +1 -0
- package/dist/cloud/crypto.js +223 -0
- package/dist/cloud/crypto.js.map +1 -0
- package/dist/cloud/http.d.ts +52 -0
- package/dist/cloud/http.d.ts.map +1 -0
- package/dist/cloud/http.js +98 -0
- package/dist/cloud/http.js.map +1 -0
- package/dist/cloud/index.d.ts +19 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +19 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/cloud/journal.d.ts +169 -0
- package/dist/cloud/journal.d.ts.map +1 -0
- package/dist/cloud/journal.js +428 -0
- package/dist/cloud/journal.js.map +1 -0
- package/dist/cloud/keys.d.ts +213 -0
- package/dist/cloud/keys.d.ts.map +1 -0
- package/dist/cloud/keys.js +362 -0
- package/dist/cloud/keys.js.map +1 -0
- package/dist/cloud/manifest-check.d.ts +36 -0
- package/dist/cloud/manifest-check.d.ts.map +1 -0
- package/dist/cloud/manifest-check.js +70 -0
- package/dist/cloud/manifest-check.js.map +1 -0
- package/dist/cloud/projects.d.ts +13 -0
- package/dist/cloud/projects.d.ts.map +1 -0
- package/dist/cloud/projects.js +11 -0
- package/dist/cloud/projects.js.map +1 -0
- package/dist/cloud/signing.d.ts +105 -0
- package/dist/cloud/signing.d.ts.map +1 -0
- package/dist/cloud/signing.js +117 -0
- package/dist/cloud/signing.js.map +1 -0
- package/dist/cloud/streams.d.ts +6 -0
- package/dist/cloud/streams.d.ts.map +1 -0
- package/dist/cloud/streams.js +5 -0
- package/dist/cloud/streams.js.map +1 -0
- package/dist/decide/audit.d.ts +131 -0
- package/dist/decide/audit.d.ts.map +1 -0
- package/dist/decide/audit.js +98 -0
- package/dist/decide/audit.js.map +1 -0
- package/dist/decide/budget.d.ts +91 -0
- package/dist/decide/budget.d.ts.map +1 -0
- package/dist/decide/budget.js +194 -0
- package/dist/decide/budget.js.map +1 -0
- package/dist/decide/cache.d.ts +49 -0
- package/dist/decide/cache.d.ts.map +1 -0
- package/dist/decide/cache.js +87 -0
- package/dist/decide/cache.js.map +1 -0
- package/dist/decide/client.d.ts +103 -0
- package/dist/decide/client.d.ts.map +1 -0
- package/dist/decide/client.js +262 -0
- package/dist/decide/client.js.map +1 -0
- package/dist/decide/credentials.d.ts +159 -0
- package/dist/decide/credentials.d.ts.map +1 -0
- package/dist/decide/credentials.js +383 -0
- package/dist/decide/credentials.js.map +1 -0
- package/dist/decide/index.d.ts +18 -0
- package/dist/decide/index.d.ts.map +1 -0
- package/dist/decide/index.js +18 -0
- package/dist/decide/index.js.map +1 -0
- package/dist/decide/jev-wire.d.ts +155 -0
- package/dist/decide/jev-wire.d.ts.map +1 -0
- package/dist/decide/jev-wire.js +420 -0
- package/dist/decide/jev-wire.js.map +1 -0
- package/dist/decide/operations.d.ts +152 -0
- package/dist/decide/operations.d.ts.map +1 -0
- package/dist/decide/operations.js +199 -0
- package/dist/decide/operations.js.map +1 -0
- package/dist/decide/provider.d.ts +90 -0
- package/dist/decide/provider.d.ts.map +1 -0
- package/dist/decide/provider.js +43 -0
- package/dist/decide/provider.js.map +1 -0
- package/dist/decide/site.d.ts +148 -0
- package/dist/decide/site.d.ts.map +1 -0
- package/dist/decide/site.js +181 -0
- package/dist/decide/site.js.map +1 -0
- package/dist/decide/transport.d.ts +67 -0
- package/dist/decide/transport.d.ts.map +1 -0
- package/dist/decide/transport.js +350 -0
- package/dist/decide/transport.js.map +1 -0
- package/dist/dispatch/mutate-projection.d.ts.map +1 -1
- package/dist/dispatch/mutate-projection.js +5 -0
- package/dist/dispatch/mutate-projection.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +25 -9
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/export-document.js +5963 -2406
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/index.d.ts +2 -2
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +1 -1
- package/dist/docs/index.js.map +1 -1
- package/dist/docs/projection.d.ts.map +1 -1
- package/dist/docs/projection.js +2 -3
- package/dist/docs/projection.js.map +1 -1
- package/dist/docs/publish-pr.d.ts +69 -1
- package/dist/docs/publish-pr.d.ts.map +1 -1
- package/dist/docs/publish-pr.js +112 -10
- package/dist/docs/publish-pr.js.map +1 -1
- package/dist/doctor/db-substrate.d.ts +13 -8
- package/dist/doctor/db-substrate.d.ts.map +1 -1
- package/dist/doctor/db-substrate.js +39 -26
- package/dist/doctor/db-substrate.js.map +1 -1
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.d.ts.map +1 -1
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/knowledge.d.ts +16 -1
- package/dist/doctor/knowledge.d.ts.map +1 -1
- package/dist/doctor/knowledge.js +58 -32
- package/dist/doctor/knowledge.js.map +1 -1
- package/dist/doctor/nexus-schema-residency.d.ts +1 -1
- package/dist/doctor/nexus-schema-residency.d.ts.map +1 -1
- package/dist/doctor/nexus-schema-residency.js +3 -0
- package/dist/doctor/nexus-schema-residency.js.map +1 -1
- package/dist/doctor/project-identity.d.ts +44 -1
- package/dist/doctor/project-identity.d.ts.map +1 -1
- package/dist/doctor/project-identity.js +237 -10
- package/dist/doctor/project-identity.js.map +1 -1
- package/dist/doctor/projects.d.ts +170 -0
- package/dist/doctor/projects.d.ts.map +1 -0
- package/dist/doctor/projects.js +1075 -0
- package/dist/doctor/projects.js.map +1 -0
- package/dist/doctor/twin-collapse.d.ts +97 -0
- package/dist/doctor/twin-collapse.d.ts.map +1 -0
- package/dist/doctor/twin-collapse.js +168 -0
- package/dist/doctor/twin-collapse.js.map +1 -0
- package/dist/doctor/worktree-orphans.d.ts.map +1 -1
- package/dist/doctor/worktree-orphans.js +13 -29
- package/dist/doctor/worktree-orphans.js.map +1 -1
- package/dist/doctor/worktree-stores.d.ts +128 -0
- package/dist/doctor/worktree-stores.d.ts.map +1 -0
- package/dist/doctor/worktree-stores.js +374 -0
- package/dist/doctor/worktree-stores.js.map +1 -0
- package/dist/engine/engine-ops.d.ts +36 -6
- package/dist/engine/engine-ops.d.ts.map +1 -1
- package/dist/engine/engine-ops.js +115 -14
- package/dist/engine/engine-ops.js.map +1 -1
- package/dist/error-catalog.d.ts.map +1 -1
- package/dist/error-catalog.js +21 -0
- package/dist/error-catalog.js.map +1 -1
- package/dist/error-registry.d.ts.map +1 -1
- package/dist/error-registry.js +40 -0
- package/dist/error-registry.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +1 -1
- package/dist/gc/cleanup.d.ts.map +1 -1
- package/dist/gc/cleanup.js +32 -19
- package/dist/gc/cleanup.js.map +1 -1
- package/dist/git/bounded-git.d.ts +170 -0
- package/dist/git/bounded-git.d.ts.map +1 -0
- package/dist/git/bounded-git.js +371 -0
- package/dist/git/bounded-git.js.map +1 -0
- package/dist/git/work-tree.d.ts +20 -0
- package/dist/git/work-tree.d.ts.map +1 -1
- package/dist/git/work-tree.js +20 -2
- package/dist/git/work-tree.js.map +1 -1
- package/dist/hooks/handlers/session-hooks.d.ts +11 -26
- package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/session-hooks.js +18 -47
- package/dist/hooks/handlers/session-hooks.js.map +1 -1
- package/dist/hooks/payload-schemas.d.ts +2 -2
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/init/engine-ops.d.ts.map +1 -1
- package/dist/init/engine-ops.js +23 -1
- package/dist/init/engine-ops.js.map +1 -1
- package/dist/init.d.ts +158 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +578 -53
- package/dist/init.js.map +1 -1
- package/dist/inject/index.d.ts.map +1 -1
- package/dist/inject/index.js +8 -3
- package/dist/inject/index.js.map +1 -1
- package/dist/injection.d.ts +55 -0
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +102 -6
- package/dist/injection.js.map +1 -1
- package/dist/internal.d.ts +11 -7
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +9 -5
- package/dist/internal.js.map +1 -1
- package/dist/issue/diagnostics.d.ts.map +1 -1
- package/dist/issue/diagnostics.js +2 -1
- package/dist/issue/diagnostics.js.map +1 -1
- package/dist/lib/concurrency.d.ts +25 -0
- package/dist/lib/concurrency.d.ts.map +1 -0
- package/dist/lib/concurrency.js +43 -0
- package/dist/lib/concurrency.js.map +1 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lifecycle/engine-ops.d.ts.map +1 -1
- package/dist/lifecycle/engine-ops.js +4 -2
- package/dist/lifecycle/engine-ops.js.map +1 -1
- package/dist/lifecycle/stage-guidance.d.ts +5 -0
- package/dist/lifecycle/stage-guidance.d.ts.map +1 -1
- package/dist/lifecycle/stage-guidance.js +14 -4
- package/dist/lifecycle/stage-guidance.js.map +1 -1
- package/dist/llm/catalog-cache.d.ts +3 -2
- package/dist/llm/catalog-cache.d.ts.map +1 -1
- package/dist/llm/catalog-cache.js +5 -5
- package/dist/llm/catalog-cache.js.map +1 -1
- package/dist/llm/credentials.d.ts +4 -4
- package/dist/llm/credentials.js +6 -6
- package/dist/llm/credentials.js.map +1 -1
- package/dist/llm/plugin-facade.js +4400 -1801
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/llm/stable-device-id.d.ts.map +1 -1
- package/dist/llm/stable-device-id.js +3 -5
- package/dist/llm/stable-device-id.js.map +1 -1
- package/dist/memory/attention.js +31 -31
- package/dist/memory/attention.js.map +1 -1
- package/dist/memory/decision-contradiction.d.ts +152 -0
- package/dist/memory/decision-contradiction.d.ts.map +1 -0
- package/dist/memory/decision-contradiction.js +273 -0
- package/dist/memory/decision-contradiction.js.map +1 -0
- package/dist/memory/decisions.d.ts +42 -11
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +153 -56
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/dialectic-evaluator.d.ts +7 -0
- package/dist/memory/dialectic-evaluator.d.ts.map +1 -1
- package/dist/memory/dialectic-evaluator.js +23 -8
- package/dist/memory/dialectic-evaluator.js.map +1 -1
- package/dist/memory/dispatch-trace-format.d.ts +39 -0
- package/dist/memory/dispatch-trace-format.d.ts.map +1 -0
- package/dist/memory/dispatch-trace-format.js +54 -0
- package/dist/memory/dispatch-trace-format.js.map +1 -0
- package/dist/memory/dispatch-trace.d.ts.map +1 -1
- package/dist/memory/dispatch-trace.js +1 -27
- package/dist/memory/dispatch-trace.js.map +1 -1
- 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 +1 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/import-from-provider.js +3 -3
- package/dist/memory/import-from-provider.js.map +1 -1
- package/dist/memory/learnings.d.ts +2 -2
- package/dist/memory/observation-type-decision.d.ts +96 -0
- package/dist/memory/observation-type-decision.d.ts.map +1 -0
- package/dist/memory/observation-type-decision.js +218 -0
- package/dist/memory/observation-type-decision.js.map +1 -0
- package/dist/memory/redaction.js +2 -0
- package/dist/memory/redaction.js.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.d.ts +9 -2
- package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.js +42 -11
- package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
- package/dist/memory/retrieval/get-current-session-id.d.ts +5 -0
- package/dist/memory/retrieval/get-current-session-id.d.ts.map +1 -1
- package/dist/memory/retrieval/get-current-session-id.js +7 -2
- package/dist/memory/retrieval/get-current-session-id.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +30 -29
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts +38 -0
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +136 -48
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/assessment-projection.d.ts +76 -0
- package/dist/nexus/assessment-projection.d.ts.map +1 -0
- package/dist/nexus/assessment-projection.js +143 -0
- package/dist/nexus/assessment-projection.js.map +1 -0
- package/dist/nexus/assessment-store.d.ts +14 -2
- package/dist/nexus/assessment-store.d.ts.map +1 -1
- package/dist/nexus/assessment-store.js +21 -3
- package/dist/nexus/assessment-store.js.map +1 -1
- package/dist/nexus/checkout-nonce.d.ts +48 -0
- package/dist/nexus/checkout-nonce.d.ts.map +1 -0
- package/dist/nexus/checkout-nonce.js +84 -0
- package/dist/nexus/checkout-nonce.js.map +1 -0
- package/dist/nexus/devices.d.ts +93 -0
- package/dist/nexus/devices.d.ts.map +1 -0
- package/dist/nexus/devices.js +141 -0
- package/dist/nexus/devices.js.map +1 -0
- package/dist/nexus/freshness.d.ts.map +1 -1
- package/dist/nexus/freshness.js +25 -6
- package/dist/nexus/freshness.js.map +1 -1
- package/dist/nexus/git-state.d.ts +225 -0
- package/dist/nexus/git-state.d.ts.map +1 -0
- package/dist/nexus/git-state.js +677 -0
- package/dist/nexus/git-state.js.map +1 -0
- package/dist/nexus/graph-manifest.d.ts +20 -4
- package/dist/nexus/graph-manifest.d.ts.map +1 -1
- package/dist/nexus/graph-manifest.js +40 -5
- package/dist/nexus/graph-manifest.js.map +1 -1
- package/dist/nexus/identity.d.ts +68 -23
- package/dist/nexus/identity.d.ts.map +1 -1
- package/dist/nexus/identity.js +99 -22
- package/dist/nexus/identity.js.map +1 -1
- package/dist/nexus/index.d.ts +5 -2
- package/dist/nexus/index.d.ts.map +1 -1
- package/dist/nexus/index.js +10 -4
- package/dist/nexus/index.js.map +1 -1
- package/dist/nexus/knowledge.d.ts +5 -0
- package/dist/nexus/knowledge.d.ts.map +1 -1
- package/dist/nexus/knowledge.js +40 -5
- package/dist/nexus/knowledge.js.map +1 -1
- package/dist/nexus/legacy-alias.d.ts +60 -0
- package/dist/nexus/legacy-alias.d.ts.map +1 -0
- package/dist/nexus/legacy-alias.js +68 -0
- package/dist/nexus/legacy-alias.js.map +1 -0
- package/dist/nexus/migrate-json-to-sqlite.js +2 -2
- package/dist/nexus/migrate-json-to-sqlite.js.map +1 -1
- package/dist/nexus/ops.d.ts +1 -0
- package/dist/nexus/ops.d.ts.map +1 -1
- package/dist/nexus/path-map.d.ts +264 -20
- package/dist/nexus/path-map.d.ts.map +1 -1
- package/dist/nexus/path-map.js +580 -44
- package/dist/nexus/path-map.js.map +1 -1
- package/dist/nexus/projects-clean.d.ts +9 -1
- package/dist/nexus/projects-clean.d.ts.map +1 -1
- package/dist/nexus/projects-clean.js +126 -14
- package/dist/nexus/projects-clean.js.map +1 -1
- package/dist/nexus/projects-scan.d.ts +56 -1
- package/dist/nexus/projects-scan.d.ts.map +1 -1
- package/dist/nexus/projects-scan.js +144 -3
- package/dist/nexus/projects-scan.js.map +1 -1
- package/dist/nexus/registry-hygiene.d.ts +18 -2
- package/dist/nexus/registry-hygiene.d.ts.map +1 -1
- package/dist/nexus/registry-hygiene.js +37 -11
- package/dist/nexus/registry-hygiene.js.map +1 -1
- package/dist/nexus/registry-roots.d.ts +47 -0
- package/dist/nexus/registry-roots.d.ts.map +1 -0
- package/dist/nexus/registry-roots.js +79 -0
- package/dist/nexus/registry-roots.js.map +1 -0
- package/dist/nexus/registry.d.ts +50 -9
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +311 -156
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/stored-roots.d.ts +171 -0
- package/dist/nexus/stored-roots.d.ts.map +1 -0
- package/dist/nexus/stored-roots.js +253 -0
- package/dist/nexus/stored-roots.js.map +1 -0
- package/dist/nexus/transfer.d.ts.map +1 -1
- package/dist/nexus/transfer.js +3 -0
- package/dist/nexus/transfer.js.map +1 -1
- package/dist/nexus/user-profile-hygiene.d.ts +168 -0
- package/dist/nexus/user-profile-hygiene.d.ts.map +1 -0
- package/dist/nexus/user-profile-hygiene.js +305 -0
- package/dist/nexus/user-profile-hygiene.js.map +1 -0
- package/dist/nexus/user-profile.d.ts +53 -2
- package/dist/nexus/user-profile.d.ts.map +1 -1
- package/dist/nexus/user-profile.js +106 -4
- package/dist/nexus/user-profile.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +4 -1
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/pivot.d.ts.map +1 -1
- package/dist/orchestrate/pivot.js +5 -1
- package/dist/orchestrate/pivot.js.map +1 -1
- package/dist/orchestrate/plan.d.ts.map +1 -1
- package/dist/orchestrate/plan.js +5 -2
- package/dist/orchestrate/plan.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts +7 -2
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +17 -6
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts +12 -0
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +157 -71
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/bootstrap.js +4 -2
- package/dist/orchestration/bootstrap.js.map +1 -1
- package/dist/orchestration/classify-readiness.d.ts +37 -1
- package/dist/orchestration/classify-readiness.d.ts.map +1 -1
- package/dist/orchestration/classify-readiness.js +48 -19
- package/dist/orchestration/classify-readiness.js.map +1 -1
- package/dist/orchestration/index.d.ts +4 -2
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +6 -4
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +8 -5
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +32 -15
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/owner-decision-readiness.d.ts +88 -0
- package/dist/orchestration/owner-decision-readiness.d.ts.map +1 -0
- package/dist/orchestration/owner-decision-readiness.js +152 -0
- package/dist/orchestration/owner-decision-readiness.js.map +1 -0
- package/dist/orchestration/parallel.d.ts.map +1 -1
- package/dist/orchestration/parallel.js +10 -17
- package/dist/orchestration/parallel.js.map +1 -1
- package/dist/orchestration/psyche-memory-budget.d.ts +105 -0
- package/dist/orchestration/psyche-memory-budget.d.ts.map +1 -0
- package/dist/orchestration/psyche-memory-budget.js +383 -0
- package/dist/orchestration/psyche-memory-budget.js.map +1 -0
- package/dist/orchestration/spawn-prompt.d.ts +16 -2
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +144 -181
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/spawn.d.ts +7 -0
- package/dist/orchestration/spawn.d.ts.map +1 -1
- package/dist/orchestration/spawn.js +1 -0
- package/dist/orchestration/spawn.js.map +1 -1
- package/dist/orchestration/status.d.ts +8 -3
- package/dist/orchestration/status.d.ts.map +1 -1
- package/dist/orchestration/status.js +9 -6
- package/dist/orchestration/status.js.map +1 -1
- package/dist/orchestration/wave-topic.d.ts +54 -0
- package/dist/orchestration/wave-topic.d.ts.map +1 -0
- package/dist/orchestration/wave-topic.js +68 -0
- package/dist/orchestration/wave-topic.js.map +1 -0
- package/dist/orchestration/waves.d.ts +47 -8
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +152 -43
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/paths.d.ts +24 -8
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +135 -71
- package/dist/paths.js.map +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +6 -2
- package/dist/phases/index.js.map +1 -1
- package/dist/platform.d.ts +6 -1
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +8 -19
- package/dist/platform.js.map +1 -1
- package/dist/project-info.d.ts +18 -0
- package/dist/project-info.d.ts.map +1 -1
- package/dist/project-info.js +29 -1
- package/dist/project-info.js.map +1 -1
- package/dist/project-lifecycle.d.ts +91 -32
- package/dist/project-lifecycle.d.ts.map +1 -1
- package/dist/project-lifecycle.js +639 -75
- package/dist/project-lifecycle.js.map +1 -1
- package/dist/project-scope.d.ts +104 -4
- package/dist/project-scope.d.ts.map +1 -1
- package/dist/project-scope.js +263 -13
- package/dist/project-scope.js.map +1 -1
- package/dist/project-tombstone.d.ts +111 -0
- package/dist/project-tombstone.d.ts.map +1 -0
- package/dist/project-tombstone.js +220 -0
- package/dist/project-tombstone.js.map +1 -0
- package/dist/release/artifacts.d.ts.map +1 -1
- package/dist/release/artifacts.js +14 -9
- package/dist/release/artifacts.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +233 -0
- package/dist/release/ci-evidence.d.ts.map +1 -0
- package/dist/release/ci-evidence.js +668 -0
- package/dist/release/ci-evidence.js.map +1 -0
- package/dist/release/engine-ops.d.ts.map +1 -1
- package/dist/release/engine-ops.js +3 -3
- package/dist/release/engine-ops.js.map +1 -1
- package/dist/release/github-pr.d.ts +30 -0
- package/dist/release/github-pr.d.ts.map +1 -1
- package/dist/release/github-pr.js +40 -0
- package/dist/release/github-pr.js.map +1 -1
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +11 -7
- package/dist/release/plan.js.map +1 -1
- package/dist/release/pr-evidence.d.ts +38 -0
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +79 -15
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +3 -2
- package/dist/release/reconcile.js.map +1 -1
- package/dist/release/release-manifest.d.ts +2 -1
- package/dist/release/release-manifest.d.ts.map +1 -1
- package/dist/release/release-manifest.js +6 -5
- package/dist/release/release-manifest.js.map +1 -1
- package/dist/render/nexus/graph/index.d.ts +4 -0
- package/dist/render/nexus/graph/index.d.ts.map +1 -1
- package/dist/render/nexus/graph/index.js +57 -0
- package/dist/render/nexus/graph/index.js.map +1 -1
- package/dist/resources/spawn-wrapper.d.ts +8 -1
- package/dist/resources/spawn-wrapper.d.ts.map +1 -1
- package/dist/resources/spawn-wrapper.js +42 -5
- package/dist/resources/spawn-wrapper.js.map +1 -1
- package/dist/routing/build-command-groups.d.ts.map +1 -1
- package/dist/routing/build-command-groups.js +1 -0
- package/dist/routing/build-command-groups.js.map +1 -1
- package/dist/sagas/index.d.ts +1 -1
- package/dist/sagas/index.d.ts.map +1 -1
- package/dist/sagas/index.js +1 -1
- package/dist/sagas/index.js.map +1 -1
- package/dist/sagas/reconcile.d.ts +24 -4
- package/dist/sagas/reconcile.d.ts.map +1 -1
- package/dist/sagas/reconcile.js +226 -100
- package/dist/sagas/reconcile.js.map +1 -1
- package/dist/scaffold/ensure-config.d.ts.map +1 -1
- package/dist/scaffold/ensure-config.js +31 -1
- package/dist/scaffold/ensure-config.js.map +1 -1
- package/dist/scaffold/project-identity.d.ts +2 -8
- package/dist/scaffold/project-identity.d.ts.map +1 -1
- package/dist/scaffold/project-identity.js +5 -21
- package/dist/scaffold/project-identity.js.map +1 -1
- package/dist/security/input-sanitization.d.ts +9 -1
- package/dist/security/input-sanitization.d.ts.map +1 -1
- package/dist/security/input-sanitization.js +20 -3
- package/dist/security/input-sanitization.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +4409 -1810
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/propose-tick.d.ts.map +1 -1
- package/dist/sentient/propose-tick.js +4 -2
- package/dist/sentient/propose-tick.js.map +1 -1
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/index.js +6 -23
- package/dist/sequence/index.js.map +1 -1
- package/dist/session/engine-ops.d.ts +15 -0
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +146 -22
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/assumptions.d.ts.map +1 -1
- package/dist/sessions/assumptions.js +7 -4
- package/dist/sessions/assumptions.js.map +1 -1
- package/dist/sessions/briefing.d.ts +23 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +59 -9
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/decisions.d.ts +38 -3
- package/dist/sessions/decisions.d.ts.map +1 -1
- package/dist/sessions/decisions.js +235 -21
- package/dist/sessions/decisions.js.map +1 -1
- package/dist/sessions/drift-watchdog.d.ts.map +1 -1
- package/dist/sessions/drift-watchdog.js +8 -5
- package/dist/sessions/drift-watchdog.js.map +1 -1
- package/dist/sessions/focus-state-store.d.ts +89 -1
- package/dist/sessions/focus-state-store.d.ts.map +1 -1
- package/dist/sessions/focus-state-store.js +97 -1
- package/dist/sessions/focus-state-store.js.map +1 -1
- package/dist/sessions/index.d.ts +2 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +34 -5
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/session-drift.d.ts.map +1 -1
- package/dist/sessions/session-drift.js +4 -3
- package/dist/sessions/session-drift.js.map +1 -1
- package/dist/sessions/session-end-snapshot-entry.d.ts +26 -0
- package/dist/sessions/session-end-snapshot-entry.d.ts.map +1 -0
- package/dist/sessions/session-end-snapshot-entry.js +59 -0
- package/dist/sessions/session-end-snapshot-entry.js.map +1 -0
- package/dist/sessions/session-end-snapshot.d.ts +114 -0
- package/dist/sessions/session-end-snapshot.d.ts.map +1 -0
- package/dist/sessions/session-end-snapshot.js +446 -0
- package/dist/sessions/session-end-snapshot.js.map +1 -0
- package/dist/sessions/session-manifest-mirror.d.ts.map +1 -1
- package/dist/sessions/session-manifest-mirror.js +10 -17
- package/dist/sessions/session-manifest-mirror.js.map +1 -1
- package/dist/sessions/session-switch.d.ts.map +1 -1
- package/dist/sessions/session-switch.js +5 -3
- package/dist/sessions/session-switch.js.map +1 -1
- package/dist/sessions/snapshot.d.ts.map +1 -1
- package/dist/sessions/snapshot.js +5 -4
- package/dist/sessions/snapshot.js.map +1 -1
- package/dist/sessions/terminal-identity.d.ts +169 -0
- package/dist/sessions/terminal-identity.d.ts.map +1 -0
- package/dist/sessions/terminal-identity.js +273 -0
- package/dist/sessions/terminal-identity.js.map +1 -0
- package/dist/setup/sections/verification.d.ts +1 -1
- package/dist/setup/sections/verification.d.ts.map +1 -1
- package/dist/setup/sections/verification.js +11 -15
- package/dist/setup/sections/verification.js.map +1 -1
- package/dist/skills/discovery.d.ts +20 -3
- package/dist/skills/discovery.d.ts.map +1 -1
- package/dist/skills/discovery.js +41 -4
- package/dist/skills/discovery.js.map +1 -1
- package/dist/skills/dispatch.d.ts +5 -2
- package/dist/skills/dispatch.d.ts.map +1 -1
- package/dist/skills/dispatch.js +5 -2
- package/dist/skills/dispatch.js.map +1 -1
- package/dist/skills/doctor.d.ts.map +1 -1
- package/dist/skills/doctor.js +5 -1
- package/dist/skills/doctor.js.map +1 -1
- package/dist/skills/federated-search.js +2 -2
- package/dist/skills/federated-search.js.map +1 -1
- package/dist/skills/index.d.ts +3 -3
- 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/migration.d.ts +5 -5
- package/dist/skills/migration.d.ts.map +1 -1
- package/dist/skills/migration.js +7 -9
- package/dist/skills/migration.js.map +1 -1
- package/dist/skills/orchestrator/startup.d.ts.map +1 -1
- package/dist/skills/orchestrator/startup.js +14 -7
- package/dist/skills/orchestrator/startup.js.map +1 -1
- package/dist/skills/prune-bundled.d.ts +267 -0
- package/dist/skills/prune-bundled.d.ts.map +1 -0
- package/dist/skills/prune-bundled.js +536 -0
- package/dist/skills/prune-bundled.js.map +1 -0
- package/dist/skills/skill-paths.d.ts +30 -3
- package/dist/skills/skill-paths.d.ts.map +1 -1
- package/dist/skills/skill-paths.js +28 -9
- package/dist/skills/skill-paths.js.map +1 -1
- package/dist/skills/skill-root.d.ts +60 -11
- package/dist/skills/skill-root.d.ts.map +1 -1
- package/dist/skills/skill-root.js +100 -14
- package/dist/skills/skill-root.js.map +1 -1
- package/dist/skills/skills-guard.js +5 -5
- package/dist/skills/skills-guard.js.map +1 -1
- package/dist/skills/types.d.ts +32 -1
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/skills/types.js +17 -34
- package/dist/skills/types.js.map +1 -1
- package/dist/spawn/agent-identity.d.ts +51 -2
- package/dist/spawn/agent-identity.d.ts.map +1 -1
- package/dist/spawn/agent-identity.js +89 -26
- package/dist/spawn/agent-identity.js.map +1 -1
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +80 -79
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/spawn/index.d.ts +2 -2
- package/dist/spawn/index.d.ts.map +1 -1
- package/dist/spawn/index.js +1 -1
- package/dist/spawn/index.js.map +1 -1
- package/dist/spawn/worktree-lock-holder.d.ts +37 -0
- package/dist/spawn/worktree-lock-holder.d.ts.map +1 -0
- package/dist/spawn/worktree-lock-holder.js +41 -0
- package/dist/spawn/worktree-lock-holder.js.map +1 -0
- package/dist/stats/index.d.ts.map +1 -1
- package/dist/stats/index.js +4 -2
- package/dist/stats/index.js.map +1 -1
- package/dist/sticky/convert.d.ts.map +1 -1
- package/dist/sticky/convert.js +16 -4
- package/dist/sticky/convert.js.map +1 -1
- package/dist/store/attachment-store.d.ts +14 -0
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +11 -1
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/backup-inspect.d.ts +7 -3
- package/dist/store/backup-inspect.d.ts.map +1 -1
- package/dist/store/backup-inspect.js +18 -7
- package/dist/store/backup-inspect.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +60 -0
- package/dist/store/backup-sidecar.d.ts.map +1 -0
- package/dist/store/backup-sidecar.js +96 -0
- package/dist/store/backup-sidecar.js.map +1 -0
- package/dist/store/data-accessor.d.ts +1 -1
- package/dist/store/data-accessor.d.ts.map +1 -1
- package/dist/store/data-accessor.js +6 -6
- package/dist/store/data-accessor.js.map +1 -1
- package/dist/store/data-safety-central.d.ts +13 -4
- package/dist/store/data-safety-central.d.ts.map +1 -1
- package/dist/store/data-safety-central.js +31 -4
- package/dist/store/data-safety-central.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +10 -0
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/__fixtures__/representative-fixture.d.ts.map +1 -1
- package/dist/store/exodus/__fixtures__/representative-fixture.js +4 -0
- package/dist/store/exodus/__fixtures__/representative-fixture.js.map +1 -1
- package/dist/store/exodus/migrate.js +1 -1
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/reconcile.js +1 -1
- package/dist/store/exodus/reconcile.js.map +1 -1
- package/dist/store/exodus/runtime-targets.d.ts +4 -3
- package/dist/store/exodus/runtime-targets.d.ts.map +1 -1
- package/dist/store/exodus/runtime-targets.js +4 -3
- package/dist/store/exodus/runtime-targets.js.map +1 -1
- package/dist/store/import-logging.d.ts.map +1 -1
- package/dist/store/import-logging.js +5 -4
- package/dist/store/import-logging.js.map +1 -1
- package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
- package/dist/store/legacy-tasks-lineage.js +2 -7
- package/dist/store/legacy-tasks-lineage.js.map +1 -1
- package/dist/store/lock.d.ts +1 -0
- package/dist/store/lock.d.ts.map +1 -1
- package/dist/store/lock.js +1 -0
- package/dist/store/lock.js.map +1 -1
- package/dist/store/memory-accessor.d.ts +8 -3
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +29 -13
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/memory-sqlite.d.ts.map +1 -1
- package/dist/store/memory-sqlite.js +5 -0
- package/dist/store/memory-sqlite.js.map +1 -1
- package/dist/store/open-cleo-db.d.ts.map +1 -1
- package/dist/store/open-cleo-db.js +11 -6
- package/dist/store/open-cleo-db.js.map +1 -1
- package/dist/store/portable-bundle-relocate.d.ts +13 -8
- package/dist/store/portable-bundle-relocate.d.ts.map +1 -1
- package/dist/store/portable-bundle-relocate.js +75 -31
- package/dist/store/portable-bundle-relocate.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +2 -1
- package/dist/store/portable-bundle-scan.js.map +1 -1
- package/dist/store/portable-bundle.d.ts.map +1 -1
- package/dist/store/portable-bundle.js +7 -3
- package/dist/store/portable-bundle.js.map +1 -1
- package/dist/store/pre-repair-snapshot.d.ts +77 -0
- package/dist/store/pre-repair-snapshot.d.ts.map +1 -0
- package/dist/store/pre-repair-snapshot.js +122 -0
- package/dist/store/pre-repair-snapshot.js.map +1 -0
- package/dist/store/regenerators.d.ts +2 -1
- package/dist/store/regenerators.d.ts.map +1 -1
- package/dist/store/regenerators.js +17 -3
- package/dist/store/regenerators.js.map +1 -1
- package/dist/store/relocated-store-guard.d.ts +71 -0
- package/dist/store/relocated-store-guard.d.ts.map +1 -0
- package/dist/store/relocated-store-guard.js +138 -0
- package/dist/store/relocated-store-guard.js.map +1 -0
- package/dist/store/safety-data-accessor.d.ts +2 -2
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +2 -2
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/schema/attachments.d.ts +2 -2
- package/dist/store/schema/audit.d.ts +5 -5
- package/dist/store/schema/background-jobs.d.ts +1 -1
- package/dist/store/schema/cleo-global/models-catalog.d.ts +1 -1
- package/dist/store/schema/cleo-global/nexus.d.ts +686 -0
- package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/nexus.js +175 -7
- package/dist/store/schema/cleo-global/nexus.js.map +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +2 -2
- package/dist/store/schema/cleo-project/audit.d.ts +5 -5
- package/dist/store/schema/cleo-project/docs.d.ts +3 -3
- package/dist/store/schema/cleo-project/lifecycle.d.ts +3 -3
- package/dist/store/schema/cleo-project/provenance-commits.d.ts +2 -2
- package/dist/store/schema/cleo-project/provenance-rest.d.ts +3 -3
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +6 -6
- package/dist/store/schema/cleo-shared/brain.d.ts +26 -26
- package/dist/store/schema/lifecycle.d.ts +3 -3
- package/dist/store/schema/manifest.d.ts +1 -1
- package/dist/store/schema/memory-schema.d.ts +26 -26
- package/dist/store/schema/nexus-schema.d.ts +696 -2
- package/dist/store/schema/nexus-schema.d.ts.map +1 -1
- package/dist/store/schema/nexus-schema.js +175 -19
- package/dist/store/schema/nexus-schema.js.map +1 -1
- package/dist/store/schema/provenance/releases.d.ts +1 -1
- package/dist/store/schema/skills-schema.d.ts +2 -2
- package/dist/store/schema/tasks.d.ts +5 -5
- package/dist/store/session-binding-schema.d.ts +132 -0
- package/dist/store/session-binding-schema.d.ts.map +1 -0
- package/dist/store/session-binding-schema.js +53 -0
- package/dist/store/session-binding-schema.js.map +1 -0
- package/dist/store/session-store.d.ts +262 -4
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +486 -26
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/skills-db.d.ts +17 -0
- package/dist/store/skills-db.d.ts.map +1 -1
- package/dist/store/skills-db.js +30 -0
- package/dist/store/skills-db.js.map +1 -1
- package/dist/store/snapshot-gate.d.ts +239 -0
- package/dist/store/snapshot-gate.d.ts.map +1 -0
- package/dist/store/snapshot-gate.js +401 -0
- package/dist/store/snapshot-gate.js.map +1 -0
- package/dist/store/split-brain-import.js +2 -2
- package/dist/store/split-brain-import.js.map +1 -1
- package/dist/store/sqlite-backup.d.ts +55 -13
- package/dist/store/sqlite-backup.d.ts.map +1 -1
- package/dist/store/sqlite-backup.js +414 -106
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +15 -0
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +73 -4
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +15 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +75 -5
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/store-write-guard.d.ts +28 -0
- package/dist/store/store-write-guard.d.ts.map +1 -0
- package/dist/store/store-write-guard.js +50 -0
- package/dist/store/store-write-guard.js.map +1 -0
- package/dist/store/table-classification.d.ts +143 -0
- package/dist/store/table-classification.d.ts.map +1 -0
- package/dist/store/table-classification.js +1866 -0
- package/dist/store/table-classification.js.map +1 -0
- package/dist/store/task-version.d.ts +72 -0
- package/dist/store/task-version.d.ts.map +1 -0
- package/dist/store/task-version.js +93 -0
- package/dist/store/task-version.js.map +1 -0
- package/dist/store/tasks-schema.d.ts +1 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +13 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts +10 -3
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +12 -3
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/twin-collapse.d.ts +320 -0
- package/dist/store/twin-collapse.d.ts.map +1 -0
- package/dist/store/twin-collapse.js +931 -0
- package/dist/store/twin-collapse.js.map +1 -0
- package/dist/store/umbrella-data-accessor.d.ts +2 -2
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +2 -2
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +63 -63
- package/dist/store/worktree-isolation-guard.d.ts +46 -24
- package/dist/store/worktree-isolation-guard.d.ts.map +1 -1
- package/dist/store/worktree-isolation-guard.js +71 -50
- package/dist/store/worktree-isolation-guard.js.map +1 -1
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +3 -67
- package/dist/system/backup.js.map +1 -1
- package/dist/system/cleo-link.d.ts +163 -0
- package/dist/system/cleo-link.d.ts.map +1 -0
- package/dist/system/cleo-link.js +246 -0
- package/dist/system/cleo-link.js.map +1 -0
- package/dist/system/dependencies.d.ts.map +1 -1
- package/dist/system/dependencies.js +3 -6
- package/dist/system/dependencies.js.map +1 -1
- package/dist/system/global-delivery.d.ts +205 -0
- package/dist/system/global-delivery.d.ts.map +1 -0
- package/dist/system/global-delivery.js +320 -0
- package/dist/system/global-delivery.js.map +1 -0
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +29 -6
- package/dist/system/health.js.map +1 -1
- package/dist/system/inject-generate.d.ts.map +1 -1
- package/dist/system/inject-generate.js +4 -4
- package/dist/system/inject-generate.js.map +1 -1
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +1 -20
- package/dist/system/project-health.js.map +1 -1
- package/dist/system/runtime.d.ts.map +1 -1
- package/dist/system/runtime.js +2 -12
- package/dist/system/runtime.js.map +1 -1
- package/dist/system/safestop.d.ts.map +1 -1
- package/dist/system/safestop.js +5 -2
- package/dist/system/safestop.js.map +1 -1
- package/dist/system/skill-fixtures.d.ts +200 -0
- package/dist/system/skill-fixtures.d.ts.map +1 -0
- package/dist/system/skill-fixtures.js +331 -0
- package/dist/system/skill-fixtures.js.map +1 -0
- package/dist/task-work/index.d.ts +6 -0
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +6 -3
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +4 -2
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +218 -0
- package/dist/tasks/affected-packages.d.ts.map +1 -0
- package/dist/tasks/affected-packages.js +496 -0
- package/dist/tasks/affected-packages.js.map +1 -0
- package/dist/tasks/affected-scope.d.ts +59 -0
- package/dist/tasks/affected-scope.d.ts.map +1 -0
- package/dist/tasks/affected-scope.js +81 -0
- package/dist/tasks/affected-scope.js.map +1 -0
- package/dist/tasks/analyze.d.ts.map +1 -1
- package/dist/tasks/analyze.js +7 -2
- package/dist/tasks/analyze.js.map +1 -1
- package/dist/tasks/change-set.d.ts +175 -0
- package/dist/tasks/change-set.d.ts.map +1 -0
- package/dist/tasks/change-set.js +753 -0
- package/dist/tasks/change-set.js.map +1 -0
- package/dist/tasks/complete.d.ts +10 -0
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +59 -5
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/component-pr.d.ts +106 -0
- package/dist/tasks/component-pr.d.ts.map +1 -0
- package/dist/tasks/component-pr.js +190 -0
- package/dist/tasks/component-pr.js.map +1 -0
- package/dist/tasks/done-plan.d.ts +123 -0
- package/dist/tasks/done-plan.d.ts.map +1 -0
- package/dist/tasks/done-plan.js +696 -0
- package/dist/tasks/done-plan.js.map +1 -0
- package/dist/tasks/done-record.d.ts +127 -0
- package/dist/tasks/done-record.d.ts.map +1 -0
- package/dist/tasks/done-record.js +302 -0
- package/dist/tasks/done-record.js.map +1 -0
- package/dist/tasks/duplicate-detector.d.ts +68 -11
- package/dist/tasks/duplicate-detector.d.ts.map +1 -1
- package/dist/tasks/duplicate-detector.js +314 -58
- package/dist/tasks/duplicate-detector.js.map +1 -1
- package/dist/tasks/evidence-paths.d.ts +78 -0
- package/dist/tasks/evidence-paths.d.ts.map +1 -0
- package/dist/tasks/evidence-paths.js +150 -0
- package/dist/tasks/evidence-paths.js.map +1 -0
- package/dist/tasks/evidence.d.ts +101 -1
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +278 -31
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts +5 -0
- package/dist/tasks/gate-preview.d.ts.map +1 -1
- package/dist/tasks/gate-preview.js +25 -21
- package/dist/tasks/gate-preview.js.map +1 -1
- package/dist/tasks/gate-result-cache.d.ts +205 -0
- package/dist/tasks/gate-result-cache.d.ts.map +1 -0
- package/dist/tasks/gate-result-cache.js +285 -0
- package/dist/tasks/gate-result-cache.js.map +1 -0
- package/dist/tasks/gate-runner.d.ts +126 -3
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +483 -28
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/session-scope.d.ts +3 -1
- package/dist/tasks/session-scope.d.ts.map +1 -1
- package/dist/tasks/session-scope.js +18 -3
- package/dist/tasks/session-scope.js.map +1 -1
- package/dist/tasks/task-next.d.ts +4 -0
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +32 -28
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/tasks/tool-resolver.d.ts +23 -6
- package/dist/tasks/tool-resolver.d.ts.map +1 -1
- package/dist/tasks/tool-resolver.js +146 -8
- package/dist/tasks/tool-resolver.js.map +1 -1
- package/dist/tasks/tool-vacuity.d.ts +71 -0
- package/dist/tasks/tool-vacuity.d.ts.map +1 -0
- package/dist/tasks/tool-vacuity.js +334 -0
- package/dist/tasks/tool-vacuity.js.map +1 -0
- package/dist/tasks/update.d.ts +8 -0
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +159 -78
- package/dist/tasks/update.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +18 -2
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +20 -2
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +64 -4
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/doctor/index.d.ts +1 -1
- package/dist/validation/doctor/index.d.ts.map +1 -1
- package/dist/validation/doctor/index.js +1 -1
- package/dist/validation/doctor/index.js.map +1 -1
- package/dist/validation/doctor/utils.d.ts.map +1 -1
- package/dist/validation/doctor/utils.js +7 -2
- package/dist/validation/doctor/utils.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +24 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +272 -98
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/validation/validate-ops.d.ts.map +1 -1
- package/dist/validation/validate-ops.js +9 -3
- package/dist/validation/validate-ops.js.map +1 -1
- package/dist/worktree/worktree-adopt.d.ts.map +1 -1
- package/dist/worktree/worktree-adopt.js +5 -2
- package/dist/worktree/worktree-adopt.js.map +1 -1
- package/migrations/drizzle-cleo-global/20260927000000_t12469-project-locations/migration.sql +121 -0
- package/migrations/drizzle-cleo-global/20260927010000_t12543-user-profile-project-scope/migration.sql +23 -0
- package/migrations/drizzle-cleo-global/20260928010000_t12470-location-candidates/migration.sql +72 -0
- package/migrations/drizzle-cleo-global/20260928020000_t12510-nexus-devices/migration.sql +34 -0
- package/migrations/drizzle-cleo-global/20260928030000_t12511-project-git-state/migration.sql +62 -0
- package/migrations/drizzle-cleo-project/20260927000000_t12499-session-terminal-bindings/migration.sql +34 -0
- package/migrations/drizzle-cleo-project/20260928000000_t12500-binding-provider-flag/migration.sql +15 -0
- package/package.json +23 -13
- package/schemas/config.schema.json +54 -0
- package/schemas/project-context.schema.json +51 -3
- package/schemas/project-info.schema.json +28 -1
- package/templates/CLEO-INJECTION.md +40 -332
- package/templates/CLEO-REFERENCE.md +303 -0
- package/templates/workflows/README.md +36 -14
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twin collapses (T12535): keep a prefixed twin in step with its BARE legacy
|
|
3
|
+
* table, which stays authoritative while an older CLEO build still writes it.
|
|
4
|
+
*
|
|
5
|
+
* ## Why
|
|
6
|
+
*
|
|
7
|
+
* The project `cleo.db` carries legacy bare tables beside their prefixed (E6)
|
|
8
|
+
* twins. For `schema_meta` and `sticky_tags` the runtime wrote the bare table
|
|
9
|
+
* while the twin held a frozen exodus copy. This build reads and writes only
|
|
10
|
+
* the twin. The release before it (2026.9.20) is still installed on machines
|
|
11
|
+
* that share project stores and keeps writing the bare tables, so the collapse
|
|
12
|
+
* runs at every open:
|
|
13
|
+
*
|
|
14
|
+
* - **Initial collapse** (no marker yet): check the free space, snapshot the
|
|
15
|
+
* store, then fold the bare table into the twin, bare-authoritative.
|
|
16
|
+
* - **Incremental re-merge** (marker present): carry over only the bare keys
|
|
17
|
+
* (sticky ids) whose hash changed since the last merge. No snapshot. An
|
|
18
|
+
* unchanged bare table costs one read of it and no write.
|
|
19
|
+
*
|
|
20
|
+
* A later release removes the incremental re-merge once no supported build
|
|
21
|
+
* writes the bare tables; slice 3 then drops them after a backup.
|
|
22
|
+
*
|
|
23
|
+
* ## State and provenance
|
|
24
|
+
*
|
|
25
|
+
* The marker row `twin_collapse:<bare table>` in the twin's key/value table
|
|
26
|
+
* holds, per key (`schema_meta`) or per sticky id (`sticky_tags`), a sha256
|
|
27
|
+
* of the BARE value and of the TWIN value as of the last merge. A bare hash
|
|
28
|
+
* that moved means the older build wrote it; a twin hash that moved means this
|
|
29
|
+
* build wrote it.
|
|
30
|
+
*
|
|
31
|
+
* ## Rules
|
|
32
|
+
*
|
|
33
|
+
* The bare row is authoritative for every key. A monotonic counter FIELD is
|
|
34
|
+
* merged on its own: the result is the bare value with that field set to
|
|
35
|
+
* max(bare, twin). A whole twin value never replaces a bare one
|
|
36
|
+
* ({@link mergeSchemaMetaValue}).
|
|
37
|
+
*
|
|
38
|
+
* | `schema_meta` key | Counter field |
|
|
39
|
+
* |---|---|
|
|
40
|
+
* | `task_id_sequence` (`{counter,lastId,checksum}` from `sequence/index.ts`, or a plain number) | `counter` (a plain number takes the max of the two numbers) |
|
|
41
|
+
* | `sqlite_snapshot_gate` (`{generation,prefixes}`, `snapshot-gate.ts`) | `generation` |
|
|
42
|
+
* | `file_meta` (`FileMeta`; `session/engine-ops.ts` bumps `generation` on session start/end/resume) | `generation` |
|
|
43
|
+
* | `backfill:*` (the two `t877` guard keys) | not carried: the lineage re-inserts them into every bare table and nothing reads them |
|
|
44
|
+
* | `twin_collapse*` | not carried: collapse state and failures live only in the twin |
|
|
45
|
+
* | every other key (`schemaVersion`, `version`, `focus_state`, `focus_state:<session>`, `project_meta`, `project`, `parallel_state`, `activeSession`, `reconcile.<task>.release`, any unknown key) | none: the bare value |
|
|
46
|
+
*
|
|
47
|
+
* - **Initial collapse**, when the bare table holds at least one carried key:
|
|
48
|
+
* every bare key is carried under the rules, and a key only the twin holds
|
|
49
|
+
* is a frozen copy and is DROPPED (listed in the receipt, marker and log).
|
|
50
|
+
* The exceptions are `task_id_sequence` and `sqlite_snapshot_gate` (a
|
|
51
|
+
* counter must not move back) and the collapse keys. `file_meta` is dropped
|
|
52
|
+
* only when the bare table lacks it; when both have it the field rule
|
|
53
|
+
* applies. When the bare table holds no carried key (a fresh lineage, e.g.
|
|
54
|
+
* after exodus landed legacy rows in the twin), nothing is dropped.
|
|
55
|
+
* - **Incremental re-merge**, per bare key whose hash changed since the last
|
|
56
|
+
* merge:
|
|
57
|
+
* - twin unchanged since the last merge → the bare value (counter fields
|
|
58
|
+
* maxed);
|
|
59
|
+
* - twin ALSO changed (this build wrote it too) → CONFLICT: the TWIN value
|
|
60
|
+
* wins, recorded in the marker and reported by `cleo doctor` as a warning.
|
|
61
|
+
* A counter key is never a conflict: the twin value is kept with its
|
|
62
|
+
* counter field maxed against the bare one.
|
|
63
|
+
* A key the older build deleted is deleted from the twin unless the twin
|
|
64
|
+
* changed it too (conflict) or it is `task_id_sequence` /
|
|
65
|
+
* `sqlite_snapshot_gate`. A key the bare table never changed is never
|
|
66
|
+
* touched.
|
|
67
|
+
*
|
|
68
|
+
* `sticky_tags` → `brain_sticky_tags` (rows are `(sticky_id, tag)`, both the
|
|
69
|
+
* key, so a collision is two identical rows and one is kept):
|
|
70
|
+
*
|
|
71
|
+
* - Initial collapse, when the bare table has rows: the twin is made equal to
|
|
72
|
+
* the bare set; frozen twin-only rows are dropped and listed. A bare tag whose
|
|
73
|
+
* note no longer exists is not carried (`skipped`; it stays in the bare table).
|
|
74
|
+
* - Incremental re-merge, per sticky id whose bare tag-set hash changed: if
|
|
75
|
+
* the twin's set for that id is unchanged since the last merge, the twin
|
|
76
|
+
* set is made equal to the bare set (additions AND removals propagate);
|
|
77
|
+
* if the twin set changed too, the twin wins and a conflict is recorded.
|
|
78
|
+
* - After every merge the junction is recomputed from each note's
|
|
79
|
+
* `tags_json` ({@link syncStickyJunction}). Both builds write `tags_json`
|
|
80
|
+
* (on the shared `brain_sticky_notes` row) together with their own junction,
|
|
81
|
+
* so it is the one value both see; the junction is derived from it, and the
|
|
82
|
+
* tag filter can never disagree with the displayed tags. A conflict is still
|
|
83
|
+
* recorded for `cleo doctor`.
|
|
84
|
+
*
|
|
85
|
+
* ## Contract
|
|
86
|
+
*
|
|
87
|
+
* 1. **Snapshot first** (initial collapse only, only when the merge changes
|
|
88
|
+
* the twin). The free space is checked before the `VACUUM INTO`. One
|
|
89
|
+
* snapshot covers every pair collapsing in this open, registered as a
|
|
90
|
+
* `migration` backup (`cleo backup list`, rotation).
|
|
91
|
+
* 2. **Atomic.** Each pair merges, verifies and writes its marker in one
|
|
92
|
+
* `BEGIN IMMEDIATE` transaction, the marker re-read under the write lock.
|
|
93
|
+
* Any failure rolls back: both tables byte-identical.
|
|
94
|
+
* 3. **Verified.** Every row the merge decided is re-read and compared before
|
|
95
|
+
* the marker is written; a mismatch rolls back.
|
|
96
|
+
* 4. **The bare table is never written**, with one sanctioned exception:
|
|
97
|
+
* {@link mirrorCounterToBare}. When this build writes a counter key
|
|
98
|
+
* (`task_id_sequence`, `sqlite_snapshot_gate`, `file_meta`) it raises the
|
|
99
|
+
* bare row's counter field to the twin's in the same transaction, and the
|
|
100
|
+
* allocation floors on the bare counter ({@link bareCounterOf}). The
|
|
101
|
+
* 2026.9.20 build still allocates ids from the bare counter; without the
|
|
102
|
+
* mirror, an id one build reserved but had not stored yet could be issued
|
|
103
|
+
* by the other.
|
|
104
|
+
* 5. **Never locked out** (inside a domain bind, `onFailure: 'degrade'`). A
|
|
105
|
+
* failed or impossible collapse does not fail the bind. The failure is
|
|
106
|
+
* recorded (`twin_collapse_failed:<table>`), and the connection is put in a
|
|
107
|
+
* READ-ONLY-FOR-USERS mode: the merged view the collapse would have
|
|
108
|
+
* committed is computed in memory and served from TEMP shadow tables of the
|
|
109
|
+
* same names (unqualified SQL resolves `temp` before `main`), so reads see
|
|
110
|
+
* bare-authoritative data while `main` stays untouched. Mutating operations
|
|
111
|
+
* are refused with `E_TWIN_COLLAPSE_FAILED` by the dispatch write guard
|
|
112
|
+
* ({@link twinCollapseFailureOf}), and the shadows carry TEMP triggers that
|
|
113
|
+
* abort any write into them ({@link SHADOW_WRITE_REFUSED}), so a direct SDK
|
|
114
|
+
* write fails instead of being lost. `cleo doctor twin-collapse --retry`
|
|
115
|
+
* runs the collapse with `onFailure: 'throw'`; on success the shadows and
|
|
116
|
+
* their triggers are dropped.
|
|
117
|
+
*
|
|
118
|
+
* ## Gate 28
|
|
119
|
+
*
|
|
120
|
+
* This module is a sanctioned writer (`scripts/lint-no-raw-table-writes.mjs`
|
|
121
|
+
* SANCTIONED): it runs on the chokepoint handle inside the domain bind, and the
|
|
122
|
+
* accessors import the modules that call it.
|
|
123
|
+
*
|
|
124
|
+
* @module
|
|
125
|
+
* @task T12535
|
|
126
|
+
*/
|
|
127
|
+
import { createHash } from 'node:crypto';
|
|
128
|
+
import { ExitCode } from '@cleocode/contracts';
|
|
129
|
+
import { CleoError } from '../errors.js';
|
|
130
|
+
import { getLogger } from '../logger.js';
|
|
131
|
+
import { planMigrationSnapshot, writeMigrationSnapshot } from './pre-repair-snapshot.js';
|
|
132
|
+
const log = getLogger('twin-collapse');
|
|
133
|
+
/** Prefix of the marker key holding a pair's collapse state. */
|
|
134
|
+
export const TWIN_COLLAPSE_MARKER_PREFIX = 'twin_collapse:';
|
|
135
|
+
/** Prefix of the key recording a pair's last failed collapse. */
|
|
136
|
+
export const TWIN_COLLAPSE_FAILURE_PREFIX = 'twin_collapse_failed:';
|
|
137
|
+
/**
|
|
138
|
+
* The snapshot gate's key (`SNAPSHOT_GATE_META_KEY` in `snapshot-gate.ts`),
|
|
139
|
+
* spelled out: `snapshot-gate.ts` imports this module to mirror its counter,
|
|
140
|
+
* so importing it back would be a cycle.
|
|
141
|
+
*/
|
|
142
|
+
const SNAPSHOT_GATE_KEY = 'sqlite_snapshot_gate';
|
|
143
|
+
/** `schema_meta` keys whose named field is a monotonic counter (merged as max). */
|
|
144
|
+
export const SCHEMA_META_COUNTER_FIELDS = {
|
|
145
|
+
task_id_sequence: 'counter',
|
|
146
|
+
[SNAPSHOT_GATE_KEY]: 'generation',
|
|
147
|
+
file_meta: 'generation',
|
|
148
|
+
};
|
|
149
|
+
/** `schema_meta` keys never deleted from the twin (a counter never moves back). */
|
|
150
|
+
export const SCHEMA_META_NEVER_DELETED = new Set([
|
|
151
|
+
'task_id_sequence',
|
|
152
|
+
SNAPSHOT_GATE_KEY,
|
|
153
|
+
]);
|
|
154
|
+
/** Most conflicts kept in the marker (the doctor warning lists them). */
|
|
155
|
+
const MAX_CONFLICTS = 50;
|
|
156
|
+
const sha = (value) => createHash('sha256').update(value).digest('hex');
|
|
157
|
+
/** Hash of an absent value (distinct from any real value's hash). */
|
|
158
|
+
const ABSENT = sha('\u0000absent');
|
|
159
|
+
function hasMainTable(db, name) {
|
|
160
|
+
return (db.prepare("SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = ?").get(name) !==
|
|
161
|
+
undefined);
|
|
162
|
+
}
|
|
163
|
+
function readKv(db, kvTable, key) {
|
|
164
|
+
return db.prepare(`SELECT value FROM main.${kvTable} WHERE key = ?`).get(key)?.value;
|
|
165
|
+
}
|
|
166
|
+
function writeKv(db, kvTable, key, value) {
|
|
167
|
+
db.prepare(`INSERT INTO main.${kvTable} (key, value) VALUES (?, ?) ` +
|
|
168
|
+
'ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);
|
|
169
|
+
}
|
|
170
|
+
function sameHashes(a, b) {
|
|
171
|
+
const ka = Object.keys(a);
|
|
172
|
+
return ka.length === Object.keys(b).length && ka.every((k) => a[k] === b[k]);
|
|
173
|
+
}
|
|
174
|
+
/** Parse a marker row; a pre-release marker (no hashes) yields `hashes: null`. */
|
|
175
|
+
function readState(db, pair) {
|
|
176
|
+
const raw = readKv(db, pair.kvTable, `${TWIN_COLLAPSE_MARKER_PREFIX}${pair.table}`);
|
|
177
|
+
if (raw === undefined)
|
|
178
|
+
return undefined;
|
|
179
|
+
let parsed = {};
|
|
180
|
+
try {
|
|
181
|
+
parsed = JSON.parse(raw);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
parsed = {};
|
|
185
|
+
}
|
|
186
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
187
|
+
return {
|
|
188
|
+
version: 3,
|
|
189
|
+
task: 'T12535',
|
|
190
|
+
collapsedAt: str(parsed.collapsedAt),
|
|
191
|
+
lastMergedAt: str(parsed.lastMergedAt),
|
|
192
|
+
snapshot: typeof parsed.snapshot === 'string' ? parsed.snapshot : null,
|
|
193
|
+
hashes: parsed.version === 3 && parsed.hashes ? parsed.hashes : null,
|
|
194
|
+
dropped: Array.isArray(parsed.dropped) ? parsed.dropped : [],
|
|
195
|
+
conflicts: Array.isArray(parsed.conflicts) ? parsed.conflicts : [],
|
|
196
|
+
conflictsAt: typeof parsed.conflictsAt === 'string' ? parsed.conflictsAt : null,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Whether the bare side moved since the stored hashes (or no usable state exists). */
|
|
200
|
+
function bareChanged(db, pair, state) {
|
|
201
|
+
return !state?.hashes || !sameHashes(state.hashes.bare, pair.bareHashes(db));
|
|
202
|
+
}
|
|
203
|
+
/** Hashes of the last merge; a pre-release marker treats the current twin as unchanged. */
|
|
204
|
+
function lastHashes(db, pair, state) {
|
|
205
|
+
return state.hashes ?? { bare: {}, twin: pair.twinHashes(db) };
|
|
206
|
+
}
|
|
207
|
+
// ── schema_meta ──────────────────────────────────────────────────────────────
|
|
208
|
+
function parseJson(value) {
|
|
209
|
+
try {
|
|
210
|
+
return JSON.parse(value);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** A counter field's value, whether the whole value is a number or an object holding it. */
|
|
217
|
+
function counterOf(value, field) {
|
|
218
|
+
const parsed = parseJson(value);
|
|
219
|
+
if (typeof parsed === 'number' && Number.isFinite(parsed))
|
|
220
|
+
return parsed;
|
|
221
|
+
if (parsed === null || typeof parsed !== 'object')
|
|
222
|
+
return undefined;
|
|
223
|
+
const n = parsed[field];
|
|
224
|
+
return typeof n === 'number' && Number.isFinite(n) ? n : undefined;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `base` with its counter field raised to the other value's counter when that
|
|
228
|
+
* is larger. `base` is returned verbatim otherwise, and when it has no
|
|
229
|
+
* counter to raise (not a number, not an object holding the field).
|
|
230
|
+
*/
|
|
231
|
+
function withMaxCounter(base, other, field) {
|
|
232
|
+
if (other === undefined)
|
|
233
|
+
return base;
|
|
234
|
+
const b = counterOf(base, field);
|
|
235
|
+
const o = counterOf(other, field);
|
|
236
|
+
if (b === undefined || o === undefined || o <= b)
|
|
237
|
+
return base;
|
|
238
|
+
const parsed = parseJson(base);
|
|
239
|
+
if (typeof parsed === 'number')
|
|
240
|
+
return String(o);
|
|
241
|
+
return JSON.stringify({ ...parsed, [field]: o });
|
|
242
|
+
}
|
|
243
|
+
/** Whether a `schema_meta` key is carried at all. */
|
|
244
|
+
function carried(key) {
|
|
245
|
+
return !key.startsWith('backfill:') && !key.startsWith('twin_collapse');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The value a `schema_meta` key gets when the bare side is authoritative (the
|
|
249
|
+
* rule table is in the module comment).
|
|
250
|
+
*
|
|
251
|
+
* @param key - The key.
|
|
252
|
+
* @param bare - Its value in the bare table.
|
|
253
|
+
* @param twin - Its value in the twin, or `undefined` when absent.
|
|
254
|
+
* @returns The value to keep, or `null` when the key is not carried.
|
|
255
|
+
* @task T12535
|
|
256
|
+
*/
|
|
257
|
+
export function mergeSchemaMetaValue(key, bare, twin) {
|
|
258
|
+
if (!carried(key))
|
|
259
|
+
return null;
|
|
260
|
+
const field = SCHEMA_META_COUNTER_FIELDS[key];
|
|
261
|
+
return field === undefined ? bare : withMaxCounter(bare, twin, field);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Raise the BARE `schema_meta` row of a counter key to the twin's counter,
|
|
265
|
+
* right after this build wrote the twin row, on the same connection (so inside
|
|
266
|
+
* the writer's transaction).
|
|
267
|
+
*
|
|
268
|
+
* The one sanctioned write to a bare table (T12535): the 2026.9.20 build still
|
|
269
|
+
* allocates task ids from the bare `task_id_sequence` and reads the bare
|
|
270
|
+
* snapshot-gate and `file_meta` generations. Without this, an id this build
|
|
271
|
+
* reserved but has not stored yet (the counter is advanced before the task
|
|
272
|
+
* row is inserted) could be issued again by the older build. Only the counter
|
|
273
|
+
* field moves (the bare row keeps its other fields, see
|
|
274
|
+
* {@link mergeSchemaMetaValue}); a missing bare row gets the twin value. A
|
|
275
|
+
* non-counter key, or a store without the bare table, is left alone. The
|
|
276
|
+
* collapse treats the resulting bare change like any other: a counter key is
|
|
277
|
+
* never a conflict.
|
|
278
|
+
*
|
|
279
|
+
* @param db - The project `cleo.db` connection that wrote the twin row.
|
|
280
|
+
* @param key - The `schema_meta` key just written.
|
|
281
|
+
* @task T12535
|
|
282
|
+
*/
|
|
283
|
+
export function mirrorCounterToBare(db, key) {
|
|
284
|
+
if (SCHEMA_META_COUNTER_FIELDS[key] === undefined)
|
|
285
|
+
return;
|
|
286
|
+
if (!hasMainTable(db, 'schema_meta'))
|
|
287
|
+
return;
|
|
288
|
+
const twin = readKv(db, 'tasks_schema_meta', key);
|
|
289
|
+
if (twin === undefined)
|
|
290
|
+
return;
|
|
291
|
+
const bare = readKv(db, 'schema_meta', key);
|
|
292
|
+
const next = bare === undefined ? twin : mergeSchemaMetaValue(key, bare, twin);
|
|
293
|
+
if (next === null || next === bare)
|
|
294
|
+
return;
|
|
295
|
+
writeKv(db, 'schema_meta', key, next);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* The counter of a key's BARE `schema_meta` row (the older build's), or 0.
|
|
299
|
+
* This build's allocation floors on it, so an id the older build reserved but
|
|
300
|
+
* has not stored yet is never issued here either.
|
|
301
|
+
*
|
|
302
|
+
* @param db - The project `cleo.db` connection.
|
|
303
|
+
* @param key - A counter key.
|
|
304
|
+
* @returns The bare counter field, or 0 when absent.
|
|
305
|
+
* @task T12535
|
|
306
|
+
*/
|
|
307
|
+
export function bareCounterOf(db, key) {
|
|
308
|
+
const name = SCHEMA_META_COUNTER_FIELDS[key];
|
|
309
|
+
if (name === undefined || !hasMainTable(db, 'schema_meta'))
|
|
310
|
+
return 0;
|
|
311
|
+
const bare = readKv(db, 'schema_meta', key);
|
|
312
|
+
return (bare === undefined ? undefined : counterOf(bare, name)) ?? 0;
|
|
313
|
+
}
|
|
314
|
+
function kvRows(db, table, schema = 'main') {
|
|
315
|
+
const rows = db.prepare(`SELECT key, value FROM ${schema}.${table}`).all();
|
|
316
|
+
return new Map(rows.map((r) => [r.key, r.value]));
|
|
317
|
+
}
|
|
318
|
+
/** Carried rows of a `schema_meta` table. */
|
|
319
|
+
function carriedRows(db, table) {
|
|
320
|
+
const all = kvRows(db, table);
|
|
321
|
+
for (const key of [...all.keys()])
|
|
322
|
+
if (!carried(key))
|
|
323
|
+
all.delete(key);
|
|
324
|
+
return all;
|
|
325
|
+
}
|
|
326
|
+
const hashMap = (m) => Object.fromEntries([...m].map(([k, v]) => [k, sha(v)]));
|
|
327
|
+
const emptyPlan = () => ({ set: new Map(), del: [], dropped: [], conflicts: [], skipped: 0 });
|
|
328
|
+
function planSchemaMeta(db, state) {
|
|
329
|
+
const plan = emptyPlan();
|
|
330
|
+
const bare = carriedRows(db, 'schema_meta');
|
|
331
|
+
const twin = carriedRows(db, 'tasks_schema_meta');
|
|
332
|
+
const want = (key, value) => {
|
|
333
|
+
if (value !== null && value !== twin.get(key))
|
|
334
|
+
plan.set.set(key, value);
|
|
335
|
+
};
|
|
336
|
+
if (state === undefined) {
|
|
337
|
+
if (bare.size === 0)
|
|
338
|
+
return plan;
|
|
339
|
+
for (const [key, value] of bare)
|
|
340
|
+
want(key, mergeSchemaMetaValue(key, value, twin.get(key)));
|
|
341
|
+
for (const key of [...twin.keys()].sort()) {
|
|
342
|
+
if (!bare.has(key) && !SCHEMA_META_NEVER_DELETED.has(key)) {
|
|
343
|
+
plan.del.push(key);
|
|
344
|
+
plan.dropped.push(key);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return plan;
|
|
348
|
+
}
|
|
349
|
+
const last = lastHashes(db, SCHEMA_META, state);
|
|
350
|
+
const twinMoved = (key) => {
|
|
351
|
+
const now = twin.has(key) ? sha(twin.get(key)) : ABSENT;
|
|
352
|
+
return now !== (last.twin[key] ?? ABSENT);
|
|
353
|
+
};
|
|
354
|
+
for (const key of [...bare.keys()].sort()) {
|
|
355
|
+
const value = bare.get(key);
|
|
356
|
+
if (last.bare[key] === sha(value))
|
|
357
|
+
continue;
|
|
358
|
+
const field = SCHEMA_META_COUNTER_FIELDS[key];
|
|
359
|
+
const current = twin.get(key);
|
|
360
|
+
if (field !== undefined) {
|
|
361
|
+
// A counter is never a conflict: keep whichever side is authoritative
|
|
362
|
+
// for the rest of the value, and max the counter field against the other.
|
|
363
|
+
const base = twinMoved(key) && current !== undefined ? current : value;
|
|
364
|
+
want(key, withMaxCounter(base, base === value ? current : value, field));
|
|
365
|
+
}
|
|
366
|
+
else if (twinMoved(key)) {
|
|
367
|
+
plan.conflicts.push(key);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
want(key, value);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const key of Object.keys(last.bare).sort()) {
|
|
374
|
+
if (bare.has(key) || SCHEMA_META_NEVER_DELETED.has(key) || !twin.has(key))
|
|
375
|
+
continue;
|
|
376
|
+
if (twinMoved(key))
|
|
377
|
+
plan.conflicts.push(key);
|
|
378
|
+
else
|
|
379
|
+
plan.del.push(key);
|
|
380
|
+
}
|
|
381
|
+
return plan;
|
|
382
|
+
}
|
|
383
|
+
function applyKv(db, schema, table, plan) {
|
|
384
|
+
const before = kvRows(db, table, schema);
|
|
385
|
+
const upsert = db.prepare(`INSERT INTO ${schema}.${table} (key, value) VALUES (?, ?) ` +
|
|
386
|
+
'ON CONFLICT(key) DO UPDATE SET value = excluded.value');
|
|
387
|
+
const del = db.prepare(`DELETE FROM ${schema}.${table} WHERE key = ?`);
|
|
388
|
+
let inserted = 0;
|
|
389
|
+
let replaced = 0;
|
|
390
|
+
let deleted = 0;
|
|
391
|
+
for (const [key, value] of plan.set) {
|
|
392
|
+
upsert.run(key, value);
|
|
393
|
+
if (before.has(key))
|
|
394
|
+
replaced++;
|
|
395
|
+
else
|
|
396
|
+
inserted++;
|
|
397
|
+
}
|
|
398
|
+
for (const key of plan.del)
|
|
399
|
+
deleted += Number(del.run(key).changes);
|
|
400
|
+
return { inserted, replaced, deleted };
|
|
401
|
+
}
|
|
402
|
+
const SCHEMA_META = {
|
|
403
|
+
table: 'schema_meta',
|
|
404
|
+
twin: 'tasks_schema_meta',
|
|
405
|
+
kvTable: 'tasks_schema_meta',
|
|
406
|
+
tables: ['schema_meta', 'tasks_schema_meta'],
|
|
407
|
+
bareHashes: (db) => hashMap(carriedRows(db, 'schema_meta')),
|
|
408
|
+
twinHashes: (db) => hashMap(carriedRows(db, 'tasks_schema_meta')),
|
|
409
|
+
plan: planSchemaMeta,
|
|
410
|
+
apply(db, plan) {
|
|
411
|
+
const counts = applyKv(db, 'main', 'tasks_schema_meta', plan);
|
|
412
|
+
for (const [key, value] of plan.set) {
|
|
413
|
+
if (readKv(db, 'tasks_schema_meta', key) !== value)
|
|
414
|
+
throw new Error(`schema_meta collapse did not verify for key ${JSON.stringify(key)}`);
|
|
415
|
+
}
|
|
416
|
+
for (const key of plan.del) {
|
|
417
|
+
if (readKv(db, 'tasks_schema_meta', key) !== undefined)
|
|
418
|
+
throw new Error(`schema_meta collapse did not verify the removal of ${JSON.stringify(key)}`);
|
|
419
|
+
}
|
|
420
|
+
return counts;
|
|
421
|
+
},
|
|
422
|
+
shadow(db, plan) {
|
|
423
|
+
db.exec('CREATE TEMP TABLE IF NOT EXISTS tasks_schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
|
424
|
+
db.exec('DELETE FROM temp.tasks_schema_meta');
|
|
425
|
+
db.exec('INSERT INTO temp.tasks_schema_meta (key, value) SELECT key, value FROM main.tasks_schema_meta');
|
|
426
|
+
applyKv(db, 'temp', 'tasks_schema_meta', plan);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
// ── sticky_tags ──────────────────────────────────────────────────────────────
|
|
430
|
+
/** Tag sets by sticky id (tags sorted). */
|
|
431
|
+
function stickySets(db, table) {
|
|
432
|
+
const rows = db
|
|
433
|
+
.prepare(`SELECT sticky_id, tag FROM main.${table} ORDER BY sticky_id, tag`)
|
|
434
|
+
.all();
|
|
435
|
+
const out = new Map();
|
|
436
|
+
for (const r of rows) {
|
|
437
|
+
const tags = out.get(r.sticky_id) ?? [];
|
|
438
|
+
tags.push(r.tag);
|
|
439
|
+
out.set(r.sticky_id, tags);
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
const hashSets = (m) => Object.fromEntries([...m].map(([k, tags]) => [k, sha(JSON.stringify(tags))]));
|
|
444
|
+
const EMPTY_SET = sha(JSON.stringify([]));
|
|
445
|
+
function planSticky(db, state) {
|
|
446
|
+
const plan = emptyPlan();
|
|
447
|
+
const bare = stickySets(db, 'sticky_tags');
|
|
448
|
+
const twin = stickySets(db, 'brain_sticky_tags');
|
|
449
|
+
const noteExists = db.prepare('SELECT 1 FROM main.brain_sticky_notes WHERE id = ?');
|
|
450
|
+
/** Make the twin's set for `id` equal the bare set. */
|
|
451
|
+
const mirror = (id, dropping) => {
|
|
452
|
+
const b = new Set(bare.get(id) ?? []);
|
|
453
|
+
const t = new Set(twin.get(id) ?? []);
|
|
454
|
+
const alive = b.size === 0 || noteExists.get(id) !== undefined;
|
|
455
|
+
for (const tag of b) {
|
|
456
|
+
if (!alive)
|
|
457
|
+
plan.skipped++;
|
|
458
|
+
else if (!t.has(tag))
|
|
459
|
+
plan.set.set(`${id}\t${tag}`, '');
|
|
460
|
+
}
|
|
461
|
+
for (const tag of t) {
|
|
462
|
+
if (b.has(tag))
|
|
463
|
+
continue;
|
|
464
|
+
plan.del.push(`${id}\t${tag}`);
|
|
465
|
+
if (dropping)
|
|
466
|
+
plan.dropped.push(`${id}\t${tag}`);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
if (state === undefined) {
|
|
470
|
+
if (bare.size === 0)
|
|
471
|
+
return plan;
|
|
472
|
+
for (const id of [...new Set([...bare.keys(), ...twin.keys()])].sort())
|
|
473
|
+
mirror(id, true);
|
|
474
|
+
return plan;
|
|
475
|
+
}
|
|
476
|
+
const last = lastHashes(db, STICKY_TAGS, state);
|
|
477
|
+
const ids = new Set([...bare.keys(), ...Object.keys(last.bare)]);
|
|
478
|
+
for (const id of [...ids].sort()) {
|
|
479
|
+
const bareNow = bare.has(id) ? sha(JSON.stringify(bare.get(id))) : EMPTY_SET;
|
|
480
|
+
if (bareNow === (last.bare[id] ?? EMPTY_SET))
|
|
481
|
+
continue;
|
|
482
|
+
const twinNow = twin.has(id) ? sha(JSON.stringify(twin.get(id))) : EMPTY_SET;
|
|
483
|
+
if (twinNow !== (last.twin[id] ?? EMPTY_SET))
|
|
484
|
+
plan.conflicts.push(id);
|
|
485
|
+
else
|
|
486
|
+
mirror(id, false);
|
|
487
|
+
}
|
|
488
|
+
return plan;
|
|
489
|
+
}
|
|
490
|
+
function applyStickyRows(db, schema, plan) {
|
|
491
|
+
const insert = db.prepare(`INSERT OR IGNORE INTO ${schema}.brain_sticky_tags (sticky_id, tag) VALUES (?, ?)`);
|
|
492
|
+
const del = db.prepare(`DELETE FROM ${schema}.brain_sticky_tags WHERE sticky_id = ? AND tag = ?`);
|
|
493
|
+
let inserted = 0;
|
|
494
|
+
let deleted = 0;
|
|
495
|
+
for (const row of plan.set.keys()) {
|
|
496
|
+
const [id, tag] = row.split('\t');
|
|
497
|
+
inserted += Number(insert.run(id, tag).changes);
|
|
498
|
+
}
|
|
499
|
+
for (const row of plan.del) {
|
|
500
|
+
const [id, tag] = row.split('\t');
|
|
501
|
+
deleted += Number(del.run(id, tag).changes);
|
|
502
|
+
}
|
|
503
|
+
return { inserted, replaced: 0, deleted };
|
|
504
|
+
}
|
|
505
|
+
/** A note's tags as the accessor reads `tags_json` (strings, non-empty, deduplicated). */
|
|
506
|
+
function tagsOf(tagsJson) {
|
|
507
|
+
if (!tagsJson)
|
|
508
|
+
return [];
|
|
509
|
+
try {
|
|
510
|
+
const parsed = JSON.parse(tagsJson);
|
|
511
|
+
if (!Array.isArray(parsed))
|
|
512
|
+
return [];
|
|
513
|
+
return [...new Set(parsed.filter((t) => typeof t === 'string' && t.length > 0))];
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
return [];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Make the junction in `schema` equal every note's `tags_json`.
|
|
521
|
+
*
|
|
522
|
+
* `tags_json` lives on the shared `brain_sticky_notes` row, which both builds
|
|
523
|
+
* write together with their own junction; the junction is derived from it.
|
|
524
|
+
* Recomputing it after every merge keeps the tag filter (the junction) and the
|
|
525
|
+
* displayed tags (`tags_json`) from disagreeing when both builds retag one
|
|
526
|
+
* note (the merge's twin-wins rule would otherwise keep a stale set).
|
|
527
|
+
*
|
|
528
|
+
* @returns Junction rows added and removed.
|
|
529
|
+
*/
|
|
530
|
+
function syncStickyJunction(db, schema) {
|
|
531
|
+
const notes = db.prepare('SELECT id, tags_json FROM main.brain_sticky_notes').all();
|
|
532
|
+
const have = new Map();
|
|
533
|
+
for (const r of db
|
|
534
|
+
.prepare(`SELECT sticky_id, tag FROM ${schema}.brain_sticky_tags`)
|
|
535
|
+
.all()) {
|
|
536
|
+
const set = have.get(r.sticky_id) ?? new Set();
|
|
537
|
+
set.add(r.tag);
|
|
538
|
+
have.set(r.sticky_id, set);
|
|
539
|
+
}
|
|
540
|
+
const insert = db.prepare(`INSERT OR IGNORE INTO ${schema}.brain_sticky_tags (sticky_id, tag) VALUES (?, ?)`);
|
|
541
|
+
const del = db.prepare(`DELETE FROM ${schema}.brain_sticky_tags WHERE sticky_id = ? AND tag = ?`);
|
|
542
|
+
let inserted = 0;
|
|
543
|
+
let deleted = 0;
|
|
544
|
+
for (const note of notes) {
|
|
545
|
+
const want = new Set(tagsOf(note.tags_json));
|
|
546
|
+
const current = have.get(note.id) ?? new Set();
|
|
547
|
+
for (const tag of want)
|
|
548
|
+
if (!current.has(tag))
|
|
549
|
+
inserted += Number(insert.run(note.id, tag).changes);
|
|
550
|
+
for (const tag of current)
|
|
551
|
+
if (!want.has(tag))
|
|
552
|
+
deleted += Number(del.run(note.id, tag).changes);
|
|
553
|
+
}
|
|
554
|
+
return { inserted, deleted };
|
|
555
|
+
}
|
|
556
|
+
const STICKY_TAGS = {
|
|
557
|
+
table: 'sticky_tags',
|
|
558
|
+
twin: 'brain_sticky_tags',
|
|
559
|
+
kvTable: 'brain_schema_meta',
|
|
560
|
+
tables: ['sticky_tags', 'brain_sticky_tags', 'brain_sticky_notes', 'brain_schema_meta'],
|
|
561
|
+
bareHashes: (db) => hashSets(stickySets(db, 'sticky_tags')),
|
|
562
|
+
twinHashes: (db) => hashSets(stickySets(db, 'brain_sticky_tags')),
|
|
563
|
+
plan: planSticky,
|
|
564
|
+
apply(db, plan) {
|
|
565
|
+
const counts = applyStickyRows(db, 'main', plan);
|
|
566
|
+
const present = db.prepare('SELECT 1 FROM main.brain_sticky_tags WHERE sticky_id = ? AND tag = ?');
|
|
567
|
+
const has = (row) => {
|
|
568
|
+
const [id, tag] = row.split('\t');
|
|
569
|
+
return present.get(id, tag) !== undefined;
|
|
570
|
+
};
|
|
571
|
+
const missing = [...plan.set.keys()].filter((r) => !has(r)).length;
|
|
572
|
+
const lingering = plan.del.filter(has).length;
|
|
573
|
+
if (missing > 0 || lingering > 0)
|
|
574
|
+
throw new Error(`sticky_tags collapse did not verify: ${missing} missing, ${lingering} not removed`);
|
|
575
|
+
const synced = syncStickyJunction(db, 'main');
|
|
576
|
+
return {
|
|
577
|
+
inserted: counts.inserted + synced.inserted,
|
|
578
|
+
replaced: 0,
|
|
579
|
+
deleted: counts.deleted + synced.deleted,
|
|
580
|
+
};
|
|
581
|
+
},
|
|
582
|
+
shadow(db, plan) {
|
|
583
|
+
db.exec('CREATE TEMP TABLE IF NOT EXISTS brain_sticky_tags (sticky_id TEXT NOT NULL, tag TEXT NOT NULL, PRIMARY KEY (sticky_id, tag))');
|
|
584
|
+
db.exec('DELETE FROM temp.brain_sticky_tags');
|
|
585
|
+
db.exec('INSERT INTO temp.brain_sticky_tags (sticky_id, tag) SELECT sticky_id, tag FROM main.brain_sticky_tags');
|
|
586
|
+
applyStickyRows(db, 'temp', plan);
|
|
587
|
+
syncStickyJunction(db, 'temp');
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
/** The pairs this build collapses, in order. */
|
|
591
|
+
const PAIRS = [SCHEMA_META, STICKY_TAGS];
|
|
592
|
+
/** Connections serving TEMP shadows because their collapse failed. */
|
|
593
|
+
const degraded = new WeakMap();
|
|
594
|
+
/**
|
|
595
|
+
* The failure a connection is degraded by, if any: reads on it are served from
|
|
596
|
+
* TEMP shadows and writes must be refused (the dispatch write guard does).
|
|
597
|
+
*
|
|
598
|
+
* @param db - A project `cleo.db` connection.
|
|
599
|
+
* @returns The failure, or `undefined` when the store is fully collapsed.
|
|
600
|
+
* @task T12535
|
|
601
|
+
*/
|
|
602
|
+
export function twinCollapseFailureOf(db) {
|
|
603
|
+
return degraded.get(db);
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Refuse a write before its first statement when the store is degraded by a
|
|
607
|
+
* failed twin collapse. The write accessors of the collapsed tables
|
|
608
|
+
* (`tasks_schema_meta`, and sticky notes and tags) call it at entry, so a
|
|
609
|
+
* degraded store never takes a partial write (e.g. a sticky's `tags_json`
|
|
610
|
+
* without its tag rows). The shadows' TEMP triggers stay as the backstop.
|
|
611
|
+
*
|
|
612
|
+
* @param handle - The connection the write would use (a `DatabaseSync`, or a
|
|
613
|
+
* drizzle instance's `$client`); anything else is not checked.
|
|
614
|
+
* @throws {CleoError} `E_TWIN_COLLAPSE_FAILED` when the connection is degraded.
|
|
615
|
+
* @task T12535
|
|
616
|
+
*/
|
|
617
|
+
export function assertTwinCollapseWritable(handle) {
|
|
618
|
+
if (typeof handle !== 'object' || handle === null)
|
|
619
|
+
return;
|
|
620
|
+
const failure = degraded.get(handle);
|
|
621
|
+
if (failure)
|
|
622
|
+
throw twinCollapseError(failure);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Build the `E_TWIN_COLLAPSE_FAILED` error for a failure.
|
|
626
|
+
*
|
|
627
|
+
* @param failure - The failure.
|
|
628
|
+
* @param cause - The underlying error, when there is one.
|
|
629
|
+
* @returns The error.
|
|
630
|
+
* @task T12535
|
|
631
|
+
*/
|
|
632
|
+
export function twinCollapseError(failure, cause) {
|
|
633
|
+
const where = !failure.snapshotPath
|
|
634
|
+
? ''
|
|
635
|
+
: failure.snapshotWritten
|
|
636
|
+
? ` Snapshot: ${failure.snapshotPath}.`
|
|
637
|
+
: ` Snapshot would be written to ${failure.snapshotPath}.`;
|
|
638
|
+
const space = failure.requiredBytes > 0
|
|
639
|
+
? ` The snapshot needs ${failure.requiredBytes} bytes free` +
|
|
640
|
+
(failure.availableBytes === null ? '.' : ` (${failure.availableBytes} free).`)
|
|
641
|
+
: '';
|
|
642
|
+
return new CleoError(ExitCode.TWIN_COLLAPSE_FAILED, `Twin collapse of ${failure.tables.join(', ')} failed (both tables unchanged; reads still ` +
|
|
643
|
+
`work, writes are refused): ${failure.cause}.${where}${space} Run 'cleo doctor twin-collapse'.`, {
|
|
644
|
+
fix: "Run 'cleo doctor twin-collapse' for details, clear the cause (free the space, make " +
|
|
645
|
+
".cleo/backups/sqlite writable), then 'cleo doctor twin-collapse --retry'.",
|
|
646
|
+
details: { field: 'twinCollapse', ...failure },
|
|
647
|
+
cause,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/** Record a failure outside the rolled-back transaction; best effort. */
|
|
651
|
+
function recordFailure(db, pairs, failure) {
|
|
652
|
+
for (const pair of pairs) {
|
|
653
|
+
try {
|
|
654
|
+
writeKv(db, pair.kvTable, `${TWIN_COLLAPSE_FAILURE_PREFIX}${pair.table}`, JSON.stringify(failure));
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
// The store itself may be the problem (disk full, read-only); the
|
|
658
|
+
// failure is still carried by the degraded state and the error.
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/** Message a write into a sealed shadow aborts with. */
|
|
663
|
+
export const SHADOW_WRITE_REFUSED = 'E_TWIN_COLLAPSE_FAILED: store is read-only until the twin collapse succeeds; run cleo doctor twin-collapse';
|
|
664
|
+
const SHADOW_WRITE_OPS = ['INSERT', 'UPDATE', 'DELETE'];
|
|
665
|
+
/** Remove a shadow's write-refusing triggers (before it is rebuilt). */
|
|
666
|
+
function unsealShadow(db, twin) {
|
|
667
|
+
for (const op of SHADOW_WRITE_OPS) {
|
|
668
|
+
db.exec(`DROP TRIGGER IF EXISTS temp.${twin}_refuse_${op.toLowerCase()}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Make a shadow refuse writes. A write through the store accessors that
|
|
673
|
+
* bypasses the dispatch write guard (a direct SDK call) then fails with
|
|
674
|
+
* {@link SHADOW_WRITE_REFUSED} instead of landing in the shadow and being lost
|
|
675
|
+
* when the connection closes. The collapse itself writes `main.<twin>`
|
|
676
|
+
* (schema-qualified), which these TEMP triggers do not cover.
|
|
677
|
+
*/
|
|
678
|
+
function sealShadow(db, twin) {
|
|
679
|
+
for (const op of SHADOW_WRITE_OPS) {
|
|
680
|
+
db.exec(`CREATE TEMP TRIGGER IF NOT EXISTS ${twin}_refuse_${op.toLowerCase()} BEFORE ${op} ON temp.${twin} ` +
|
|
681
|
+
`BEGIN SELECT RAISE(ABORT, '${SHADOW_WRITE_REFUSED}'); END`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/** Drop the TEMP shadows (and their triggers) of a connection that is no longer degraded. */
|
|
685
|
+
function clearShadows(db) {
|
|
686
|
+
for (const pair of PAIRS)
|
|
687
|
+
db.exec(`DROP TABLE IF EXISTS temp.${pair.twin}`);
|
|
688
|
+
degraded.delete(db);
|
|
689
|
+
}
|
|
690
|
+
function receipt(pair, status, snapshotPath) {
|
|
691
|
+
return {
|
|
692
|
+
table: pair.table,
|
|
693
|
+
twin: pair.twin,
|
|
694
|
+
status,
|
|
695
|
+
snapshotPath,
|
|
696
|
+
inserted: 0,
|
|
697
|
+
replaced: 0,
|
|
698
|
+
deleted: 0,
|
|
699
|
+
skipped: 0,
|
|
700
|
+
dropped: [],
|
|
701
|
+
conflicts: [],
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
/** Run one pair's merge in its own transaction. */
|
|
705
|
+
function collapsePair(db, pair, snapshotPath) {
|
|
706
|
+
db.exec('BEGIN IMMEDIATE');
|
|
707
|
+
try {
|
|
708
|
+
// Re-read under the write lock: another process may have merged since.
|
|
709
|
+
const state = readState(db, pair);
|
|
710
|
+
if (!bareChanged(db, pair, state)) {
|
|
711
|
+
db.exec('ROLLBACK');
|
|
712
|
+
return receipt(pair, 'unchanged', state?.snapshot ?? null);
|
|
713
|
+
}
|
|
714
|
+
const plan = pair.plan(db, state);
|
|
715
|
+
if (state === undefined && snapshotPath === null && (plan.set.size > 0 || plan.del.length > 0))
|
|
716
|
+
throw new Error(`bare ${pair.table} changed after the snapshot decision; retry the open`);
|
|
717
|
+
const counts = pair.apply(db, plan);
|
|
718
|
+
const now = new Date().toISOString();
|
|
719
|
+
const next = {
|
|
720
|
+
version: 3,
|
|
721
|
+
task: 'T12535',
|
|
722
|
+
collapsedAt: state?.collapsedAt || now,
|
|
723
|
+
lastMergedAt: now,
|
|
724
|
+
snapshot: state?.snapshot ?? snapshotPath,
|
|
725
|
+
hashes: { bare: pair.bareHashes(db), twin: pair.twinHashes(db) },
|
|
726
|
+
dropped: state === undefined ? plan.dropped : state.dropped,
|
|
727
|
+
conflicts: plan.conflicts.slice(0, MAX_CONFLICTS),
|
|
728
|
+
conflictsAt: plan.conflicts.length > 0 ? now : null,
|
|
729
|
+
};
|
|
730
|
+
writeKv(db, pair.kvTable, `${TWIN_COLLAPSE_MARKER_PREFIX}${pair.table}`, JSON.stringify(next));
|
|
731
|
+
db.prepare(`DELETE FROM main.${pair.kvTable} WHERE key = ?`).run(`${TWIN_COLLAPSE_FAILURE_PREFIX}${pair.table}`);
|
|
732
|
+
db.exec('COMMIT');
|
|
733
|
+
const done = {
|
|
734
|
+
...receipt(pair, state === undefined ? 'initial' : 'incremental', next.snapshot),
|
|
735
|
+
...counts,
|
|
736
|
+
skipped: plan.skipped,
|
|
737
|
+
dropped: plan.dropped,
|
|
738
|
+
conflicts: plan.conflicts,
|
|
739
|
+
};
|
|
740
|
+
if (counts.inserted + counts.replaced + counts.deleted > 0 || plan.conflicts.length > 0)
|
|
741
|
+
log.warn(done, `carried bare ${pair.table} into ${pair.twin} (${done.status}, T12535)`);
|
|
742
|
+
return done;
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
if (db.isTransaction)
|
|
746
|
+
db.exec('ROLLBACK');
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Collapse every bare/twin pair present in the store: the initial collapse
|
|
752
|
+
* (snapshot first) where no marker exists yet, an incremental re-merge of
|
|
753
|
+
* changed bare keys where one does.
|
|
754
|
+
*
|
|
755
|
+
* @param nativeDb - The project `cleo.db` connection, outside any transaction.
|
|
756
|
+
* @param dbPath - Its file path (locates `.cleo/backups/sqlite/`).
|
|
757
|
+
* @param options - Failure handling.
|
|
758
|
+
* @returns One receipt per pair.
|
|
759
|
+
* @throws {CleoError} `E_TWIN_COLLAPSE_FAILED` with `onFailure: 'throw'`.
|
|
760
|
+
* @task T12535
|
|
761
|
+
*/
|
|
762
|
+
export function collapseTwinTables(nativeDb, dbPath, options = {}) {
|
|
763
|
+
const onFailure = options.onFailure ?? 'throw';
|
|
764
|
+
// A bind on a connection already degraded in this process does not retry:
|
|
765
|
+
// the cause is outside CLEO; `cleo doctor twin-collapse --retry` retries.
|
|
766
|
+
if (onFailure === 'degrade' && degraded.has(nativeDb))
|
|
767
|
+
return PAIRS.map((p) => receipt(p, 'degraded', null));
|
|
768
|
+
const byTable = new Map();
|
|
769
|
+
const inOrder = () => PAIRS.map((p) => byTable.get(p.table)).filter((r) => r !== undefined);
|
|
770
|
+
const pending = [];
|
|
771
|
+
for (const pair of PAIRS) {
|
|
772
|
+
if (!pair.tables.every((t) => hasMainTable(nativeDb, t))) {
|
|
773
|
+
byTable.set(pair.table, receipt(pair, 'no-bare-table', null));
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const state = readState(nativeDb, pair);
|
|
777
|
+
if (bareChanged(nativeDb, pair, state))
|
|
778
|
+
pending.push(pair);
|
|
779
|
+
else
|
|
780
|
+
byTable.set(pair.table, receipt(pair, 'unchanged', state?.snapshot ?? null));
|
|
781
|
+
}
|
|
782
|
+
if (pending.length === 0) {
|
|
783
|
+
if (degraded.has(nativeDb))
|
|
784
|
+
clearShadows(nativeDb);
|
|
785
|
+
return inOrder();
|
|
786
|
+
}
|
|
787
|
+
if (nativeDb.isTransaction)
|
|
788
|
+
throw new Error('twin collapse needs a connection outside a transaction');
|
|
789
|
+
const fail = (pairs, failure, cause) => {
|
|
790
|
+
recordFailure(nativeDb, pairs, failure);
|
|
791
|
+
log.error(failure, `twin collapse of ${failure.tables.join(', ')} failed (T12535)`);
|
|
792
|
+
if (onFailure === 'throw')
|
|
793
|
+
throw twinCollapseError(failure, cause);
|
|
794
|
+
// Read-only-for-users mode: serve the merged view from TEMP shadows. If
|
|
795
|
+
// even that cannot be built, reads cannot be served correctly either.
|
|
796
|
+
try {
|
|
797
|
+
for (const pair of pairs) {
|
|
798
|
+
unsealShadow(nativeDb, pair.twin);
|
|
799
|
+
pair.shadow(nativeDb, pair.plan(nativeDb, readState(nativeDb, pair)));
|
|
800
|
+
sealShadow(nativeDb, pair.twin);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch (shadowError) {
|
|
804
|
+
throw twinCollapseError(failure, shadowError);
|
|
805
|
+
}
|
|
806
|
+
degraded.set(nativeDb, failure);
|
|
807
|
+
for (const pair of pairs)
|
|
808
|
+
byTable.set(pair.table, receipt(pair, 'degraded', failure.snapshotPath));
|
|
809
|
+
};
|
|
810
|
+
// One snapshot covers every pair whose INITIAL collapse changes its twin.
|
|
811
|
+
const needSnapshot = pending.filter((p) => {
|
|
812
|
+
if (readState(nativeDb, p) !== undefined)
|
|
813
|
+
return false;
|
|
814
|
+
const plan = p.plan(nativeDb, undefined);
|
|
815
|
+
return plan.set.size > 0 || plan.del.length > 0;
|
|
816
|
+
});
|
|
817
|
+
let snapshotPath = null;
|
|
818
|
+
if (needSnapshot.length > 0) {
|
|
819
|
+
const plan = planMigrationSnapshot(nativeDb, dbPath);
|
|
820
|
+
try {
|
|
821
|
+
snapshotPath = writeMigrationSnapshot(nativeDb, plan, `T12535 twin collapse of ${needSnapshot.map((p) => p.table).join(', ')} (store before the merge)`);
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
fail(needSnapshot, {
|
|
825
|
+
tables: needSnapshot.map((p) => p.table),
|
|
826
|
+
cause: `snapshot not written: ${error instanceof Error ? error.message : String(error)}`,
|
|
827
|
+
snapshotPath: plan.snapshotPath,
|
|
828
|
+
snapshotWritten: false,
|
|
829
|
+
requiredBytes: plan.requiredBytes,
|
|
830
|
+
availableBytes: plan.availableBytes,
|
|
831
|
+
failedAt: new Date().toISOString(),
|
|
832
|
+
}, error);
|
|
833
|
+
// Pairs that need no snapshot (incremental, or an initial no-op) still run.
|
|
834
|
+
const rest = pending.filter((p) => !needSnapshot.includes(p));
|
|
835
|
+
pending.length = 0;
|
|
836
|
+
pending.push(...rest);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const failed = [];
|
|
840
|
+
let firstError;
|
|
841
|
+
for (const pair of pending) {
|
|
842
|
+
try {
|
|
843
|
+
byTable.set(pair.table, collapsePair(nativeDb, pair, snapshotPath));
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
failed.push(pair);
|
|
847
|
+
firstError ??= error;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (failed.length > 0) {
|
|
851
|
+
fail(failed, {
|
|
852
|
+
tables: failed.map((p) => p.table),
|
|
853
|
+
cause: firstError instanceof Error ? firstError.message : String(firstError),
|
|
854
|
+
snapshotPath,
|
|
855
|
+
snapshotWritten: snapshotPath !== null,
|
|
856
|
+
requiredBytes: 0,
|
|
857
|
+
availableBytes: null,
|
|
858
|
+
failedAt: new Date().toISOString(),
|
|
859
|
+
}, firstError);
|
|
860
|
+
}
|
|
861
|
+
else if (degraded.has(nativeDb) && byTable.size === PAIRS.length) {
|
|
862
|
+
if ([...byTable.values()].every((r) => r.status !== 'degraded'))
|
|
863
|
+
clearShadows(nativeDb);
|
|
864
|
+
}
|
|
865
|
+
return inOrder();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Report every pair's collapse state without writing (for `cleo doctor`).
|
|
869
|
+
*
|
|
870
|
+
* @param db - A connection on the project `cleo.db`; read-only is enough.
|
|
871
|
+
* @returns One status per pair.
|
|
872
|
+
* @task T12535
|
|
873
|
+
*/
|
|
874
|
+
export function inspectTwinCollapse(db) {
|
|
875
|
+
return PAIRS.map((pair) => {
|
|
876
|
+
const empty = {
|
|
877
|
+
table: pair.table,
|
|
878
|
+
twin: pair.twin,
|
|
879
|
+
wouldChangeTwin: false,
|
|
880
|
+
snapshotPath: null,
|
|
881
|
+
collapsedAt: null,
|
|
882
|
+
lastMergedAt: null,
|
|
883
|
+
changedSinceMerge: 0,
|
|
884
|
+
conflicts: [],
|
|
885
|
+
conflictsAt: null,
|
|
886
|
+
failure: null,
|
|
887
|
+
};
|
|
888
|
+
if (!pair.tables.every((t) => hasMainTable(db, t)))
|
|
889
|
+
return { ...empty, state: 'no-bare-table' };
|
|
890
|
+
let failure = null;
|
|
891
|
+
try {
|
|
892
|
+
const raw = readKv(db, pair.kvTable, `${TWIN_COLLAPSE_FAILURE_PREFIX}${pair.table}`);
|
|
893
|
+
failure = raw === undefined ? null : JSON.parse(raw);
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
failure = null;
|
|
897
|
+
}
|
|
898
|
+
const state = readState(db, pair);
|
|
899
|
+
const now = pair.bareHashes(db);
|
|
900
|
+
const prev = state?.hashes?.bare ?? {};
|
|
901
|
+
let changed = 0;
|
|
902
|
+
for (const k of new Set([...Object.keys(now), ...Object.keys(prev)])) {
|
|
903
|
+
if (now[k] !== prev[k])
|
|
904
|
+
changed++;
|
|
905
|
+
}
|
|
906
|
+
let wouldChangeTwin = false;
|
|
907
|
+
if (state === undefined) {
|
|
908
|
+
const plan = pair.plan(db, undefined);
|
|
909
|
+
wouldChangeTwin = plan.set.size > 0 || plan.del.length > 0;
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
...empty,
|
|
913
|
+
state: failure !== null
|
|
914
|
+
? 'failed'
|
|
915
|
+
: state === undefined
|
|
916
|
+
? 'pending'
|
|
917
|
+
: changed > 0 || state.hashes === null
|
|
918
|
+
? 'bare-changed'
|
|
919
|
+
: 'collapsed',
|
|
920
|
+
wouldChangeTwin,
|
|
921
|
+
snapshotPath: state?.snapshot ?? failure?.snapshotPath ?? null,
|
|
922
|
+
collapsedAt: state?.collapsedAt || null,
|
|
923
|
+
lastMergedAt: state?.lastMergedAt || null,
|
|
924
|
+
changedSinceMerge: state === undefined ? 0 : changed,
|
|
925
|
+
conflicts: state?.conflicts ?? [],
|
|
926
|
+
conflictsAt: state?.conflictsAt ?? null,
|
|
927
|
+
failure,
|
|
928
|
+
};
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
//# sourceMappingURL=twin-collapse.js.map
|