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,61 +1,22 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
/**
|
|
5
|
-
* The single, keyed-on-ref implementation of "is this a derived memory?" and
|
|
6
|
-
* "which parent does it derive from?" (R12).
|
|
7
|
-
*
|
|
8
|
-
* Two divergent copies previously lived side by side — the CONSUMER
|
|
9
|
-
* (`memory-improve.ts`, keyed on the memory name) and the PRODUCER
|
|
10
|
-
* (`memory-contradiction-detect.ts`, keyed on the file path). The producer's
|
|
11
|
-
* copy was strictly narrower: it ignored `derivedFrom` entirely and matched
|
|
12
|
-
* `source:` through a separate parser. That let the producer and consumer
|
|
13
|
-
* disagree on a memory's parent — the exact defect plan §6 calls out
|
|
14
|
-
* ("producer/consumer cannot disagree").
|
|
15
|
-
*
|
|
16
|
-
* Both sides now share this one impl, keyed on the memory NAME (the stash-
|
|
17
|
-
* relative path without the `.md` extension, e.g. `nested/foo.derived`). The
|
|
18
|
-
* producer converts its file path to a name via `toMemoryRef` before calling
|
|
19
|
-
* in. Adopting it on the producer side is an INTENTIONAL widening, pinned by
|
|
20
|
-
* `tests/commands/improve/derived-ref.test.ts`:
|
|
21
|
-
* - `derivedFrom`-keyed families now resolve a parent (and so participate in
|
|
22
|
-
* contradiction detection); and
|
|
23
|
-
* - `source:` is parsed through `parseRefInput` so current bundle-qualified
|
|
24
|
-
* refs resolve consistently.
|
|
25
|
-
*
|
|
26
|
-
* Resolution order (source → derivedFrom → `.derived` suffix) matches the
|
|
27
|
-
* consumer's prior behaviour exactly, so the consumer side is a pure move.
|
|
28
|
-
*/
|
|
4
|
+
/** The one implementation of "is this a derived memory, and what is its parent?" (R12), keyed on the memory name. */
|
|
29
5
|
import { conceptIdFromTypeName, parseRefInput } from "../../../core/asset/resolve-ref.js";
|
|
30
6
|
import { asNonEmptyString } from "../../../core/common.js";
|
|
31
7
|
import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
|
|
32
8
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* - `memory:<name>` — the internal identity spelling, and what belief edges
|
|
38
|
-
* written before 0.9.0 carry on disk;
|
|
39
|
-
* - `memories/<name>` / `<bundle>//memories/<name>` — the conceptId grammar
|
|
40
|
-
* the current writers emit (`akm remember --supersedes` and `akm import
|
|
41
|
-
* --supersedes` go through `writeSupersededEdge` with the write result's
|
|
42
|
-
* `ref`, which is fully qualified).
|
|
43
|
-
*
|
|
44
|
-
* Accepting only the first spelling silently reduced every edge written by the
|
|
45
|
-
* current code path to nothing, so a superseded memory read back as active.
|
|
46
|
-
* Both normalize to the bare name; the caller re-mints the internal
|
|
47
|
-
* `memory:<name>` form via {@link memoryIdentityRef}, so there is still exactly
|
|
48
|
-
* one identity spelling downstream.
|
|
9
|
+
* A belief edge (`supersededBy` / `contradictedBy` / `currentBeliefRefs`) as a
|
|
10
|
+
* bare memory name. Both spellings occur: `memory:<name>` (pre-0.9 edges) and
|
|
11
|
+
* `[bundle//]memories/<name>` (what `--supersedes` writes today); accepting only
|
|
12
|
+
* the first once read every superseded memory back as active.
|
|
49
13
|
*/
|
|
50
14
|
export function parseMemoryName(value) {
|
|
51
15
|
if (!value)
|
|
52
16
|
return undefined;
|
|
53
17
|
const trimmed = value.trim();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const name = trimmed.slice(MEMORY_PREFIX.length);
|
|
57
|
-
return name.length > 0 ? name : undefined;
|
|
58
|
-
}
|
|
18
|
+
if (trimmed.startsWith("memory:"))
|
|
19
|
+
return trimmed.slice("memory:".length) || undefined;
|
|
59
20
|
try {
|
|
60
21
|
const parsed = parseRefInput(trimmed);
|
|
61
22
|
return parsed.type === "memory" && parsed.name.length > 0 ? parsed.name : undefined;
|
|
@@ -64,10 +25,7 @@ export function parseMemoryName(value) {
|
|
|
64
25
|
return undefined;
|
|
65
26
|
}
|
|
66
27
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Parse a current `source:` backref and return its canonical memory conceptId,
|
|
69
|
-
* or `undefined` when it is empty, invalid, or not a memory ref.
|
|
70
|
-
*/
|
|
28
|
+
/** A `source:` backref as a memory conceptId, or `undefined` when it is not a memory ref. */
|
|
71
29
|
export function parseMemoryRef(value) {
|
|
72
30
|
if (!value)
|
|
73
31
|
return undefined;
|
|
@@ -79,37 +37,15 @@ export function parseMemoryRef(value) {
|
|
|
79
37
|
return undefined;
|
|
80
38
|
}
|
|
81
39
|
}
|
|
82
|
-
/**
|
|
83
|
-
* Format a bare memory name into the belief-edge IDENTITY channel ref
|
|
84
|
-
* `memory:<name>` — the ONE implementation of that spelling.
|
|
85
|
-
*
|
|
86
|
-
* Belief-edge / identity
|
|
87
|
-
* channel (`contradictedBy` / `supersededBy` / `currentBeliefRefs`, and a derived
|
|
88
|
-
* memory's own `record.ref`) uses `memory:<name>` and is compared against a
|
|
89
|
-
* derived memory's identity ref. This channel is separate from `source:` asset
|
|
90
|
-
* refs, which use `memories/<name>`. Both `memory-improve.ts` (`refArray`) and
|
|
91
|
-
* `memory-contradiction-detect.ts` (`toMemoryRef`) emit through here so the
|
|
92
|
-
* identity spelling has one implementation.
|
|
93
|
-
*/
|
|
40
|
+
/** The belief-edge identity spelling, `memory:<name>` (separate from `source:` asset refs). */
|
|
94
41
|
export function memoryIdentityRef(name) {
|
|
95
42
|
return `memory:${name}`;
|
|
96
43
|
}
|
|
97
|
-
/**
|
|
98
|
-
* True when the named memory is a derived/inferred child — either it carries
|
|
99
|
-
* `inferred: true` in its frontmatter or its name ends with the structural
|
|
100
|
-
* `.derived` suffix.
|
|
101
|
-
*/
|
|
44
|
+
/** Inferred (`inferred: true`) or named with the `.derived` suffix. */
|
|
102
45
|
export function isDerivedMemory(name, frontmatter) {
|
|
103
46
|
return frontmatter.inferred === true || name.endsWith(DERIVED_SUFFIX);
|
|
104
47
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Resolve the parent (source) memory ref for a derived memory as a canonical
|
|
107
|
-
* `memories/<name>` conceptId, or `undefined` when none
|
|
108
|
-
* can be determined. Precedence:
|
|
109
|
-
* 1. `frontmatter.source` (normalised through {@link parseMemoryRef});
|
|
110
|
-
* 2. `frontmatter.derivedFrom` (a bare memory name → `memories/<name>`);
|
|
111
|
-
* 3. the `.derived` name suffix, stripped → `memories/<name>`.
|
|
112
|
-
*/
|
|
48
|
+
/** A derived memory's parent conceptId: from `source`, else `derivedFrom`, else the name minus `.derived`. */
|
|
113
49
|
export function resolveParentRef(name, frontmatter) {
|
|
114
50
|
const fromSource = parseMemoryRef(asNonEmptyString(frontmatter.source));
|
|
115
51
|
if (fromSource)
|
|
@@ -117,8 +53,7 @@ export function resolveParentRef(name, frontmatter) {
|
|
|
117
53
|
const derivedFrom = asNonEmptyString(frontmatter.derivedFrom);
|
|
118
54
|
if (derivedFrom)
|
|
119
55
|
return conceptIdFromTypeName("memory", derivedFrom);
|
|
120
|
-
if (name.endsWith(DERIVED_SUFFIX))
|
|
56
|
+
if (name.endsWith(DERIVED_SUFFIX))
|
|
121
57
|
return conceptIdFromTypeName("memory", name.slice(0, -DERIVED_SUFFIX.length));
|
|
122
|
-
}
|
|
123
58
|
return undefined;
|
|
124
59
|
}
|
|
@@ -2,141 +2,39 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* was previously encoded only in `memory-improve.ts`. `akmConsolidate` used a
|
|
15
|
-
* flat merge/delete/promote model with no belief states, causing the two engines
|
|
16
|
-
* to diverge. This module:
|
|
17
|
-
*
|
|
18
|
-
* 1. Re-exports the belief-state types from `memory-improve.ts` so callers
|
|
19
|
-
* can import from one canonical location.
|
|
20
|
-
* 2. Provides `writeContradictEdge` — a shared primitive that both engines
|
|
21
|
-
* use when an LLM (or heuristic) identifies a contradiction between two
|
|
22
|
-
* memories. This is the bridge between `akmConsolidate`'s LLM-detected
|
|
23
|
-
* contradictions and `resolveFamilyContradictions`' SCC resolver.
|
|
24
|
-
*
|
|
25
|
-
* # References
|
|
26
|
-
*
|
|
27
|
-
* - Zep / Graphiti (arXiv:2501.13956 §3) — unified belief-revision pipeline
|
|
28
|
-
* - MemOS (arXiv:2507.03724) — formal archive/merge/transition with shared state model
|
|
5
|
+
* Belief-state edge writer (#382): append a `supersededBy` edge to an asset's
|
|
6
|
+
* frontmatter and demote its `beliefState`, metadata only. Idempotent (a
|
|
7
|
+
* present edge AND demotion is a no-op; an edge without its demotion is
|
|
8
|
+
* repaired) and never weakens a stronger demotion — severity is
|
|
9
|
+
* superseded > contradicted > archived. (alpha.4 removed belief weights from
|
|
10
|
+
* ranking: search no longer reads `beliefState` at all, and `--belief
|
|
11
|
+
* current|historical` is the only reader, opt-in.)
|
|
12
|
+
* The SCC resolver in memory-improve.ts is a state-transition writer (it
|
|
13
|
+
* replaces and clears edges) and deliberately does not use this (#885).
|
|
29
14
|
*/
|
|
30
15
|
import { mutateFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
31
|
-
// ── Shared edge-list reader ───────────────────────────────────────────────────
|
|
32
16
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* Scalar edges are LIVE data: the indexer's `normalizeNonEmptyStringList`
|
|
37
|
-
* accepts them and lint deliberately never flags them. An Array.isArray-only
|
|
38
|
-
* read treats a scalar as "no existing edges" and silently destroys the edge
|
|
39
|
-
* on the next merge — mirror `mergeXrefsIntoContent`'s scalar promotion
|
|
40
|
-
* instead.
|
|
17
|
+
* An edge value as a list. A scalar string is live data (the indexer accepts
|
|
18
|
+
* it), so it is promoted rather than dropped on the next write.
|
|
41
19
|
*/
|
|
42
20
|
function readEdgeList(value) {
|
|
43
|
-
if (Array.isArray(value))
|
|
21
|
+
if (Array.isArray(value))
|
|
44
22
|
return value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
45
|
-
}
|
|
46
23
|
if (typeof value === "string" && value.trim())
|
|
47
24
|
return [value.trim()];
|
|
48
25
|
return [];
|
|
49
26
|
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Write `contradictedBy` and `beliefState: contradicted` edges to a memory
|
|
53
|
-
* file's frontmatter (C-3 / #382).
|
|
54
|
-
*
|
|
55
|
-
* The shared primitive for APPENDING one contradiction edge. Used by
|
|
56
|
-
* `memory-contradiction-detect.ts`'s automated contradiction pass.
|
|
57
|
-
*
|
|
58
|
-
* NOT used by `persistBeliefStateTransition` in `memory-improve.ts`, and that
|
|
59
|
-
* is deliberate (#885): the SCC resolver is a state-TRANSITION writer, not an
|
|
60
|
-
* edge appender. It replaces `contradictedBy` wholesale from recomputed
|
|
61
|
-
* `currentBeliefRefs`, deletes the key when a memory transitions away from
|
|
62
|
-
* `contradicted`, and moves a memory to any target state including back to
|
|
63
|
-
* active. An append-only primitive that never weakens a demotion cannot
|
|
64
|
-
* express those, and routing it through this would break the resolver's
|
|
65
|
-
* ability to clear an edge.
|
|
66
|
-
*
|
|
67
|
-
* Idempotent: if the `contradictedByRef` is already in `contradictedBy` AND
|
|
68
|
-
* the file already carries the demotion state, the file is not rewritten. The
|
|
69
|
-
* guard is state-aware like its sibling {@link writeSupersededEdge}: an edge
|
|
70
|
-
* present WITHOUT the demotion (e.g. a hand-written `contradictedBy:` line,
|
|
71
|
-
* or a beliefState lost to a partial edit) is repaired, not skipped — an
|
|
72
|
-
* edge-only guard would make such a file a permanent no-op while
|
|
73
|
-
* contradiction writers report the contradiction as applied.
|
|
74
|
-
*
|
|
75
|
-
* Never weakens a stronger demotion: `archived` ranks BELOW `contradicted`
|
|
76
|
-
* (see `BELIEF_STATE_SCORE_CEILINGS` in
|
|
77
|
-
* src/indexer/search/ranking-contributors.ts), so contradicting an archived
|
|
78
|
-
* memory keeps `archived` and only appends the edge.
|
|
79
|
-
*
|
|
80
|
-
* @param filePath - Absolute path to the memory markdown file.
|
|
81
|
-
* @param contradictedByRef - The ref that contradicts this memory.
|
|
82
|
-
* @returns `true` when the file was rewritten, `false` when the edge and the
|
|
83
|
-
* demotion were already present (the idempotent no-op). Callers count
|
|
84
|
-
* edges written from this.
|
|
85
|
-
*/
|
|
86
|
-
export function writeContradictEdge(filePath, contradictedByRef) {
|
|
87
|
-
return mutateFrontmatter(filePath, (parsed) => {
|
|
88
|
-
const existing = readEdgeList(parsed.data.contradictedBy);
|
|
89
|
-
const currentState = parsed.data.beliefState;
|
|
90
|
-
const nextState = currentState === "archived" ? currentState : "contradicted";
|
|
91
|
-
if (existing.includes(contradictedByRef) && currentState === nextState) {
|
|
92
|
-
return null; // Already written — idempotent.
|
|
93
|
-
}
|
|
94
|
-
const nextContradictedBy = [...new Set([...existing, contradictedByRef])].sort();
|
|
95
|
-
return {
|
|
96
|
-
...parsed.data,
|
|
97
|
-
contradictedBy: nextContradictedBy,
|
|
98
|
-
beliefState: nextState,
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
// ── Supersession edge writer ─────────────────────────────────────────────────
|
|
103
|
-
/**
|
|
104
|
-
* Write `supersededBy` and `beliefState: superseded` edges to an asset file's
|
|
105
|
-
* frontmatter (SPEC-5, stash-conventions-code-spec.md).
|
|
106
|
-
*
|
|
107
|
-
* Sibling of {@link writeContradictEdge}: the shared primitive for the
|
|
108
|
-
* conventions' corrections pattern — when a new asset supersedes an old one,
|
|
109
|
-
* the old asset gets a METADATA-ONLY demotion edit (every other frontmatter
|
|
110
|
-
* key and the body are preserved) so the ranker's beliefStateBoost demotes the
|
|
111
|
-
* stale incumbent and `--belief current` hides it. Used by
|
|
112
|
-
* `akm remember --supersedes` / `akm import --supersedes`.
|
|
113
|
-
*
|
|
114
|
-
* Idempotent: if `supersededByRef` is already in `supersededBy` and the file
|
|
115
|
-
* is already demoted, the file is not rewritten. Multiple corrections
|
|
116
|
-
* sorted-set-append their refs.
|
|
117
|
-
*
|
|
118
|
-
* Never WEAKENS an existing demotion: `contradicted` and `archived` rank
|
|
119
|
-
* BELOW `superseded` (severity order deprecated > superseded > contradicted >
|
|
120
|
-
* archived — see `BELIEF_STATE_SCORE_CEILINGS` in
|
|
121
|
-
* src/indexer/search/ranking-contributors.ts), so superseding an already
|
|
122
|
-
* contradicted/archived asset keeps the stronger state and only appends the
|
|
123
|
-
* `supersededBy` edge.
|
|
124
|
-
*
|
|
125
|
-
* @param filePath - Absolute path to the asset markdown file.
|
|
126
|
-
* @param supersededByRef - The ref of the correction that supersedes this asset.
|
|
127
|
-
*/
|
|
27
|
+
/** Mark an asset superseded by a correction (`akm remember|import --supersedes`). */
|
|
128
28
|
export function writeSupersededEdge(filePath, supersededByRef) {
|
|
129
29
|
mutateFrontmatter(filePath, (parsed) => {
|
|
130
30
|
const existing = readEdgeList(parsed.data.supersededBy);
|
|
131
31
|
const currentState = parsed.data.beliefState;
|
|
132
32
|
const nextState = currentState === "contradicted" || currentState === "archived" ? currentState : "superseded";
|
|
133
|
-
if (existing.includes(supersededByRef) && currentState === nextState)
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const nextSupersededBy = [...new Set([...existing, supersededByRef])].sort();
|
|
33
|
+
if (existing.includes(supersededByRef) && currentState === nextState)
|
|
34
|
+
return null;
|
|
137
35
|
return {
|
|
138
36
|
...parsed.data,
|
|
139
|
-
supersededBy:
|
|
37
|
+
supersededBy: [...new Set([...existing, supersededByRef])].sort(),
|
|
140
38
|
beliefState: nextState,
|
|
141
39
|
};
|
|
142
40
|
});
|
|
@@ -7,11 +7,13 @@ import { assembleAsset } from "../../../core/asset/asset-serialize.js";
|
|
|
7
7
|
import { mutateFrontmatter, parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
8
8
|
import { MEMORY_ARCHIVE_REL } from "../../../core/asset/memory-archive.js";
|
|
9
9
|
import { conceptIdFromTypeName } from "../../../core/asset/resolve-ref.js";
|
|
10
|
-
import { asNonEmptyString, groupBy, stringArray } from "../../../core/common.js";
|
|
10
|
+
import { asNonEmptyString, groupBy, stringArray, toPosix } from "../../../core/common.js";
|
|
11
11
|
import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
|
|
12
12
|
import { warn } from "../../../core/warn.js";
|
|
13
13
|
import { recordWrittenPath } from "../../../core/write-provenance.js";
|
|
14
14
|
import { walkMarkdownFiles } from "../../../indexer/walk/walker.js";
|
|
15
|
+
import { checkGitPathSafety, isGitBackedStash } from "../../../sources/providers/git-stash.js";
|
|
16
|
+
import { contentHash } from "../content-hash.js";
|
|
15
17
|
import { isDerivedMemory, memoryIdentityRef, parseMemoryName, resolveParentRef } from "./derived-ref.js";
|
|
16
18
|
export function analyzeMemoryCleanup(stashDir, options = {}) {
|
|
17
19
|
const records = collectDerivedMemories(stashDir, options.parentRef);
|
|
@@ -462,17 +464,80 @@ function stronglyConnectedComponents(refs, edges) {
|
|
|
462
464
|
}
|
|
463
465
|
return { components, componentIndexByRef };
|
|
464
466
|
}
|
|
465
|
-
|
|
467
|
+
/**
|
|
468
|
+
* The `.derived` twin of a non-derived memory file, if one exists on disk:
|
|
469
|
+
* `<name>.derived.md` beside it, the naming convention
|
|
470
|
+
* `indexer/passes/memory-inference.ts`'s `derivedChildPath` writes.
|
|
471
|
+
* `undefined` for a knowledge or lesson ref (no such twin exists), or for a
|
|
472
|
+
* memory that is already itself `.derived` (it has no further twin).
|
|
473
|
+
*
|
|
474
|
+
* Used by `akm proposal accept` (alpha.9) to take a retired or promoted
|
|
475
|
+
* memory's derived child along when it archives the memory.
|
|
476
|
+
*/
|
|
477
|
+
export function derivedTwinPath(filePath, refType) {
|
|
478
|
+
if (refType !== "memory" || filePath.endsWith(`${DERIVED_SUFFIX}.md`))
|
|
479
|
+
return undefined;
|
|
480
|
+
const twin = `${filePath.slice(0, -3)}${DERIVED_SUFFIX}.md`;
|
|
481
|
+
return fs.existsSync(twin) ? twin : undefined;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* True for a retire-proposal-caused archive (alpha.9: the consolidate pair
|
|
485
|
+
* pass, or O1's promotion retirement) — distinguished from a memory-cleanup
|
|
486
|
+
* family-prune candidate by carrying a `proposalId`. The two paths differ in
|
|
487
|
+
* how `previousBeliefState` is derived (below) and in which extra tombstone
|
|
488
|
+
* fields apply.
|
|
489
|
+
*/
|
|
490
|
+
function isRetireCandidate(candidate) {
|
|
491
|
+
return candidate.proposalId !== undefined;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* The tombstone's `previousBeliefState`. Memory cleanup's own family-prune
|
|
495
|
+
* candidates keep their original reason-based inference (unchanged, so
|
|
496
|
+
* existing behavior is not disturbed by this generalization). A
|
|
497
|
+
* retire-proposal candidate has no such reason vocabulary to infer from, so
|
|
498
|
+
* it reads the asset's ACTUAL frontmatter `beliefState` instead — more
|
|
499
|
+
* correct, and available because every retire path already has the file on
|
|
500
|
+
* disk right before the move.
|
|
501
|
+
*/
|
|
502
|
+
function resolvePreviousBeliefState(candidate, filePath) {
|
|
503
|
+
if (!isRetireCandidate(candidate))
|
|
504
|
+
return priorBeliefStateForArchive(candidate);
|
|
505
|
+
try {
|
|
506
|
+
return resolveBeliefState(parseFrontmatter(fs.readFileSync(filePath, "utf8")).data);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return "active";
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Move `filePath` into the recoverable cleanup archive
|
|
514
|
+
* (`.akm/memory-cleanup/archive/<stamp>-<ref>/`) with a `cleanup.md`
|
|
515
|
+
* tombstone, journaling both ends (`recordWrittenPath`) so a LATER sync
|
|
516
|
+
* commits the move — `akm sync`, or the batched auto-sync an `akm improve`
|
|
517
|
+
* run does at its own end (`docs/architecture/improvement.md`, "Auto-sync").
|
|
518
|
+
* This call does not itself commit anything: a standalone `akm proposal
|
|
519
|
+
* accept` (the only way a retire proposal is ever accepted — triage never
|
|
520
|
+
* auto-accepts one) leaves the move journaled but uncommitted until
|
|
521
|
+
* something later reads that journal, unless the write target's `kind` is
|
|
522
|
+
* `"git"`, in which case the caller's own `commitWriteTargetBoundary` commits
|
|
523
|
+
* (and maybe pushes) immediately as part of the SAME accept.
|
|
524
|
+
*
|
|
525
|
+
* Generalized in alpha.9 to cover any memory, knowledge or lesson file in a
|
|
526
|
+
* writable bundle — not only `.derived` memories — so `akm proposal accept`
|
|
527
|
+
* can archive a consolidate pair-pass `retire` proposal's target, or (O1) an
|
|
528
|
+
* accepted promotion's source memory, through the same one encoding memory
|
|
529
|
+
* cleanup already used (D27: never two coexisting encodings). A
|
|
530
|
+
* retire-proposal candidate (one carrying `proposalId`) additionally stamps
|
|
531
|
+
* `proposalId`, `successorRefs` and `retiredAt` on the tombstone.
|
|
532
|
+
*/
|
|
533
|
+
export function archiveCleanupCandidate(stashDir, candidate, filePath) {
|
|
466
534
|
const archivedAt = new Date().toISOString();
|
|
535
|
+
const previousBeliefState = resolvePreviousBeliefState(candidate, filePath);
|
|
467
536
|
const originalPath = path.relative(stashDir, filePath).replace(/\\/g, "/");
|
|
468
537
|
const archiveDir = createArchiveDir(stashDir, candidate.ref, archivedAt);
|
|
469
538
|
const archivedPath = path.join(archiveDir, originalPath);
|
|
470
539
|
fs.mkdirSync(path.dirname(archivedPath), { recursive: true });
|
|
471
|
-
|
|
472
|
-
// #652: an archive is a delete + a create. BOTH ends are journaled so the
|
|
473
|
-
// sync stages the removal of the original alongside the archived copy.
|
|
474
|
-
recordWrittenPath(filePath);
|
|
475
|
-
recordWrittenPath(archivedPath);
|
|
540
|
+
const retiring = isRetireCandidate(candidate);
|
|
476
541
|
const archiveRef = path.relative(stashDir, archivedPath).replace(/\\/g, "/");
|
|
477
542
|
const auditPath = path.join(archiveDir, "cleanup.md");
|
|
478
543
|
const auditRef = path.relative(stashDir, auditPath).replace(/\\/g, "/");
|
|
@@ -481,29 +546,216 @@ function archiveCleanupCandidate(stashDir, candidate, filePath) {
|
|
|
481
546
|
kind: "memory-cleanup-archive",
|
|
482
547
|
archivedAt,
|
|
483
548
|
beliefState: "archived",
|
|
484
|
-
previousBeliefState
|
|
549
|
+
previousBeliefState,
|
|
485
550
|
ref: candidate.ref,
|
|
486
|
-
parentRef: candidate.parentRef,
|
|
551
|
+
...(candidate.parentRef ? { parentRef: candidate.parentRef } : {}),
|
|
487
552
|
reason: candidate.reason,
|
|
488
553
|
...(candidate.survivorRef ? { survivorRef: candidate.survivorRef } : {}),
|
|
489
554
|
originalPath,
|
|
490
555
|
archivedPath: archiveRef,
|
|
556
|
+
...(retiring ? { proposalId: candidate.proposalId, retiredAt: archivedAt } : {}),
|
|
557
|
+
...(retiring && candidate.successorRefs && candidate.successorRefs.length > 0
|
|
558
|
+
? { successorRefs: candidate.successorRefs }
|
|
559
|
+
: {}),
|
|
491
560
|
}, "Archived derived memory for recoverable cleanup.\n");
|
|
561
|
+
// 4c (third review round): write the tombstone BEFORE moving the file — a
|
|
562
|
+
// crash in between used to leave a file already at archivedPath with no
|
|
563
|
+
// cleanup.md to explain it (unrecoverable: revert refuses on a missing
|
|
564
|
+
// tombstone, and nothing else knows this archive dir exists). Reordered,
|
|
565
|
+
// a crash here instead leaves, at worst, a tombstone describing a move
|
|
566
|
+
// that has not happened yet, with the file still at its original
|
|
567
|
+
// location — the ordinary "nothing archived yet" state every caller
|
|
568
|
+
// already handles.
|
|
492
569
|
fs.writeFileSync(auditPath, auditAsset, "utf8");
|
|
493
570
|
recordWrittenPath(auditPath);
|
|
571
|
+
fs.renameSync(filePath, archivedPath);
|
|
572
|
+
// #652: an archive is a delete + a create. BOTH ends are journaled so the
|
|
573
|
+
// sync stages the removal of the original alongside the archived copy.
|
|
574
|
+
recordWrittenPath(filePath);
|
|
575
|
+
recordWrittenPath(archivedPath);
|
|
494
576
|
return {
|
|
495
577
|
ref: candidate.ref,
|
|
496
|
-
parentRef: candidate.parentRef,
|
|
578
|
+
...(candidate.parentRef ? { parentRef: candidate.parentRef } : {}),
|
|
497
579
|
reason: candidate.reason,
|
|
498
580
|
beliefState: "archived",
|
|
499
|
-
previousBeliefState
|
|
581
|
+
previousBeliefState,
|
|
500
582
|
...(candidate.survivorRef ? { survivorRef: candidate.survivorRef } : {}),
|
|
501
583
|
originalPath,
|
|
502
584
|
archivedPath: archiveRef,
|
|
503
585
|
auditPath: auditRef,
|
|
504
586
|
archivedAt,
|
|
587
|
+
...(retiring ? { proposalId: candidate.proposalId, retiredAt: archivedAt } : {}),
|
|
588
|
+
...(retiring && candidate.successorRefs && candidate.successorRefs.length > 0
|
|
589
|
+
? { successorRefs: candidate.successorRefs }
|
|
590
|
+
: {}),
|
|
505
591
|
};
|
|
506
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* How long a retirement's archived bytes stay on disk after `retiredAt`
|
|
595
|
+
* before the purge sweep deletes them. Git history keeps the bytes (D27;
|
|
596
|
+
* plan §5.4 "Purge").
|
|
597
|
+
*/
|
|
598
|
+
export const RETIRE_GRACE_DAYS = 30;
|
|
599
|
+
const RETIRE_GRACE_MS = RETIRE_GRACE_DAYS * 24 * 60 * 60 * 1000;
|
|
600
|
+
/** The one file every archive dir keeps forever — never deleted by the purge sweep. */
|
|
601
|
+
const TOMBSTONE_FILENAME = "cleanup.md";
|
|
602
|
+
const EMPTY_ARCHIVE_PURGE_RESULT = { purgedDirs: 0, purgedFiles: 0 };
|
|
603
|
+
/**
|
|
604
|
+
* The purge sweep (0.9.17-alpha.9 plan §5.4, §8 step 8): deterministic, no
|
|
605
|
+
* LLM, run once at improve-run start. Deletes the archived asset bytes —
|
|
606
|
+
* never `cleanup.md` — of every retirement whose tombstone `retiredAt` is
|
|
607
|
+
* more than {@link RETIRE_GRACE_DAYS} old AND whose archived files are all
|
|
608
|
+
* git-tracked and clean at the time of the sweep (see below). Git history
|
|
609
|
+
* keeps the bytes (D27); the tombstone, and ref resolution through it
|
|
610
|
+
* (`core/asset/memory-archive.ts`), are unaffected — only the tombstone's
|
|
611
|
+
* own `originalPath` file(s) are removed.
|
|
612
|
+
*
|
|
613
|
+
* Git-backed bundles only: a bundle with no `.git` of its own has no history
|
|
614
|
+
* to fall back on, so its archive is left untouched (`akm health` reports
|
|
615
|
+
* its size instead — see `health/archive-usage.ts`). Every deleted path is
|
|
616
|
+
* journaled (`recordWrittenPath`) so the end-of-run sync commits the
|
|
617
|
+
* removal, the same way it commits the archive move itself (#652).
|
|
618
|
+
*
|
|
619
|
+
* `.git` presence is necessary but NOT sufficient: `proposal accept` only
|
|
620
|
+
* commits for a `kind: "git"` write target (`core/write-source.ts`
|
|
621
|
+
* `commitWriteTargetBoundary`), and improve's own auto-sync stages only the
|
|
622
|
+
* paths its own run wrote. A filesystem-kind bundle that merely happens to
|
|
623
|
+
* have a `.git` directory (e.g. the owner committing by hand, or an old
|
|
624
|
+
* repo that was never configured as a git source) can carry retirements
|
|
625
|
+
* that were archived but never committed — deleting those would lose the
|
|
626
|
+
* only surviving copy. So every archived file under a directory past grace
|
|
627
|
+
* is checked against `git ls-files` (tracked), `git status --porcelain
|
|
628
|
+
* -uall` (clean), and `git ls-files -v` (verifiable — an assume-unchanged
|
|
629
|
+
* or skip-worktree file hides its own edits from `git status`, so it is
|
|
630
|
+
* never trusted as clean either) — each computed ONCE per sweep, not per
|
|
631
|
+
* directory; a directory with even one untracked, modified, or
|
|
632
|
+
* unverifiable file (tombstone included) is left whole for a later sweep.
|
|
633
|
+
* If any of those three git calls itself fails (a broken submodule can fail
|
|
634
|
+
* `git status` while `git ls-files` still succeeds, or git can be missing
|
|
635
|
+
* from `PATH` entirely), the whole sweep purges nothing and warns once —
|
|
636
|
+
* an empty result from a FAILED check is never treated the same as a
|
|
637
|
+
* verified-empty one.
|
|
638
|
+
*
|
|
639
|
+
* A memory-cleanup family-prune archive (not a retire proposal's) carries no
|
|
640
|
+
* `retiredAt` in its tombstone at all, so it is never a candidate here —
|
|
641
|
+
* this sweep only ever touches retirements, never that older archive class.
|
|
642
|
+
*/
|
|
643
|
+
export function purgeGracedArchive(stashDir, now = new Date()) {
|
|
644
|
+
if (!isGitBackedStash(stashDir))
|
|
645
|
+
return EMPTY_ARCHIVE_PURGE_RESULT;
|
|
646
|
+
const archiveRoot = path.join(stashDir, MEMORY_ARCHIVE_REL);
|
|
647
|
+
let entries;
|
|
648
|
+
try {
|
|
649
|
+
entries = fs.readdirSync(archiveRoot, { withFileTypes: true });
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return EMPTY_ARCHIVE_PURGE_RESULT; // no archive yet
|
|
653
|
+
}
|
|
654
|
+
const cutoffMs = now.getTime() - RETIRE_GRACE_MS;
|
|
655
|
+
// One git inspection per sweep, not per directory. All three sets are
|
|
656
|
+
// repo-relative POSIX paths, matched below against each archived file's
|
|
657
|
+
// own repo-relative path — a file is safe to delete only if it is in
|
|
658
|
+
// `tracked`, NOT in `dirty`, and NOT in `unverifiable`.
|
|
659
|
+
//
|
|
660
|
+
// Each of the three git calls can itself fail independently — a broken
|
|
661
|
+
// submodule can make `git status` exit nonzero while `git ls-files`
|
|
662
|
+
// succeeds, or vice versa (round-3 review, probes G8/G9). `[]` from a
|
|
663
|
+
// failed call is indistinguishable from a genuinely empty result once it
|
|
664
|
+
// is in a Set, so this checks `ok` FIRST: any failure purges nothing this
|
|
665
|
+
// sweep rather than silently trusting whichever check happened to
|
|
666
|
+
// succeed — a `dirty`/`unverifiable` set that came back empty ONLY
|
|
667
|
+
// because the call failed must never read as "nothing to protect".
|
|
668
|
+
// G10: assume-unchanged / skip-worktree files never show up as dirty even
|
|
669
|
+
// when genuinely modified — `checkGitPathSafety` treats them the same as
|
|
670
|
+
// "not tracked" below, so such a file (and its whole retirement) is left
|
|
671
|
+
// for a later sweep.
|
|
672
|
+
const gitSafety = checkGitPathSafety(stashDir, MEMORY_ARCHIVE_REL);
|
|
673
|
+
if (!gitSafety.ok) {
|
|
674
|
+
warn(`[improve] archive purge: skipped this sweep — could not determine the archive's git state at ${stashDir} ` +
|
|
675
|
+
"(git status/ls-files failed); nothing was purged.");
|
|
676
|
+
return EMPTY_ARCHIVE_PURGE_RESULT;
|
|
677
|
+
}
|
|
678
|
+
let purgedDirs = 0;
|
|
679
|
+
let purgedFiles = 0;
|
|
680
|
+
for (const entry of entries) {
|
|
681
|
+
// `Dirent.isDirectory()` reflects `lstat`, so it is false for a symlink
|
|
682
|
+
// even when the symlink points at a directory — a symlinked
|
|
683
|
+
// `archive/<name>` is skipped here, never followed (N1). Everything
|
|
684
|
+
// below only ever joins path components onto `archiveRoot` through
|
|
685
|
+
// `entry.name`/`readdirSync` results, so a purge can never reach
|
|
686
|
+
// outside `.akm/memory-cleanup/archive/`.
|
|
687
|
+
if (!entry.isDirectory())
|
|
688
|
+
continue;
|
|
689
|
+
const dir = path.join(archiveRoot, entry.name);
|
|
690
|
+
let data;
|
|
691
|
+
try {
|
|
692
|
+
data = parseFrontmatter(fs.readFileSync(path.join(dir, TOMBSTONE_FILENAME), "utf8")).data;
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
continue; // not a tombstone dir, or unreadable — never guess
|
|
696
|
+
}
|
|
697
|
+
const retiredAt = data.retiredAt;
|
|
698
|
+
if (typeof retiredAt !== "string")
|
|
699
|
+
continue; // family-prune archive, not a retirement — out of scope
|
|
700
|
+
const retiredMs = Date.parse(retiredAt);
|
|
701
|
+
if (!Number.isFinite(retiredMs) || retiredMs >= cutoffMs)
|
|
702
|
+
continue; // "more than" the grace period — exactly at it is not enough
|
|
703
|
+
const allFiles = listFilesRecursive(dir); // tombstone included — the whole entry must be a clean, committed unit
|
|
704
|
+
const isSafeToPurge = allFiles.every((filePath) => gitSafety.isSafe(toPosix(path.relative(stashDir, filePath))));
|
|
705
|
+
if (!isSafeToPurge)
|
|
706
|
+
continue; // untracked, modified, or unverifiable entry — skip the whole directory this sweep (B1, G10)
|
|
707
|
+
let children;
|
|
708
|
+
try {
|
|
709
|
+
children = fs.readdirSync(dir);
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
let purgedAnyInThisDir = false;
|
|
715
|
+
for (const child of children) {
|
|
716
|
+
if (child === TOMBSTONE_FILENAME)
|
|
717
|
+
continue;
|
|
718
|
+
const childPath = path.join(dir, child);
|
|
719
|
+
// The archived original path may be nested (e.g. `memories/sub/foo.md`
|
|
720
|
+
// under this dir) — the journal (like git) tracks FILES, so every leaf
|
|
721
|
+
// under childPath is recorded individually, not the directory itself.
|
|
722
|
+
const filesUnderChild = listFilesRecursive(childPath);
|
|
723
|
+
try {
|
|
724
|
+
fs.rmSync(childPath, { recursive: true, force: true });
|
|
725
|
+
for (const filePath of filesUnderChild)
|
|
726
|
+
recordWrittenPath(filePath);
|
|
727
|
+
purgedFiles += filesUnderChild.length;
|
|
728
|
+
purgedAnyInThisDir = purgedAnyInThisDir || filesUnderChild.length > 0;
|
|
729
|
+
}
|
|
730
|
+
catch {
|
|
731
|
+
// Best-effort: a locked or already-gone entry is skipped, not fatal to the run.
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (purgedAnyInThisDir)
|
|
735
|
+
purgedDirs++;
|
|
736
|
+
}
|
|
737
|
+
return { purgedDirs, purgedFiles };
|
|
738
|
+
}
|
|
739
|
+
/** Every file under `target` (itself included if it's a file), for individual journaling before a recursive delete. */
|
|
740
|
+
function listFilesRecursive(target) {
|
|
741
|
+
let stat;
|
|
742
|
+
try {
|
|
743
|
+
stat = fs.lstatSync(target);
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return [];
|
|
747
|
+
}
|
|
748
|
+
if (!stat.isDirectory())
|
|
749
|
+
return stat.isFile() ? [target] : [];
|
|
750
|
+
let children;
|
|
751
|
+
try {
|
|
752
|
+
children = fs.readdirSync(target);
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
return children.flatMap((child) => listFilesRecursive(path.join(target, child)));
|
|
758
|
+
}
|
|
507
759
|
function persistBeliefStateTransition(filePath, transition) {
|
|
508
760
|
mutateFrontmatter(filePath, (parsed) => {
|
|
509
761
|
const nextFrontmatter = {
|
|
@@ -676,7 +928,7 @@ function resolveBeliefState(frontmatter) {
|
|
|
676
928
|
// firstExistingRef's byRef map), so they are NORMALIZED to `memory:<name>` here.
|
|
677
929
|
// On disk they arrive in either spelling — `memory:<name>` from pre-0.9.0
|
|
678
930
|
// writes, or the `[<bundle>//]memories/<name>` conceptId that
|
|
679
|
-
// `writeSupersededEdge
|
|
931
|
+
// `writeSupersededEdge` persists today — and
|
|
680
932
|
// `parseMemoryName` accepts both. Reading only the first spelling silently
|
|
681
933
|
// dropped every edge the current write path produces.
|
|
682
934
|
function refArray(value) {
|
|
@@ -697,13 +949,13 @@ function refArray(value) {
|
|
|
697
949
|
return [...refs].sort();
|
|
698
950
|
}
|
|
699
951
|
function buildFingerprint(title, description, tags, searchHints, body) {
|
|
700
|
-
return JSON.stringify({
|
|
952
|
+
return contentHash(JSON.stringify({
|
|
701
953
|
title: normalizeSignal(title),
|
|
702
954
|
description: normalizeSignal(description),
|
|
703
955
|
tags: normalizeList(tags),
|
|
704
956
|
searchHints: normalizeList(searchHints),
|
|
705
957
|
body: normalizeBody(body),
|
|
706
|
-
});
|
|
958
|
+
}));
|
|
707
959
|
}
|
|
708
960
|
function normalizeBody(value) {
|
|
709
961
|
return value
|