akm-cli 0.9.0-beta.9 → 0.9.0-rc.0

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 (325) hide show
  1. package/CHANGELOG.md +592 -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/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  39. package/dist/assets/templates/html/health.html +281 -111
  40. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  41. package/dist/assets/wiki/schema-template.md +4 -4
  42. package/dist/cli/clack.js +56 -0
  43. package/dist/cli/config-migrate.js +7 -1
  44. package/dist/cli/confirm.js +1 -1
  45. package/dist/cli/parse-args.js +46 -1
  46. package/dist/cli/shared.js +28 -0
  47. package/dist/cli.js +25 -21
  48. package/dist/commands/agent/agent-dispatch.js +3 -2
  49. package/dist/commands/agent/agent-support.js +0 -7
  50. package/dist/commands/agent/contribute-cli.js +26 -7
  51. package/dist/commands/config-cli.js +26 -13
  52. package/dist/commands/env/child-env.js +47 -0
  53. package/dist/commands/env/env-cli.js +220 -227
  54. package/dist/commands/env/env.js +14 -67
  55. package/dist/commands/env/secret-cli.js +140 -138
  56. package/dist/commands/feedback-cli.js +156 -155
  57. package/dist/commands/graph/graph-cli.js +5 -13
  58. package/dist/commands/graph/graph.js +3 -3
  59. package/dist/commands/health/advisories.js +151 -0
  60. package/dist/commands/health/checks.js +103 -16
  61. package/dist/commands/health/html-report.js +447 -81
  62. package/dist/commands/health/improve-metrics.js +771 -0
  63. package/dist/commands/health/llm-usage.js +65 -0
  64. package/dist/commands/health/md-report.js +103 -0
  65. package/dist/commands/health/metrics.js +278 -0
  66. package/dist/commands/health/stash-exposure.js +46 -0
  67. package/dist/commands/health/surfaces.js +216 -0
  68. package/dist/commands/health/task-runs.js +135 -0
  69. package/dist/commands/health/types.js +26 -0
  70. package/dist/commands/health/windows.js +195 -0
  71. package/dist/commands/health.js +91 -1091
  72. package/dist/commands/improve/anti-collapse.js +170 -0
  73. package/dist/commands/improve/calibration.js +161 -0
  74. package/dist/commands/improve/collapse-detector.js +421 -0
  75. package/dist/commands/improve/consolidate/chunking.js +141 -0
  76. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  77. package/dist/commands/improve/consolidate/merge.js +145 -0
  78. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  79. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  80. package/dist/commands/improve/consolidate.js +1295 -1277
  81. package/dist/commands/improve/dedup.js +482 -0
  82. package/dist/commands/improve/distill/content-repair.js +202 -0
  83. package/dist/commands/improve/distill/promote-memory.js +229 -0
  84. package/dist/commands/improve/distill/quality-gate.js +236 -0
  85. package/dist/commands/improve/distill-guards.js +127 -0
  86. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  87. package/dist/commands/improve/distill.js +228 -605
  88. package/dist/commands/improve/eligibility.js +434 -0
  89. package/dist/commands/improve/encoding-salience.js +205 -0
  90. package/dist/commands/improve/extract-cli.js +179 -59
  91. package/dist/commands/improve/extract-prompt.js +54 -3
  92. package/dist/commands/improve/extract-watch.js +140 -0
  93. package/dist/commands/improve/extract.js +409 -43
  94. package/dist/commands/improve/feedback-valence.js +54 -0
  95. package/dist/commands/improve/hot-probation.js +45 -0
  96. package/dist/commands/improve/improve-auto-accept.js +157 -10
  97. package/dist/commands/improve/improve-cli.js +115 -73
  98. package/dist/commands/improve/improve-profiles.js +28 -8
  99. package/dist/commands/improve/improve-result-file.js +15 -25
  100. package/dist/commands/improve/improve-session.js +58 -0
  101. package/dist/commands/improve/improve.js +485 -2764
  102. package/dist/commands/improve/locks.js +154 -0
  103. package/dist/commands/improve/loop-stages.js +1100 -0
  104. package/dist/commands/improve/memory/memory-belief.js +14 -15
  105. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  106. package/dist/commands/improve/memory/memory-improve.js +27 -27
  107. package/dist/commands/improve/outcome-loop.js +270 -0
  108. package/dist/commands/improve/preparation.js +2002 -0
  109. package/dist/commands/improve/proactive-maintenance.js +37 -35
  110. package/dist/commands/improve/procedural.js +398 -0
  111. package/dist/commands/improve/recombine.js +818 -0
  112. package/dist/commands/improve/reflect-noise.js +0 -0
  113. package/dist/commands/improve/reflect.js +206 -45
  114. package/dist/commands/improve/salience.js +455 -0
  115. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  116. package/dist/commands/improve/shared.js +51 -0
  117. package/dist/commands/improve/triage.js +93 -0
  118. package/dist/commands/lint/agent-linter.js +19 -24
  119. package/dist/commands/lint/base-linter.js +173 -60
  120. package/dist/commands/lint/command-linter.js +19 -24
  121. package/dist/commands/lint/env-key-rules.js +38 -1
  122. package/dist/commands/lint/fact-linter.js +39 -0
  123. package/dist/commands/lint/index.js +31 -13
  124. package/dist/commands/lint/memory-linter.js +1 -1
  125. package/dist/commands/lint/registry.js +7 -2
  126. package/dist/commands/lint/task-linter.js +3 -3
  127. package/dist/commands/lint/workflow-linter.js +26 -1
  128. package/dist/commands/observability-cli.js +4 -4
  129. package/dist/commands/proposal/drain-policies.js +13 -4
  130. package/dist/commands/proposal/drain.js +45 -51
  131. package/dist/commands/proposal/legacy-import.js +115 -0
  132. package/dist/commands/proposal/proposal-cli.js +24 -34
  133. package/dist/commands/proposal/proposal.js +2 -1
  134. package/dist/commands/proposal/propose.js +8 -3
  135. package/dist/commands/proposal/repository.js +829 -0
  136. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  137. package/dist/commands/proposal/validators/proposals.js +93 -895
  138. package/dist/commands/read/curate.js +410 -111
  139. package/dist/commands/read/knowledge.js +10 -3
  140. package/dist/commands/read/remember-cli.js +133 -138
  141. package/dist/commands/read/search-cli.js +15 -8
  142. package/dist/commands/read/search.js +22 -11
  143. package/dist/commands/read/show.js +106 -14
  144. package/dist/commands/registry-cli.js +76 -87
  145. package/dist/commands/remember.js +11 -12
  146. package/dist/commands/sources/add-cli.js +91 -95
  147. package/dist/commands/sources/history.js +1 -1
  148. package/dist/commands/sources/init.js +66 -18
  149. package/dist/commands/sources/installed-stashes.js +11 -3
  150. package/dist/commands/sources/schema-repair.js +44 -46
  151. package/dist/commands/sources/self-update.js +2 -2
  152. package/dist/commands/sources/source-add.js +7 -3
  153. package/dist/commands/sources/sources-cli.js +3 -3
  154. package/dist/commands/sources/stash-cli.js +19 -39
  155. package/dist/commands/sources/stash-skeleton.js +57 -8
  156. package/dist/commands/tasks/default-tasks.js +15 -2
  157. package/dist/commands/tasks/tasks-cli.js +20 -29
  158. package/dist/commands/tasks/tasks.js +39 -11
  159. package/dist/commands/wiki-cli.js +23 -38
  160. package/dist/commands/workflow-cli.js +15 -1
  161. package/dist/core/asset/asset-registry.js +3 -1
  162. package/dist/core/asset/asset-spec.js +21 -4
  163. package/dist/core/asset/frontmatter.js +188 -167
  164. package/dist/core/asset/markdown.js +8 -0
  165. package/dist/core/authoring-rules.js +92 -0
  166. package/dist/core/common.js +4 -23
  167. package/dist/core/concurrent.js +10 -1
  168. package/dist/core/config/config-io.js +10 -1
  169. package/dist/core/config/config-migration.js +18 -40
  170. package/dist/core/config/config-schema.js +382 -62
  171. package/dist/core/config/config-types.js +3 -3
  172. package/dist/core/config/config.js +67 -22
  173. package/dist/core/deep-merge.js +38 -0
  174. package/dist/core/errors.js +1 -0
  175. package/dist/core/eval/rank-metrics.js +113 -0
  176. package/dist/core/events.js +4 -7
  177. package/dist/core/improve-types.js +47 -8
  178. package/dist/core/logs-db.js +14 -75
  179. package/dist/core/parse.js +36 -16
  180. package/dist/core/paths.js +18 -18
  181. package/dist/core/standards/resolve-standards-context.js +87 -0
  182. package/dist/core/standards/resolve-stash-standards.js +99 -0
  183. package/dist/core/standards/resolve-type-conventions.js +66 -0
  184. package/dist/core/state/migrations.js +770 -0
  185. package/dist/core/state-db.js +132 -1126
  186. package/dist/core/structured.js +69 -0
  187. package/dist/core/time.js +53 -0
  188. package/dist/core/warn.js +21 -0
  189. package/dist/core/write-source.js +37 -0
  190. package/dist/indexer/db/db.js +259 -769
  191. package/dist/indexer/db/entry-mapper.js +41 -0
  192. package/dist/indexer/db/graph-db.js +129 -86
  193. package/dist/indexer/db/llm-cache.js +2 -2
  194. package/dist/indexer/db/schema.js +516 -0
  195. package/dist/indexer/ensure-index.js +36 -92
  196. package/dist/indexer/feedback/utility-policy.js +75 -0
  197. package/dist/indexer/graph/graph-boost.js +51 -41
  198. package/dist/indexer/graph/graph-extraction.js +207 -4
  199. package/dist/indexer/index-writer-lock.js +18 -11
  200. package/dist/indexer/index-written-assets.js +105 -0
  201. package/dist/indexer/indexer.js +182 -204
  202. package/dist/indexer/passes/dir-staleness.js +114 -0
  203. package/dist/indexer/passes/memory-inference.js +13 -5
  204. package/dist/indexer/passes/metadata.js +20 -0
  205. package/dist/indexer/read-preflight.js +23 -0
  206. package/dist/indexer/search/db-search.js +89 -13
  207. package/dist/indexer/search/fts-query.js +51 -0
  208. package/dist/indexer/search/ranking-contributors.js +95 -9
  209. package/dist/indexer/search/ranking.js +79 -3
  210. package/dist/indexer/search/search-fields.js +6 -0
  211. package/dist/indexer/search/search-source.js +32 -21
  212. package/dist/indexer/search/semantic-status.js +4 -0
  213. package/dist/indexer/walk/matchers.js +9 -0
  214. package/dist/indexer/walk/walker.js +21 -13
  215. package/dist/integrations/agent/builders.js +39 -13
  216. package/dist/integrations/agent/config.js +20 -59
  217. package/dist/integrations/agent/detect.js +9 -0
  218. package/dist/integrations/agent/index.js +3 -19
  219. package/dist/integrations/agent/model-aliases.js +7 -2
  220. package/dist/integrations/agent/profiles.js +7 -1
  221. package/dist/integrations/agent/prompts.js +75 -9
  222. package/dist/integrations/agent/runner-dispatch.js +59 -0
  223. package/dist/integrations/agent/runner.js +13 -9
  224. package/dist/integrations/agent/spawn.js +69 -67
  225. package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
  226. package/dist/integrations/harnesses/claude/index.js +2 -0
  227. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  228. package/dist/integrations/harnesses/index.js +2 -3
  229. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  230. package/dist/integrations/harnesses/opencode/index.js +2 -0
  231. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  232. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  233. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +98 -17
  234. package/dist/integrations/harnesses/types.js +1 -0
  235. package/dist/integrations/session-logs/index.js +16 -0
  236. package/dist/llm/call-ai.js +2 -2
  237. package/dist/llm/client.js +34 -11
  238. package/dist/llm/embedder.js +67 -4
  239. package/dist/llm/embedders/cache.js +3 -1
  240. package/dist/llm/embedders/deterministic.js +66 -0
  241. package/dist/llm/embedders/local.js +73 -3
  242. package/dist/llm/feature-gate.js +16 -15
  243. package/dist/llm/graph-extract.js +67 -44
  244. package/dist/llm/memory-infer-impl.js +138 -0
  245. package/dist/llm/memory-infer.js +1 -127
  246. package/dist/llm/metadata-enhance.js +44 -31
  247. package/dist/llm/structured-call.js +49 -0
  248. package/dist/migrate-storage-node.mjs +8 -0
  249. package/dist/output/context.js +5 -5
  250. package/dist/output/renderers.js +85 -14
  251. package/dist/output/shapes/curate.js +14 -2
  252. package/dist/output/shapes/helpers.js +0 -3
  253. package/dist/output/shapes/passthrough.js +2 -1
  254. package/dist/output/text/helpers.js +29 -1
  255. package/dist/output/text/workflow.js +1 -0
  256. package/dist/registry/providers/skills-sh.js +21 -147
  257. package/dist/registry/providers/static-index.js +15 -157
  258. package/dist/registry/resolve.js +27 -9
  259. package/dist/runtime.js +25 -1
  260. package/dist/scripts/migrate-storage.js +2661 -2369
  261. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +883 -596
  262. package/dist/setup/detect.js +9 -0
  263. package/dist/setup/legacy-config.js +106 -0
  264. package/dist/setup/prompt.js +57 -0
  265. package/dist/setup/providers.js +14 -0
  266. package/dist/setup/registry-stash-loader.js +12 -0
  267. package/dist/setup/semantic-assets.js +124 -0
  268. package/dist/setup/setup.js +52 -1614
  269. package/dist/setup/steps/connection.js +734 -0
  270. package/dist/setup/steps/output.js +31 -0
  271. package/dist/setup/steps/platforms.js +124 -0
  272. package/dist/setup/steps/semantic.js +27 -0
  273. package/dist/setup/steps/sources.js +222 -0
  274. package/dist/setup/steps/stashdir.js +42 -0
  275. package/dist/setup/steps/tasks.js +152 -0
  276. package/dist/sources/include.js +6 -2
  277. package/dist/sources/providers/filesystem.js +0 -1
  278. package/dist/sources/providers/git-install.js +210 -0
  279. package/dist/sources/providers/git-provider.js +234 -0
  280. package/dist/sources/providers/git-stash.js +248 -0
  281. package/dist/sources/providers/git.js +10 -661
  282. package/dist/sources/providers/npm.js +2 -6
  283. package/dist/sources/providers/provider-utils.js +13 -7
  284. package/dist/sources/providers/sync-from-ref.js +9 -1
  285. package/dist/sources/providers/website.js +9 -5
  286. package/dist/sources/website-ingest.js +187 -29
  287. package/dist/sources/wiki-fetchers/registry.js +53 -0
  288. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  289. package/dist/storage/database.js +45 -10
  290. package/dist/storage/managed-db.js +82 -0
  291. package/dist/storage/repositories/canaries-repository.js +107 -0
  292. package/dist/storage/repositories/consolidation-repository.js +38 -0
  293. package/dist/storage/repositories/embeddings-repository.js +72 -0
  294. package/dist/storage/repositories/events-repository.js +187 -0
  295. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  296. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  297. package/dist/storage/repositories/index-db.js +14 -8
  298. package/dist/storage/repositories/proposals-repository.js +220 -0
  299. package/dist/storage/repositories/recombine-repository.js +213 -0
  300. package/dist/storage/repositories/registry-cache.js +93 -0
  301. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  302. package/dist/storage/repositories/task-history-repository.js +93 -0
  303. package/dist/storage/sqlite-pragmas.js +146 -0
  304. package/dist/tasks/backends/cron.js +1 -1
  305. package/dist/tasks/backends/index.js +9 -0
  306. package/dist/tasks/backends/launchd.js +1 -1
  307. package/dist/tasks/backends/schtasks.js +1 -1
  308. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  309. package/dist/tasks/runner.js +15 -13
  310. package/dist/text-import-hook.mjs +0 -0
  311. package/dist/wiki/wiki.js +52 -11
  312. package/dist/workflows/cli.js +1 -0
  313. package/dist/workflows/db.js +3 -4
  314. package/dist/workflows/runtime/runs.js +43 -118
  315. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  316. package/dist/workflows/validate-summary.js +2 -7
  317. package/docs/README.md +69 -18
  318. package/docs/data-and-telemetry.md +5 -4
  319. package/docs/migration/release-notes/0.7.0.md +1 -1
  320. package/docs/migration/release-notes/0.9.0.md +39 -0
  321. package/package.json +10 -10
  322. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  323. package/dist/commands/db-cli.js +0 -23
  324. package/dist/indexer/db/db-backup.js +0 -376
  325. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -0,0 +1,170 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * WS-3b Step 8 — Anti-collapse merge guards.
6
+ *
7
+ * (a) Generation counter: merged.generation = max(sources)+1; refuse merge
8
+ * of two assets both above generation N (default 2); merges cite sources.
9
+ * (b) Lexical-diversity check: low n-gram diversity ⇒ raise merge threshold.
10
+ * (c) Merge-information floor (R5 §4.2): provenance union must not shrink and
11
+ * the merged body must retain a minimum fraction of the source tokens.
12
+ * (d) Occasional random non-similar cluster in the pool.
13
+ *
14
+ * @module anti-collapse
15
+ */
16
+ /** Default max generation depth before merge is refused. */
17
+ export const DEFAULT_MAX_GENERATION = 2;
18
+ /** Default fraction of pool to fill with random (non-similar) clusters. */
19
+ export const DEFAULT_RANDOM_CLUSTER_FRACTION = 0.05;
20
+ /**
21
+ * Read the `generation` field from an asset's frontmatter.
22
+ * Returns 0 when absent (no generation metadata = original asset).
23
+ */
24
+ export function readAssetGeneration(frontmatterData) {
25
+ const gen = frontmatterData.generation;
26
+ if (typeof gen === "number" && Number.isFinite(gen) && gen >= 0) {
27
+ return Math.floor(gen);
28
+ }
29
+ return 0;
30
+ }
31
+ /**
32
+ * Compute the new generation for a merged asset.
33
+ * Rule: `merged.generation = max(source generations) + 1`.
34
+ */
35
+ export function computeMergedGeneration(sourceGenerations) {
36
+ if (sourceGenerations.length === 0)
37
+ return 1;
38
+ return Math.max(...sourceGenerations) + 1;
39
+ }
40
+ /**
41
+ * Check whether a merge of the given assets should be refused due to the
42
+ * anti-collapse generation guard.
43
+ *
44
+ * Returns `{ refused: true, reason }` when BOTH assets have generation > maxGeneration.
45
+ * Returns `{ refused: false }` when the merge is allowed.
46
+ *
47
+ * @param sourceGenerations - Generation values for all merge participants.
48
+ * @param config - Anti-collapse config.
49
+ */
50
+ export function checkGenerationGuard(sourceGenerations, config) {
51
+ // R5: default ON — only an explicit opt-out disables the guard.
52
+ if (config.enabled === false)
53
+ return { refused: false };
54
+ const maxGen = config.maxGeneration ?? DEFAULT_MAX_GENERATION;
55
+ const highGenCount = sourceGenerations.filter((g) => g > maxGen).length;
56
+ if (highGenCount >= 2) {
57
+ return {
58
+ refused: true,
59
+ reason: `Anti-collapse: ${highGenCount} merge participants have generation > ${maxGen} (${sourceGenerations.join(", ")}); refusing to merge over-consolidated assets.`,
60
+ };
61
+ }
62
+ return { refused: false };
63
+ }
64
+ /** Distinct-token retention floor default (R5 §4.2). */
65
+ export const DEFAULT_MIN_SPECIFICITY_RETENTION = 0.6;
66
+ function distinctTokens(text) {
67
+ // Same lowercase whitespace tokenization computeBigramDiversity uses.
68
+ return new Set(text
69
+ .toLowerCase()
70
+ .split(/\s+/)
71
+ .filter((w) => w.length > 0));
72
+ }
73
+ /**
74
+ * A merge must strictly increase information (R5 §4.2):
75
+ * 1. Provenance: the merged asset's `source_refs` must be a superset of the
76
+ * union of all participants' `source_refs` plus the participant refs
77
+ * themselves — provenance never shrinks through a merge.
78
+ * 2. Specificity: distinctTokens(mergedBody) ≥ minSpecificityRetention ×
79
+ * |union(distinctTokens(participant bodies))| — a merge that only
80
+ * shortens/genericizes fails.
81
+ *
82
+ * Pure and deterministic; ADVISORY in v1 (the caller counts violations, it
83
+ * does not refuse the merge). Returns `passed: true` immediately when the
84
+ * anti-collapse suite or the floor itself is opted out.
85
+ */
86
+ export function checkMergeInformationFloor(mergedBody, mergedSourceRefs, participants, config) {
87
+ if (config.enabled === false || config.mergeInformationFloor === false || participants.length === 0) {
88
+ return { passed: true, provenanceBefore: 0, provenanceAfter: 0, specificityRetention: 1 };
89
+ }
90
+ // 1. Provenance union: participants + everything they already cited.
91
+ const required = new Set();
92
+ for (const p of participants) {
93
+ required.add(p.ref);
94
+ for (const sr of p.sourceRefs)
95
+ required.add(sr);
96
+ }
97
+ const after = new Set(mergedSourceRefs);
98
+ const missing = [...required].filter((r) => !after.has(r));
99
+ // 2. Specificity retention over the union of source tokens.
100
+ const sourceTokens = new Set();
101
+ for (const p of participants) {
102
+ for (const t of distinctTokens(p.body))
103
+ sourceTokens.add(t);
104
+ }
105
+ const mergedTokens = distinctTokens(mergedBody);
106
+ // Clamped at computation so the pass/fail decision, the reason string, and
107
+ // the reported field all describe the same value.
108
+ const specificityRetention = Math.min(1, sourceTokens.size === 0 ? 1 : mergedTokens.size / sourceTokens.size);
109
+ const minRetention = config.minSpecificityRetention ?? DEFAULT_MIN_SPECIFICITY_RETENTION;
110
+ const provenanceOk = missing.length === 0;
111
+ const specificityOk = specificityRetention >= minRetention;
112
+ const reasons = [];
113
+ if (!provenanceOk) {
114
+ reasons.push(`provenance shrank: merged source_refs missing ${missing.length} ref(s) (e.g. ${missing[0]})`);
115
+ }
116
+ if (!specificityOk) {
117
+ reasons.push(`specificity retention ${specificityRetention.toFixed(2)} < ${minRetention} (merge genericized/shortened)`);
118
+ }
119
+ return {
120
+ passed: provenanceOk && specificityOk,
121
+ provenanceBefore: required.size,
122
+ provenanceAfter: after.size,
123
+ specificityRetention,
124
+ ...(reasons.length > 0 ? { reason: reasons.join("; ") } : {}),
125
+ };
126
+ }
127
+ /**
128
+ * Compute the bigram n-gram diversity of a text string.
129
+ * Returns a value in [0, 1] where 0 = all identical bigrams, 1 = all unique.
130
+ * Used by the lexical-diversity check to detect correlated-extraction artifacts.
131
+ */
132
+ export function computeBigramDiversity(text) {
133
+ const words = text
134
+ .toLowerCase()
135
+ .split(/\s+/)
136
+ .filter((w) => w.length > 0);
137
+ if (words.length < 2)
138
+ return 1; // too short to have bigrams; treat as diverse
139
+ const total = words.length - 1;
140
+ const unique = new Set();
141
+ for (let i = 0; i < total; i++) {
142
+ unique.add(`${words[i]}\t${words[i + 1]}`);
143
+ }
144
+ return unique.size / total;
145
+ }
146
+ /**
147
+ * Check whether a cluster of memories exhibits suspiciously low lexical diversity.
148
+ * When true, the cluster is likely a correlated-extraction artifact; the merge
149
+ * threshold should be raised.
150
+ *
151
+ * @param bodies - The stripped body texts of the cluster members.
152
+ * @param config - Anti-collapse config.
153
+ * @returns `{ lowDiversity: true, diversity }` when the cluster diversity is
154
+ * below the 0.3 threshold; `{ lowDiversity: false }` otherwise.
155
+ */
156
+ export function checkLexicalDiversity(bodies, config) {
157
+ // R5: default ON — only an explicit opt-out disables the check.
158
+ if (config.enabled === false || config.lexicalDiversityCheck === false) {
159
+ return { lowDiversity: false };
160
+ }
161
+ if (bodies.length === 0)
162
+ return { lowDiversity: false };
163
+ // Average bigram diversity across all bodies in the cluster.
164
+ const avg = bodies.reduce((sum, b) => sum + computeBigramDiversity(b), 0) / bodies.length;
165
+ const DIVERSITY_FLOOR = 0.3;
166
+ if (avg < DIVERSITY_FLOOR) {
167
+ return { lowDiversity: true, diversity: avg };
168
+ }
169
+ return { lowDiversity: false };
170
+ }
@@ -0,0 +1,161 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ // ---------------------------------------------------------------------------
5
+ // Reliability computation
6
+ // ---------------------------------------------------------------------------
7
+ /** Number of fixed-width reliability buckets over [0, 1]. */
8
+ export const CALIBRATION_BUCKET_COUNT = 10;
9
+ function roundRate(value) {
10
+ return Number(value.toFixed(4));
11
+ }
12
+ /**
13
+ * Assign a confidence in [0, 1] to one of {@link CALIBRATION_BUCKET_COUNT}
14
+ * fixed-width buckets. The final bucket is closed on the right so confidence
15
+ * === 1 lands in the top bucket rather than overflowing.
16
+ */
17
+ function bucketIndex(confidence) {
18
+ const clamped = Math.min(1, Math.max(0, confidence));
19
+ const idx = Math.floor(clamped * CALIBRATION_BUCKET_COUNT);
20
+ return Math.min(CALIBRATION_BUCKET_COUNT - 1, idx);
21
+ }
22
+ /**
23
+ * Compute a deterministic calibration summary from a list of acted-on gate
24
+ * decisions. Pure: identical input always yields identical output, with no
25
+ * dependency on wall-clock time or randomness.
26
+ */
27
+ export function summarizeCalibration(samples) {
28
+ const buckets = [];
29
+ const bucketCounts = new Array(CALIBRATION_BUCKET_COUNT).fill(0);
30
+ const bucketAccepted = new Array(CALIBRATION_BUCKET_COUNT).fill(0);
31
+ const bucketConfSum = new Array(CALIBRATION_BUCKET_COUNT).fill(0);
32
+ let accepted = 0;
33
+ let confSum = 0;
34
+ for (const sample of samples) {
35
+ const idx = bucketIndex(sample.confidence);
36
+ bucketCounts[idx] = (bucketCounts[idx] ?? 0) + 1;
37
+ bucketConfSum[idx] = (bucketConfSum[idx] ?? 0) + sample.confidence;
38
+ confSum += sample.confidence;
39
+ if (sample.outcome === "auto-accepted") {
40
+ accepted += 1;
41
+ bucketAccepted[idx] = (bucketAccepted[idx] ?? 0) + 1;
42
+ }
43
+ }
44
+ const total = samples.length;
45
+ const width = 1 / CALIBRATION_BUCKET_COUNT;
46
+ for (let i = 0; i < CALIBRATION_BUCKET_COUNT; i += 1) {
47
+ const count = bucketCounts[i] ?? 0;
48
+ const acc = bucketAccepted[i] ?? 0;
49
+ buckets.push({
50
+ lower: roundRate(i * width),
51
+ upper: roundRate((i + 1) * width),
52
+ count,
53
+ accepted: acc,
54
+ acceptRate: count > 0 ? roundRate(acc / count) : 0,
55
+ meanConfidence: count > 0 ? roundRate((bucketConfSum[i] ?? 0) / count) : 0,
56
+ });
57
+ }
58
+ const overallAcceptRate = total > 0 ? roundRate(accepted / total) : 0;
59
+ const meanConfidence = total > 0 ? roundRate(confSum / total) : 0;
60
+ return {
61
+ samples: total,
62
+ accepted,
63
+ rejected: total - accepted,
64
+ overallAcceptRate,
65
+ meanConfidence,
66
+ calibrationGap: total > 0 ? roundRate(meanConfidence - overallAcceptRate) : 0,
67
+ buckets,
68
+ };
69
+ }
70
+ /**
71
+ * Project a list of `gateDecision` records (read from the proposal store) into
72
+ * the acted-on calibration samples within an optional `[since, until)` window.
73
+ *
74
+ * Only `auto-accepted` / `auto-rejected` decisions with a finite confidence in
75
+ * [0, 1] contribute. `deferred` decisions and decisions missing a confidence
76
+ * are excluded (no realized accept/reject signal). The window filter uses each
77
+ * decision's `decidedAt` timestamp; decisions with an unparseable timestamp are
78
+ * kept only when no window is supplied.
79
+ *
80
+ * Exploration-budget promotions (`reason === "exploration-budget"`) are EXCLUDED:
81
+ * they are accepted regardless of confidence, so they carry no reliability signal
82
+ * about the gate threshold. Counting them would inflate the apparent accept-rate
83
+ * and bias the auto-tuner downward — they are exempt from auto-tune by design
84
+ * (WS-4 exploration budget).
85
+ */
86
+ export function gateDecisionsToSamples(decisions, window) {
87
+ const sinceMs = window?.since ? new Date(window.since).getTime() : undefined;
88
+ const untilMs = window?.until ? new Date(window.until).getTime() : undefined;
89
+ const samples = [];
90
+ for (const decision of decisions) {
91
+ if (!decision)
92
+ continue;
93
+ if (decision.outcome !== "auto-accepted" && decision.outcome !== "auto-rejected")
94
+ continue;
95
+ if (decision.reason === "exploration-budget")
96
+ continue;
97
+ const confidence = decision.confidence;
98
+ if (typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1)
99
+ continue;
100
+ if (sinceMs !== undefined || untilMs !== undefined) {
101
+ const ts = new Date(decision.decidedAt).getTime();
102
+ if (!Number.isFinite(ts))
103
+ continue;
104
+ if (sinceMs !== undefined && ts < sinceMs)
105
+ continue;
106
+ if (untilMs !== undefined && ts >= untilMs)
107
+ continue;
108
+ }
109
+ samples.push({ confidence, outcome: decision.outcome });
110
+ }
111
+ return samples;
112
+ }
113
+ /**
114
+ * Compute a bounded, opt-in threshold adjustment from a calibration summary.
115
+ * PURE and deterministic — does not mutate config or read the clock. The
116
+ * caller is responsible for persisting `newThreshold` and logging the result.
117
+ *
118
+ * Algorithm (deliberately simple and bounded):
119
+ * - When `autoTune` is false → no-op (`disabled`).
120
+ * - When samples < `minSamples` → no-op (`insufficient-samples`).
121
+ * - Otherwise nudge by at most `maxStep` toward `targetAcceptRate`, then
122
+ * clamp into `[minThreshold, maxThreshold]`. The step size scales with the
123
+ * gap from target but is capped, so a single run can never make a large
124
+ * swing.
125
+ */
126
+ export function computeThresholdAutoTune(currentThreshold, summary, config) {
127
+ const previousThreshold = Math.round(currentThreshold);
128
+ const noop = (reason) => ({
129
+ adjusted: false,
130
+ previousThreshold,
131
+ newThreshold: previousThreshold,
132
+ delta: 0,
133
+ reason,
134
+ });
135
+ if (!config.autoTune)
136
+ return noop("disabled");
137
+ if (summary.samples < config.minSamples)
138
+ return noop("insufficient-samples");
139
+ const gap = config.targetAcceptRate - summary.overallAcceptRate;
140
+ // A small dead-band so tiny noise doesn't churn the threshold every run.
141
+ const DEAD_BAND = 0.01;
142
+ if (Math.abs(gap) <= DEAD_BAND)
143
+ return noop("within-target");
144
+ // gap > 0 ⇒ realized below target ⇒ raise threshold (be stricter).
145
+ // gap < 0 ⇒ realized above target ⇒ lower threshold (be more permissive).
146
+ const direction = gap > 0 ? 1 : -1;
147
+ // Scale the step with the gap magnitude (in points) but cap at maxStep.
148
+ const desiredMagnitude = Math.min(config.maxStep, Math.max(1, Math.round(Math.abs(gap) * 100)));
149
+ const proposed = previousThreshold + direction * desiredMagnitude;
150
+ const clamped = Math.min(config.maxThreshold, Math.max(config.minThreshold, proposed));
151
+ const delta = clamped - previousThreshold;
152
+ if (delta === 0)
153
+ return noop("clamped-at-bound");
154
+ return {
155
+ adjusted: true,
156
+ previousThreshold,
157
+ newThreshold: clamped,
158
+ delta,
159
+ reason: direction > 0 ? "below-target-raise" : "above-target-lower",
160
+ };
161
+ }