akm-cli 0.9.0-beta.5 → 0.9.0-beta.51

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 (221) hide show
  1. package/CHANGELOG.md +711 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/profiles/default.json +9 -4
  6. package/dist/assets/profiles/frequent.json +1 -1
  7. package/dist/assets/profiles/memory-focus.json +1 -1
  8. package/dist/assets/profiles/quick.json +1 -1
  9. package/dist/assets/profiles/synthesize.json +15 -0
  10. package/dist/assets/profiles/thorough.json +1 -1
  11. package/dist/assets/prompts/consolidate-system.md +23 -0
  12. package/dist/assets/prompts/contradiction-judge.md +33 -0
  13. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  14. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  15. package/dist/assets/prompts/extract-session.md +6 -2
  16. package/dist/assets/prompts/graph-extract-system.md +1 -0
  17. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  18. package/dist/assets/prompts/memory-infer-system.md +1 -0
  19. package/dist/assets/prompts/memory-infer-user.md +5 -0
  20. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  21. package/dist/assets/prompts/procedural-system.md +44 -0
  22. package/dist/assets/prompts/recombine-system.md +40 -0
  23. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  24. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  25. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  34. package/dist/assets/templates/html/health.html +281 -111
  35. package/dist/assets/wiki/ingest-workflow-template.md +38 -10
  36. package/dist/cli/parse-args.js +46 -1
  37. package/dist/cli/shared.js +28 -0
  38. package/dist/cli.js +27 -11
  39. package/dist/commands/agent/agent-dispatch.js +2 -2
  40. package/dist/commands/agent/agent-support.js +0 -7
  41. package/dist/commands/agent/contribute-cli.js +17 -4
  42. package/dist/commands/config-cli.js +18 -2
  43. package/dist/commands/env/child-env.js +47 -0
  44. package/dist/commands/env/env-cli.js +33 -26
  45. package/dist/commands/env/secret-cli.js +36 -22
  46. package/dist/commands/feedback-cli.js +15 -6
  47. package/dist/commands/graph/graph-cli.js +5 -13
  48. package/dist/commands/graph/graph.js +76 -72
  49. package/dist/commands/health/checks.js +49 -1
  50. package/dist/commands/health/html-report.js +422 -80
  51. package/dist/commands/health.js +386 -9
  52. package/dist/commands/improve/calibration.js +161 -0
  53. package/dist/commands/improve/consolidate/chunking.js +141 -0
  54. package/dist/commands/improve/consolidate/eligibility.js +81 -0
  55. package/dist/commands/improve/consolidate/merge.js +145 -0
  56. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  57. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  58. package/dist/commands/improve/consolidate.js +635 -660
  59. package/dist/commands/improve/dedup.js +482 -0
  60. package/dist/commands/improve/distill.js +159 -69
  61. package/dist/commands/improve/eligibility.js +434 -0
  62. package/dist/commands/improve/encoding-salience.js +205 -0
  63. package/dist/commands/improve/extract-cli.js +124 -2
  64. package/dist/commands/improve/extract-prompt.js +39 -2
  65. package/dist/commands/improve/extract-watch.js +140 -0
  66. package/dist/commands/improve/extract.js +389 -40
  67. package/dist/commands/improve/feedback-valence.js +54 -0
  68. package/dist/commands/improve/homeostatic.js +467 -0
  69. package/dist/commands/improve/improve-auto-accept.js +138 -7
  70. package/dist/commands/improve/improve-cli.js +36 -61
  71. package/dist/commands/improve/improve-profiles.js +14 -0
  72. package/dist/commands/improve/improve-result-file.js +14 -25
  73. package/dist/commands/improve/improve-session.js +58 -0
  74. package/dist/commands/improve/improve.js +485 -2498
  75. package/dist/commands/improve/locks.js +154 -0
  76. package/dist/commands/improve/loop-stages.js +1083 -0
  77. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  78. package/dist/commands/improve/outcome-loop.js +256 -0
  79. package/dist/commands/improve/preparation.js +1966 -0
  80. package/dist/commands/improve/proactive-maintenance.js +115 -0
  81. package/dist/commands/improve/procedural.js +418 -0
  82. package/dist/commands/improve/recombine.js +850 -0
  83. package/dist/commands/improve/reflect-noise.js +0 -0
  84. package/dist/commands/improve/reflect.js +183 -40
  85. package/dist/commands/improve/salience.js +438 -0
  86. package/dist/commands/improve/triage.js +93 -0
  87. package/dist/commands/lint/agent-linter.js +19 -24
  88. package/dist/commands/lint/base-linter.js +173 -60
  89. package/dist/commands/lint/command-linter.js +19 -24
  90. package/dist/commands/lint/env-key-rules.js +38 -1
  91. package/dist/commands/lint/fact-linter.js +39 -0
  92. package/dist/commands/lint/index.js +31 -13
  93. package/dist/commands/lint/memory-linter.js +1 -1
  94. package/dist/commands/lint/registry.js +7 -2
  95. package/dist/commands/lint/task-linter.js +3 -3
  96. package/dist/commands/lint/workflow-linter.js +26 -1
  97. package/dist/commands/proposal/drain-policies.js +5 -0
  98. package/dist/commands/proposal/drain.js +43 -50
  99. package/dist/commands/proposal/proposal-cli.js +21 -31
  100. package/dist/commands/proposal/proposal.js +5 -0
  101. package/dist/commands/proposal/propose.js +7 -2
  102. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  103. package/dist/commands/proposal/validators/proposals.js +189 -63
  104. package/dist/commands/read/curate.js +414 -94
  105. package/dist/commands/read/knowledge.js +6 -3
  106. package/dist/commands/read/search-cli.js +9 -4
  107. package/dist/commands/read/search.js +10 -6
  108. package/dist/commands/read/show.js +86 -7
  109. package/dist/commands/sources/init.js +49 -17
  110. package/dist/commands/sources/installed-stashes.js +11 -3
  111. package/dist/commands/sources/schema-repair.js +43 -45
  112. package/dist/commands/sources/self-update.js +2 -2
  113. package/dist/commands/sources/source-add.js +7 -3
  114. package/dist/commands/sources/stash-cli.js +28 -40
  115. package/dist/commands/sources/stash-skeleton.js +23 -8
  116. package/dist/commands/tasks/tasks-cli.js +19 -27
  117. package/dist/commands/tasks/tasks.js +39 -11
  118. package/dist/commands/wiki-cli.js +21 -35
  119. package/dist/core/asset/asset-registry.js +3 -1
  120. package/dist/core/asset/asset-spec.js +18 -2
  121. package/dist/core/asset/frontmatter.js +166 -167
  122. package/dist/core/asset/markdown.js +8 -0
  123. package/dist/core/authoring-rules.js +92 -0
  124. package/dist/core/common.js +0 -5
  125. package/dist/core/config/config-migration.js +12 -11
  126. package/dist/core/config/config-schema.js +340 -56
  127. package/dist/core/config/config-types.js +3 -3
  128. package/dist/core/config/config.js +28 -7
  129. package/dist/core/events.js +3 -7
  130. package/dist/core/improve-types.js +11 -8
  131. package/dist/core/logs-db.js +10 -66
  132. package/dist/core/parse.js +36 -16
  133. package/dist/core/paths.js +3 -0
  134. package/dist/core/standards/resolve-standards-context.js +87 -0
  135. package/dist/core/standards/resolve-stash-standards.js +99 -0
  136. package/dist/core/standards/resolve-type-conventions.js +66 -0
  137. package/dist/core/state/migrations.js +714 -0
  138. package/dist/core/state-db.js +525 -474
  139. package/dist/indexer/db/db.js +439 -247
  140. package/dist/indexer/db/graph-db.js +129 -86
  141. package/dist/indexer/ensure-index.js +152 -17
  142. package/dist/indexer/graph/graph-boost.js +51 -41
  143. package/dist/indexer/graph/graph-extraction.js +218 -4
  144. package/dist/indexer/index-writer-lock.js +99 -0
  145. package/dist/indexer/indexer.js +123 -221
  146. package/dist/indexer/passes/dir-staleness.js +114 -0
  147. package/dist/indexer/passes/memory-inference.js +13 -5
  148. package/dist/indexer/passes/staleness-detect.js +2 -5
  149. package/dist/indexer/search/db-search.js +19 -6
  150. package/dist/indexer/search/ranking-contributors.js +22 -0
  151. package/dist/indexer/search/ranking.js +4 -0
  152. package/dist/indexer/search/search-source.js +17 -18
  153. package/dist/indexer/search/semantic-status.js +4 -0
  154. package/dist/indexer/walk/matchers.js +9 -0
  155. package/dist/integrations/agent/config.js +6 -53
  156. package/dist/integrations/agent/index.js +2 -18
  157. package/dist/integrations/agent/prompts.js +75 -9
  158. package/dist/integrations/agent/runner-dispatch.js +59 -0
  159. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  160. package/dist/integrations/harnesses/index.js +2 -3
  161. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  162. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  163. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
  164. package/dist/integrations/session-logs/index.js +16 -0
  165. package/dist/llm/client.js +45 -15
  166. package/dist/llm/embedder.js +42 -3
  167. package/dist/llm/embedders/deterministic.js +66 -0
  168. package/dist/llm/embedders/local.js +66 -2
  169. package/dist/llm/feature-gate.js +8 -4
  170. package/dist/llm/graph-extract.js +67 -44
  171. package/dist/llm/memory-infer-impl.js +138 -0
  172. package/dist/llm/memory-infer.js +1 -127
  173. package/dist/llm/metadata-enhance.js +44 -31
  174. package/dist/llm/structured-call.js +49 -0
  175. package/dist/migrate-storage-node.mjs +8 -0
  176. package/dist/output/context.js +5 -5
  177. package/dist/output/renderers.js +74 -2
  178. package/dist/output/shapes/curate.js +14 -2
  179. package/dist/output/shapes/passthrough.js +0 -1
  180. package/dist/output/text/helpers.js +16 -1
  181. package/dist/registry/providers/skills-sh.js +21 -147
  182. package/dist/registry/providers/static-index.js +15 -157
  183. package/dist/registry/resolve.js +22 -9
  184. package/dist/runtime.js +25 -1
  185. package/dist/scripts/migrate-storage.js +2617 -1961
  186. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
  187. package/dist/setup/setup.js +29 -8
  188. package/dist/sources/include.js +6 -2
  189. package/dist/sources/providers/filesystem.js +0 -1
  190. package/dist/sources/providers/git-install.js +210 -0
  191. package/dist/sources/providers/git-provider.js +234 -0
  192. package/dist/sources/providers/git-stash.js +248 -0
  193. package/dist/sources/providers/git.js +10 -661
  194. package/dist/sources/providers/npm.js +2 -6
  195. package/dist/sources/providers/provider-utils.js +13 -7
  196. package/dist/sources/providers/sync-from-ref.js +9 -1
  197. package/dist/sources/providers/tar-utils.js +16 -8
  198. package/dist/sources/providers/website.js +9 -5
  199. package/dist/sources/website-ingest.js +187 -29
  200. package/dist/sources/wiki-fetchers/registry.js +53 -0
  201. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  202. package/dist/storage/database.js +45 -10
  203. package/dist/storage/managed-db.js +82 -0
  204. package/dist/storage/repositories/registry-cache.js +92 -0
  205. package/dist/storage/sqlite-pragmas.js +146 -0
  206. package/dist/tasks/backends/cron.js +1 -1
  207. package/dist/tasks/backends/launchd.js +1 -1
  208. package/dist/tasks/backends/schtasks.js +1 -1
  209. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  210. package/dist/tasks/runner.js +5 -13
  211. package/dist/text-import-hook.mjs +0 -0
  212. package/dist/wiki/wiki.js +37 -0
  213. package/dist/workflows/db.js +3 -4
  214. package/dist/workflows/runtime/runs.js +1 -117
  215. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  216. package/dist/workflows/validate-summary.js +2 -7
  217. package/docs/data-and-telemetry.md +3 -2
  218. package/docs/migration/release-notes/0.9.0.md +39 -0
  219. package/package.json +13 -11
  220. package/dist/commands/db-cli.js +0 -23
  221. package/dist/indexer/db/db-backup.js +0 -376
@@ -52,22 +52,33 @@
52
52
  */
53
53
  import fs from "node:fs";
54
54
  import path from "node:path";
55
+ import distillKnowledgeSystemPrompt from "../../assets/prompts/distill-knowledge-system.md" with { type: "text" };
56
+ import distillLessonSystemPrompt from "../../assets/prompts/distill-lesson-system.md" with { type: "text" };
55
57
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
56
58
  import { assembleAssetFromString } from "../../core/asset/asset-serialize.js";
57
- import { parseFrontmatter } from "../../core/asset/frontmatter.js";
59
+ import { parseFrontmatter, writeSalienceToFrontmatter } from "../../core/asset/frontmatter.js";
58
60
  import { stripMarkdownFences } from "../../core/asset/markdown.js";
61
+ import { authoringRulesForType } from "../../core/authoring-rules.js";
59
62
  import { resolveStashDir, timestampForFilename } from "../../core/common.js";
60
63
  import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
61
64
  import { ConfigError, UsageError } from "../../core/errors.js";
62
65
  import { appendEvent, readEvents } from "../../core/events.js";
63
66
  import { lintLessonContent } from "../../core/lesson-lint.js";
67
+ import { getDbPath } from "../../core/paths.js";
68
+ import { resolveStashStandards } from "../../core/standards/resolve-stash-standards.js";
69
+ import { withStateDb } from "../../core/state-db.js";
64
70
  import { warnVerbose } from "../../core/warn.js";
71
+ import { closeDatabase, getAllEntries, openIndexDatabase } from "../../indexer/db/db.js";
65
72
  import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
66
73
  import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
67
74
  import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
68
75
  import { createProposal, isProposalSkipped, listProposals, } from "../proposal/validators/proposals.js";
69
76
  import { akmSearch } from "../read/search.js";
77
+ import { stripFrontmatterBody as stripBodyForFidelity } from "./dedup.js";
70
78
  import { assessMemoryKnowledgePromotionCandidate, deriveKnowledgeRef } from "./distill-promotion-policy.js";
79
+ import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
80
+ import { buildClsContext, checkDistillFidelity } from "./homeostatic.js";
81
+ import { computeSalience, upsertAssetSalience } from "./salience.js";
71
82
  /**
72
83
  * Asset-ref types that `akm distill` structurally refuses as inputs.
73
84
  *
@@ -122,68 +133,8 @@ import { repairTruncatedDescription } from "../../core/text-truncation.js";
122
133
  import { detectDoubleFrontmatter, isValidDescription, isValidWhenToUse, } from "../proposal/validators/proposal-quality-validators.js";
123
134
  export { detectDoubleFrontmatter, isValidDescription, isValidWhenToUse };
124
135
  // ── Prompt assembly ─────────────────────────────────────────────────────────
125
- const LESSON_SYSTEM_PROMPT = [
126
- "You are the akm `distill` distiller.",
127
- "Given an asset and recent feedback events about it, produce a single",
128
- "concise *lesson* an agent should remember next time it works on this",
129
- "asset's domain.",
130
- "",
131
- "YOUR RESPONSE MUST START EXACTLY WITH `---` ON THE VERY FIRST LINE.",
132
- "DO NOT output any prose, explanation, or code fences before or after.",
133
- "",
134
- "Required output format — copy this structure exactly:",
135
- "---",
136
- "description: <one complete sentence (ending with `.`) summarising what the lesson teaches>",
137
- "when_to_use: <one complete sentence describing the concrete trigger condition>",
138
- "---",
139
- "",
140
- "<lesson body — plain markdown, 1–3 short paragraphs of practical guidance>",
141
- "",
142
- "## description field (MANDATORY)",
143
- "- A single complete sentence in present tense, 80-200 chars, NO markdown.",
144
- "- Self-contained: a reviewer must understand the lesson from this field alone.",
145
- '- DO NOT start with "When ", "If ", or a connector word — that belongs in when_to_use.',
146
- '- DO NOT copy a section heading ("Key takeaways", "For example", "Key pitfalls").',
147
- "- DO NOT begin with a numbered list marker, code fence, or markdown heading.",
148
- "",
149
- 'GOOD: "Always validate ref existence before promoting a memory to knowledge; missing refs surface as silent 404s during accept."',
150
- 'BAD: "Key pitfalls"',
151
- 'BAD: "When working with the akm CLI"',
152
- 'BAD: "For example, you might..."',
153
- 'BAD: "1. Check the file"',
154
- "",
155
- "RULES:",
156
- "- `when_to_use` MUST be a complete sentence describing a concrete trigger. Never write `When working with <asset-name>` — that is circular and useless.",
157
- "- `description` and `when_to_use` MUST differ from each other.",
158
- "- The lesson body MUST be non-empty markdown prose. Do NOT restate `description:` or `when_to_use:` inside the body (no `**description:** ...` or `**when_to_use:** ...` lines — the frontmatter is the only place those keys belong).",
159
- "- Do NOT emit a second `---` fence after the opening frontmatter — there are exactly two `---` lines in the output, both belonging to the single frontmatter block at the top.",
160
- "- Do NOT reproduce the source asset verbatim — distil what a caller needs to know.",
161
- "- Output ONLY the lesson file. No preamble, no code fences, no trailing prose.",
162
- ].join("\n");
163
- const KNOWLEDGE_SYSTEM_PROMPT = [
164
- "You are the akm `distill` distiller.",
165
- "Given an asset and recent feedback events about it, produce a concise",
166
- "*knowledge* markdown document capturing the durable, reusable facts.",
167
- "Prefer stable guidance over narrative recap.",
168
- "",
169
- "YOUR RESPONSE MUST START EXACTLY WITH `---` ON THE VERY FIRST LINE.",
170
- "DO NOT output any prose, explanation, or code fences before or after.",
171
- "",
172
- "Required output format:",
173
- "---",
174
- "description: <one-line summary of the knowledge asset>",
175
- "tags: [<tag1>, <tag2>]",
176
- "---",
177
- "",
178
- "# <Title>",
179
- "",
180
- "<body — structured markdown, durable facts only>",
181
- "",
182
- "RULES:",
183
- "- `description` MUST be a non-empty single-line string.",
184
- "- Include a meaningful markdown body with a `# Title` heading.",
185
- "- Output ONLY the knowledge file. No preamble, no code fences, no trailing prose.",
186
- ].join("\n");
136
+ const LESSON_SYSTEM_PROMPT = distillLessonSystemPrompt;
137
+ const KNOWLEDGE_SYSTEM_PROMPT = distillKnowledgeSystemPrompt;
187
138
  // ── Structured-output schemas (responseSchema lift) ─────────────────────────
188
139
  //
189
140
  // PR 1 of the asset-writers decision (see knowledge:projects/akm/
@@ -358,6 +309,18 @@ export function buildDistillPrompt(input) {
358
309
  const lines = [];
359
310
  lines.push(`Asset ref: ${input.inputRef}`);
360
311
  lines.push("");
312
+ if (input.standardsContext?.trim()) {
313
+ lines.push("Standards to follow (the rulebook for this target):");
314
+ lines.push(input.standardsContext.trim());
315
+ lines.push("");
316
+ }
317
+ {
318
+ const authoringRules = authoringRulesForType(input.proposalKind ?? "lesson");
319
+ if (authoringRules) {
320
+ lines.push(authoringRules);
321
+ lines.push("");
322
+ }
323
+ }
361
324
  lines.push("Asset content:");
362
325
  if (input.assetContent) {
363
326
  const body = input.assetContent.trim().slice(0, 3000);
@@ -586,7 +549,7 @@ similarLessons) {
586
549
  * @param reason - Human-readable rejection reason.
587
550
  * @param extraMeta - Optional additional metadata for the event.
588
551
  */
589
- function writeQualityRejection(stash, inputRef, lessonRef, content, score, reason, extraMeta = {}) {
552
+ function writeQualityRejection(stash, inputRef, lessonRef, content, score, reason, extraMeta = {}, eligibilitySource) {
590
553
  // D-5 / #388: reviewNeeded flag selects "review_needed" vs "quality_rejected" outcome.
591
554
  const outcome = extraMeta.reviewNeeded ? "review_needed" : "quality_rejected";
592
555
  const rejectDir = path.join(stash, ".akm", "distill-rejected");
@@ -602,6 +565,9 @@ function writeQualityRejection(stash, inputRef, lessonRef, content, score, reaso
602
565
  score,
603
566
  reason,
604
567
  ...extraMeta,
568
+ // Attribution tagging: stamp the eligibility lane so distill_invoked can be
569
+ // sliced by lane downstream. See EligibilitySource.
570
+ ...(eligibilitySource ? { eligibilitySource } : {}),
605
571
  },
606
572
  });
607
573
  return {
@@ -629,6 +595,12 @@ export async function akmDistill(options) {
629
595
  // Validate the ref shape up front so a typo never reaches the LLM.
630
596
  const parsedInputRef = parseAssetRef(inputRef);
631
597
  const targetKind = options.proposalKind ?? "lesson";
598
+ // Attribution tagging: spread into every distill_invoked event's metadata so
599
+ // the lane that selected this asset is recorded uniformly across all outcome
600
+ // branches. Empty object when no lane was supplied (direct `akm distill`).
601
+ const eligMeta = options.eligibilitySource
602
+ ? { eligibilitySource: options.eligibilitySource }
603
+ : {};
632
604
  // Recursive-distillation guard. Distill produces *lessons* from non-lesson
633
605
  // sources (memory, skill, knowledge, etc.). Calling distill on an existing
634
606
  // lesson would derive `lesson:lesson-<name>-lesson-lesson` (double `-lesson`
@@ -650,6 +622,7 @@ export async function akmDistill(options) {
650
622
  lessonRef: skippedRef,
651
623
  message: "distill refuses lesson inputs — lessons are the distilled form, not a source",
652
624
  skipReason: "recursive_lesson_input",
625
+ ...eligMeta,
653
626
  },
654
627
  });
655
628
  return {
@@ -671,15 +644,74 @@ export async function akmDistill(options) {
671
644
  // Best-effort load: when the asset is not yet indexed we still proceed —
672
645
  // the LLM is asked to distil from "available signal" (feedback alone).
673
646
  let assetContent = null;
647
+ let assetFilePath = null;
674
648
  try {
675
649
  const filePath = await lookup(inputRef);
676
650
  if (filePath && fs.existsSync(filePath)) {
651
+ assetFilePath = filePath;
677
652
  assetContent = fs.readFileSync(filePath, "utf8");
678
653
  }
679
654
  }
680
655
  catch {
681
656
  assetContent = null;
682
657
  }
658
+ // ── #608: Encoding-time salience scoring ────────────────────────────────
659
+ // Score the source asset with the three-signal model (novelty × 0.40 +
660
+ // magnitude × 0.35 + predictionError × 0.25) and persist the result to:
661
+ // 1. The asset's frontmatter (human-readable mirror; idempotent delta gate).
662
+ // 2. state.db :: asset_salience (canonical; feeds improve's high-salience gate).
663
+ // Both writes are best-effort — a DB error never blocks distillation.
664
+ if (assetContent && assetFilePath) {
665
+ try {
666
+ const parsedRef = parseAssetRef(inputRef);
667
+ // Build bigram vocabulary from currently-indexed refs for novelty signal.
668
+ let existingRefVocabulary = new Set();
669
+ try {
670
+ const embCfg = config?.embedding;
671
+ const indexDb = openIndexDatabase(getDbPath(), embCfg?.dimension ? { embeddingDim: embCfg.dimension } : undefined);
672
+ try {
673
+ const allRefs = getAllEntries(indexDb).map((e) => e.entryKey);
674
+ existingRefVocabulary = buildRefVocabulary(allRefs);
675
+ }
676
+ finally {
677
+ closeDatabase(indexDb);
678
+ }
679
+ }
680
+ catch {
681
+ // Index not available — novelty defaults to type-floor.
682
+ }
683
+ const salienceResult = scoreEncodingSalience({
684
+ body: assetContent,
685
+ type: parsedRef.type,
686
+ existingRefVocabulary,
687
+ revisionCount: 0,
688
+ });
689
+ // 1. Write salience to the source asset frontmatter (idempotent).
690
+ const updatedContent = writeSalienceToFrontmatter(assetContent, salienceResult.score, salienceResult);
691
+ if (updatedContent !== assetContent) {
692
+ fs.writeFileSync(assetFilePath, updatedContent, "utf8");
693
+ assetContent = updatedContent;
694
+ }
695
+ // 2. Persist encoding_salience to state.db.
696
+ try {
697
+ withStateDb((stateDb) => {
698
+ const vector = computeSalience({
699
+ ref: inputRef,
700
+ type: parsedRef.type,
701
+ retrievalFreq: 0,
702
+ encodingSalience: salienceResult.score,
703
+ });
704
+ upsertAssetSalience(stateDb, inputRef, vector);
705
+ });
706
+ }
707
+ catch {
708
+ // State DB unavailable — frontmatter mirror is the only persistence.
709
+ }
710
+ }
711
+ catch {
712
+ // Scoring errors never block distillation.
713
+ }
714
+ }
683
715
  const { events } = readEventsImpl({
684
716
  ref: inputRef,
685
717
  type: "feedback",
@@ -766,6 +798,7 @@ export async function akmDistill(options) {
766
798
  outcome: "skipped",
767
799
  lessonRef: promotion.knowledgeRef,
768
800
  message: "D-1: LLM resolved destination conflict as NOOP — existing content kept",
801
+ ...eligMeta,
769
802
  },
770
803
  });
771
804
  return {
@@ -814,9 +847,9 @@ export async function akmDistill(options) {
814
847
  if (!judgeResult.pass) {
815
848
  if (judgeResult.reviewNeeded) {
816
849
  // Uncertainty band (2.5–3.5): queue as review_needed instead of rejecting.
817
- return writeQualityRejection(stash, inputRef, promotion.knowledgeRef, resolvedPromotionContent, judgeResult.score, judgeResult.reason, { reviewNeeded: true });
850
+ return writeQualityRejection(stash, inputRef, promotion.knowledgeRef, resolvedPromotionContent, judgeResult.score, judgeResult.reason, { reviewNeeded: true }, options.eligibilitySource);
818
851
  }
819
- return writeQualityRejection(stash, inputRef, promotion.knowledgeRef, resolvedPromotionContent, judgeResult.score, judgeResult.reason);
852
+ return writeQualityRejection(stash, inputRef, promotion.knowledgeRef, resolvedPromotionContent, judgeResult.score, judgeResult.reason, {}, options.eligibilitySource);
820
853
  }
821
854
  // Normalize 1-5 judge score to [0, 1]. Score of -1 means pass-through
822
855
  // (no LLM / timeout / parse failure) — leave confidence undefined so
@@ -834,6 +867,8 @@ export async function akmDistill(options) {
834
867
  ...(Object.keys(knowledgeParsed.data).length > 0 ? { frontmatter: knowledgeParsed.data } : {}),
835
868
  },
836
869
  ...(knowledgeJudgeConfidence !== undefined ? { confidence: knowledgeJudgeConfidence } : {}),
870
+ // Attribution tagging: persist the eligibility lane on the proposal.
871
+ ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
837
872
  }, options.ctx);
838
873
  if (isProposalSkipped(proposalResult)) {
839
874
  appendEvent({
@@ -844,6 +879,7 @@ export async function akmDistill(options) {
844
879
  lessonRef: promotion.knowledgeRef,
845
880
  message: proposalResult.message,
846
881
  skipReason: proposalResult.reason,
882
+ ...eligMeta,
847
883
  },
848
884
  });
849
885
  return {
@@ -867,6 +903,7 @@ export async function akmDistill(options) {
867
903
  proposalId: proposal.id,
868
904
  ...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
869
905
  ...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
906
+ ...eligMeta,
870
907
  },
871
908
  });
872
909
  return {
@@ -894,13 +931,36 @@ export async function akmDistill(options) {
894
931
  reason: p.review?.reason ?? "no reason given",
895
932
  contentPreview: p.payload.content.slice(0, 500),
896
933
  }));
897
- const userPrompt = buildDistillPrompt({
934
+ // WS-3b CLS interleaving (step 9).
935
+ // When cls.enabled, inject embedding-retrieved adjacent lessons/knowledge
936
+ // into the distill prompt so the LLM avoids overwriting prior generalizations
937
+ // (catastrophic interference). DEFAULT OFF.
938
+ const clsConfig = config.profiles?.improve?.default?.processes?.distill?.cls ?? {};
939
+ let clsContext = "";
940
+ if (clsConfig.enabled) {
941
+ try {
942
+ const adjacentCount = clsConfig.adjacentCount ?? 3;
943
+ // Use the asset content or input ref as the query for adjacent retrieval.
944
+ const clsQuery = assetContent ? assetContent.slice(0, 500) : inputRef;
945
+ const adjacentItems = await fetchSimilarLessonsFn(clsQuery, adjacentCount);
946
+ clsContext = buildClsContext(adjacentItems, clsConfig);
947
+ }
948
+ catch {
949
+ // Fail open — CLS is supplemental, never required.
950
+ }
951
+ }
952
+ // Distill output is a lesson/knowledge (non-wiki) → stash authoring
953
+ // standards. Resolved once for this single call.
954
+ const standardsContext = resolveStashStandards(stash);
955
+ const baseUserPrompt = buildDistillPrompt({
898
956
  inputRef,
899
957
  assetContent,
900
958
  feedback,
901
959
  proposalKind: effectiveProposalKind,
902
960
  ...(rejectedForRef.length > 0 ? { rejectedProposals: rejectedForRef } : {}),
961
+ ...(standardsContext.trim() ? { standardsContext } : {}),
903
962
  });
963
+ const userPrompt = clsContext ? `${baseUserPrompt}${clsContext}` : baseUserPrompt;
904
964
  const messages = [
905
965
  { role: "system", content: effectiveProposalKind === "knowledge" ? KNOWLEDGE_SYSTEM_PROMPT : LESSON_SYSTEM_PROMPT },
906
966
  { role: "user", content: userPrompt },
@@ -979,6 +1039,7 @@ export async function akmDistill(options) {
979
1039
  lessonRef: effectiveLessonRef,
980
1040
  proposalKind: effectiveProposalKind,
981
1041
  ...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
1042
+ ...eligMeta,
982
1043
  },
983
1044
  });
984
1045
  return {
@@ -1203,6 +1264,7 @@ export async function akmDistill(options) {
1203
1264
  proposalKind: effectiveProposalKind,
1204
1265
  findingKinds: findings.map((f) => f.kind),
1205
1266
  ...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
1267
+ ...eligMeta,
1206
1268
  },
1207
1269
  });
1208
1270
  const message = findings.map((f) => f.message).join("\n");
@@ -1224,9 +1286,9 @@ export async function akmDistill(options) {
1224
1286
  return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, judgeResult.score, judgeResult.reason, {
1225
1287
  reviewNeeded: true,
1226
1288
  ...(exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {}),
1227
- });
1289
+ }, options.eligibilitySource);
1228
1290
  }
1229
- return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, judgeResult.score, judgeResult.reason, exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {});
1291
+ return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, judgeResult.score, judgeResult.reason, exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {}, options.eligibilitySource);
1230
1292
  }
1231
1293
  // Normalize 1-5 judge score to [0, 1]. Score of -1 means pass-through
1232
1294
  // (no LLM / timeout / parse failure) — leave confidence undefined so
@@ -1234,6 +1296,30 @@ export async function akmDistill(options) {
1234
1296
  if (judgeResult.score > 0)
1235
1297
  lessonJudgeConfidence = judgeResult.score / 5;
1236
1298
  }
1299
+ // WS-3b: Distill→source fidelity check (step 10).
1300
+ // When fidelityCheck.enabled, check the distill proposal against its cited
1301
+ // source memories. A contradiction flag routes to human review (not auto-accept).
1302
+ // DEFAULT OFF. Fail-open: any error is treated as no-contradiction.
1303
+ const fidelityConfig = config.profiles?.improve?.default?.processes?.distill?.fidelityCheck ?? {};
1304
+ if (fidelityConfig.enabled && assetContent) {
1305
+ try {
1306
+ const proposalBody = stripBodyForFidelity(content);
1307
+ const sourceBodies = [stripBodyForFidelity(assetContent)];
1308
+ const fidelityResult = checkDistillFidelity(proposalBody, sourceBodies, fidelityConfig);
1309
+ if (fidelityResult.contradictionDetected) {
1310
+ // Route to human review by writing a quality rejection with reviewNeeded=true.
1311
+ return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, 2.0, // below auto-accept threshold, signals review needed
1312
+ fidelityResult.reason ?? "Proposal may contradict cited source memories.", {
1313
+ reviewNeeded: true,
1314
+ fidelityContradiction: true,
1315
+ ...(exclusionSet.size > 0 ? { filteredFeedbackCount, feedbackFullyFiltered } : {}),
1316
+ }, options.eligibilitySource);
1317
+ }
1318
+ }
1319
+ catch {
1320
+ // Fail open — fidelity check is supplemental.
1321
+ }
1322
+ }
1237
1323
  // Round-trip the parsed frontmatter so the proposal carries it as a
1238
1324
  // structured payload alongside the raw content (matches the shape used by
1239
1325
  // other proposal sources).
@@ -1256,6 +1342,8 @@ export async function akmDistill(options) {
1256
1342
  frontmatter: frontmatterWithSources,
1257
1343
  },
1258
1344
  ...(lessonJudgeConfidence !== undefined ? { confidence: lessonJudgeConfidence } : {}),
1345
+ // Attribution tagging: persist the eligibility lane on the proposal.
1346
+ ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
1259
1347
  }, options.ctx);
1260
1348
  if (isProposalSkipped(proposalResult2)) {
1261
1349
  appendEvent({
@@ -1266,6 +1354,7 @@ export async function akmDistill(options) {
1266
1354
  lessonRef: effectiveLessonRef,
1267
1355
  message: proposalResult2.message,
1268
1356
  skipReason: proposalResult2.reason,
1357
+ ...eligMeta,
1269
1358
  },
1270
1359
  });
1271
1360
  return {
@@ -1290,6 +1379,7 @@ export async function akmDistill(options) {
1290
1379
  ...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
1291
1380
  ...(exclusionSet.size > 0 ? { filteredFeedbackCount } : {}),
1292
1381
  ...(descriptionSwapped > 0 ? { descriptionSwapped } : {}),
1382
+ ...eligMeta,
1293
1383
  },
1294
1384
  });
1295
1385
  return {