@cleocode/core 2026.10.21 → 2026.10.22-rc.ci.693
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/STABILITY.md +1 -1
- package/dist/build-provenance.json +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/docs/document-edges.d.ts +38 -0
- package/dist/docs/document-edges.d.ts.map +1 -0
- package/dist/docs/document-edges.js +62 -0
- package/dist/docs/document-edges.js.map +1 -0
- package/dist/docs/projection.d.ts +3 -2
- package/dist/docs/projection.d.ts.map +1 -1
- package/dist/docs/projection.js +33 -36
- package/dist/docs/projection.js.map +1 -1
- package/dist/events/event-bus.d.ts +6 -5
- package/dist/events/event-bus.d.ts.map +1 -1
- package/dist/events/event-bus.js +9 -8
- package/dist/events/event-bus.js.map +1 -1
- package/dist/gateway-client/generated/index.d.ts +2 -2
- package/dist/gateway-client/generated/index.d.ts.map +1 -1
- package/dist/gateway-client/generated/index.js +1 -1
- package/dist/gateway-client/generated/index.js.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.d.ts +1 -0
- package/dist/gateway-client/generated/namespaces.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/namespaces.gen.js +2 -1
- package/dist/gateway-client/generated/namespaces.gen.js.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.d.ts +5 -1
- package/dist/gateway-client/generated/sdk.gen.d.ts.map +1 -1
- package/dist/gateway-client/generated/sdk.gen.js +11 -0
- package/dist/gateway-client/generated/sdk.gen.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +34 -0
- package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
- package/dist/git/base-ref.d.ts +81 -0
- package/dist/git/base-ref.d.ts.map +1 -0
- package/dist/git/base-ref.js +135 -0
- package/dist/git/base-ref.js.map +1 -0
- package/dist/git/hooks-install.d.ts +1 -1
- package/dist/git/hooks-install.d.ts.map +1 -1
- package/dist/git/work-tree.d.ts +40 -0
- package/dist/git/work-tree.d.ts.map +1 -1
- package/dist/git/work-tree.js +62 -7
- package/dist/git/work-tree.js.map +1 -1
- package/dist/hooks/handlers/conduit-hooks.d.ts +2 -1
- package/dist/hooks/handlers/conduit-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/conduit-hooks.js +9 -23
- package/dist/hooks/handlers/conduit-hooks.js.map +1 -1
- package/dist/hooks/handlers/index.d.ts +0 -1
- package/dist/hooks/handlers/index.d.ts.map +1 -1
- package/dist/hooks/handlers/index.js +0 -2
- package/dist/hooks/handlers/index.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +5 -12
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +2 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle/index.d.ts +15 -1
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +12 -6
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/stage-guidance.js +1 -1
- package/dist/lifecycle/stage-guidance.js.map +1 -1
- package/dist/memory/auto-row-quarantine.d.ts +196 -0
- package/dist/memory/auto-row-quarantine.d.ts.map +1 -0
- package/dist/memory/auto-row-quarantine.js +363 -0
- package/dist/memory/auto-row-quarantine.js.map +1 -0
- package/dist/memory/auto-row-rules.d.ts +29 -0
- package/dist/memory/auto-row-rules.d.ts.map +1 -0
- package/dist/memory/auto-row-rules.js +55 -0
- package/dist/memory/auto-row-rules.js.map +1 -0
- package/dist/memory/brain-lifecycle.d.ts.map +1 -1
- package/dist/memory/brain-lifecycle.js +13 -8
- package/dist/memory/brain-lifecycle.js.map +1 -1
- package/dist/memory/brain-search.d.ts +49 -29
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-search.js +159 -156
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/brain-similarity.d.ts +27 -4
- package/dist/memory/brain-similarity.d.ts.map +1 -1
- package/dist/memory/brain-similarity.js +41 -20
- package/dist/memory/brain-similarity.js.map +1 -1
- package/dist/memory/brain-stub-prune.d.ts +13 -0
- package/dist/memory/brain-stub-prune.d.ts.map +1 -1
- package/dist/memory/brain-stub-prune.js +14 -3
- package/dist/memory/brain-stub-prune.js.map +1 -1
- package/dist/memory/brain-writer-handlers.d.ts +3 -7
- package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
- package/dist/memory/brain-writer-handlers.js +5 -20
- package/dist/memory/brain-writer-handlers.js.map +1 -1
- package/dist/memory/brain-writer-thread.d.ts +2 -11
- package/dist/memory/brain-writer-thread.d.ts.map +1 -1
- package/dist/memory/brain-writer-thread.js +20 -78
- package/dist/memory/brain-writer-thread.js.map +1 -1
- package/dist/memory/brain-writer-worker.js +1 -8
- package/dist/memory/brain-writer-worker.js.map +1 -1
- package/dist/memory/dispatch-trace-format.d.ts +13 -14
- package/dist/memory/dispatch-trace-format.d.ts.map +1 -1
- package/dist/memory/dispatch-trace-format.js +13 -14
- package/dist/memory/dispatch-trace-format.js.map +1 -1
- package/dist/memory/eligibility.d.ts +22 -0
- package/dist/memory/eligibility.d.ts.map +1 -1
- package/dist/memory/eligibility.js +36 -0
- package/dist/memory/eligibility.js.map +1 -1
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +11 -1
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/evidence-graph-links.d.ts +253 -0
- package/dist/memory/evidence-graph-links.d.ts.map +1 -0
- package/dist/memory/evidence-graph-links.js +554 -0
- package/dist/memory/evidence-graph-links.js.map +1 -0
- package/dist/memory/extraction-gate.d.ts +7 -0
- package/dist/memory/extraction-gate.d.ts.map +1 -1
- package/dist/memory/extraction-gate.js +20 -8
- package/dist/memory/extraction-gate.js.map +1 -1
- package/dist/memory/global-brain.d.ts +4 -6
- package/dist/memory/global-brain.d.ts.map +1 -1
- package/dist/memory/global-brain.js +15 -2
- package/dist/memory/global-brain.js.map +1 -1
- package/dist/memory/graph-auto-populate.d.ts +73 -1
- package/dist/memory/graph-auto-populate.d.ts.map +1 -1
- package/dist/memory/graph-auto-populate.js +111 -8
- package/dist/memory/graph-auto-populate.js.map +1 -1
- package/dist/memory/import-from-provider.d.ts +19 -6
- package/dist/memory/import-from-provider.d.ts.map +1 -1
- package/dist/memory/import-from-provider.js +28 -15
- package/dist/memory/import-from-provider.js.map +1 -1
- package/dist/memory/lessons-import.d.ts +175 -0
- package/dist/memory/lessons-import.d.ts.map +1 -0
- package/dist/memory/lessons-import.js +233 -0
- package/dist/memory/lessons-import.js.map +1 -0
- package/dist/memory/lessons.d.ts +67 -0
- package/dist/memory/lessons.d.ts.map +1 -0
- package/dist/memory/lessons.js +121 -0
- package/dist/memory/lessons.js.map +1 -0
- package/dist/memory/memory-bridge.d.ts.map +1 -1
- package/dist/memory/memory-bridge.js +3 -2
- package/dist/memory/memory-bridge.js.map +1 -1
- package/dist/memory/public-api.d.ts.map +1 -1
- package/dist/memory/public-api.js +7 -6
- package/dist/memory/public-api.js.map +1 -1
- package/dist/memory/recall-ranking.d.ts +88 -0
- package/dist/memory/recall-ranking.d.ts.map +1 -0
- package/dist/memory/recall-ranking.js +130 -0
- package/dist/memory/recall-ranking.js.map +1 -0
- package/dist/memory/retrieval/build-retrieval-bundle.js +5 -5
- package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
- package/dist/memory/retrieval/fetch.d.ts +3 -2
- package/dist/memory/retrieval/fetch.d.ts.map +1 -1
- package/dist/memory/retrieval/fetch.js +4 -3
- package/dist/memory/retrieval/fetch.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts +1 -7
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +9 -58
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/retrieval/search.d.ts +5 -6
- package/dist/memory/retrieval/search.d.ts.map +1 -1
- package/dist/memory/retrieval/search.js +38 -102
- package/dist/memory/retrieval/search.js.map +1 -1
- package/dist/memory/retrieval/telemetry.d.ts +5 -16
- package/dist/memory/retrieval/telemetry.d.ts.map +1 -1
- package/dist/memory/retrieval/telemetry.js +5 -22
- package/dist/memory/retrieval/telemetry.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts +15 -0
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +53 -6
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/brief-map.d.ts +60 -0
- package/dist/nexus/brief-map.d.ts.map +1 -0
- package/dist/nexus/brief-map.js +234 -0
- package/dist/nexus/brief-map.js.map +1 -0
- package/dist/nexus/freshness.d.ts +128 -13
- package/dist/nexus/freshness.d.ts.map +1 -1
- package/dist/nexus/freshness.js +374 -86
- package/dist/nexus/freshness.js.map +1 -1
- package/dist/nexus/graph-manifest.d.ts +47 -0
- package/dist/nexus/graph-manifest.d.ts.map +1 -1
- package/dist/nexus/graph-manifest.js +69 -0
- package/dist/nexus/graph-manifest.js.map +1 -1
- package/dist/nexus/hooks-augment.d.ts.map +1 -1
- package/dist/nexus/hooks-augment.js +7 -3
- package/dist/nexus/hooks-augment.js.map +1 -1
- package/dist/nexus/index-health.d.ts +112 -0
- package/dist/nexus/index-health.d.ts.map +1 -0
- package/dist/nexus/index-health.js +199 -0
- package/dist/nexus/index-health.js.map +1 -0
- package/dist/nexus/living-brain.d.ts.map +1 -1
- package/dist/nexus/living-brain.js +2 -15
- package/dist/nexus/living-brain.js.map +1 -1
- package/dist/nexus/pid-lock.d.ts +50 -0
- package/dist/nexus/pid-lock.d.ts.map +1 -0
- package/dist/nexus/pid-lock.js +135 -0
- package/dist/nexus/pid-lock.js.map +1 -0
- package/dist/nexus/projects-clean.d.ts.map +1 -1
- package/dist/nexus/projects-clean.js +33 -2
- package/dist/nexus/projects-clean.js.map +1 -1
- package/dist/nexus/refresh-child.js +4 -0
- package/dist/nexus/refresh-child.js.map +1 -1
- package/dist/nexus/registry-host.d.ts +70 -0
- package/dist/nexus/registry-host.d.ts.map +1 -0
- package/dist/nexus/registry-host.js +79 -0
- package/dist/nexus/registry-host.js.map +1 -0
- package/dist/nexus/registry.d.ts +37 -0
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +49 -1
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/reindex-on-merge.d.ts +94 -0
- package/dist/nexus/reindex-on-merge.d.ts.map +1 -0
- package/dist/nexus/reindex-on-merge.js +192 -0
- package/dist/nexus/reindex-on-merge.js.map +1 -0
- package/dist/orchestrate/handoff-ops.d.ts.map +1 -1
- package/dist/orchestrate/handoff-ops.js +1 -13
- package/dist/orchestrate/handoff-ops.js.map +1 -1
- package/dist/orchestrate/index.d.ts +2 -3
- package/dist/orchestrate/index.d.ts.map +1 -1
- package/dist/orchestrate/index.js +2 -2
- package/dist/orchestrate/index.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.d.ts +3 -0
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +5 -5
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/spawn-ops.d.ts +8 -28
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +8 -67
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +7 -0
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts +8 -29
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +44 -112
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/spawn.d.ts +7 -14
- package/dist/orchestration/spawn.d.ts.map +1 -1
- package/dist/orchestration/spawn.js +2 -5
- package/dist/orchestration/spawn.js.map +1 -1
- package/dist/orchestration/wave-topic.d.ts +0 -13
- package/dist/orchestration/wave-topic.d.ts.map +1 -1
- package/dist/orchestration/wave-topic.js +0 -20
- package/dist/orchestration/wave-topic.js.map +1 -1
- package/dist/release/engine-ops.js +2 -2
- package/dist/release/engine-ops.js.map +1 -1
- package/dist/release/github-pr.d.ts +8 -5
- package/dist/release/github-pr.d.ts.map +1 -1
- package/dist/release/github-pr.js +11 -16
- package/dist/release/github-pr.js.map +1 -1
- package/dist/release/plan.d.ts +4 -2
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +11 -4
- package/dist/release/plan.js.map +1 -1
- package/dist/release/pr-evidence.d.ts +39 -11
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +122 -31
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/release/reconcile.d.ts +15 -0
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +36 -0
- package/dist/release/reconcile.js.map +1 -1
- package/dist/render/brain/index.d.ts +3 -2
- package/dist/render/brain/index.d.ts.map +1 -1
- package/dist/render/brain/index.js +4 -2
- package/dist/render/brain/index.js.map +1 -1
- package/dist/render/brain/migrate-repo-ids.d.ts +19 -0
- package/dist/render/brain/migrate-repo-ids.d.ts.map +1 -0
- package/dist/render/brain/migrate-repo-ids.js +41 -0
- package/dist/render/brain/migrate-repo-ids.js.map +1 -0
- package/dist/render/nexus/graph/index.d.ts.map +1 -1
- package/dist/render/nexus/graph/index.js +10 -1
- package/dist/render/nexus/graph/index.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +39 -4
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +147 -32
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/hook-footprint.d.ts +90 -0
- package/dist/resources/hook-footprint.d.ts.map +1 -0
- package/dist/resources/hook-footprint.js +245 -0
- package/dist/resources/hook-footprint.js.map +1 -0
- package/dist/resources/pressure-gate.d.ts +3 -2
- package/dist/resources/pressure-gate.d.ts.map +1 -1
- package/dist/resources/pressure-gate.js +8 -5
- package/dist/resources/pressure-gate.js.map +1 -1
- package/dist/resources/run-admission.d.ts +23 -3
- package/dist/resources/run-admission.d.ts.map +1 -1
- package/dist/resources/run-admission.js +70 -8
- package/dist/resources/run-admission.js.map +1 -1
- package/dist/resources/run-class.d.ts +1 -0
- package/dist/resources/run-class.d.ts.map +1 -1
- package/dist/resources/run-class.js +7 -0
- package/dist/resources/run-class.js.map +1 -1
- package/dist/resources/run-governed.d.ts +13 -1
- package/dist/resources/run-governed.d.ts.map +1 -1
- package/dist/resources/run-governed.js +29 -6
- package/dist/resources/run-governed.js.map +1 -1
- package/dist/resources/top-memory.d.ts +67 -0
- package/dist/resources/top-memory.d.ts.map +1 -0
- package/dist/resources/top-memory.js +143 -0
- package/dist/resources/top-memory.js.map +1 -0
- package/dist/scaffold/global-scaffold.d.ts +6 -1
- package/dist/scaffold/global-scaffold.d.ts.map +1 -1
- package/dist/scaffold/global-scaffold.js +11 -2
- package/dist/scaffold/global-scaffold.js.map +1 -1
- package/dist/scaffold/scaffold-global.d.ts +3 -2
- package/dist/scaffold/scaffold-global.d.ts.map +1 -1
- package/dist/scaffold/scaffold-global.js +3 -2
- package/dist/scaffold/scaffold-global.js.map +1 -1
- package/dist/sentient/ingesters/brain-ingester.d.ts.map +1 -1
- package/dist/sentient/ingesters/brain-ingester.js +2 -1
- package/dist/sentient/ingesters/brain-ingester.js.map +1 -1
- package/dist/session/engine-ops.js +1 -1
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +26 -0
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +46 -0
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/session-end-snapshot.d.ts +1 -1
- package/dist/sessions/session-end-snapshot.js +1 -1
- package/dist/sessions/session-id.d.ts +2 -1
- package/dist/sessions/session-id.d.ts.map +1 -1
- package/dist/sessions/session-id.js +3 -2
- package/dist/sessions/session-id.js.map +1 -1
- package/dist/store/agent-resolver.d.ts +0 -6
- package/dist/store/agent-resolver.d.ts.map +1 -1
- package/dist/store/agent-resolver.js +0 -31
- package/dist/store/agent-resolver.js.map +1 -1
- package/dist/store/memory-accessor.d.ts +45 -2
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +81 -7
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/schema/cleo-shared/brain.d.ts +2 -2
- package/dist/store/schema/memory-schema.d.ts +3 -3
- package/dist/store/schema/memory-schema.d.ts.map +1 -1
- package/dist/store/schema/memory-schema.js +2 -0
- package/dist/store/schema/memory-schema.js.map +1 -1
- package/dist/store/session-store.d.ts +5 -2
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +10 -5
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-native.d.ts +5 -4
- package/dist/store/sqlite-native.d.ts.map +1 -1
- package/dist/store/sqlite-native.js +5 -4
- package/dist/store/sqlite-native.js.map +1 -1
- package/dist/store/sync/merge/rules.d.ts.map +1 -1
- package/dist/store/sync/merge/rules.js +4 -3
- package/dist/store/sync/merge/rules.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +1 -0
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/store/vault-manifest.js +1 -1
- package/dist/store/vault-manifest.js.map +1 -1
- package/dist/system/project-health.d.ts +7 -0
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +13 -0
- package/dist/system/project-health.js.map +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +0 -15
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts +26 -11
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +54 -17
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +24 -0
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +57 -0
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/change-set.d.ts +72 -9
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +227 -20
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/complete-ci.d.ts.map +1 -1
- package/dist/tasks/complete-ci.js +2 -0
- package/dist/tasks/complete-ci.js.map +1 -1
- package/dist/tasks/complete.d.ts +2 -2
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +38 -17
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +66 -18
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/epic-enforcement.d.ts +39 -1
- package/dist/tasks/epic-enforcement.d.ts.map +1 -1
- package/dist/tasks/epic-enforcement.js +46 -2
- package/dist/tasks/epic-enforcement.js.map +1 -1
- package/dist/tasks/evidence.d.ts +74 -22
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +221 -35
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +2 -12
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/qa-run-binding.d.ts +2 -0
- package/dist/tasks/qa-run-binding.d.ts.map +1 -1
- package/dist/tasks/qa-run-binding.js +6 -2
- package/dist/tasks/qa-run-binding.js.map +1 -1
- package/dist/tasks/test-run-binding.d.ts +2 -0
- package/dist/tasks/test-run-binding.d.ts.map +1 -1
- package/dist/tasks/test-run-binding.js +8 -3
- package/dist/tasks/test-run-binding.js.map +1 -1
- package/dist/tasks/verification-policy.d.ts +57 -1
- package/dist/tasks/verification-policy.d.ts.map +1 -1
- package/dist/tasks/verification-policy.js +94 -1
- package/dist/tasks/verification-policy.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +2 -15
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +39 -19
- package/dist/validation/engine-ops.js.map +1 -1
- package/package.json +13 -13
- package/schemas/config.schema.json +69 -0
- package/templates/CLEO-INJECTION.md +4 -5
- package/templates/CLEO-REFERENCE.md +2 -4
- package/templates/git-hooks/post-merge +25 -0
- package/dist/memory/dispatch-trace.d.ts +0 -44
- package/dist/memory/dispatch-trace.d.ts.map +0 -1
- package/dist/memory/dispatch-trace.js +0 -65
- package/dist/memory/dispatch-trace.js.map +0 -1
- package/dist/memory/retrieval/increment-citation-counts.d.ts +0 -11
- package/dist/memory/retrieval/increment-citation-counts.d.ts.map +0 -1
- package/dist/memory/retrieval/increment-citation-counts.js +0 -43
- package/dist/memory/retrieval/increment-citation-counts.js.map +0 -1
package/dist/nexus/freshness.js
CHANGED
|
@@ -13,25 +13,33 @@
|
|
|
13
13
|
* @task T12316
|
|
14
14
|
* @module nexus/freshness
|
|
15
15
|
*/
|
|
16
|
-
import { existsSync } from 'node:fs';
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
17
18
|
import { performance } from 'node:perf_hooks';
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { getHeapStatistics } from 'node:v8';
|
|
20
21
|
import { PARSER_WORKER_HEAP_MAX_MB } from '@cleocode/contracts/code-symbol.js';
|
|
22
|
+
import { sql } from 'drizzle-orm';
|
|
21
23
|
import { getConfigValue } from '../config/registry.js';
|
|
22
24
|
import { readEnvOverride } from '../config.js';
|
|
23
25
|
import { pushWarning } from '../output.js';
|
|
26
|
+
import { getCleoDirAbsolute } from '../paths.js';
|
|
24
27
|
import { getNexusDb } from '../store/nexus-sqlite.js';
|
|
25
28
|
import { runNexusAnalysis } from './analyze-orchestrator.js';
|
|
26
|
-
import { readFileManifest, readRunCost } from './graph-manifest.js';
|
|
29
|
+
import { readActiveRefreshCooldown, readFileManifest, readRunCost, refreshCooldownApplies, writeRefreshCooldown, } from './graph-manifest.js';
|
|
30
|
+
import { envSwitch, measureAgainstBase, missingIndexedPaths, NEXUS_ALLOW_STALE_ENV, } from './index-health.js';
|
|
31
|
+
import { releasePidLock, takePidLock } from './pid-lock.js';
|
|
32
|
+
export { DEFAULT_MAX_COMMITS_BEHIND, measureAgainstBase, NEXUS_ALLOW_STALE_ENV, probeNexusIndexHealth, readNexusStalePolicy, } from './index-health.js';
|
|
27
33
|
/** Command that refreshes the index; incremental by default since T12315. */
|
|
28
34
|
export const NEXUS_REFRESH_COMMAND = 'cleo nexus analyze';
|
|
29
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Retired file-count cap for an inline refresh (T14014): the size-scaled cost
|
|
37
|
+
* estimate against the budget decides. Kept as the reported default of the
|
|
38
|
+
* ignored `nexus.autoRefresh.maxFiles`.
|
|
39
|
+
*/
|
|
30
40
|
export const DEFAULT_AUTO_REFRESH_MAX_FILES = 25;
|
|
31
41
|
/** Default wall-clock budget for an inline refresh. */
|
|
32
42
|
export const DEFAULT_AUTO_REFRESH_BUDGET_MS = 60_000;
|
|
33
|
-
/** Share of changed files above which analysis rebuilds fully (mirrors the pipeline). */
|
|
34
|
-
const FULL_REBUILD_RATIO = 0.3;
|
|
35
43
|
/**
|
|
36
44
|
* Judge the queried symbol's own files against an assessment.
|
|
37
45
|
* @param assessment - Assessment taken before the query ran.
|
|
@@ -49,25 +57,74 @@ export function judgeSymbolFiles(assessment, files) {
|
|
|
49
57
|
symbolFile: stale ?? unique[0],
|
|
50
58
|
};
|
|
51
59
|
}
|
|
52
|
-
/**
|
|
53
|
-
|
|
60
|
+
/** Largest factor a recorded run's whole-graph cost is scaled up by (T14014). */
|
|
61
|
+
const MAX_COST_SCALE = 4;
|
|
62
|
+
/**
|
|
63
|
+
* Estimate a refresh from the last recorded run, or `null` when nothing is
|
|
64
|
+
* recorded (T14014).
|
|
65
|
+
*
|
|
66
|
+
* Every refresh re-resolves, re-clusters and republishes the whole graph, so
|
|
67
|
+
* that cost is scaled from the last run's per-file rate to today's file count
|
|
68
|
+
* (at most {@link MAX_COST_SCALE} times, so one small sample cannot vouch for
|
|
69
|
+
* a far larger repository). Files without an exact cache entry for this build
|
|
70
|
+
* are parsed at the last run's parse rate, whatever their staleness: a wiped
|
|
71
|
+
* cache or an extractor upgrade means a full parse. Decoding reused
|
|
72
|
+
* extractions is recorded apart (`reuseDecode`) and never inflates that rate.
|
|
73
|
+
*/
|
|
74
|
+
function estimateRefreshMs(cost, toParse, reusable, fileCount) {
|
|
54
75
|
if (!cost)
|
|
55
76
|
return null;
|
|
56
77
|
const parseMs = cost.summary.phaseMs['parse'] ?? 0;
|
|
78
|
+
const decodeMs = cost.summary.phaseMs['reuseDecode'] ?? 0;
|
|
57
79
|
const parsed = cost.summary.parsedFiles;
|
|
58
|
-
const
|
|
80
|
+
const resolved = Math.max(1, cost.summary.resolvedFiles);
|
|
81
|
+
const wholeGraphMs = Math.max(0, cost.durationMs - parseMs - decodeMs);
|
|
82
|
+
const scale = Math.max(1, fileCount / resolved);
|
|
83
|
+
if (scale > MAX_COST_SCALE)
|
|
84
|
+
return null;
|
|
59
85
|
const perFileMs = parsed > 0 ? parseMs / parsed : 0;
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
const perReusedMs = cost.summary.reusedFiles > 0 ? decodeMs / cost.summary.reusedFiles : 0;
|
|
87
|
+
return Math.round(wholeGraphMs * scale + toParse * perFileMs + reusable * perReusedMs);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Split the parseable files of the manifest (T14014) into those a refresh must
|
|
91
|
+
* parse (stale, or without an exact cache entry for this build) and those it
|
|
92
|
+
* can reuse (unchanged, same content hash, this build's extractor fingerprint).
|
|
93
|
+
* Files no extractor parses are neither.
|
|
94
|
+
*/
|
|
95
|
+
async function planParse(db, manifestFiles, stale) {
|
|
96
|
+
const { computeExtractorFingerprint, isParseablePath, parseCacheFingerprint } = await import('@cleocode/nexus/pipeline');
|
|
97
|
+
// The inline refresh parses under the default limits (T14016).
|
|
98
|
+
const fingerprint = parseCacheFingerprint(computeExtractorFingerprint());
|
|
99
|
+
const cached = new Map();
|
|
100
|
+
for (const [path, contentHash, entryFingerprint] of db.values(sql `SELECT path, content_hash, fingerprint FROM main._nexus_parse_cache`)) {
|
|
101
|
+
if (entryFingerprint === fingerprint &&
|
|
102
|
+
typeof path === 'string' &&
|
|
103
|
+
typeof contentHash === 'string')
|
|
104
|
+
cached.set(path, contentHash);
|
|
105
|
+
}
|
|
106
|
+
let reusable = 0;
|
|
107
|
+
let toParse = 0;
|
|
108
|
+
for (const [path, , , contentHash] of manifestFiles) {
|
|
109
|
+
if (!isParseablePath(path))
|
|
110
|
+
continue;
|
|
111
|
+
if (!stale.has(path) && cached.get(path) === contentHash)
|
|
112
|
+
reusable++;
|
|
113
|
+
else
|
|
114
|
+
toParse++;
|
|
115
|
+
}
|
|
116
|
+
// A new parseable file is stale but not yet in the manifest.
|
|
117
|
+
const known = new Set(manifestFiles.map(([path]) => path));
|
|
118
|
+
for (const path of stale)
|
|
119
|
+
if (isParseablePath(path) && !known.has(path))
|
|
120
|
+
toParse++;
|
|
121
|
+
return { toParse, reusable };
|
|
63
122
|
}
|
|
64
123
|
/** Render an estimate for humans and agents alike. */
|
|
65
|
-
function describeEstimate(ms, staleFiles,
|
|
124
|
+
function describeEstimate(ms, staleFiles, toParse) {
|
|
66
125
|
if (staleFiles === 0)
|
|
67
126
|
return 'none needed';
|
|
68
|
-
const kind =
|
|
69
|
-
? 'full rebuild'
|
|
70
|
-
: `incremental, re-parses ~${staleFiles} file(s)`;
|
|
127
|
+
const kind = `re-parses ~${toParse} file(s), re-resolves the whole graph`;
|
|
71
128
|
if (ms === null)
|
|
72
129
|
return `unknown duration (${kind}; no previous analysis cost recorded)`;
|
|
73
130
|
return `~${Math.max(1, Math.round(ms / 1000))}s (${kind})`;
|
|
@@ -146,6 +203,7 @@ async function assessWithStaleSet(projectRoot, sampleLimit = 10) {
|
|
|
146
203
|
// T12474: an unreachable source root is an unassessable index, never a crash.
|
|
147
204
|
const freshness = {
|
|
148
205
|
...base,
|
|
206
|
+
...(await measureAgainstBase(db, manifest.sourceRoot, manifest.assessedAt)),
|
|
149
207
|
indexed: true,
|
|
150
208
|
status: 'unknown',
|
|
151
209
|
lastIndexedAt: manifest.assessedAt,
|
|
@@ -165,23 +223,97 @@ async function assessWithStaleSet(projectRoot, sampleLimit = 10) {
|
|
|
165
223
|
if (known.get(file.path)?.contentHash !== file.contentHash)
|
|
166
224
|
stale.add(file.path);
|
|
167
225
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
226
|
+
const unseen = [...known.keys()].filter((path) => !seen.has(path));
|
|
227
|
+
for (const path of unseen)
|
|
228
|
+
stale.add(path);
|
|
229
|
+
// T14021: unseen paths can be excluded files still on disk; only absent ones are missing.
|
|
230
|
+
const missing = missingIndexedPaths(manifest.sourceRoot, unseen);
|
|
171
231
|
const fileCount = manifest.files.length;
|
|
172
|
-
const
|
|
232
|
+
const { toParse, reusable } = stale.size > 0 ? await planParse(db, manifest.files, stale) : { toParse: 0, reusable: 0 };
|
|
233
|
+
const cost = readRunCost(db);
|
|
234
|
+
const estimate = estimateRefreshMs(cost, toParse, reusable, fileCount);
|
|
173
235
|
const freshness = {
|
|
174
236
|
...base,
|
|
237
|
+
...(await measureAgainstBase(db, manifest.sourceRoot, manifest.assessedAt)),
|
|
175
238
|
indexed: true,
|
|
176
239
|
status: stale.size === 0 ? 'fresh' : 'stale',
|
|
177
240
|
lastIndexedAt: manifest.assessedAt,
|
|
178
241
|
fileCount,
|
|
179
242
|
staleFileCount: stale.size,
|
|
180
243
|
stalePaths: [...stale].sort().slice(0, sampleLimit),
|
|
181
|
-
|
|
244
|
+
missingFileCount: missing.length,
|
|
245
|
+
missingPaths: missing.slice(0, sampleLimit),
|
|
246
|
+
...(cost?.summary.communitiesSkipped
|
|
247
|
+
? { communitiesSkipped: cost.summary.communitiesSkipped }
|
|
248
|
+
: {}),
|
|
249
|
+
refreshEstimate: describeEstimate(estimate, stale.size, toParse),
|
|
182
250
|
checkMs: Math.round(performance.now() - started),
|
|
183
251
|
};
|
|
184
|
-
return { freshness, staleFiles: stale };
|
|
252
|
+
return { freshness, staleFiles: stale, refreshPlan: { toParse, reusable, estimateMs: estimate } };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The freshness block every index-sensitive answer carries as `data.freshness`.
|
|
256
|
+
*
|
|
257
|
+
* @param freshness - The assessment behind the answer.
|
|
258
|
+
* @returns Revision, base, commits behind, age and verdict.
|
|
259
|
+
*/
|
|
260
|
+
export function answerFreshness(freshness) {
|
|
261
|
+
return {
|
|
262
|
+
status: freshness.status,
|
|
263
|
+
indexedRevision: freshness.indexedRevision ?? null,
|
|
264
|
+
baseRef: freshness.baseRef ?? null,
|
|
265
|
+
commitsBehind: freshness.commitsBehind ?? null,
|
|
266
|
+
commitsBehindHead: freshness.commitsBehindHead ?? null,
|
|
267
|
+
changedFilesSinceIndex: freshness.changedFilesSinceIndex ?? null,
|
|
268
|
+
refreshCooldownUntil: freshness.refreshCooldownUntil ?? null,
|
|
269
|
+
ageDays: freshness.ageDays ?? null,
|
|
270
|
+
lastIndexedAt: freshness.lastIndexedAt,
|
|
271
|
+
missingFileCount: freshness.missingFileCount ?? null,
|
|
272
|
+
missingPaths: freshness.missingPaths ?? [],
|
|
273
|
+
communitiesSkipped: freshness.communitiesSkipped ?? null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Whether a graph answer must be refused as stale: the index trails the
|
|
278
|
+
* checked-out HEAD by more than the policy allows, or the target is missing
|
|
279
|
+
* from an index that is stale for this checkout (commits behind HEAD or
|
|
280
|
+
* changed files), so it may exist in code the index never saw. Distance to the
|
|
281
|
+
* base branch (`commitsBehind`) is reported only: on a feature branch or
|
|
282
|
+
* worktree it grows as the base moves, and no reindex here could clear it.
|
|
283
|
+
*
|
|
284
|
+
* @param freshness - The assessment behind the answer.
|
|
285
|
+
* @param policy - The stale-answer policy.
|
|
286
|
+
* @param options - `targetMissing` when the query found no symbol; `projectRoot`
|
|
287
|
+
* names the checkout in the reindex command.
|
|
288
|
+
* @returns The refusal, or `null` when the answer may stand.
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* const refusal = staleIndexRefusal(freshness, policy, { projectRoot: root });
|
|
292
|
+
* if (refusal) return refusalEnvelope(refusal);
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
export function staleIndexRefusal(freshness, policy, options = {}) {
|
|
296
|
+
if (policy.allowStale)
|
|
297
|
+
return null;
|
|
298
|
+
// Judged against what is checked out: a reindex here clears it on any branch
|
|
299
|
+
// or worktree. Distance to the base branch is reported, never refused on.
|
|
300
|
+
const behind = freshness.commitsBehindHead ?? null;
|
|
301
|
+
const tooFar = behind !== null && behind > policy.maxCommitsBehind;
|
|
302
|
+
const staleHere = freshness.status === 'stale' || (behind !== null && behind > 0);
|
|
303
|
+
if (!tooFar && !(options.targetMissing && staleHere))
|
|
304
|
+
return null;
|
|
305
|
+
const reason = tooFar
|
|
306
|
+
? `The index is ${behind} commit(s) behind the checked-out HEAD (more than nexus.freshness.maxCommitsBehind = ${policy.maxCommitsBehind})`
|
|
307
|
+
: `The symbol is missing from an index that is stale for this checkout (${behind ?? 0} commit(s) behind HEAD, ${freshness.staleFileCount} changed file(s)); it may exist in code the index has not seen`;
|
|
308
|
+
const reindex = options.projectRoot
|
|
309
|
+
? `${NEXUS_REFRESH_COMMAND} ${JSON.stringify(options.projectRoot)}`
|
|
310
|
+
: NEXUS_REFRESH_COMMAND;
|
|
311
|
+
return {
|
|
312
|
+
code: 'E_NEXUS_INDEX_STALE',
|
|
313
|
+
message: `${reason}; this answer would describe code that has changed.`,
|
|
314
|
+
fix: `${reindex} (then repeat the query); to accept the stale answer, pass --allow-stale (nexus impact, nexus context) or set ${NEXUS_ALLOW_STALE_ENV}=1`,
|
|
315
|
+
details: { ...answerFreshness(freshness), maxCommitsBehind: policy.maxCommitsBehind },
|
|
316
|
+
};
|
|
185
317
|
}
|
|
186
318
|
/**
|
|
187
319
|
* Read the auto-refresh policy from `nexus.autoRefresh.*`, applying defaults.
|
|
@@ -210,15 +342,6 @@ export async function readNexusAutoRefreshPolicy(projectRoot) {
|
|
|
210
342
|
return { ...policyFrom(false, maxFiles, budgetMs), disabledBy: 'CLEO_NEXUS_AUTO_REFRESH' };
|
|
211
343
|
return policyFrom(enabled, maxFiles, budgetMs);
|
|
212
344
|
}
|
|
213
|
-
/** Read an on/off env value (`true`/`false`, `1`/`0`, `yes`/`no`, `on`/`off`); `undefined` otherwise. */
|
|
214
|
-
function envSwitch(value) {
|
|
215
|
-
const text = String(value).trim().toLowerCase();
|
|
216
|
-
if (['true', '1', 'yes', 'on'].includes(text))
|
|
217
|
-
return true;
|
|
218
|
-
if (['false', '0', 'no', 'off'].includes(text))
|
|
219
|
-
return false;
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
345
|
/** Apply defaults to raw `nexus.autoRefresh.*` values. */
|
|
223
346
|
function policyFrom(enabled, maxFiles, budgetMs) {
|
|
224
347
|
return {
|
|
@@ -232,8 +355,8 @@ function policyFrom(enabled, maxFiles, budgetMs) {
|
|
|
232
355
|
};
|
|
233
356
|
}
|
|
234
357
|
/**
|
|
235
|
-
* Run a refresh in this thread,
|
|
236
|
-
*
|
|
358
|
+
* Run a refresh in this thread, bounded by its budget. Unbounded in memory:
|
|
359
|
+
* {@link runRefreshInChild} runs it inside a heap-limited child process.
|
|
237
360
|
*
|
|
238
361
|
* @param input - The refresh to run.
|
|
239
362
|
* @returns The refresh summary.
|
|
@@ -244,7 +367,8 @@ export async function runRefreshInProcess(input) {
|
|
|
244
367
|
repoPath: input.repoPath,
|
|
245
368
|
...(input.includedRepositories ? { includedRepositories: input.includedRepositories } : {}),
|
|
246
369
|
parserLimits: { signal: AbortSignal.timeout(input.budgetMs) },
|
|
247
|
-
|
|
370
|
+
// T14016: the same budget, as a deadline uninterruptible phases can check.
|
|
371
|
+
deadlineAt: Date.now() + input.budgetMs,
|
|
248
372
|
});
|
|
249
373
|
return { mode: summary.mode, reason: summary.reason };
|
|
250
374
|
}
|
|
@@ -311,89 +435,235 @@ export async function runRefreshInChild(input, options = {}) {
|
|
|
311
435
|
await handle.stop();
|
|
312
436
|
}
|
|
313
437
|
}
|
|
438
|
+
/** Cooldown after an inline refresh fails or is aborted (T14014). */
|
|
439
|
+
export const INLINE_REFRESH_COOLDOWN_MS = 30 * 60_000;
|
|
440
|
+
/** A refresh parsing more than this many files (or 30% of them) needs `cleo run` admission. */
|
|
441
|
+
const ADMISSION_FREE_PARSE_FILES = 25;
|
|
442
|
+
/** Share of files above which a refresh needs `cleo run` admission. */
|
|
443
|
+
const ADMISSION_FREE_PARSE_RATIO = 0.3;
|
|
444
|
+
/**
|
|
445
|
+
* Indexed files above which every inline refresh needs `cleo run` admission
|
|
446
|
+
* (T14019): resolve, communities, flows and the republish run over the whole
|
|
447
|
+
* graph, so on a large repository they dominate memory even when one file is
|
|
448
|
+
* re-parsed.
|
|
449
|
+
*/
|
|
450
|
+
export const ADMISSION_GRAPH_FILES = 2_000;
|
|
451
|
+
/** Admission footprint per indexed file of an inline refresh (T14019). */
|
|
452
|
+
const REFRESH_BYTES_PER_INDEXED_FILE = 256 * 1024;
|
|
453
|
+
/** Smallest admission footprint of an inline refresh (T14019). */
|
|
454
|
+
const REFRESH_MIN_FOOTPRINT_BYTES = 256 * 1024 * 1024;
|
|
455
|
+
/**
|
|
456
|
+
* The memory an inline refresh asks `cleo run` admission for (T14019): scaled
|
|
457
|
+
* to the graph it re-resolves (indexed files), at least
|
|
458
|
+
* {@link REFRESH_MIN_FOOTPRINT_BYTES}, at most the build class's footprint.
|
|
459
|
+
*
|
|
460
|
+
* @param indexedFiles - Files in the published generation.
|
|
461
|
+
* @param classBytes - Footprint of the `scoped-build` class on this machine.
|
|
462
|
+
* @returns Footprint in bytes.
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* inlineRefreshFootprintBytes(10_000, footprintForClass('scoped-build'));
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
export function inlineRefreshFootprintBytes(indexedFiles, classBytes) {
|
|
469
|
+
const scaled = Math.max(0, indexedFiles) * REFRESH_BYTES_PER_INDEXED_FILE;
|
|
470
|
+
return Math.min(classBytes, Math.max(REFRESH_MIN_FOOTPRINT_BYTES, scaled));
|
|
471
|
+
}
|
|
472
|
+
/** Fallback cooldown file under `.cleo/` when `_nexus_meta` cannot be written (T14014). */
|
|
473
|
+
export const INLINE_REFRESH_COOLDOWN_FILE = 'nexus-refresh-cooldown.json';
|
|
474
|
+
/** Last resort when neither the store nor the file can hold a cooldown. */
|
|
475
|
+
let inProcessCooldown = null;
|
|
314
476
|
/**
|
|
315
|
-
*
|
|
477
|
+
* Record a cooldown: in `_nexus_meta`, else (SQLITE_BUSY while another
|
|
478
|
+
* analysis publishes, a read-only store) in a file under `.cleo/`, else in
|
|
479
|
+
* this process. Never throws: a failed write must not fail the query or lose
|
|
480
|
+
* the cooldown.
|
|
481
|
+
*/
|
|
482
|
+
function recordCooldown(db, cleoDir, cooldown) {
|
|
483
|
+
try {
|
|
484
|
+
writeRefreshCooldown(db, cooldown);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
// fall through to the file
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
mkdirSync(cleoDir, { recursive: true });
|
|
492
|
+
writeFileSync(join(cleoDir, INLINE_REFRESH_COOLDOWN_FILE), JSON.stringify(cooldown));
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
inProcessCooldown = cooldown;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/** The cooldown in force from the store, its fallback file, or this process. */
|
|
499
|
+
function activeCooldown(db, cleoDir) {
|
|
500
|
+
const applies = (cooldown) => {
|
|
501
|
+
try {
|
|
502
|
+
return cooldown && refreshCooldownApplies(db, cooldown) ? cooldown : null;
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
let stored = null;
|
|
509
|
+
try {
|
|
510
|
+
stored = readActiveRefreshCooldown(db);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
stored = null;
|
|
514
|
+
}
|
|
515
|
+
if (stored)
|
|
516
|
+
return stored;
|
|
517
|
+
let filed = null;
|
|
518
|
+
try {
|
|
519
|
+
filed = JSON.parse(readFileSync(join(cleoDir, INLINE_REFRESH_COOLDOWN_FILE), 'utf-8'));
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
filed = null;
|
|
523
|
+
}
|
|
524
|
+
return applies(filed) ?? applies(inProcessCooldown);
|
|
525
|
+
}
|
|
526
|
+
/** Lock file under `.cleo/`: one inline refresh at a time per project. */
|
|
527
|
+
export const INLINE_REFRESH_LOCK_FILE = 'nexus-inline-refresh.lock';
|
|
528
|
+
/** The production admitter: one non-waiting `cleo run` admission pass, sized to the graph. */
|
|
529
|
+
const admitRefresh = async (projectRoot, indexedFiles) => {
|
|
530
|
+
const { admit, footprintForClass } = await import('../resources/admission-ledger.js');
|
|
531
|
+
const outcome = await admit({
|
|
532
|
+
label: 'nexus:inline-refresh',
|
|
533
|
+
footprintBytes: inlineRefreshFootprintBytes(indexedFiles, footprintForClass('scoped-build')),
|
|
534
|
+
cwd: projectRoot,
|
|
535
|
+
footprintReason: `inline NEXUS refresh re-resolving a ${indexedFiles}-file graph (T14014, T14019)`,
|
|
536
|
+
}, { wait: false, timeoutMs: 0 });
|
|
537
|
+
return outcome.admitted ? outcome.grant : { refused: outcome.refusal.reason };
|
|
538
|
+
};
|
|
539
|
+
/**
|
|
540
|
+
* Assess freshness and, when the refresh is affordable, refresh the index
|
|
316
541
|
* inline before the caller answers from it.
|
|
317
542
|
*
|
|
318
|
-
* A refresh runs
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
543
|
+
* A refresh runs when the recorded cost, scaled to the repository and to the
|
|
544
|
+
* files it must parse (T14014), fits `budgetMs`. It is guarded so a stale
|
|
545
|
+
* index never turns every query into another heavy run:
|
|
546
|
+
* - one inline refresh at a time per project (a pid lock; a concurrent query
|
|
547
|
+
* answers from the stale index);
|
|
548
|
+
* - a refresh beyond a small incremental set needs a non-waiting `cleo run`
|
|
549
|
+
* admission, and is skipped when the machine is busy;
|
|
550
|
+
* - a refresh that fails or is aborted sets a cooldown: no inline refresh for
|
|
551
|
+
* {@link INLINE_REFRESH_COOLDOWN_MS}, or until an analysis publishes a new
|
|
552
|
+
* generation. The cooldown is reported as `refreshCooldownUntil`.
|
|
553
|
+
* The budget also cancels a run at its next check, and publication is atomic,
|
|
554
|
+
* so an abandoned refresh leaves the previous graph intact. Every decision is
|
|
555
|
+
* disclosed in `autoRefresh`.
|
|
324
556
|
*
|
|
325
557
|
* @param projectRoot - Project whose graph is about to be queried.
|
|
326
558
|
* @param policy - Explicit policy; read from config when omitted.
|
|
327
559
|
* @param runRefresh - Runs the refresh; a heap-bounded worker unless supplied (T13537).
|
|
560
|
+
* @param admitter - Admits a heavy refresh; `cleo run` admission unless supplied.
|
|
328
561
|
* @returns Freshness of the index as it stands after any refresh, with its stale set.
|
|
329
562
|
*/
|
|
330
|
-
export async function ensureNexusIndexFresh(projectRoot, policy, runRefresh = runRefreshInChild) {
|
|
563
|
+
export async function ensureNexusIndexFresh(projectRoot, policy, runRefresh = runRefreshInChild, admitter = admitRefresh) {
|
|
331
564
|
const initial = await assessWithStaleSet(projectRoot);
|
|
332
565
|
const before = initial.freshness;
|
|
333
566
|
if (before.status !== 'stale')
|
|
334
567
|
return initial;
|
|
335
568
|
const effective = policy ?? (await readNexusAutoRefreshPolicy(projectRoot));
|
|
336
|
-
const skip = (reason) => ({
|
|
569
|
+
const skip = (reason, cooldownUntil) => ({
|
|
337
570
|
staleFiles: initial.staleFiles,
|
|
571
|
+
...(initial.refreshPlan ? { refreshPlan: initial.refreshPlan } : {}),
|
|
338
572
|
freshness: {
|
|
339
573
|
...before,
|
|
574
|
+
...(cooldownUntil ? { refreshCooldownUntil: cooldownUntil } : {}),
|
|
340
575
|
autoRefresh: { refreshed: false, staleFiles: before.staleFileCount, durationMs: 0, reason },
|
|
341
576
|
},
|
|
342
577
|
});
|
|
343
578
|
if (!effective.enabled)
|
|
344
579
|
return skip(`auto-refresh is disabled (${effective.disabledBy ?? 'nexus.autoRefresh.enabled = false'}); answered from the stale index`);
|
|
345
|
-
if (before.staleFileCount > effective.maxFiles)
|
|
346
|
-
return skip(`${before.staleFileCount} stale files exceed nexus.autoRefresh.maxFiles = ${effective.maxFiles}; ` +
|
|
347
|
-
'answered from the stale index');
|
|
348
|
-
// T13523: a read query never runs a full rebuild inline; the pipeline also
|
|
349
|
-
// refuses one it would fall back to (requireIncremental below).
|
|
350
|
-
if (before.fileCount > 0 && before.staleFileCount / before.fileCount > FULL_REBUILD_RATIO)
|
|
351
|
-
return skip(`${before.staleFileCount} of ${before.fileCount} files are stale, which needs a full rebuild; answered from the stale index (run ${NEXUS_REFRESH_COMMAND})`);
|
|
352
580
|
const db = await getNexusDb(projectRoot);
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
581
|
+
const cleoDir = getCleoDirAbsolute(projectRoot);
|
|
582
|
+
const cooldown = activeCooldown(db, cleoDir);
|
|
583
|
+
if (cooldown)
|
|
584
|
+
return skip(`a recent inline refresh did not finish (${cooldown.reason}); no inline refresh until ${cooldown.until} or the next ${NEXUS_REFRESH_COMMAND}; answered from the stale index`, cooldown.until);
|
|
585
|
+
const plan = initial.refreshPlan;
|
|
586
|
+
const estimate = plan?.estimateMs ?? null;
|
|
587
|
+
// T13523: with no comparable recorded cost there is no bound on the run, so
|
|
588
|
+
// do not start one.
|
|
356
589
|
if (estimate === null)
|
|
357
|
-
return skip(`no previous analysis cost recorded, so the refresh cannot be bounded; answered from the stale index (run ${NEXUS_REFRESH_COMMAND} to refresh and record one)`);
|
|
590
|
+
return skip(`no previous analysis cost recorded (or the repository has grown more than ${MAX_COST_SCALE}x since), so the refresh cannot be bounded; answered from the stale index (run ${NEXUS_REFRESH_COMMAND} to refresh and record one)`);
|
|
358
591
|
if (estimate > effective.budgetMs)
|
|
359
592
|
return skip(`estimated refresh ${Math.round(estimate / 1000)}s exceeds nexus.autoRefresh.budgetMs = ` +
|
|
360
593
|
`${effective.budgetMs}; answered from the stale index`);
|
|
361
|
-
|
|
362
|
-
const
|
|
594
|
+
mkdirSync(cleoDir, { recursive: true });
|
|
595
|
+
const lockPath = join(cleoDir, INLINE_REFRESH_LOCK_FILE);
|
|
596
|
+
const holder = takePidLock(lockPath);
|
|
597
|
+
if (holder !== null)
|
|
598
|
+
return skip(`an inline refresh is already running${holder > 0 ? ` (pid ${holder})` : ''}; answered from the stale index`);
|
|
599
|
+
let grant = null;
|
|
363
600
|
try {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
601
|
+
const toParse = plan?.toParse ?? before.staleFileCount;
|
|
602
|
+
// T14019: the whole-graph phases make a large graph heavy even for one file.
|
|
603
|
+
const largeGraph = before.fileCount > (effective.admissionGraphFiles ?? ADMISSION_GRAPH_FILES);
|
|
604
|
+
const heavy = largeGraph ||
|
|
605
|
+
toParse > ADMISSION_FREE_PARSE_FILES ||
|
|
606
|
+
(before.fileCount > 0 && toParse / before.fileCount > ADMISSION_FREE_PARSE_RATIO);
|
|
607
|
+
if (heavy) {
|
|
608
|
+
const admitted = await admitter(projectRoot, before.fileCount);
|
|
609
|
+
if ('refused' in admitted)
|
|
610
|
+
return skip(`a refresh ${largeGraph ? `re-resolving a ${before.fileCount}-file graph` : `parsing ~${toParse} file(s)`} needs cleo run admission and the machine is busy (${admitted.refused}); answered from the stale index`);
|
|
611
|
+
grant = admitted;
|
|
612
|
+
}
|
|
613
|
+
const manifest = readFileManifest(db, projectRoot);
|
|
614
|
+
const started = performance.now();
|
|
615
|
+
const generation = db.values(sql `SELECT value FROM main._nexus_meta WHERE key = 'graph_generation'`)[0]?.[0];
|
|
616
|
+
try {
|
|
617
|
+
const summary = await runRefresh({
|
|
618
|
+
projectRoot,
|
|
619
|
+
repoPath: manifest?.sourceRoot ?? projectRoot,
|
|
620
|
+
...(manifest ? { includedRepositories: manifest.includedRepositories } : {}),
|
|
621
|
+
budgetMs: effective.budgetMs,
|
|
622
|
+
});
|
|
623
|
+
const after = await assessWithStaleSet(projectRoot);
|
|
624
|
+
return {
|
|
625
|
+
staleFiles: after.staleFiles,
|
|
626
|
+
...(after.refreshPlan ? { refreshPlan: after.refreshPlan } : {}),
|
|
627
|
+
freshness: {
|
|
628
|
+
...after.freshness,
|
|
629
|
+
autoRefresh: {
|
|
630
|
+
refreshed: summary.mode !== 'unchanged',
|
|
631
|
+
staleFiles: before.staleFileCount,
|
|
632
|
+
durationMs: Math.round(performance.now() - started),
|
|
633
|
+
reason: `${summary.mode}: ${summary.reason}`,
|
|
634
|
+
},
|
|
380
635
|
},
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
640
|
+
// T14014: an aborted or failed refresh records no cost, so without a
|
|
641
|
+
// cooldown every query would start another one.
|
|
642
|
+
const until = new Date(Date.now() + INLINE_REFRESH_COOLDOWN_MS).toISOString();
|
|
643
|
+
recordCooldown(db, cleoDir, {
|
|
644
|
+
until,
|
|
645
|
+
generation: typeof generation === 'string' ? generation : null,
|
|
646
|
+
reason: message.slice(0, 300),
|
|
647
|
+
});
|
|
648
|
+
return {
|
|
649
|
+
staleFiles: initial.staleFiles,
|
|
650
|
+
...(initial.refreshPlan ? { refreshPlan: initial.refreshPlan } : {}),
|
|
651
|
+
freshness: {
|
|
652
|
+
...before,
|
|
653
|
+
refreshCooldownUntil: until,
|
|
654
|
+
autoRefresh: {
|
|
655
|
+
refreshed: false,
|
|
656
|
+
staleFiles: before.staleFileCount,
|
|
657
|
+
durationMs: Math.round(performance.now() - started),
|
|
658
|
+
reason: `refresh failed, previous graph retained: ${message}; inline refresh paused until ${until}`,
|
|
659
|
+
},
|
|
394
660
|
},
|
|
395
|
-
}
|
|
396
|
-
}
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
finally {
|
|
665
|
+
await grant?.release();
|
|
666
|
+
releasePidLock(lockPath);
|
|
397
667
|
}
|
|
398
668
|
}
|
|
399
669
|
/**
|
|
@@ -497,6 +767,24 @@ export function discloseNexusFreshness(subject, freshness) {
|
|
|
497
767
|
message: `Refreshed the index inline before answering (${refresh.staleFiles} stale file(s), ${refresh.durationMs}ms): ${refresh.reason}`,
|
|
498
768
|
});
|
|
499
769
|
}
|
|
770
|
+
const missing = freshness.missingFileCount ?? 0;
|
|
771
|
+
if (missing > 0) {
|
|
772
|
+
pushWarning({
|
|
773
|
+
code: 'W_NEXUS_INDEX_MISSING_PATHS',
|
|
774
|
+
severity: 'warn',
|
|
775
|
+
message: `${subject} answered from an index that still holds ${missing} file(s) no longer on disk ` +
|
|
776
|
+
`(e.g. ${(freshness.missingPaths ?? []).slice(0, 3).join(', ')}); results may cite deleted code. ` +
|
|
777
|
+
`Refresh: ${freshness.refreshCommand}.`,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
if (freshness.communitiesSkipped) {
|
|
781
|
+
pushWarning({
|
|
782
|
+
code: 'W_NEXUS_COMMUNITIES_SKIPPED',
|
|
783
|
+
severity: 'warn',
|
|
784
|
+
message: `${subject} answered from an index with no communities: ${freshness.communitiesSkipped}. ` +
|
|
785
|
+
`Community-based results are missing until ${freshness.refreshCommand} runs without a budget.`,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
500
788
|
if (freshness.status === 'fresh')
|
|
501
789
|
return;
|
|
502
790
|
const symbol = freshness.symbolFileStale === true
|