akm-cli 0.9.16 → 0.9.17-alpha.10
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/CHANGELOG.md +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* The remaining `surfaces` advisory group for `akm health` (meta-review 08).
|
|
6
|
-
* `stash-git-exposure` (08-F1) shipped first in ./stash-exposure.ts; this
|
|
7
|
-
* module adds the other two read-only checks the adjudication approved:
|
|
8
|
-
*
|
|
9
|
-
* - `binary-config-skew` — config.json written by a NEWER akm than this binary (F3)
|
|
10
|
-
* - `egress-endpoints` — the remote-destination list, for eyeball diff (surfaces 3/9)
|
|
11
|
-
*
|
|
12
|
-
* Every collector is a pure projection over injected paths/config (no
|
|
13
|
-
* process.env reads) and is silent when there is nothing to report, matching
|
|
14
|
-
* the stash-exposure pattern. `egress-endpoints` is the one informational
|
|
15
|
-
* (pass-status) entry: it emits whenever any remote endpoint is configured.
|
|
16
|
-
*/
|
|
17
|
-
import { readTextFile } from "../../core/common.js";
|
|
18
|
-
import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
|
|
19
|
-
import { compareConfigVersion } from "../../core/config/config-version.js";
|
|
20
|
-
import { formatRegistryUrl } from "../../core/registry-url.js";
|
|
21
|
-
/**
|
|
22
|
-
* `binary-config-skew` (08-F3): warn when config.json carries a configVersion
|
|
23
|
-
* NEWER than (or unorderable against) this binary's CURRENT_CONFIG_VERSION —
|
|
24
|
-
* i.e. a newer/foreign akm wrote the shared config and this install is stale.
|
|
25
|
-
* That is the proven multi-install incident class. Silent for current or older
|
|
26
|
-
* versions and unreadable configs (config loading surfaces its own errors).
|
|
27
|
-
*/
|
|
28
|
-
export function collectConfigSkewAdvisory(configPath) {
|
|
29
|
-
let raw;
|
|
30
|
-
try {
|
|
31
|
-
raw = JSON.parse(readTextFile(configPath, "Config file"));
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
const onDisk = raw.configVersion;
|
|
37
|
-
const order = compareConfigVersion(onDisk, CURRENT_CONFIG_VERSION);
|
|
38
|
-
const skewed = order === 1 || (onDisk !== undefined && order === undefined);
|
|
39
|
-
if (!skewed)
|
|
40
|
-
return undefined;
|
|
41
|
-
return {
|
|
42
|
-
name: "binary-config-skew",
|
|
43
|
-
kind: "deterministic",
|
|
44
|
-
status: "warn",
|
|
45
|
-
confidence: "high",
|
|
46
|
-
message: `config.json has configVersion ${JSON.stringify(onDisk)} but this binary knows ${CURRENT_CONFIG_VERSION} — ` +
|
|
47
|
-
"a newer akm wrote the shared config, so this install is stale. Upgrade this install; do not keep a stale " +
|
|
48
|
-
"binary against the shared config/DBs.",
|
|
49
|
-
evidence: { onDiskConfigVersion: onDisk, binaryConfigVersion: CURRENT_CONFIG_VERSION },
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* `egress-endpoints` (08 surfaces 3/9): the full list of remote destinations
|
|
54
|
-
* akm can talk to under the effective config — registries, remote sources,
|
|
55
|
-
* LLM endpoints, embedding endpoint — as one pass-status informational entry
|
|
56
|
-
* for eyeball diff against expectations. Silent only when nothing remote is
|
|
57
|
-
* configured at all.
|
|
58
|
-
*/
|
|
59
|
-
export function collectEgressAdvisory(config) {
|
|
60
|
-
if (!config)
|
|
61
|
-
return undefined;
|
|
62
|
-
const endpoints = [];
|
|
63
|
-
for (const reg of config.registries ?? []) {
|
|
64
|
-
if (reg.enabled === false || !reg.url)
|
|
65
|
-
continue;
|
|
66
|
-
endpoints.push(`registry ${reg.name ?? "(unnamed)"}: ${formatRegistryUrl(reg.url)}`);
|
|
67
|
-
}
|
|
68
|
-
for (const [key, bundle] of Object.entries(config.bundles ?? {})) {
|
|
69
|
-
if (!bundle)
|
|
70
|
-
continue;
|
|
71
|
-
const url = bundle.git ?? bundle.website?.url;
|
|
72
|
-
if (!url)
|
|
73
|
-
continue;
|
|
74
|
-
endpoints.push(`source ${key} (${bundle.git ? "git" : "website"}): ${url}`);
|
|
75
|
-
}
|
|
76
|
-
for (const [name, engine] of Object.entries(config.engines ?? {})) {
|
|
77
|
-
if (engine?.kind !== "llm" || !engine.endpoint)
|
|
78
|
-
continue;
|
|
79
|
-
endpoints.push(`llm ${name}: ${engine.endpoint}`);
|
|
80
|
-
}
|
|
81
|
-
if (config.embedding?.endpoint)
|
|
82
|
-
endpoints.push(`embedding: ${config.embedding.endpoint}`);
|
|
83
|
-
if (endpoints.length === 0)
|
|
84
|
-
return undefined;
|
|
85
|
-
return {
|
|
86
|
-
name: "egress-endpoints",
|
|
87
|
-
kind: "deterministic",
|
|
88
|
-
status: "pass",
|
|
89
|
-
confidence: "high",
|
|
90
|
-
message: `${endpoints.length} remote endpoint(s) in the effective config (registries/sources/LLM/embedding) — ` +
|
|
91
|
-
"review the evidence list for unexpected destinations.",
|
|
92
|
-
evidence: { endpoints },
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Aggregate the two collectors into the advisories array shape `akmHealth`
|
|
97
|
-
* consumes. Order is fixed: skew → egress.
|
|
98
|
-
*/
|
|
99
|
-
export function collectSurfacesAdvisories(input) {
|
|
100
|
-
const results = [collectConfigSkewAdvisory(input.configPath), collectEgressAdvisory(input.config)];
|
|
101
|
-
return results.filter((r) => r !== undefined);
|
|
102
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* WS-3b Step 8 — Anti-collapse merge guards.
|
|
6
|
-
*
|
|
7
|
-
* (a) Generation counter: merged.generation = max(sources)+1; merges cite
|
|
8
|
-
* sources. `over_generation_count` (collapse-detector.ts) tracks assets
|
|
9
|
-
* above the generation threshold as an advisory metric only — there is
|
|
10
|
-
* no merge-refusal path wired in.
|
|
11
|
-
* (b) Lexical-diversity check: low n-gram diversity ⇒ raise merge threshold.
|
|
12
|
-
* (d) Occasional random non-similar cluster in the pool.
|
|
13
|
-
*
|
|
14
|
-
* @module anti-collapse
|
|
15
|
-
*/
|
|
16
|
-
/** Default max generation depth before merge is refused. */
|
|
17
|
-
export const DEFAULT_MAX_GENERATION = 2;
|
|
18
|
-
/** Default fraction of pool to fill with random (non-similar) clusters. */
|
|
19
|
-
export const DEFAULT_RANDOM_CLUSTER_FRACTION = 0.05;
|
|
20
|
-
/**
|
|
21
|
-
* Read the `generation` field from an asset's frontmatter.
|
|
22
|
-
* Returns 0 when absent (no generation metadata = original asset).
|
|
23
|
-
*/
|
|
24
|
-
export function readAssetGeneration(frontmatterData) {
|
|
25
|
-
const gen = frontmatterData.generation;
|
|
26
|
-
if (typeof gen === "number" && Number.isFinite(gen) && gen >= 0) {
|
|
27
|
-
return Math.floor(gen);
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Compute the new generation for a merged asset.
|
|
33
|
-
* Rule: `merged.generation = max(source generations) + 1`.
|
|
34
|
-
*/
|
|
35
|
-
export function computeMergedGeneration(sourceGenerations) {
|
|
36
|
-
if (sourceGenerations.length === 0)
|
|
37
|
-
return 1;
|
|
38
|
-
return Math.max(...sourceGenerations) + 1;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Compute the bigram n-gram diversity of a text string.
|
|
42
|
-
* Returns a value in [0, 1] where 0 = all identical bigrams, 1 = all unique.
|
|
43
|
-
* Used by the lexical-diversity check to detect correlated-extraction artifacts.
|
|
44
|
-
*/
|
|
45
|
-
export function computeBigramDiversity(text) {
|
|
46
|
-
const words = text
|
|
47
|
-
.toLowerCase()
|
|
48
|
-
.split(/\s+/)
|
|
49
|
-
.filter((w) => w.length > 0);
|
|
50
|
-
if (words.length < 2)
|
|
51
|
-
return 1; // too short to have bigrams; treat as diverse
|
|
52
|
-
const total = words.length - 1;
|
|
53
|
-
const unique = new Set();
|
|
54
|
-
for (let i = 0; i < total; i++) {
|
|
55
|
-
unique.add(`${words[i]}\t${words[i + 1]}`);
|
|
56
|
-
}
|
|
57
|
-
return unique.size / total;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Check whether a cluster of memories exhibits suspiciously low lexical diversity.
|
|
61
|
-
* When true, the cluster is likely a correlated-extraction artifact; the merge
|
|
62
|
-
* threshold should be raised.
|
|
63
|
-
*
|
|
64
|
-
* @param bodies - The stripped body texts of the cluster members.
|
|
65
|
-
* @param config - Anti-collapse config.
|
|
66
|
-
* @returns `{ lowDiversity: true, diversity }` when the cluster diversity is
|
|
67
|
-
* below the 0.3 threshold; `{ lowDiversity: false }` otherwise.
|
|
68
|
-
*/
|
|
69
|
-
export function checkLexicalDiversity(bodies, config) {
|
|
70
|
-
// R5: default ON — only an explicit opt-out disables the check.
|
|
71
|
-
if (config.enabled === false || config.lexicalDiversityCheck === false) {
|
|
72
|
-
return { lowDiversity: false };
|
|
73
|
-
}
|
|
74
|
-
if (bodies.length === 0)
|
|
75
|
-
return { lowDiversity: false };
|
|
76
|
-
// Average bigram diversity across all bodies in the cluster.
|
|
77
|
-
const avg = bodies.reduce((sum, b) => sum + computeBigramDiversity(b), 0) / bodies.length;
|
|
78
|
-
const DIVERSITY_FLOOR = 0.3;
|
|
79
|
-
if (avg < DIVERSITY_FLOOR) {
|
|
80
|
-
return { lowDiversity: true, diversity: avg };
|
|
81
|
-
}
|
|
82
|
-
return { lowDiversity: false };
|
|
83
|
-
}
|
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* R5 — Longitudinal collapse detector
|
|
6
|
-
* (docs/architecture/specs/improve-collapse-churn-detector-design.md).
|
|
7
|
-
*
|
|
8
|
-
* Detects the measured collapse failure mode of LLM-consolidated memory
|
|
9
|
-
* stores: repeated merges destroy information — canary retrieval recall
|
|
10
|
-
* downtrends, distinct-content entropy downtrends, or the store shrinks
|
|
11
|
-
* while generation counts rise.
|
|
12
|
-
*
|
|
13
|
-
* The CHURN alert class described in the design doc was removed: production
|
|
14
|
-
* never threaded a real accepted-change-volume signal in (the value was a
|
|
15
|
-
* hardcoded 0 from the 0.9.0 confidence-gate deletion onward), so the alert
|
|
16
|
-
* could never fire. See the design doc's status note for the removal
|
|
17
|
-
* rationale. `improve_cycle_metrics.accepted_actions` stays in the schema and
|
|
18
|
-
* is written as 0 because deployed `state.db` files already contain it and
|
|
19
|
-
* removing it would be a destructive schema change.
|
|
20
|
-
*
|
|
21
|
-
* Hard invariants: deterministic only (FTS BM25 + hashing — never an LLM,
|
|
22
|
-
* never an embedding model); bounded storage (< 2 KB per qualifying cycle,
|
|
23
|
-
* 365-day retention); fail-open (an error warns and skips, never breaks an
|
|
24
|
-
* improve run); runs only on cycles where consolidate did work.
|
|
25
|
-
*
|
|
26
|
-
* Observe-only in v1: alerts land in `improve_cycle_metrics.alerts_json`, the
|
|
27
|
-
* events log (`collapse_detector_alert`), and the `akm health` advisory —
|
|
28
|
-
* nothing is ever blocked.
|
|
29
|
-
*
|
|
30
|
-
* @module collapse-detector
|
|
31
|
-
*/
|
|
32
|
-
import { randomBytes } from "node:crypto";
|
|
33
|
-
import { parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
34
|
-
import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
|
|
35
|
-
import { getImproveProcessConfig } from "../../core/config/config.js";
|
|
36
|
-
import { appendEvent } from "../../core/events.js";
|
|
37
|
-
import { withStateDb } from "../../core/state-db.js";
|
|
38
|
-
import { warn } from "../../core/warn.js";
|
|
39
|
-
import { deactivateCanarySet, getActiveCanaries, getCanariesBySetId, insertCanaries, insertCycleMetrics, listActiveCanarySetIds, queryRecentCycleMetrics, } from "../../storage/repositories/canaries-repository.js";
|
|
40
|
-
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
41
|
-
import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
|
|
42
|
-
import { searchFts } from "../../storage/repositories/index-fts-repository.js";
|
|
43
|
-
import { computeBigramDiversity, DEFAULT_MAX_GENERATION } from "./anti-collapse.js";
|
|
44
|
-
import { getAllRankScores } from "./salience.js";
|
|
45
|
-
// ── Defaults (mirrored in config-schema.ts ImproveCollapseDetectorSchema) ────
|
|
46
|
-
export const DEFAULT_CANARY_COUNT = 40; // owner-approved 30–50 range
|
|
47
|
-
export const DEFAULT_CANARY_K = 10;
|
|
48
|
-
export const DEFAULT_WINDOW_CYCLES = 5;
|
|
49
|
-
export const DEFAULT_RECALL_DROP_THRESHOLD = 0.15;
|
|
50
|
-
export const DEFAULT_ENTROPY_DROP_THRESHOLD = 0.05;
|
|
51
|
-
export const DEFAULT_RETENTION_DAYS = 365;
|
|
52
|
-
/** Deterministic bigram-diversity sample cap (cost bound at 10k assets). */
|
|
53
|
-
const DIVERSITY_SAMPLE_CAP = 2000;
|
|
54
|
-
/**
|
|
55
|
-
* Minimum merge-floor violations in one cycle before the advisory alert fires.
|
|
56
|
-
* The specificity floor is deliberately strict (Phase-1 tuning pending), so a
|
|
57
|
-
* couple of borderline merges per cycle must not flip `akm health` to warn —
|
|
58
|
-
* that alert fatigue would drown the real collapse signals.
|
|
59
|
-
*/
|
|
60
|
-
const MERGE_FLOOR_ALERT_MIN = 3;
|
|
61
|
-
/** The learning-store types the detector measures. */
|
|
62
|
-
const LEARNING_TYPES = new Set(["memory", "lesson", "knowledge"]);
|
|
63
|
-
// ── Canary set ────────────────────────────────────────────────────────────────
|
|
64
|
-
/** Deterministic query string for one anchor entry: name tokens + top tags + description head. */
|
|
65
|
-
function buildCanaryQuery(entry) {
|
|
66
|
-
const nameTokens = entry.entry.name.split(/[-_/.]+/).filter((t) => t.length > 1);
|
|
67
|
-
const tags = (entry.entry.tags ?? []).slice(0, 3);
|
|
68
|
-
const descriptionHead = (entry.entry.description ?? "").split(/\s+/).slice(0, 6);
|
|
69
|
-
const parts = [...nameTokens, ...tags, ...descriptionHead].filter((t) => t.length > 0);
|
|
70
|
-
return [...new Set(parts)].join(" ");
|
|
71
|
-
}
|
|
72
|
-
/** Build the mint candidate list (deterministic given index + salience tables). */
|
|
73
|
-
function buildMintList(stateDb, entries, cfg) {
|
|
74
|
-
const canaryCount = cfg.canaryCount ?? DEFAULT_CANARY_COUNT;
|
|
75
|
-
const rankScores = getAllRankScores(stateDb);
|
|
76
|
-
// Chunk-8 WI-8.5c: the canary anchor + salience correlation both key on the
|
|
77
|
-
// SHORT conceptId (`<stash-subdir>/<name>`, D-R2) — the same spelling the
|
|
78
|
-
// improve candidate refs and content xrefs now carry (post WI-8.5a).
|
|
79
|
-
const candidates = entries
|
|
80
|
-
.filter((e) => LEARNING_TYPES.has(e.entry.type))
|
|
81
|
-
.map((e) => {
|
|
82
|
-
const ref = conceptIdFromTypeName(e.entry.type, e.entry.name);
|
|
83
|
-
return { e, ref, score: rankScores.get(ref) ?? 0 };
|
|
84
|
-
})
|
|
85
|
-
.sort((a, b) => b.score - a.score || (a.ref < b.ref ? -1 : 1));
|
|
86
|
-
// Type-stratified top slice: ⅓ per learning type, backfill from global order.
|
|
87
|
-
const perType = Math.ceil(canaryCount / 3);
|
|
88
|
-
const picked = new Map();
|
|
89
|
-
for (const type of LEARNING_TYPES) {
|
|
90
|
-
let taken = 0;
|
|
91
|
-
for (const c of candidates) {
|
|
92
|
-
if (taken >= perType || picked.size >= canaryCount)
|
|
93
|
-
break;
|
|
94
|
-
if (c.e.entry.type === type && !picked.has(c.ref)) {
|
|
95
|
-
picked.set(c.ref, c);
|
|
96
|
-
taken++;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
for (const c of candidates) {
|
|
101
|
-
if (picked.size >= canaryCount)
|
|
102
|
-
break;
|
|
103
|
-
if (!picked.has(c.ref))
|
|
104
|
-
picked.set(c.ref, c);
|
|
105
|
-
}
|
|
106
|
-
return [...picked.values()]
|
|
107
|
-
.map((c) => ({ anchorRef: c.ref, query: buildCanaryQuery(c.e) }))
|
|
108
|
-
.filter((c) => c.query.length > 0);
|
|
109
|
-
}
|
|
110
|
-
/** Collision-safe mint token (same-millisecond mints happen in tests + concurrent runs). */
|
|
111
|
-
function newCanarySetId() {
|
|
112
|
-
return `canary-${Date.now().toString(36)}-${randomBytes(2).toString("hex")}`;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Mint (or return) the active canary set. Deterministic given the index +
|
|
116
|
-
* salience tables: rank the three learning types by `asset_salience.rank_score`
|
|
117
|
-
* (fallback 0, tie-broken by ref), take a type-stratified top slice
|
|
118
|
-
* (⅓ per type, backfilled from the global ranking when a type is short).
|
|
119
|
-
*
|
|
120
|
-
* Returns `null` when the index has no mintable learning entries — a cycle
|
|
121
|
-
* with no canary set is NOT recorded (a fresh unused set id every cycle would
|
|
122
|
-
* mean the trend window never fills and recall reads as a fake 0).
|
|
123
|
-
*
|
|
124
|
-
* NEVER auto-refreshes: once minted the set is frozen until an explicit
|
|
125
|
-
* `bun scripts/refresh-canary-set.ts --refresh` — silent re-baselining is how
|
|
126
|
-
* a slow collapse hides. Rows are read back BY OUR OWN set id (never "newest
|
|
127
|
-
* active") so a concurrent mint in another process cannot relabel this run's
|
|
128
|
-
* metrics.
|
|
129
|
-
*/
|
|
130
|
-
export function ensureCanarySet(stateDb, indexDb, cfg, preloadedEntries) {
|
|
131
|
-
const existing = getActiveCanaries(stateDb);
|
|
132
|
-
if (existing.length > 0) {
|
|
133
|
-
return { canarySetId: existing[0].canary_set_id, canaries: existing };
|
|
134
|
-
}
|
|
135
|
-
const minted = buildMintList(stateDb, preloadedEntries ?? getAllEntries(indexDb), cfg);
|
|
136
|
-
if (minted.length === 0)
|
|
137
|
-
return null;
|
|
138
|
-
const canarySetId = newCanarySetId();
|
|
139
|
-
insertCanaries(stateDb, canarySetId, minted);
|
|
140
|
-
return { canarySetId, canaries: getCanariesBySetId(stateDb, canarySetId) };
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Explicit canary re-mint (the ONLY refresh path —
|
|
144
|
-
* `bun scripts/refresh-canary-set.ts --refresh`). Mint-first, deactivate-after:
|
|
145
|
-
* when the index is empty or unreadable the current baseline is left untouched
|
|
146
|
-
* instead of destroyed. Deactivates ALL other active sets (not just the
|
|
147
|
-
* newest) so stragglers from an interrupted refresh can never resurrect.
|
|
148
|
-
*/
|
|
149
|
-
export function refreshCanarySet(stateDb, indexDb, cfg) {
|
|
150
|
-
const minted = buildMintList(stateDb, getAllEntries(indexDb), cfg);
|
|
151
|
-
if (minted.length === 0)
|
|
152
|
-
return null; // nothing mintable — keep the old baseline
|
|
153
|
-
const canarySetId = newCanarySetId();
|
|
154
|
-
insertCanaries(stateDb, canarySetId, minted);
|
|
155
|
-
for (const oldSetId of listActiveCanarySetIds(stateDb)) {
|
|
156
|
-
if (oldSetId !== canarySetId)
|
|
157
|
-
deactivateCanarySet(stateDb, oldSetId);
|
|
158
|
-
}
|
|
159
|
-
return { canarySetId, canaries: getCanariesBySetId(stateDb, canarySetId) };
|
|
160
|
-
}
|
|
161
|
-
// ── Cycle metrics ─────────────────────────────────────────────────────────────
|
|
162
|
-
/**
|
|
163
|
-
* Name-free content fingerprint text for entropy metrics. The indexed
|
|
164
|
-
* search_text EMBEDS the (unique) entry name, which would pin the
|
|
165
|
-
* distinct-content ratio at 1.0 forever; convergence shows up in the
|
|
166
|
-
* description/tags/heading fields, so those are what get hashed. Bounded body
|
|
167
|
-
* content is searchable but deliberately excluded from this fingerprint: the
|
|
168
|
-
* detector measures the structured surface generic merged assets converge on.
|
|
169
|
-
*/
|
|
170
|
-
function contentFingerprint(entry) {
|
|
171
|
-
const parts = [entry.description ?? "", (entry.tags ?? []).join(" "), (entry.toc ?? []).map((h) => h.text).join(" ")];
|
|
172
|
-
return parts.filter((t) => t.length > 0).join(" ");
|
|
173
|
-
}
|
|
174
|
-
/** FNV-1a 64-bit over lowercased whitespace-collapsed text (distinct-content hashing). */
|
|
175
|
-
export function normHash(text) {
|
|
176
|
-
const normalized = text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
177
|
-
let hash = 0xcbf29ce484222325n;
|
|
178
|
-
const prime = 0x100000001b3n;
|
|
179
|
-
for (let i = 0; i < normalized.length; i++) {
|
|
180
|
-
hash ^= BigInt(normalized.charCodeAt(i));
|
|
181
|
-
hash = (hash * prime) & 0xffffffffffffffffn;
|
|
182
|
-
}
|
|
183
|
-
return hash.toString(16);
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Score one canary against the live index, merge-following via canonical
|
|
187
|
-
* `xrefs`. A hit is the anchor ref itself or any returned entry whose
|
|
188
|
-
* provenance contains the anchor.
|
|
189
|
-
* Returns the 0-based rank of the first hit, or -1.
|
|
190
|
-
*/
|
|
191
|
-
function scoreCanary(indexDb, canary, k) {
|
|
192
|
-
const results = searchFts(indexDb, canary.query, k);
|
|
193
|
-
const anchorConceptId = canaryConceptId(canary.anchor_ref);
|
|
194
|
-
for (let i = 0; i < Math.min(results.length, k); i++) {
|
|
195
|
-
const r = results[i];
|
|
196
|
-
// Persisted canaries may be fully qualified after a state migration while
|
|
197
|
-
// index refs and xrefs remain short conceptIds. Compare identity, not display
|
|
198
|
-
// spelling, so migration alone cannot collapse recall to zero.
|
|
199
|
-
const ref = conceptIdFromTypeName(r.entry.type, r.entry.name);
|
|
200
|
-
if (ref === anchorConceptId)
|
|
201
|
-
return i;
|
|
202
|
-
const provenance = r.entry.xrefs ?? [];
|
|
203
|
-
if (provenance.some((sourceRef) => canaryConceptId(sourceRef) === anchorConceptId))
|
|
204
|
-
return i;
|
|
205
|
-
}
|
|
206
|
-
return -1;
|
|
207
|
-
}
|
|
208
|
-
function canaryConceptId(ref) {
|
|
209
|
-
try {
|
|
210
|
-
return parseBundleRef(ref).conceptId;
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
return ref;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Compute one qualifying cycle's store-health snapshot. One `entries` scan +
|
|
218
|
-
* `canaryCount` FTS queries; no LLM, no embedding model, no filesystem reads.
|
|
219
|
-
* Returns `null` when no canary set exists AND none is mintable (empty index)
|
|
220
|
-
* — such a cycle is not measurable and must not be recorded.
|
|
221
|
-
*/
|
|
222
|
-
export function computeCycleMetrics(stateDb, indexDb, args) {
|
|
223
|
-
const k = args.cfg.k ?? DEFAULT_CANARY_K;
|
|
224
|
-
const maxGeneration = args.maxGeneration ?? DEFAULT_MAX_GENERATION;
|
|
225
|
-
// Single entries scan — shared by the canary mint (if one is needed) and
|
|
226
|
-
// the store-shape metrics below.
|
|
227
|
-
const all = getAllEntries(indexDb);
|
|
228
|
-
const canarySet = ensureCanarySet(stateDb, indexDb, args.cfg, all);
|
|
229
|
-
if (canarySet === null)
|
|
230
|
-
return null;
|
|
231
|
-
const { canarySetId, canaries } = canarySet;
|
|
232
|
-
// ── Canary retrieval metrics ───────────────────────────────────────────────
|
|
233
|
-
const ranks = [];
|
|
234
|
-
let recallSum = 0;
|
|
235
|
-
let ndcgSum = 0;
|
|
236
|
-
let mrrSum = 0;
|
|
237
|
-
for (const canary of canaries) {
|
|
238
|
-
const rank = scoreCanary(indexDb, canary, k);
|
|
239
|
-
ranks.push([canary.id, rank]);
|
|
240
|
-
if (rank >= 0) {
|
|
241
|
-
recallSum += 1;
|
|
242
|
-
mrrSum += 1 / (rank + 1);
|
|
243
|
-
// Single-relevant nDCG@k closed form: ideal DCG is 1, so the score is
|
|
244
|
-
// just the discount at the hit rank.
|
|
245
|
-
ndcgSum += 1 / Math.log2(rank + 2);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
const n = Math.max(1, canaries.length);
|
|
249
|
-
// ── Store-shape metrics (same single entries scan) ────────────────────────
|
|
250
|
-
const byType = new Map();
|
|
251
|
-
const contentHashes = new Set();
|
|
252
|
-
let learningTotal = 0;
|
|
253
|
-
let overGeneration = 0;
|
|
254
|
-
const learningTexts = [];
|
|
255
|
-
for (const e of all) {
|
|
256
|
-
byType.set(e.entry.type, (byType.get(e.entry.type) ?? 0) + 1);
|
|
257
|
-
if (!LEARNING_TYPES.has(e.entry.type))
|
|
258
|
-
continue;
|
|
259
|
-
learningTotal++;
|
|
260
|
-
const fingerprint = contentFingerprint(e.entry);
|
|
261
|
-
contentHashes.add(normHash(fingerprint));
|
|
262
|
-
if ((e.entry.generation ?? 0) > maxGeneration)
|
|
263
|
-
overGeneration++;
|
|
264
|
-
learningTexts.push({ key: e.itemRef, text: fingerprint });
|
|
265
|
-
}
|
|
266
|
-
// Deterministic diversity sample: sort by item ref, take every ⌈N/cap⌉-th row.
|
|
267
|
-
learningTexts.sort((a, b) => (a.key < b.key ? -1 : 1));
|
|
268
|
-
const step = Math.max(1, Math.ceil(learningTexts.length / DIVERSITY_SAMPLE_CAP));
|
|
269
|
-
let diversitySum = 0;
|
|
270
|
-
let diversityCount = 0;
|
|
271
|
-
for (let i = 0; i < learningTexts.length; i += step) {
|
|
272
|
-
diversitySum += computeBigramDiversity(learningTexts[i].text);
|
|
273
|
-
diversityCount++;
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
run_id: args.runId,
|
|
277
|
-
ts: (args.now ?? new Date()).toISOString(),
|
|
278
|
-
pass: args.pass,
|
|
279
|
-
canary_set_id: canarySetId,
|
|
280
|
-
mean_recall: recallSum / n,
|
|
281
|
-
mean_ndcg: ndcgSum / n,
|
|
282
|
-
mean_mrr: mrrSum / n,
|
|
283
|
-
canary_ranks_json: JSON.stringify(ranks),
|
|
284
|
-
store_total: learningTotal,
|
|
285
|
-
store_by_type_json: JSON.stringify(Object.fromEntries([...byType.entries()].sort())),
|
|
286
|
-
distinct_content_ratio: learningTotal === 0 ? 1 : contentHashes.size / learningTotal,
|
|
287
|
-
mean_bigram_diversity: diversityCount === 0 ? 1 : diversitySum / diversityCount,
|
|
288
|
-
over_generation_count: overGeneration,
|
|
289
|
-
// Always 0 — the CHURN alert this fed was removed. The column is NOT NULL
|
|
290
|
-
// in a released migration body, so it is written rather than dropped.
|
|
291
|
-
accepted_actions: 0,
|
|
292
|
-
merge_floor_violations: args.mergeFloorViolations,
|
|
293
|
-
alerts_json: "[]",
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
// ── Alert evaluation (pure) ───────────────────────────────────────────────────
|
|
297
|
-
function median(values) {
|
|
298
|
-
if (values.length === 0)
|
|
299
|
-
return Number.NaN;
|
|
300
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
301
|
-
const mid = Math.floor(sorted.length / 2);
|
|
302
|
-
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Evaluate the §1 alert definitions. PURE — history rows (oldest-first, NOT
|
|
306
|
-
* including `current`) plus the current row in, alerts out. A window shorter
|
|
307
|
-
* than `windowCycles` never fires (no baseline yet); the merge-floor advisory
|
|
308
|
-
* is per-cycle and fires regardless of window depth.
|
|
309
|
-
*/
|
|
310
|
-
export function evaluateCollapseAlerts(history, current, cfg) {
|
|
311
|
-
const alerts = [];
|
|
312
|
-
// MERGE-FLOOR advisory: per-cycle, window-independent. Gated on a minimum
|
|
313
|
-
// count — the specificity floor is deliberately strict pre-tuning, and one
|
|
314
|
-
// or two borderline merges per cycle must not generate alert fatigue.
|
|
315
|
-
if (current.merge_floor_violations >= MERGE_FLOOR_ALERT_MIN) {
|
|
316
|
-
alerts.push({
|
|
317
|
-
kind: "merge-floor",
|
|
318
|
-
detail: `${current.merge_floor_violations} merge(s) failed the information floor this cycle (provenance shrank or specificity below threshold)`,
|
|
319
|
-
metrics: { mergeFloorViolations: current.merge_floor_violations },
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
const W = cfg.windowCycles ?? DEFAULT_WINDOW_CYCLES;
|
|
323
|
-
const hist = history.slice(-W);
|
|
324
|
-
if (hist.length < W)
|
|
325
|
-
return alerts; // no baseline yet
|
|
326
|
-
// Window is non-empty here: `windowCycles` is schema-bounded to ≥2 (default 5),
|
|
327
|
-
// so `hist.length >= W >= 2`. `windowStart` is the oldest row in the window.
|
|
328
|
-
const windowStart = hist[0];
|
|
329
|
-
const recallDrop = cfg.recallDropThreshold ?? DEFAULT_RECALL_DROP_THRESHOLD;
|
|
330
|
-
const entropyDrop = cfg.entropyDropThreshold ?? DEFAULT_ENTROPY_DROP_THRESHOLD;
|
|
331
|
-
// COLLAPSE 1 — canary recall drop vs window median (median, not previous
|
|
332
|
-
// cycle, so one noisy cycle can neither fire nor mask the alert).
|
|
333
|
-
const medianRecall = median(hist.map((h) => h.mean_recall));
|
|
334
|
-
if (current.mean_recall <= medianRecall - recallDrop) {
|
|
335
|
-
alerts.push({
|
|
336
|
-
kind: "collapse-recall",
|
|
337
|
-
detail: `mean canary recall ${current.mean_recall.toFixed(3)} dropped ≥${recallDrop} below the ${W}-cycle median ${medianRecall.toFixed(3)}`,
|
|
338
|
-
metrics: { currentRecall: current.mean_recall, medianRecall, threshold: recallDrop },
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
// COLLAPSE 2 — monotonic distinct-content-ratio decline over the window.
|
|
342
|
-
const series = [...hist.map((h) => h.distinct_content_ratio), current.distinct_content_ratio];
|
|
343
|
-
const monotonicNonIncreasing = series.every((v, i) => i === 0 || v <= series[i - 1]);
|
|
344
|
-
const totalDecline = windowStart.distinct_content_ratio - current.distinct_content_ratio;
|
|
345
|
-
if (monotonicNonIncreasing && totalDecline >= entropyDrop) {
|
|
346
|
-
alerts.push({
|
|
347
|
-
kind: "collapse-entropy",
|
|
348
|
-
detail: `distinct-content ratio declined monotonically by ${totalDecline.toFixed(3)} (≥${entropyDrop}) over ${W} cycles — store content is converging`,
|
|
349
|
-
metrics: {
|
|
350
|
-
windowStart: windowStart.distinct_content_ratio,
|
|
351
|
-
current: current.distinct_content_ratio,
|
|
352
|
-
decline: totalDecline,
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
// COLLAPSE 3 — store shrinking BECAUSE of re-merging (not deletion hygiene).
|
|
357
|
-
const maxStore = Math.max(...hist.map((h) => h.store_total));
|
|
358
|
-
if (current.store_total < 0.8 * maxStore && current.over_generation_count > windowStart.over_generation_count) {
|
|
359
|
-
alerts.push({
|
|
360
|
-
kind: "collapse-shrink",
|
|
361
|
-
detail: `store shrank >20% (${current.store_total} vs window max ${maxStore}) while over-generation count rose (${windowStart.over_generation_count} → ${current.over_generation_count})`,
|
|
362
|
-
metrics: {
|
|
363
|
-
storeTotal: current.store_total,
|
|
364
|
-
windowMax: maxStore,
|
|
365
|
-
overGeneration: current.over_generation_count,
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
return alerts;
|
|
370
|
-
}
|
|
371
|
-
// ── Orchestrator ─────────────────────────────────────────────────────────────
|
|
372
|
-
/**
|
|
373
|
-
* Run the detector for one qualifying cycle: ensure canaries → compute →
|
|
374
|
-
* evaluate against stored history → persist the row → append one
|
|
375
|
-
* `collapse_detector_alert` event per fired alert. FAIL-OPEN: any error warns
|
|
376
|
-
* and returns undefined — an improve run is never broken by its own
|
|
377
|
-
* instrumentation.
|
|
378
|
-
*/
|
|
379
|
-
export function runCollapseDetector(args) {
|
|
380
|
-
const cfg = args.config.improve?.collapseDetector ?? {};
|
|
381
|
-
if (cfg.enabled === false)
|
|
382
|
-
return undefined;
|
|
383
|
-
try {
|
|
384
|
-
let indexDb;
|
|
385
|
-
try {
|
|
386
|
-
indexDb = openExistingDatabase(args.indexDbPath);
|
|
387
|
-
const db = indexDb;
|
|
388
|
-
// Over-generation threshold mirrors the guard actually in effect —
|
|
389
|
-
// reading the same config key keeps the two aligned when tuned.
|
|
390
|
-
const antiCollapse = getImproveProcessConfig("consolidate", args.improveProfile)?.antiCollapse;
|
|
391
|
-
const maxGeneration = antiCollapse?.maxGeneration ?? DEFAULT_MAX_GENERATION;
|
|
392
|
-
return withStateDb((stateDb) => {
|
|
393
|
-
const row = computeCycleMetrics(stateDb, db, {
|
|
394
|
-
runId: args.runId,
|
|
395
|
-
pass: args.pass,
|
|
396
|
-
mergeFloorViolations: args.mergeFloorViolations,
|
|
397
|
-
cfg,
|
|
398
|
-
maxGeneration,
|
|
399
|
-
});
|
|
400
|
-
if (row === null)
|
|
401
|
-
return undefined; // empty index — nothing to measure
|
|
402
|
-
const windowCycles = cfg.windowCycles ?? DEFAULT_WINDOW_CYCLES;
|
|
403
|
-
const history = queryRecentCycleMetrics(stateDb, row.canary_set_id, windowCycles);
|
|
404
|
-
const alerts = evaluateCollapseAlerts(history, row, cfg);
|
|
405
|
-
row.alerts_json = JSON.stringify(alerts.map((a) => a.kind));
|
|
406
|
-
insertCycleMetrics(stateDb, row);
|
|
407
|
-
for (const alert of alerts) {
|
|
408
|
-
appendEvent({
|
|
409
|
-
eventType: "collapse_detector_alert",
|
|
410
|
-
ref: undefined,
|
|
411
|
-
metadata: {
|
|
412
|
-
kind: alert.kind,
|
|
413
|
-
detail: alert.detail,
|
|
414
|
-
metrics: alert.metrics,
|
|
415
|
-
canarySetId: row.canary_set_id,
|
|
416
|
-
runId: args.runId,
|
|
417
|
-
},
|
|
418
|
-
}, args.eventsCtx);
|
|
419
|
-
}
|
|
420
|
-
return row;
|
|
421
|
-
}, { path: args.eventsCtx?.dbPath, borrowed: args.eventsCtx?.db });
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
if (indexDb)
|
|
425
|
-
closeDatabase(indexDb);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
catch (err) {
|
|
429
|
-
warn(`[collapse-detector] skipped (fail-open): ${err instanceof Error ? err.message : String(err)}`);
|
|
430
|
-
return undefined;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
// Eligibility / safety predicates for consolidate: "may we touch this memory?"
|
|
5
|
-
// One reason to change — the policy for what consolidate is allowed to act on.
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
8
|
-
import { hasHotCaptureMode } from "../../proposal/validators/proposal-quality-validators.js";
|
|
9
|
-
export function isConsolidationEligibleMemoryName(name) {
|
|
10
|
-
return !name.endsWith(".derived");
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Returns true when the memory file has `captureMode: hot` in its frontmatter.
|
|
14
|
-
*
|
|
15
|
-
* Hot memories are USER-EXPLICIT (written via `akm remember` on the hot path).
|
|
16
|
-
* The consolidate LLM is forbidden from deleting or auto-merging them — the
|
|
17
|
-
* user wrote them on purpose and only the user can decide to retire them.
|
|
18
|
-
*
|
|
19
|
-
* Reads the file once per check; consolidate runs against ~10 memories per
|
|
20
|
-
* chunk so the IO cost is trivial. Returns false on any read/parse error
|
|
21
|
-
* (fail-safe: an unreadable or unparseable file is treated as HOT — protected
|
|
22
|
-
* — because a deletion shield must not fail open; a missing file is not-hot).
|
|
23
|
-
*
|
|
24
|
-
* Defends against four observed defect classes (see
|
|
25
|
-
* `memories/akm-improve-critical-review-2026-05-20`):
|
|
26
|
-
* - LLM marks a memory contradicted then deletes (dangling contradictedBy)
|
|
27
|
-
* - LLM merges two unrelated memories sharing a topic keyword
|
|
28
|
-
* - LLM judges a recent durable design memo as "redundant"
|
|
29
|
-
* - Cascade deletes (LLM uses ref:X as `contradictedBy` for ref:Y then deletes both)
|
|
30
|
-
*/
|
|
31
|
-
export function isHotCapturedMemory(filePath) {
|
|
32
|
-
if (!fs.existsSync(filePath))
|
|
33
|
-
return false;
|
|
34
|
-
try {
|
|
35
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
36
|
-
const parsed = parseFrontmatter(content);
|
|
37
|
-
return hasHotCaptureMode(parsed.data);
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Fail CLOSED. This predicate is a deletion shield: "hot" memories are
|
|
41
|
-
// protected from consolidate's merge/delete. Returning false on a read or
|
|
42
|
-
// parse failure marked exactly the memories we could not inspect as fair
|
|
43
|
-
// game — the one direction a protection check must never fail. A missing
|
|
44
|
-
// file stays false (nothing to protect); an unreadable one is protected
|
|
45
|
-
// until someone can actually read it.
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|