akm-cli 0.9.17-alpha.8 → 0.9.17

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +277 -1694
  2. package/STABILITY.md +9 -8
  3. package/dist/assets/hints/cli-hints-full.md +6 -7
  4. package/dist/assets/improve-strategies/catchup.json +0 -3
  5. package/dist/assets/improve-strategies/consolidate.json +0 -1
  6. package/dist/assets/improve-strategies/default.json +1 -2
  7. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  8. package/dist/assets/improve-strategies/quick.json +1 -2
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  10. package/dist/assets/improve-strategies/thorough.json +0 -3
  11. package/dist/assets/prompts/consolidate-pair.md +20 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +17 -19
  13. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  14. package/dist/assets/templates/html/health.html +3 -5
  15. package/dist/cli/retired-commands.js +1 -1
  16. package/dist/cli/unknown-flags.js +24 -1
  17. package/dist/cli.js +46 -1
  18. package/dist/commands/health/archive-usage.js +92 -0
  19. package/dist/commands/health/data-dir-usage.js +25 -13
  20. package/dist/commands/health/html-report.js +1 -4
  21. package/dist/commands/health/improve-metrics.js +25 -37
  22. package/dist/commands/health/md-report.js +1 -6
  23. package/dist/commands/health/report-view-model.js +4 -14
  24. package/dist/commands/health/windows.js +0 -1
  25. package/dist/commands/health.js +13 -0
  26. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  27. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  28. package/dist/commands/improve/consolidate.js +38 -63
  29. package/dist/commands/improve/extract-prompt.js +1 -2
  30. package/dist/commands/improve/improve-cli.js +1 -1
  31. package/dist/commands/improve/improve-strategies.js +23 -5
  32. package/dist/commands/improve/improve.js +19 -30
  33. package/dist/commands/improve/ledger.js +3 -2
  34. package/dist/commands/improve/loop-stages.js +5 -85
  35. package/dist/commands/improve/memory/memory-belief.js +3 -1
  36. package/dist/commands/improve/memory/memory-improve.js +262 -11
  37. package/dist/commands/improve/planner.js +0 -5
  38. package/dist/commands/improve/preparation.js +20 -135
  39. package/dist/commands/improve/retrieval-scope.js +19 -4
  40. package/dist/commands/improve/salience.js +1 -14
  41. package/dist/commands/improve/stage.js +0 -1
  42. package/dist/commands/lint/base-linter.js +19 -11
  43. package/dist/commands/proposal/drain.js +8 -1
  44. package/dist/commands/proposal/proposal-cli.js +16 -2
  45. package/dist/commands/proposal/proposal-types.js +7 -0
  46. package/dist/commands/proposal/proposal.js +37 -6
  47. package/dist/commands/proposal/repository.js +613 -4
  48. package/dist/commands/proposal/validators/proposals.js +9 -0
  49. package/dist/commands/read/knowledge.js +3 -2
  50. package/dist/commands/read/show.js +0 -14
  51. package/dist/commands/sources/info.js +122 -18
  52. package/dist/commands/sources/stash-cli.js +23 -3
  53. package/dist/core/bundle-rename.js +1 -7
  54. package/dist/core/config/config-schema.js +8 -1
  55. package/dist/core/config/config.js +23 -48
  56. package/dist/core/config/engine-semantics.js +0 -2
  57. package/dist/core/config/schema/improve-processes.js +17 -42
  58. package/dist/core/config/schema/index-config.js +5 -25
  59. package/dist/core/file-change.js +13 -5
  60. package/dist/core/improve-result.js +22 -6
  61. package/dist/core/improve-types.js +0 -1
  62. package/dist/core/loopback.js +7 -12
  63. package/dist/core/parse.js +13 -16
  64. package/dist/core/state/migrations.js +15 -0
  65. package/dist/core/time.js +0 -20
  66. package/dist/indexer/db/llm-cache.js +2 -2
  67. package/dist/indexer/ensure-index.js +2 -2
  68. package/dist/indexer/index-written-assets.js +2 -3
  69. package/dist/indexer/indexer.js +18 -418
  70. package/dist/indexer/passes/metadata.js +0 -19
  71. package/dist/indexer/walk/walker.js +3 -4
  72. package/dist/llm/client.js +8 -10
  73. package/dist/llm/embedders/remote.js +1 -2
  74. package/dist/llm/feature-gate.js +0 -5
  75. package/dist/output/shapes/helpers.js +20 -4
  76. package/dist/output/text/command-format.js +9 -8
  77. package/dist/output/text/proposal-format.js +47 -1
  78. package/dist/output/text/show-format.js +0 -20
  79. package/dist/scripts/akm-migrate-node.js +923 -950
  80. package/dist/scripts/akm-migrate.js +923 -950
  81. package/dist/setup/steps/connection.js +5 -6
  82. package/dist/setup/steps/platforms.js +2 -2
  83. package/dist/sources/providers/git-stash.js +83 -4
  84. package/dist/storage/repositories/improve-ledger-repository.js +48 -7
  85. package/dist/storage/repositories/index-connection.js +5 -2
  86. package/dist/storage/repositories/index-entries-repository.js +4 -7
  87. package/dist/storage/repositories/index-entry-schema.js +4 -2
  88. package/dist/storage/repositories/index-llm-cache-repository.js +7 -26
  89. package/dist/storage/repositories/index-schema.js +55 -104
  90. package/dist/storage/repositories/proposals-repository.js +61 -0
  91. package/dist/storage/repositories/salience-repository.js +1 -19
  92. package/docs/migration/README.md +1 -1
  93. package/docs/migration/release-notes/0.9.17.md +130 -41
  94. package/docs/migration/release-notes/README.md +7 -0
  95. package/docs/reference/cli.md +27 -21
  96. package/docs/reference/configuration.md +21 -12
  97. package/docs/reference/data-and-telemetry.md +0 -1
  98. package/package.json +1 -1
  99. package/schemas/akm-config.json +0 -342
  100. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  101. package/dist/assets/prompts/contradiction-judge.md +0 -33
  102. package/dist/assets/prompts/graph-extract-system.md +0 -1
  103. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  104. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  105. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  106. package/dist/indexer/db/graph-db.js +0 -399
  107. package/dist/indexer/graph/graph-extraction.js +0 -809
  108. package/dist/indexer/graph/graph-related.js +0 -131
  109. package/dist/indexer/graph/graph-types.js +0 -4
  110. package/dist/llm/graph-extract.js +0 -892
  111. package/dist/llm/metadata-enhance.js +0 -95
@@ -4,12 +4,19 @@
4
4
  /**
5
5
  * `akm consolidate` — show the model the memory pool in chunks of similar
6
6
  * memories and queue a knowledge proposal for each memory it says should be
7
- * promoted. Promotion is the only operation: it emits a reviewable proposal and
8
- * never touches the memory. Memories the improve ledger judged recently and
7
+ * promoted. Promotion emits a reviewable proposal and never touches the
8
+ * memory directly; accepting it later retires the source memory (O1, in
9
+ * `proposal/repository.ts`). Memories the improve ledger judged recently and
9
10
  * that have not changed since are not judged again.
10
11
  *
11
- * Accounting invariant: `processed == promoted + judgedNoAction +
12
- * Σ(skipReasons) + failedChunkMemories`.
12
+ * Accounting invariant (the promote pass only): `processed == promoted +
13
+ * judgedNoAction + Σ(skipReasons) + failedChunkMemories`.
14
+ *
15
+ * A second pass, the pair pass (`consolidate/pair-pass.ts`, alpha.9), runs
16
+ * alongside this one and keeps its own separate counters (`pairPass` on the
17
+ * result) — it judges near-duplicate and superseding pairs across the wider
18
+ * memory tier and mints `retire` proposals; see that module's own doc
19
+ * comment.
13
20
  */
14
21
  import fs from "node:fs";
15
22
  import path from "node:path";
@@ -21,7 +28,6 @@ import { conceptIdFromTypeName, displayRef, parseRefInput } from "../../core/ass
21
28
  import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
22
29
  import { parseEmbeddedJsonResponse } from "../../core/parse.js";
23
30
  import { openStateDatabase } from "../../core/state-db.js";
24
- import { parseSinceToIsoLenient } from "../../core/time.js";
25
31
  import { warn, warnVerbose } from "../../core/warn.js";
26
32
  import { resolveWriteTarget } from "../../core/write-source.js";
27
33
  import { deriveInstallations } from "../../indexer/installations.js";
@@ -31,11 +37,11 @@ import { assertRunnerCredentials } from "../../integrations/agent/runner-dispatc
31
37
  import { cosineSimilarity, embedBatch, resolveEmbeddingModelId } from "../../llm/embedder.js";
32
38
  import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
33
39
  import { closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
34
- import { findEntryIdByRef, getAllEntries, getEntryById } from "../../storage/repositories/index-entries-repository.js";
35
- import { getNeighborsByEntryId } from "../../storage/repositories/index-vec-repository.js";
40
+ import { getAllEntries } from "../../storage/repositories/index-entries-repository.js";
36
41
  import { listProposals, listProposalsReadOnly, proposalContent } from "../proposal/repository.js";
37
42
  import { hasHotCaptureMode, hasSupersededStatus, validateProposalFrontmatter, } from "../proposal/validators/proposal-quality-validators.js";
38
43
  import { buildChunkPrompt, computeSafeChunkSize, DEFAULT_CONTEXT_LENGTH_TOKENS } from "./consolidate/chunking.js";
44
+ import { runConsolidatePairPass } from "./consolidate/pair-pass.js";
39
45
  import { sanitizeMergedContent } from "./consolidate/sanitize.js";
40
46
  import { contentHash } from "./content-hash.js";
41
47
  import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
@@ -77,7 +83,8 @@ export function isHotCapturedMemory(filePath) {
77
83
  }
78
84
  /**
79
85
  * Structured-output schema for a plan. Promote-only: merge/delete/contradict
80
- * were advisory, never executed, and cost thousands of completion tokens.
86
+ * were removed in 0.9.17-alpha.1 (`e82eec811`) after running in production —
87
+ * they cost thousands of completion tokens.
81
88
  */
82
89
  export const CONSOLIDATE_PLAN_JSON_SCHEMA = {
83
90
  type: "object",
@@ -435,9 +442,6 @@ export function inspectConsolidationPool(opts, stashDir, warnings, existingKnowl
435
442
  const beforeScope = memories.length;
436
443
  memories = memories.filter((memory) => isInRetrievalScope(retrievalScope, conceptIdFromTypeName("memory", memory.name), memory.filePath));
437
444
  const outsideRetrievalScope = beforeScope - memories.length;
438
- if (opts.incrementalSince && memories.length > 0) {
439
- memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings, opts.neighborsPerChanged, readOnly);
440
- }
441
445
  const dedupPoolSize = memories.length;
442
446
  if (opts.limit === undefined && memories.length > 150) {
443
447
  warnings.push(`Consolidation: pool has ${memories.length} memories and no limit is set. Consider adding a limit to your consolidate config to prevent timeouts on slow LLM endpoints.`);
@@ -656,6 +660,12 @@ async function consolidate(opts, config, stashDir, startMs, stateDb) {
656
660
  warnings.push(`Consolidation: pre-filtered ${prefilteredAlreadyPromoted} ${plural(prefilteredAlreadyPromoted)} whose body already exists verbatim in knowledge/ before chunking.`);
657
661
  }
658
662
  const target = opts.target ?? stashDir;
663
+ // The pair pass (alpha.9) has its own initiator/candidate selection (it
664
+ // sees .derived memories, flat knowledge and lessons, not just the
665
+ // promote pool above), so it runs regardless of whether the promote pool
666
+ // is empty — every return path below carries its result.
667
+ const pairPassBundleId = resolveConsolidationSourceOwner(opts, stashDir)?.bundleId;
668
+ const pairPass = await runConsolidatePairPass(opts, config, stashDir, pairPassBundleId, warnings);
659
669
  if (memories.length === 0) {
660
670
  return makeConsolidateResult({
661
671
  dryRun: opts.dryRun ?? false,
@@ -663,6 +673,7 @@ async function consolidate(opts, config, stashDir, startMs, stateDb) {
663
673
  warnings,
664
674
  durationMs: Date.now() - startMs,
665
675
  prefilteredAlreadyPromoted,
676
+ pairPass,
666
677
  });
667
678
  }
668
679
  const acc = {
@@ -692,7 +703,7 @@ async function consolidate(opts, config, stashDir, startMs, stateDb) {
692
703
  durationMs: Date.now() - startMs,
693
704
  });
694
705
  if (opts.dryRun)
695
- return makeConsolidateResult({ ...summary(), dryRun: true, previewOnly: true });
706
+ return makeConsolidateResult({ ...summary(), dryRun: true, previewOnly: true, pairPass });
696
707
  warn(`[consolidate] plan: ${plan.allOps.length} operation(s)`);
697
708
  const ctx = {
698
709
  config,
@@ -720,6 +731,7 @@ async function consolidate(opts, config, stashDir, startMs, stateDb) {
720
731
  ...summary(),
721
732
  promoted: ctx.promoted,
722
733
  failedPromotions: ctx.promotionFailures.count,
734
+ pairPass,
723
735
  perfTelemetry: {
724
736
  dedupPoolSize: pool.dedupPoolSize,
725
737
  llmPoolSize: plan.llmPoolSize,
@@ -799,6 +811,12 @@ export async function emitPromotionProposal(op, ctx) {
799
811
  catch (e) {
800
812
  return skip("promote_read_failed", `Promote: could not read ${op.ref}: ${String(e)}`);
801
813
  }
814
+ // O1 hash nit: the RAW body, before sanitization — accept re-reads the
815
+ // source with a plain fs.readFileSync and never re-sanitizes, so hashing
816
+ // anything else here would compare two different representations of the
817
+ // same unedited memory and report a false "changed since mint" (measured:
818
+ // 3 of 767 real memories sanitize to different bytes than their raw body).
819
+ const sourceRawBodyHash = contentHash(memoryContent, "body");
802
820
  const sanitized = sanitizeMergedContent(memoryContent);
803
821
  if (!sanitized.ok) {
804
822
  return skip("promote_sanitization_failed", `Promote: rejected ${op.ref} — source memory failed sanitization (${sanitized.reason}).`);
@@ -856,6 +874,14 @@ export async function emitPromotionProposal(op, ctx) {
856
874
  ...(typeof op.confidence === "number" ? { confidence: op.confidence } : {}),
857
875
  // The ledger keys the attempt by the source memory.
858
876
  attemptedRefs: [op.ref],
877
+ // O1 (alpha.9): on accept, promoteProposal retires this source memory
878
+ // (and its .derived twin) so promotion no longer leaves a duplicate.
879
+ promotionSource: op.ref,
880
+ // B3: recorded so accept can refuse to archive a source that was
881
+ // edited after this promotion was queued — the RAW body hash (see
882
+ // sourceRawBodyHash's own comment above), not `bodyHash`, which is the
883
+ // sanitized-for-knowledge representation accept never re-derives.
884
+ promotionSourceHash: sourceRawBodyHash,
859
885
  });
860
886
  ctx.promoted.push(proposal.id);
861
887
  ctx.promotedSourceRefs.add(op.ref);
@@ -865,57 +891,6 @@ export async function emitPromotionProposal(op, ctx) {
865
891
  skip("promote_create_failed", `Promote: createProposal failed for ${op.ref}: ${String(e)}`);
866
892
  }
867
893
  }
868
- /**
869
- * {changed} ∪ {top-k indexed neighbours of each changed memory}, within the
870
- * pool: nothing changed → []; everything changed or no index → the full pool.
871
- */
872
- export function narrowToIncrementalCandidates(memories, since, warnings, neighborsPerChanged = 5, readOnly = false) {
873
- // Lenient: a garbage `since` passes through and selects nothing.
874
- const sinceIso = parseSinceToIsoLenient(since);
875
- const changed = memories.filter((m) => {
876
- try {
877
- return fs.statSync(m.filePath).mtime.toISOString() > sinceIso;
878
- }
879
- catch {
880
- return true; // never silently drop a memory we cannot stat
881
- }
882
- });
883
- if (changed.length === 0)
884
- return [];
885
- if (changed.length === memories.length)
886
- return memories;
887
- const inPool = new Set(memories.map((m) => m.name));
888
- const keep = new Set(changed.map((m) => m.name));
889
- let db;
890
- try {
891
- db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
892
- if (!db)
893
- return memories;
894
- for (const m of changed) {
895
- const id = findEntryIdByRef(db, conceptIdFromTypeName("memory", m.name));
896
- if (id === undefined)
897
- continue;
898
- for (const hit of getNeighborsByEntryId(db, id, neighborsPerChanged + 1)) {
899
- if (hit.id === id)
900
- continue;
901
- const name = getEntryById(db, hit.id)?.entry.name;
902
- if (name && inPool.has(name))
903
- keep.add(name);
904
- }
905
- }
906
- }
907
- catch {
908
- warnings.push("Incremental consolidation: index unavailable — processing full pool.");
909
- return memories;
910
- }
911
- finally {
912
- if (db)
913
- closeDatabase(db);
914
- }
915
- const candidates = memories.filter((m) => keep.has(m.name));
916
- warnings.push(`Incremental consolidation: ${changed.length} changed + neighbours → ${candidates.length}/${memories.length} memories considered (since ${since}${sinceIso !== since ? ` = ${sinceIso}` : ""}).`);
917
- return candidates;
918
- }
919
894
  /** The target bundle's eligible memories from the index, else walked from disk. */
920
895
  function loadMemoriesForSource(source, warnings, readOnly) {
921
896
  let memories = [];
@@ -111,8 +111,7 @@ function formatAlreadyPreserved(inlineRefs) {
111
111
  }
112
112
  /**
113
113
  * Delimiters that fence the untrusted session transcript in the extract prompt.
114
- * Mirrors the `=== ASSET N ===` convention (`graph-extract.ts`): everything
115
- * between the markers is DATA to analyze, never instructions to obey. The
114
+ * Everything between the markers is DATA to analyze, never instructions to obey. The
116
115
  * transcript is external, attacker-influenceable content, so an explicit,
117
116
  * greppable boundary defuses prompt-injection that tries to pose as a command.
118
117
  */
@@ -250,7 +250,7 @@ export const improveCommand = defineCommand({
250
250
  },
251
251
  strategy: {
252
252
  type: "string",
253
- description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default, graph-refresh, proactive-maintenance, quick, reflect-distill, thorough). Controls which sub-processes run and which asset types are processed.",
253
+ description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default, proactive-maintenance, quick, reflect-distill, thorough). Controls which sub-processes run and which asset types are processed.",
254
254
  },
255
255
  sync: {
256
256
  type: "boolean",
@@ -4,13 +4,11 @@
4
4
  import catchup from "../../assets/improve-strategies/catchup.json" with { type: "json" };
5
5
  import consolidate from "../../assets/improve-strategies/consolidate.json" with { type: "json" };
6
6
  import defaultStrategy from "../../assets/improve-strategies/default.json" with { type: "json" };
7
- import graphRefresh from "../../assets/improve-strategies/graph-refresh.json" with { type: "json" };
8
7
  import proactiveMaintenance from "../../assets/improve-strategies/proactive-maintenance.json" with { type: "json" };
9
8
  import quick from "../../assets/improve-strategies/quick.json" with { type: "json" };
10
9
  import reflectDistill from "../../assets/improve-strategies/reflect-distill.json" with { type: "json" };
11
10
  import thorough from "../../assets/improve-strategies/thorough.json" with { type: "json" };
12
11
  import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
13
- import { getIndexPassConfig, } from "../../core/config/config.js";
14
12
  import { ImproveProfileConfigSchema } from "../../core/config/config-schema.js";
15
13
  import { deepMergeConfig } from "../../core/config/deep-merge.js";
16
14
  import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_PROCESS_ENGINE_CAPABILITIES, } from "../../core/config/engine-semantics.js";
@@ -72,7 +70,6 @@ const BUILTIN_STRATEGIES = {
72
70
  default: defaultStrategy,
73
71
  quick,
74
72
  thorough,
75
- "graph-refresh": graphRefresh,
76
73
  consolidate,
77
74
  catchup,
78
75
  "reflect-distill": reflectDistill,
@@ -84,6 +81,29 @@ if (BUILTIN_IMPROVE_STRATEGY_NAMES.some((name) => !(name in BUILTIN_STRATEGIES))
84
81
  export function resolveImproveStrategy(name, config) {
85
82
  const selectedName = name ?? config.defaults?.improveStrategy ?? "default";
86
83
  const userStrategies = config.improve?.strategies ?? {};
84
+ // graph-refresh named a specific, common retirement — refuse it
85
+ // unconditionally, even when `improve.strategies["graph-refresh"]` still
86
+ // has an override block from when it customized the (now-deleted) built-in
87
+ // strategy of the same name. That override is a partial patch (e.g. just
88
+ // `processes.graphExtraction.mode`), not a full strategy definition —
89
+ // falling through to the generic "resolve as a user strategy" path below
90
+ // would silently merge it onto `default` and run a full, unplanned improve
91
+ // pass instead of refusing. `akm migrate apply` drops the leftover block
92
+ // (src/core/config/config.ts, normalizeConfigFile) since it can never
93
+ // apply again.
94
+ if (selectedName === "graph-refresh") {
95
+ const hasLeftoverOverride = Boolean(userStrategies["graph-refresh"]);
96
+ throw new ConfigError(`Improve strategy "graph-refresh" was retired in 0.9.17-alpha.9 along with the LLM entity-graph extraction it ran.` +
97
+ (hasLeftoverOverride
98
+ ? ' Your config still has a leftover `improve.strategies["graph-refresh"]` override for it — `akm migrate apply` removes it.'
99
+ : ""), "UNKNOWN_IMPROVE_STRATEGY",
100
+ // Override CONFIG_HINTS.UNKNOWN_IMPROVE_STRATEGY (src/core/errors.ts):
101
+ // its "listed strategy names" phrase does not apply here (this message
102
+ // names no strategies), and its "define it under improve.strategies"
103
+ // suggestion is actively wrong — that is exactly the leftover-override
104
+ // shape this refusal exists to reject, not a way around it.
105
+ "Choose a different strategy. `graph-refresh` cannot be redefined under `improve.strategies` — it always refuses.");
106
+ }
87
107
  if (!(selectedName in BUILTIN_STRATEGIES) && !userStrategies[selectedName]) {
88
108
  const valid = [...new Set([...Object.keys(BUILTIN_STRATEGIES), ...Object.keys(userStrategies)])].sort();
89
109
  throw new ConfigError(`Improve strategy "${selectedName}" not found. Valid strategies: ${valid.join(", ")}.`, "UNKNOWN_IMPROVE_STRATEGY");
@@ -212,8 +232,6 @@ function buildImprovePlan(strategy, config, options) {
212
232
  profile: strategy.config,
213
233
  process: sourceProcessConfig,
214
234
  processName,
215
- // Graph extraction's standing engine, model, timeout and llm settings (GR-D15).
216
- ...(processName === "graphExtraction" ? { index: getIndexPassConfig(config.index, "graph") } : {}),
217
235
  });
218
236
  runner = resolved?.runner ?? null;
219
237
  notices = resolved?.notices ?? [];
@@ -42,7 +42,7 @@ import { buildImproveUsageReport } from "./improve-usage-report.js";
42
42
  import { lastAttemptByRef, loadLedgerSnapshot } from "./ledger.js";
43
43
  import { improveLockPath, releaseImproveLock, tryAcquireImproveLock } from "./locks.js";
44
44
  import { runImproveLoopStage, runImprovePostLoopStage } from "./loop-stages.js";
45
- import { analyzeMemoryCleanup } from "./memory/memory-improve.js";
45
+ import { analyzeMemoryCleanup, purgeGracedArchive, RETIRE_GRACE_DAYS, } from "./memory/memory-improve.js";
46
46
  import { buildImproveExecutionPlan } from "./planner.js";
47
47
  import { CONSOLIDATION_CONFIG_KEYS, pickDefined, recordImproveSkip, runImprovePreparationStage } from "./preparation.js";
48
48
  import { DEFAULT_DUE_DAYS, filterProactiveDue } from "./proactive-maintenance.js";
@@ -175,6 +175,21 @@ export async function akmImprove(options = {}) {
175
175
  initialGitPaths =
176
176
  syncRepoDir && isGitBackedStash(syncRepoDir) ? new Set(listGitChangedPaths(syncRepoDir)) : new Set();
177
177
  journal = beginWriteProvenance();
178
+ // Step 8 (alpha.9 plan §5.4): the purge sweep. Deterministic, no LLM,
179
+ // and first — before anything else this run might touch the archive
180
+ // for. Git-backed bundles only; a bundle with no git history of its
181
+ // own is left untouched (akm health reports its size instead).
182
+ if (setup.primaryStashDir) {
183
+ try {
184
+ const purge = purgeGracedArchive(setup.primaryStashDir);
185
+ if (purge.purgedFiles > 0) {
186
+ info(`[improve] archive purge: ${purge.purgedFiles} file(s) across ${purge.purgedDirs} retirement(s) past the ${RETIRE_GRACE_DAYS}d grace period removed`);
187
+ }
188
+ }
189
+ catch (err) {
190
+ preEnsureCleanupWarnings.push(`archive purge failed: ${err instanceof Error ? err.message : String(err)}`);
191
+ }
192
+ }
178
193
  // The index is made current BEFORE triage (R6): triage promotes into the
179
194
  // stash, and a reindex after it would always find fresh work.
180
195
  const bootstrap = await runIndexBootstrapPass(setup, budgetAbortController.signal);
@@ -564,7 +579,6 @@ function buildResultExecutionPlan(run, preparation, collected, dryRun) {
564
579
  : undefined;
565
580
  const belowMinPending = pendingMemories !== undefined && inferenceMinPending !== undefined && pendingMemories < inferenceMinPending;
566
581
  const memoryInferenceEnabled = resolvedPlan.processes.memoryInference.enabled && !belowMinPending;
567
- const graphExtractionEnabled = resolvedPlan.processes.graphExtraction.enabled && run.primaryStashDir !== undefined;
568
582
  // Per-process routing, plus how many effective refs each ref-scoped process would act on (#947).
569
583
  const processes = projectResolvedProcessRouting(resolvedPlan).map((row) => {
570
584
  const eligibleRefs = eligibleRefCount(preparation.loopRefs, row.process, resolvedPlan.strategy.config);
@@ -608,16 +622,6 @@ function buildResultExecutionPlan(run, preparation, collected, dryRun) {
608
622
  },
609
623
  stageConfig: {
610
624
  extract: { enabled: preparation.planning.extract.wouldRun, reason: preparation.planning.extract.reason },
611
- graphExtraction: {
612
- enabled: graphExtractionEnabled,
613
- reason: !resolvedPlan.processes.graphExtraction.enabled
614
- ? "disabled"
615
- : graphExtractionEnabled
616
- ? improveProfile.processes?.graphExtraction?.fullScan === true
617
- ? "enabled for a full-corpus scan"
618
- : "enabled for refs touched by this run"
619
- : "enabled but no primary source is available",
620
- },
621
625
  memoryInference: {
622
626
  enabled: memoryInferenceEnabled,
623
627
  reason: !resolvedPlan.processes.memoryInference.enabled
@@ -825,7 +829,7 @@ async function runImproveStageSequence(run, collected, preEnsureCleanupWarnings,
825
829
  improveProfile,
826
830
  resolvedPlan,
827
831
  });
828
- let postLoop = { allWarnings: [], memoryInferenceDurationMs: 0, graphExtractionDurationMs: 0 };
832
+ let postLoop = { allWarnings: [], memoryInferenceDurationMs: 0 };
829
833
  const remainingBudget = budgetAbortController.signal.remainingBudgetMs;
830
834
  if (budgetAbortController.signal.aborted || (remainingBudget !== undefined && remainingBudget <= 0)) {
831
835
  info("[improve] post-loop maintenance skipped (wall-clock budget exhausted)");
@@ -859,8 +863,8 @@ function finalizeImproveResult(args) {
859
863
  const { options, startMs, resolvedPlan } = run;
860
864
  const { memoryCleanupPlan, strategyFilteredRefs } = collected;
861
865
  const consolidation = preparation.consolidation;
862
- const { memoryInference, graphExtraction, allWarnings, deadUrls, deadUrlCoverage, orphansPurged, proposalsExpired } = postLoop;
863
- const { memoryInferenceDurationMs, graphExtractionDurationMs } = postLoop;
866
+ const { memoryInference, allWarnings, deadUrls, deadUrlCoverage, orphansPurged, proposalsExpired } = postLoop;
867
+ const { memoryInferenceDurationMs } = postLoop;
864
868
  // The per-ref distill-skipped rows fold into a bounded aggregate before persistence (C1).
865
869
  const { actions: persistedActions, aggregate: distillSkippedAggregate } = foldDistillSkipped(finalActions);
866
870
  // This run's LLM accounting (#944): llm_usage rows carry no run id, so the
@@ -880,7 +884,6 @@ function finalizeImproveResult(args) {
880
884
  consolidation,
881
885
  ...(preparation.extract ?? []).flatMap((extract) => [extract, ...(extract.sessions ?? [])]),
882
886
  memoryInference,
883
- graphExtraction,
884
887
  triageDrain,
885
888
  ]);
886
889
  const applied = preparation.appliedCleanup;
@@ -933,11 +936,9 @@ function finalizeImproveResult(args) {
933
936
  ...(deadUrlCoverage !== undefined ? { deadUrlCoverage } : {}),
934
937
  ...(reflectsWithErrorContext > 0 ? { reflectsWithErrorContext } : {}),
935
938
  ...(memoryInference ? { memoryInference } : {}),
936
- ...(graphExtraction ? { graphExtraction } : {}),
937
939
  // Top-level phase durations feed health's wall-time buckets; a phase that
938
940
  // did not run is omitted, not zero.
939
941
  ...(memoryInferenceDurationMs > 0 ? { memoryInferenceDurationMs } : {}),
940
- ...(graphExtractionDurationMs > 0 ? { graphExtractionDurationMs } : {}),
941
942
  ...(ensureIndexDurationMs !== undefined ? { ensureIndexDurationMs } : {}),
942
943
  ...(orphansPurged !== undefined ? { orphansPurged } : {}),
943
944
  ...(proposalsExpired !== undefined && proposalsExpired > 0 ? { proposalsExpired } : {}),
@@ -960,7 +961,6 @@ function finalizeImproveResult(args) {
960
961
  };
961
962
  emitImproveCompletedEvent(result, {
962
963
  memoryInferenceDurationMs,
963
- graphExtractionDurationMs,
964
964
  totalDurationMs: Date.now() - startMs,
965
965
  warningCount: allWarnings.length,
966
966
  orphansPurged: orphansPurged ?? 0,
@@ -989,7 +989,6 @@ const ACTION_COUNTER = {
989
989
  "distill-skipped": "distillSkippedActions",
990
990
  "memory-prune": "memoryPruneActions",
991
991
  "memory-inference": "memoryInferenceActions",
992
- "graph-extraction": "graphExtractionActions",
993
992
  error: "errorActions",
994
993
  "reflect-failed": "reflectFailedActions",
995
994
  "reflect-cooldown": "reflectCooldownActions",
@@ -1011,7 +1010,6 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
1011
1010
  counts.distillSkippedActions = (counts.distillSkippedActions ?? 0) + distillSkippedTotal;
1012
1011
  classCounts.skipped += distillSkippedTotal;
1013
1012
  const cleanup = result.memoryCleanup;
1014
- const quality = result.graphExtraction?.quality;
1015
1013
  appendEvent({
1016
1014
  eventType: "improve_completed",
1017
1015
  ref: result.scope.mode === "ref"
@@ -1043,21 +1041,12 @@ function emitImproveCompletedEvent(result, durations, eventsCtx) {
1043
1041
  consolidationDurationMs: result.consolidation?.durationMs ?? 0,
1044
1042
  memoryInferenceWrites: result.memoryInference?.writtenFacts ?? 0,
1045
1043
  memoryInferenceDurationMs: durations.memoryInferenceDurationMs,
1046
- graphExtractionExtractedFiles: quality?.extractedFiles ?? 0,
1047
- graphExtractionDurationMs: durations.graphExtractionDurationMs,
1048
1044
  proactiveSelected: result.proactiveMaintenance?.selected ?? 0,
1049
1045
  proactiveDueTotal: result.proactiveMaintenance?.dueTotal ?? 0,
1050
1046
  proactiveNeverReflected: result.proactiveMaintenance?.neverReflected ?? 0,
1051
1047
  durationMs: durations.totalDurationMs,
1052
1048
  warningCount: durations.warningCount,
1053
1049
  orphansPurged: durations.orphansPurged,
1054
- ...(quality
1055
- ? {
1056
- graphCoverage: quality.extractionCoverage,
1057
- graphDensity: quality.density,
1058
- graphEntities: quality.entityCount,
1059
- }
1060
- : {}),
1061
1050
  },
1062
1051
  }, eventsCtx);
1063
1052
  }
@@ -9,9 +9,9 @@
9
9
  import fs from "node:fs";
10
10
  import { getStateDbPath, withImmediateTransaction, withStateDb } from "../../core/state-db.js";
11
11
  import { warn } from "../../core/warn.js";
12
- import { isLedgerBlocked, listImproveLedgerRows, recordImproveLedger, } from "../../storage/repositories/improve-ledger-repository.js";
12
+ import { isLedgerBlocked, listImproveLedgerRows, PAIR_PASS_LEDGER_SOURCE, recordImproveLedger, } from "../../storage/repositories/improve-ledger-repository.js";
13
13
  import { openSqliteReadSnapshot } from "../../storage/sqlite-read-snapshot.js";
14
- export { isLedgerBlocked };
14
+ export { isLedgerBlocked, PAIR_PASS_LEDGER_SOURCE };
15
15
  /** An improve candidate's durable state key: its index item_ref, else its conceptId. */
16
16
  export function stateKey(ref, itemRef) {
17
17
  return itemRef ?? ref;
@@ -69,6 +69,7 @@ export function recordLedgerAttempt(access, inputs) {
69
69
  at,
70
70
  ...(input.proposalId !== undefined ? { proposalId: input.proposalId } : {}),
71
71
  ...(input.detail !== undefined ? { detail: input.detail } : {}),
72
+ ...(input.contentHash !== undefined ? { contentHash: input.contentHash } : {}),
72
73
  });
73
74
  }
74
75
  }));
@@ -3,7 +3,6 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /** The improve loop (reflect + distill per ref), the post-loop checks and the maintenance passes. */
5
5
  import fs from "node:fs";
6
- import path from "node:path";
7
6
  import { parseRefInput } from "../../core/asset/resolve-ref.js";
8
7
  import { daysToMs } from "../../core/common.js";
9
8
  import { loadConfig } from "../../core/config/config.js";
@@ -13,12 +12,9 @@ import { openLogsDatabase, purgeOldTaskLogs } from "../../core/logs-db.js";
13
12
  import { getDbPath, getTaskLogDir } from "../../core/paths.js";
14
13
  import { withStateDb } from "../../core/state-db.js";
15
14
  import { info } from "../../core/warn.js";
16
- import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
17
15
  import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
18
- import { deriveWritableBundleIds } from "../../indexer/installations.js";
19
16
  import { collectPendingMemories, runMemoryInferencePass, } from "../../indexer/passes/memory-inference.js";
20
17
  import { resolveSourceEntries } from "../../indexer/search/search-source.js";
21
- import { isProcessEnabled } from "../../llm/feature-gate.js";
22
18
  import { purgeOldEvents } from "../../storage/repositories/events-repository.js";
23
19
  import { purgeOldImproveRuns } from "../../storage/repositories/improve-runs-repository.js";
24
20
  import { closeDatabase, openIndexDatabase } from "../../storage/repositories/index-connection.js";
@@ -29,7 +25,7 @@ import { readFreelistInfo, STATE_DB_VACUUMED_EVENT, vacuumIfReclaimable } from "
29
25
  import { purgeOldTaskLogFiles } from "../../tasks/run/task-log.js";
30
26
  import { expireStaleProposals, purgeOrphanProposals } from "../proposal/repository.js";
31
27
  import { checkDeadUrls } from "../url-checker.js";
32
- import { findAssetFilePath, isDistillCandidateRef } from "./eligibility.js";
28
+ import { isDistillCandidateRef } from "./eligibility.js";
33
29
  import { shouldSkipRef } from "./improve-strategies.js";
34
30
  import { recordLedgerAttempt, stateKey, stripBundle } from "./ledger.js";
35
31
  import { pushRecentError } from "./preparation.js";
@@ -327,23 +323,21 @@ export async function runImprovePostLoopStage(args) {
327
323
  deadUrls,
328
324
  ...(deadUrlCoverage ? { deadUrlCoverage } : {}),
329
325
  ...(maintenance.memoryInference ? { memoryInference: maintenance.memoryInference } : {}),
330
- ...(maintenance.graphExtraction ? { graphExtraction: maintenance.graphExtraction } : {}),
331
326
  ...(maintenance.actions && maintenance.actions.length > 0 ? { maintenanceActions: maintenance.actions } : {}),
332
327
  memoryInferenceDurationMs: maintenance.memoryInferenceDurationMs,
333
- graphExtractionDurationMs: maintenance.graphExtractionDurationMs,
334
328
  orphansPurged: maintenance.orphansPurged,
335
329
  proposalsExpired: maintenance.proposalsExpired,
336
330
  };
337
331
  }
338
332
  /**
339
- * Memory inference → index what it wrote → graph extraction → proposal hygiene
340
- * (orphan purge, expiration) → orphan-state GC → retention purges. Warnings go
341
- * to `allWarnings`.
333
+ * Memory inference → index what it wrote → proposal hygiene (orphan purge,
334
+ * expiration) → orphan-state GC → retention purges. Warnings go to
335
+ * `allWarnings`.
342
336
  */
343
337
  export async function runImproveMaintenancePasses(args) {
344
338
  const { options, primaryStashDir, allWarnings, budgetSignal, eventsCtx } = args;
345
339
  if (!primaryStashDir || budgetSignal?.aborted)
346
- return { memoryInferenceDurationMs: 0, graphExtractionDurationMs: 0 };
340
+ return { memoryInferenceDurationMs: 0 };
347
341
  const config = options.config ?? loadConfig();
348
342
  const ctx = {
349
343
  config,
@@ -354,7 +348,6 @@ export async function runImproveMaintenancePasses(args) {
354
348
  improveProfile: args.improveProfile,
355
349
  resolvedPlan: args.resolvedPlan,
356
350
  memoryInferenceFn: options.memoryInferenceFn ?? runMemoryInferencePass,
357
- graphExtractionFn: options.graphExtractionFn ?? runGraphExtractionPass,
358
351
  };
359
352
  const openIndexDb = () => openIndexDatabase(getDbPath());
360
353
  const dbCell = {};
@@ -386,20 +379,14 @@ export async function runImproveMaintenancePasses(args) {
386
379
  allWarnings.push(`indexing after memory inference failed: ${errMessage(err)}`);
387
380
  }
388
381
  }
389
- const graph = await runGraphExtractionMaintenancePass(ctx, dbCell, args);
390
- if (graph.action)
391
- actions.push(graph.action);
392
- allWarnings.push(...graph.warnings);
393
382
  const hygiene = runProposalHygienePass(ctx);
394
383
  allWarnings.push(...hygiene.warnings);
395
384
  allWarnings.push(...runOrphanStateGcPass(ctx, dbCell).warnings);
396
385
  allWarnings.push(...runRetentionPurgePass(ctx).warnings);
397
386
  return {
398
387
  ...(inference.memoryInference ? { memoryInference: inference.memoryInference } : {}),
399
- ...(graph.graphExtraction ? { graphExtraction: graph.graphExtraction } : {}),
400
388
  ...(actions.length > 0 ? { actions } : {}),
401
389
  memoryInferenceDurationMs: inference.durationMs,
402
- graphExtractionDurationMs: graph.durationMs,
403
390
  orphansPurged: hygiene.orphansPurged,
404
391
  proposalsExpired: hygiene.proposalsExpired,
405
392
  };
@@ -467,73 +454,6 @@ export async function runMemoryInferenceMaintenancePass(ctx, dbCell, memoryRefsF
467
454
  warnings: pass.warnings,
468
455
  };
469
456
  }
470
- /**
471
- * Graph extraction over the files this run touched, or the whole corpus when
472
- * the profile sets `graphExtraction.fullScan` (the `graph-refresh` strategy).
473
- * With nothing touched the pass still runs and extracts nothing.
474
- */
475
- export async function runGraphExtractionMaintenancePass(ctx, dbCell, args) {
476
- const { config, sources, primaryStashDir, resolvedPlan } = ctx;
477
- const settings = ctx.improveProfile?.processes?.graphExtraction;
478
- if (settings?.enabled === false) {
479
- info("[improve] graph extraction skipped (disabled by improve profile)");
480
- return { durationMs: 0, warnings: [] };
481
- }
482
- if (sources.length === 0)
483
- return { durationMs: 0, warnings: [] };
484
- // `index.graph.enabled: false` turns graph extraction off everywhere,
485
- // whatever the strategy enables.
486
- if (!isProcessEnabled("index", "graph_extraction", config)) {
487
- info("[improve] graph extraction skipped (index.graph.enabled is false)");
488
- return { durationMs: 0, warnings: [] };
489
- }
490
- const fullScan = settings?.fullScan === true;
491
- info(`[improve] graph extraction starting${fullScan ? " (full-corpus scan)" : ""}`);
492
- const pass = await timedLlmPass("graph extraction", async () => {
493
- let candidatePaths;
494
- if (!fullScan) {
495
- candidatePaths = new Set();
496
- const touched = new Set([...args.actionableRefs.map((r) => r.ref), ...args.memoryRefsForInference]);
497
- if (primaryStashDir && touched.size > 0) {
498
- const writableBundleIds = deriveWritableBundleIds(resolveSourceEntries(primaryStashDir));
499
- const resolved = await Promise.all([...touched].map((ref) => findAssetFilePath(ref, primaryStashDir, writableBundleIds).catch(() => null)));
500
- for (const p of resolved)
501
- if (typeof p === "string" && p.length > 0)
502
- candidatePaths.add(p);
503
- }
504
- }
505
- return attributeStage(resolvedPlan, "graphExtraction", () => ctx.graphExtractionFn({
506
- config,
507
- ...(resolvedPlan ? { llmRunner: resolvedPlan.processes.graphExtraction.runner } : {}),
508
- sources,
509
- signal: ctx.budgetSignal,
510
- db: dbCell.current,
511
- reEnrich: false,
512
- onProgress: (event) => {
513
- const current = event.currentPath ? ` ${path.basename(event.currentPath)}` : "";
514
- info(`[improve] graph extraction ${event.processed}/${event.total}${current} (extracted ${event.extracted}, entities ${event.totalEntities}, relations ${event.totalRelations})`);
515
- },
516
- options: {
517
- candidatePaths,
518
- // Only what the strategy sets: the pass falls back to index.graph, then its defaults (GR-D15).
519
- ...(settings?.includeTypes ? { includeTypes: settings.includeTypes } : {}),
520
- ...(settings?.batchSize != null ? { batchSize: settings.batchSize } : {}),
521
- ...(settings?.topN != null ? { topN: settings.topN } : {}),
522
- ...(settings?.maxChunksPerAsset != null ? { maxChunksPerAsset: settings.maxChunksPerAsset } : {}),
523
- },
524
- }));
525
- });
526
- const graphExtraction = pass.result;
527
- if (!graphExtraction)
528
- return { durationMs: pass.durationMs, warnings: pass.warnings };
529
- info(`[improve] graph extraction complete (${graphExtraction.quality.extractedFiles} files, ${graphExtraction.quality.entityCount} entities, ${graphExtraction.quality.relationCount} relations)`);
530
- return {
531
- graphExtraction,
532
- durationMs: pass.durationMs,
533
- action: { ref: "graph/_artifact", mode: "graph-extraction", result: graphExtraction },
534
- warnings: pass.warnings,
535
- };
536
- }
537
457
  /**
538
458
  * Reject pending proposals whose target no longer exists, then expire pending
539
459
  * proposals past the retention window; each emits a roll-up event.
@@ -6,7 +6,9 @@
6
6
  * frontmatter and demote its `beliefState`, metadata only. Idempotent (a
7
7
  * present edge AND demotion is a no-op; an edge without its demotion is
8
8
  * repaired) and never weakens a stronger demotion — severity is
9
- * superseded > contradicted > archived, per the ranker's `beliefStateBoost`.
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.)
10
12
  * The SCC resolver in memory-improve.ts is a state-transition writer (it
11
13
  * replaces and clears edges) and deliberately does not use this (#885).
12
14
  */