akm-cli 0.9.16 → 0.9.17-alpha.10

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 (403) hide show
  1. package/CHANGELOG.md +2101 -0
  2. package/STABILITY.md +11 -10
  3. package/dist/akm +124 -193
  4. package/dist/akm-migrate +38 -19
  5. package/dist/assets/hints/cli-hints-full.md +6 -7
  6. package/dist/assets/improve-strategies/catchup.json +0 -3
  7. package/dist/assets/improve-strategies/consolidate.json +0 -1
  8. package/dist/assets/improve-strategies/default.json +1 -2
  9. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
  10. package/dist/assets/improve-strategies/quick.json +1 -2
  11. package/dist/assets/improve-strategies/reflect-distill.json +1 -2
  12. package/dist/assets/improve-strategies/thorough.json +0 -3
  13. package/dist/assets/prompts/consolidate-pair.md +20 -0
  14. package/dist/assets/prompts/consolidate-system.md +4 -11
  15. package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
  17. package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
  18. package/dist/assets/templates/html/health.html +3 -5
  19. package/dist/cli/retired-commands.js +1 -1
  20. package/dist/cli/shared.js +6 -2
  21. package/dist/cli/unknown-flags.js +24 -1
  22. package/dist/cli.js +68 -10
  23. package/dist/commands/agent/agent-dispatch.js +1 -1
  24. package/dist/commands/command/command-execution.js +24 -62
  25. package/dist/commands/feedback-cli.js +0 -1
  26. package/dist/commands/health/accept-rate.js +6 -0
  27. package/dist/commands/health/archive-usage.js +92 -0
  28. package/dist/commands/health/checks.js +83 -74
  29. package/dist/commands/health/config-skew.js +38 -0
  30. package/dist/commands/health/data-dir-usage.js +25 -13
  31. package/dist/commands/health/egress.js +54 -0
  32. package/dist/commands/health/html-report.js +1 -42
  33. package/dist/commands/health/improve-metrics.js +136 -591
  34. package/dist/commands/health/md-report.js +1 -6
  35. package/dist/commands/health/plugin-staleness.js +53 -3
  36. package/dist/commands/health/renderers.js +12 -4
  37. package/dist/commands/health/report-view-model.js +14 -120
  38. package/dist/commands/health/types-improve.js +4 -19
  39. package/dist/commands/health/windows.js +64 -74
  40. package/dist/commands/health.js +145 -143
  41. package/dist/commands/improve/consolidate/chunking.js +26 -117
  42. package/dist/commands/improve/consolidate/continuity-check.js +137 -0
  43. package/dist/commands/improve/consolidate/pair-pass.js +791 -0
  44. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  45. package/dist/commands/improve/consolidate.js +589 -1127
  46. package/dist/commands/improve/content-hash.js +16 -24
  47. package/dist/commands/improve/distill/content-repair.js +18 -100
  48. package/dist/commands/improve/distill-guards.js +20 -81
  49. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  50. package/dist/commands/improve/distill.js +608 -1041
  51. package/dist/commands/improve/eligibility.js +126 -390
  52. package/dist/commands/improve/execution.js +8 -10
  53. package/dist/commands/improve/extract-prompt.js +1 -2
  54. package/dist/commands/improve/extract.js +487 -1046
  55. package/dist/commands/improve/feedback-valence.js +0 -25
  56. package/dist/commands/improve/improve-cli.js +75 -169
  57. package/dist/commands/improve/improve-result-file.js +10 -66
  58. package/dist/commands/improve/improve-strategies.js +52 -4
  59. package/dist/commands/improve/improve-usage-report.js +18 -64
  60. package/dist/commands/improve/improve.js +480 -1074
  61. package/dist/commands/improve/ledger.js +119 -0
  62. package/dist/commands/improve/locks.js +2 -8
  63. package/dist/commands/improve/loop-stages.js +415 -1073
  64. package/dist/commands/improve/memory/derived-ref.js +12 -77
  65. package/dist/commands/improve/memory/memory-belief.js +16 -118
  66. package/dist/commands/improve/memory/memory-improve.js +266 -14
  67. package/dist/commands/improve/outcome-loop.js +28 -156
  68. package/dist/commands/improve/planner.js +5 -15
  69. package/dist/commands/improve/preparation.js +779 -2319
  70. package/dist/commands/improve/proactive-maintenance.js +34 -101
  71. package/dist/commands/improve/reflect-noise.js +104 -280
  72. package/dist/commands/improve/reflect.js +642 -1353
  73. package/dist/commands/improve/retrieval-gate.js +127 -0
  74. package/dist/commands/improve/retrieval-scope.js +92 -0
  75. package/dist/commands/improve/salience.js +41 -240
  76. package/dist/commands/improve/session-asset.js +19 -100
  77. package/dist/commands/improve/stage.js +322 -0
  78. package/dist/commands/lint/base-linter.js +37 -15
  79. package/dist/commands/proposal/drain.js +261 -578
  80. package/dist/commands/proposal/proposal-cli.js +19 -20
  81. package/dist/commands/proposal/proposal-types.js +31 -24
  82. package/dist/commands/proposal/proposal.js +38 -8
  83. package/dist/commands/proposal/propose.js +134 -160
  84. package/dist/commands/proposal/repository.js +1097 -1394
  85. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  86. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  87. package/dist/commands/proposal/validators/proposals.js +22 -89
  88. package/dist/commands/read/curate.js +105 -462
  89. package/dist/commands/read/knowledge.js +3 -2
  90. package/dist/commands/read/search-cli.js +16 -33
  91. package/dist/commands/read/search.js +17 -23
  92. package/dist/commands/read/show.js +57 -108
  93. package/dist/commands/sources/bundle-cli.js +25 -2
  94. package/dist/commands/sources/bundle-config-ops.js +4 -0
  95. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  96. package/dist/commands/sources/info.js +127 -29
  97. package/dist/commands/sources/installed-stashes.js +197 -746
  98. package/dist/commands/sources/schema-repair.js +98 -129
  99. package/dist/commands/sources/source-add.js +62 -12
  100. package/dist/commands/sources/source-manage.js +9 -2
  101. package/dist/commands/sources/stash-cli.js +24 -4
  102. package/dist/commands/tasks/explain.js +10 -13
  103. package/dist/commands/tasks/tasks-cli.js +12 -13
  104. package/dist/commands/tasks/tasks.js +350 -936
  105. package/dist/commands/tasks/validate.js +26 -24
  106. package/dist/commands/workflow/plan.js +22 -29
  107. package/dist/commands/workflow-cli.js +4 -4
  108. package/dist/core/adapter/adapters/akm-adapter.js +2 -1
  109. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  110. package/dist/core/adapter/adapters/akm-metadata.js +42 -12
  111. package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
  112. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  113. package/dist/core/adapter/execution-source.js +17 -29
  114. package/dist/core/asset/asset-placement.js +4 -13
  115. package/dist/core/asset/frontmatter.js +106 -1
  116. package/dist/core/asset/resolve-ref.js +1 -1
  117. package/dist/core/bundle-id.js +42 -5
  118. package/dist/core/bundle-rename.js +285 -0
  119. package/dist/core/config/config-io.js +1 -2
  120. package/dist/core/config/config-schema.js +9 -34
  121. package/dist/core/config/config-walker.js +1 -1
  122. package/dist/core/config/config.js +184 -111
  123. package/dist/core/config/engine-semantics.js +0 -2
  124. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  125. package/dist/core/config/schema/embedding.js +20 -5
  126. package/dist/core/config/schema/engines.js +5 -0
  127. package/dist/core/config/schema/execution.js +1 -1
  128. package/dist/core/config/schema/experimental.js +1 -1
  129. package/dist/core/config/schema/improve-processes.js +54 -125
  130. package/dist/core/config/schema/improve.js +4 -42
  131. package/dist/core/config/schema/index-config.js +9 -48
  132. package/dist/core/config/schema/scheduler.js +12 -12
  133. package/dist/core/config/schema/search.js +6 -22
  134. package/dist/core/env-secret-ref.js +0 -1
  135. package/dist/core/errors.js +8 -9
  136. package/dist/core/file-change.js +13 -5
  137. package/dist/core/file-lock.js +76 -173
  138. package/dist/core/improve-result.js +35 -7
  139. package/dist/core/improve-types.js +0 -1
  140. package/dist/core/logs-db.js +2 -2
  141. package/dist/core/loopback.js +7 -12
  142. package/dist/core/non-task-input.js +20 -0
  143. package/dist/core/parse.js +13 -16
  144. package/dist/core/paths.js +0 -24
  145. package/dist/core/redaction.js +109 -2
  146. package/dist/core/run-lock.js +2 -5
  147. package/dist/core/spawn-env.js +1 -1
  148. package/dist/core/state/migrations.js +123 -61
  149. package/dist/core/state-db-scope.js +2 -4
  150. package/dist/core/state-db.js +126 -692
  151. package/dist/core/time.js +0 -20
  152. package/dist/core/type-presentation.js +1 -9
  153. package/dist/core/write-source.js +294 -1005
  154. package/dist/execution/input-contract.js +1 -1
  155. package/dist/execution/resolved-request.js +135 -689
  156. package/dist/execution/source.js +63 -257
  157. package/dist/execution/target-ref.js +1 -1
  158. package/dist/indexer/bundle-identity-guard.js +2 -2
  159. package/dist/indexer/db/llm-cache.js +2 -2
  160. package/dist/indexer/ensure-index.js +77 -73
  161. package/dist/indexer/index-rebuild-lock.js +3 -11
  162. package/dist/indexer/index-writer-lock.js +8 -17
  163. package/dist/indexer/index-written-assets.js +141 -154
  164. package/dist/indexer/indexer.js +400 -1124
  165. package/dist/indexer/links/declared-links.js +90 -0
  166. package/dist/indexer/materialize-embeddings.js +60 -397
  167. package/dist/indexer/passes/memory-inference.js +96 -90
  168. package/dist/indexer/passes/metadata.js +132 -219
  169. package/dist/indexer/read-preflight.js +0 -7
  170. package/dist/indexer/scan/doc-to-entry.js +2 -3
  171. package/dist/indexer/scan/drain-dir.js +1 -1
  172. package/dist/indexer/search/db-search.js +190 -590
  173. package/dist/indexer/search/fts-query.js +30 -41
  174. package/dist/indexer/search/ranking.js +28 -154
  175. package/dist/indexer/search/search-attribution.js +12 -32
  176. package/dist/indexer/search/search-fields.js +11 -15
  177. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  178. package/dist/indexer/search/search-source.js +1 -4
  179. package/dist/indexer/usage/usage-events.js +36 -7
  180. package/dist/indexer/walk/walker.js +3 -4
  181. package/dist/integrations/agent/engine-fallback.js +23 -40
  182. package/dist/integrations/agent/engine-resolution.js +93 -183
  183. package/dist/integrations/agent/execution.js +507 -0
  184. package/dist/integrations/agent/model-map.js +28 -156
  185. package/dist/integrations/agent/request-lowering.js +66 -141
  186. package/dist/integrations/agent/runner-dispatch.js +143 -321
  187. package/dist/integrations/agent/runner.js +54 -14
  188. package/dist/integrations/lockfile.js +53 -101
  189. package/dist/llm/client.js +18 -6
  190. package/dist/llm/embedders/deterministic.js +2 -3
  191. package/dist/llm/embedders/profile.js +71 -0
  192. package/dist/llm/embedders/remote.js +11 -17
  193. package/dist/llm/feature-gate.js +0 -8
  194. package/dist/llm/index-passes.js +3 -5
  195. package/dist/llm/memory-infer.js +1 -2
  196. package/dist/llm/structured-call.js +5 -24
  197. package/dist/output/generic-render.js +23 -11
  198. package/dist/output/html-render.js +13 -10
  199. package/dist/output/render-registry.js +3 -32
  200. package/dist/output/shapes/helpers.js +25 -38
  201. package/dist/output/shapes/passthrough.js +1 -9
  202. package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
  203. package/dist/output/text/command-format.js +69 -31
  204. package/dist/output/text/helpers.js +1 -1
  205. package/dist/output/text/migrate.js +5 -14
  206. package/dist/output/text/proposal-format.js +48 -3
  207. package/dist/output/text/show-format.js +13 -17
  208. package/dist/output/text/workflow-format.js +0 -32
  209. package/dist/output/text.js +2 -0
  210. package/dist/registry/factory.js +4 -19
  211. package/dist/registry/network.js +66 -220
  212. package/dist/registry/providers/index.js +0 -2
  213. package/dist/registry/providers/skills-sh.js +3 -14
  214. package/dist/registry/providers/static-index.js +24 -26
  215. package/dist/registry/resolve.js +55 -131
  216. package/dist/scripts/akm-migrate-node.js +42948 -92369
  217. package/dist/scripts/akm-migrate.js +42935 -92354
  218. package/dist/setup/registry-stash-loader.js +4 -13
  219. package/dist/setup/semantic-assets.js +3 -44
  220. package/dist/setup/setup.js +1 -1
  221. package/dist/setup/steps/connection.js +5 -6
  222. package/dist/setup/steps/platforms.js +2 -2
  223. package/dist/setup/steps/tasks.js +25 -15
  224. package/dist/sources/provider-factory.js +17 -18
  225. package/dist/sources/providers/filesystem.js +2 -3
  226. package/dist/sources/providers/git-install.js +7 -1
  227. package/dist/sources/providers/git-provider.js +0 -3
  228. package/dist/sources/providers/git-stash.js +83 -21
  229. package/dist/sources/providers/npm.js +2 -4
  230. package/dist/sources/providers/provider-utils.js +5 -10
  231. package/dist/sources/providers/website.js +0 -2
  232. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  233. package/dist/sources/website-url.js +2 -2
  234. package/dist/storage/database.js +9 -35
  235. package/dist/storage/repositories/improve-ledger-repository.js +209 -0
  236. package/dist/storage/repositories/index-connection.js +39 -72
  237. package/dist/storage/repositories/index-entries-repository.js +131 -129
  238. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  239. package/dist/storage/repositories/index-entry-schema.js +101 -268
  240. package/dist/storage/repositories/index-fts-repository.js +86 -256
  241. package/dist/storage/repositories/index-links-repository.js +143 -0
  242. package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
  243. package/dist/storage/repositories/index-meta-repository.js +6 -4
  244. package/dist/storage/repositories/index-schema.js +257 -325
  245. package/dist/storage/repositories/index-utility-repository.js +8 -29
  246. package/dist/storage/repositories/index-vec-repository.js +133 -414
  247. package/dist/storage/repositories/outcome-repository.js +2 -1
  248. package/dist/storage/repositories/proposals-repository.js +104 -1
  249. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  250. package/dist/storage/repositories/salience-repository.js +1 -19
  251. package/dist/storage/repositories/task-history-repository.js +26 -4
  252. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  253. package/dist/storage/sqlite-migrations.js +136 -0
  254. package/dist/storage/sqlite-pragmas.js +11 -9
  255. package/dist/storage/sqlite-transaction.js +170 -0
  256. package/dist/storage/state-db-integrity.js +130 -0
  257. package/dist/tasks/activation-config.js +134 -62
  258. package/dist/tasks/backends/cron.js +191 -302
  259. package/dist/tasks/backends/exec-utils.js +2 -5
  260. package/dist/tasks/backends/launchd.js +141 -748
  261. package/dist/tasks/backends/schtasks.js +119 -623
  262. package/dist/tasks/prepare/prepare-support.js +5 -15
  263. package/dist/tasks/prepare/prepare.js +0 -2
  264. package/dist/tasks/resolve-akm-bin.js +20 -79
  265. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  266. package/dist/tasks/run/load-task.js +1 -1
  267. package/dist/tasks/scheduler-binding.js +20 -238
  268. package/dist/tasks/scheduler-invocation.js +136 -244
  269. package/dist/tasks/scheduler-lock.js +53 -0
  270. package/dist/tasks/scheduler-sync.js +368 -679
  271. package/dist/tasks/source/parse-task-source.js +55 -9
  272. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  273. package/dist/tasks/source/task-to-v4.js +464 -88
  274. package/dist/workflows/authoring/authoring.js +3 -12
  275. package/dist/workflows/compile.js +211 -0
  276. package/dist/workflows/concurrency-policy.js +13 -74
  277. package/dist/workflows/exec/child-invocation.js +3 -17
  278. package/dist/workflows/exec/child-workflow.js +32 -141
  279. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  280. package/dist/workflows/exec/environment.js +98 -0
  281. package/dist/workflows/exec/exec-unit.js +33 -140
  282. package/dist/workflows/exec/frozen-judge.js +7 -59
  283. package/dist/workflows/exec/native-executor.js +82 -341
  284. package/dist/workflows/exec/param-secrets.js +29 -47
  285. package/dist/workflows/exec/run-workflow.js +154 -387
  286. package/dist/workflows/exec/scheduler.js +9 -36
  287. package/dist/workflows/exec/step-work.js +127 -430
  288. package/dist/workflows/exec/unit-dispatch.js +11 -63
  289. package/dist/workflows/exec/unit-writer.js +8 -52
  290. package/dist/workflows/exec/worktree.js +39 -273
  291. package/dist/workflows/freeze/child-output-references.js +4 -15
  292. package/dist/workflows/freeze/environment.js +99 -92
  293. package/dist/workflows/freeze/freeze.js +172 -0
  294. package/dist/workflows/freeze/step-values.js +19 -21
  295. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  296. package/dist/workflows/freeze/targets/command.js +10 -33
  297. package/dist/workflows/freeze/targets/script.js +5 -12
  298. package/dist/workflows/freeze/targets/shell.js +3 -6
  299. package/dist/workflows/freeze/targets/task.js +25 -80
  300. package/dist/workflows/freeze/task-bindings.js +20 -67
  301. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  302. package/dist/workflows/ir/params.js +6 -51
  303. package/dist/workflows/ir/plan-hash.js +2 -34
  304. package/dist/workflows/parser.js +140 -43
  305. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  306. package/dist/workflows/renderer.js +36 -69
  307. package/dist/workflows/resource-limits.js +12 -120
  308. package/dist/workflows/runtime/agent-identity.js +8 -40
  309. package/dist/workflows/runtime/run-outputs.js +3 -6
  310. package/dist/workflows/runtime/run-plan.js +316 -0
  311. package/dist/workflows/runtime/runs.js +48 -200
  312. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  313. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  314. package/dist/workflows/validate-summary.js +2 -7
  315. package/docs/integration/bundling-akm.md +49 -42
  316. package/docs/migration/README.md +1 -0
  317. package/docs/migration/release-notes/0.9.17.md +43 -0
  318. package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
  319. package/docs/reference/cli.md +232 -135
  320. package/docs/reference/configuration.md +71 -57
  321. package/docs/reference/data-and-telemetry.md +20 -21
  322. package/docs/reference/tasks.md +105 -39
  323. package/docs/reference/workflow-schema.md +14 -18
  324. package/docs/reference/workflows.md +6 -9
  325. package/package.json +1 -1
  326. package/schemas/akm-config.json +115 -738
  327. package/schemas/akm-workflow.json +1 -0
  328. package/dist/assets/improve-strategies/graph-refresh.json +0 -15
  329. package/dist/assets/prompts/contradiction-judge.md +0 -33
  330. package/dist/assets/prompts/graph-extract-system.md +0 -1
  331. package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
  332. package/dist/assets/prompts/metadata-enhance-system.md +0 -1
  333. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
  334. package/dist/commands/health/advisories.js +0 -150
  335. package/dist/commands/health/metrics.js +0 -329
  336. package/dist/commands/health/surfaces.js +0 -102
  337. package/dist/commands/improve/anti-collapse.js +0 -83
  338. package/dist/commands/improve/collapse-detector.js +0 -432
  339. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  340. package/dist/commands/improve/consolidate/merge.js +0 -149
  341. package/dist/commands/improve/distill/promote-memory.js +0 -291
  342. package/dist/commands/improve/distill/quality-gate.js +0 -337
  343. package/dist/commands/improve/eval-cases.js +0 -52
  344. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  345. package/dist/commands/improve/proposal-envelope.js +0 -31
  346. package/dist/commands/improve/run-context.js +0 -123
  347. package/dist/commands/improve/shared.js +0 -31
  348. package/dist/commands/improve/source-identity.js +0 -28
  349. package/dist/commands/improve/triage.js +0 -96
  350. package/dist/commands/proposal/drain-policies.js +0 -151
  351. package/dist/commands/sources/update-transaction.js +0 -220
  352. package/dist/core/action-contributors.js +0 -28
  353. package/dist/core/config/config-version-shim.js +0 -101
  354. package/dist/core/fs-txn.js +0 -405
  355. package/dist/core/lexical-score.js +0 -25
  356. package/dist/core/maintenance-barrier.js +0 -167
  357. package/dist/execution/executable-identity.js +0 -105
  358. package/dist/execution/guarded-source.js +0 -427
  359. package/dist/indexer/db/graph-db.js +0 -444
  360. package/dist/indexer/graph/graph-boost.js +0 -427
  361. package/dist/indexer/graph/graph-dedup.js +0 -95
  362. package/dist/indexer/graph/graph-extraction.js +0 -1108
  363. package/dist/indexer/search/name-match.js +0 -35
  364. package/dist/indexer/search/ranking-contributors.js +0 -515
  365. package/dist/indexer/search/ranking-types.js +0 -4
  366. package/dist/indexer/walk/project-context.js +0 -192
  367. package/dist/integrations/agent/execution-cascade.js +0 -566
  368. package/dist/integrations/agent/execution-definitions.js +0 -202
  369. package/dist/integrations/agent/execution-lowering.js +0 -841
  370. package/dist/integrations/agent/execution-preparation.js +0 -98
  371. package/dist/integrations/agent/inline-execution.js +0 -74
  372. package/dist/llm/graph-extract.js +0 -728
  373. package/dist/llm/metadata-enhance.js +0 -96
  374. package/dist/registry/create-provider-registry.js +0 -29
  375. package/dist/registry/pinned-request-helper.js +0 -247
  376. package/dist/registry/pinned-transport.js +0 -717
  377. package/dist/sources/providers/index.js +0 -14
  378. package/dist/storage/engines/sqlite-migrations.js +0 -271
  379. package/dist/storage/repositories/canaries-repository.js +0 -107
  380. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  381. package/dist/storage/repositories/registry-cache.js +0 -113
  382. package/dist/tasks/scheduler-sync-preview.js +0 -52
  383. package/dist/tasks/source/task-to-v3.js +0 -507
  384. package/dist/workflows/freeze/resolve-steps.js +0 -86
  385. package/dist/workflows/freeze/source-freeze.js +0 -64
  386. package/dist/workflows/ir/compile.js +0 -321
  387. package/dist/workflows/ir/environment-v4.js +0 -330
  388. package/dist/workflows/ir/freeze-v4.js +0 -153
  389. package/dist/workflows/ir/schema-v4.js +0 -745
  390. package/dist/workflows/ir/schema.js +0 -354
  391. package/dist/workflows/program/schema.js +0 -77
  392. package/dist/workflows/runtime/checkin.js +0 -57
  393. package/dist/workflows/runtime/plan-classifier.js +0 -196
  394. package/dist/workflows/runtime/unit-checkin.js +0 -45
  395. package/dist/workflows/runtime/unit-phases.js +0 -20
  396. package/dist/workflows/schema.js +0 -4
  397. package/dist/workflows/source-ir/compile.js +0 -200
  398. package/dist/workflows/source-ir/program.js +0 -50
  399. package/dist/workflows/source-ir/result.js +0 -26
  400. package/dist/workflows/source-ir/schema.js +0 -786
  401. package/dist/workflows/source-ir/triggers.js +0 -79
  402. package/dist/workflows/source-ir/uses.js +0 -40
  403. package/dist/workflows/validator.js +0 -60
@@ -2,37 +2,18 @@
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
- * Session asset generation for the `extract` pass (#561).
6
- *
7
- * After the extractor distills memory proposals from a session, it ALSO writes
8
- * the session itself to the stash as a first-class `session` asset so any agent
9
- * — on any harness — can discover prior work via `akm search` / `akm curate`.
10
- *
11
- * Design constraints (see #561):
12
- * - ADDITIVE + FAIL-OPEN + CONFIG-GATED. Disabled (or no LLM provider) →
13
- * extract behaves EXACTLY as before. Nothing is written.
14
- * - The LLM summary call routes through the injectable {@link SessionSummaryGenerator}
15
- * seam so tests never touch a real provider, and so production wraps the
16
- * call in the existing `tryLlmFeature` fail-open pattern.
17
- * - The `log_path` + `access` frontmatter fields are the durable correlation
18
- * key — they survive index rebuilds (the body is re-derived from disk).
19
- *
20
- * The asset is written to `sessions/<harness>/<session-id>.md`; the registered
21
- * `session` asset type (see `asset-spec.ts`) makes the normal index pass pick it
22
- * up for FTS + vector search with no special-casing.
5
+ * Session assets (#561): besides its memory proposals, extract writes each
6
+ * session to `sessions/<harness>/<session-id>.md` as a searchable `session`
7
+ * asset. Additive and fail-open — no summary means nothing is written — and
8
+ * `log_path` + `access` in the frontmatter tell any agent how to read the raw log.
23
9
  */
24
10
  import fs from "node:fs";
25
11
  import path from "node:path";
26
12
  import { stashDirFor } from "../../core/asset/asset-placement.js";
27
13
  import { assembleAsset } from "../../core/asset/asset-serialize.js";
28
14
  import { conceptIdFromTypeName } from "../../core/asset/resolve-ref.js";
15
+ import { parseEmbeddedJsonResponse } from "../../core/parse.js";
29
16
  import { recordWrittenPath } from "../../core/write-provenance.js";
30
- /**
31
- * JSON Schema for the session-summary LLM call. Strict so providers that
32
- * support schema enforcement constrain the output upstream; the parser only
33
- * has to handle the happy path. `additionalProperties: false` drops any
34
- * hallucinated keys before parsing.
35
- */
36
17
  export const SESSION_SUMMARY_JSON_SCHEMA = {
37
18
  type: "object",
38
19
  required: ["summary", "key_topics"],
@@ -43,11 +24,7 @@ export const SESSION_SUMMARY_JSON_SCHEMA = {
43
24
  tags: { type: "array", items: { type: "string" } },
44
25
  },
45
26
  };
46
- /**
47
- * Render a compact transcript snippet from session events for the summary
48
- * prompt. Mirrors the extract transcript format but caps total length so the
49
- * summary prompt stays bounded regardless of session size.
50
- */
27
+ /** The transcript for the summary prompt, capped at `maxChars`. */
51
28
  function renderTranscriptForSummary(events, maxChars = 12_000) {
52
29
  if (events.length === 0)
53
30
  return "(empty — no events)";
@@ -66,11 +43,6 @@ function renderTranscriptForSummary(events, maxChars = 12_000) {
66
43
  }
67
44
  return lines.join("\n\n") || "(empty — no textual events)";
68
45
  }
69
- /**
70
- * Build the user prompt for the session-summary LLM call. Pure — no IO. The
71
- * model is asked for a dense 2–4 sentence summary plus key topics, optimised
72
- * for semantic search recall.
73
- */
74
46
  export function buildSessionSummaryPrompt(data) {
75
47
  const ref = data.ref;
76
48
  const startedAt = isoOrUndefined(ref.startedAt) ?? "unknown";
@@ -92,33 +64,13 @@ export function buildSessionSummaryPrompt(data) {
92
64
  'Respond as JSON: {"summary": string, "key_topics": string[], "tags"?: string[]}.',
93
65
  ].join("\n");
94
66
  }
95
- /**
96
- * Parse the session-summary LLM response into a {@link SessionSummaryResult}.
97
- * Defensive: tolerates prose preamble/postamble around the JSON, and returns
98
- * `undefined` when nothing usable parses (fail-open: no asset is written).
99
- */
67
+ /** The summary JSON, tolerating prose around it; `undefined` when nothing usable parses. */
100
68
  export function parseSessionSummary(raw) {
101
69
  if (!raw || raw.trim().length === 0)
102
70
  return undefined;
103
- let parsed;
104
- try {
105
- parsed = JSON.parse(raw);
106
- }
107
- catch {
108
- const start = raw.indexOf("{");
109
- const end = raw.lastIndexOf("}");
110
- if (start === -1 || end <= start)
111
- return undefined;
112
- try {
113
- parsed = JSON.parse(raw.slice(start, end + 1));
114
- }
115
- catch {
116
- return undefined;
117
- }
118
- }
119
- if (!parsed || typeof parsed !== "object")
71
+ const obj = parseEmbeddedJsonResponse(raw);
72
+ if (!obj || typeof obj !== "object" || Array.isArray(obj))
120
73
  return undefined;
121
- const obj = parsed;
122
74
  const summary = typeof obj.summary === "string" ? obj.summary.trim() : "";
123
75
  if (summary.length === 0)
124
76
  return undefined;
@@ -130,62 +82,40 @@ export function parseSessionSummary(raw) {
130
82
  : undefined;
131
83
  return { summary, keyTopics, ...(tags && tags.length > 0 ? { tags } : {}) };
132
84
  }
133
- /**
134
- * Decide whether a session is long enough to index. `minDurationMinutes <= 0`
135
- * disables the gate. When either timestamp is missing we DON'T gate it out —
136
- * fail-open toward indexing, since a missing timestamp is not evidence of a
137
- * trivial session.
138
- */
85
+ /** Long enough to index (`<= 0` disables; a missing timestamp is no evidence of a trivial session). */
139
86
  export function sessionMeetsDurationGate(data, minDurationMinutes) {
140
87
  if (!Number.isFinite(minDurationMinutes) || minDurationMinutes <= 0)
141
88
  return true;
142
89
  const { startedAt, endedAt } = data.ref;
143
90
  if (typeof startedAt !== "number" || typeof endedAt !== "number")
144
91
  return true;
145
- const durationMinutes = (endedAt - startedAt) / 60_000;
146
- return durationMinutes >= minDurationMinutes;
92
+ return (endedAt - startedAt) / 60_000 >= minDurationMinutes;
147
93
  }
148
- /**
149
- * Build per-harness `access` instructions for reading the raw session log.
150
- *
151
- * Documented convention (#561, checklist item "Document `access` field
152
- * convention per harness"): the string tells a downstream agent exactly how to
153
- * read and parse the source at `log_path`. New harnesses fall back to a generic
154
- * `cat <log_path>` hint for file-backed logs.
155
- */
94
+ /** How an agent reads and parses the raw log at `log_path`, per harness (`cat` otherwise). */
156
95
  export function buildSessionAccessInstructions(harness, logPath, sessionId) {
157
- const canonical = harness;
158
- if (canonical === "claude") {
96
+ if (harness === "claude") {
159
97
  return [
160
98
  `Read with: cat ${logPath}`,
161
99
  `Parse messages: jq -r 'select(.type=="message") | .message.content[]? | select(.type=="text") | .text' ${logPath}`,
162
100
  ].join("\n");
163
101
  }
164
- if (canonical === "opencode") {
102
+ if (harness === "opencode") {
165
103
  return [
166
104
  `Open the SQLite database at ${JSON.stringify(logPath)} in read-only mode.`,
167
105
  "Query: SELECT m.data, p.data FROM message AS m JOIN part AS p ON p.message_id = m.id WHERE m.session_id = ? AND p.session_id = ? ORDER BY m.time_created, p.time_created;",
168
106
  `Bind both parameters to ${JSON.stringify(sessionId)}.`,
169
107
  ].join("\n");
170
108
  }
171
- // Generic fallback — file-backed logs are always readable with cat.
172
109
  return `Read with: cat ${logPath}`;
173
110
  }
174
- /** ISO-8601 (UTC) from a ms-epoch, or undefined when absent/non-finite. */
175
111
  function isoOrUndefined(ms) {
176
112
  return typeof ms === "number" && Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
177
113
  }
178
114
  /** Default session-name slug: `<harness>-session-<yyyy-mm-dd>-<shortId>`. */
179
115
  export function buildSessionAssetName(harness, sessionId, startedAtMs) {
180
- const canonical = harness;
181
- const datePart = isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date";
182
- const shortId = sessionId.slice(0, 8);
183
- return `${canonical}-session-${datePart}-${shortId}`;
116
+ return `${harness}-session-${isoOrUndefined(startedAtMs)?.slice(0, 10) ?? "unknown-date"}-${sessionId.slice(0, 8)}`;
184
117
  }
185
- /**
186
- * Assemble the full session asset (frontmatter + `## Summary` / `## Key topics`).
187
- * Pure — no IO. Returns the serialized markdown string.
188
- */
118
+ /** The session asset: frontmatter plus `## Summary` and `## Key topics`. */
189
119
  export function buildSessionAssetContent(data, summary) {
190
120
  const ref = data.ref;
191
121
  const harness = ref.harness;
@@ -213,8 +143,7 @@ export function buildSessionAssetContent(data, summary) {
213
143
  .map((t) => `- ${t.trim()}`)
214
144
  .join("\n");
215
145
  const body = `## Summary\n\n${summary.summary.trim()}\n\n## Key topics\n\n${topics || "- (none extracted)"}\n`;
216
- // `description` is duplicated into frontmatter so the metadata pass surfaces
217
- // it without re-reading the body — matches how other content types behave.
146
+ // The summary doubles as the description, as for other types.
218
147
  const content = assembleAsset({ ...frontmatter, description: summary.summary.trim() }, body);
219
148
  return { name, frontmatter, content };
220
149
  }
@@ -223,13 +152,7 @@ export function resolveSessionAssetPath(stashDir, harness, sessionId) {
223
152
  const dir = stashDirFor("session") ?? "sessions";
224
153
  return path.join(stashDir, dir, harness, `${sessionId}.md`);
225
154
  }
226
- /**
227
- * Generate (via the injected summarizer) and write a session asset to the stash.
228
- *
229
- * FAIL-OPEN: when the summarizer returns `undefined` (disabled / no LLM /
230
- * error), NOTHING is written and `{ written: false }` is returned. Any write
231
- * error is swallowed by the caller — session indexing must NEVER break extract.
232
- */
155
+ /** Summarize and write a session asset; nothing without a summary. The caller swallows write errors. */
233
156
  export async function writeSessionAsset(data, stashDir, generate) {
234
157
  const summary = await generate(data);
235
158
  if (!summary?.summary || summary.summary.trim().length === 0) {
@@ -241,15 +164,11 @@ export async function writeSessionAsset(data, stashDir, generate) {
241
164
  const filePath = resolveSessionAssetPath(stashDir, harness, sessionId);
242
165
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
243
166
  fs.writeFileSync(filePath, content, "utf8");
244
- // #652: extract's session asset is written outside the proposal queue —
245
- // journal it so the run's auto-sync stages it as one of its own writes.
167
+ // Written outside the proposal queue: journal it so auto-sync commits it (#652).
246
168
  recordWrittenPath(filePath);
247
169
  return {
248
170
  written: true,
249
171
  filePath,
250
- // Canonical 0.9.0 conceptId (`sessions/<harness>/<id>`, D-R3) — the same
251
- // spelling the xrefs / usage-event readers now expect. Historical
252
- // `session:<harness>/<id>` rows persist un-migrated and are tolerated.
253
172
  ref: conceptIdFromTypeName("session", `${harness}/${sessionId}`),
254
173
  logPath: data.ref.filePath,
255
174
  };
@@ -0,0 +1,322 @@
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
+ import { getImproveProcessConfig } from "../../core/config/config.js";
5
+ import { ConfigError } from "../../core/errors.js";
6
+ import { parseEmbeddedJsonResponse } from "../../core/parse.js";
7
+ import { warn } from "../../core/warn.js";
8
+ import { LlmCallError } from "../../llm/client.js";
9
+ import { callStructured } from "../../llm/structured-call.js";
10
+ import { withLlmStage } from "../../llm/usage-telemetry.js";
11
+ import { isProceduralRejection } from "../proposal/proposal-types.js";
12
+ import { createProposal, listProposalsReadOnly, proposalContentHash, recordGateDecision, } from "../proposal/repository.js";
13
+ import { resolveImproveLlmExecution } from "./execution.js";
14
+ /** Normalize an unknown thrown value to a message. */
15
+ export function errMessage(e) {
16
+ return e instanceof Error ? e.message : String(e);
17
+ }
18
+ /** The lowering notices a stage's dispatches emitted, each once. */
19
+ export function noticeSet(forward) {
20
+ const byKey = new Map();
21
+ const add = (notices) => {
22
+ for (const notice of notices)
23
+ byKey.set(JSON.stringify(notice), notice);
24
+ forward?.(notices);
25
+ };
26
+ const list = () => Object.freeze([...byKey.values()]);
27
+ return { add, list, fields: () => (byKey.size > 0 ? { notices: list() } : {}) };
28
+ }
29
+ /**
30
+ * A stage's LLM runner: the one the improve plan froze for it (an own
31
+ * `llmRunner` key, `null` meaning "none"), else the process engine cascade.
32
+ */
33
+ export function stageRunner(frozen, config, profile, processName, onNotices) {
34
+ if (Object.hasOwn(frozen, "llmRunner"))
35
+ return frozen.llmRunner ?? undefined;
36
+ const resolved = resolveImproveLlmExecution({
37
+ config,
38
+ profile,
39
+ process: getImproveProcessConfig(processName, profile),
40
+ processName,
41
+ });
42
+ if (resolved)
43
+ onNotices?.(resolved.notices);
44
+ return resolved?.runner;
45
+ }
46
+ /**
47
+ * One model call. Provider trouble (transport error, timeout, a disabled
48
+ * feature) comes back as `{ ok: false }`; only a configuration failure throws.
49
+ */
50
+ export async function callStage(call) {
51
+ const messages = [
52
+ ...(call.system ? [{ role: "system", content: call.system }] : []),
53
+ ...(call.history ?? []),
54
+ { role: "user", content: call.prompt },
55
+ ];
56
+ let failure;
57
+ try {
58
+ const raw = await callStructured({
59
+ feature: call.feature,
60
+ ...(call.gate
61
+ ? { akmConfig: call.gate.config, ...(call.gate.enabled !== undefined ? { enabled: call.gate.enabled } : {}) }
62
+ : {}),
63
+ runner: call.runner,
64
+ messages,
65
+ ...(call.request ? { request: call.request } : {}),
66
+ ...(call.onNotices ? { onNotices: call.onNotices } : {}),
67
+ parse: (r) => r ?? "",
68
+ onError: (_cls, err) => {
69
+ failure = { ok: false, reason: "error", error: errMessage(err) };
70
+ return undefined;
71
+ },
72
+ fallback: undefined,
73
+ onFallback: (event) => {
74
+ failure ??= { ok: false, reason: event.reason, ...(event.error ? { error: event.error.message } : {}) };
75
+ },
76
+ });
77
+ return raw === undefined ? (failure ?? { ok: false, reason: "error" }) : { ok: true, raw };
78
+ }
79
+ catch (err) {
80
+ if (err instanceof ConfigError)
81
+ throw err;
82
+ const timedOut = err instanceof LlmCallError && err.code === "timeout";
83
+ return { ok: false, reason: timedOut ? "timeout" : "error", error: errMessage(err) };
84
+ }
85
+ }
86
+ /** Attribute a stage's LLM calls to its process and planned engine (the usage report). */
87
+ const STAGE_LABELS = {
88
+ reflect: "reflect",
89
+ distill: "distill",
90
+ consolidate: "consolidate",
91
+ extract: "session-extraction",
92
+ memoryInference: "memory-inference",
93
+ validation: "validation",
94
+ };
95
+ export function attributeStage(plan, process, fn) {
96
+ return withLlmStage(STAGE_LABELS[process], fn, { engine: plan?.processes[process].runner?.engine, process });
97
+ }
98
+ /** How many prior rejected proposals are shown to the model as "don't repeat this". */
99
+ export const MAX_REJECTED_PROPOSALS = 3;
100
+ /**
101
+ * Reflexion context: the newest reviewer rejections for `ref`. Procedural
102
+ * refusals (expiry, stale target, missing asset) are not judgements on the
103
+ * content and are left out. Reads never create state.db.
104
+ */
105
+ export function rejectedProposalContext(stash, ref, ctx) {
106
+ if (!ref)
107
+ return [];
108
+ return listProposalsReadOnly(stash, { ref, status: "rejected", includeArchive: true }, ctx)
109
+ .filter((p) => !isProceduralRejection(p))
110
+ .sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())
111
+ .slice(0, MAX_REJECTED_PROPOSALS)
112
+ .map((p) => ({
113
+ ref: p.ref,
114
+ reason: p.review?.reason ?? "no reason given",
115
+ // `payload.content` is populated on every row, including legacy ones.
116
+ contentPreview: p.payload.content.slice(0, 500),
117
+ }));
118
+ }
119
+ // ── Mint ─────────────────────────────────────────────────────────────────────
120
+ /**
121
+ * Create a stage's proposal. `judged` stamps a `staged` gate decision with the
122
+ * judged content's hash (the triage drain accepts it while the content still
123
+ * matches); `review` leaves it `deferred` for a human (`review_needed` in the
124
+ * improve ledger).
125
+ */
126
+ export function mintProposal(stash, proposalsCtx, input, verdict = {}) {
127
+ const proposal = createProposal(stash, input, proposalsCtx);
128
+ if (verdict.review) {
129
+ return recordGateDecision(stash, proposal.id, { outcome: "deferred", ...verdict.review }, proposalsCtx) ?? proposal;
130
+ }
131
+ return verdict.judged ? stageJudgedProposal(stash, proposal, proposalsCtx) : proposal;
132
+ }
133
+ /**
134
+ * Stamp a proposal the quality judge passed. Best-effort: a failed stamp only
135
+ * means the triage drain judges it again.
136
+ */
137
+ export function stageJudgedProposal(stash, proposal, proposalsCtx) {
138
+ try {
139
+ return (recordGateDecision(stash, proposal.id, {
140
+ outcome: "staged",
141
+ reason: "quality-judge",
142
+ gate: "quality-gate",
143
+ contentHash: proposalContentHash(proposal),
144
+ }, proposalsCtx) ?? proposal);
145
+ }
146
+ catch (error) {
147
+ warn(`[akm] failed to record the quality-judge pass for ${proposal.id}: ${errMessage(error)}`);
148
+ return proposal;
149
+ }
150
+ }
151
+ /** Lesson judge prompt; similar existing lessons let it mark near-duplicates down. */
152
+ export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
153
+ const lines = [
154
+ "You are evaluating a proposed lesson asset for an akm knowledge base.",
155
+ "",
156
+ "Score this lesson on each criterion from 1 (poor) to 5 (excellent):",
157
+ "1. NOVELTY: Does the lesson add information not already present in the source asset?",
158
+ "2. NON-REDUNDANCY: Is this lesson meaningfully different from what the source already says?",
159
+ "",
160
+ "Source asset content:",
161
+ "```",
162
+ sourceContent.slice(0, 2000),
163
+ "```",
164
+ ];
165
+ if (similarLessons && similarLessons.length > 0) {
166
+ lines.push("", "Existing similar lessons (top-3 by similarity). Rate lower if the proposed lesson is substantially similar to any of these:");
167
+ for (const sl of similarLessons)
168
+ lines.push(`\nExisting lesson ref: ${sl.ref}`, "```", sl.content.slice(0, 500), "```");
169
+ }
170
+ lines.push("", "Proposed lesson content:", "```", lessonContent.slice(0, 1000), "```", "", 'Return ONLY valid JSON, no prose: {"scores": {"novelty": <1-5 integer>, "nonRedundancy": <1-5 integer>}, "reason": "<one sentence>"}');
171
+ return lines.join("\n");
172
+ }
173
+ function boundedDocument(content, maxChars = 6000) {
174
+ if (content.length <= maxChars)
175
+ return content;
176
+ const half = Math.floor((maxChars - 80) / 2);
177
+ return `${content.slice(0, half)}\n\n[... middle omitted for bounded judge context ...]\n\n${content.slice(-half)}`;
178
+ }
179
+ function buildChangedRegion(sourceContent, candidateContent) {
180
+ const source = sourceContent.split("\n");
181
+ const candidate = candidateContent.split("\n");
182
+ let prefix = 0;
183
+ while (prefix < source.length && prefix < candidate.length && source[prefix] === candidate[prefix])
184
+ prefix++;
185
+ let suffix = 0;
186
+ while (suffix < source.length - prefix &&
187
+ suffix < candidate.length - prefix &&
188
+ source[source.length - 1 - suffix] === candidate[candidate.length - 1 - suffix]) {
189
+ suffix++;
190
+ }
191
+ const removed = source.slice(prefix, source.length - suffix).join("\n");
192
+ const added = candidate.slice(prefix, candidate.length - suffix).join("\n");
193
+ return boundedDocument(`Removed or replaced:\n${removed || "(none)"}\n\nAdded or replacement:\n${added || "(none)"}`);
194
+ }
195
+ /** Judge prompt for an in-place revision (overlap with the source is expected). */
196
+ export function buildReflectJudgePrompt(candidateContent, sourceContent, feedback) {
197
+ return [
198
+ "You are evaluating a proposed revision to an existing akm asset.",
199
+ "",
200
+ "Score this revision on each criterion from 1 (poor) to 5 (excellent):",
201
+ "1. FEEDBACK ALIGNMENT: Does the revision address the supplied feedback or improve retrieval and clarity?",
202
+ "2. PRESERVATION: Does it retain the source's concrete facts, code, commands, examples, and structure without truncation?",
203
+ "3. QUALITY: Is the revision coherent, actionable, complete, and free of unsupported claims?",
204
+ "",
205
+ "Overlap with the source is expected and must not lower the score by itself; this is an in-place revision, not a new lesson.",
206
+ "",
207
+ "Feedback:",
208
+ "```",
209
+ (feedback.length > 0 ? feedback.join("\n") : "No explicit feedback supplied.").slice(0, 1000),
210
+ "```",
211
+ "",
212
+ "Source asset content:",
213
+ "```",
214
+ boundedDocument(sourceContent),
215
+ "```",
216
+ "",
217
+ "Proposed revision:",
218
+ "```",
219
+ boundedDocument(candidateContent),
220
+ "```",
221
+ "",
222
+ "Changed region:",
223
+ "```",
224
+ buildChangedRegion(sourceContent, candidateContent),
225
+ "```",
226
+ "",
227
+ 'Return ONLY valid JSON, no prose: {"scores": {"feedbackAlignment": <1-5 integer>, "preservation": <1-5 integer>, "quality": <1-5 integer>}, "reason": "<one sentence>"}',
228
+ ].join("\n");
229
+ }
230
+ const LESSON_JUDGE_CRITERIA = ["novelty", "nonRedundancy"];
231
+ const REFLECT_JUDGE_CRITERIA = ["feedbackAlignment", "preservation", "quality"];
232
+ /**
233
+ * Read a judge response: the per-criterion shape (averaged here) or the older
234
+ * `{"score"}` shape. Only the expected criteria are read; any missing or
235
+ * out-of-range (1..5) value is a parse failure, extra keys are ignored.
236
+ */
237
+ function parseJudgeResponse(raw, keys) {
238
+ const parsed = parseEmbeddedJsonResponse(raw);
239
+ if (!parsed || typeof parsed.reason !== "string")
240
+ return undefined;
241
+ const reason = parsed.reason;
242
+ const inRange = (value) => typeof value === "number" && Number.isFinite(value) && value >= 1 && value <= 5;
243
+ if (parsed.scores !== undefined) {
244
+ if (typeof parsed.scores !== "object" || parsed.scores === null || Array.isArray(parsed.scores))
245
+ return undefined;
246
+ const scores = parsed.scores;
247
+ const criteria = {};
248
+ for (const key of keys) {
249
+ const value = scores[key];
250
+ if (!inRange(value))
251
+ return undefined;
252
+ criteria[key] = value;
253
+ }
254
+ return { score: Object.values(criteria).reduce((a, b) => a + b, 0) / keys.length, reason, criteria };
255
+ }
256
+ return inRange(parsed.score) ? { score: parsed.score, reason } : undefined;
257
+ }
258
+ function judgeResponseSchema(keys) {
259
+ return {
260
+ type: "object",
261
+ required: ["scores", "reason"],
262
+ additionalProperties: false,
263
+ properties: {
264
+ scores: {
265
+ type: "object",
266
+ required: [...keys],
267
+ additionalProperties: false,
268
+ properties: Object.fromEntries(keys.map((key) => [key, { type: "integer", minimum: 1, maximum: 5 }])),
269
+ },
270
+ reason: { type: "string" },
271
+ },
272
+ };
273
+ }
274
+ /**
275
+ * The quality judge. Fails closed: no runner, an unparseable verdict or a
276
+ * provider failure never passes content. Bands: >= 3.5 pass, 2.5-3.5 review,
277
+ * < 2.5 reject. Temperature is pinned to 0 so verdicts do not flip.
278
+ */
279
+ async function runQualityJudge(feature, config, prompt, keys, chat, options) {
280
+ const resolved = !options.runnerSelectionFrozen && !options.llmRunner
281
+ ? resolveImproveLlmExecution({ config, processName: `${feature}-judge` })
282
+ : null;
283
+ if (resolved)
284
+ options.onNotices?.(resolved.notices);
285
+ const runner = options.llmRunner ?? resolved?.runner;
286
+ if (!runner)
287
+ return { pass: false, score: -1, reason: "no LLM configured — cannot judge, failing closed" };
288
+ const outcome = await callStage({
289
+ feature,
290
+ runner,
291
+ system: "Return only valid JSON. No prose.",
292
+ prompt,
293
+ request: {
294
+ enableThinking: false,
295
+ temperature: 0,
296
+ responseSchema: judgeResponseSchema(keys),
297
+ ...(Object.hasOwn(options, "timeoutMs") ? { timeoutMs: options.timeoutMs } : {}),
298
+ ...(options.signal ? { signal: options.signal } : {}),
299
+ ...(chat ? { chat } : {}),
300
+ },
301
+ ...(options.onNotices ? { onNotices: options.onNotices } : {}),
302
+ });
303
+ if (!outcome.ok) {
304
+ return { pass: false, score: -1, reason: "judge timeout/error — routed to review", reviewNeeded: true };
305
+ }
306
+ const parsed = parseJudgeResponse(outcome.raw, keys);
307
+ if (!parsed)
308
+ return { pass: false, score: -1, reason: "judge parse failed — routed to review", reviewNeeded: true };
309
+ const { score, reason, criteria } = parsed;
310
+ const verdict = score >= 3.5 ? { pass: true } : score >= 2.5 ? { pass: false, reviewNeeded: true } : { pass: false };
311
+ return { ...verdict, score, reason, ...(criteria ? { criteria } : {}) };
312
+ }
313
+ /** Judge a proposed lesson (or knowledge promotion) against its source. */
314
+ export function runLessonQualityJudge(config, lessonContent, sourceContent, chat, options = {}) {
315
+ const prompt = buildJudgePrompt(lessonContent, sourceContent, options.similarLessons);
316
+ return runQualityJudge("lesson_quality_gate", config, prompt, LESSON_JUDGE_CRITERIA, chat, options);
317
+ }
318
+ /** Judge an in-place reflect revision without new-lesson novelty criteria. */
319
+ export function runReflectQualityJudge(config, candidateContent, sourceContent, feedback, chat, options = {}) {
320
+ const prompt = buildReflectJudgePrompt(candidateContent, sourceContent, feedback);
321
+ return runQualityJudge("proposal_quality_gate", config, prompt, REFLECT_JUDGE_CRITERIA, chat, options);
322
+ }
@@ -42,6 +42,7 @@ import { isArchivedRelPath } from "../../core/asset/memory-archive.js";
42
42
  import { conceptIdFromTypeName, typeNameFromConceptId } from "../../core/asset/resolve-ref.js";
43
43
  import { localDateStamp } from "../../core/common.js";
44
44
  import { containsRedactedContent, REDACTED_CONTENT_MARKER } from "../../core/content-safety.js";
45
+ import { DERIVED_SUFFIX } from "../../core/recognition-util.js";
45
46
  import { findFenceRegions } from "./markdown-insertion.js";
46
47
  // ── Helpers ───────────────────────────────────────────────────────────────────
47
48
  /** Fold physically wrapped prose the same way a YAML plain scalar does. */
@@ -203,12 +204,14 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
203
204
  if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
204
205
  return true;
205
206
  }
206
- // #884: a memory pruned by `analyzeMemoryCleanup` was ARCHIVED, not deleted —
207
- // its bytes and identity live on under `.akm/memory-cleanup/archive`. Inbound
208
- // belief edges to it are satisfied, not dangling, so resolve the tombstone
209
- // rather than reporting `missing-ref`. Checked only after every live location
210
- // misses: a tombstone must never shadow a real file, and the scan then costs
211
- // one directory read per root instead of one per ref.
207
+ // #884: an asset `analyzeMemoryCleanup` pruned, or (alpha.9) a consolidate
208
+ // pair-pass `retire` proposal or a promotion's source memory retired, was
209
+ // ARCHIVED, not deleted — its bytes and identity live on under
210
+ // `.akm/memory-cleanup/archive`. Inbound refs to it are satisfied, not
211
+ // dangling, so resolve the tombstone rather than reporting `missing-ref`.
212
+ // Checked only after every live location misses: a tombstone must never
213
+ // shadow a real file, and the scan then costs one directory read per root
214
+ // instead of one per ref.
212
215
  //
213
216
  // Existence ONLY. `resolveRefPathInStash` deliberately does NOT consult the
214
217
  // archive: it hands back a path callers MUTATE (SPEC-5 `--supersedes`
@@ -217,17 +220,36 @@ export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
217
220
  return memoryArchiveHasRef(refType, refName, stashRoots);
218
221
  }
219
222
  /**
220
- * True when `(refType, refName)` names a memory that prune archived in any
221
- * root. Mirrors `resolveRefPathInStash`'s candidate set so a ref that resolved
222
- * through the `.derived.md` twin (#882) still resolves once archived.
223
+ * The stash-relative files that satisfy a ref, in preference order: its own
224
+ * placement spellings, then, for a memory, the `<name>.derived.md` child (#882),
225
+ * so an edge to a parent whose plain `.md` is gone still reaches the child it was
226
+ * distilled into. This is lint's reachability rule only; the child owns
227
+ * `memories/<name>.derived`, never `memories/<name>`.
228
+ */
229
+ function refPathCandidates(refType, typeDir, refName) {
230
+ const candidates = assetPathCandidatesForName(refType, typeDir, refName);
231
+ if (refType !== "memory" || refName.endsWith(DERIVED_SUFFIX))
232
+ return candidates;
233
+ return [...candidates, assetPathForName(refType, typeDir, `${refName}${DERIVED_SUFFIX}`)];
234
+ }
235
+ /**
236
+ * True when `(refType, refName)` names an asset the cleanup archive holds a
237
+ * tombstone for, in any root. Mirrors `resolveRefPathInStash`'s candidate set
238
+ * so a memory ref that resolved through the `.derived.md` child (#882) still
239
+ * resolves once archived.
240
+ *
241
+ * Originally memory-only (#884: only `.derived` memories were ever pruned).
242
+ * 0.9.17-alpha.9 generalized `archiveCleanupCandidate` to any memory,
243
+ * knowledge or lesson file (a consolidate pair-pass `retire` proposal, or an
244
+ * accepted promotion's source memory), so this must check every type, not
245
+ * just `memory` — otherwise an xref to a retired knowledge or lesson asset
246
+ * reports `missing-ref` even though it resolves fine through the tombstone.
223
247
  */
224
248
  function memoryArchiveHasRef(refType, refName, stashRoots) {
225
- if (refType !== "memory")
226
- return false; // only memories are ever archived
227
249
  const typeDir = stashDirFor(refType);
228
250
  if (typeDir === undefined)
229
251
  return false;
230
- const candidates = assetPathCandidatesForName(refType, typeDir, refName);
252
+ const candidates = refPathCandidates(refType, typeDir, refName);
231
253
  for (const root of stashRoots) {
232
254
  for (const candidate of candidates) {
233
255
  if (isArchivedRelPath(candidate, root))
@@ -241,8 +263,8 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
241
263
  * the same reachability rules (in the same order) as
242
264
  * {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
243
265
  * of the file that makes the ref "exist" — for a multi-file skill directory
244
- * that is its `SKILL.md` primary, for a `memory` ref its `.derived.md` twin
245
- * when the plain `.md` is absent (#882, see `assetPathCandidatesForName`) —
266
+ * that is its `SKILL.md` primary, for a `memory` ref its `.derived.md` child
267
+ * when the plain `.md` is absent (#882, see {@link refPathCandidates}) —
246
268
  * or `null` when the ref does not resolve in this root.
247
269
  *
248
270
  * Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
@@ -253,7 +275,7 @@ function memoryArchiveHasRef(refType, refName, stashRoots) {
253
275
  */
254
276
  export function resolveRefPathInStash(relPath, refType, refName, root) {
255
277
  const typeDir = stashDirFor(refType);
256
- const candidates = typeDir === undefined ? [relPath] : assetPathCandidatesForName(refType, typeDir, refName);
278
+ const candidates = typeDir === undefined ? [relPath] : refPathCandidates(refType, typeDir, refName);
257
279
  for (const candidate of candidates) {
258
280
  const absPath = path.join(root, candidate);
259
281
  if (fs.existsSync(absPath))