akm-cli 0.9.17-alpha.7 → 0.9.17-alpha.9

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 (116) hide show
  1. package/CHANGELOG.md +473 -0
  2. package/STABILITY.md +9 -8
  3. package/dist/akm +55 -22
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  15. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  16. package/dist/assets/templates/html/health.html +3 -5
  17. package/dist/cli/retired-commands.js +1 -1
  18. package/dist/commands/health/archive-usage.js +98 -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 +0 -25
  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 -84
  35. package/dist/commands/improve/memory/memory-belief.js +3 -1
  36. package/dist/commands/improve/memory/memory-improve.js +269 -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/curate.js +40 -13
  50. package/dist/commands/read/knowledge.js +3 -2
  51. package/dist/commands/read/show.js +55 -16
  52. package/dist/commands/sources/info.js +3 -0
  53. package/dist/commands/sources/stash-cli.js +2 -2
  54. package/dist/core/adapter/adapters/akm-adapter.js +2 -0
  55. package/dist/core/adapter/adapters/akm-metadata.js +31 -0
  56. package/dist/core/bundle-rename.js +1 -7
  57. package/dist/core/config/config-schema.js +8 -1
  58. package/dist/core/config/config.js +23 -48
  59. package/dist/core/config/engine-semantics.js +0 -2
  60. package/dist/core/config/schema/improve-processes.js +17 -42
  61. package/dist/core/config/schema/index-config.js +5 -25
  62. package/dist/core/file-change.js +13 -5
  63. package/dist/core/improve-result.js +16 -5
  64. package/dist/core/improve-types.js +0 -1
  65. package/dist/core/loopback.js +7 -12
  66. package/dist/core/parse.js +13 -16
  67. package/dist/core/state/migrations.js +15 -0
  68. package/dist/core/time.js +0 -20
  69. package/dist/indexer/db/llm-cache.js +2 -2
  70. package/dist/indexer/ensure-index.js +2 -2
  71. package/dist/indexer/index-written-assets.js +2 -3
  72. package/dist/indexer/indexer.js +18 -418
  73. package/dist/indexer/links/declared-links.js +90 -0
  74. package/dist/indexer/passes/metadata.js +0 -19
  75. package/dist/indexer/scan/doc-to-entry.js +1 -0
  76. package/dist/indexer/walk/walker.js +3 -4
  77. package/dist/llm/client.js +8 -10
  78. package/dist/llm/embedders/remote.js +1 -2
  79. package/dist/llm/feature-gate.js +0 -5
  80. package/dist/output/shapes/helpers.js +23 -4
  81. package/dist/output/text/command-format.js +0 -8
  82. package/dist/output/text/proposal-format.js +47 -1
  83. package/dist/output/text/show-format.js +13 -17
  84. package/dist/scripts/akm-migrate-node.js +2754 -2836
  85. package/dist/scripts/akm-migrate.js +2754 -2836
  86. package/dist/setup/steps/connection.js +5 -6
  87. package/dist/setup/steps/platforms.js +2 -2
  88. package/dist/sources/providers/git-stash.js +55 -4
  89. package/dist/storage/repositories/improve-ledger-repository.js +48 -7
  90. package/dist/storage/repositories/index-entries-repository.js +16 -13
  91. package/dist/storage/repositories/index-entry-schema.js +22 -3
  92. package/dist/storage/repositories/index-links-repository.js +143 -0
  93. package/dist/storage/repositories/index-llm-cache-repository.js +7 -26
  94. package/dist/storage/repositories/index-schema.js +82 -104
  95. package/dist/storage/repositories/proposals-repository.js +61 -0
  96. package/dist/storage/repositories/salience-repository.js +1 -19
  97. package/dist/tasks/source/task-to-v4.js +462 -74
  98. package/docs/migration/release-notes/0.9.17.md +7 -5
  99. package/docs/reference/cli.md +33 -21
  100. package/docs/reference/configuration.md +21 -12
  101. package/docs/reference/data-and-telemetry.md +0 -1
  102. package/package.json +1 -1
  103. package/schemas/akm-config.json +0 -342
  104. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  105. package/dist/assets/prompts/contradiction-judge.md +0 -33
  106. package/dist/assets/prompts/graph-extract-system.md +0 -1
  107. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  108. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  109. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  110. package/dist/indexer/db/graph-db.js +0 -431
  111. package/dist/indexer/graph/graph-extraction.js +0 -807
  112. package/dist/indexer/graph/graph-related.js +0 -131
  113. package/dist/indexer/graph/graph-types.js +0 -4
  114. package/dist/llm/graph-extract.js +0 -903
  115. package/dist/llm/metadata-enhance.js +0 -95
  116. package/dist/tasks/source/task-to-v3.js +0 -453
@@ -62,7 +62,7 @@ export function computeSalience(inputs) {
62
62
  const rankScore = clamp01((we * encoding + wo * outcome + wr * retrieval) * sizePenalty);
63
63
  return { encoding, outcome, retrieval, rankScore, encodingSource };
64
64
  }
65
- export { getAllRankScores, getAssetSalience, getConsecutiveNoOps, recordNoOp, resetConsecutiveNoOps, upsertAssetSalience, } from "../../storage/repositories/salience-repository.js";
65
+ export { getAssetSalience, getConsecutiveNoOps, recordNoOp, resetConsecutiveNoOps, upsertAssetSalience, } from "../../storage/repositories/salience-repository.js";
66
66
  /** Whether a stored row's encoding salience is content-derived (unknown provenance is not). */
67
67
  export function isContentEncodingRow(row) {
68
68
  return row.encoding_source === "content";
@@ -73,19 +73,6 @@ export function isContentEncodingRow(row) {
73
73
  */
74
74
  export const SALIENCE_NO_OP_DAMPEN_THRESHOLD = 3;
75
75
  export const SALIENCE_NO_OP_DAMPEN_FACTOR = 0.5;
76
- /** Forgetting safety: compare 1-indexed rank positions between two rankings. */
77
- export function buildRankChangeReport(oldRanks, newRanks, oldTopN = 200, forgettingThreshold = 500) {
78
- const allChanges = [];
79
- for (const [ref, oldRank] of oldRanks) {
80
- const newRank = newRanks.get(ref);
81
- if (newRank !== undefined)
82
- allChanges.push({ ref, oldRank, newRank, rankDelta: newRank - oldRank });
83
- }
84
- const forgettingCandidates = allChanges
85
- .filter((c) => c.oldRank <= oldTopN && c.newRank > forgettingThreshold)
86
- .sort((a, b) => b.rankDelta - a.rankDelta);
87
- return { forgettingCandidates, allChanges };
88
- }
89
76
  /** `ref → last retrieval (ms)` from the index's utility scores; absent means never retrieved. */
90
77
  export function getLastUseMsByRef(indexDb, candidates) {
91
78
  const result = new Map();
@@ -90,7 +90,6 @@ const STAGE_LABELS = {
90
90
  consolidate: "consolidate",
91
91
  extract: "session-extraction",
92
92
  memoryInference: "memory-inference",
93
- graphExtraction: "graph-extraction",
94
93
  validation: "validation",
95
94
  };
96
95
  export function attributeStage(plan, process, fn) {
@@ -204,12 +204,14 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
204
204
  if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
205
205
  return true;
206
206
  }
207
- // #884: a memory pruned by `analyzeMemoryCleanup` was ARCHIVED, not deleted —
208
- // its bytes and identity live on under `.akm/memory-cleanup/archive`. Inbound
209
- // belief edges to it are satisfied, not dangling, so resolve the tombstone
210
- // rather than reporting `missing-ref`. Checked only after every live location
211
- // misses: a tombstone must never shadow a real file, and the scan then costs
212
- // one directory read per root instead of one per ref.
207
+ // #884: an asset `analyzeMemoryCleanup` pruned, or (alpha.9) a consolidate
208
+ // pair-pass `retire` proposal or a promotion's source memory retired, was
209
+ // ARCHIVED, not deleted — its bytes and identity live on under
210
+ // `.akm/memory-cleanup/archive`. Inbound refs to it are satisfied, not
211
+ // dangling, so resolve the tombstone rather than reporting `missing-ref`.
212
+ // Checked only after every live location misses: a tombstone must never
213
+ // shadow a real file, and the scan then costs one directory read per root
214
+ // instead of one per ref.
213
215
  //
214
216
  // Existence ONLY. `resolveRefPathInStash` deliberately does NOT consult the
215
217
  // archive: it hands back a path callers MUTATE (SPEC-5 `--supersedes`
@@ -231,13 +233,19 @@ function refPathCandidates(refType, typeDir, refName) {
231
233
  return [...candidates, assetPathForName(refType, typeDir, `${refName}${DERIVED_SUFFIX}`)];
232
234
  }
233
235
  /**
234
- * True when `(refType, refName)` names a memory that prune archived in any
235
- * root. Mirrors `resolveRefPathInStash`'s candidate set so a ref that resolved
236
- * through the `.derived.md` child (#882) still resolves once archived.
236
+ * True when `(refType, refName)` names an asset the cleanup archive holds a
237
+ * tombstone for, in any root. Mirrors `resolveRefPathInStash`'s candidate set
238
+ * so a memory ref that resolved through the `.derived.md` child (#882) still
239
+ * resolves once archived.
240
+ *
241
+ * Originally memory-only (#884: only `.derived` memories were ever pruned).
242
+ * 0.9.17-alpha.9 generalized `archiveCleanupCandidate` to any memory,
243
+ * knowledge or lesson file (a consolidate pair-pass `retire` proposal, or an
244
+ * accepted promotion's source memory), so this must check every type, not
245
+ * just `memory` — otherwise an xref to a retired knowledge or lesson asset
246
+ * reports `missing-ref` even though it resolves fine through the tombstone.
237
247
  */
238
248
  function memoryArchiveHasRef(refType, refName, stashRoots) {
239
- if (refType !== "memory")
240
- return false; // only memories are ever archived
241
249
  const typeDir = stashDirFor(refType);
242
250
  if (typeDir === undefined)
243
251
  return false;
@@ -29,7 +29,7 @@ import { buildExecution, resolveExecution } from "../../integrations/agent/execu
29
29
  import { assertRunnerCredentials, runExecution, } from "../../integrations/agent/runner-dispatch.js";
30
30
  import { errMessage, noticeSet } from "../improve/stage.js";
31
31
  import { akmProposalAccept, akmProposalReject } from "./proposal.js";
32
- import { STALE_TARGET_GATE_REASON } from "./proposal-types.js";
32
+ import { isRetireProposal, STALE_TARGET_GATE_REASON } from "./proposal-types.js";
33
33
  import { listProposals, listProposalsReadOnly, preflightProposalPromotion, proposalContent, proposalContentHash, readFreshProposalTarget, recordGateDecision, } from "./repository.js";
34
34
  /** The gate label on every decision the drain records. */
35
35
  const DRAIN_GATE = "triage";
@@ -305,6 +305,13 @@ export async function drainProposals(opts, promoteFn = akmProposalAccept, reject
305
305
  const accepts = [];
306
306
  const empties = [];
307
307
  for (const proposal of pending) {
308
+ // A consolidate pair-pass `retire` proposal is never auto-decided here,
309
+ // whatever `applyMode` says (alpha.9 brief §A "Review"; spec §25.6):
310
+ // untouched, still pending, waiting for a direct `akm proposal accept`.
311
+ // Checked before isEmptyDiff, which reads proposalContent() and has
312
+ // nothing meaningful to read on a delete-primary change anyway.
313
+ if (isRetireProposal(proposal))
314
+ continue;
308
315
  const decision = proposal.gateDecision;
309
316
  // Another gate's rejection stands; a human-review deferral from the distill
310
317
  // quality gate is left for that human.
@@ -89,6 +89,10 @@ const proposalListCommand = defineJsonCommand({
89
89
  },
90
90
  ref: { type: "string", description: "Filter by asset ref ([bundle//]conceptId, e.g. knowledge/guide.md)" },
91
91
  type: { type: "string", description: "Filter by asset type" },
92
+ generator: {
93
+ type: "string",
94
+ description: "Filter by generator/source (e.g. reflect, distill, consolidate-pair) — same value accept/reject --generator take.",
95
+ },
92
96
  },
93
97
  run({ args }) {
94
98
  const status = parseProposalStatus(args.status);
@@ -97,6 +101,7 @@ const proposalListCommand = defineJsonCommand({
97
101
  status,
98
102
  ref: args.ref,
99
103
  type: args.type,
104
+ generator: args.generator,
100
105
  includeArchive: status === "accepted" || status === "rejected" || status === "reverted",
101
106
  });
102
107
  output("proposal-list", result);
@@ -149,7 +154,7 @@ const proposalAcceptCommand = defineJsonCommand({
149
154
  return;
150
155
  }
151
156
  const { maxDiffLines, olderThanMs } = parseBulkFilterFlags(args);
152
- const { count, results } = await bulkAdjudicateProposals({
157
+ const { count, results, skippedForContinuityRisk } = await bulkAdjudicateProposals({
153
158
  action: "accept",
154
159
  generator,
155
160
  maxDiffLines,
@@ -158,7 +163,16 @@ const proposalAcceptCommand = defineJsonCommand({
158
163
  queue: args.queue,
159
164
  target: args.target,
160
165
  });
161
- output("proposal-accept-batch", { accepted: count, results, dryRun: args["dry-run"] });
166
+ output("proposal-accept-batch", {
167
+ accepted: count,
168
+ results,
169
+ // S4: how many otherwise-matching proposals were skipped specifically
170
+ // because they carry retirement.continuityRisk — visible under
171
+ // --dry-run and on a real run alike, distinct from an ordinary
172
+ // --max-diff-lines/--older-than miss.
173
+ skippedForContinuityRisk,
174
+ dryRun: args["dry-run"],
175
+ });
162
176
  return;
163
177
  }
164
178
  if (!args.id) {
@@ -9,6 +9,8 @@ export const PROPOSAL_SOURCES = [
9
9
  "reflect",
10
10
  "distill",
11
11
  "consolidate",
12
+ /** The consolidate pair pass's own retire proposals (alpha.9, S6) — kept apart from `consolidate`'s promotions so a bulk `accept --generator consolidate` never sweeps a retirement, and the reverse. */
13
+ "consolidate-pair",
12
14
  "extract",
13
15
  "improve",
14
16
  "feedback",
@@ -23,6 +25,7 @@ export const AUTOMATED_PROPOSAL_SOURCES = [
23
25
  "reflect",
24
26
  "distill",
25
27
  "consolidate",
28
+ "consolidate-pair",
26
29
  "extract",
27
30
  "improve",
28
31
  "schema-repair",
@@ -33,6 +36,10 @@ export function isValidProposalSource(source) {
33
36
  export function isAutomatedProposalSource(source) {
34
37
  return AUTOMATED_PROPOSAL_SOURCES.includes(source);
35
38
  }
39
+ /** A pending or accepted proposal whose primary change deletes its target (a consolidate retire proposal, alpha.9). */
40
+ export function isRetireProposal(proposal) {
41
+ return proposal.changes[0]?.op === "delete";
42
+ }
36
43
  /** A promote refused because the target changed after mint (STALE, R20) — not a merit judgement. */
37
44
  export const STALE_TARGET_GATE_REASON = "stale-target";
38
45
  export const EXPIRED_GATE_REASON = "expired";
@@ -15,6 +15,7 @@ import { resolveStashDir } from "../../core/common.js";
15
15
  import { loadConfig } from "../../core/config/config.js";
16
16
  import { resolveWriteTarget } from "../../core/write-source.js";
17
17
  import { withAssetMutationLease } from "../../indexer/index-writer-lock.js";
18
+ import { isRetireProposal } from "./proposal-types.js";
18
19
  import { diffProposal, listProposals, preflightProposalPromotion, promoteProposal, proposalContent, rejectProposalDurably, resolveProposalId, revertProposal, } from "./repository.js";
19
20
  import { validateProposal } from "./validators/proposals.js";
20
21
  // ── Shared helpers ──────────────────────────────────────────────────────────
@@ -55,7 +56,7 @@ export function akmProposalList(options = {}) {
55
56
  status: options.status,
56
57
  ref: options.ref,
57
58
  type: options.type,
58
- });
59
+ }).filter((p) => options.generator === undefined || p.source === options.generator);
59
60
  return { schemaVersion: 1, totalCount: proposals.length, proposals };
60
61
  }
61
62
  export function akmProposalShow(options) {
@@ -64,9 +65,13 @@ export function akmProposalShow(options) {
64
65
  const proposal = resolveProposalId(stash, options.id);
65
66
  let validation = validateProposal(proposal);
66
67
  // An explicit stashDir without config is an in-process storage test seam; it
67
- // has no authenticated write-target context to preflight against.
68
+ // has no authenticated write-target context to preflight against. A retire
69
+ // proposal writes no content — preflightProposalPromotion's stamp/lint
70
+ // machinery is create/update-shaped and has nothing meaningful to check
71
+ // here; `diff` already shows the body being retired.
68
72
  if (validation.ok &&
69
73
  proposal.status === "pending" &&
74
+ !isRetireProposal(proposal) &&
70
75
  (options.config !== undefined || options.stashDir === undefined)) {
71
76
  try {
72
77
  preflightProposalPromotion(options.config ?? loadConfig(), proposal, { queueTarget: queue.target });
@@ -161,6 +166,16 @@ export async function akmProposalRevert(options) {
161
166
  proposal: result.proposal,
162
167
  };
163
168
  }
169
+ /** The retired file's current line count for `--max-diff-lines` (S6); unresolvable is never size-filtered here — the real accept/reject attempt fails cleanly on a genuinely stale target. */
170
+ function retiredTargetLineCount(stashDir, config, proposal, queueTarget) {
171
+ try {
172
+ const existing = diffProposal(stashDir, config, proposal.id, { queueTarget }).existing;
173
+ return existing === null ? 0 : existing.split("\n").length;
174
+ }
175
+ catch {
176
+ return 0;
177
+ }
178
+ }
164
179
  /**
165
180
  * Bulk accept/reject every pending proposal from one generator, applying the
166
181
  * shared `--max-diff-lines` / `--older-than` filters. Consolidates the two
@@ -170,12 +185,21 @@ export async function akmProposalRevert(options) {
170
185
  */
171
186
  export async function bulkAdjudicateProposals(options) {
172
187
  const config = options.config ?? loadConfig();
173
- const { stashDir } = resolveProposalQueue(options.stashDir, options.queue, config);
174
- const pending = listProposals(stashDir, { status: "pending" }).filter((p) => {
188
+ const { stashDir, target: queueTarget } = resolveProposalQueue(options.stashDir, options.queue, config);
189
+ // Every filter EXCEPT the continuityRisk exclusion below — matched against
190
+ // separately so its own count (S4) can be reported apart from an ordinary
191
+ // --max-diff-lines/--older-than miss.
192
+ const matched = listProposals(stashDir, { status: "pending" }).filter((p) => {
175
193
  if (p.source !== options.generator)
176
194
  return false;
177
195
  if (options.maxDiffLines !== undefined) {
178
- const lines = proposalContent(p).split("\n").length;
196
+ // S6: a retire proposal's own payload is empty (it deletes its
197
+ // target) — proposalContent() would always read as 1 line, so
198
+ // --max-diff-lines could never filter one out. Count the retired
199
+ // file's own current line count instead.
200
+ const lines = isRetireProposal(p)
201
+ ? retiredTargetLineCount(stashDir, config, p, queueTarget)
202
+ : proposalContent(p).split("\n").length;
179
203
  if (lines > options.maxDiffLines)
180
204
  return false;
181
205
  }
@@ -186,6 +210,13 @@ export async function bulkAdjudicateProposals(options) {
186
210
  }
187
211
  return true;
188
212
  });
213
+ // Item 1 (continuity check, alpha.9 plan §5.4, rule R3): a retire proposal
214
+ // the check flagged is never bulk-accepted, by generator or any other
215
+ // sweep — only a person accepting it by id can. Bulk reject is unaffected:
216
+ // declining a risky proposal is never the unsafe direction.
217
+ const isContinuityExcluded = (p) => options.action === "accept" && Boolean(p.retirement?.continuityRisk);
218
+ const skippedForContinuityRisk = matched.filter(isContinuityExcluded).length;
219
+ const pending = matched.filter((p) => !isContinuityExcluded(p));
189
220
  const results = [];
190
221
  for (const proposal of pending) {
191
222
  if (options.dryRun) {
@@ -198,5 +229,5 @@ export async function bulkAdjudicateProposals(options) {
198
229
  results.push(await akmProposalReject({ stashDir, id: proposal.id, queue: options.queue, reason: options.reason, config }));
199
230
  }
200
231
  }
201
- return { count: results.length, results };
232
+ return { count: results.length, results, skippedForContinuityRisk };
202
233
  }