akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +731 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +361 -751
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -441
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -2,77 +2,27 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * Shared content-quality validators consumed by the improve pipeline
6
- * (`distill`, `consolidate`, `reflect`) and by the `proposal accept` gate.
5
+ * Content-quality validators for the improve stages and `proposal accept`.
7
6
  *
8
- * ## Reflect size gate — calibrated blended formula (2026-05-22)
9
- *
10
- * ### Distribution baseline (n=844 reflect-eligible stash assets)
11
- *
12
- * min=1 p10=371 p25=778 p50=1508 p75=5456 p90=11721 p99=43463 max=298010 bytes
13
- * Buckets: <500=135 (16%), 500–2000=340 (40%), 2000–8000=222 (26%), >8000=147 (17%)
14
- *
15
- * ### Problem with the original fixed-ratio gate
16
- *
17
- * - Small sources (~420 bytes, 16th pct): the 200% expansion ceiling fires at
18
- * only 840 bytes proposed — one good paragraph. Hair-trigger for a terse
19
- * reference note.
20
- * - Large sources (~7KB, 75th pct): 200% ceiling = 14KB; reasonable, but a hard
21
- * cap prevents runaway expansion from LLM hallucinations.
22
- *
23
- * ### Blended-bound formula
24
- *
25
- * Shrinkage floor (accept if proposed >= lower):
26
- * lower = max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)
27
- * → For tiny sources (sourceLen < 300), the absolute floor dominates so a
28
- * genuinely tightened note still passes.
29
- * → For large sources (>1KB), the ratio floor dominates (50% of 7KB = 3.5KB).
30
- *
31
- * Expansion ceiling (accept if proposed <= upper):
32
- * upper = max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES)
33
- * …but always capped at REFLECT_ABSOLUTE_MAX_BYTES.
34
- * → For small sources (≤778 bytes, p25), the absolute ceiling (2000 bytes)
35
- * dominates — one substantive paragraph is always acceptable.
36
- * → For medium/large sources (>1KB), the ratio ceiling dominates.
37
- * → Any proposal exceeding 25000 bytes is always rejected regardless of ratio.
38
- *
39
- * ### Constant calibration rationale
40
- *
41
- * REFLECT_ABSOLUTE_FLOOR_BYTES = 150
42
- * Half of p10 (371) ≈ 185; we set 150 so even very aggressive condensation
43
- * of a seed note is allowed down to roughly a two-sentence summary.
44
- *
45
- * REFLECT_ABSOLUTE_CEILING_BYTES = 2500
46
- * Raised from 2000 (2026-05-22): small-source rejections at 248–281% on
47
- * 900–953 byte assets were borderline false positives. 2500 gives a short
48
- * lesson or command ~1.5KB of room to grow before the absolute kicks in.
49
- *
50
- * REFLECT_ABSOLUTE_MAX_BYTES = 25000
51
- * Below p99 (43463). Catches genuine LLM runaway (whole-chapter insertions)
52
- * without blocking legitimate large rewrites of large sources.
53
- *
54
- * REFLECT_EXPAND_RATIO_MAX = 2.5
55
- * Raised from 2.0 (2026-05-22): 2× was too tight for dense short assets
56
- * (lessons, commands) that have legitimate room to grow. 2.5× resolves
57
- * 248% expansion on a 900-byte lesson while still catching 281%+ on ~1KB
58
- * assets where the absolute ceiling takes over.
7
+ * The reflect size gate bounds a rewrite's body against its source's:
8
+ * shrinking below max(50% of the source, 150 bytes) suggests deleted content,
9
+ * growing past min(max(250% of the source, 2500 bytes), 25000 bytes) suggests
10
+ * speculation. The absolute bounds keep small assets (a p25 source is ~780
11
+ * bytes) from tripping on one good paragraph, and 25000 (below p99) still
12
+ * catches runaway expansion. Sources under 200 bytes are too noisy to judge.
59
13
  */
60
- // ── Reflect-size guard ───────────────────────────────────────────────────────
61
14
  import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
62
15
  import { parseRefInput } from "../../../core/asset/resolve-ref.js";
63
16
  import { DESCRIPTION_MAX_CHARS, DESCRIPTION_MIN_CHARS, WHEN_TO_USE_MAX_CHARS, WHEN_TO_USE_MIN_CHARS, } from "../../../core/authoring-rules.js";
64
17
  import { containsRedactedContent, containsReflectPromptScaffolding, REDACTED_CONTENT_MARKER, REFLECT_AVOID_PATTERNS_HEADING, } from "../../../core/content-safety.js";
65
18
  import { proposalContent } from "../../../core/file-change.js";
66
- /**
67
- * The canonical asset NAME an inputRef names, lower-cased — the tail the
68
- * "just restates the ref" heuristics compare against. WI-8.5c: the ref is the
69
- * conceptId (`<subdir>/<name>`), so the name is parsed off the conceptId.
70
- */
19
+ import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
20
+ import { REFLECT_TRUNCATION_MARKER } from "../../../integrations/agent/prompts.js";
21
+ import { splitFrontmatter } from "../../improve/reflect-noise.js";
22
+ /** The asset name a ref names, lower-cased — what the "just restates the ref" checks compare. */
71
23
  function refNameTail(inputRef) {
72
24
  return parseRefInput(inputRef).name.toLowerCase();
73
25
  }
74
- import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
75
- import { REFLECT_TRUNCATION_MARKER } from "../../../integrations/agent/prompts.js";
76
26
  // ── Description / when_to_use shape ─────────────────────────────────────────
77
27
  export const HEADING_FRAGMENT_PATTERNS = [
78
28
  /^for example\b/i,
@@ -164,9 +114,8 @@ export function detectDoubleFrontmatter(content) {
164
114
  kind: "double-frontmatter-fence",
165
115
  message: `Content contains ${fenceLines.length} \`---\` fence lines; assets with frontmatter must have exactly 2 (one open, one close).`,
166
116
  };
167
- const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
168
- const pseudoLine = body
169
- .split(/\r?\n/)
117
+ const pseudoLine = splitFrontmatter(content)
118
+ .body.split(/\r?\n/)
170
119
  .find((l) => /^\s*(\*\*|__)?\s*(description|when_to_use)\s*(\*\*|__)?\s*:/i.test(l));
171
120
  if (pseudoLine)
172
121
  return {
@@ -191,50 +140,13 @@ export function hasSupersededStatus(frontmatter) {
191
140
  export function hasHotCaptureMode(frontmatter) {
192
141
  return frontmatter?.captureMode === "hot";
193
142
  }
194
- // ── Consolidate merge size gate ──────────────────────────────────────────────
195
- // ── Reflect size gate ────────────────────────────────────────────────────────
196
- /** Ratio lower-bound: proposed body must be at least this fraction of source. */
197
143
  export const REFLECT_SHRINK_RATIO_MIN = 0.5;
198
- /** Ratio upper-bound: proposed body must not exceed this fraction of source. */
199
144
  export const REFLECT_EXPAND_RATIO_MAX = 2.5;
200
- /**
201
- * Below this byte count, ratio checks are too noisy — skip them entirely.
202
- * Unchanged from the original gate.
203
- */
204
145
  export const REFLECT_SIZE_GUARD_MIN_BYTES = 200;
205
- /**
206
- * Absolute shrinkage floor (bytes). Even if `ratio * sourceLen` is lower, a
207
- * proposed body of at least this many bytes is always accepted on the shrinkage
208
- * side. Protects against false positives when the source is small (<300 bytes).
209
- */
210
146
  export const REFLECT_ABSOLUTE_FLOOR_BYTES = 150;
211
- /**
212
- * Absolute expansion ceiling (bytes). Even if `ratio * sourceLen` is lower, a
213
- * proposed body up to this many bytes is always accepted on the expansion side.
214
- * Protects against false positives when the source is small (≤778 bytes, p25).
215
- */
216
147
  export const REFLECT_ABSOLUTE_CEILING_BYTES = 2500;
217
- /**
218
- * Hard expansion cap (bytes). Regardless of ratio, a proposed body exceeding
219
- * this limit is always rejected. Guards against runaway LLM hallucinations on
220
- * large sources.
221
- */
222
148
  export const REFLECT_ABSOLUTE_MAX_BYTES = 25000;
223
- /**
224
- * Calibrated size check: compare proposed body length against source body
225
- * length using a blended-bound formula.
226
- *
227
- * **Shrinkage** — accept if:
228
- * `proposedLen >= max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)`
229
- *
230
- * **Expansion** — accept if:
231
- * `proposedLen <= min(max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES), REFLECT_ABSOLUTE_MAX_BYTES)`
232
- *
233
- * Returns `{ ok: true }` when:
234
- * - `sourceBody` is absent or `undefined`
235
- * - source body is shorter than {@link REFLECT_SIZE_GUARD_MIN_BYTES}
236
- * - the proposed length is within the blended bounds
237
- */
149
+ /** The reflect size gate (see the module note); no source or a tiny one passes. */
238
150
  export function checkReflectSize(sourceBody, proposedBody) {
239
151
  if (typeof sourceBody !== "string")
240
152
  return { ok: true };
@@ -243,19 +155,14 @@ export function checkReflectSize(sourceBody, proposedBody) {
243
155
  return { ok: true };
244
156
  const proposedLen = proposedBody.trim().length;
245
157
  const ratio = proposedLen / sourceLen;
246
- // Shrinkage check: lower bound = max(ratio floor, absolute floor)
247
- const shrinkFloor = Math.max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES);
248
- if (proposedLen < shrinkFloor) {
158
+ if (proposedLen < Math.max(REFLECT_SHRINK_RATIO_MIN * sourceLen, REFLECT_ABSOLUTE_FLOOR_BYTES)) {
249
159
  return { ok: false, code: "EXCESSIVE_SHRINKAGE", ratio };
250
160
  }
251
- // Expansion check: upper bound = min(max(ratio ceiling, absolute ceiling), hard cap)
252
161
  const expandCeiling = Math.min(Math.max(REFLECT_EXPAND_RATIO_MAX * sourceLen, REFLECT_ABSOLUTE_CEILING_BYTES), REFLECT_ABSOLUTE_MAX_BYTES);
253
- if (proposedLen > expandCeiling) {
162
+ if (proposedLen > expandCeiling)
254
163
  return { ok: false, code: "EXCESSIVE_EXPANSION", ratio };
255
- }
256
164
  return { ok: true };
257
165
  }
258
- // ── ProposalValidator entries (registered with proposal-validators.ts) ──────
259
166
  const descriptionQualityValidator = {
260
167
  name: "description-quality",
261
168
  appliesTo(_proposal, ctx) {
@@ -282,6 +189,46 @@ const descriptionQualityValidator = {
282
189
  ];
283
190
  },
284
191
  };
192
+ /**
193
+ * The lesson checks distill and accept share: a valid description and
194
+ * when_to_use that differ, and no pseudo-frontmatter in the body. `text`
195
+ * continues a caller-specific subject.
196
+ */
197
+ export function lessonQualityIssues(fm, content, inputRef) {
198
+ const issues = [];
199
+ const descCheck = isValidDescription(fm.description, inputRef);
200
+ if (!descCheck.ok) {
201
+ issues.push({
202
+ kind: "invalid-description",
203
+ field: "description",
204
+ text: ` has an invalid description: ${descCheck.reason}.`,
205
+ ...(descCheck.severity ? { severity: descCheck.severity } : {}),
206
+ });
207
+ }
208
+ const wtuCheck = isValidWhenToUse(fm.when_to_use, inputRef);
209
+ if (!wtuCheck.ok) {
210
+ issues.push({
211
+ kind: "invalid-when_to_use",
212
+ field: "when_to_use",
213
+ text: ` has an invalid when_to_use: ${wtuCheck.reason}.`,
214
+ });
215
+ }
216
+ if (descCheck.ok &&
217
+ wtuCheck.ok &&
218
+ typeof fm.description === "string" &&
219
+ typeof fm.when_to_use === "string" &&
220
+ fm.description.trim().toLowerCase() === fm.when_to_use.trim().toLowerCase()) {
221
+ issues.push({
222
+ kind: "description-equals-when_to_use",
223
+ field: "description",
224
+ text: " has identical description and when_to_use.",
225
+ });
226
+ }
227
+ const dfm = detectDoubleFrontmatter(content);
228
+ if (dfm)
229
+ issues.push({ kind: dfm.kind, field: "body", text: `: ${dfm.message}` });
230
+ return issues;
231
+ }
285
232
  const lessonContentQualityValidator = {
286
233
  name: "lesson-content-quality",
287
234
  appliesTo(_proposal, ctx) {
@@ -297,34 +244,11 @@ const lessonContentQualityValidator = {
297
244
  catch {
298
245
  return [];
299
246
  }
300
- const findings = [];
301
- const descCheck = isValidDescription(fm.description, proposal.ref);
302
- if (!descCheck.ok)
303
- findings.push({
304
- kind: "invalid-description",
305
- message: `Lesson proposal ${proposal.id} (${proposal.ref}) has an invalid description: ${descCheck.reason}.`,
306
- ...(descCheck.severity ? { severity: descCheck.severity } : {}),
307
- });
308
- const wtuCheck = isValidWhenToUse(fm.when_to_use, proposal.ref);
309
- if (!wtuCheck.ok)
310
- findings.push({
311
- kind: "invalid-when_to_use",
312
- message: `Lesson proposal ${proposal.id} (${proposal.ref}) has an invalid when_to_use: ${wtuCheck.reason}.`,
313
- });
314
- if (descCheck.ok &&
315
- wtuCheck.ok &&
316
- typeof fm.description === "string" &&
317
- typeof fm.when_to_use === "string" &&
318
- fm.description.trim().toLowerCase() === fm.when_to_use.trim().toLowerCase()) {
319
- findings.push({
320
- kind: "description-equals-when_to_use",
321
- message: `Lesson proposal ${proposal.id} (${proposal.ref}) has identical description and when_to_use.`,
322
- });
323
- }
324
- const dfm = detectDoubleFrontmatter(proposalContent(proposal));
325
- if (dfm)
326
- findings.push({ kind: dfm.kind, message: `Lesson proposal ${proposal.id} (${proposal.ref}): ${dfm.message}` });
327
- return findings;
247
+ return lessonQualityIssues(fm, proposalContent(proposal), proposal.ref).map((issue) => ({
248
+ kind: issue.kind,
249
+ message: `Lesson proposal ${proposal.id} (${proposal.ref})${issue.text}`,
250
+ ...(issue.severity ? { severity: issue.severity } : {}),
251
+ }));
328
252
  },
329
253
  };
330
254
  const sourceNotSupersededValidator = {
@@ -344,18 +268,14 @@ const sourceNotSupersededValidator = {
344
268
  return [];
345
269
  },
346
270
  };
347
- /** Strip an opening frontmatter block (`---\n…\n---`) from `content`, returning the body. */
348
- function stripFrontmatterBody(content) {
349
- return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
350
- }
351
271
  const reflectSizeGuardValidator = {
352
272
  name: "reflect-size-guard",
353
273
  appliesTo(proposal, ctx) {
354
274
  return proposal.source === "reflect" && typeof ctx.source?.content === "string";
355
275
  },
356
276
  validate(proposal, ctx) {
357
- const sourceBody = stripFrontmatterBody(ctx.source?.content ?? "");
358
- const proposedBody = typeof proposal.payload?.content === "string" ? stripFrontmatterBody(proposalContent(proposal)) : "";
277
+ const sourceBody = splitFrontmatter(ctx.source?.content ?? "").body;
278
+ const proposedBody = typeof proposal.payload?.content === "string" ? splitFrontmatter(proposalContent(proposal)).body : "";
359
279
  const outcome = checkReflectSize(sourceBody, proposedBody);
360
280
  if (outcome.ok)
361
281
  return [];
@@ -373,16 +293,10 @@ const reflectSizeGuardValidator = {
373
293
  },
374
294
  };
375
295
  /**
376
- * Accept-time data-loss guard (#952 Addendum). `sanitizeReflectPayload`
377
- * already defers a proposal whose body echoes {@link REFLECT_TRUNCATION_MARKER}
378
- * (the notice appended when the source asset was too large to send in full)
379
- * with `reflect-truncation-leak` — but that is a creation-time check, and a
380
- * proposal can reach `proposal accept` / drain promotion without ever going
381
- * through it (e.g. a defer that a human then accepts anyway, or a future
382
- * reflect code path that mints proposals directly). A leaked marker replacing
383
- * real asset content on disk is data loss, not a quality nit, so unlike the
384
- * rest of this file's validators this one is NOT wrapped by {@link advisory}
385
- * — it blocks acceptance the same way the generic/canonical validators do.
296
+ * A body still carrying {@link REFLECT_TRUNCATION_MARKER} (its source was too
297
+ * large to send in full) would overwrite the asset with an incomplete rewrite:
298
+ * data loss, so this blocks at accept even for a proposal that never passed
299
+ * reflect's own creation-time check (#952).
386
300
  */
387
301
  const reflectTruncationMarkerValidator = {
388
302
  name: "reflect-truncation-marker",
@@ -435,19 +349,9 @@ const reflectPromptScaffoldingValidator = {
435
349
  },
436
350
  };
437
351
  /**
438
- * Report a validator's findings as advisory.
439
- *
440
- * These validators judge prose quality — a description that reads like a
441
- * heading, an odd backtick count, a body that grew more than the reflect
442
- * ratio allows. They used to BLOCK `proposal accept`, which a human types
443
- * after reading the diff, and the error told that human to "fix the proposal
444
- * payload and try again" — but there is no `akm proposal edit` and `accept`
445
- * takes no `--force`, so the only way out was hand-editing the proposals
446
- * database. A blocking check whose remedy does not exist is not a check.
447
- *
448
- * Structural findings stay blocking: an empty body, an unparseable ref,
449
- * malformed frontmatter and a broken workflow shape genuinely cannot be
450
- * written, and they live in {@link defaultProposalValidators}.
352
+ * Prose-quality findings only advise: a human accepting after reading the diff
353
+ * has no way to edit the proposal, so blocking on them left no remedy.
354
+ * Structural findings (in the default validators) still block.
451
355
  */
452
356
  function advisory(validator) {
453
357
  return {
@@ -455,14 +359,7 @@ function advisory(validator) {
455
359
  validate: (proposal, ctx) => validator.validate(proposal, ctx).map((finding) => ({ ...finding, severity: "warn" })),
456
360
  };
457
361
  }
458
- /**
459
- * Full set of quality validators in registration order. Appended onto
460
- * {@link defaultProposalValidators} so they run inside `validateProposal` on
461
- * `proposal accept` automatically. All prose-quality checks report without
462
- * blocking (see {@link advisory}). The truncation-marker, redacted-content,
463
- * and reflected-prompt-scaffolding validators block because they protect
464
- * durable content rather than judging prose quality.
465
- */
362
+ /** The quality validators `validateProposal` runs; the last three protect durable content and block. */
466
363
  export const defaultProposalQualityValidators = [
467
364
  ...[
468
365
  descriptionQualityValidator,
@@ -6,7 +6,7 @@ import { parseRefInput } from "../../../core/asset/resolve-ref.js";
6
6
  import { proposalContent } from "../../../core/file-change.js";
7
7
  import { lintLessonContent } from "../../../core/lesson-lint.js";
8
8
  import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
9
- import { compileWorkflowSource } from "../../../workflows/source-ir/compile.js";
9
+ import { compileWorkflowSource } from "../../../workflows/compile.js";
10
10
  import { defaultProposalQualityValidators } from "./proposal-quality-validators.js";
11
11
  const genericProposalValidator = {
12
12
  name: "generic-proposal-validator",
@@ -1,105 +1,29 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /**
5
- * Proposal validation and content repair.
6
- *
7
- * The proposal repository and domain service moved to `../repository.ts` (#578
8
- * storage consolidation). This module keeps only the two proposal *validators* — {@link validateProposal} and
9
- * {@link repairProposalContent}.
10
- */
4
+ /** Proposal validation and the one content repair applied before promotion. */
11
5
  import { repairTruncatedDescription } from "../../../core/text-truncation.js";
6
+ import { splitFrontmatter } from "../../improve/reflect-noise.js";
12
7
  import { runProposalValidators } from "./proposal-validators.js";
13
8
  /**
14
- * Validate a proposal payload before promotion. Generic by default — any
15
- * proposal must parse cleanly and carry a non-empty body. Asset types in the
16
- * fail-closed canonical registry run their existing parser or validator; types
17
- * without one remain on generic validation.
9
+ * Validate a proposal before promotion: it must parse and carry a body, and a
10
+ * type with a canonical validator runs it.
18
11
  */
19
12
  export function validateProposal(proposal) {
20
13
  return runProposalValidators(proposal);
21
14
  }
22
- // ── Content repair ──────────────────────────────────────────────────────────
23
15
  /**
24
- * Attempt bounded, deterministic repair of mechanically-fixable defects in a
25
- * proposal's markdown content. NEVER fabricates text — only strips known-bad
26
- * structure and applies {@link repairTruncatedDescription} to a truncated
27
- * description when one is detected.
28
- *
29
- * Repairs performed:
30
- * 1. Apply {@link repairTruncatedDescription} to a truncated/hanging
31
- * `description` field in the frontmatter.
32
- *
33
- * It deliberately does NOT delete body lines. Two earlier repairs dropped
34
- * every body line that restated a frontmatter key and every `---` in a body
35
- * with frontmatter. Both fired inside fenced code blocks, so any asset
36
- * documenting frontmatter — a note about akm, Claude Code skills, Jekyll,
37
- * Hugo — was silently gutted on `proposal accept`, and the repaired bytes
38
- * were written back over the original in the proposals database. A repair
39
- * that can destroy content is not a repair.
40
- *
41
- * Returns the repaired content string. When no repairs apply the input is
42
- * returned byte-identical so callers can use strict equality to detect
43
- * whether a repair actually happened.
44
- *
45
- * CRITICAL: This function is CONTENT-PRESERVING. Callers MUST re-validate the
46
- * repaired output via {@link validateProposal} / {@link runProposalValidators}
47
- * before promotion — a repair that makes things *worse* (or is simply
48
- * insufficient) must be caught by the existing gate.
16
+ * Normalize line endings and complete a truncated frontmatter `description`
17
+ * (`repairTruncatedDescription`, with the body as context). Nothing else: an
18
+ * earlier repair that deleted body lines gutted any asset documenting
19
+ * frontmatter. Callers re-validate the result.
49
20
  */
50
21
  export function repairProposalContent(content) {
51
22
  if (typeof content !== "string" || content.trim() === "")
52
23
  return content;
53
- // Determine whether the content has a frontmatter block so we know how
54
- // many `---` fence lines are expected.
55
- const hasFrontmatter = /^---\r?\n[\s\S]*?\r?\n---/.test(content);
56
- // Split into lines for structural repairs.
57
- const lines = content.split(/\r?\n/);
58
- // Track whether we are inside the opening frontmatter block so we can
59
- // leave it untouched and only repair the body.
60
- let inFrontmatter = false;
61
- // Frontmatter fence index tracking: first fence opens FM, second closes it.
62
- let fmOpenSeen = false;
63
- let fmCloseSeen = false;
64
- const repairedLines = [];
65
- for (const line of lines) {
66
- const isFence = /^---\s*$/.test(line);
67
- // Track frontmatter fences (first two `---` fences delimit the FM block).
68
- if (isFence && !fmCloseSeen) {
69
- if (!fmOpenSeen) {
70
- fmOpenSeen = true;
71
- inFrontmatter = true;
72
- repairedLines.push(line);
73
- continue;
74
- }
75
- if (inFrontmatter) {
76
- fmCloseSeen = true;
77
- inFrontmatter = false;
78
- repairedLines.push(line);
79
- continue;
80
- }
81
- }
82
- // We are now in the body (past the frontmatter or no frontmatter).
83
- if (inFrontmatter) {
84
- // Still inside the frontmatter — keep as-is.
85
- repairedLines.push(line);
86
- continue;
87
- }
88
- repairedLines.push(line);
89
- }
90
- let repaired = repairedLines.join("\n");
91
- // Repair 3: Apply repairTruncatedDescription to the description field.
92
- // We operate on the raw text rather than re-parsing YAML to avoid
93
- // reformatting unrelated frontmatter keys.
94
- if (hasFrontmatter) {
95
- // Extract the body text (after the second `---`) so we can pass it to
96
- // repairTruncatedDescription as context for the swap-in heuristic.
97
- const bodyMatch = repaired.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
98
- const bodyText = bodyMatch?.[1] ?? "";
99
- repaired = repaired.replace(/^(description:\s*)(.*?)(\r?\n)/m, (_match, prefix, rawDesc, nl) => {
100
- const fixed = repairTruncatedDescription(rawDesc.trim(), bodyText);
101
- return `${prefix}${fixed}${nl}`;
102
- });
103
- }
104
- return repaired;
24
+ const repaired = content.replace(/\r\n/g, "\n");
25
+ const { fmText, body } = splitFrontmatter(repaired);
26
+ if (fmText === null)
27
+ return repaired;
28
+ return repaired.replace(/^(description:\s*)(.*?)(\r?\n)/m, (_match, prefix, rawDesc, nl) => `${prefix}${repairTruncatedDescription(rawDesc.trim(), body)}${nl}`);
105
29
  }