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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CHANGELOG.md +731 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +361 -751
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -441
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -4,9 +4,8 @@
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import { writeFileAtomic } from "../core/common.js";
7
- import { ConfigError, rethrowIfTestIsolationError } from "../core/errors.js";
7
+ import { ConfigError, rethrowIfTestIsolationError, TransientError } from "../core/errors.js";
8
8
  import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync } from "../core/file-lock.js";
9
- import { acquireMaintenanceBarrier } from "../core/maintenance-barrier.js";
10
9
  import { classifyPathAccess, describeInaccessiblePath } from "../core/path-access.js";
11
10
  import { getDataDir, getLockfileLockPath, getLockfilePath } from "../core/paths.js";
12
11
  import { warn } from "../core/warn.js";
@@ -27,23 +26,18 @@ async function acquireLockSentinel() {
27
26
  let delayMs = LOCK_RETRY_INITIAL_DELAY_MS;
28
27
  let announced = false;
29
28
  for (;;) {
30
- const releaseBarrier = acquireMaintenanceBarrier();
31
- try {
32
- const ownership = tryAcquireLockSync(sentinelPath, createLockPayload());
33
- if (ownership) {
34
- return () => releaseLock(ownership);
35
- }
36
- const probe = probeLock(sentinelPath);
37
- if (probe.state === "stale" && reclaimStaleLock(sentinelPath, probe)) {
38
- continue; // Reclaimed — retry immediately.
39
- }
29
+ const ownership = tryAcquireLockSync(sentinelPath, createLockPayload());
30
+ if (ownership) {
31
+ return () => releaseLock(ownership);
40
32
  }
41
- finally {
42
- releaseBarrier();
33
+ const probe = probeLock(sentinelPath);
34
+ if (probe.state === "stale" && reclaimStaleLock(sentinelPath, probe)) {
35
+ continue; // Reclaimed — retry immediately.
43
36
  }
44
- // Another process holds the lock.
37
+ // Another process holds the lock: ordinary contention, exit 75, not a
38
+ // config error a supervisor should stop retrying on.
45
39
  if (Date.now() >= deadline) {
46
- throw new ConfigError(`Could not acquire lockfile sentinel at ${sentinelPath} after ${(timeoutMs / 1000).toFixed(1)}s; refusing to write without exclusive ownership.`, "INVALID_CONFIG_FILE");
40
+ throw new TransientError(`Could not acquire lockfile sentinel at ${sentinelPath} after ${(timeoutMs / 1000).toFixed(1)}s; refusing to write without exclusive ownership.`, "LOCKFILE_CONTENDED");
47
41
  }
48
42
  if (!announced) {
49
43
  announced = true;
@@ -93,37 +87,33 @@ function assertLockfilePathReadable(target) {
93
87
  "records, and writing over them would destroy every bundle they track. Fix the ownership or mode of that " +
94
88
  "path (or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns) and retry.", "DATA_DIR_UNREADABLE");
95
89
  }
96
- function readLockfileSnapshotOrThrow() {
90
+ /**
91
+ * Like {@link readLockfile}, but THROWS instead of silently degrading to `[]`
92
+ * when the on-disk lockfile exists yet cannot be read or parsed (R-012, #791).
93
+ *
94
+ * `readLockfile`'s fail-open contract is intentional for READ paths — a
95
+ * corrupt lock degrades a managed bundle to "unmanaged" rather than erroring
96
+ * every read-only command. But every writer below reads the current entries
97
+ * and writes the whole array back; if that read silently returned `[]` for a
98
+ * corrupt or unreadable file, the write would replace it with only the one
99
+ * entry being changed — permanently destroying every other lock record (a
100
+ * corrupt lockfile once wiped every entry this way). A missing file is NOT
101
+ * corruption: there is nothing to preserve, so that case returns `[]`.
102
+ */
103
+ function readLockfileOrThrow() {
97
104
  const lockfilePath = getLockfilePath();
98
- let fd;
99
105
  let raw;
100
- let mode;
101
106
  try {
102
- // One descriptor owns both byte and metadata observation so a rename or
103
- // chmod between separate path-based calls cannot synthesize a generation
104
- // that never existed on disk.
105
- fd = fs.openSync(lockfilePath, "r");
106
- raw = fs.readFileSync(fd, "utf8");
107
- mode = fs.fstatSync(fd).mode & 0o777;
107
+ raw = fs.readFileSync(lockfilePath, "utf8");
108
108
  }
109
109
  catch (err) {
110
110
  rethrowIfTestIsolationError(err);
111
- if (err.code === "ENOENT") {
112
- return { entries: [], raw: null, mode: 0o600 };
113
- }
114
- // "Missing file" is the only failure with nothing to preserve. An
115
- // UNREADABLE lockfile has everything to preserve and we cannot see it —
116
- // degrading it to `[]` here is precisely the destructive overwrite this
117
- // function was written to prevent, only triggered by a permission fault
118
- // instead of a corrupt file (#791). Classify AFTER the failed read so the
119
- // happy path costs no extra syscall and the answer describes the failure
120
- // we actually got.
111
+ if (err.code === "ENOENT")
112
+ return [];
113
+ // An UNREADABLE lockfile has everything to preserve and we cannot see it.
114
+ // Classify AFTER the failed read so the happy path costs no extra syscall.
121
115
  assertLockfilePathReadable(lockfilePath);
122
- return { entries: [], raw: null, mode: 0o600 };
123
- }
124
- finally {
125
- if (fd !== undefined)
126
- fs.closeSync(fd);
116
+ return [];
127
117
  }
128
118
  let parsed;
129
119
  try {
@@ -145,23 +135,7 @@ function readLockfileSnapshotOrThrow() {
145
135
  throw new ConfigError(`Refusing to modify lockfile ${lockfilePath}: ${invalid.length} existing entr${invalid.length === 1 ? "y is" : "ies are"} malformed. ` +
146
136
  "Fix or remove the file by hand before retrying — those entries would otherwise be lost.", "INVALID_CONFIG_FILE");
147
137
  }
148
- return {
149
- entries: parsed.filter(isValidLockfileEntry),
150
- raw,
151
- mode,
152
- };
153
- }
154
- function readLockfileOrThrow() {
155
- return readLockfileSnapshotOrThrow().entries;
156
- }
157
- /**
158
- * Read the exact lock generation that a source-lifecycle transaction may
159
- * replace. Unlike {@link readLockfile}, this refuses corrupt, malformed, or
160
- * unreadable state so an update can never treat state it could not snapshot as
161
- * an empty generation.
162
- */
163
- export function readLockfileForUpdate() {
164
- return readLockfileSnapshotOrThrow();
138
+ return parsed;
165
139
  }
166
140
  /**
167
141
  * The materialized content root recorded in the lock for a managed (git/npm)
@@ -201,66 +175,44 @@ export async function writeLockfile(entries) {
201
175
  release();
202
176
  }
203
177
  }
204
- /**
205
- * Publish an update from one exact raw + parsed lockfile generation and return
206
- * the exact bytes that were written. Formatting-only concurrent edits are a
207
- * generation change here: rollback must never overwrite bytes it did not
208
- * publish merely because they parse to an equivalent array.
209
- */
210
- export async function publishLockfileUpdate(expected, desired) {
178
+ export async function upsertLockEntry(entry) {
211
179
  const release = await acquireLockSentinel();
212
180
  try {
213
- const current = readLockfileSnapshotOrThrow();
214
- if (JSON.stringify(current.entries) !== JSON.stringify(expected.entries) ||
215
- current.raw !== expected.raw ||
216
- current.mode !== expected.mode) {
217
- return null;
218
- }
219
- writeLockfileUnlocked(desired);
220
- return readLockfileSnapshotOrThrow();
181
+ // R-012: readLockfileOrThrow (not readLockfile) — a corrupt/malformed
182
+ // lockfile must abort the upsert loudly rather than read as `[]` and get
183
+ // silently overwritten with just this one entry.
184
+ const entries = readLockfileOrThrow();
185
+ const withoutExisting = entries.filter((e) => e.id !== entry.id);
186
+ writeLockfileUnlocked([...withoutExisting, entry]);
221
187
  }
222
188
  finally {
223
189
  release();
224
190
  }
225
191
  }
226
- /** Restore an exact raw snapshot after verifying the exact generation we published. */
227
- export async function compareAndSwapLockfileSnapshot(expected, desired) {
192
+ /**
193
+ * Rename a lock entry's id in place (D6 — `akm bundle rename`), keeping every
194
+ * other resolved field (`localRoot`, `resolvedVersion`, …) unchanged. Returns
195
+ * `true` when an entry for `oldId` existed and was renamed, `false` when
196
+ * there was nothing to rename (e.g. a filesystem bundle, which has no lock
197
+ * entry).
198
+ */
199
+ export async function renameLockEntry(oldId, newId) {
228
200
  const release = await acquireLockSentinel();
229
201
  try {
230
- const current = readLockfileSnapshotOrThrow();
231
- if (JSON.stringify(current.entries) !== JSON.stringify(expected.entries) ||
232
- current.raw !== expected.raw ||
233
- current.mode !== expected.mode) {
202
+ // R-012: see upsertLockEntry — a corrupt lockfile must abort loudly
203
+ // rather than read as `[]` and get silently overwritten.
204
+ const entries = readLockfileOrThrow();
205
+ const existing = entries.find((e) => e.id === oldId);
206
+ if (!existing)
234
207
  return false;
235
- }
236
- const lockfilePath = getLockfilePath();
237
- if (desired.raw === null) {
238
- fs.rmSync(lockfilePath, { force: true });
239
- }
240
- else {
241
- fs.mkdirSync(path.dirname(lockfilePath), { recursive: true });
242
- writeFileAtomic(lockfilePath, desired.raw, desired.mode);
243
- }
208
+ const renamed = entries.map((e) => (e.id === oldId ? { ...e, id: newId } : e));
209
+ writeLockfileUnlocked(renamed);
244
210
  return true;
245
211
  }
246
212
  finally {
247
213
  release();
248
214
  }
249
215
  }
250
- export async function upsertLockEntry(entry) {
251
- const release = await acquireLockSentinel();
252
- try {
253
- // R-012: readLockfileOrThrow (not readLockfile) — a corrupt/malformed
254
- // lockfile must abort the upsert loudly rather than read as `[]` and get
255
- // silently overwritten with just this one entry.
256
- const entries = readLockfileOrThrow();
257
- const withoutExisting = entries.filter((e) => e.id !== entry.id);
258
- writeLockfileUnlocked([...withoutExisting, entry]);
259
- }
260
- finally {
261
- release();
262
- }
263
- }
264
216
  export async function removeLockEntry(id) {
265
217
  // Returning early says "there is no lock record to remove", and the uninstall
266
218
  // that called us reports success on that basis. Only an absent data dir earns
@@ -4,9 +4,8 @@
4
4
  /** Env var that switches the whole embedding facade into deterministic mode. */
5
5
  export const DETERMINISTIC_EMBED_ENV = "AKM_EMBED_DETERMINISTIC";
6
6
  /**
7
- * Vector width. Matches the default local model (`bge-small`, 384 dims) so the
8
- * index DB's embedding column and sqlite-vec table dimensions line up without
9
- * any extra config.
7
+ * Vector width. Matches the default local model (`bge-small`, 384 dims), so a
8
+ * deterministic index is the same shape as a default local one.
10
9
  */
11
10
  export const DETERMINISTIC_EMBED_DIM = 384;
12
11
  /**
@@ -0,0 +1,71 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Embedding profile: the text an embedding model expects around a query and
6
+ * around a document. Retrieval models are trained with these prompts, and
7
+ * sending raw text costs recall — Qwen3-Embedding without its query
8
+ * instruction lost 0.096 nDCG@10 on the retrieval suite.
9
+ *
10
+ * Presets are matched on the model name; `embedding.queryTemplate` and
11
+ * `embedding.documentTemplate` override them (an empty string means none).
12
+ * `{text}` marks where the text goes; a template without it is a prefix.
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ import { isDeterministicEmbedEnabled } from "./deterministic.js";
16
+ import { DEFAULT_LOCAL_MODEL } from "./local.js";
17
+ const RETRIEVAL_QUERY_PREFIX = "Represent this sentence for searching relevant passages: {text}";
18
+ const PRESETS = [
19
+ {
20
+ model: /qwen3-embedding/i,
21
+ profile: {
22
+ queryTemplate: "Instruct: Given a question or task, retrieve the knowledge asset that helps with it\nQuery:{text}",
23
+ },
24
+ },
25
+ {
26
+ model: /nomic-embed/i,
27
+ profile: { queryTemplate: "search_query: {text}", documentTemplate: "search_document: {text}" },
28
+ },
29
+ {
30
+ model: /bge-(?:small|base|large)-en|mxbai-embed|arctic-embed/i,
31
+ profile: { queryTemplate: RETRIEVAL_QUERY_PREFIX },
32
+ },
33
+ {
34
+ model: /(?:^|[^a-z0-9])e5-(?:small|base|large)(?!.*instruct)/i,
35
+ profile: { queryTemplate: "query: {text}", documentTemplate: "passage: {text}" },
36
+ },
37
+ ];
38
+ /** The model a config embeds with, as the presets match it. */
39
+ function embeddingModelName(config) {
40
+ if (config?.endpoint)
41
+ return config.model ?? "";
42
+ return config?.localModel ?? DEFAULT_LOCAL_MODEL;
43
+ }
44
+ /** The configured model's templates: its preset, with each side replaced by an explicit config value. */
45
+ export function resolveEmbeddingProfile(config) {
46
+ const model = embeddingModelName(config);
47
+ const preset = isDeterministicEmbedEnabled() ? {} : (PRESETS.find((entry) => entry.model.test(model))?.profile ?? {});
48
+ const queryTemplate = config?.queryTemplate ?? preset.queryTemplate;
49
+ const documentTemplate = config?.documentTemplate ?? preset.documentTemplate;
50
+ return {
51
+ ...(queryTemplate ? { queryTemplate } : {}),
52
+ ...(documentTemplate ? { documentTemplate } : {}),
53
+ };
54
+ }
55
+ /** Put `text` into `template`; no template returns the text unchanged. */
56
+ export function applyEmbeddingTemplate(template, text) {
57
+ if (!template)
58
+ return text;
59
+ // A replacer function keeps `$&`-style sequences in the text literal.
60
+ return template.includes("{text}") ? template.replaceAll("{text}", () => text) : `${template}${text}`;
61
+ }
62
+ /**
63
+ * The document template's part of the embedding fingerprint: empty without a
64
+ * template, so vectors embedded without one stay current.
65
+ */
66
+ export function documentTemplateFingerprint(config) {
67
+ const template = resolveEmbeddingProfile(config).documentTemplate;
68
+ if (!template)
69
+ return "";
70
+ return `|doc:${createHash("sha256").update(template).digest("hex").slice(0, 12)}`;
71
+ }
@@ -428,7 +428,7 @@ export class RemoteEmbedder {
428
428
  textBatches.splice(dispatchedBatchCount, textBatches.length - dispatchedBatchCount, ...replanned);
429
429
  }
430
430
  warnVerbose(`[embed] provider rejected a ${rejectedRequestTokens}-token request as over its context; request budget lowered to ${effectiveTokenBudget.toLocaleString()} for the rest of this run`);
431
- commitBatch(rejectedIndices, rejectedIndices.map(() => undefined), undefined, {
431
+ commitBatch(rejectedIndices, rejectedIndices.map(() => undefined), {
432
432
  batchIndex: rejectedBatchIndex,
433
433
  batchCount: textBatches.length,
434
434
  docCount: rejectedIndices.length,
@@ -471,7 +471,7 @@ export class RemoteEmbedder {
471
471
  // fabricated "batch-request-failed" skip (#954). Checked (and rethrown)
472
472
  // once the pool drains, the same way `signal?.aborted` is today.
473
473
  let firstOnBatchError;
474
- const commitBatch = (indices, embeddings, model, outcome) => {
474
+ const commitBatch = (indices, embeddings, outcome) => {
475
475
  if (!onBatch)
476
476
  return;
477
477
  // Once persistence has failed once, an already in-flight batch that
@@ -481,7 +481,7 @@ export class RemoteEmbedder {
481
481
  if (firstOnBatchError !== undefined)
482
482
  return;
483
483
  try {
484
- onBatch(indices, embeddings, model, outcome);
484
+ onBatch(indices, embeddings, outcome);
485
485
  }
486
486
  catch (err) {
487
487
  firstOnBatchError = err;
@@ -523,16 +523,14 @@ export class RemoteEmbedder {
523
523
  const requestTimeoutMs = scaleEmbeddingTimeoutMs(configuredTimeoutMs, requestTokens, effectiveTokenBudget);
524
524
  const requestStart = Date.now();
525
525
  let batchEmbeddings;
526
- let responseModel;
527
526
  let outcome;
528
527
  let failureReason;
529
528
  try {
530
- const { vectors, model } = await this.requestBatch(batch, headers, ollamaOpts, requestTimeoutMs, signal);
529
+ const vectors = await this.requestBatch(batch, headers, ollamaOpts, requestTimeoutMs, signal);
531
530
  for (let k = 0; k < indices.length; k++) {
532
531
  results[indices[k]] = vectors[k];
533
532
  }
534
533
  batchEmbeddings = indices.map((i) => results[i]);
535
- responseModel = model;
536
534
  outcome = "stored";
537
535
  }
538
536
  catch (err) {
@@ -571,7 +569,7 @@ export class RemoteEmbedder {
571
569
  // failed or succeeded yet, so there is nothing to persist:
572
570
  // `embeddings` are all `undefined` and the materializer's onBatch
573
571
  // must not touch storage for this event.
574
- commitBatch(indices, indices.map(() => undefined), undefined, {
572
+ commitBatch(indices, indices.map(() => undefined), {
575
573
  batchIndex,
576
574
  batchCount: textBatches.length,
577
575
  docCount: indices.length,
@@ -635,7 +633,7 @@ export class RemoteEmbedder {
635
633
  outcome = "failed";
636
634
  failureReason = message;
637
635
  }
638
- commitBatch(indices, batchEmbeddings, responseModel, {
636
+ commitBatch(indices, batchEmbeddings, {
639
637
  batchIndex,
640
638
  batchCount: textBatches.length,
641
639
  docCount: indices.length,
@@ -665,7 +663,7 @@ export class RemoteEmbedder {
665
663
  // Never made a request — excluded from the default-level per-batch
666
664
  // line (there is no request outcome to report), but still counted
667
665
  // in the run's oversized-skip total via `onSkip` above.
668
- commitBatch([idx], [undefined], undefined, {
666
+ commitBatch([idx], [undefined], {
669
667
  batchIndex,
670
668
  batchCount: textBatches.length,
671
669
  docCount: 1,
@@ -701,11 +699,8 @@ export class RemoteEmbedder {
701
699
  return results;
702
700
  }
703
701
  /**
704
- * Send one batch request and return its embeddings in input order, plus the
705
- * server-reported `model` id when the response body carried one (#955) —
706
- * used by the embedding-fingerprint canary to verify a config-string
707
- * rename against what the endpoint actually served, not just re-assert the
708
- * configured string. Throws on any failure.
702
+ * Send one batch request and return its embeddings in input order. Throws
703
+ * on any failure.
709
704
  *
710
705
  * `timeoutMs` is the caller's ALREADY-SCALED per-request timeout (#954
711
706
  * — see {@link scaleEmbeddingTimeoutMs}), not re-resolved here:
@@ -756,7 +751,7 @@ export class RemoteEmbedder {
756
751
  }
757
752
  results.push(l2Normalize(d.embedding));
758
753
  }
759
- return { vectors: results, model: typeof json.model === "string" && json.model ? json.model : undefined };
754
+ return results;
760
755
  }
761
756
  buildHeaders() {
762
757
  const headers = { "Content-Type": "application/json" };
@@ -491,11 +491,10 @@ function formatContextHint(llmRunner) {
491
491
  return llmRunner.connection.contextLength ? `, configured contextLength=${llmRunner.connection.contextLength}` : "";
492
492
  }
493
493
  /** Dispatch one raw graph prompt through the common resolved-request adapter. */
494
- async function callGraphLlm(runner, messages, request, lease, onNotices) {
494
+ async function callGraphLlm(runner, messages, request, onNotices) {
495
495
  return callStructured({
496
496
  feature: "graph_extraction",
497
497
  runner,
498
- ...(lease ? { lease } : {}),
499
498
  messages,
500
499
  request,
501
500
  onNotices,
@@ -623,7 +622,7 @@ export async function extractGraphFromBodies(llmRunner, bodies, signal, akmConfi
623
622
  signal,
624
623
  responseSchema: batchResponseSchema,
625
624
  onRetryAttempt: () => bumpTelemetry(options.telemetry, "retryAttempts"),
626
- }, options.lease, options.onNotices);
625
+ }, options.onNotices);
627
626
  if (!raw)
628
627
  return { kind: "value", value: null };
629
628
  // Array-preferring salvage (#635): the batch contract is a top-level
@@ -643,7 +642,7 @@ export async function extractGraphFromBodies(llmRunner, bodies, signal, akmConfi
643
642
  timeoutMs: llmRunner.timeoutMs,
644
643
  signal,
645
644
  responseSchema: batchResponseSchema,
646
- }, options.lease, options.onNotices);
645
+ }, options.onNotices);
647
646
  parsed = retryRaw ? parseEmbeddedJsonResponse(retryRaw, { expect: "array" }) : undefined;
648
647
  }
649
648
  if (!Array.isArray(parsed)) {
@@ -807,7 +806,6 @@ export async function extractGraphFromBody(llmRunner, body, signal, akmConfig, o
807
806
  feature: "graph_extraction",
808
807
  akmConfig,
809
808
  runner: llmRunner,
810
- ...(options.lease ? { lease: options.lease } : {}),
811
809
  messages: [
812
810
  { role: "system", content: SYSTEM_PROMPT },
813
811
  { role: "user", content: userPrompt },
@@ -863,10 +861,3 @@ export async function extractGraphFromBody(llmRunner, body, signal, akmConfig, o
863
861
  result.truncatedChunks = (result.truncatedChunks ?? 0) + truncatedChunkCount;
864
862
  return result;
865
863
  }
866
- // deduplicateGraph lives in src/indexer/graph/graph-dedup.ts (pure utility, no
867
- // LLM calls) — import it from there directly. The re-export that used to live
868
- // here created a value edge back to graph-dedup.ts, which (via its type-only
869
- // import of GraphExtraction/GraphRelation below) formed a 2-file import cycle
870
- // (chunk 9 WI-9.8 KILL 4 sever). No src or test consumer used this re-export
871
- // (graph-extraction.ts and the test suite already import graph-dedup.ts
872
- // directly), so it is deleted outright rather than repointed.
@@ -3,8 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { warn } from "../core/warn.js";
5
5
  import { cloneExecutionJsonObject } from "../execution/json.js";
6
- import { lowerResolvedExecutionRequest } from "../integrations/agent/execution-lowering.js";
7
- import { prepareInlineExecution } from "../integrations/agent/inline-execution.js";
6
+ import { buildExecution, resolveExecution } from "../integrations/agent/execution.js";
8
7
  const NO_LOWERING_NOTICES = Object.freeze([]);
9
8
  function own(value, key) {
10
9
  return value !== undefined && Object.hasOwn(value, key);
@@ -39,14 +38,13 @@ export function resolveIndexPassExecution(passName, config) {
39
38
  ...indexExecutionDefaults(defaults),
40
39
  ...(!own(defaults, "engine") && fallbackLlmEngine ? { engine: fallbackLlmEngine } : {}),
41
40
  };
42
- const prepared = prepareInlineExecution({
41
+ const prepared = resolveExecution({
43
42
  content: "",
44
43
  config,
45
- invocationKind: "direct",
46
44
  invocationDefaults,
47
45
  current: indexExecutionDefaults(pass),
48
46
  });
49
- const lowered = lowerResolvedExecutionRequest(prepared.request, prepared.config);
47
+ const lowered = buildExecution(prepared.request, prepared.runner);
50
48
  if (lowered.runner.kind !== "llm") {
51
49
  warn("[akm] Index pass %s requires an LLM engine; %s is not one. Skipping this pass.", passName, selectedEngine);
52
50
  return Object.freeze({ runner: undefined, notices: NO_LOWERING_NOTICES });
@@ -68,7 +68,7 @@ const DERIVED_MEMORY_JSON_SCHEMA = {
68
68
  * feature gate, error classification, and onFallback hook are honoured uniformly
69
69
  * (Fix C5).
70
70
  */
71
- export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akmConfig, onFallback, telemetry, onRetryAttempt, onNotices, lease) {
71
+ export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akmConfig, onFallback, telemetry, onRetryAttempt, onNotices) {
72
72
  const trimmedBody = body.trim();
73
73
  if (!trimmedBody)
74
74
  return undefined;
@@ -83,7 +83,6 @@ export async function compressMemoryToDerivedMemory(llmRunner, body, signal, akm
83
83
  feature: "memory_inference",
84
84
  akmConfig,
85
85
  runner: llmRunner,
86
- ...(lease ? { lease } : {}),
87
86
  messages: [
88
87
  { role: "system", content: SYSTEM_PROMPT },
89
88
  { role: "user", content: userPrompt },
@@ -22,7 +22,7 @@ const SYSTEM_PROMPT = metadataEnhanceSystemPrompt;
22
22
  * `akmConfig` is `undefined` the gate is bypassed entirely: the LLM call runs
23
23
  * unconditionally and errors propagate to direct callers such as tests.
24
24
  */
25
- export async function enhanceMetadata(runner, entry, fileContent, signal, akmConfig, onNotices, lease) {
25
+ export async function enhanceMetadata(runner, entry, fileContent, signal, akmConfig, onNotices) {
26
26
  const contextParts = [`Name: ${entry.name}`, `Type: ${entry.type}`];
27
27
  if (entry.description)
28
28
  contextParts.push(`Current description: ${entry.description}`);
@@ -52,7 +52,6 @@ Return ONLY the JSON object, no explanation.`;
52
52
  feature: "metadata_enhance",
53
53
  akmConfig,
54
54
  runner,
55
- ...(lease ? { lease } : {}),
56
55
  messages: [
57
56
  { role: "system", content: SYSTEM_PROMPT },
58
57
  { role: "user", content: userPrompt },
@@ -2,8 +2,8 @@
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
  import { ConfigError } from "../core/errors.js";
5
- import { acquireLoweredExecutionDispatchLease, dispatchLoweredExecutionRequest, lowerResolvedExecutionRequestWithRunner, } from "../integrations/agent/execution-lowering.js";
6
- import { prepareInlineExecutionWithRunner } from "../integrations/agent/inline-execution.js";
5
+ import { buildExecution, resolveExecution } from "../integrations/agent/execution.js";
6
+ import { runExecution } from "../integrations/agent/runner-dispatch.js";
7
7
  import { isContextSizeError, LlmCallError } from "./client.js";
8
8
  import { isLlmFeatureEnabled, tryLlmFeature, } from "./feature-gate.js";
9
9
  /**
@@ -62,22 +62,6 @@ function dispatchFailure(result) {
62
62
  const message = result.error ?? result.stderr ?? result.reason ?? "LLM dispatch failed";
63
63
  return result.llmErrorCode ? new LlmCallError(message, result.llmErrorCode) : new Error(message);
64
64
  }
65
- /**
66
- * Validate one already-selected symbolic LLM runner before an operation makes
67
- * any durable mutation. Callers must apply their feature/authorization gates
68
- * first. Acquisition crosses the canonical prepare -> lower -> lease boundary,
69
- * so required credentials are snapshotted by the same central authority as a
70
- * real call without contacting the provider.
71
- */
72
- export async function preflightStructuredLlmRunner(runner) {
73
- const prepared = prepareInlineExecutionWithRunner({
74
- content: "Validate the selected LLM runner before operation dispatch.",
75
- runner,
76
- invocationKind: "direct",
77
- });
78
- const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
79
- return acquireLoweredExecutionDispatchLease(lowered);
80
- }
81
65
  export async function callStructured(opts) {
82
66
  const { feature, akmConfig, enabled, messages, request, parse, onError, fallback, onFallback } = opts;
83
67
  // A disabled feature owns a true no-work path: it does not need a runner,
@@ -97,19 +81,16 @@ export async function callStructured(opts) {
97
81
  const terminal = requireTerminalUserMessage(messages);
98
82
  const prepareInvocation = () => {
99
83
  const current = resolveStructuredCurrent(opts.current, request);
100
- const prepared = prepareInlineExecutionWithRunner({
84
+ const prepared = resolveExecution({
101
85
  content: terminal.content,
102
86
  conversation: terminal.conversation,
103
87
  runner,
104
- invocationKind: "direct",
105
88
  ...(current ? { current } : {}),
106
- ...(opts.authorizeTools ? { authorizeTools: opts.authorizeTools } : {}),
107
89
  });
108
- const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
90
+ const lowered = buildExecution(prepared.request, prepared.runner);
109
91
  opts.onNotices?.(lowered.notices);
110
92
  return async () => {
111
- const result = await dispatchLoweredExecutionRequest(lowered, {
112
- ...(opts.lease ? { lease: opts.lease } : {}),
93
+ const result = await runExecution(lowered, {
113
94
  ...(request?.chat ? { chat: request.chat } : {}),
114
95
  ...(request?.onRetryAttempt ? { onRetryAttempt: request.onRetryAttempt } : {}),
115
96
  ...(own(request, "signal") ? { runOptions: { signal: request?.signal } } : {}),
@@ -1,6 +1,29 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Generic `md` / `html` / `text` rendering of a shaped output envelope (D7).
6
+ *
7
+ * Every command gets `json`, `jsonl`, and `yaml` for free because those are
8
+ * serializations of the envelope rather than renderings of it. `md`, `html`,
9
+ * and `text` are renderings, and before D7 only `akm health` had `md`/`html`:
10
+ * `md` silently emitted the JSON envelope everywhere else and `html` threw.
11
+ * `text` had the same silent-JSON gap for any command with no registered
12
+ * text formatter, closed later than `md`/`html` — see `renderGenericText`
13
+ * below for why it is its own function rather than a reuse of
14
+ * `renderGenericMarkdown`.
15
+ *
16
+ * These functions close that gap structurally rather than per command. A
17
+ * command that registers a bespoke renderer (`akm health`) still wins; every
18
+ * other command falls back to a real rendering derived from whatever its
19
+ * envelope happens to contain. The shape is discovered, not declared, so a new
20
+ * command is covered the day it is added.
21
+ *
22
+ * Deliberately dependency-free apart from the shared detail types and
23
+ * `escapeHtml`: this module sits underneath the per-command renderer
24
+ * registries and must not import them.
25
+ */
26
+ import { escapeHtml } from "./html-render.js";
4
27
  /**
5
28
  * True when `value` is a non-empty array of plain objects — the shape worth
6
29
  * rendering as a table rather than as a nested list. Uniformity is judged on
@@ -106,17 +129,6 @@ export function renderGenericMarkdown(command, value) {
106
129
  return `${rendered}\n`;
107
130
  }
108
131
  // ── HTML ─────────────────────────────────────────────────────────────────────
109
- const HTML_ESCAPES = {
110
- "&": "&amp;",
111
- "<": "&lt;",
112
- ">": "&gt;",
113
- '"': "&quot;",
114
- "'": "&#39;",
115
- };
116
- /** Escape every value that reaches the document — this is untrusted content. */
117
- function escapeHtml(text) {
118
- return text.replace(/[&<>"']/g, (char) => HTML_ESCAPES[char] ?? char);
119
- }
120
132
  function htmlTable(rows) {
121
133
  const columns = tableColumns(rows);
122
134
  if (columns.length === 0)
@@ -5,12 +5,14 @@
5
5
  * `--format html` rendering primitives (#582).
6
6
  *
7
7
  * Templates live in `src/assets/templates/html/` (mirrored to
8
- * `dist/assets/templates/html/` by `scripts/copy-assets.ts`). `--format html`
9
- * is health-only (chunk-9 WI-9.4c / Decision 4): `akm health` ships the sole
10
- * bespoke `<command>.html` template; every other command rejects `--format
11
- * html` with a `UsageError` before reaching this module (see
12
- * `src/cli/shared.ts`'s `output()`). Substitution is plain `%%TOKEN%%` string
13
- * replacement — no template engine, by design.
8
+ * `dist/assets/templates/html/` by `scripts/copy-assets.ts`). `akm health` is
9
+ * the only command with a bespoke `<command>.html` template (substituted here
10
+ * via plain `%%TOKEN%%` string replacement — no template engine, by design);
11
+ * `src/cli/shared.ts`'s `output()` calls it directly for `command === "health"`
12
+ * and every other command falls back to `renderGenericHtml` (`./generic-render`).
13
+ * `--format html` therefore works for every command (D7) — this module's
14
+ * template path is just health's opt-in bespoke rendering, not a format-wide
15
+ * gate.
14
16
  */
15
17
  import fs from "node:fs";
16
18
  import path from "node:path";
@@ -81,10 +83,11 @@ function readTemplate(templatePath) {
81
83
  }
82
84
  }
83
85
  /**
84
- * Minimal HTML entity escaping for text interpolated into templates. Escapes
85
- * the single quote as well as the double quote so escaped values are safe in
86
- * both `"…"` and `'…'` attribute contexts, not only the double-quoted
87
- * attributes the bundled templates use today.
86
+ * Minimal HTML entity escaping for text interpolated into a document — a
87
+ * template substitution here, or a value placed into `generic-render.ts`'s
88
+ * envelope rendering. Escapes the single quote as well as the double quote so
89
+ * escaped values are safe in both `"…"` and `'…'` attribute contexts, not only
90
+ * the double-quoted attributes the bundled templates use today.
88
91
  */
89
92
  export function escapeHtml(value) {
90
93
  return value