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,337 +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
|
-
* Distill quality-gate cluster — LLM-as-judge, quality-rejection envelope
|
|
6
|
-
* writer, and output-salience persistence. Extracted verbatim from
|
|
7
|
-
* `distill.ts` so the main `akmDistill` orchestrator and the memory→knowledge
|
|
8
|
-
* promotion branch (`promote-memory.ts`) can share the same helpers without a
|
|
9
|
-
* circular import. Logic is byte-identical to the pre-extraction inline code.
|
|
10
|
-
*/
|
|
11
|
-
import fs from "node:fs";
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import { parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
14
|
-
import { timestampForFilename } from "../../../core/common.js";
|
|
15
|
-
import { ConfigError } from "../../../core/errors.js";
|
|
16
|
-
import { appendEvent } from "../../../core/events.js";
|
|
17
|
-
import { parseEmbeddedJsonResponse } from "../../../core/parse.js";
|
|
18
|
-
import { getDistillRejectedDir } from "../../../core/paths.js";
|
|
19
|
-
import { withStateDb } from "../../../core/state-db.js";
|
|
20
|
-
import { recordWrittenPath } from "../../../core/write-provenance.js";
|
|
21
|
-
import { callStructured } from "../../../llm/structured-call.js";
|
|
22
|
-
import { akmSearch } from "../../read/search.js";
|
|
23
|
-
import { scoreEncodingSalience } from "../encoding-salience.js";
|
|
24
|
-
import { resolveImproveLlmExecution } from "../execution.js";
|
|
25
|
-
import { computeSalience, upsertAssetSalience } from "../salience.js";
|
|
26
|
-
// ── D-4 / #390: Top-3 similar lessons retrieval ──────────────────────────────
|
|
27
|
-
/**
|
|
28
|
-
* Default implementation: use akmSearch to find top-N similar lesson assets.
|
|
29
|
-
* Returns empty array when search fails or returns no results.
|
|
30
|
-
* Requires embedding configured for semantic similarity; degrades gracefully.
|
|
31
|
-
*/
|
|
32
|
-
export async function fetchTopSimilarLessons(query, n, _stashDir) {
|
|
33
|
-
try {
|
|
34
|
-
const result = await akmSearch({
|
|
35
|
-
query,
|
|
36
|
-
type: "lesson",
|
|
37
|
-
limit: n,
|
|
38
|
-
skipLogging: true,
|
|
39
|
-
eventSource: "improve",
|
|
40
|
-
});
|
|
41
|
-
const hits = result?.hits ?? [];
|
|
42
|
-
return hits
|
|
43
|
-
.filter((h) => "path" in h && typeof h.path === "string")
|
|
44
|
-
.slice(0, n)
|
|
45
|
-
.map((h) => {
|
|
46
|
-
let content = "";
|
|
47
|
-
try {
|
|
48
|
-
if (h.path && fs.existsSync(h.path)) {
|
|
49
|
-
content = fs.readFileSync(h.path, "utf8");
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
/* best-effort */
|
|
54
|
-
}
|
|
55
|
-
return { ref: h.ref, content };
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// ── LLM-as-judge quality gate (P2-B) ────────────────────────────────────────
|
|
63
|
-
/**
|
|
64
|
-
* D-4 / #390: Build the LLM-as-judge prompt.
|
|
65
|
-
*
|
|
66
|
-
* When similarLessons are provided (top-3 by embedding similarity), they are
|
|
67
|
-
* included in the context so the judge can lower the score for near-duplicates.
|
|
68
|
-
* Voyager arXiv:2305.16291 — skill library admission requires similarity check
|
|
69
|
-
* against the existing library. A-MEM arXiv:2502.12110 — new notes are checked
|
|
70
|
-
* against existing notes before linking.
|
|
71
|
-
*/
|
|
72
|
-
export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
|
|
73
|
-
const lines = [
|
|
74
|
-
"You are evaluating a proposed lesson asset for an akm knowledge base.",
|
|
75
|
-
"",
|
|
76
|
-
"Score this lesson on each criterion from 1 (poor) to 5 (excellent):",
|
|
77
|
-
"1. NOVELTY: Does the lesson add information not already present in the source asset?",
|
|
78
|
-
"2. ACTIONABILITY: Can an agent follow this lesson without additional context?",
|
|
79
|
-
"3. NON-REDUNDANCY: Is this lesson meaningfully different from what the source already says?",
|
|
80
|
-
"",
|
|
81
|
-
"Source asset content:",
|
|
82
|
-
"```",
|
|
83
|
-
sourceContent.slice(0, 2000),
|
|
84
|
-
"```",
|
|
85
|
-
];
|
|
86
|
-
if (similarLessons && similarLessons.length > 0) {
|
|
87
|
-
lines.push("");
|
|
88
|
-
lines.push("Existing similar lessons (top-3 by similarity). Rate lower if the proposed lesson is substantially similar to any of these:");
|
|
89
|
-
for (const sl of similarLessons) {
|
|
90
|
-
lines.push(`\nExisting lesson ref: ${sl.ref}`);
|
|
91
|
-
lines.push("```");
|
|
92
|
-
lines.push(sl.content.slice(0, 500));
|
|
93
|
-
lines.push("```");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
lines.push("");
|
|
97
|
-
lines.push("Proposed lesson content:");
|
|
98
|
-
lines.push("```");
|
|
99
|
-
lines.push(lessonContent.slice(0, 1000));
|
|
100
|
-
lines.push("```");
|
|
101
|
-
lines.push("");
|
|
102
|
-
lines.push('Return ONLY valid JSON, no prose: {"score": <average score 1-5 as float>, "reason": "<one sentence>"}');
|
|
103
|
-
return lines.join("\n");
|
|
104
|
-
}
|
|
105
|
-
function boundedDocument(content, maxChars = 6000) {
|
|
106
|
-
if (content.length <= maxChars)
|
|
107
|
-
return content;
|
|
108
|
-
const half = Math.floor((maxChars - 80) / 2);
|
|
109
|
-
return `${content.slice(0, half)}\n\n[... middle omitted for bounded judge context ...]\n\n${content.slice(-half)}`;
|
|
110
|
-
}
|
|
111
|
-
function buildChangedRegion(sourceContent, candidateContent) {
|
|
112
|
-
const source = sourceContent.split("\n");
|
|
113
|
-
const candidate = candidateContent.split("\n");
|
|
114
|
-
let prefix = 0;
|
|
115
|
-
while (prefix < source.length && prefix < candidate.length && source[prefix] === candidate[prefix])
|
|
116
|
-
prefix++;
|
|
117
|
-
let suffix = 0;
|
|
118
|
-
while (suffix < source.length - prefix &&
|
|
119
|
-
suffix < candidate.length - prefix &&
|
|
120
|
-
source[source.length - 1 - suffix] === candidate[candidate.length - 1 - suffix]) {
|
|
121
|
-
suffix++;
|
|
122
|
-
}
|
|
123
|
-
const removed = source.slice(prefix, source.length - suffix).join("\n");
|
|
124
|
-
const added = candidate.slice(prefix, candidate.length - suffix).join("\n");
|
|
125
|
-
return boundedDocument(`Removed or replaced:\n${removed || "(none)"}\n\nAdded or replacement:\n${added || "(none)"}`);
|
|
126
|
-
}
|
|
127
|
-
/** Build quality criteria for revising an existing asset in place. */
|
|
128
|
-
export function buildReflectJudgePrompt(candidateContent, sourceContent, feedback) {
|
|
129
|
-
return [
|
|
130
|
-
"You are evaluating a proposed revision to an existing akm asset.",
|
|
131
|
-
"",
|
|
132
|
-
"Score this revision on each criterion from 1 (poor) to 5 (excellent):",
|
|
133
|
-
"1. FEEDBACK ALIGNMENT: Does the revision address the supplied feedback or improve retrieval and clarity?",
|
|
134
|
-
"2. PRESERVATION: Does it retain the source's concrete facts, code, commands, examples, and structure without truncation?",
|
|
135
|
-
"3. QUALITY: Is the revision coherent, actionable, complete, and free of unsupported claims?",
|
|
136
|
-
"",
|
|
137
|
-
"Overlap with the source is expected and must not lower the score by itself; this is an in-place revision, not a new lesson.",
|
|
138
|
-
"",
|
|
139
|
-
"Feedback:",
|
|
140
|
-
"```",
|
|
141
|
-
(feedback.length > 0 ? feedback.join("\n") : "No explicit feedback supplied.").slice(0, 1000),
|
|
142
|
-
"```",
|
|
143
|
-
"",
|
|
144
|
-
"Source asset content:",
|
|
145
|
-
"```",
|
|
146
|
-
boundedDocument(sourceContent),
|
|
147
|
-
"```",
|
|
148
|
-
"",
|
|
149
|
-
"Proposed revision:",
|
|
150
|
-
"```",
|
|
151
|
-
boundedDocument(candidateContent),
|
|
152
|
-
"```",
|
|
153
|
-
"",
|
|
154
|
-
"Changed region:",
|
|
155
|
-
"```",
|
|
156
|
-
buildChangedRegion(sourceContent, candidateContent),
|
|
157
|
-
"```",
|
|
158
|
-
"",
|
|
159
|
-
'Return ONLY valid JSON, no prose: {"score": <average score 1-5 as float>, "reason": "<one sentence>"}',
|
|
160
|
-
].join("\n");
|
|
161
|
-
}
|
|
162
|
-
async function runQualityJudge(feature, config, prompt, chat, options = {}) {
|
|
163
|
-
const resolvedDefault = !options.runnerSelectionFrozen && !options.llmRunner
|
|
164
|
-
? resolveImproveLlmExecution({ config, processName: `${feature}-judge` })
|
|
165
|
-
: null;
|
|
166
|
-
if (resolvedDefault)
|
|
167
|
-
options.onNotices?.(resolvedDefault.notices);
|
|
168
|
-
const runner = options.llmRunner ?? resolvedDefault?.runner;
|
|
169
|
-
if (!runner) {
|
|
170
|
-
return { pass: false, score: -1, reason: "no LLM configured — cannot judge, failing closed" };
|
|
171
|
-
}
|
|
172
|
-
try {
|
|
173
|
-
// UNGATED at the seam (no akmConfig): the quality gates' enablement is
|
|
174
|
-
// resolved by the caller before this function runs, and a transport throw
|
|
175
|
-
// propagates into the fail-closed catch below. `feature` labels the call.
|
|
176
|
-
const raw = await callStructured({
|
|
177
|
-
feature,
|
|
178
|
-
runner,
|
|
179
|
-
...(options.lease ? { lease: options.lease } : {}),
|
|
180
|
-
messages: [
|
|
181
|
-
{ role: "system", content: "Return only valid JSON. No prose." },
|
|
182
|
-
{ role: "user", content: prompt },
|
|
183
|
-
],
|
|
184
|
-
request: {
|
|
185
|
-
enableThinking: false,
|
|
186
|
-
...(Object.hasOwn(options, "timeoutMs") ? { timeoutMs: options.timeoutMs } : {}),
|
|
187
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
188
|
-
...(chat ? { chat } : {}),
|
|
189
|
-
},
|
|
190
|
-
parse: (rawResponse) => rawResponse ?? "",
|
|
191
|
-
// Unreachable on the ungated path (errors propagate); fail closed anyway.
|
|
192
|
-
onError: () => "",
|
|
193
|
-
fallback: "",
|
|
194
|
-
...(options.onNotices ? { onNotices: options.onNotices } : {}),
|
|
195
|
-
});
|
|
196
|
-
const parsed = parseEmbeddedJsonResponse(raw);
|
|
197
|
-
if (!parsed ||
|
|
198
|
-
typeof parsed.score !== "number" ||
|
|
199
|
-
!Number.isFinite(parsed.score) ||
|
|
200
|
-
parsed.score < 1 ||
|
|
201
|
-
parsed.score > 5 ||
|
|
202
|
-
typeof parsed.reason !== "string") {
|
|
203
|
-
return { pass: false, score: -1, reason: "judge parse failed — routed to review", reviewNeeded: true };
|
|
204
|
-
}
|
|
205
|
-
// D-5 / #388: Three-band system (MT-Bench arXiv:2306.05685 — ~±0.5 judge variance).
|
|
206
|
-
// >= 3.5: auto-queue as pending (pass: true)
|
|
207
|
-
// 2.5–3.5: review-needed band — uncertain, escalate to human (reviewNeeded: true)
|
|
208
|
-
// < 2.5: auto-reject (pass: false)
|
|
209
|
-
const score = parsed.score;
|
|
210
|
-
const reason = parsed.reason ?? "";
|
|
211
|
-
if (score >= 3.5)
|
|
212
|
-
return { pass: true, score, reason };
|
|
213
|
-
if (score >= 2.5)
|
|
214
|
-
return { pass: false, score, reason, reviewNeeded: true };
|
|
215
|
-
return { pass: false, score, reason };
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
// Invalid symbolic credentials are configuration failures, not a negative
|
|
219
|
-
// content verdict. Provider/runtime failures retain the fail-closed result.
|
|
220
|
-
if (error instanceof ConfigError)
|
|
221
|
-
throw error;
|
|
222
|
-
return { pass: false, score: -1, reason: "judge timeout/error — routed to review", reviewNeeded: true };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Run the LLM-as-judge quality gate on a proposal's content.
|
|
227
|
-
*
|
|
228
|
-
* Exported so reflect.ts can apply the same gate to reflect proposals (R-5 / #374).
|
|
229
|
-
* The selected strategy's distill/reflect quality-gate setting is resolved by
|
|
230
|
-
* the caller before this function runs.
|
|
231
|
-
*
|
|
232
|
-
* Fail-CLOSED (07 P0-2): returns `pass: false` (score -1) on timeout, parse
|
|
233
|
-
* failure, or missing LLM. Minted content that cannot be judged is rejected,
|
|
234
|
-
* not passed through — an unverifiable judge must never wave content into the
|
|
235
|
-
* stash. The rejection is `quality_rejected`, not `review_needed`.
|
|
236
|
-
*/
|
|
237
|
-
export async function runLessonQualityJudge(config, lessonContent, sourceContent, chat, options = {}) {
|
|
238
|
-
return runQualityJudge("lesson_quality_gate", config, buildJudgePrompt(lessonContent, sourceContent, options.similarLessons), chat, options);
|
|
239
|
-
}
|
|
240
|
-
/** Judge an in-place reflect revision without applying new-lesson novelty criteria. */
|
|
241
|
-
export async function runReflectQualityJudge(config, candidateContent, sourceContent, feedback, chat, options = {}) {
|
|
242
|
-
return runQualityJudge("proposal_quality_gate", config, buildReflectJudgePrompt(candidateContent, sourceContent, feedback), chat, options);
|
|
243
|
-
}
|
|
244
|
-
// ── Quality-rejection helper ─────────────────────────────────────────────────
|
|
245
|
-
/**
|
|
246
|
-
* Write a rejected lesson to `$STATE/improve/distill-rejected/<stash>/`
|
|
247
|
-
* (itlackey/akm#890), append a `distill_invoked` quality-rejected event, and
|
|
248
|
-
* return the `quality_rejected` envelope.
|
|
249
|
-
*
|
|
250
|
-
* @param stash - Root stash directory.
|
|
251
|
-
* @param inputRef - The original input ref (for the event).
|
|
252
|
-
* @param proposalRef - The proposed lesson/knowledge ref.
|
|
253
|
-
* @param content - The raw content that failed the quality gate.
|
|
254
|
-
* @param score - Quality score from the judge.
|
|
255
|
-
* @param reason - Human-readable rejection reason.
|
|
256
|
-
* @param extraMeta - Optional additional metadata for the event.
|
|
257
|
-
* @param eventsCtx - Events context so the emit takes appendEvent's fast path (R25).
|
|
258
|
-
*/
|
|
259
|
-
export function writeQualityRejection(stash, inputRef, proposalRef, content, score, reason, extraMeta = {}, eligibilitySource, eventsCtx) {
|
|
260
|
-
// D-5 / #388: reviewNeeded flag selects "review_needed" vs "quality_rejected" outcome.
|
|
261
|
-
const outcome = extraMeta.reviewNeeded ? "review_needed" : "quality_rejected";
|
|
262
|
-
const rejectDir = getDistillRejectedDir(stash);
|
|
263
|
-
fs.mkdirSync(rejectDir, { recursive: true });
|
|
264
|
-
const ts = timestampForFilename();
|
|
265
|
-
const rejectPath = path.join(rejectDir, `${ts}-${proposalRef.replace(/[:/\\]/g, "-")}.md`);
|
|
266
|
-
fs.writeFileSync(rejectPath, `---\nscore: ${score}\nreason: ${reason}\noutcome: ${outcome}\n---\n\n${content}`, "utf8");
|
|
267
|
-
// #652 / itlackey/akm#890: journal it even though it now lands under
|
|
268
|
-
// `$STATE`, outside the stash's git repo — `result.writtenPaths` reports
|
|
269
|
-
// every path a run touched, in or out of the stash (describeRunWrittenPaths
|
|
270
|
-
// in improve.ts falls back to the absolute path for anything outside the
|
|
271
|
-
// stash root), and the auto-sync commit's own containment check
|
|
272
|
-
// (resolveSyncPathSet's `relativeWrittenPath`) already drops anything
|
|
273
|
-
// outside `repoDir` from what gets staged — recording it here cannot cause
|
|
274
|
-
// it to be committed.
|
|
275
|
-
recordWrittenPath(rejectPath);
|
|
276
|
-
appendEvent({
|
|
277
|
-
eventType: "distill_invoked",
|
|
278
|
-
ref: inputRef,
|
|
279
|
-
metadata: {
|
|
280
|
-
outcome,
|
|
281
|
-
proposalRef,
|
|
282
|
-
score,
|
|
283
|
-
reason,
|
|
284
|
-
...extraMeta,
|
|
285
|
-
// Attribution tagging: stamp the eligibility lane so distill_invoked can be
|
|
286
|
-
// sliced by lane downstream. See EligibilitySource.
|
|
287
|
-
...(eligibilitySource ? { eligibilitySource } : {}),
|
|
288
|
-
},
|
|
289
|
-
}, eventsCtx);
|
|
290
|
-
return {
|
|
291
|
-
schemaVersion: 1,
|
|
292
|
-
ok: true,
|
|
293
|
-
outcome,
|
|
294
|
-
inputRef,
|
|
295
|
-
proposalRef,
|
|
296
|
-
score,
|
|
297
|
-
reason,
|
|
298
|
-
...extraMeta,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* G4 — content-score a distilled OUTPUT (lesson/knowledge proposal body) and
|
|
303
|
-
* persist it to state.db :: asset_salience with `encoding_source: "content"`.
|
|
304
|
-
*
|
|
305
|
-
* Lessons are refused as distill INPUTS (`DISTILL_REFUSED_INPUT_TYPES`), so
|
|
306
|
-
* this creation-time write is their only chance to earn a real content-derived
|
|
307
|
-
* encoding score instead of sitting on the type-weight stub forever. Best-effort:
|
|
308
|
-
* never blocks or fails the proposal flow.
|
|
309
|
-
*/
|
|
310
|
-
export function persistOutputEncodingSalience(ref, body, existingRefVocabulary,
|
|
311
|
-
// Operator opt-out (improve.salience.outcomeWeightEnabled: false) must apply
|
|
312
|
-
// here too, or distill-written rank_score rows would use WS-2 weights while
|
|
313
|
-
// preparation uses parity weights — inconsistent salience semantics.
|
|
314
|
-
outcomeWeightEnabled) {
|
|
315
|
-
try {
|
|
316
|
-
const parsedRef = parseRefInput(ref);
|
|
317
|
-
const salienceResult = scoreEncodingSalience({
|
|
318
|
-
body,
|
|
319
|
-
type: parsedRef.type,
|
|
320
|
-
existingRefVocabulary,
|
|
321
|
-
revisionCount: 0, // a freshly distilled output IS a first encounter
|
|
322
|
-
});
|
|
323
|
-
withStateDb((stateDb) => {
|
|
324
|
-
const vector = computeSalience({
|
|
325
|
-
ref,
|
|
326
|
-
type: parsedRef.type,
|
|
327
|
-
retrievalFreq: 0,
|
|
328
|
-
encodingSalience: salienceResult.score,
|
|
329
|
-
outcomeWeightEnabled,
|
|
330
|
-
});
|
|
331
|
-
upsertAssetSalience(stateDb, ref, vector);
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
// Best-effort — scoring must never block proposal creation.
|
|
336
|
-
}
|
|
337
|
-
}
|
|
@@ -1,52 +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
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { writeFileAtomic } from "../../core/common.js";
|
|
7
|
-
import { getEvalCasesDir } from "../../core/paths.js";
|
|
8
|
-
import { recordWrittenPath } from "../../core/write-provenance.js";
|
|
9
|
-
export function writeEvalCase(stashDir, evalCase) {
|
|
10
|
-
const evalDir = getEvalCasesDir(stashDir);
|
|
11
|
-
fs.mkdirSync(evalDir, { recursive: true });
|
|
12
|
-
const fileName = `${evalCase.slug}.md`;
|
|
13
|
-
const filePath = path.join(evalDir, fileName);
|
|
14
|
-
const content = `---
|
|
15
|
-
ref: ${evalCase.ref}
|
|
16
|
-
failureReason: ${evalCase.failureReason}
|
|
17
|
-
assetType: ${evalCase.assetType}
|
|
18
|
-
rejectedAt: ${evalCase.rejectedAt}
|
|
19
|
-
source: ${evalCase.source}
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Eval Case: ${evalCase.ref}
|
|
23
|
-
|
|
24
|
-
**Failure reason:** ${evalCase.failureReason}
|
|
25
|
-
**Source:** ${evalCase.source}
|
|
26
|
-
**Asset type:** ${evalCase.assetType}
|
|
27
|
-
|
|
28
|
-
This case was automatically captured when a distillation or proposal was rejected.
|
|
29
|
-
Use it as a regression test: future improve runs on this ref should not produce
|
|
30
|
-
output that would be rejected for the same reason.
|
|
31
|
-
`;
|
|
32
|
-
writeFileAtomic(filePath, content);
|
|
33
|
-
// itlackey/akm#890: journal it even though it now lands under `$STATE`,
|
|
34
|
-
// outside the stash's git repo — `result.writtenPaths` still reports it
|
|
35
|
-
// (as an absolute path; see describeRunWrittenPaths in improve.ts), and
|
|
36
|
-
// the auto-sync commit's own containment check already excludes anything
|
|
37
|
-
// outside the stash from what gets staged, so recording it here cannot
|
|
38
|
-
// cause it to be committed.
|
|
39
|
-
recordWrittenPath(filePath);
|
|
40
|
-
return filePath;
|
|
41
|
-
}
|
|
42
|
-
export function countEvalCases(stashDir) {
|
|
43
|
-
const evalDir = getEvalCasesDir(stashDir);
|
|
44
|
-
if (!fs.existsSync(evalDir))
|
|
45
|
-
return 0;
|
|
46
|
-
try {
|
|
47
|
-
return fs.readdirSync(evalDir).filter((f) => f.endsWith(".md")).length;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,291 +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
|
-
* LLM-based contradiction-detection pass for derived memories (M-1 / #367).
|
|
6
|
-
*
|
|
7
|
-
* Runs BEFORE `analyzeMemoryCleanup` to populate `contradictedBy` frontmatter
|
|
8
|
-
* edges so the existing `resolveFamilyContradictions` SCC resolver has real
|
|
9
|
-
* input to work on. Without this pass the SCC resolver operates on a nearly
|
|
10
|
-
* empty edge graph because no automated subsystem was previously generating
|
|
11
|
-
* contradiction edges — the elegant Tarjan implementation in memory-improve.ts
|
|
12
|
-
* had no input.
|
|
13
|
-
*
|
|
14
|
-
* # Algorithm
|
|
15
|
-
*
|
|
16
|
-
* 1. Collect all derived memories grouped by `parentRef` family.
|
|
17
|
-
* 2. For each family, enumerate candidate pairs.
|
|
18
|
-
* 3. For each pair, call the LLM to judge whether the two memories are in
|
|
19
|
-
* direct factual conflict.
|
|
20
|
-
* 4. For confirmed contradictions, append a `contradictedBy` edge to the
|
|
21
|
-
* losing memory's frontmatter via `writeContradictEdge`
|
|
22
|
-
* (`./memory-belief.ts`).
|
|
23
|
-
*
|
|
24
|
-
* That last step used to call a private near-copy of `writeContradictEdge`
|
|
25
|
-
* living in this file. The copy had drifted (#885): it read `contradictedBy`
|
|
26
|
-
* with `Array.isArray` only, so a SCALAR edge — live data the indexer accepts
|
|
27
|
-
* and lint never flags — read as "no edges" and was overwritten out of
|
|
28
|
-
* existence; and it set `beliefState: "contradicted"` unconditionally,
|
|
29
|
-
* promoting an `archived` memory back up (archived ranks BELOW contradicted).
|
|
30
|
-
* Both behaviors had tests, but the tests exercised the shared primitive,
|
|
31
|
-
* which nothing called — so they guarded dead code while the live path
|
|
32
|
-
* carried the bugs.
|
|
33
|
-
*
|
|
34
|
-
* # LLM Feature Gate
|
|
35
|
-
*
|
|
36
|
-
* The pass is gated by the selected strategy's consolidate contradiction setting.
|
|
37
|
-
* When the gate is disabled or no LLM is configured,
|
|
38
|
-
* the pass is a no-op and `analyzeMemoryCleanup` proceeds with only manually
|
|
39
|
-
* annotated edges.
|
|
40
|
-
*
|
|
41
|
-
* # References
|
|
42
|
-
*
|
|
43
|
-
* - Zep / Graphiti (arXiv:2501.13956): writes contradiction edges at detection time.
|
|
44
|
-
* - ATMS (de Kleer 1986): assumption-based truth maintenance via edge propagation.
|
|
45
|
-
* - mem0 contradiction probe (arXiv:2504.19413): pairwise LLM-judge pattern.
|
|
46
|
-
*/
|
|
47
|
-
import fs from "node:fs";
|
|
48
|
-
import path from "node:path";
|
|
49
|
-
import contradictionJudgeTemplate from "../../../assets/prompts/contradiction-judge.md" with { type: "text" };
|
|
50
|
-
import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
51
|
-
import { parseEmbeddedJsonResponse } from "../../../core/parse.js";
|
|
52
|
-
import { disposeLoweredExecutionDispatchLease } from "../../../integrations/agent/execution-lowering.js";
|
|
53
|
-
import { callStructured, preflightStructuredLlmRunner } from "../../../llm/structured-call.js";
|
|
54
|
-
import { resolveImproveLlmExecution } from "../execution.js";
|
|
55
|
-
import { isDerivedMemory, memoryIdentityRef, resolveParentRef } from "./derived-ref.js";
|
|
56
|
-
import { writeContradictEdge } from "./memory-belief.js";
|
|
57
|
-
// ── Constants ────────────────────────────────────────────────────────────────
|
|
58
|
-
/**
|
|
59
|
-
* Minimum confidence required to write a contradiction edge. Below this
|
|
60
|
-
* threshold the LLM may be flagging topic-overlap rather than genuine logical
|
|
61
|
-
* exclusivity (investigation 2026-06-18).
|
|
62
|
-
*/
|
|
63
|
-
const CONTRADICT_CONFIDENCE_THRESHOLD = 0.92;
|
|
64
|
-
/**
|
|
65
|
-
* Truncation limit for memory body content sent to the LLM judge.
|
|
66
|
-
* Keeps prompts compact while preserving the key factual claims.
|
|
67
|
-
*/
|
|
68
|
-
const BODY_TRUNCATION = 800;
|
|
69
|
-
// ── Prompt builder ────────────────────────────────────────────────────────────
|
|
70
|
-
function buildContradictionJudgePrompt(a, b) {
|
|
71
|
-
return contradictionJudgeTemplate
|
|
72
|
-
.replace("{{A_REF}}", a.ref)
|
|
73
|
-
.replace("{{A_DESCRIPTION}}", a.description || "(none)")
|
|
74
|
-
.replace("{{A_BODY}}", a.body.slice(0, BODY_TRUNCATION))
|
|
75
|
-
.replace("{{B_REF}}", b.ref)
|
|
76
|
-
.replace("{{B_DESCRIPTION}}", b.description || "(none)")
|
|
77
|
-
.replace("{{B_BODY}}", b.body.slice(0, BODY_TRUNCATION));
|
|
78
|
-
}
|
|
79
|
-
// ── Filesystem helpers ────────────────────────────────────────────────────────
|
|
80
|
-
function* walkMarkdownFilesLocal(root) {
|
|
81
|
-
if (!fs.existsSync(root))
|
|
82
|
-
return;
|
|
83
|
-
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
84
|
-
const full = path.join(root, entry.name);
|
|
85
|
-
if (entry.isDirectory())
|
|
86
|
-
yield* walkMarkdownFilesLocal(full);
|
|
87
|
-
else if (entry.isFile() && entry.name.endsWith(".md"))
|
|
88
|
-
yield full;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Build the derived memory's own belief-edge IDENTITY ref from its file path.
|
|
92
|
-
// Emits through the shared {@link memoryIdentityRef} so this — the former THIRD
|
|
93
|
-
// hand-rolled copy of the identity-channel spelling — no longer diverges from
|
|
94
|
-
// memory-improve's `refArray` (ref-grammar decision D-R3 identity-channel
|
|
95
|
-
// exception, documented at `memoryIdentityRef`).
|
|
96
|
-
function toMemoryRef(memoriesDir, filePath) {
|
|
97
|
-
const rel = path.relative(memoriesDir, filePath);
|
|
98
|
-
if (!rel || rel.startsWith(".."))
|
|
99
|
-
return undefined;
|
|
100
|
-
const name = rel.replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
101
|
-
return memoryIdentityRef(name);
|
|
102
|
-
}
|
|
103
|
-
// ── Edge writing ─────────────────────────────────────────────────────────────
|
|
104
|
-
/**
|
|
105
|
-
* Deterministically pick, for a confirmed-contradiction pair, the LOSER memory
|
|
106
|
-
* that receives the single directed `contradictedBy` edge (SCC-resolved to
|
|
107
|
-
* `contradicted`) and the WINNER ref that survives as the current belief.
|
|
108
|
-
*
|
|
109
|
-
* A SINGLE directed edge is essential. Writing mutual A↔B edges forms a 2-cycle
|
|
110
|
-
* that {@link resolveFamilyContradictions} collapses into one strongly-connected
|
|
111
|
-
* SINK component and refreshes BOTH members back to active — erasing the
|
|
112
|
-
* contradiction on every run (the self-erasing bug this fix removes).
|
|
113
|
-
*
|
|
114
|
-
* Direction = lexicographic ref order: the ref that sorts LATER is the loser.
|
|
115
|
-
* This is a **total order** over the family's (distinct) refs, so the induced
|
|
116
|
-
* edges are always acyclic — a family of any size resolves to a DAG with a
|
|
117
|
-
* single sink, never a cycle that the resolver would refresh back to active.
|
|
118
|
-
* It is also immutable across runs (unlike file mtime, which the resolver
|
|
119
|
-
* bumps when it rewrites loser files), so detection is idempotent. Ref order
|
|
120
|
-
* carries no recency meaning — no derived-memory writer sets a `createdAt`/
|
|
121
|
-
* timestamp today — but the mechanism only needs a stable, acyclic direction;
|
|
122
|
-
* eliminating worst-case self-erasure, not ranking by recency, is the goal.
|
|
123
|
-
*/
|
|
124
|
-
function pickContradictionLoser(a, b) {
|
|
125
|
-
return a.ref < b.ref ? { loser: b, winnerRef: a.ref } : { loser: a, winnerRef: b.ref };
|
|
126
|
-
}
|
|
127
|
-
// ── Main entry point ──────────────────────────────────────────────────────────
|
|
128
|
-
/**
|
|
129
|
-
* Run the LLM-based contradiction-detection pass on derived memories in
|
|
130
|
-
* `<stashDir>/memories/`. Writes `contradictedBy` frontmatter edges for
|
|
131
|
-
* confirmed contradiction pairs so the subsequent `resolveFamilyContradictions`
|
|
132
|
-
* SCC pass has edges to work on.
|
|
133
|
-
*
|
|
134
|
-
* @param stashDir - Root stash directory.
|
|
135
|
-
* @param config - Loaded AKM config (used to access LLM settings).
|
|
136
|
-
* @param chat - Optional test-only chat seam.
|
|
137
|
-
*/
|
|
138
|
-
export async function detectAndWriteContradictions(stashDir, config, chat, strategy, resolvedRunner) {
|
|
139
|
-
const result = {
|
|
140
|
-
familiesExamined: 0,
|
|
141
|
-
pairsChecked: 0,
|
|
142
|
-
edgesWritten: 0,
|
|
143
|
-
warnings: [],
|
|
144
|
-
};
|
|
145
|
-
if (!(strategy?.processes?.consolidate?.contradictionDetection?.enabled ?? false))
|
|
146
|
-
return result;
|
|
147
|
-
const noticesByKey = new Map();
|
|
148
|
-
const resolvedExecution = resolvedRunner === undefined
|
|
149
|
-
? resolveImproveLlmExecution({
|
|
150
|
-
config,
|
|
151
|
-
profile: strategy,
|
|
152
|
-
process: strategy?.processes?.consolidate,
|
|
153
|
-
processName: "memory-contradiction-detection",
|
|
154
|
-
})
|
|
155
|
-
: null;
|
|
156
|
-
for (const notice of resolvedExecution?.notices ?? [])
|
|
157
|
-
noticesByKey.set(JSON.stringify(notice), notice);
|
|
158
|
-
const contradictionRunner = resolvedRunner === null ? undefined : (resolvedRunner ?? resolvedExecution?.runner);
|
|
159
|
-
if (!contradictionRunner)
|
|
160
|
-
return result;
|
|
161
|
-
// Collect derived memories grouped by parent.
|
|
162
|
-
const memoriesDir = path.join(stashDir, "memories");
|
|
163
|
-
const byParent = new Map();
|
|
164
|
-
for (const filePath of walkMarkdownFilesLocal(memoriesDir)) {
|
|
165
|
-
let raw;
|
|
166
|
-
try {
|
|
167
|
-
raw = fs.readFileSync(filePath, "utf8");
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const parsed = parseFrontmatter(raw);
|
|
173
|
-
const ref = toMemoryRef(memoriesDir, filePath);
|
|
174
|
-
if (!ref)
|
|
175
|
-
continue;
|
|
176
|
-
// Key the shared derived-ref helpers on the memory NAME (stash-relative, no
|
|
177
|
-
// extension) — the same key the consumer uses — so producer and consumer
|
|
178
|
-
// resolve the identical parent (R12). This intentionally widens the producer
|
|
179
|
-
// to honour `derivedFrom` and normalised `source:` values it previously
|
|
180
|
-
// dropped (pinned by derived-ref.test.ts).
|
|
181
|
-
const name = ref.slice("memory:".length);
|
|
182
|
-
if (!isDerivedMemory(name, parsed.data))
|
|
183
|
-
continue;
|
|
184
|
-
const parentRef = resolveParentRef(name, parsed.data);
|
|
185
|
-
if (!parentRef)
|
|
186
|
-
continue;
|
|
187
|
-
const entry = {
|
|
188
|
-
filePath,
|
|
189
|
-
ref,
|
|
190
|
-
parentRef,
|
|
191
|
-
body: parsed.content.trim(),
|
|
192
|
-
description: typeof parsed.data.description === "string" ? parsed.data.description : "",
|
|
193
|
-
};
|
|
194
|
-
const family = byParent.get(parentRef) ?? [];
|
|
195
|
-
family.push(entry);
|
|
196
|
-
byParent.set(parentRef, family);
|
|
197
|
-
}
|
|
198
|
-
const candidatePairs = [];
|
|
199
|
-
for (const [, family] of byParent) {
|
|
200
|
-
if (family.length < 2)
|
|
201
|
-
continue;
|
|
202
|
-
result.familiesExamined++;
|
|
203
|
-
for (let i = 0; i < family.length - 1; i++) {
|
|
204
|
-
for (let j = i + 1; j < family.length; j++) {
|
|
205
|
-
const a = family[i];
|
|
206
|
-
const b = family[j];
|
|
207
|
-
if (!a || !b)
|
|
208
|
-
continue;
|
|
209
|
-
// Resolve the directed edge up front (independent of the judge — it is
|
|
210
|
-
// decided by lexicographic ref order). Skip when that single loser→winner
|
|
211
|
-
// edge already exists (no new information; avoids re-judging resolved
|
|
212
|
-
// pairs across runs).
|
|
213
|
-
//
|
|
214
|
-
const aParsed = parseFrontmatter(fs.readFileSync(a.filePath, "utf8"));
|
|
215
|
-
const bParsed = parseFrontmatter(fs.readFileSync(b.filePath, "utf8"));
|
|
216
|
-
const { loser, winnerRef } = pickContradictionLoser(a, b);
|
|
217
|
-
const loserData = loser === a ? aParsed.data : bParsed.data;
|
|
218
|
-
const loserCB = Array.isArray(loserData.contradictedBy) ? loserData.contradictedBy : [];
|
|
219
|
-
if (loserCB.includes(winnerRef))
|
|
220
|
-
continue;
|
|
221
|
-
candidatePairs.push({ a, b, loser, winnerRef });
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (candidatePairs.length === 0) {
|
|
226
|
-
const notices = Object.freeze([...noticesByKey.values()]);
|
|
227
|
-
return notices.length > 0 ? { ...result, notices } : result;
|
|
228
|
-
}
|
|
229
|
-
const dispatchLease = await preflightStructuredLlmRunner(contradictionRunner);
|
|
230
|
-
try {
|
|
231
|
-
for (const { a, b, loser, winnerRef } of candidatePairs) {
|
|
232
|
-
const prompt = buildContradictionJudgePrompt(a, b);
|
|
233
|
-
const judgeResult = await callStructured({
|
|
234
|
-
feature: "memory_contradiction_detection",
|
|
235
|
-
akmConfig: config,
|
|
236
|
-
// Resolver-less key: the strategy decision IS the gate (default-off).
|
|
237
|
-
enabled: true,
|
|
238
|
-
runner: contradictionRunner,
|
|
239
|
-
lease: dispatchLease,
|
|
240
|
-
messages: [
|
|
241
|
-
{ role: "system", content: "Return only valid JSON. No prose." },
|
|
242
|
-
{ role: "user", content: prompt },
|
|
243
|
-
],
|
|
244
|
-
...(chat ? { request: { chat } } : {}),
|
|
245
|
-
onNotices: (notices) => {
|
|
246
|
-
for (const notice of notices)
|
|
247
|
-
noticesByKey.set(JSON.stringify(notice), notice);
|
|
248
|
-
},
|
|
249
|
-
parse: (raw) => raw ?? null,
|
|
250
|
-
// A transport throw used to escape the gated fn into the gate's
|
|
251
|
-
// catch and take the null fallback ("skip"); onError reproduces it.
|
|
252
|
-
onError: () => null,
|
|
253
|
-
fallback: null, // null means "skip" — gate disabled or LLM call failed.
|
|
254
|
-
});
|
|
255
|
-
result.pairsChecked++;
|
|
256
|
-
if (!judgeResult)
|
|
257
|
-
continue; // Feature gate disabled or LLM call failed.
|
|
258
|
-
let parsed = null;
|
|
259
|
-
try {
|
|
260
|
-
parsed = parseEmbeddedJsonResponse(judgeResult);
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
result.warnings.push(`Could not parse contradiction judge response for pair ${a.ref} / ${b.ref}`);
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
if (!parsed?.contradicts)
|
|
267
|
-
continue;
|
|
268
|
-
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : 0;
|
|
269
|
-
if (confidence < CONTRADICT_CONFIDENCE_THRESHOLD) {
|
|
270
|
-
result.warnings.push(`Pair ${a.ref} / ${b.ref}: confidence ${confidence.toFixed(2)} below ${CONTRADICT_CONFIDENCE_THRESHOLD} threshold — skipped.`);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
// Write a SINGLE directed contradiction edge: the losing (older) memory
|
|
274
|
-
// gets `contradictedBy` pointing to the winner. A mutual A↔B pair forms
|
|
275
|
-
// a 2-cycle that the SCC resolver refreshes back to active, erasing the
|
|
276
|
-
// contradiction every run (see pickContradictionLoser).
|
|
277
|
-
try {
|
|
278
|
-
const wrote = writeContradictEdge(loser.filePath, winnerRef);
|
|
279
|
-
result.edgesWritten += wrote ? 1 : 0;
|
|
280
|
-
}
|
|
281
|
-
catch (err) {
|
|
282
|
-
result.warnings.push(`Failed to write contradiction edge ${loser.ref} -> ${winnerRef}: ${err instanceof Error ? err.message : String(err)}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
finally {
|
|
287
|
-
disposeLoweredExecutionDispatchLease(dispatchLease);
|
|
288
|
-
}
|
|
289
|
-
const notices = Object.freeze([...noticesByKey.values()]);
|
|
290
|
-
return notices.length > 0 ? { ...result, notices } : result;
|
|
291
|
-
}
|