akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -29,7 +29,8 @@
29
29
  * NEVER deleted — the user keeps what was already produced.
30
30
  *
31
31
  * Locked v1 contract:
32
- * - LLM access is exclusively via `resolveIndexPassLLM("memory", config)`.
32
+ * - LLM access is exclusively via the frozen runner returned by
33
+ * `resolveIndexPassExecution("memory", config)`.
33
34
  * - All child memory writes go through `writeAssetToSource` in
34
35
  * `src/core/write-source.ts`. The parent's frontmatter rewrite is an
35
36
  * explicit narrow exception — see {@link markParentProcessed}.
@@ -40,14 +41,19 @@ import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
40
41
  import { assembleAsset } from "../../core/asset/asset-serialize.js";
41
42
  import { parseFrontmatter, parseFrontmatterBlock } from "../../core/asset/frontmatter.js";
42
43
  import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
44
+ import { bestEffort } from "../../core/best-effort.js";
43
45
  import { todayIso } from "../../core/common.js";
44
46
  import { concurrentMap } from "../../core/concurrent.js";
47
+ import { ConfigError } from "../../core/errors.js";
45
48
  import { warn } from "../../core/warn.js";
46
49
  import { recordWrittenPath } from "../../core/write-provenance.js";
47
50
  import { writeAssetToSource } from "../../core/write-source.js";
51
+ import { disposeLoweredExecutionDispatchLease, } from "../../integrations/agent/execution-lowering.js";
48
52
  import { isProcessEnabled } from "../../llm/feature-gate.js";
49
- import { resolveIndexPassLLM } from "../../llm/index-passes.js";
53
+ import { resolveIndexPassExecution } from "../../llm/index-passes.js";
50
54
  import * as memoryInfer from "../../llm/memory-infer.js";
55
+ import { preflightStructuredLlmRunner } from "../../llm/structured-call.js";
56
+ import { computeBodyHash, getLlmCacheEntry } from "../../storage/repositories/index-llm-cache-repository.js";
51
57
  import { withLlmCache } from "../db/llm-cache.js";
52
58
  import { walkMarkdownFiles } from "../walk/walker.js";
53
59
  /**
@@ -58,6 +64,114 @@ const FM_INFERRED = "inferred";
58
64
  const FM_INFERENCE_PROCESSED = "inferenceProcessed";
59
65
  const FM_SOURCE = "source";
60
66
  const FM_CAPTURE_MODE = "captureMode";
67
+ function validateDerivedMemoryDraft(raw) {
68
+ if (!raw || typeof raw !== "object")
69
+ return undefined;
70
+ const parsed = raw;
71
+ const title = typeof parsed.title === "string" ? parsed.title : "";
72
+ const description = typeof parsed.description === "string" ? parsed.description : "";
73
+ const content = typeof parsed.content === "string" ? parsed.content : "";
74
+ const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag) => typeof tag === "string") : [];
75
+ const searchHints = Array.isArray(parsed.searchHints)
76
+ ? parsed.searchHints.filter((hint) => typeof hint === "string")
77
+ : [];
78
+ return title && description && content && tags.length > 0 && searchHints.length > 0
79
+ ? { title, description, tags, searchHints, content }
80
+ : undefined;
81
+ }
82
+ function planPendingMemoryRecord(record, ctx) {
83
+ if (ctx.signal?.aborted)
84
+ return { kind: "aborted", record };
85
+ if (fs.existsSync(derivedChildPath(record)))
86
+ return { kind: "existing-child", record };
87
+ if (ctx.db && !ctx.reEnrich) {
88
+ const cached = bestEffort(() => {
89
+ const entry = getLlmCacheEntry(ctx.db, record.filePath, computeBodyHash(record.body), "memory-inference-v2");
90
+ return entry ? validateDerivedMemoryDraft(JSON.parse(entry.resultJson)) : undefined;
91
+ }, "memory inference cache read corrupt — fall through to recompute");
92
+ if (cached)
93
+ return { kind: "cache-hit", record, derived: cached };
94
+ }
95
+ return { kind: "model", record };
96
+ }
97
+ function memoryExecutionForContext(ctx, config) {
98
+ if (Object.hasOwn(ctx, "llmRunner")) {
99
+ return Object.freeze({ runner: ctx.llmRunner ?? undefined, notices: Object.freeze([]) });
100
+ }
101
+ return resolveIndexPassExecution("memory", config);
102
+ }
103
+ async function inferPendingMemoryRecord(plan, ctx) {
104
+ const { record } = plan;
105
+ const { config, featureConfig, signal, db, reEnrich, llmRunner, lease, inferTelemetry, compressMemoryToDerivedMemory, onNotices, onConfigFailure, } = ctx;
106
+ if (signal?.aborted || plan.kind === "aborted")
107
+ return { aborted: true };
108
+ // Existing children are complete, but the parent may be unmarked after a
109
+ // crash between the child write and parent update (or an external write).
110
+ if (fs.existsSync(derivedChildPath(record))) {
111
+ markParentProcessed(record);
112
+ return {
113
+ skipped: false,
114
+ splitParent: false,
115
+ written: 0,
116
+ fromCache: false,
117
+ retryAttempts: 0,
118
+ childExists: true,
119
+ precheck: true,
120
+ };
121
+ }
122
+ // The child can disappear after the read-only classification pass (for
123
+ // example, an external cleanup racing this index run). The plan is not proof
124
+ // that the child still exists at the mutation boundary: leave the parent
125
+ // pending so a later run can classify and preflight the now-real model work.
126
+ if (plan.kind === "existing-child") {
127
+ return { skipped: true, fromCache: false, retryAttempts: 0 };
128
+ }
129
+ let fromCache = plan.kind === "cache-hit";
130
+ let retryAttempts = 0;
131
+ const onRetryAttempt = () => {
132
+ retryAttempts += 1;
133
+ };
134
+ const infer = () => compressMemoryToDerivedMemory(llmRunner, record.body, signal, featureConfig, (event) => warn(`[akm] LLM fallback for ${event.feature}: ${event.reason}`), inferTelemetry, onRetryAttempt, onNotices, lease);
135
+ let derived = plan.kind === "cache-hit" ? plan.derived : undefined;
136
+ try {
137
+ if (plan.kind === "model") {
138
+ derived = db
139
+ ? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, infer, validateDerivedMemoryDraft, undefined, "memory-inference-v2", {
140
+ onCacheHit: () => {
141
+ fromCache = true;
142
+ },
143
+ })
144
+ : await compressMemoryToDerivedMemory(llmRunner, record.body, signal, config, (event) => warn(`[akm] LLM fallback for ${event.feature}: ${event.reason}`), inferTelemetry, onRetryAttempt, onNotices, lease);
145
+ }
146
+ }
147
+ catch (error) {
148
+ if (error instanceof ConfigError) {
149
+ onConfigFailure(error);
150
+ return undefined;
151
+ }
152
+ throw error;
153
+ }
154
+ if (!derived)
155
+ return { skipped: true, fromCache, retryAttempts };
156
+ const writeOutcome = await writeDerivedMemory(record, derived);
157
+ if (writeOutcome.written > 0) {
158
+ markParentProcessed(record);
159
+ return { skipped: false, splitParent: true, written: writeOutcome.written, fromCache, retryAttempts };
160
+ }
161
+ // A child that appeared mid-flight is complete and can safely mark its
162
+ // parent. A genuine write failure remains unmarked for a later retry.
163
+ if (writeOutcome.childExists)
164
+ markParentProcessed(record);
165
+ return {
166
+ skipped: false,
167
+ splitParent: false,
168
+ written: 0,
169
+ fromCache,
170
+ retryAttempts,
171
+ childExists: true,
172
+ precheck: false,
173
+ };
174
+ }
61
175
  /**
62
176
  * Top-level entry point. Returns a no-op result when the pass is disabled.
63
177
  *
@@ -66,8 +180,8 @@ const FM_CAPTURE_MODE = "captureMode";
66
180
  * 1. **Feature gate** — the selected strategy's `processes.memoryInference.enabled`
67
181
  * (defaults to `true`). When `false`, no network call may issue regardless
68
182
  * of per-pass settings.
69
- * 2. **Per-pass gate** — `resolveIndexPassLLM("memory", config)` (which
70
- * reads `index.memory.llm`). When `false`, the indexer simply skips
183
+ * 2. **Per-pass gate** — `resolveIndexPassExecution("memory", config)` reads
184
+ * the index pass selection. Without a runner, the indexer simply skips
71
185
  * this pass for the current run.
72
186
  *
73
187
  * Both must allow the call for the pass to run. Either set to `false`
@@ -75,7 +189,7 @@ const FM_CAPTURE_MODE = "captureMode";
75
189
  */
76
190
  export async function runMemoryInferencePass(ctx) {
77
191
  const { config, sources, signal, db, reEnrich, onProgress, options = {} } = ctx;
78
- const invocationOwnsConnection = Object.hasOwn(ctx, "llmConfig");
192
+ const invocationOwnsRunner = Object.hasOwn(ctx, "llmRunner");
79
193
  const compressMemoryToDerivedMemory = options.compressMemoryToDerivedMemory ?? memoryInfer.compressMemoryToDerivedMemory;
80
194
  const result = {
81
195
  considered: 0,
@@ -93,17 +207,29 @@ export async function runMemoryInferencePass(ctx) {
93
207
  // HTML-error categorization (which is otherwise swallowed inside the feature
94
208
  // gate) bubbles up into the pass result.
95
209
  const inferTelemetry = {};
210
+ const noticesByKey = new Map();
211
+ const onNotices = (notices) => {
212
+ for (const notice of notices)
213
+ noticesByKey.set(JSON.stringify(notice), notice);
214
+ };
215
+ const completeResult = () => {
216
+ if (noticesByKey.size > 0)
217
+ result.notices = Object.freeze([...noticesByKey.values()]);
218
+ return result;
219
+ };
96
220
  // Gate 1 — feature gate via isProcessEnabled, which reads the 0.8.0 path
97
221
  // (selected strategy's processes.memoryInference.enabled). Defaults to
98
222
  // enabled when the key is absent.
99
- if (!invocationOwnsConnection && !isProcessEnabled("index", "memory_inference", config))
100
- return result;
223
+ if (!invocationOwnsRunner && !isProcessEnabled("index", "memory_inference", config))
224
+ return completeResult();
101
225
  // Gate 2 — per-pass opt-out (#208). Returns the resolved llm config or
102
226
  // `undefined` when the pass should not run.
103
- const llmConfig = Object.hasOwn(ctx, "llmConfig") ? ctx.llmConfig : resolveIndexPassLLM("memory", config);
104
- if (!llmConfig)
105
- return result;
106
- const featureConfig = invocationOwnsConnection
227
+ const execution = memoryExecutionForContext(ctx, config);
228
+ onNotices(execution.notices);
229
+ const llmRunner = execution.runner;
230
+ if (!llmRunner)
231
+ return completeResult();
232
+ const featureConfig = invocationOwnsRunner
107
233
  ? { ...config, index: { ...config.index, memory: { ...config.index?.memory, enabled: true } } }
108
234
  : config;
109
235
  // The pass only writes to the primary (working) stash. Read-only caches
@@ -111,128 +237,94 @@ export async function runMemoryInferencePass(ctx) {
111
237
  // children there would be clobbered by the next sync().
112
238
  const primary = sources[0];
113
239
  if (!primary || (primary.adapterId ?? detectAdapterId(primary.path)) !== "akm")
114
- return result;
240
+ return completeResult();
115
241
  const pending = collectPendingMemories(primary.path).filter((record) => !options.candidateRefs || options.candidateRefs.has(record.ref));
116
242
  result.considered = pending.length;
117
243
  if (pending.length === 0)
118
- return result;
119
- let processed = 0;
120
- const total = pending.length;
121
- onProgress?.({ processed, total, writtenFacts: 0, skippedNoFacts: 0 });
122
- const perRecordResults = await concurrentMap(pending, async (record) => {
123
- // Aborted BEFORE a fresh LLM call. Returned as a typed outcome so the
124
- // for-loop below increments `skippedAborted` instead of silently
125
- // dropping the record (which historically inflated freshAttempts and
126
- // dragged the health-reported yield rate down — see investigation
127
- // 2026-05-26).
128
- if (signal?.aborted)
129
- return { aborted: true };
130
- // Pre-check (#588): when `<parent>.derived.md` is already on disk the
131
- // inference is by definition complete — the parent only looks pending
132
- // because `markParentProcessed` never ran (process killed between the
133
- // child write and the mark) or the child was created externally (e.g.
134
- // consolidation). Skip the LLM/cache call entirely and mark the parent
135
- // so it never re-pends. Before this check, production measurements
136
- // showed ~55% of the pass's LLM budget re-deriving such parents only to
137
- // discover the existing child after the fact.
138
- if (fs.existsSync(derivedChildPath(record))) {
139
- markParentProcessed(record);
140
- return {
141
- skipped: false,
142
- splitParent: false,
143
- written: 0,
144
- fromCache: false,
145
- retryAttempts: 0,
146
- childExists: true,
147
- precheck: true,
148
- };
149
- }
150
- // Incremental cache: skip LLM call when body hash is unchanged and
151
- // --re-enrich was not requested. The cache ref is the absolute file path.
152
- const validate = (raw) => {
153
- if (!raw || typeof raw !== "object")
154
- return undefined;
155
- const parsed = raw;
156
- const title = typeof parsed.title === "string" ? parsed.title : "";
157
- const description = typeof parsed.description === "string" ? parsed.description : "";
158
- const content = typeof parsed.content === "string" ? parsed.content : "";
159
- const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((t) => typeof t === "string") : [];
160
- const searchHints = Array.isArray(parsed.searchHints)
161
- ? parsed.searchHints.filter((h) => typeof h === "string")
162
- : [];
163
- if (title && description && content && tags.length > 0 && searchHints.length > 0) {
164
- return { title, description, tags, searchHints, content };
244
+ return completeResult();
245
+ // Classify the whole batch without mutation. Required credentials are
246
+ // materialized only when at least one record will actually dispatch; this
247
+ // keeps aborted, existing-child, and validated-cache-only batches available
248
+ // offline while preserving all-or-nothing preflight for mixed batches.
249
+ const plans = pending.map((record) => planPendingMemoryRecord(record, { signal, db, reEnrich }));
250
+ const dispatchLease = plans.some((plan) => plan.kind === "model")
251
+ ? await preflightStructuredLlmRunner(llmRunner)
252
+ : undefined;
253
+ try {
254
+ let processed = 0;
255
+ const total = pending.length;
256
+ onProgress?.({ processed, total, writtenFacts: 0, skippedNoFacts: 0 });
257
+ let configFailure;
258
+ const perRecordResults = await concurrentMap(plans, (plan) => inferPendingMemoryRecord(plan, {
259
+ config,
260
+ featureConfig,
261
+ signal,
262
+ db,
263
+ reEnrich,
264
+ llmRunner,
265
+ lease: dispatchLease,
266
+ inferTelemetry,
267
+ compressMemoryToDerivedMemory,
268
+ onNotices,
269
+ onConfigFailure: (error) => {
270
+ configFailure ??= error;
271
+ },
272
+ }),
273
+ // Caller-set connection concurrency or 1: `resolveLlmEngineUse` does
274
+ // not forward `engines.<name>.concurrency`, so config cannot raise this.
275
+ llmRunner.connection.concurrency ?? 1);
276
+ if (configFailure)
277
+ throw configFailure;
278
+ for (let i = 0; i < perRecordResults.length; i++) {
279
+ const res = perRecordResults[i];
280
+ if (!res)
281
+ continue;
282
+ if ("aborted" in res && res.aborted) {
283
+ result.skippedAborted += 1;
284
+ processed++;
285
+ onProgress?.({
286
+ processed,
287
+ total,
288
+ writtenFacts: result.writtenFacts,
289
+ skippedNoFacts: result.skippedNoFacts,
290
+ currentRef: pending[i]?.ref,
291
+ });
292
+ continue;
293
+ }
294
+ if (res.fromCache) {
295
+ result.cacheHits += 1;
296
+ }
297
+ if ("retryAttempts" in res) {
298
+ result.retryAttempts += res.retryAttempts;
299
+ }
300
+ if (res.skipped) {
301
+ result.skippedNoFacts += 1;
302
+ // Intentionally NOT marked processed — a transient LLM failure should
303
+ // be retried on the next index run.
304
+ }
305
+ else if (res.splitParent) {
306
+ result.splitParents += 1;
307
+ result.writtenFacts += res.written;
308
+ }
309
+ else if ("childExists" in res && res.childExists) {
310
+ // Derived child already on disk. Track separately so this category is
311
+ // observable in health output and stops bleeding into the
312
+ // freshAttempts denominator. Pre-check skips (#588) are the routine
313
+ // self-healing path — no LLM attempt was consumed and the parent has
314
+ // been marked processed — so only the rare post-LLM case (mid-flight
315
+ // race or write failure) warrants a per-ref warning.
316
+ result.skippedChildExists += 1;
317
+ if (!res.precheck) {
318
+ warn(`memory inference: derived child for ${pending[i]?.ref ?? "<unknown>"} already existed or write failed; counted as skippedChildExists`);
319
+ }
320
+ }
321
+ else {
322
+ // The per-record state machine should cover every outcome. A hit here
323
+ // means a new code path slipped past the categorisation — surface it
324
+ // loudly so health metrics stay honest and we get a signal to fix.
325
+ result.unaccounted += 1;
326
+ warn(`memory inference: unaccounted per-record outcome for ${pending[i]?.ref ?? "<unknown>"}`);
165
327
  }
166
- return undefined;
167
- };
168
- // Track whether THIS candidate's result came from the body-hash
169
- // cache vs. a fresh LLM call. The cache short-circuits when the
170
- // parent body has not changed since a prior derived write — surfacing
171
- // the hit count separately so the operational yield rate
172
- // (writtenFacts / freshAttempts) is interpretable as the cache warms.
173
- let fromCache = false;
174
- // Count single bounded retries for transient LLM failures on this
175
- // candidate. Bumped via the `onRetryAttempt` callback threaded into
176
- // `chatCompletion`; surfaced as `retryAttempts` telemetry, never as a
177
- // failure for the same call.
178
- let retryAttempts = 0;
179
- const onRetryAttempt = () => {
180
- retryAttempts += 1;
181
- };
182
- const derived = db
183
- ? await withLlmCache(db, record.filePath, record.body, reEnrich ?? false, () => compressMemoryToDerivedMemory(llmConfig, record.body, signal, featureConfig, (evt) => {
184
- warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
185
- }, inferTelemetry, onRetryAttempt), validate, undefined, "memory-inference-v2", {
186
- onCacheHit: () => {
187
- fromCache = true;
188
- },
189
- })
190
- : await compressMemoryToDerivedMemory(llmConfig, record.body, signal, config, (evt) => {
191
- warn(`[akm] LLM fallback for ${evt.feature}: ${evt.reason}`);
192
- }, inferTelemetry, onRetryAttempt);
193
- if (!derived) {
194
- return { skipped: true, fromCache, retryAttempts };
195
- }
196
- const writeOutcome = await writeDerivedMemory(record, derived);
197
- if (writeOutcome.written > 0) {
198
- markParentProcessed(record);
199
- return { skipped: false, splitParent: true, written: writeOutcome.written, fromCache, retryAttempts };
200
- }
201
- // LLM produced a valid derived draft but no file was written — either
202
- // because `<parent>.derived.md` appeared on disk after the pre-check
203
- // above (a rare mid-flight race) or `writeAssetToSource` threw.
204
- // Categorise as `childExists` so the consumed attempt is accounted for
205
- // in health metrics rather than vanishing into the freshAttempts
206
- // denominator.
207
- //
208
- // When the child exists the inference is, by definition, complete — so
209
- // mark the parent processed here too (#550), otherwise
210
- // `isPendingMemory()` re-queues the same parent every run. A genuine
211
- // write *failure* (`writeAssetToSource` threw) must NOT mark the parent
212
- // — it should be retried next run — so we key off the explicit
213
- // `childExists` outcome rather than the conflated `written === 0`.
214
- if (writeOutcome.childExists) {
215
- markParentProcessed(record);
216
- }
217
- return {
218
- skipped: false,
219
- splitParent: false,
220
- written: 0,
221
- fromCache,
222
- retryAttempts,
223
- childExists: true,
224
- precheck: false,
225
- };
226
- },
227
- // Caller-set connection concurrency or 1: `resolveLlmEngineUse` does
228
- // not forward `engines.<name>.concurrency`, so config cannot raise this.
229
- llmConfig.concurrency ?? 1);
230
- for (let i = 0; i < perRecordResults.length; i++) {
231
- const res = perRecordResults[i];
232
- if (!res)
233
- continue;
234
- if ("aborted" in res && res.aborted) {
235
- result.skippedAborted += 1;
236
328
  processed++;
237
329
  onProgress?.({
238
330
  processed,
@@ -241,53 +333,14 @@ export async function runMemoryInferencePass(ctx) {
241
333
  skippedNoFacts: result.skippedNoFacts,
242
334
  currentRef: pending[i]?.ref,
243
335
  });
244
- continue;
245
- }
246
- if (res.fromCache) {
247
- result.cacheHits += 1;
248
- }
249
- if ("retryAttempts" in res) {
250
- result.retryAttempts += res.retryAttempts;
251
336
  }
252
- if (res.skipped) {
253
- result.skippedNoFacts += 1;
254
- // Intentionally NOT marked processed — a transient LLM failure should
255
- // be retried on the next index run.
256
- }
257
- else if (res.splitParent) {
258
- result.splitParents += 1;
259
- result.writtenFacts += res.written;
260
- }
261
- else if ("childExists" in res && res.childExists) {
262
- // Derived child already on disk. Track separately so this category is
263
- // observable in health output and stops bleeding into the
264
- // freshAttempts denominator. Pre-check skips (#588) are the routine
265
- // self-healing path — no LLM attempt was consumed and the parent has
266
- // been marked processed — so only the rare post-LLM case (mid-flight
267
- // race or write failure) warrants a per-ref warning.
268
- result.skippedChildExists += 1;
269
- if (!res.precheck) {
270
- warn(`memory inference: derived child for ${pending[i]?.ref ?? "<unknown>"} already existed or write failed; counted as skippedChildExists`);
271
- }
272
- }
273
- else {
274
- // The per-record state machine should cover every outcome. A hit here
275
- // means a new code path slipped past the categorisation — surface it
276
- // loudly so health metrics stay honest and we get a signal to fix.
277
- result.unaccounted += 1;
278
- warn(`memory inference: unaccounted per-record outcome for ${pending[i]?.ref ?? "<unknown>"}`);
279
- }
280
- processed++;
281
- onProgress?.({
282
- processed,
283
- total,
284
- writtenFacts: result.writtenFacts,
285
- skippedNoFacts: result.skippedNoFacts,
286
- currentRef: pending[i]?.ref,
287
- });
337
+ result.htmlErrorCount = inferTelemetry.htmlErrorCount ?? 0;
338
+ return completeResult();
339
+ }
340
+ finally {
341
+ if (dispatchLease)
342
+ disposeLoweredExecutionDispatchLease(dispatchLease);
288
343
  }
289
- result.htmlErrorCount = inferTelemetry.htmlErrorCount ?? 0;
290
- return result;
291
344
  }
292
345
  // ── Pending detection ───────────────────────────────────────────────────────
293
346
  /**