@cleocode/core 2026.9.23 → 2026.9.24
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/admin/import-tasks.d.ts.map +1 -1
- package/dist/admin/import-tasks.js +35 -6
- package/dist/admin/import-tasks.js.map +1 -1
- package/dist/admin/import.d.ts.map +1 -1
- package/dist/admin/import.js +25 -6
- package/dist/admin/import.js.map +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/cloud/http.d.ts +8 -0
- package/dist/cloud/http.d.ts.map +1 -1
- package/dist/cloud/http.js +8 -1
- package/dist/cloud/http.js.map +1 -1
- package/dist/cloud/index.d.ts +6 -1
- package/dist/cloud/index.d.ts.map +1 -1
- package/dist/cloud/index.js +6 -1
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/nexus-auth.d.ts +169 -0
- package/dist/cloud/nexus-auth.d.ts.map +1 -0
- package/dist/cloud/nexus-auth.js +372 -0
- package/dist/cloud/nexus-auth.js.map +1 -0
- package/dist/cloud/nexus-credentials.d.ts +188 -0
- package/dist/cloud/nexus-credentials.d.ts.map +1 -0
- package/dist/cloud/nexus-credentials.js +323 -0
- package/dist/cloud/nexus-credentials.js.map +1 -0
- package/dist/cloud/nexus-link.d.ts +85 -0
- package/dist/cloud/nexus-link.d.ts.map +1 -0
- package/dist/cloud/nexus-link.js +226 -0
- package/dist/cloud/nexus-link.js.map +1 -0
- package/dist/cloud/presence.d.ts +88 -0
- package/dist/cloud/presence.d.ts.map +1 -0
- package/dist/cloud/presence.js +137 -0
- package/dist/cloud/presence.js.map +1 -0
- package/dist/cloud/projects.d.ts +3 -1
- package/dist/cloud/projects.d.ts.map +1 -1
- package/dist/cloud/projects.js +4 -2
- package/dist/cloud/projects.js.map +1 -1
- package/dist/crypto/credentials.d.ts +14 -4
- package/dist/crypto/credentials.d.ts.map +1 -1
- package/dist/crypto/credentials.js +15 -1
- package/dist/crypto/credentials.js.map +1 -1
- package/dist/decide/bench/dataset.d.ts +125 -0
- package/dist/decide/bench/dataset.d.ts.map +1 -0
- package/dist/decide/bench/dataset.js +457 -0
- package/dist/decide/bench/dataset.js.map +1 -0
- package/dist/decide/bench/index.d.ts +23 -0
- package/dist/decide/bench/index.d.ts.map +1 -0
- package/dist/decide/bench/index.js +23 -0
- package/dist/decide/bench/index.js.map +1 -0
- package/dist/decide/bench/metrics.d.ts +55 -0
- package/dist/decide/bench/metrics.d.ts.map +1 -0
- package/dist/decide/bench/metrics.js +100 -0
- package/dist/decide/bench/metrics.js.map +1 -0
- package/dist/decide/bench/operation.d.ts +119 -0
- package/dist/decide/bench/operation.d.ts.map +1 -0
- package/dist/decide/bench/operation.js +171 -0
- package/dist/decide/bench/operation.js.map +1 -0
- package/dist/decide/bench/profiles.d.ts +74 -0
- package/dist/decide/bench/profiles.d.ts.map +1 -0
- package/dist/decide/bench/profiles.js +131 -0
- package/dist/decide/bench/profiles.js.map +1 -0
- package/dist/decide/bench/questions.d.ts +40 -0
- package/dist/decide/bench/questions.d.ts.map +1 -0
- package/dist/decide/bench/questions.js +107 -0
- package/dist/decide/bench/questions.js.map +1 -0
- package/dist/decide/bench/report.d.ts +21 -0
- package/dist/decide/bench/report.d.ts.map +1 -0
- package/dist/decide/bench/report.js +120 -0
- package/dist/decide/bench/report.js.map +1 -0
- package/dist/decide/bench/runner.d.ts +203 -0
- package/dist/decide/bench/runner.d.ts.map +1 -0
- package/dist/decide/bench/runner.js +335 -0
- package/dist/decide/bench/runner.js.map +1 -0
- package/dist/decide/bench/source.d.ts +21 -0
- package/dist/decide/bench/source.d.ts.map +1 -0
- package/dist/decide/bench/source.js +67 -0
- package/dist/decide/bench/source.js.map +1 -0
- package/dist/decide/bench/spot-check.d.ts +117 -0
- package/dist/decide/bench/spot-check.d.ts.map +1 -0
- package/dist/decide/bench/spot-check.js +221 -0
- package/dist/decide/bench/spot-check.js.map +1 -0
- package/dist/decide/bench/stats.d.ts +57 -0
- package/dist/decide/bench/stats.d.ts.map +1 -0
- package/dist/decide/bench/stats.js +82 -0
- package/dist/decide/bench/stats.js.map +1 -0
- package/dist/decide/bench/types.d.ts +216 -0
- package/dist/decide/bench/types.d.ts.map +1 -0
- package/dist/decide/bench/types.js +104 -0
- package/dist/decide/bench/types.js.map +1 -0
- package/dist/decide/client.d.ts +35 -1
- package/dist/decide/client.d.ts.map +1 -1
- package/dist/decide/client.js +124 -30
- package/dist/decide/client.js.map +1 -1
- package/dist/decide/credentials.d.ts +177 -17
- package/dist/decide/credentials.d.ts.map +1 -1
- package/dist/decide/credentials.js +505 -41
- package/dist/decide/credentials.js.map +1 -1
- package/dist/decide/index.d.ts +8 -8
- package/dist/decide/index.d.ts.map +1 -1
- package/dist/decide/index.js +8 -8
- package/dist/decide/index.js.map +1 -1
- package/dist/decide/jev-wire.d.ts +25 -19
- package/dist/decide/jev-wire.d.ts.map +1 -1
- package/dist/decide/jev-wire.js +56 -31
- package/dist/decide/jev-wire.js.map +1 -1
- package/dist/decide/operations.d.ts +63 -12
- package/dist/decide/operations.d.ts.map +1 -1
- package/dist/decide/operations.js +116 -58
- package/dist/decide/operations.js.map +1 -1
- package/dist/decide/provider-state.d.ts +85 -12
- package/dist/decide/provider-state.d.ts.map +1 -1
- package/dist/decide/provider-state.js +154 -21
- package/dist/decide/provider-state.js.map +1 -1
- package/dist/decide/provider.d.ts +5 -0
- package/dist/decide/provider.d.ts.map +1 -1
- package/dist/decide/provider.js.map +1 -1
- package/dist/decide/providers.d.ts +15 -2
- package/dist/decide/providers.d.ts.map +1 -1
- package/dist/decide/providers.js +22 -6
- package/dist/decide/providers.js.map +1 -1
- package/dist/decide/sites/registry.d.ts +22 -0
- package/dist/decide/sites/registry.d.ts.map +1 -1
- package/dist/decide/sites/registry.js +23 -0
- package/dist/decide/sites/registry.js.map +1 -1
- package/dist/decide/wizard.d.ts +12 -4
- package/dist/decide/wizard.d.ts.map +1 -1
- package/dist/decide/wizard.js +79 -26
- package/dist/decide/wizard.js.map +1 -1
- package/dist/dispatch/mvi-projection.d.ts +15 -2
- package/dist/dispatch/mvi-projection.d.ts.map +1 -1
- package/dist/dispatch/mvi-projection.js +73 -12
- package/dist/dispatch/mvi-projection.js.map +1 -1
- package/dist/docs/adr-allocator.d.ts +2 -2
- package/dist/docs/adr-allocator.d.ts.map +1 -1
- package/dist/docs/adr-allocator.js +17 -4
- package/dist/docs/adr-allocator.js.map +1 -1
- package/dist/docs/blob-consolidation.js +1 -1
- package/dist/docs/blob-consolidation.js.map +1 -1
- package/dist/docs/display-alias.d.ts +1 -1
- package/dist/docs/display-alias.d.ts.map +1 -1
- package/dist/docs/display-alias.js +7 -4
- package/dist/docs/display-alias.js.map +1 -1
- package/dist/docs/docs-inconsistency-detector.js +1 -1
- package/dist/docs/docs-inconsistency-detector.js.map +1 -1
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +6 -1
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/docs/export-document.js +2928 -1053
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/numbering.js +1 -1
- package/dist/docs/numbering.js.map +1 -1
- package/dist/docs/supersede.d.ts.map +1 -1
- package/dist/docs/supersede.js +6 -3
- package/dist/docs/supersede.js.map +1 -1
- package/dist/docs/version-ssot.js +1 -1
- package/dist/docs/version-ssot.js.map +1 -1
- package/dist/doctor/db-substrate.d.ts.map +1 -1
- package/dist/doctor/db-substrate.js +39 -24
- package/dist/doctor/db-substrate.js.map +1 -1
- package/dist/doctor/knowledge-summary.d.ts +10 -3
- package/dist/doctor/knowledge-summary.d.ts.map +1 -1
- package/dist/doctor/knowledge-summary.js +10 -3
- package/dist/doctor/knowledge-summary.js.map +1 -1
- package/dist/doctor/project-identity.d.ts +128 -37
- package/dist/doctor/project-identity.d.ts.map +1 -1
- package/dist/doctor/project-identity.js +476 -63
- package/dist/doctor/project-identity.js.map +1 -1
- package/dist/doctor/twin-collapse.d.ts +52 -2
- package/dist/doctor/twin-collapse.d.ts.map +1 -1
- package/dist/doctor/twin-collapse.js +108 -4
- package/dist/doctor/twin-collapse.js.map +1 -1
- package/dist/error-catalog.js +2 -2
- package/dist/error-catalog.js.map +1 -1
- package/dist/error-registry.js +1 -1
- package/dist/error-registry.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +22 -2
- package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +20 -11
- package/dist/init.js.map +1 -1
- package/dist/inject/index.d.ts.map +1 -1
- package/dist/inject/index.js +2 -3
- package/dist/inject/index.js.map +1 -1
- package/dist/internal.d.ts +5 -4
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +6 -4
- package/dist/internal.js.map +1 -1
- package/dist/llm/credential-remove-entry.d.ts +59 -0
- package/dist/llm/credential-remove-entry.d.ts.map +1 -0
- package/dist/llm/credential-remove-entry.js +101 -0
- package/dist/llm/credential-remove-entry.js.map +1 -0
- package/dist/llm/oauth/device-code.d.ts +26 -6
- package/dist/llm/oauth/device-code.d.ts.map +1 -1
- package/dist/llm/oauth/device-code.js +44 -12
- package/dist/llm/oauth/device-code.js.map +1 -1
- package/dist/llm/plugin-facade.js +3017 -1142
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/attention.d.ts.map +1 -1
- package/dist/memory/attention.js +5 -3
- package/dist/memory/attention.js.map +1 -1
- package/dist/memory/decisions.d.ts +48 -3
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +98 -14
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/learnings.d.ts +6 -6
- package/dist/memory/patterns.d.ts +4 -4
- package/dist/migration/manual/T10165-backfill-adr-index.d.ts.map +1 -1
- package/dist/migration/manual/T10165-backfill-adr-index.js +5 -1
- package/dist/migration/manual/T10165-backfill-adr-index.js.map +1 -1
- package/dist/nexus/fleet-status.d.ts +83 -0
- package/dist/nexus/fleet-status.d.ts.map +1 -0
- package/dist/nexus/fleet-status.js +414 -0
- package/dist/nexus/fleet-status.js.map +1 -0
- package/dist/nexus/git-state.d.ts +15 -0
- package/dist/nexus/git-state.d.ts.map +1 -1
- package/dist/nexus/git-state.js +67 -2
- package/dist/nexus/git-state.js.map +1 -1
- package/dist/nexus/identity.d.ts.map +1 -1
- package/dist/nexus/identity.js +4 -0
- package/dist/nexus/identity.js.map +1 -1
- package/dist/nexus/index.d.ts +4 -1
- package/dist/nexus/index.d.ts.map +1 -1
- package/dist/nexus/index.js +7 -1
- package/dist/nexus/index.js.map +1 -1
- package/dist/nexus/migrate-json-to-sqlite.d.ts +3 -2
- package/dist/nexus/migrate-json-to-sqlite.d.ts.map +1 -1
- package/dist/nexus/migrate-json-to-sqlite.js +9 -17
- 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/project-activity.d.ts +104 -0
- package/dist/nexus/project-activity.d.ts.map +1 -0
- package/dist/nexus/project-activity.js +150 -0
- package/dist/nexus/project-activity.js.map +1 -0
- package/dist/nexus/query.d.ts +2 -2
- package/dist/nexus/query.d.ts.map +1 -1
- package/dist/nexus/query.js +4 -19
- package/dist/nexus/query.js.map +1 -1
- package/dist/nexus/registry-errors.d.ts +61 -0
- package/dist/nexus/registry-errors.d.ts.map +1 -0
- package/dist/nexus/registry-errors.js +85 -0
- package/dist/nexus/registry-errors.js.map +1 -0
- package/dist/nexus/registry.d.ts +30 -5
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +115 -70
- package/dist/nexus/registry.js.map +1 -1
- package/dist/orchestrate/pivot.d.ts +4 -0
- package/dist/orchestrate/pivot.d.ts.map +1 -1
- package/dist/orchestrate/pivot.js +75 -12
- package/dist/orchestrate/pivot.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +29 -27
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +29 -3
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/bootstrap.js +20 -14
- package/dist/orchestration/bootstrap.js.map +1 -1
- package/dist/orchestration/index.d.ts +15 -1
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +25 -6
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +7 -1
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +6 -3
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/spawn-prompt.js +1 -1
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/waves.d.ts +21 -10
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +38 -35
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/paths.d.ts +9 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +30 -13
- package/dist/paths.js.map +1 -1
- package/dist/phases/index.d.ts +1 -1
- package/dist/phases/index.d.ts.map +1 -1
- package/dist/phases/index.js +3 -4
- package/dist/phases/index.js.map +1 -1
- package/dist/project-info.d.ts +40 -3
- package/dist/project-info.d.ts.map +1 -1
- package/dist/project-info.js +74 -6
- package/dist/project-info.js.map +1 -1
- package/dist/project-lifecycle.d.ts +28 -7
- package/dist/project-lifecycle.d.ts.map +1 -1
- package/dist/project-lifecycle.js +114 -47
- package/dist/project-lifecycle.js.map +1 -1
- package/dist/project-scope.d.ts +70 -3
- package/dist/project-scope.d.ts.map +1 -1
- package/dist/project-scope.js +154 -15
- package/dist/project-scope.js.map +1 -1
- package/dist/release/open.d.ts +12 -2
- package/dist/release/open.d.ts.map +1 -1
- package/dist/release/open.js +32 -3
- package/dist/release/open.js.map +1 -1
- package/dist/release/preflight-skip.d.ts +73 -0
- package/dist/release/preflight-skip.d.ts.map +1 -0
- package/dist/release/preflight-skip.js +254 -0
- package/dist/release/preflight-skip.js.map +1 -0
- package/dist/render/decide/index.d.ts +51 -0
- package/dist/render/decide/index.d.ts.map +1 -0
- package/dist/render/decide/index.js +289 -0
- package/dist/render/decide/index.js.map +1 -0
- package/dist/render/index.d.ts +1 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +1 -0
- package/dist/render/index.js.map +1 -1
- package/dist/render/nexus/graph/index.d.ts +5 -0
- package/dist/render/nexus/graph/index.d.ts.map +1 -1
- package/dist/render/nexus/graph/index.js +53 -0
- package/dist/render/nexus/graph/index.js.map +1 -1
- package/dist/scaffold/ensure-config.d.ts +14 -6
- package/dist/scaffold/ensure-config.d.ts.map +1 -1
- package/dist/scaffold/ensure-config.js +39 -17
- package/dist/scaffold/ensure-config.js.map +1 -1
- package/dist/scaffold/project-identity.d.ts +115 -23
- package/dist/scaffold/project-identity.d.ts.map +1 -1
- package/dist/scaffold/project-identity.js +196 -44
- package/dist/scaffold/project-identity.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +3027 -1152
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/cross-project-hygiene.d.ts +12 -6
- package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
- package/dist/sentient/cross-project-hygiene.js +15 -10
- package/dist/sentient/cross-project-hygiene.js.map +1 -1
- package/dist/session/engine-ops.d.ts +13 -4
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +38 -45
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +7 -3
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +10 -10
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/drift-watchdog.d.ts.map +1 -1
- package/dist/sessions/drift-watchdog.js +11 -19
- package/dist/sessions/drift-watchdog.js.map +1 -1
- package/dist/sessions/focus-state-store.d.ts +103 -26
- package/dist/sessions/focus-state-store.d.ts.map +1 -1
- package/dist/sessions/focus-state-store.js +166 -37
- package/dist/sessions/focus-state-store.js.map +1 -1
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/handoff.js +9 -18
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/sessions/index.d.ts +4 -2
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +35 -6
- 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 -2
- package/dist/sessions/session-drift.js.map +1 -1
- package/dist/sessions/session-view.d.ts +8 -1
- package/dist/sessions/session-view.d.ts.map +1 -1
- package/dist/sessions/session-view.js +9 -2
- package/dist/sessions/session-view.js.map +1 -1
- package/dist/setup/config-json-merge.d.ts.map +1 -1
- package/dist/setup/config-json-merge.js +1 -0
- package/dist/setup/config-json-merge.js.map +1 -1
- package/dist/setup/index.d.ts +4 -2
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js +6 -2
- package/dist/setup/index.js.map +1 -1
- package/dist/setup/sections/nexus-account.d.ts +32 -0
- package/dist/setup/sections/nexus-account.d.ts.map +1 -0
- package/dist/setup/sections/nexus-account.js +64 -0
- package/dist/setup/sections/nexus-account.js.map +1 -0
- package/dist/setup/wizard.d.ts +1 -1
- package/dist/setup/wizard.d.ts.map +1 -1
- package/dist/setup/wizard.js.map +1 -1
- package/dist/skills/orchestrator/startup.d.ts.map +1 -1
- package/dist/skills/orchestrator/startup.js +18 -31
- package/dist/skills/orchestrator/startup.js.map +1 -1
- package/dist/snapshot/index.d.ts.map +1 -1
- package/dist/snapshot/index.js +60 -53
- package/dist/snapshot/index.js.map +1 -1
- package/dist/spawn/agent-identity.d.ts +33 -1
- package/dist/spawn/agent-identity.d.ts.map +1 -1
- package/dist/spawn/agent-identity.js +53 -1
- package/dist/spawn/agent-identity.js.map +1 -1
- package/dist/spawn/index.d.ts +1 -1
- 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/stats/index.d.ts.map +1 -1
- package/dist/stats/index.js +3 -4
- package/dist/stats/index.js.map +1 -1
- package/dist/status/index.d.ts +12 -3
- package/dist/status/index.d.ts.map +1 -1
- package/dist/status/index.js +47 -5
- package/dist/status/index.js.map +1 -1
- package/dist/store/attachment-repair.d.ts.map +1 -1
- package/dist/store/attachment-repair.js +42 -3
- package/dist/store/attachment-repair.js.map +1 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +30 -46
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/backup-recover-brain.d.ts +4 -0
- package/dist/store/backup-recover-brain.d.ts.map +1 -1
- package/dist/store/backup-recover-brain.js +2 -0
- package/dist/store/backup-recover-brain.js.map +1 -1
- package/dist/store/backup-recover.d.ts +9 -0
- package/dist/store/backup-recover.d.ts.map +1 -1
- package/dist/store/backup-recover.js +28 -1
- package/dist/store/backup-recover.js.map +1 -1
- package/dist/store/backup-sidecar.d.ts +30 -0
- package/dist/store/backup-sidecar.d.ts.map +1 -1
- package/dist/store/backup-sidecar.js +90 -2
- package/dist/store/backup-sidecar.js.map +1 -1
- package/dist/store/blob-keep.d.ts +135 -0
- package/dist/store/blob-keep.d.ts.map +1 -0
- package/dist/store/blob-keep.js +283 -0
- package/dist/store/blob-keep.js.map +1 -0
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +11 -0
- package/dist/store/converters.js.map +1 -1
- package/dist/store/credential-transfer.d.ts +37 -5
- package/dist/store/credential-transfer.d.ts.map +1 -1
- package/dist/store/credential-transfer.js +73 -18
- package/dist/store/credential-transfer.js.map +1 -1
- 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.map +1 -1
- package/dist/store/db-helpers.d.ts +18 -0
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +46 -0
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts.map +1 -1
- package/dist/store/exodus/on-open.js +35 -0
- package/dist/store/exodus/on-open.js.map +1 -1
- package/dist/store/exodus/runtime-targets.d.ts +3 -3
- package/dist/store/exodus/runtime-targets.js +3 -3
- package/dist/store/import-remap.d.ts +12 -1
- package/dist/store/import-remap.d.ts.map +1 -1
- package/dist/store/import-remap.js +14 -0
- package/dist/store/import-remap.js.map +1 -1
- package/dist/store/legacy-tasks-lineage.d.ts +3 -1
- package/dist/store/legacy-tasks-lineage.d.ts.map +1 -1
- package/dist/store/legacy-tasks-lineage.js +3 -1
- package/dist/store/legacy-tasks-lineage.js.map +1 -1
- package/dist/store/memory-accessor.js +1 -1
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/migration-manager.d.ts +61 -0
- package/dist/store/migration-manager.d.ts.map +1 -1
- package/dist/store/migration-manager.js +102 -4
- package/dist/store/migration-manager.js.map +1 -1
- package/dist/store/nexus-validation-schemas.d.ts +64 -0
- package/dist/store/nexus-validation-schemas.d.ts.map +1 -1
- package/dist/store/portable-bundle-import.d.ts +8 -0
- package/dist/store/portable-bundle-import.d.ts.map +1 -1
- package/dist/store/portable-bundle-import.js +10 -0
- package/dist/store/portable-bundle-import.js.map +1 -1
- package/dist/store/portable-bundle-scan.d.ts.map +1 -1
- package/dist/store/portable-bundle-scan.js +1 -0
- 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 +15 -14
- package/dist/store/portable-bundle.js.map +1 -1
- package/dist/store/pre-repair-snapshot.d.ts +4 -1
- package/dist/store/pre-repair-snapshot.d.ts.map +1 -1
- package/dist/store/pre-repair-snapshot.js +5 -1
- package/dist/store/pre-repair-snapshot.js.map +1 -1
- package/dist/store/regenerators.d.ts +4 -2
- package/dist/store/regenerators.d.ts.map +1 -1
- package/dist/store/regenerators.js +7 -3
- package/dist/store/regenerators.js.map +1 -1
- package/dist/store/repair-malformed-dbs.d.ts +4 -0
- package/dist/store/repair-malformed-dbs.d.ts.map +1 -1
- package/dist/store/repair-malformed-dbs.js +3 -0
- package/dist/store/repair-malformed-dbs.js.map +1 -1
- package/dist/store/safety-data-accessor.d.ts +8 -3
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +11 -4
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/schema/cleo-global/nexus.d.ts +48 -0
- package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/nexus.js +17 -0
- package/dist/store/schema/cleo-global/nexus.js.map +1 -1
- package/dist/store/schema/cleo-project/docs.d.ts +16 -0
- package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/docs.js +10 -0
- package/dist/store/schema/cleo-project/docs.js.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +80 -0
- package/dist/store/schema/cleo-project/tasks-core.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.js +25 -1
- package/dist/store/schema/cleo-project/tasks-core.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +1 -1
- package/dist/store/schema/memory-schema.d.ts +1 -1
- package/dist/store/schema/nexus-schema.d.ts +48 -0
- package/dist/store/schema/nexus-schema.d.ts.map +1 -1
- package/dist/store/schema/nexus-schema.js +28 -7
- package/dist/store/schema/nexus-schema.js.map +1 -1
- package/dist/store/session-store.d.ts +65 -0
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +108 -7
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +245 -47
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +15 -3
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +52 -20
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/store-write-guard.d.ts +9 -3
- package/dist/store/store-write-guard.d.ts.map +1 -1
- package/dist/store/store-write-guard.js +25 -4
- package/dist/store/store-write-guard.js.map +1 -1
- package/dist/store/table-classification.d.ts +1 -1
- package/dist/store/table-classification.d.ts.map +1 -1
- package/dist/store/table-classification.js +34 -8
- package/dist/store/table-classification.js.map +1 -1
- package/dist/store/task-claim.d.ts +144 -0
- package/dist/store/task-claim.d.ts.map +1 -0
- package/dist/store/task-claim.js +195 -0
- package/dist/store/task-claim.js.map +1 -0
- package/dist/store/task-version.d.ts +43 -5
- package/dist/store/task-version.d.ts.map +1 -1
- package/dist/store/task-version.js +113 -17
- package/dist/store/task-version.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +2 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +1 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/tasks-sqlite.js +1 -1
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/twin-collapse.d.ts +339 -22
- package/dist/store/twin-collapse.d.ts.map +1 -1
- package/dist/store/twin-collapse.js +1323 -48
- package/dist/store/twin-collapse.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +8 -3
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +10 -4
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +161 -1
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/store/with-retry.d.ts +36 -0
- package/dist/store/with-retry.d.ts.map +1 -1
- package/dist/store/with-retry.js +51 -0
- package/dist/store/with-retry.js.map +1 -1
- package/dist/store/worktree-build-guard.d.ts +13 -4
- package/dist/store/worktree-build-guard.d.ts.map +1 -1
- package/dist/store/worktree-build-guard.js +21 -10
- package/dist/store/worktree-build-guard.js.map +1 -1
- package/dist/store/worktree-isolation-guard.d.ts +121 -10
- package/dist/store/worktree-isolation-guard.d.ts.map +1 -1
- package/dist/store/worktree-isolation-guard.js +155 -34
- package/dist/store/worktree-isolation-guard.js.map +1 -1
- package/dist/system/backup.d.ts +7 -0
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +5 -0
- package/dist/system/backup.js.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +3 -1
- package/dist/system/health.js.map +1 -1
- package/dist/system/inject-generate.d.ts.map +1 -1
- package/dist/system/inject-generate.js +23 -10
- package/dist/system/inject-generate.js.map +1 -1
- package/dist/system/project-health.d.ts +8 -0
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +13 -5
- package/dist/system/project-health.js.map +1 -1
- package/dist/task-tools/index.d.ts +1 -1
- package/dist/task-tools/index.d.ts.map +1 -1
- package/dist/task-tools/index.js +1 -1
- package/dist/task-tools/index.js.map +1 -1
- package/dist/task-tools/score-task-priority.d.ts +38 -0
- package/dist/task-tools/score-task-priority.d.ts.map +1 -1
- package/dist/task-tools/score-task-priority.js +51 -0
- package/dist/task-tools/score-task-priority.js.map +1 -1
- package/dist/task-work/claims.d.ts +215 -0
- package/dist/task-work/claims.d.ts.map +1 -0
- package/dist/task-work/claims.js +272 -0
- package/dist/task-work/claims.js.map +1 -0
- package/dist/task-work/index.d.ts +47 -3
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +74 -41
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +3 -2
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/analyze.d.ts.map +1 -1
- package/dist/tasks/analyze.js +3 -4
- package/dist/tasks/analyze.js.map +1 -1
- package/dist/tasks/change-set.d.ts +5 -0
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +59 -3
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete.d.ts +16 -0
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +27 -7
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/component-pr.d.ts +162 -1
- package/dist/tasks/component-pr.d.ts.map +1 -1
- package/dist/tasks/component-pr.js +560 -59
- package/dist/tasks/component-pr.js.map +1 -1
- package/dist/tasks/done-record.d.ts +14 -1
- package/dist/tasks/done-record.d.ts.map +1 -1
- package/dist/tasks/done-record.js +37 -0
- package/dist/tasks/done-record.js.map +1 -1
- package/dist/tasks/duplicate-detector.d.ts +20 -1
- package/dist/tasks/duplicate-detector.d.ts.map +1 -1
- package/dist/tasks/duplicate-detector.js +10 -2
- package/dist/tasks/duplicate-detector.js.map +1 -1
- package/dist/tasks/engine-wrap-ops.d.ts +19 -10
- package/dist/tasks/engine-wrap-ops.d.ts.map +1 -1
- package/dist/tasks/engine-wrap-ops.js +38 -10
- package/dist/tasks/engine-wrap-ops.js.map +1 -1
- package/dist/tasks/graph-ops.d.ts +9 -2
- package/dist/tasks/graph-ops.d.ts.map +1 -1
- package/dist/tasks/graph-ops.js +24 -32
- package/dist/tasks/graph-ops.js.map +1 -1
- package/dist/tasks/plan.d.ts.map +1 -1
- package/dist/tasks/plan.js +16 -60
- package/dist/tasks/plan.js.map +1 -1
- package/dist/tasks/task-context.d.ts.map +1 -1
- package/dist/tasks/task-context.js +13 -0
- package/dist/tasks/task-context.js.map +1 -1
- package/dist/tasks/task-import.d.ts.map +1 -1
- package/dist/tasks/task-import.js +21 -12
- package/dist/tasks/task-import.js.map +1 -1
- package/dist/tasks/task-next.d.ts +25 -2
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +32 -13
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +7 -5
- package/dist/tasks/update.js.map +1 -1
- package/dist/upgrade.d.ts +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +48 -9
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +20 -6
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +51 -8
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/validate-ops.d.ts.map +1 -1
- package/dist/validation/validate-ops.js +2 -3
- package/dist/validation/validate-ops.js.map +1 -1
- package/migrations/drizzle-cleo-global/20260929010000_t12512-registry-probed-opened/migration.sql +35 -0
- package/migrations/drizzle-cleo-global/20260929020000_t12721-git-state-head-committed-at/migration.sql +25 -0
- package/migrations/drizzle-cleo-project/20260929000000_t12535-docs-attachments-union-shape/migration.sql +21 -0
- package/migrations/drizzle-cleo-project/20260929120000_t12502-task-claim-leases/migration.sql +77 -0
- package/migrations/drizzle-cleo-project/20260929130000_t12736-claim-lease-iso-repair/migration.sql +37 -0
- package/package.json +23 -13
- package/schemas/project-info.schema.json +10 -4
- package/templates/CLEO-INJECTION.md +1 -1
- package/templates/CLEO-REFERENCE.md +3 -3
- package/templates/cleo-gitignore +11 -0
- package/templates/workflows/release-prepare.yml.tmpl +156 -10
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Inspect and resolve the portable project identity (T12353 · ADR-094).
|
|
2
|
+
* Inspect and resolve the portable project identity (T12353 · ADR-094 · T12716).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The tracked identity is `.cleo/project.json` `{schemaVersion, id, name}`
|
|
5
|
+
* (T12716), with the ADR-094 `.cleo/project-id` kept as a legacy mirror.
|
|
6
|
+
* `project-info.json` is untracked and device-local; its `projectId` is a
|
|
7
|
+
* cache of the tracked id. The tracked id always wins at runtime. This module
|
|
8
|
+
* is the ONLY place that migrates or re-keys identity files — nothing does it
|
|
9
|
+
* on open, init or upgrade:
|
|
9
10
|
*
|
|
10
|
-
* - {@link inspectProjectIdentity} is read-only. It classifies the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
11
|
+
* - {@link inspectProjectIdentity} is read-only. It classifies the three
|
|
12
|
+
* sources (project.json / project-id / project-info.json), checks whether
|
|
13
|
+
* git actually tracks the canonical file, and returns the exact remedy.
|
|
14
|
+
* - {@link inspectProjectNameDrift} is read-only. It compares the registry's
|
|
15
|
+
* label with the declared display name.
|
|
16
|
+
* - {@link resolveProjectIdentity} applies the remedy. A legacy project gets
|
|
17
|
+
* `project.json` written from its `project-id` and `project-info.json` name
|
|
18
|
+
* (no id ever changes). For a conflict it re-keys the LOCAL side to the
|
|
19
|
+
* tracked id: the registry row's primary key and every alias pointing at the
|
|
20
|
+
* old id are updated in one registry transaction, and the old id is itself
|
|
21
|
+
* recorded as an alias. Nothing is deleted: rows elsewhere that still carry
|
|
22
|
+
* the old id (audit log, session manifest, background jobs) keep resolving
|
|
23
|
+
* through the alias table, and credentials sealed under it are re-wrapped on
|
|
24
|
+
* their next open (`previousProjectIds`).
|
|
18
25
|
*
|
|
19
|
-
* The tracked file is never modified here. It is write-once, and a
|
|
20
|
-
*
|
|
26
|
+
* The id in a tracked file is never modified here. It is write-once, and a
|
|
27
|
+
* malformed file is restored from version control, never regenerated.
|
|
21
28
|
*
|
|
22
29
|
* T12557: `projectRoot` is a path fact, derived from the real root at runtime.
|
|
23
30
|
* A persisted copy in `project-info.json` or `project-context.json` goes stale
|
|
@@ -29,13 +36,16 @@
|
|
|
29
36
|
*
|
|
30
37
|
* @task T12353
|
|
31
38
|
* @task T12557
|
|
39
|
+
* @task T12716
|
|
32
40
|
* @see ADR-094 — write-once portable project identity (amends ADR-013 §9)
|
|
41
|
+
* @see ADR-096 — one committed `.cleo/project.json` {id, name} (amends ADR-094)
|
|
33
42
|
*/
|
|
34
43
|
import { execFileSync } from 'node:child_process';
|
|
44
|
+
import { randomUUID } from 'node:crypto';
|
|
35
45
|
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
36
46
|
import { join } from 'node:path';
|
|
37
|
-
import { canonicalizePath, getCleoHome, isValidPortableProjectId, PORTABLE_PROJECT_ID_FILE, readDeclaredProjectIdentity, readPortableProjectId, } from '@cleocode/paths';
|
|
38
|
-
import { ensurePortableProjectId } from '../scaffold/project-identity.js';
|
|
47
|
+
import { canonicalizePath, getCleoHome, isValidPortableProjectId, isValidProjectDisplayName, PORTABLE_PROJECT_ID_FILE, PROJECT_MANIFEST_FILE, readDeclaredProjectIdentity, readPortableProjectId, readProjectIdFile, readProjectManifest, } from '@cleocode/paths';
|
|
48
|
+
import { createProjectIdMirror, createProjectManifest, defaultProjectDisplayName, ensurePortableProjectId, } from '../scaffold/project-identity.js';
|
|
39
49
|
import { detectRelocatedRoot } from '../store/relocated-store-guard.js';
|
|
40
50
|
/** Command that reports identity state; the prefix of every remedy below. */
|
|
41
51
|
const IDENTITY_COMMAND = 'cleo doctor project-identity';
|
|
@@ -95,9 +105,9 @@ function findDerivedFields(projectRoot) {
|
|
|
95
105
|
}
|
|
96
106
|
return found;
|
|
97
107
|
}
|
|
98
|
-
/** Git's view of
|
|
99
|
-
function gitFileState(projectRoot) {
|
|
100
|
-
const rel = `.cleo/${
|
|
108
|
+
/** Git's view of a tracked identity file: `null` when this is not a git work tree. */
|
|
109
|
+
function gitFileState(projectRoot, file = PROJECT_MANIFEST_FILE) {
|
|
110
|
+
const rel = `.cleo/${file}`;
|
|
101
111
|
const run = (args) => {
|
|
102
112
|
try {
|
|
103
113
|
execFileSync('git', args, { cwd: projectRoot, stdio: 'pipe', timeout: GIT_PROBE_TIMEOUT_MS });
|
|
@@ -116,7 +126,20 @@ function gitFileState(projectRoot) {
|
|
|
116
126
|
return 'untracked';
|
|
117
127
|
}
|
|
118
128
|
/**
|
|
119
|
-
*
|
|
129
|
+
* The name a migrated `project.json` records: the cached `displayName` (a
|
|
130
|
+
* legacy rename), else the cached `name`, when valid; else the basename.
|
|
131
|
+
*/
|
|
132
|
+
function migrationName(projectRoot, info) {
|
|
133
|
+
if (info && info !== 'unparseable')
|
|
134
|
+
for (const field of ['displayName', 'name', 'projectName']) {
|
|
135
|
+
const name = info.data[field];
|
|
136
|
+
if (typeof name === 'string' && isValidProjectDisplayName(name))
|
|
137
|
+
return name;
|
|
138
|
+
}
|
|
139
|
+
return defaultProjectDisplayName(projectRoot);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Classify the identity sources of a project, read-only.
|
|
120
143
|
*
|
|
121
144
|
* @param projectRoot - Absolute project root.
|
|
122
145
|
* @returns The inspection with the exact remedy command.
|
|
@@ -128,26 +151,51 @@ function gitFileState(projectRoot) {
|
|
|
128
151
|
* ```
|
|
129
152
|
*/
|
|
130
153
|
export function inspectProjectIdentity(projectRoot) {
|
|
154
|
+
const manifest = readProjectManifest(projectRoot);
|
|
155
|
+
const legacy = readProjectIdFile(projectRoot);
|
|
131
156
|
const tracked = readPortableProjectId(projectRoot);
|
|
132
157
|
const info = readInfo(projectRoot);
|
|
133
158
|
const trackedId = tracked.status === 'valid' ? tracked.projectId : null;
|
|
134
159
|
const localId = info && info !== 'unparseable' ? info.id : null;
|
|
135
|
-
const
|
|
160
|
+
const manifestId = manifest.status === 'valid' ? manifest.manifest.id : null;
|
|
161
|
+
const legacyId = legacy.status === 'valid' ? legacy.projectId : null;
|
|
162
|
+
const base = {
|
|
163
|
+
projectRoot,
|
|
164
|
+
trackedId,
|
|
165
|
+
manifestId,
|
|
166
|
+
legacyId,
|
|
167
|
+
declaredName: manifest.status === 'valid' ? manifest.manifest.name : null,
|
|
168
|
+
localId,
|
|
169
|
+
derivedFields: findDerivedFields(projectRoot),
|
|
170
|
+
};
|
|
136
171
|
// A CLEO root need not be a git work tree; git remedies apply only inside one.
|
|
137
|
-
|
|
138
|
-
const gitState = () => {
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
return
|
|
172
|
+
const gitStates = new Map();
|
|
173
|
+
const gitState = (file = PROJECT_MANIFEST_FILE) => {
|
|
174
|
+
if (!gitStates.has(file))
|
|
175
|
+
gitStates.set(file, gitFileState(projectRoot, file));
|
|
176
|
+
return gitStates.get(file) ?? null;
|
|
142
177
|
};
|
|
143
178
|
if (tracked.status === 'invalid') {
|
|
179
|
+
const rel = `.cleo/${tracked.file ?? PORTABLE_PROJECT_ID_FILE}`;
|
|
144
180
|
return {
|
|
145
181
|
...base,
|
|
146
182
|
state: 'invalid',
|
|
147
|
-
message:
|
|
183
|
+
message: `${rel} is unusable (${tracked.reason}). CLEO never regenerates it.`,
|
|
184
|
+
remedy: gitState() === null
|
|
185
|
+
? `restore ${rel} from a backup of this project (the CLEO root is not a git work tree)`
|
|
186
|
+
: `git checkout -- ${rel} (restore the committed file; inspect with \`git log -p -- ${rel}\`)`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (manifestId && legacy.status !== 'absent' && legacyId !== manifestId) {
|
|
190
|
+
return {
|
|
191
|
+
...base,
|
|
192
|
+
state: 'mirror-conflict',
|
|
193
|
+
message: legacy.status === 'invalid'
|
|
194
|
+
? `.cleo/project-id is unusable (${legacy.reason}), while .cleo/project.json declares ${manifestId}. CLEO uses ${manifestId}; builds that read only .cleo/project-id cannot.`
|
|
195
|
+
: `.cleo/project.json declares ${manifestId} but .cleo/project-id declares ${legacyId}. CLEO uses ${manifestId}; builds that read only .cleo/project-id use ${legacyId}.`,
|
|
148
196
|
remedy: gitState() === null
|
|
149
|
-
? 'restore
|
|
150
|
-
: 'git
|
|
197
|
+
? 'restore both files from a backup of this project; CLEO never rewrites a tracked id'
|
|
198
|
+
: 'inspect `git log -p -- .cleo/project.json .cleo/project-id`, then restore the edited file with `git checkout <commit> -- <file>` (CLEO never rewrites a tracked id)',
|
|
151
199
|
};
|
|
152
200
|
}
|
|
153
201
|
if (info === undefined) {
|
|
@@ -159,7 +207,7 @@ export function inspectProjectIdentity(projectRoot) {
|
|
|
159
207
|
return {
|
|
160
208
|
...base,
|
|
161
209
|
state: 'not-adopted',
|
|
162
|
-
message:
|
|
210
|
+
message: `The tracked identity declares ${relocated.projectId}, which was rerooted from here to ${relocated.movedTo} (${relocated.via}).`,
|
|
163
211
|
remedy: `cd "${relocated.movedTo}" (the live project). A DIFFERENT project here needs a new id: \`cleo init --here --new-identity\``,
|
|
164
212
|
};
|
|
165
213
|
}
|
|
@@ -167,13 +215,13 @@ export function inspectProjectIdentity(projectRoot) {
|
|
|
167
215
|
? {
|
|
168
216
|
...base,
|
|
169
217
|
state: 'not-adopted',
|
|
170
|
-
message:
|
|
218
|
+
message: `The tracked identity declares ${trackedId}, but this checkout has not been initialized.`,
|
|
171
219
|
remedy: 'cleo init (adopts the tracked id; it never mints a new one)',
|
|
172
220
|
}
|
|
173
221
|
: {
|
|
174
222
|
...base,
|
|
175
223
|
state: 'uninitialized',
|
|
176
|
-
message: 'No project identity:
|
|
224
|
+
message: 'No project identity: none of .cleo/project.json, .cleo/project-id or .cleo/project-info.json exists.',
|
|
177
225
|
remedy: 'cleo init',
|
|
178
226
|
};
|
|
179
227
|
}
|
|
@@ -189,40 +237,60 @@ export function inspectProjectIdentity(projectRoot) {
|
|
|
189
237
|
return {
|
|
190
238
|
...base,
|
|
191
239
|
state: 'missing',
|
|
192
|
-
message:
|
|
240
|
+
message: `No tracked identity: .cleo/project.json is missing. The local id is ${localId}.`,
|
|
193
241
|
remedy: gitState() === null
|
|
194
|
-
? `${IDENTITY_COMMAND} --resolve (the CLEO root is not a git work tree; keep .cleo/project
|
|
195
|
-
: `${IDENTITY_COMMAND} --resolve && git add .cleo/project-id && git commit -m "chore: track CLEO project
|
|
242
|
+
? `${IDENTITY_COMMAND} --resolve (the CLEO root is not a git work tree; keep .cleo/project.json with the project)`
|
|
243
|
+
: `${IDENTITY_COMMAND} --resolve && git add .cleo/project.json .cleo/project-id && git commit -m "chore: track CLEO project identity"`,
|
|
196
244
|
};
|
|
197
245
|
}
|
|
198
246
|
if (trackedId !== localId) {
|
|
199
247
|
return {
|
|
200
248
|
...base,
|
|
201
249
|
state: 'conflict',
|
|
202
|
-
message: `Identity conflict:
|
|
250
|
+
message: `Identity conflict: the tracked id is ${trackedId}, but the project-info.json cache is ${localId}. Two lineages; CLEO uses ${trackedId}, while local state (registry row, aliases) is still keyed by ${localId}.`,
|
|
203
251
|
remedy: `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve (re-keys local state to ${trackedId}; ${localId} stays resolvable as an alias)`,
|
|
204
252
|
};
|
|
205
253
|
}
|
|
254
|
+
if (!manifestId) {
|
|
255
|
+
return {
|
|
256
|
+
...base,
|
|
257
|
+
state: 'legacy',
|
|
258
|
+
message: `Only the legacy .cleo/project-id records ${trackedId}; .cleo/project.json (id + name) does not exist yet.`,
|
|
259
|
+
remedy: gitState(PORTABLE_PROJECT_ID_FILE) === null
|
|
260
|
+
? `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve (writes .cleo/project.json; no id changes)`
|
|
261
|
+
: `${IDENTITY_COMMAND} --resolve --dry-run then ${IDENTITY_COMMAND} --resolve && git add .cleo/project.json .cleo/.gitignore && git commit -m "chore: track CLEO project.json" (no id changes)`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (!legacyId) {
|
|
265
|
+
return {
|
|
266
|
+
...base,
|
|
267
|
+
state: 'mirror-missing',
|
|
268
|
+
message: `.cleo/project.json records ${trackedId}, but the legacy .cleo/project-id mirror is missing, so builds that read only that file cannot resolve the id.`,
|
|
269
|
+
remedy: gitState() === null
|
|
270
|
+
? `${IDENTITY_COMMAND} --resolve (writes the mirror)`
|
|
271
|
+
: `${IDENTITY_COMMAND} --resolve && git add .cleo/project-id && git commit -m "chore: track CLEO project-id mirror"`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
206
274
|
if (gitState() === 'ignored') {
|
|
207
275
|
return {
|
|
208
276
|
...base,
|
|
209
277
|
state: 'ignored',
|
|
210
|
-
message: '.cleo/project
|
|
211
|
-
remedy:
|
|
278
|
+
message: '.cleo/project.json is gitignored, so clones cannot inherit it. The .cleo/.gitignore predates T12716.',
|
|
279
|
+
remedy: `${IDENTITY_COMMAND} --resolve (adds \`!project.json\` to .cleo/.gitignore; \`cleo upgrade\` refreshes the whole file), then git add .cleo/.gitignore .cleo/project.json .cleo/project-id`,
|
|
212
280
|
};
|
|
213
281
|
}
|
|
214
|
-
if (gitState() === 'untracked') {
|
|
282
|
+
if (gitState() === 'untracked' || gitState(PORTABLE_PROJECT_ID_FILE) === 'untracked') {
|
|
215
283
|
return {
|
|
216
284
|
...base,
|
|
217
285
|
state: 'untracked',
|
|
218
|
-
message: '
|
|
219
|
-
remedy: 'git add .cleo/project-id && git commit -m "chore: track CLEO project
|
|
286
|
+
message: 'The tracked identity files are not committed yet, so clones cannot inherit them.',
|
|
287
|
+
remedy: 'git add .cleo/project.json .cleo/project-id && git commit -m "chore: track CLEO project identity"',
|
|
220
288
|
};
|
|
221
289
|
}
|
|
222
290
|
return {
|
|
223
291
|
...base,
|
|
224
292
|
state: 'ok',
|
|
225
|
-
message: `Project identity ${trackedId} is tracked and consistent.`,
|
|
293
|
+
message: `Project identity ${trackedId} ("${base.declaredName}") is tracked and consistent.`,
|
|
226
294
|
remedy: null,
|
|
227
295
|
};
|
|
228
296
|
}
|
|
@@ -347,20 +415,81 @@ async function confirmCandidateCheckout(candidate, cleoHome) {
|
|
|
347
415
|
}, { behavior: 'immediate' });
|
|
348
416
|
}
|
|
349
417
|
/**
|
|
350
|
-
*
|
|
418
|
+
* If `.cleo/.gitignore` (a CLEO-managed file older than T12716) ignores
|
|
419
|
+
* `.cleo/project.json`, the surgical repair: insert `!project.json` beside
|
|
420
|
+
* `!project-id` (or append it). Nothing else in the file changes; a rule from
|
|
421
|
+
* any other ignore file is reported, never edited.
|
|
351
422
|
*
|
|
352
|
-
*
|
|
423
|
+
* @returns The step to report, or `null` when the file is not ignored.
|
|
424
|
+
*/
|
|
425
|
+
function allowManifestInGitignore(projectRoot, dryRun) {
|
|
426
|
+
const rel = `.cleo/${PROJECT_MANIFEST_FILE}`;
|
|
427
|
+
let source;
|
|
428
|
+
try {
|
|
429
|
+
source = execFileSync('git', ['check-ignore', '-v', '--no-index', rel], {
|
|
430
|
+
cwd: projectRoot,
|
|
431
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
432
|
+
timeout: GIT_PROBE_TIMEOUT_MS,
|
|
433
|
+
encoding: 'utf-8',
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return null; // not ignored (exit 1), or not a git work tree
|
|
438
|
+
}
|
|
439
|
+
// `<source>:<line>:<pattern>\t<path>`; a matching `!pattern` means NOT ignored.
|
|
440
|
+
const [ignoreFile = '', , pattern = ''] = (source.split('\t')[0] ?? '').split(':');
|
|
441
|
+
if (pattern.startsWith('!'))
|
|
442
|
+
return null;
|
|
443
|
+
const cleoIgnore = join(projectRoot, '.cleo', '.gitignore');
|
|
444
|
+
if (ignoreFile !== '.cleo/.gitignore' || !existsSync(cleoIgnore)) {
|
|
445
|
+
return {
|
|
446
|
+
refused: `${rel} is ignored by ${ignoreFile || 'a gitignore rule'} (${source.trim()}). Allow it there (\`!${rel}\`), then git add ${rel}.`,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const step = {
|
|
450
|
+
action: 'allow-project-json',
|
|
451
|
+
detail: `add \`!${PROJECT_MANIFEST_FILE}\` to .cleo/.gitignore (it predates T12716 and ignores ${rel}); commit it with ${rel}`,
|
|
452
|
+
};
|
|
453
|
+
if (!dryRun) {
|
|
454
|
+
const lines = readFileSync(cleoIgnore, 'utf-8').split('\n');
|
|
455
|
+
const at = lines.findIndex((line) => line.trim() === `!${PORTABLE_PROJECT_ID_FILE}`);
|
|
456
|
+
if (at >= 0)
|
|
457
|
+
lines.splice(at, 0, `!${PROJECT_MANIFEST_FILE}`);
|
|
458
|
+
else
|
|
459
|
+
lines.splice(lines.at(-1) === '' ? lines.length - 1 : lines.length, 0, `!${PROJECT_MANIFEST_FILE}`);
|
|
460
|
+
writeFileSync(cleoIgnore, lines.join('\n'));
|
|
461
|
+
}
|
|
462
|
+
return step;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Resolve the identity state that {@link inspectProjectIdentity} reported —
|
|
466
|
+
* the ONLY migration path from the legacy files to `.cleo/project.json`
|
|
467
|
+
* (T12716); nothing migrates on open, init or upgrade.
|
|
468
|
+
*
|
|
469
|
+
* - `legacy` writes `.cleo/project.json` (create-only) from the legacy
|
|
470
|
+
* `.cleo/project-id` id and the `project-info.json` name (else the
|
|
471
|
+
* basename). `project-id` stays as the legacy mirror; no id changes.
|
|
472
|
+
* - `missing` writes `.cleo/project.json` and the `project-id` mirror from the
|
|
473
|
+
* local id (create-only).
|
|
474
|
+
* - `mirror-missing` writes the legacy `.cleo/project-id` mirror (create-only).
|
|
475
|
+
* - Whenever `project.json` is (or is about to be) ignored by a pre-T12716
|
|
476
|
+
* `.cleo/.gitignore` — including the `ignored` state — `!project.json` is
|
|
477
|
+
* inserted there, so the file can be committed; nothing else in it changes.
|
|
353
478
|
* - `info-invalid` re-runs the scaffold decision (tracked id or registry re-link).
|
|
354
479
|
* - `conflict` re-keys local state to the tracked id. One registry transaction
|
|
355
480
|
* renames the row's primary key, re-points aliases and records the old id as
|
|
356
481
|
* an alias. Then `project-info.json` is rewritten, keeping the old id under
|
|
357
|
-
* `previousProjectIds
|
|
482
|
+
* `previousProjectIds`, and `project.json` is written when only the legacy
|
|
483
|
+
* file existed.
|
|
358
484
|
* - In every state, persisted `projectRoot` fields are stripped first
|
|
359
485
|
* (T12557). The removed values are kept in `project-info.json`
|
|
360
486
|
* `strippedFields`. `projectHash` is never touched.
|
|
487
|
+
* - Last, a registry label that drifted from the declared name is synced
|
|
488
|
+
* (see {@link inspectProjectNameDrift}), unless another project holds it.
|
|
361
489
|
*
|
|
362
|
-
* Every other state
|
|
363
|
-
* re-running after a partial
|
|
490
|
+
* Every other state (`invalid`, `mirror-conflict`, `untracked`, …) is refused
|
|
491
|
+
* with its remedy. The operation is idempotent: re-running after a partial
|
|
492
|
+
* apply completes the remaining steps.
|
|
364
493
|
*
|
|
365
494
|
* @param projectRoot - Absolute project root.
|
|
366
495
|
* @param options - `dryRun` and registry home.
|
|
@@ -373,6 +502,42 @@ async function confirmCandidateCheckout(candidate, cleoHome) {
|
|
|
373
502
|
* ```
|
|
374
503
|
*/
|
|
375
504
|
export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
505
|
+
const resolution = await resolveIdentityFiles(projectRoot, options);
|
|
506
|
+
// A refusal with a remedy (invalid, mirror-conflict, …) stops here.
|
|
507
|
+
if (resolution.refused && resolution.refused !== NOTHING_TO_RESOLVE)
|
|
508
|
+
return resolution;
|
|
509
|
+
// Compare against the name project.json holds after the file steps, so a
|
|
510
|
+
// dry-run plans exactly what an apply does.
|
|
511
|
+
const writesManifest = resolution.steps.some((step) => step.action === 'write-project-json' || step.action === 'write-tracked-id');
|
|
512
|
+
// A dry-run conflict has not re-keyed yet: its row is still keyed by the
|
|
513
|
+
// cached id, while the apply compares after the re-key. Look the row up by
|
|
514
|
+
// the id it holds NOW, so both see the same row (review finding 2).
|
|
515
|
+
const rekeyPending = options.dryRun === true &&
|
|
516
|
+
resolution.steps.some((step) => step.action === 'rekey-registry-row');
|
|
517
|
+
const drift = await inspectProjectNameDrift(projectRoot, options.cleoHome, writesManifest ? migrationName(projectRoot, readInfo(projectRoot)) : undefined, rekeyPending ? (resolution.before.localId ?? undefined) : undefined);
|
|
518
|
+
if (drift.state !== 'drift' || drift.projectId === null || drift.declaredName === null)
|
|
519
|
+
return resolution;
|
|
520
|
+
const steps = [
|
|
521
|
+
...resolution.steps,
|
|
522
|
+
{
|
|
523
|
+
action: 'sync-registry-name',
|
|
524
|
+
detail: `registry label '${drift.registryName}' -> '${drift.declaredName}' (the declared name)`,
|
|
525
|
+
},
|
|
526
|
+
];
|
|
527
|
+
if (options.dryRun !== true) {
|
|
528
|
+
const { nexusRenameProject } = await import('../nexus/registry.js');
|
|
529
|
+
const { generateProjectHash } = await import('../nexus/hash.js');
|
|
530
|
+
const { worktreeScope } = await import('../project-scope.js');
|
|
531
|
+
const { projectId, declaredName } = drift;
|
|
532
|
+
// Scoped to the explicit root, so no ambient project owns the write.
|
|
533
|
+
await worktreeScope.run({ worktreeRoot: projectRoot, projectHash: generateProjectHash(projectRoot) }, () => nexusRenameProject(projectId, declaredName));
|
|
534
|
+
}
|
|
535
|
+
return { ...resolution, steps, refused: null };
|
|
536
|
+
}
|
|
537
|
+
/** Refusal text when nothing needs resolving. */
|
|
538
|
+
const NOTHING_TO_RESOLVE = 'Nothing to resolve.';
|
|
539
|
+
/** The file steps of {@link resolveProjectIdentity}, before the name sync. */
|
|
540
|
+
async function resolveIdentityFiles(projectRoot, options) {
|
|
376
541
|
const dryRun = options.dryRun === true;
|
|
377
542
|
const before = inspectProjectIdentity(projectRoot);
|
|
378
543
|
const steps = [];
|
|
@@ -407,20 +572,71 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
407
572
|
if (!dryRun && !stripDerivedFields(projectRoot, before.derivedFields, new Date().toISOString()))
|
|
408
573
|
return result('Cannot record a strip receipt: .cleo/project-info.json is missing or unreadable.');
|
|
409
574
|
}
|
|
575
|
+
const name = migrationName(projectRoot, readInfo(projectRoot));
|
|
576
|
+
// A migrated or ignored project.json must be committable (T12716).
|
|
577
|
+
const allowStep = () => {
|
|
578
|
+
const allowed = allowManifestInGitignore(projectRoot, dryRun);
|
|
579
|
+
if (allowed === null)
|
|
580
|
+
return null;
|
|
581
|
+
if ('refused' in allowed)
|
|
582
|
+
return allowed.refused;
|
|
583
|
+
steps.push(allowed);
|
|
584
|
+
return null;
|
|
585
|
+
};
|
|
586
|
+
// T12716: the migration. Create-only; the id is copied, never changed.
|
|
587
|
+
const writeManifestStep = async (projectId) => {
|
|
588
|
+
steps.push({
|
|
589
|
+
action: 'write-project-json',
|
|
590
|
+
detail: `create .cleo/project.json = {id: ${projectId}, name: ${JSON.stringify(name)}} (.cleo/project-id stays as the legacy mirror; commit .cleo/project.json)`,
|
|
591
|
+
});
|
|
592
|
+
if (dryRun)
|
|
593
|
+
return null;
|
|
594
|
+
const outcome = await createProjectManifest(projectRoot, projectId, name);
|
|
595
|
+
return outcome === 'written' || outcome === 'present'
|
|
596
|
+
? null
|
|
597
|
+
: `Cannot write .cleo/project.json (${outcome}).`;
|
|
598
|
+
};
|
|
410
599
|
if (before.state === 'missing' && before.localId) {
|
|
411
600
|
steps.push({
|
|
412
601
|
action: 'write-tracked-id',
|
|
413
|
-
detail: `create .cleo/project
|
|
602
|
+
detail: `create .cleo/project.json = {id: ${before.localId}, name: ${JSON.stringify(name)}} and the .cleo/project-id mirror`,
|
|
414
603
|
});
|
|
415
604
|
if (!dryRun)
|
|
416
|
-
await ensurePortableProjectId(projectRoot, before.localId);
|
|
605
|
+
await ensurePortableProjectId(projectRoot, before.localId, name);
|
|
606
|
+
const ignored = allowStep();
|
|
607
|
+
await confirmCandidateStep();
|
|
608
|
+
return result(ignored);
|
|
609
|
+
}
|
|
610
|
+
if (before.state === 'legacy' && before.legacyId) {
|
|
611
|
+
const failed = await writeManifestStep(before.legacyId);
|
|
612
|
+
if (failed)
|
|
613
|
+
return result(failed);
|
|
614
|
+
const ignored = allowStep();
|
|
615
|
+
await confirmCandidateStep();
|
|
616
|
+
return result(ignored);
|
|
617
|
+
}
|
|
618
|
+
if (before.state === 'ignored') {
|
|
619
|
+
const ignored = allowStep();
|
|
620
|
+
await confirmCandidateStep();
|
|
621
|
+
return result(ignored);
|
|
622
|
+
}
|
|
623
|
+
if (before.state === 'mirror-missing' && before.manifestId) {
|
|
624
|
+
steps.push({
|
|
625
|
+
action: 'write-legacy-mirror',
|
|
626
|
+
detail: `create .cleo/project-id = ${before.manifestId} (for builds that read only that file; commit it)`,
|
|
627
|
+
});
|
|
628
|
+
if (!dryRun) {
|
|
629
|
+
const outcome = await createProjectIdMirror(projectRoot, before.manifestId);
|
|
630
|
+
if (outcome !== 'written' && outcome !== 'present')
|
|
631
|
+
return result(`Cannot write .cleo/project-id (${outcome}).`);
|
|
632
|
+
}
|
|
417
633
|
await confirmCandidateStep();
|
|
418
634
|
return result(null);
|
|
419
635
|
}
|
|
420
636
|
if (before.state === 'info-invalid') {
|
|
421
637
|
steps.push({
|
|
422
638
|
action: 'rewrite-project-info',
|
|
423
|
-
detail: 'regenerate the project-info.json id from
|
|
639
|
+
detail: 'regenerate the project-info.json id from the tracked identity or the registry',
|
|
424
640
|
});
|
|
425
641
|
if (!dryRun) {
|
|
426
642
|
const { ensureProjectInfo } = await import('../scaffold/ensure-config.js');
|
|
@@ -434,13 +650,14 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
434
650
|
if (before.state !== 'conflict' || !before.trackedId || !before.localId) {
|
|
435
651
|
if (before.remedy)
|
|
436
652
|
return result(`${before.message} Remedy: ${before.remedy}`);
|
|
437
|
-
return result(steps.length > 0 ? null :
|
|
653
|
+
return result(steps.length > 0 ? null : NOTHING_TO_RESOLVE);
|
|
438
654
|
}
|
|
439
655
|
const oldId = before.localId;
|
|
440
656
|
const newId = before.trackedId;
|
|
441
657
|
const { getNexusRegistryDb } = await import('../store/nexus-sqlite.js');
|
|
442
|
-
const { projectIdAliases, projectRegistry } = await import('../store/schema/nexus-schema.js');
|
|
443
|
-
const { eq } = await import('drizzle-orm');
|
|
658
|
+
const { nexusAuditLog, projectGitState, projectIdAliases, projectLocations, projectRegistry } = await import('../store/schema/nexus-schema.js');
|
|
659
|
+
const { and, eq } = await import('drizzle-orm');
|
|
660
|
+
const { isSupersededRegistryPath, projectHolding } = await import('../nexus/path-map.js');
|
|
444
661
|
const db = await getNexusRegistryDb(options.cleoHome ?? getCleoHome());
|
|
445
662
|
const countRows = () => db.select().from(projectRegistry).all().length;
|
|
446
663
|
const rowsBefore = countRows();
|
|
@@ -454,10 +671,25 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
454
671
|
.from(projectRegistry)
|
|
455
672
|
.where(eq(projectRegistry.projectId, newId))
|
|
456
673
|
.get();
|
|
674
|
+
// Both ids registered: the cached id was registered before the tracked file
|
|
675
|
+
// arrived, and a later encounter registered the tracked id (review finding
|
|
676
|
+
// 5). When the cached-id row is THIS checkout, or its path no longer holds
|
|
677
|
+
// the cached id, the rows are one project: fold the cached-id row into the
|
|
678
|
+
// tracked row. Its content is kept whole in a nexus audit receipt, its
|
|
679
|
+
// locations move to the tracked id, and the cached id stays resolvable
|
|
680
|
+
// through the alias. A cached-id row still live elsewhere is a second
|
|
681
|
+
// lineage and is refused, never unregistered.
|
|
682
|
+
const merge = Boolean(oldRow && newRow);
|
|
457
683
|
if (oldRow && newRow) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
684
|
+
const here = canonicalizePath(projectRoot);
|
|
685
|
+
const elsewhereLive = oldRow.projectPath !== here &&
|
|
686
|
+
!isSupersededRegistryPath(oldRow.projectPath) &&
|
|
687
|
+
projectHolding(oldRow.projectPath, oldId) !== 'no';
|
|
688
|
+
if (elsewhereLive) {
|
|
689
|
+
return result(`Both ids are registered on this device, and ${oldId} is still declared by ${oldRow.projectPath}. ` +
|
|
690
|
+
'That is a second checkout of the old lineage; nothing was changed. ' +
|
|
691
|
+
`Remedy: run \`${IDENTITY_COMMAND} --resolve --dry-run\` in "${oldRow.projectPath}" first (it adopts the tracked id there), then re-run it here.`, { before: rowsBefore, after: rowsBefore });
|
|
692
|
+
}
|
|
461
693
|
}
|
|
462
694
|
const newIdAlias = db
|
|
463
695
|
.select()
|
|
@@ -473,7 +705,12 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
473
705
|
.from(projectIdAliases)
|
|
474
706
|
.where(eq(projectIdAliases.canonicalId, oldId))
|
|
475
707
|
.all();
|
|
476
|
-
if (oldRow)
|
|
708
|
+
if (merge && oldRow && newRow)
|
|
709
|
+
steps.push({
|
|
710
|
+
action: 'merge-registry-row',
|
|
711
|
+
detail: `fold registry row ${oldId} (path ${oldRow.projectPath}, permissions ${oldRow.permissions}) into ${newId} (path ${newRow.projectPath} kept; stronger permissions and earlier registeredAt carried over); the whole row is kept in a nexus audit receipt and ${oldId} resolves through the alias`,
|
|
712
|
+
});
|
|
713
|
+
else if (oldRow)
|
|
477
714
|
steps.push({
|
|
478
715
|
action: 'rekey-registry-row',
|
|
479
716
|
detail: `registry row ${oldId} -> ${newId} (path ${oldRow.projectPath} kept)`,
|
|
@@ -492,15 +729,80 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
492
729
|
action: 'rewrite-project-info',
|
|
493
730
|
detail: `project-info.json projectId ${oldId} -> ${newId} (previousProjectIds keeps ${oldId})`,
|
|
494
731
|
});
|
|
495
|
-
|
|
496
|
-
|
|
732
|
+
// Credentials sealed under the old id are re-wrapped eagerly, not on their
|
|
733
|
+
// next open (review finding 4). The step is the receipt: every row named.
|
|
734
|
+
const rewrapStep = async () => {
|
|
735
|
+
const { migrateProjectCredentialsAtRoot } = await import('../store/credential-transfer.js');
|
|
736
|
+
const plan = await migrateProjectCredentialsAtRoot(projectRoot, {
|
|
737
|
+
dryRun,
|
|
738
|
+
...(options.cleoHome !== undefined && { cleoHome: options.cleoHome }),
|
|
739
|
+
});
|
|
740
|
+
if (plan.migrated.length === 0 && plan.reentry.length === 0)
|
|
741
|
+
return;
|
|
742
|
+
steps.push({
|
|
743
|
+
action: 'rewrap-credentials',
|
|
744
|
+
detail: `re-wrap ${plan.migrated.length} credential(s) under ${newId}: ${plan.migrated.map((c) => `${c.store}:${c.id}`).join(', ') || 'none'}${plan.reentry.length > 0 ? `; ${plan.reentry.length} cannot be opened and must be re-entered: ${plan.reentry.map((r) => `${r.store}:${r.id}`).join(', ')}` : ''}`,
|
|
745
|
+
});
|
|
746
|
+
};
|
|
747
|
+
if (dryRun) {
|
|
748
|
+
await rewrapStep();
|
|
749
|
+
if (!before.manifestId)
|
|
750
|
+
await writeManifestStep(newId);
|
|
751
|
+
return result(before.manifestId ? null : allowStep(), {
|
|
752
|
+
before: rowsBefore,
|
|
753
|
+
after: rowsBefore,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
497
756
|
const now = new Date().toISOString();
|
|
757
|
+
const foldedLocations = [projectLocations, projectGitState].flatMap((table) => db.select().from(table).where(eq(table.projectId, oldId)).all());
|
|
758
|
+
const rank = { read: 0, write: 1, execute: 2 };
|
|
759
|
+
const strongest = (a, b) => (rank[a] ?? 0) >= (rank[b] ?? 0) ? a : b;
|
|
498
760
|
db.transaction((tx) => {
|
|
499
|
-
if (oldRow)
|
|
761
|
+
if (merge && oldRow && newRow) {
|
|
762
|
+
tx.insert(nexusAuditLog)
|
|
763
|
+
.values({
|
|
764
|
+
id: randomUUID(),
|
|
765
|
+
action: 'merge-identity',
|
|
766
|
+
projectHash: newRow.projectHash,
|
|
767
|
+
projectId: newId,
|
|
768
|
+
domain: 'nexus',
|
|
769
|
+
operation: 'doctor.project-identity.resolve',
|
|
770
|
+
source: 'cleo doctor project-identity --resolve',
|
|
771
|
+
success: 1,
|
|
772
|
+
detailsJson: JSON.stringify({ foldedRow: oldRow, foldedLocations, into: newId }),
|
|
773
|
+
})
|
|
774
|
+
.run();
|
|
775
|
+
tx.update(projectRegistry)
|
|
776
|
+
.set({
|
|
777
|
+
permissions: strongest(newRow.permissions, oldRow.permissions),
|
|
778
|
+
registeredAt: oldRow.registeredAt < newRow.registeredAt ? oldRow.registeredAt : newRow.registeredAt,
|
|
779
|
+
})
|
|
780
|
+
.where(eq(projectRegistry.projectId, newId))
|
|
781
|
+
.run();
|
|
782
|
+
tx.delete(projectRegistry).where(eq(projectRegistry.projectId, oldId)).run();
|
|
783
|
+
}
|
|
784
|
+
else if (oldRow)
|
|
500
785
|
tx.update(projectRegistry)
|
|
501
786
|
.set({ projectId: newId })
|
|
502
787
|
.where(eq(projectRegistry.projectId, oldId))
|
|
503
788
|
.run();
|
|
789
|
+
// Per-checkout rows follow the id. A checkout the tracked id already
|
|
790
|
+
// records keeps the tracked row (earliest firstSeen carried over); the
|
|
791
|
+
// duplicate is in the receipt, so nothing is lost.
|
|
792
|
+
for (const table of [projectLocations, projectGitState]) {
|
|
793
|
+
for (const row of tx.select().from(table).where(eq(table.projectId, oldId)).all()) {
|
|
794
|
+
const at = (id) => and(eq(table.projectId, id), eq(table.deviceId, row.deviceId), eq(table.path, row.path));
|
|
795
|
+
const taken = tx.select().from(table).where(at(newId)).get();
|
|
796
|
+
if (!taken) {
|
|
797
|
+
tx.update(table).set({ projectId: newId }).where(at(oldId)).run();
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (table === projectLocations && 'firstSeen' in row && 'firstSeen' in taken)
|
|
801
|
+
if (row.firstSeen < taken.firstSeen)
|
|
802
|
+
tx.update(projectLocations).set({ firstSeen: row.firstSeen }).where(at(newId)).run();
|
|
803
|
+
tx.delete(table).where(at(oldId)).run();
|
|
804
|
+
}
|
|
805
|
+
}
|
|
504
806
|
if (newIdAlias?.canonicalId === oldId)
|
|
505
807
|
tx.delete(projectIdAliases).where(eq(projectIdAliases.legacyId, newId)).run();
|
|
506
808
|
tx.update(projectIdAliases)
|
|
@@ -529,6 +831,117 @@ export async function resolveProjectIdentity(projectRoot, options = {}) {
|
|
|
529
831
|
lastUpdated: now,
|
|
530
832
|
});
|
|
531
833
|
}
|
|
532
|
-
|
|
834
|
+
await rewrapStep();
|
|
835
|
+
const manifestFailure = before.manifestId
|
|
836
|
+
? null
|
|
837
|
+
: ((await writeManifestStep(newId)) ?? allowStep());
|
|
838
|
+
return result(manifestFailure, { before: rowsBefore, after: countRows() });
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Compare the global registry's label for this project with the name the
|
|
842
|
+
* project declares in its committed `.cleo/project.json` (T12716), read-only.
|
|
843
|
+
* Only a committed name counts: a legacy project's cached name or basename
|
|
844
|
+
* differs per checkout, so it is never a drift source.
|
|
845
|
+
*
|
|
846
|
+
* @param projectRoot - Absolute project root.
|
|
847
|
+
* @param cleoHome - Global CLEO home whose registry is read.
|
|
848
|
+
* @param plannedName - The name `project.json` will hold once a planned
|
|
849
|
+
* migration writes it (used by `--resolve` so its dry-run matches the apply).
|
|
850
|
+
* @param rowId - The id the registry row is keyed by right now, when it
|
|
851
|
+
* differs from the declared id (a conflict whose re-key is only planned).
|
|
852
|
+
* @returns The drift report; an unreadable registry is `unavailable`, never `ok`.
|
|
853
|
+
*
|
|
854
|
+
* @example
|
|
855
|
+
* ```ts
|
|
856
|
+
* const drift = await inspectProjectNameDrift('/repo');
|
|
857
|
+
* if (drift.state === 'drift') console.log(drift.remedy);
|
|
858
|
+
* ```
|
|
859
|
+
* @task T12716
|
|
860
|
+
*/
|
|
861
|
+
export async function inspectProjectNameDrift(projectRoot, cleoHome, plannedName, rowId) {
|
|
862
|
+
const declared = readDeclaredProjectIdentity(projectRoot);
|
|
863
|
+
const declaredName = declared?.name ?? plannedName ?? null;
|
|
864
|
+
if (!declared) {
|
|
865
|
+
return {
|
|
866
|
+
state: 'undeclared',
|
|
867
|
+
projectId: null,
|
|
868
|
+
declaredName,
|
|
869
|
+
registryName: null,
|
|
870
|
+
message: 'The project declares no identity, so no registry row can be compared.',
|
|
871
|
+
remedy: 'cleo init',
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
const { getNexusRegistryDb } = await import('../store/nexus-sqlite.js');
|
|
876
|
+
const { projectRegistry } = await import('../store/schema/nexus-schema.js');
|
|
877
|
+
const { eq } = await import('drizzle-orm');
|
|
878
|
+
if (declaredName === null) {
|
|
879
|
+
return {
|
|
880
|
+
state: 'no-declared-name',
|
|
881
|
+
projectId: declared.projectId,
|
|
882
|
+
declaredName,
|
|
883
|
+
registryName: null,
|
|
884
|
+
message: 'No .cleo/project.json yet, so the project declares no committed name.',
|
|
885
|
+
remedy: null,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const db = await getNexusRegistryDb(cleoHome ?? getCleoHome());
|
|
889
|
+
const row = db
|
|
890
|
+
.select({ name: projectRegistry.name })
|
|
891
|
+
.from(projectRegistry)
|
|
892
|
+
.where(eq(projectRegistry.projectId, rowId ?? declared.projectId))
|
|
893
|
+
.get();
|
|
894
|
+
const base = { projectId: declared.projectId, declaredName };
|
|
895
|
+
if (!row) {
|
|
896
|
+
return {
|
|
897
|
+
...base,
|
|
898
|
+
state: 'not-registered',
|
|
899
|
+
registryName: null,
|
|
900
|
+
message: `No registry row for ${declared.projectId} on this device.`,
|
|
901
|
+
remedy: null,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
if (row.name === declaredName) {
|
|
905
|
+
return {
|
|
906
|
+
...base,
|
|
907
|
+
state: 'ok',
|
|
908
|
+
registryName: row.name,
|
|
909
|
+
message: `The registry label matches the declared name "${declaredName}".`,
|
|
910
|
+
remedy: null,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
const holder = db
|
|
914
|
+
.select({ projectId: projectRegistry.projectId })
|
|
915
|
+
.from(projectRegistry)
|
|
916
|
+
.where(eq(projectRegistry.name, declaredName))
|
|
917
|
+
.all()
|
|
918
|
+
.find((other) => other.projectId !== (rowId ?? declared.projectId));
|
|
919
|
+
if (holder) {
|
|
920
|
+
return {
|
|
921
|
+
...base,
|
|
922
|
+
state: 'taken',
|
|
923
|
+
registryName: row.name,
|
|
924
|
+
message: `The registry labels this project "${row.name}", but it declares "${declaredName}", which project ${holder.projectId} already uses on this device.`,
|
|
925
|
+
remedy: `cleo project rename <a-unique-name> (here, or in the project ${holder.projectId})`,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
...base,
|
|
930
|
+
state: 'drift',
|
|
931
|
+
registryName: row.name,
|
|
932
|
+
message: `The registry labels this project "${row.name}", but it declares "${declaredName}".`,
|
|
933
|
+
remedy: `${IDENTITY_COMMAND} --resolve (syncs the registry label to the declared name)`,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
catch (error) {
|
|
937
|
+
return {
|
|
938
|
+
state: 'unavailable',
|
|
939
|
+
projectId: declared.projectId,
|
|
940
|
+
declaredName,
|
|
941
|
+
registryName: null,
|
|
942
|
+
message: `Registry unavailable, name drift not checked: ${error instanceof Error ? error.message : String(error)}`,
|
|
943
|
+
remedy: null,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
533
946
|
}
|
|
534
947
|
//# sourceMappingURL=project-identity.js.map
|