akm-cli 0.9.0-rc.1 → 0.9.0-rc.13

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 (587) hide show
  1. package/CHANGELOG.md +1190 -52
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/stash-skeleton/README.md +38 -10
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  38. package/dist/assets/tasks/core/extract.yml +3 -2
  39. package/dist/assets/tasks/core/improve.yml +2 -1
  40. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  41. package/dist/assets/tasks/core/sync.yml +1 -0
  42. package/dist/assets/tasks/core/version-check.yml +2 -1
  43. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  44. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  45. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  46. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  48. package/dist/assets/templates/html/health.html +5 -4
  49. package/dist/assets/workflows/workflow-template.md +31 -15
  50. package/dist/cli/invocation.js +279 -0
  51. package/dist/cli/parse-args.js +5 -90
  52. package/dist/cli/retired-commands.js +78 -0
  53. package/dist/cli/shared.js +158 -48
  54. package/dist/cli-node.mjs +2 -1
  55. package/dist/cli.js +747 -293
  56. package/dist/commands/agent/agent-dispatch.js +19 -18
  57. package/dist/commands/agent/agent-support.js +0 -24
  58. package/dist/commands/agent/contribute-cli.js +43 -97
  59. package/dist/commands/completions.js +80 -23
  60. package/dist/commands/config-cli.js +44 -281
  61. package/dist/commands/env/env-binding.js +13 -9
  62. package/dist/commands/env/env-cli.js +76 -159
  63. package/dist/commands/env/env.js +12 -163
  64. package/dist/commands/env/marker-path.js +6 -0
  65. package/dist/commands/env/secret-cli.js +45 -61
  66. package/dist/commands/env/secret.js +32 -62
  67. package/dist/commands/feedback-cli.js +179 -85
  68. package/dist/commands/health/accept-rate.js +58 -0
  69. package/dist/commands/health/advisories.js +7 -8
  70. package/dist/commands/health/checks.js +279 -94
  71. package/dist/commands/health/html-report.js +197 -578
  72. package/dist/commands/health/improve-metrics.js +277 -246
  73. package/dist/commands/health/llm-usage.js +19 -19
  74. package/dist/commands/health/md-report.js +16 -7
  75. package/dist/commands/health/metrics.js +67 -32
  76. package/dist/commands/health/renderers.js +47 -0
  77. package/dist/commands/health/report-view-model.js +508 -0
  78. package/dist/commands/health/stash-exposure.js +1 -1
  79. package/dist/commands/health/surfaces.js +16 -56
  80. package/dist/commands/health/task-runs.js +3 -67
  81. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  82. package/dist/commands/health/types-improve.js +29 -0
  83. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  84. package/dist/commands/health/types-result.js +7 -0
  85. package/dist/commands/health/types-runs.js +4 -0
  86. package/dist/commands/health/types-session-log.js +4 -0
  87. package/dist/commands/health/types-windows.js +4 -0
  88. package/dist/commands/health/types.js +26 -21
  89. package/dist/commands/health/windows.js +2 -3
  90. package/dist/commands/health.js +296 -167
  91. package/dist/commands/improve/anti-collapse.js +5 -5
  92. package/dist/commands/improve/autonomy-gate.js +68 -0
  93. package/dist/commands/improve/collapse-detector.js +65 -52
  94. package/dist/commands/improve/consolidate/chunking.js +9 -7
  95. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  96. package/dist/commands/improve/consolidate/merge.js +4 -0
  97. package/dist/commands/improve/consolidate.js +454 -1354
  98. package/dist/commands/improve/content-hash.js +39 -0
  99. package/dist/commands/improve/distill/content-repair.js +4 -10
  100. package/dist/commands/improve/distill/promote-memory.js +89 -64
  101. package/dist/commands/improve/distill/quality-gate.js +118 -42
  102. package/dist/commands/improve/distill-guards.js +1 -1
  103. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  104. package/dist/commands/improve/distill.js +607 -363
  105. package/dist/commands/improve/eligibility.js +165 -79
  106. package/dist/commands/improve/extract-cli.js +35 -126
  107. package/dist/commands/improve/extract-prompt.js +6 -35
  108. package/dist/commands/improve/extract.js +640 -391
  109. package/dist/commands/improve/feedback-valence.js +2 -12
  110. package/dist/commands/improve/improve-cli.js +134 -135
  111. package/dist/commands/improve/improve-result-file.js +30 -50
  112. package/dist/commands/improve/improve-run-types.js +4 -0
  113. package/dist/commands/improve/improve-strategies.js +135 -0
  114. package/dist/commands/improve/improve.js +904 -701
  115. package/dist/commands/improve/locks.js +64 -111
  116. package/dist/commands/improve/loop-stages.js +1110 -923
  117. package/dist/commands/improve/memory/derived-ref.js +124 -0
  118. package/dist/commands/improve/memory/memory-belief.js +79 -7
  119. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  120. package/dist/commands/improve/memory/memory-improve.js +25 -37
  121. package/dist/commands/improve/outcome-loop.js +25 -88
  122. package/dist/commands/improve/preparation.js +1034 -813
  123. package/dist/commands/improve/proactive-maintenance.js +34 -9
  124. package/dist/commands/improve/proposal-envelope.js +31 -0
  125. package/dist/commands/improve/reflect.js +983 -794
  126. package/dist/commands/improve/run-context.js +119 -0
  127. package/dist/commands/improve/salience.js +24 -127
  128. package/dist/commands/improve/session-asset.js +7 -3
  129. package/dist/commands/improve/shared.js +14 -34
  130. package/dist/commands/improve/source-identity.js +28 -0
  131. package/dist/commands/improve/triage.js +20 -17
  132. package/dist/commands/lint/base-linter.js +340 -313
  133. package/dist/commands/lint/env-key-rules.js +31 -47
  134. package/dist/commands/lint/index.js +185 -30
  135. package/dist/commands/{events.js → log.js} +28 -38
  136. package/dist/commands/migrate-cli.js +54 -0
  137. package/dist/commands/migration-tool.js +55 -0
  138. package/dist/commands/observability-cli.js +70 -208
  139. package/dist/commands/proposal/diff-format.js +50 -0
  140. package/dist/commands/proposal/drain-policies.js +0 -6
  141. package/dist/commands/proposal/drain.js +91 -40
  142. package/dist/commands/proposal/proposal-cli.js +134 -132
  143. package/dist/commands/proposal/proposal-types.js +56 -0
  144. package/dist/commands/proposal/proposal.js +83 -65
  145. package/dist/commands/proposal/propose-cli.js +88 -0
  146. package/dist/commands/proposal/propose.js +105 -88
  147. package/dist/commands/proposal/repository.js +1303 -278
  148. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  149. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  150. package/dist/commands/proposal/validators/proposals.js +6 -8
  151. package/dist/commands/read/curate.js +78 -73
  152. package/dist/commands/read/knowledge.js +510 -13
  153. package/dist/commands/read/registry-search.js +2 -2
  154. package/dist/commands/read/remember-cli.js +84 -15
  155. package/dist/commands/read/search-cli.js +203 -96
  156. package/dist/commands/read/search.js +126 -94
  157. package/dist/commands/read/show.js +226 -250
  158. package/dist/commands/registry-cli.js +34 -60
  159. package/dist/commands/remember.js +18 -57
  160. package/dist/commands/sources/add-cli.js +104 -49
  161. package/dist/commands/sources/bundle-cli.js +166 -0
  162. package/dist/commands/sources/bundle-config-ops.js +63 -0
  163. package/dist/commands/sources/info.js +27 -15
  164. package/dist/commands/sources/init.js +30 -40
  165. package/dist/commands/sources/installed-stashes.js +469 -172
  166. package/dist/commands/sources/schema-repair.js +10 -9
  167. package/dist/commands/sources/self-update.js +182 -121
  168. package/dist/commands/sources/source-add.js +169 -178
  169. package/dist/commands/sources/source-clone.js +144 -41
  170. package/dist/commands/sources/source-manage.js +94 -59
  171. package/dist/commands/sources/sources-cli.js +64 -205
  172. package/dist/commands/sources/stash-cli.js +91 -54
  173. package/dist/commands/sources/stash-skeleton.js +1 -1
  174. package/dist/commands/tasks/tasks-cli.js +106 -104
  175. package/dist/commands/tasks/tasks.js +445 -262
  176. package/dist/commands/workflow-cli.js +75 -228
  177. package/dist/core/action-contributors.js +1 -1
  178. package/dist/core/activation-policy.js +49 -0
  179. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  180. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  181. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  182. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  183. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  184. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  185. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  186. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  187. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  188. package/dist/core/adapter/adapters/index.js +80 -0
  189. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  190. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  191. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  192. package/dist/core/adapter/adapters/shared.js +286 -0
  193. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  194. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  195. package/dist/core/adapter/bundle-adapter.js +4 -0
  196. package/dist/core/adapter/detect-adapter.js +17 -0
  197. package/dist/core/adapter/recognize-match.js +44 -0
  198. package/dist/core/adapter/registry.js +56 -0
  199. package/dist/core/adapter/types.js +4 -0
  200. package/dist/core/asset/akm-markdown.js +30 -0
  201. package/dist/core/asset/asset-placement.js +243 -0
  202. package/dist/core/asset/asset-ref.js +110 -79
  203. package/dist/core/asset/asset-serialize.js +20 -0
  204. package/dist/core/asset/frontmatter.js +28 -12
  205. package/dist/core/asset/markdown.js +40 -51
  206. package/dist/core/asset/resolve-ref.js +274 -0
  207. package/dist/core/asset/stash-meta.js +2 -2
  208. package/dist/core/bundle-id.js +51 -0
  209. package/dist/core/common.js +281 -86
  210. package/dist/core/config/config-io.js +42 -128
  211. package/dist/core/config/config-schema.js +233 -855
  212. package/dist/core/config/config-sources.js +162 -39
  213. package/dist/core/config/config-types.js +16 -11
  214. package/dist/core/config/config-version.js +29 -0
  215. package/dist/core/config/config-walker.js +126 -37
  216. package/dist/core/config/config.js +154 -331
  217. package/dist/core/config/deep-merge.js +41 -0
  218. package/dist/core/config/engine-semantics.js +28 -0
  219. package/dist/core/config/experimental.js +21 -0
  220. package/dist/core/config/schema/embedding.js +38 -0
  221. package/dist/core/config/schema/engines.js +116 -0
  222. package/dist/core/config/schema/experimental.js +47 -0
  223. package/dist/core/config/schema/feedback.js +31 -0
  224. package/dist/core/config/schema/improve-processes.js +389 -0
  225. package/dist/core/config/schema/improve.js +94 -0
  226. package/dist/core/config/schema/index-config.js +176 -0
  227. package/dist/core/config/schema/output.js +18 -0
  228. package/dist/core/config/schema/primitives.js +94 -0
  229. package/dist/core/config/schema/search.js +30 -0
  230. package/dist/core/config/schema/setup.js +18 -0
  231. package/dist/core/config/schema/sources-bundles.js +169 -0
  232. package/dist/core/config/schema/workflow.js +29 -0
  233. package/dist/core/env-secret-ref.js +155 -20
  234. package/dist/core/errors.js +17 -15
  235. package/dist/core/events-types.js +4 -0
  236. package/dist/core/events.js +46 -128
  237. package/dist/core/extra-params.js +62 -0
  238. package/dist/core/file-change.js +17 -0
  239. package/dist/core/file-lock.js +202 -57
  240. package/dist/core/fs-txn.js +392 -0
  241. package/dist/core/git-message.js +59 -0
  242. package/dist/core/improve-result.js +167 -0
  243. package/dist/core/lesson-lint.js +1 -17
  244. package/dist/core/logs-db.js +1 -1
  245. package/dist/core/maintenance-barrier.js +135 -0
  246. package/dist/core/migration-operation.js +44 -0
  247. package/dist/core/mutation-target.js +78 -0
  248. package/dist/core/paths.js +22 -25
  249. package/dist/core/platform.js +10 -0
  250. package/dist/core/recognition-util.js +128 -0
  251. package/dist/core/redaction.js +392 -0
  252. package/dist/core/standards/resolve-standards-context.js +36 -65
  253. package/dist/core/standards/resolve-stash-standards.js +2 -2
  254. package/dist/core/standards/resolve-type-conventions.js +5 -5
  255. package/dist/core/state/migrations.js +242 -11
  256. package/dist/core/state-db.js +98 -10
  257. package/dist/core/structured.js +1 -1
  258. package/dist/core/subprocess.js +303 -0
  259. package/dist/core/text-truncation.js +9 -5
  260. package/dist/core/time.js +20 -0
  261. package/dist/core/type-presentation.js +130 -0
  262. package/dist/core/warn.js +0 -3
  263. package/dist/core/write-source.js +834 -118
  264. package/dist/indexer/bundle-identity-guard.js +92 -0
  265. package/dist/indexer/db/graph-db.js +1 -25
  266. package/dist/indexer/db/llm-cache.js +1 -1
  267. package/dist/indexer/ensure-index.js +30 -9
  268. package/dist/indexer/graph/graph-boost.js +9 -30
  269. package/dist/indexer/graph/graph-extraction.js +41 -27
  270. package/dist/indexer/graph/graph-types.js +4 -0
  271. package/dist/indexer/index-writer-lock.js +93 -49
  272. package/dist/indexer/index-written-assets.js +100 -53
  273. package/dist/indexer/indexer.js +746 -329
  274. package/dist/indexer/init.js +18 -25
  275. package/dist/indexer/installations.js +142 -0
  276. package/dist/indexer/passes/dir-staleness.js +18 -10
  277. package/dist/indexer/passes/memory-inference.js +25 -15
  278. package/dist/indexer/passes/metadata.js +412 -243
  279. package/dist/indexer/scan/doc-to-entry.js +160 -0
  280. package/dist/indexer/scan/drain-dir.js +134 -0
  281. package/dist/indexer/search/db-search.js +292 -108
  282. package/dist/indexer/search/fts-query.js +64 -0
  283. package/dist/indexer/search/ranking-contributors.js +145 -25
  284. package/dist/indexer/search/ranking-types.js +4 -0
  285. package/dist/indexer/search/ranking.js +28 -71
  286. package/dist/indexer/search/search-attribution.js +67 -0
  287. package/dist/indexer/search/search-fields.js +18 -3
  288. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  289. package/dist/indexer/search/search-source.js +157 -111
  290. package/dist/indexer/search/semantic-status.js +4 -1
  291. package/dist/indexer/usage/usage-events.js +10 -30
  292. package/dist/indexer/walk/file-context.js +3 -45
  293. package/dist/indexer/walk/matchers.js +42 -73
  294. package/dist/indexer/walk/path-resolver.js +11 -5
  295. package/dist/indexer/walk/walker.js +42 -14
  296. package/dist/integrations/agent/builder-shared.js +7 -0
  297. package/dist/integrations/agent/builders.js +5 -58
  298. package/dist/integrations/agent/config.js +3 -143
  299. package/dist/integrations/agent/detect.js +17 -2
  300. package/dist/integrations/agent/engine-resolution.js +231 -0
  301. package/dist/integrations/agent/index.js +1 -2
  302. package/dist/integrations/agent/model-aliases.js +8 -3
  303. package/dist/integrations/agent/profiles.js +6 -99
  304. package/dist/integrations/agent/prompts.js +46 -18
  305. package/dist/integrations/agent/runner-dispatch.js +78 -13
  306. package/dist/integrations/agent/runner.js +76 -208
  307. package/dist/integrations/agent/spawn.js +48 -279
  308. package/dist/integrations/harnesses/aider/agent-builder.js +9 -8
  309. package/dist/integrations/harnesses/aider/index.js +2 -12
  310. package/dist/integrations/harnesses/amazonq/agent-builder.js +10 -16
  311. package/dist/integrations/harnesses/amazonq/index.js +3 -17
  312. package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
  313. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  314. package/dist/integrations/harnesses/claude/index.js +1 -14
  315. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  316. package/dist/integrations/harnesses/codex/agent-builder.js +8 -7
  317. package/dist/integrations/harnesses/codex/index.js +2 -13
  318. package/dist/integrations/harnesses/copilot/agent-builder.js +9 -9
  319. package/dist/integrations/harnesses/copilot/index.js +1 -13
  320. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -9
  321. package/dist/integrations/harnesses/gemini/index.js +1 -13
  322. package/dist/integrations/harnesses/ids.js +24 -0
  323. package/dist/integrations/harnesses/index.js +31 -33
  324. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  325. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  326. package/dist/integrations/harnesses/opencode/index.js +1 -18
  327. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  328. package/dist/integrations/harnesses/opencode-sdk/harness.js +3 -17
  329. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
  330. package/dist/integrations/harnesses/openhands/agent-builder.js +12 -10
  331. package/dist/integrations/harnesses/openhands/index.js +2 -12
  332. package/dist/integrations/harnesses/pi/agent-builder.js +11 -18
  333. package/dist/integrations/harnesses/pi/index.js +3 -16
  334. package/dist/integrations/harnesses/shared.js +17 -0
  335. package/dist/integrations/harnesses/types.js +38 -34
  336. package/dist/integrations/lockfile.js +211 -24
  337. package/dist/integrations/session-logs/index.js +24 -40
  338. package/dist/integrations/session-logs/provider-base.js +113 -0
  339. package/dist/llm/client.js +182 -110
  340. package/dist/llm/embedders/deterministic.js +2 -2
  341. package/dist/llm/embedders/remote.js +21 -9
  342. package/dist/llm/feature-gate.js +17 -57
  343. package/dist/llm/graph-extract.js +12 -13
  344. package/dist/llm/index-passes.js +8 -42
  345. package/dist/llm/memory-infer.js +144 -1
  346. package/dist/llm/metadata-enhance.js +45 -30
  347. package/dist/llm/structured-call.js +16 -8
  348. package/dist/llm/usage-persist.js +30 -5
  349. package/dist/llm/usage-telemetry.js +59 -6
  350. package/dist/output/cli-hints.js +1 -2
  351. package/dist/output/command-registry.js +27 -0
  352. package/dist/output/context.js +22 -7
  353. package/dist/output/format-exempt.js +80 -0
  354. package/dist/output/generic-render.js +251 -0
  355. package/dist/output/html-render.js +11 -16
  356. package/dist/output/render-registry.js +57 -0
  357. package/dist/output/renderers.js +15 -281
  358. package/dist/output/shapes/curate.js +10 -1
  359. package/dist/output/shapes/events.js +12 -7
  360. package/dist/output/shapes/helpers.js +58 -84
  361. package/dist/output/shapes/passthrough.js +8 -40
  362. package/dist/output/shapes/proposal/producer.js +15 -7
  363. package/dist/output/shapes/registry.js +12 -6
  364. package/dist/output/shapes.js +0 -9
  365. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  366. package/dist/output/text/bundle-show.js +7 -0
  367. package/dist/output/text/command-format.js +562 -0
  368. package/dist/output/text/env.js +1 -3
  369. package/dist/output/text/events.js +8 -7
  370. package/dist/output/text/helpers.js +15 -1375
  371. package/dist/output/text/proposal/producer.js +4 -2
  372. package/dist/output/text/proposal-format.js +202 -0
  373. package/dist/output/text/registry-commands.js +1 -2
  374. package/dist/output/text/registry.js +12 -6
  375. package/dist/output/text/show-directives.js +117 -0
  376. package/dist/output/text/show-format.js +103 -0
  377. package/dist/output/text/sync.js +5 -0
  378. package/dist/output/text/workflow-format.js +332 -0
  379. package/dist/output/text/workflow.js +1 -2
  380. package/dist/output/text.js +10 -19
  381. package/dist/registry/factory.js +4 -6
  382. package/dist/registry/origin-resolve.js +16 -27
  383. package/dist/registry/providers/skills-sh.js +3 -3
  384. package/dist/registry/providers/static-index.js +15 -25
  385. package/dist/registry/resolve.js +43 -94
  386. package/dist/registry/semver.js +43 -0
  387. package/dist/runtime.js +81 -12
  388. package/dist/scripts/akm-migrate.js +35529 -0
  389. package/dist/setup/detect.js +5 -7
  390. package/dist/setup/detected-engines.js +136 -0
  391. package/dist/setup/engine-config.js +100 -0
  392. package/dist/setup/registry-stash-loader.js +3 -3
  393. package/dist/setup/semantic-assets.js +12 -9
  394. package/dist/setup/setup.js +444 -208
  395. package/dist/setup/steps/connection-shared.js +120 -0
  396. package/dist/setup/steps/connection.js +108 -305
  397. package/dist/setup/steps/platforms.js +13 -12
  398. package/dist/setup/steps/semantic.js +15 -3
  399. package/dist/setup/steps/sources.js +21 -15
  400. package/dist/setup/steps/stashdir.js +6 -4
  401. package/dist/setup/steps/tasks.js +236 -119
  402. package/dist/setup/steps.js +3 -2
  403. package/dist/sources/freshness.js +39 -0
  404. package/dist/sources/provider-factory.js +11 -17
  405. package/dist/sources/providers/filesystem.js +2 -3
  406. package/dist/sources/providers/git-install.js +278 -34
  407. package/dist/sources/providers/git-provider.js +54 -56
  408. package/dist/sources/providers/git-stash.js +420 -91
  409. package/dist/sources/providers/git.js +2 -2
  410. package/dist/sources/providers/npm.js +16 -19
  411. package/dist/sources/providers/provider-utils.js +47 -22
  412. package/dist/sources/providers/sync-from-ref.js +3 -9
  413. package/dist/sources/providers/website.js +2 -2
  414. package/dist/sources/resolve.js +11 -10
  415. package/dist/sources/snapshot-fetchers/types.js +4 -0
  416. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  417. package/dist/storage/database.js +60 -4
  418. package/dist/storage/engines/sqlite-migrations.js +156 -5
  419. package/dist/storage/locations.js +1 -2
  420. package/dist/storage/repositories/canaries-repository.js +1 -1
  421. package/dist/storage/repositories/events-repository.js +51 -11
  422. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  423. package/dist/storage/repositories/index-connection.js +79 -0
  424. package/dist/storage/repositories/index-db.js +4 -3
  425. package/dist/storage/repositories/index-entries-repository.js +863 -0
  426. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  427. package/dist/storage/repositories/index-entry-types.js +4 -0
  428. package/dist/storage/repositories/index-fts-repository.js +167 -0
  429. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  430. package/dist/storage/repositories/index-meta-repository.js +49 -0
  431. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  432. package/dist/storage/repositories/index-sql.js +12 -0
  433. package/dist/storage/repositories/index-utility-repository.js +356 -0
  434. package/dist/storage/repositories/index-vec-repository.js +250 -0
  435. package/dist/storage/repositories/outcome-repository.js +119 -0
  436. package/dist/storage/repositories/proposals-repository.js +317 -75
  437. package/dist/storage/repositories/registry-cache.js +1 -1
  438. package/dist/storage/repositories/salience-repository.js +172 -0
  439. package/dist/storage/repositories/task-history-repository.js +110 -3
  440. package/dist/storage/repositories/workflow-runs-repository.js +68 -35
  441. package/dist/tasks/backends/cron.js +169 -46
  442. package/dist/tasks/backends/exec-utils.js +76 -3
  443. package/dist/tasks/backends/index.js +6 -9
  444. package/dist/tasks/backends/launchd.js +292 -55
  445. package/dist/tasks/backends/schtasks.js +557 -70
  446. package/dist/tasks/backends/types.js +4 -0
  447. package/dist/tasks/command-executable.js +93 -0
  448. package/dist/tasks/embedded.js +56 -38
  449. package/dist/tasks/parser.js +156 -64
  450. package/dist/tasks/resolve-akm-bin.js +144 -51
  451. package/dist/tasks/runner.js +377 -209
  452. package/dist/tasks/schedule.js +108 -19
  453. package/dist/tasks/scheduler-invocation.js +296 -0
  454. package/dist/tasks/schema.js +1 -1
  455. package/dist/tasks/task-id.js +35 -0
  456. package/dist/tasks/validator.js +30 -16
  457. package/dist/workflows/authoring/authoring.js +96 -148
  458. package/dist/workflows/authoring/scope-key.js +1 -1
  459. package/dist/workflows/cli.js +0 -20
  460. package/dist/workflows/concurrency-policy.js +15 -0
  461. package/dist/workflows/exec/brief.js +25 -59
  462. package/dist/workflows/exec/frozen-judge.js +47 -0
  463. package/dist/workflows/exec/native-executor.js +157 -94
  464. package/dist/workflows/exec/report.js +365 -200
  465. package/dist/workflows/exec/run-workflow.js +46 -40
  466. package/dist/workflows/exec/scheduler.js +12 -41
  467. package/dist/workflows/exec/step-work.js +239 -205
  468. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  469. package/dist/workflows/ir/compile.js +141 -283
  470. package/dist/workflows/ir/freeze.js +233 -0
  471. package/dist/workflows/ir/plan-hash.js +40 -5
  472. package/dist/workflows/ir/schema.js +537 -1
  473. package/dist/workflows/parser.js +878 -306
  474. package/dist/workflows/program/expressions.js +20 -208
  475. package/dist/workflows/program/schema.js +7 -10
  476. package/dist/workflows/renderer.js +99 -121
  477. package/dist/workflows/resource-limits.js +22 -0
  478. package/dist/workflows/runtime/checkin.js +1 -1
  479. package/dist/workflows/runtime/plan-classifier.js +131 -0
  480. package/dist/workflows/runtime/runs.js +200 -113
  481. package/dist/workflows/runtime/unit-checkin.js +1 -1
  482. package/dist/workflows/runtime/unit-phases.js +20 -0
  483. package/dist/workflows/runtime/workflow-asset-loader.js +235 -97
  484. package/dist/workflows/schema.js +1 -11
  485. package/dist/workflows/validate-summary.js +4 -26
  486. package/dist/workflows/validator.js +52 -30
  487. package/docs/README.md +42 -78
  488. package/docs/migration/README.md +8 -0
  489. package/docs/migration/release-notes/0.6.0.md +1 -1
  490. package/docs/migration/release-notes/0.7.0.md +9 -8
  491. package/docs/migration/release-notes/0.9.0.md +158 -14
  492. package/docs/migration/v0.7-to-v0.8.md +46 -47
  493. package/docs/migration/v0.8-to-v0.9.md +844 -0
  494. package/docs/reference/README.md +12 -0
  495. package/docs/reference/data-and-telemetry.md +333 -0
  496. package/package.json +21 -17
  497. package/schemas/akm-asset-envelope.json +93 -0
  498. package/schemas/akm-config.json +4636 -0
  499. package/schemas/akm-task.json +87 -0
  500. package/{dist/schemas → schemas}/akm-workflow.json +127 -82
  501. package/dist/akm-migrate-storage +0 -38
  502. package/dist/assets/help/help-accept.md +0 -12
  503. package/dist/assets/help/help-improve.md +0 -84
  504. package/dist/assets/help/help-proposals.md +0 -17
  505. package/dist/assets/help/help-propose.md +0 -17
  506. package/dist/assets/help/help-reject.md +0 -11
  507. package/dist/assets/profiles/frequent.json +0 -13
  508. package/dist/assets/profiles/recombine-only.json +0 -21
  509. package/dist/assets/profiles/reflect-distill.json +0 -30
  510. package/dist/assets/profiles/synthesize.json +0 -15
  511. package/dist/assets/prompts/procedural-system.md +0 -44
  512. package/dist/assets/prompts/recombine-system.md +0 -40
  513. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  514. package/dist/assets/tasks/core/backup.yml +0 -4
  515. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  516. package/dist/assets/templates/html/default.html +0 -78
  517. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  518. package/dist/assets/wiki/index-template.md +0 -12
  519. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  520. package/dist/assets/wiki/log-template.md +0 -8
  521. package/dist/assets/wiki/schema-template.md +0 -61
  522. package/dist/cli/config-migrate.js +0 -150
  523. package/dist/cli/config-validate.js +0 -39
  524. package/dist/commands/graph/graph-cli.js +0 -124
  525. package/dist/commands/graph/graph.js +0 -487
  526. package/dist/commands/improve/calibration.js +0 -161
  527. package/dist/commands/improve/dedup.js +0 -482
  528. package/dist/commands/improve/extract-watch.js +0 -140
  529. package/dist/commands/improve/hot-probation.js +0 -45
  530. package/dist/commands/improve/improve-auto-accept.js +0 -276
  531. package/dist/commands/improve/improve-profiles.js +0 -168
  532. package/dist/commands/improve/procedural.js +0 -398
  533. package/dist/commands/improve/recombine.js +0 -818
  534. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  535. package/dist/commands/lint/agent-linter.js +0 -44
  536. package/dist/commands/lint/command-linter.js +0 -44
  537. package/dist/commands/lint/default-linter.js +0 -16
  538. package/dist/commands/lint/fact-linter.js +0 -39
  539. package/dist/commands/lint/knowledge-linter.js +0 -16
  540. package/dist/commands/lint/memory-linter.js +0 -61
  541. package/dist/commands/lint/registry.js +0 -41
  542. package/dist/commands/lint/skill-linter.js +0 -45
  543. package/dist/commands/lint/task-linter.js +0 -50
  544. package/dist/commands/lint/workflow-linter.js +0 -81
  545. package/dist/commands/proposal/legacy-import.js +0 -115
  546. package/dist/commands/sources/history.js +0 -196
  547. package/dist/commands/tasks/default-tasks.js +0 -186
  548. package/dist/commands/wiki-cli.js +0 -292
  549. package/dist/core/asset/asset-registry.js +0 -76
  550. package/dist/core/asset/asset-spec.js +0 -316
  551. package/dist/core/config/config-migration.js +0 -602
  552. package/dist/core/deep-merge.js +0 -38
  553. package/dist/core/eval/rank-metrics.js +0 -113
  554. package/dist/core/ripgrep/install.js +0 -163
  555. package/dist/core/ripgrep/resolve.js +0 -81
  556. package/dist/indexer/db/db.js +0 -1414
  557. package/dist/indexer/manifest.js +0 -170
  558. package/dist/indexer/passes/metadata-contributors.js +0 -31
  559. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  560. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -25
  561. package/dist/llm/call-ai.js +0 -62
  562. package/dist/llm/memory-infer-impl.js +0 -138
  563. package/dist/output/shapes/distill.js +0 -14
  564. package/dist/output/shapes/history.js +0 -11
  565. package/dist/output/text/distill.js +0 -6
  566. package/dist/output/text/enable-disable.js +0 -8
  567. package/dist/output/text/history.js +0 -6
  568. package/dist/output/text/wiki.js +0 -16
  569. package/dist/registry/build-index.js +0 -386
  570. package/dist/schemas/akm-config.json +0 -14225
  571. package/dist/scripts/migrate-storage.js +0 -13169
  572. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -10169
  573. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  574. package/dist/setup/legacy-config.js +0 -106
  575. package/dist/storage/repositories/consolidation-repository.js +0 -38
  576. package/dist/storage/repositories/recombine-repository.js +0 -213
  577. package/dist/wiki/wiki-templates.js +0 -15
  578. package/dist/wiki/wiki.js +0 -1012
  579. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  580. package/dist/workflows/db.js +0 -350
  581. package/dist/workflows/exec/watch.js +0 -116
  582. package/dist/workflows/program/parser.js +0 -760
  583. package/dist/workflows/program/project.js +0 -105
  584. package/docs/data-and-telemetry.md +0 -227
  585. package/docs/migration/release-notes/0.9.0-beta.60.md +0 -19
  586. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  587. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -13,20 +13,29 @@ export { extractInlineRefMentions } from "./inline-refs.js";
13
13
  // ([claude-code, opencode] — visible in e.g. `extract --auto` result order)
14
14
  // is preserved deterministically, independent of HARNESS_REGISTRY declaration
15
15
  // order (which is pinned for JSON-schema enum stability).
16
+ //
17
+ // WI-9.7 (H1): `SESSION_LOG_HARNESSES` is `HARNESS_REGISTRY` narrowed by the
18
+ // `isSessionLogHarness` type-predicate (see `../harnesses/types.ts`), so every
19
+ // element's `capabilities.sessionLogs` is the literal `true` and
20
+ // `sessionLogProvider` is a required (non-optional) field — a harness
21
+ // declaring `sessionLogs: true` without a provider is now a compile error at
22
+ // its `HARNESS_REGISTRY` entry, not a throw here. The load-time throw this
23
+ // used to guard (`h.sessionLogProvider?.()` returning undefined) is gone;
24
+ // `h.sessionLogProvider()` cannot be called with no provider to invoke.
16
25
  const HARNESSES = [...SESSION_LOG_HARNESSES]
17
26
  .sort((a, b) => a.id.localeCompare(b.id))
18
- .map((h) => {
19
- const provider = h.sessionLogProvider?.();
20
- if (!provider) {
21
- throw new Error(`[akm] harness "${h.id}" declares capabilities.sessionLogs but no sessionLogProvider factory (src/integrations/harnesses). Add one to its descriptor.`);
22
- }
23
- return provider;
24
- });
25
- // Reverse invariant (kept from #562): every derived provider's runtime name
26
- // must resolve back — via the id-normalization bridge, so a provider named
27
- // "claude-code" still maps to the canonical "claude" harness — to a registry
28
- // harness whose `sessionLogs` capability is set. Catches a descriptor whose
29
- // factory returns a provider named for a different/unregistered harness.
27
+ .map((h) => h.sessionLogProvider());
28
+ // Reverse invariant (kept from #562): every derived provider's runtime `name`
29
+ // — a plain string the provider implementation sets independently of the
30
+ // harness registry (e.g. `ClaudeCodeProvider`'s `"claude-code"`) — must
31
+ // resolve back, via the id-normalization bridge, to a registry harness whose
32
+ // `sessionLogs` capability is set. This is NOT expressible as a compile-time
33
+ // type constraint: `SessionLogHarness.name` is a runtime string with no
34
+ // static link to the `AkmHarness` that produced it, so a typo'd or
35
+ // stale `name` (e.g. copy-pasted from another provider, or renamed on one
36
+ // side only) would only ever surface as a wrong/silent lookup miss much later
37
+ // (health scans, `extract --auto` attribution) rather than here at load time.
38
+ // Kept as a runtime guard for that reason.
30
39
  for (const provider of HARNESSES) {
31
40
  const harness = getHarness(provider.name);
32
41
  if (!harness?.capabilities.sessionLogs) {
@@ -41,22 +50,6 @@ const ERROR_PATTERNS = /error|failed|exception|cannot|undefined|null pointer|ENO
41
50
  export function getAvailableHarnesses() {
42
51
  return HARNESSES.filter((harness) => harness.isAvailable());
43
52
  }
44
- /**
45
- * Map each available harness to its `{ harnessName, roots }` watch target,
46
- * skipping harnesses that expose no roots (absent `watchRoots()` or an empty
47
- * result). This is the one stable entry point the watcher uses so it never
48
- * reaches into providers directly.
49
- */
50
- export function getWatchTargets() {
51
- const targets = [];
52
- for (const harness of getAvailableHarnesses()) {
53
- const roots = harness.watchRoots?.() ?? [];
54
- if (roots.length === 0)
55
- continue;
56
- targets.push({ harnessName: harness.name, roots });
57
- }
58
- return targets;
59
- }
60
53
  export function normalizeSessionTopic(text) {
61
54
  const normalized = text.replace(/\s+/g, " ").trim().toLowerCase();
62
55
  if (normalized.length < 10)
@@ -98,16 +91,13 @@ export function aggregateSessionEvents(events) {
98
91
  * Collect normalized session events from a set of harnesses for the health
99
92
  * candidate scan (#568).
100
93
  *
101
- * Pipeline selection per harness (capability-gated):
102
- * - readSession-capable harness (`supportsReadSession !== false`): drive the
103
- * richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
94
+ * Pipeline selection per harness:
95
+ * - Drive the richer `listSessions()` + `readSession()` pipeline. `readSession` flattens
104
96
  * structured content — tool calls, assistant content blocks, thinking,
105
97
  * tool_result — into event text (e.g. ClaudeCodeProvider's `parseClaudeEvent`
106
98
  * surfaces `[tool:*]` / `[tool_result]` blocks that the legacy flat
107
99
  * `readEvents` scan drops entirely). This is what lets health advisories see
108
100
  * repeated tool failures / long runs that the flat scan hid.
109
- * - legacy-only harness (`supportsReadSession === false`): fall back to the
110
- * legacy flat `readEvents()` scan (behaviour-preserving).
111
101
  *
112
102
  * Extracted as a pure function (harnesses injected) so it is unit-testable
113
103
  * without touching the real on-disk session-log locations.
@@ -120,19 +110,13 @@ export function aggregateSessionEvents(events) {
120
110
  * health command (`akm health`, which calls this synchronously) blow past its
121
111
  * latency budget. `listSessions()` returns summaries sorted newest-first, so
122
112
  * capping to the most-recent N sessions per harness keeps the richer signal
123
- * for what actually matters (recent activity) while bounding cost. The legacy
124
- * flat-scan path is naturally cheaper and is left uncapped.
113
+ * for what actually matters (recent activity) while bounding cost.
125
114
  */
126
115
  const DEFAULT_MAX_SESSIONS_PER_HARNESS = 50;
127
116
  export function collectSessionEvents(harnesses, sinceMs, maxSessionsPerHarness = DEFAULT_MAX_SESSIONS_PER_HARNESS) {
128
117
  const events = [];
129
118
  for (const harness of harnesses) {
130
119
  try {
131
- if (harness.supportsReadSession === false) {
132
- // Legacy-only harness: only the flat event scan is available.
133
- events.push(...harness.readEvents({ sinceMs }));
134
- continue;
135
- }
136
120
  // Rich path: enumerate sessions cheaply, then read each one's full
137
121
  // structured event stream. Falls back to readEvents if listSessions
138
122
  // surfaces nothing (e.g. a harness that wired readSession but whose
@@ -0,0 +1,113 @@
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
+ * Shared skeleton for {@link SessionLogHarness} providers (§4.6 dedup).
6
+ *
7
+ * Holds only what the concrete providers genuinely share: safe stat'ing,
8
+ * recursive directory walking, the mtime-filtered file→summary listing loop,
9
+ * the flat JSONL/log line→event scan, and conditional-spread assembly of
10
+ * {@link SessionSummary} refs stamped with the provider's runtime name.
11
+ * Everything platform-specific — file layouts, metadata peeking, SQLite
12
+ * stores, message flattening — stays in the subclasses.
13
+ */
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ export class AbstractSessionLogProvider {
17
+ isAvailable() {
18
+ return fs.existsSync(this.availabilityRoot());
19
+ }
20
+ /** `fs.statSync` that returns `undefined` instead of throwing. */
21
+ statSafe(target) {
22
+ try {
23
+ return fs.statSync(target);
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ /** Recursively yield files under `dir` whose basename passes `matches`. */
30
+ *walkFiles(dir, matches) {
31
+ try {
32
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
33
+ const full = path.join(dir, entry.name);
34
+ if (entry.isDirectory())
35
+ yield* this.walkFiles(full, matches);
36
+ else if (matches(entry.name))
37
+ yield full;
38
+ }
39
+ }
40
+ catch {
41
+ // permission errors etc.
42
+ }
43
+ }
44
+ /**
45
+ * Assemble a {@link SessionSummary} stamped with this provider's name.
46
+ * Timestamps are included whenever defined (0 is valid); `projectHint` /
47
+ * `title` only when non-empty, so absent metadata stays absent.
48
+ */
49
+ sessionRef(input) {
50
+ return {
51
+ harness: this.name,
52
+ sessionId: input.sessionId,
53
+ filePath: input.filePath,
54
+ ...(input.startedAt !== undefined ? { startedAt: input.startedAt } : {}),
55
+ ...(input.endedAt !== undefined ? { endedAt: input.endedAt } : {}),
56
+ ...(input.projectHint ? { projectHint: input.projectHint } : {}),
57
+ ...(input.title ? { title: input.title } : {}),
58
+ };
59
+ }
60
+ /**
61
+ * The shared listing loop: enumerate candidate session files, drop the
62
+ * unstat'able and the ones older than `sinceMs`, summarize the rest, and
63
+ * sort newest-ended first. An enumeration failure (root missing or
64
+ * unreadable) returns what was collected so far rather than throwing.
65
+ */
66
+ listSessionsFromFiles(input) {
67
+ const summaries = [];
68
+ try {
69
+ for (const filePath of input.enumerate()) {
70
+ const stat = this.statSafe(filePath);
71
+ if (!stat)
72
+ continue;
73
+ if (stat.mtimeMs < input.sinceMs)
74
+ continue;
75
+ const summary = input.summarize(filePath, stat);
76
+ if (summary)
77
+ summaries.push(summary);
78
+ }
79
+ }
80
+ catch {
81
+ // Root missing or unreadable — return what we have.
82
+ }
83
+ return summaries.sort((a, b) => (b.endedAt ?? 0) - (a.endedAt ?? 0));
84
+ }
85
+ /**
86
+ * The legacy flat line scan shared by both providers' `readEvents`: parse
87
+ * each line as JSON, pull text/session-id through the per-provider
88
+ * selectors, skip entries whose text is missing or under 10 chars, and
89
+ * fall back to the file mtime when the entry carries no numeric timestamp.
90
+ */
91
+ *logLineEvents(input) {
92
+ for (const line of input.lines) {
93
+ try {
94
+ const entry = JSON.parse(line);
95
+ const text = input.selectText(entry);
96
+ if (typeof text !== "string" || text.length < 10)
97
+ continue;
98
+ const sessionId = input.selectSessionId(entry);
99
+ yield {
100
+ harness: this.name,
101
+ text,
102
+ ts: typeof entry?.timestamp === "number" ? entry.timestamp : input.fallbackTsMs,
103
+ sessionId: typeof sessionId === "string" ? sessionId : undefined,
104
+ role: typeof entry?.role === "string" ? entry.role : "unknown",
105
+ filePath: input.filePath,
106
+ };
107
+ }
108
+ catch {
109
+ // skip malformed lines
110
+ }
111
+ }
112
+ }
113
+ }
@@ -4,45 +4,34 @@
4
4
  /**
5
5
  * Low-level OpenAI-compatible chat completions client and capability probing.
6
6
  *
7
- * Split out of `llm.ts` to keep the transport-layer concerns (HTTP request,
8
- * response parsing, JSON-fence stripping, capability probe, availability
9
- * check) separate from higher-level metadata-enhancement workflows.
10
- *
11
- * `llm.ts` re-exports everything from this module for backward compatibility.
7
+ * Keeps transport-layer concerns (HTTP request, response parsing, capability
8
+ * probing, and availability checks) separate from higher-level workflows.
12
9
  */
13
- import { fetchWithTimeout } from "../core/common.js";
10
+ import { fetchWithTimeout, readBodyWithByteCap } from "../core/common.js";
14
11
  import { resolveSecret } from "../core/config/config.js";
15
- import { escapeJsonStringControls, parseJsonResponse, stripCodeFences, stripThinkBlocks } from "../core/parse.js";
12
+ import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-params.js";
13
+ import { parseJsonResponse } from "../core/parse.js";
14
+ import { redactCredentialPatterns, redactSensitiveText } from "../core/redaction.js";
16
15
  import { warnVerbose } from "../core/warn.js";
17
- import { emitLlmUsage, extractUsageTokens } from "./usage-telemetry.js";
18
- // Re-export shared parse utilities so existing importers of `client.ts` continue
19
- // to resolve `parseJsonResponse` and `parseEmbeddedJsonResponse` from this module.
20
- export { escapeJsonStringControls, parseEmbeddedJsonResponse, parseJsonResponse, stripCodeFences, stripThinkBlocks, } from "../core/parse.js";
16
+ import { DEFAULT_LLM_TIMEOUT_MS } from "../integrations/agent/config.js";
17
+ import { emitLlmUsage, extractUsageTokens, } from "./usage-telemetry.js";
21
18
  /** Maximum length of an LLM error response body included in thrown errors. */
22
19
  const ERROR_BODY_MAX_LEN = 200;
20
+ /** Stable OpenAI-compatible response-schema name used for every structured call. */
21
+ const JSON_SCHEMA_RESPONSE_NAME = "akm_response";
23
22
  /**
24
23
  * Redact credential-shaped substrings from an upstream error body before
25
24
  * including it in a thrown Error. The body is also trimmed to a fixed length
26
25
  * so that a verbose provider response cannot leak large amounts of context.
27
26
  *
28
- * Targets:
29
- * - `Bearer <token>` headers echoed back by the provider
30
- * - `sk-…` / `sk_…` style API keys (OpenAI / Anthropic-shaped)
31
- * - `key-…` / `key_…` shorthand keys
32
- * - `"api_key": "…"` / `"apiKey": "…"` JSON fields
27
+ * The pattern set itself lives in {@link redactCredentialPatterns}
28
+ * (src/core/redaction.ts) so other output paths (e.g. task run logs) can
29
+ * reuse it without this function's length cap.
33
30
  */
34
31
  export function redactErrorBody(input) {
35
32
  if (!input)
36
33
  return "";
37
- let out = input
38
- // Bearer tokens (case-insensitive)
39
- .replace(/\bBearer\s+[A-Za-z0-9._\-+/=]+/gi, "Bearer [REDACTED]")
40
- // sk-/sk_ style keys
41
- .replace(/\bsk[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
42
- // key-/key_ shorthand keys
43
- .replace(/\bkey[-_][A-Za-z0-9._-]{6,}/g, "[REDACTED]")
44
- // JSON-style "api_key": "...", "apiKey": "...", "api-key": "..."
45
- .replace(/("(?:api[_-]?key|apiKey|authorization|token)"\s*:\s*")([^"]*)(")/gi, "$1[REDACTED]$3");
34
+ let out = redactCredentialPatterns(input);
46
35
  if (out.length > ERROR_BODY_MAX_LEN) {
47
36
  out = `${out.slice(0, ERROR_BODY_MAX_LEN)}…`;
48
37
  }
@@ -98,6 +87,19 @@ const RETRY_BUDGET_FRACTION = 0.9;
98
87
  export function sleep(ms) {
99
88
  return new Promise((resolve) => setTimeout(resolve, ms));
100
89
  }
90
+ async function waitForRetry(ms, wait, signal) {
91
+ if (!signal)
92
+ return wait(ms);
93
+ if (signal.aborted)
94
+ throw new LlmCallError("LLM request aborted", "aborted");
95
+ await new Promise((resolve, reject) => {
96
+ const onAbort = () => reject(new LlmCallError("LLM request aborted", "aborted"));
97
+ signal.addEventListener("abort", onAbort, { once: true });
98
+ wait(ms)
99
+ .then(resolve, reject)
100
+ .finally(() => signal.removeEventListener("abort", onAbort));
101
+ });
102
+ }
101
103
  /** Compute a uniform jittered backoff in the [200, 800)ms range. */
102
104
  function retryBackoffMs() {
103
105
  return RETRY_BACKOFF_MIN_MS + Math.random() * (RETRY_BACKOFF_MAX_MS - RETRY_BACKOFF_MIN_MS);
@@ -178,7 +180,11 @@ export async function chatCompletion(config, messages, options) {
178
180
  return chatCompletionReal(config, messages, options);
179
181
  }
180
182
  async function chatCompletionReal(config, messages, options) {
181
- const effectiveTimeoutMs = options?.timeoutMs ?? config.timeoutMs ?? 120_000;
183
+ const effectiveTimeoutMs = options && Object.hasOwn(options, "timeoutMs")
184
+ ? (options.timeoutMs ?? null)
185
+ : Object.hasOwn(config, "timeoutMs")
186
+ ? (config.timeoutMs ?? null)
187
+ : DEFAULT_LLM_TIMEOUT_MS;
182
188
  const started = Date.now();
183
189
  try {
184
190
  return await chatCompletionAttempt(config, messages, options, effectiveTimeoutMs);
@@ -189,8 +195,9 @@ async function chatCompletionReal(config, messages, options) {
189
195
  // Timeout-budget guard: if the first attempt already burned most of the
190
196
  // budget, a second attempt cannot complete — skip the retry.
191
197
  const elapsed = Date.now() - started;
192
- const remaining = effectiveTimeoutMs - elapsed;
193
- if (elapsed >= effectiveTimeoutMs * RETRY_BUDGET_FRACTION || remaining <= 0) {
198
+ const remaining = effectiveTimeoutMs === null ? null : effectiveTimeoutMs - elapsed;
199
+ if (effectiveTimeoutMs !== null &&
200
+ (elapsed >= effectiveTimeoutMs * RETRY_BUDGET_FRACTION || (remaining !== null && remaining <= 0))) {
194
201
  throw err;
195
202
  }
196
203
  // Signal the caller so it can bump `retryAttempts` (NOT `failureCount`).
@@ -199,7 +206,7 @@ async function chatCompletionReal(config, messages, options) {
199
206
  // authoritative.
200
207
  warnVerbose(`[akm] LLM transient failure (${err.code}); retrying once: ${err.message}`);
201
208
  const wait = retryBackoffMs();
202
- await (options?.sleep ?? sleep)(wait);
209
+ await waitForRetry(wait, options?.sleep ?? sleep, options?.signal);
203
210
  // The retry must not exceed the original budget.
204
211
  return await chatCompletionAttempt(config, messages, options, remaining);
205
212
  }
@@ -210,6 +217,11 @@ async function chatCompletionReal(config, messages, options) {
210
217
  * transient failures.
211
218
  */
212
219
  async function chatCompletionAttempt(config, messages, options, timeoutMs) {
220
+ if (config.extraParams !== undefined) {
221
+ const issue = validateExtraParams(config.extraParams)[0];
222
+ if (issue)
223
+ throw new Error(formatExtraParamsIssue("LLM extraParams", issue));
224
+ }
213
225
  const headers = { "Content-Type": "application/json" };
214
226
  const resolvedKey = resolveSecret(config.apiKey);
215
227
  if (resolvedKey) {
@@ -220,96 +232,142 @@ async function chatCompletionAttempt(config, messages, options, timeoutMs) {
220
232
  // guess is wrong. Users who need a cap can set llm.maxTokens in config.
221
233
  const resolvedMaxTokens = options?.maxTokens ?? config.maxTokens;
222
234
  const responseFormat = options?.responseSchema && config.supportsJsonSchema
223
- ? { response_format: { type: "json_schema", json_schema: { schema: options.responseSchema, strict: true } } }
235
+ ? {
236
+ response_format: {
237
+ type: "json_schema",
238
+ json_schema: { name: JSON_SCHEMA_RESPONSE_NAME, schema: options.responseSchema, strict: true },
239
+ },
240
+ }
224
241
  : {};
225
- // Wall-clock start for per-call usage telemetry (#576). Captured here so the
242
+ const resolvedEnableThinking = options?.enableThinking ?? config.enableThinking;
243
+ const thinkingParams = resolvedEnableThinking === undefined
244
+ ? {}
245
+ : config.provider === "vllm"
246
+ ? { chat_template_kwargs: { enable_thinking: resolvedEnableThinking } }
247
+ : { enable_thinking: resolvedEnableThinking };
248
+ const requestBody = JSON.stringify({
249
+ model: config.model,
250
+ messages,
251
+ temperature: options?.temperature ?? config.temperature ?? 0.3,
252
+ ...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
253
+ ...responseFormat,
254
+ ...thinkingParams,
255
+ ...config.extraParams,
256
+ });
257
+ // Wall-clock start for per-attempt usage telemetry (#576). Captured here so the
226
258
  // emitted duration covers the full request/response/parse cycle of a single
227
259
  // attempt, not the retry-wrapping `chatCompletion`.
228
260
  const requestStartedAt = Date.now();
229
- let response;
261
+ const requestDeadlineAt = timeoutMs === null ? null : requestStartedAt + timeoutMs;
262
+ const remainingAttemptMs = () => requestDeadlineAt === null ? undefined : Math.max(0, requestDeadlineAt - Date.now());
263
+ let terminalFields = {
264
+ model: config.model,
265
+ modelSource: "configured",
266
+ };
230
267
  try {
231
- response = await fetchWithTimeout(config.endpoint, {
232
- method: "POST",
233
- headers,
234
- body: JSON.stringify({
235
- model: config.model,
236
- messages,
237
- temperature: options?.temperature ?? config.temperature ?? 0.3,
238
- ...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
239
- ...responseFormat,
240
- ...(options?.enableThinking !== undefined
241
- ? { enable_thinking: options.enableThinking }
242
- : config.enableThinking !== undefined
243
- ? { enable_thinking: config.enableThinking }
244
- : {}),
245
- ...config.extraParams,
246
- }),
247
- }, timeoutMs, options?.signal);
248
- }
249
- catch (err) {
250
- // fetchWithTimeout throws a plain Error with a message containing
251
- // "timed out" for AbortController-driven timeouts, or "aborted" for
252
- // caller-driven cancellations. Map both to typed LlmCallError.
253
- const msg = err instanceof Error ? err.message : String(err);
254
- if (err instanceof DOMException && err.name === "AbortError") {
255
- throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
268
+ let response;
269
+ try {
270
+ response = await fetchWithTimeout(config.endpoint, {
271
+ method: "POST",
272
+ headers,
273
+ body: requestBody,
274
+ }, timeoutMs, options?.signal);
256
275
  }
257
- if (msg.includes("timed out")) {
258
- throw new LlmCallError(`Request timed out after ${timeoutMs}ms`, "timeout");
276
+ catch (err) {
277
+ // fetchWithTimeout throws a plain Error with a message containing
278
+ // "timed out" for AbortController-driven timeouts, or "aborted" for
279
+ // caller-driven cancellations. Map both to typed LlmCallError.
280
+ const msg = err instanceof Error ? err.message : String(err);
281
+ if (options?.signal?.aborted || msg.includes("Request aborted")) {
282
+ throw new LlmCallError("LLM request aborted", "aborted");
283
+ }
284
+ if (err instanceof DOMException && err.name === "AbortError") {
285
+ throw new LlmCallError(`Request timed out${timeoutMs === null ? "" : ` after ${timeoutMs}ms`}`, "timeout");
286
+ }
287
+ if (msg.includes("timed out")) {
288
+ throw new LlmCallError(`Request timed out${timeoutMs === null ? "" : ` after ${timeoutMs}ms`}`, "timeout");
289
+ }
290
+ throw new LlmCallError(`Network error: ${msg}`, "network_error");
259
291
  }
260
- throw new LlmCallError(`Network error: ${msg}`, "network_error");
261
- }
262
- if (!response.ok) {
263
- const rawBody = await response.text().catch(() => "");
264
- const safeBody = redactErrorBody(rawBody);
265
- const status = response.status;
266
- if (status === 429) {
267
- throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
292
+ if (!response.ok) {
293
+ const rawBody = await readBodyWithByteCap(response, undefined, {
294
+ ...(requestDeadlineAt === null ? {} : { bodyTimeoutMs: remainingAttemptMs() }),
295
+ signal: options?.signal,
296
+ }).catch((err) => {
297
+ if (options?.signal?.aborted)
298
+ throw new LlmCallError("LLM response read aborted", "aborted");
299
+ if (err instanceof Error && err.name === "BodyReadTimeoutError") {
300
+ throw new LlmCallError("LLM response body read timed out", "timeout");
301
+ }
302
+ return "";
303
+ });
304
+ const safeBody = redactSensitiveText(redactErrorBody(rawBody), resolvedKey ? [resolvedKey] : []);
305
+ const status = response.status;
306
+ if (status === 429) {
307
+ throw new LlmCallError(`LLM request rate limited (429) ${config.endpoint}: ${safeBody}`, "rate_limited", status);
308
+ }
309
+ if (status >= 500 && isHtmlResponse(rawBody)) {
310
+ throw new LlmCallError(`LLM provider returned HTML instead of JSON (${status}) ${config.endpoint}: ${redactSensitiveText(htmlExcerpt(rawBody), resolvedKey ? [resolvedKey] : [])}`, "provider_html_error", status);
311
+ }
312
+ if (status >= 500) {
313
+ throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
314
+ }
315
+ throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
268
316
  }
269
- if (status >= 500 && isHtmlResponse(rawBody)) {
270
- throw new LlmCallError(`LLM provider returned HTML instead of JSON (${status}) ${config.endpoint}: ${htmlExcerpt(rawBody)}`, "provider_html_error", status);
317
+ // A 2xx response is still an error if the body is HTML where JSON was
318
+ // expected (e.g. a provider serving its web UI). Read the raw body first so
319
+ // we can categorize an HTML page distinctly from a malformed-JSON parse_error.
320
+ let rawOkBody;
321
+ try {
322
+ rawOkBody = await readBodyWithByteCap(response, undefined, {
323
+ ...(requestDeadlineAt === null ? {} : { bodyTimeoutMs: remainingAttemptMs() }),
324
+ signal: options?.signal,
325
+ });
271
326
  }
272
- if (status >= 500) {
273
- throw new LlmCallError(`LLM provider error (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
327
+ catch (err) {
328
+ if (options?.signal?.aborted)
329
+ throw new LlmCallError("LLM response read aborted", "aborted");
330
+ if (err instanceof Error && err.name === "BodyReadTimeoutError") {
331
+ throw new LlmCallError("LLM response body read timed out", "timeout");
332
+ }
333
+ throw err;
274
334
  }
275
- throw new LlmCallError(`LLM request failed (${status}) ${config.endpoint}: ${safeBody}`, "provider_error", status);
276
- }
277
- // A 2xx response is still an error if the body is HTML where JSON was
278
- // expected (e.g. a provider serving its web UI). Read the raw body first so
279
- // we can categorize an HTML page distinctly from a malformed-JSON parse_error.
280
- const rawOkBody = await response.text();
281
- if (isHtmlResponse(rawOkBody)) {
282
- throw new LlmCallError(`LLM provider returned HTML instead of JSON (${response.status}) ${config.endpoint}: ${htmlExcerpt(rawOkBody)}`, "provider_html_error", response.status);
283
- }
284
- let json;
285
- try {
286
- json = JSON.parse(rawOkBody);
335
+ if (isHtmlResponse(rawOkBody)) {
336
+ throw new LlmCallError(`LLM provider returned HTML instead of JSON (${response.status}) ${config.endpoint}: ${redactSensitiveText(htmlExcerpt(rawOkBody), resolvedKey ? [resolvedKey] : [])}`, "provider_html_error", response.status);
337
+ }
338
+ let json;
339
+ try {
340
+ json = JSON.parse(rawOkBody);
341
+ }
342
+ catch {
343
+ throw new LlmCallError(`LLM response was not valid JSON ${config.endpoint}: ${redactSensitiveText(redactErrorBody(rawOkBody), resolvedKey ? [resolvedKey] : [])}`, "parse_error", response.status);
344
+ }
345
+ const responseModel = typeof json.model === "string" && json.model.trim().length > 0 ? json.model : undefined;
346
+ terminalFields = {
347
+ model: responseModel ?? config.model,
348
+ modelSource: responseModel === undefined ? "configured" : "response",
349
+ finishReason: typeof json.choices?.[0]?.finish_reason === "string" ? json.choices[0].finish_reason : undefined,
350
+ ...extractUsageTokens(json.usage),
351
+ };
352
+ const content = (json.choices?.[0]?.message?.content ?? "").trim();
353
+ const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
354
+ const result = redactSensitiveText(content || reasoning, resolvedKey ? [resolvedKey] : []);
355
+ emitLlmUsage({
356
+ ...terminalFields,
357
+ outcome: "success",
358
+ durationMs: Date.now() - requestStartedAt,
359
+ });
360
+ return result;
287
361
  }
288
- catch {
289
- throw new LlmCallError(`LLM response was not valid JSON ${config.endpoint}: ${redactErrorBody(rawOkBody)}`, "parse_error", response.status);
362
+ catch (err) {
363
+ emitLlmUsage({
364
+ ...terminalFields,
365
+ outcome: "error",
366
+ durationMs: Date.now() - requestStartedAt,
367
+ errorCode: err instanceof LlmCallError ? err.code : "unknown_error",
368
+ });
369
+ throw err;
290
370
  }
291
- // Per-call usage telemetry (#576). Best-effort and fully isolated: a missing
292
- // or garbled usage block still records duration + model, and a throwing sink
293
- // can never fail the call (emitLlmUsage swallows its own errors). The stage
294
- // is supplied ambiently by emitLlmUsage; no `stage` param is threaded here.
295
- emitLlmUsage({
296
- model: typeof json.model === "string" && json.model ? json.model : config.model,
297
- durationMs: Date.now() - requestStartedAt,
298
- finishReason: typeof json.choices?.[0]?.finish_reason === "string" ? json.choices[0].finish_reason : undefined,
299
- ...extractUsageTokens(json.usage),
300
- });
301
- const content = (json.choices?.[0]?.message?.content ?? "").trim();
302
- const reasoning = (json.choices?.[0]?.message?.reasoning_content ?? "").trim();
303
- return content || reasoning;
304
- }
305
- /**
306
- * Strip `<think>` blocks, code fences, and escape control characters in JSON
307
- * strings. Thin wrapper kept for backward compatibility with call sites that
308
- * import `stripJsonFences` from this module. New code should prefer the
309
- * granular helpers from `../core/parse`.
310
- */
311
- export function stripJsonFences(raw) {
312
- return escapeJsonStringControls(stripCodeFences(stripThinkBlocks(raw)));
313
371
  }
314
372
  // ── Availability check ──────────────────────────────────────────────────────
315
373
  /**
@@ -325,6 +383,16 @@ export async function isLlmAvailable(config) {
325
383
  }
326
384
  }
327
385
  // ── Capability probe ────────────────────────────────────────────────────────
386
+ const CAPABILITY_PROBE_JSON_SCHEMA = {
387
+ type: "object",
388
+ properties: {
389
+ ok: { type: "boolean", const: true },
390
+ ingest: { type: "boolean", const: true },
391
+ lint: { type: "boolean", const: true },
392
+ },
393
+ required: ["ok", "ingest", "lint"],
394
+ additionalProperties: false,
395
+ };
328
396
  /**
329
397
  * Ask the model to emit a strict JSON object so we know whether the knowledge
330
398
  * wiki ingest/lint flows can rely on structured output. Failure is non-fatal —
@@ -332,7 +400,7 @@ export async function isLlmAvailable(config) {
332
400
  */
333
401
  export async function probeLlmCapabilities(config) {
334
402
  try {
335
- const raw = await chatCompletion(config, [
403
+ const raw = await chatCompletion({ ...config, supportsJsonSchema: true }, [
336
404
  {
337
405
  role: "system",
338
406
  content: "You return only valid JSON. No prose, no markdown fences.",
@@ -341,13 +409,17 @@ export async function probeLlmCapabilities(config) {
341
409
  role: "user",
342
410
  content: 'Return exactly this JSON object and nothing else: {"ok": true, "ingest": true, "lint": true}',
343
411
  },
344
- ], { maxTokens: 64, temperature: 0 });
412
+ ], { maxTokens: 64, temperature: 0, responseSchema: CAPABILITY_PROBE_JSON_SCHEMA });
345
413
  if (!raw)
346
414
  return { reachable: false, structuredOutput: false, error: "empty response" };
347
415
  const parsed = parseJsonResponse(raw);
348
416
  return {
349
417
  reachable: true,
350
- structuredOutput: Boolean(parsed && parsed.ok === true),
418
+ structuredOutput: Boolean(parsed &&
419
+ Object.keys(parsed).length === 3 &&
420
+ parsed.ok === true &&
421
+ parsed.ingest === true &&
422
+ parsed.lint === true),
351
423
  };
352
424
  }
353
425
  catch (err) {
@@ -49,7 +49,7 @@ export function deterministicEmbed(text, dim = DETERMINISTIC_EMBED_DIM) {
49
49
  const idx = h % dim;
50
50
  // Use a higher bit for the sign so it is independent of the bucket index.
51
51
  const sign = (h >>> 16) & 1 ? 1 : -1;
52
- vec[idx] += sign;
52
+ vec[idx] = vec[idx] + sign;
53
53
  }
54
54
  let norm = 0;
55
55
  for (const v of vec)
@@ -61,6 +61,6 @@ export function deterministicEmbed(text, dim = DETERMINISTIC_EMBED_DIM) {
61
61
  return vec;
62
62
  }
63
63
  for (let i = 0; i < dim; i++)
64
- vec[i] /= norm;
64
+ vec[i] = vec[i] / norm;
65
65
  return vec;
66
66
  }