akm-cli 0.6.1 → 0.7.0

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 (333) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +712 -34
  3. package/dist/{commands → src/commands}/config-cli.js +47 -4
  4. package/dist/src/commands/distill.js +283 -0
  5. package/dist/src/commands/events.js +108 -0
  6. package/dist/src/commands/history.js +191 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +1 -1
  8. package/dist/src/commands/proposal.js +119 -0
  9. package/dist/src/commands/propose.js +171 -0
  10. package/dist/src/commands/reflect.js +193 -0
  11. package/dist/{commands → src/commands}/registry-search.js +71 -7
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +104 -4
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +73 -0
  16. package/dist/{commands → src/commands}/source-add.js +5 -1
  17. package/dist/{commands → src/commands}/source-manage.js +7 -1
  18. package/dist/{core → src/core}/asset-ref.js +5 -5
  19. package/dist/{core → src/core}/asset-spec.js +12 -0
  20. package/dist/{core → src/core}/common.js +1 -1
  21. package/dist/{core → src/core}/config.js +203 -121
  22. package/dist/{core → src/core}/errors.js +4 -0
  23. package/dist/src/core/events.js +239 -0
  24. package/dist/src/core/lesson-lint.js +86 -0
  25. package/dist/src/core/proposals.js +406 -0
  26. package/dist/src/core/warn.js +72 -0
  27. package/dist/{core → src/core}/write-source.js +80 -5
  28. package/dist/{indexer → src/indexer}/db-search.js +114 -24
  29. package/dist/{indexer → src/indexer}/db.js +76 -23
  30. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  31. package/dist/src/indexer/graph-boost.js +179 -0
  32. package/dist/src/indexer/graph-extraction.js +212 -0
  33. package/dist/{indexer → src/indexer}/indexer.js +88 -7
  34. package/dist/{indexer → src/indexer}/matchers.js +1 -1
  35. package/dist/src/indexer/memory-inference.js +263 -0
  36. package/dist/{indexer → src/indexer}/metadata.js +111 -3
  37. package/dist/{indexer → src/indexer}/search-source.js +4 -2
  38. package/dist/src/integrations/agent/config.js +292 -0
  39. package/dist/src/integrations/agent/detect.js +94 -0
  40. package/dist/src/integrations/agent/index.js +17 -0
  41. package/dist/src/integrations/agent/profiles.js +65 -0
  42. package/dist/src/integrations/agent/prompts.js +167 -0
  43. package/dist/src/integrations/agent/spawn.js +272 -0
  44. package/dist/{integrations → src/integrations}/github.js +9 -3
  45. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  46. package/dist/{llm → src/llm}/client.js +33 -2
  47. package/dist/{llm → src/llm}/embedders/remote.js +37 -3
  48. package/dist/src/llm/feature-gate.js +108 -0
  49. package/dist/src/llm/graph-extract.js +107 -0
  50. package/dist/src/llm/index-passes.js +35 -0
  51. package/dist/src/llm/memory-infer.js +86 -0
  52. package/dist/{output → src/output}/cli-hints.js +15 -2
  53. package/dist/{output → src/output}/renderers.js +63 -2
  54. package/dist/src/output/shapes.js +523 -0
  55. package/dist/src/output/text.js +1116 -0
  56. package/dist/{registry → src/registry}/build-index.js +19 -8
  57. package/dist/{registry → src/registry}/factory.js +0 -8
  58. package/dist/{registry → src/registry}/providers/static-index.js +6 -3
  59. package/dist/{registry → src/registry}/resolve.js +68 -2
  60. package/dist/{setup → src/setup}/setup.js +52 -5
  61. package/dist/{sources → src/sources}/providers/git.js +7 -15
  62. package/dist/{wiki → src/wiki}/wiki.js +54 -6
  63. package/dist/{workflows → src/workflows}/runs.js +37 -3
  64. package/dist/tests/add-website-source.test.js +119 -0
  65. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  66. package/dist/tests/agent/agent-config.test.js +221 -0
  67. package/dist/tests/agent/agent-detect.test.js +100 -0
  68. package/dist/tests/agent/agent-spawn.test.js +234 -0
  69. package/dist/tests/agent-output.test.js +186 -0
  70. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  71. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  72. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  73. package/dist/tests/asset-ref.test.js +192 -0
  74. package/dist/tests/asset-registry.test.js +103 -0
  75. package/dist/tests/asset-spec.test.js +241 -0
  76. package/dist/tests/bench/attribution.test.js +996 -0
  77. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  78. package/dist/tests/bench/cleanup.js +234 -0
  79. package/dist/tests/bench/cleanup.test.js +166 -0
  80. package/dist/tests/bench/cli.js +1018 -0
  81. package/dist/tests/bench/cli.test.js +445 -0
  82. package/dist/tests/bench/compare.test.js +556 -0
  83. package/dist/tests/bench/corpus.js +317 -0
  84. package/dist/tests/bench/corpus.test.js +258 -0
  85. package/dist/tests/bench/doctor.js +525 -0
  86. package/dist/tests/bench/driver.js +401 -0
  87. package/dist/tests/bench/driver.test.js +584 -0
  88. package/dist/tests/bench/environment.js +233 -0
  89. package/dist/tests/bench/environment.test.js +199 -0
  90. package/dist/tests/bench/evolve-metrics.js +179 -0
  91. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  92. package/dist/tests/bench/evolve.js +647 -0
  93. package/dist/tests/bench/evolve.test.js +624 -0
  94. package/dist/tests/bench/failure-modes.test.js +349 -0
  95. package/dist/tests/bench/feedback-integrity.test.js +457 -0
  96. package/dist/tests/bench/leakage.test.js +228 -0
  97. package/dist/tests/bench/learning-curve.test.js +134 -0
  98. package/dist/tests/bench/metrics.js +2395 -0
  99. package/dist/tests/bench/metrics.test.js +1150 -0
  100. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  101. package/dist/tests/bench/opencode-config.js +194 -0
  102. package/dist/tests/bench/opencode-config.test.js +370 -0
  103. package/dist/tests/bench/report.js +1885 -0
  104. package/dist/tests/bench/report.test.js +1038 -0
  105. package/dist/tests/bench/run-config.js +355 -0
  106. package/dist/tests/bench/run-config.test.js +298 -0
  107. package/dist/tests/bench/run-curate-test.js +32 -0
  108. package/dist/tests/bench/run-failing-tasks.js +56 -0
  109. package/dist/tests/bench/run-full-bench.js +51 -0
  110. package/dist/tests/bench/run-items36-targeted.js +69 -0
  111. package/dist/tests/bench/run-nano-quick.js +42 -0
  112. package/dist/tests/bench/run-waveg-targeted.js +62 -0
  113. package/dist/tests/bench/runner.js +699 -0
  114. package/dist/tests/bench/runner.test.js +958 -0
  115. package/dist/tests/bench/search-bridge.test.js +331 -0
  116. package/dist/tests/bench/tmp.js +131 -0
  117. package/dist/tests/bench/trajectory.js +116 -0
  118. package/dist/tests/bench/trajectory.test.js +127 -0
  119. package/dist/tests/bench/verifier.js +114 -0
  120. package/dist/tests/bench/verifier.test.js +118 -0
  121. package/dist/tests/bench/workflow-evaluator.js +557 -0
  122. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  123. package/dist/tests/bench/workflow-spec.js +345 -0
  124. package/dist/tests/bench/workflow-spec.test.js +363 -0
  125. package/dist/tests/bench/workflow-trace.js +472 -0
  126. package/dist/tests/bench/workflow-trace.test.js +254 -0
  127. package/dist/tests/benchmark-search-quality.js +536 -0
  128. package/dist/tests/benchmark-suite.js +1441 -0
  129. package/dist/tests/capture-cli.test.js +112 -0
  130. package/dist/tests/cli-errors.test.js +204 -0
  131. package/dist/tests/commands/events.test.js +370 -0
  132. package/dist/tests/commands/history.test.js +418 -0
  133. package/dist/tests/commands/import.test.js +103 -0
  134. package/dist/tests/commands/proposal-cli.test.js +209 -0
  135. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  136. package/dist/tests/commands/remember.test.js +97 -0
  137. package/dist/tests/commands/scope-flags.test.js +300 -0
  138. package/dist/tests/commands/search.test.js +537 -0
  139. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  140. package/dist/tests/commands/show.test.js +294 -0
  141. package/dist/tests/common.test.js +266 -0
  142. package/dist/tests/completions.test.js +142 -0
  143. package/dist/tests/config-cli.test.js +193 -0
  144. package/dist/tests/config-llm-features.test.js +139 -0
  145. package/dist/tests/config.test.js +569 -0
  146. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  147. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  148. package/dist/tests/contracts/spec-helpers.js +46 -0
  149. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  150. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  151. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  152. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  153. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  154. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  155. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  156. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  157. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  158. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  159. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  160. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  161. package/dist/tests/core/write-source.test.js +366 -0
  162. package/dist/tests/curate-command.test.js +87 -0
  163. package/dist/tests/db-scoring.test.js +201 -0
  164. package/dist/tests/db.test.js +654 -0
  165. package/dist/tests/distill-cli-flag.test.js +208 -0
  166. package/dist/tests/distill.test.js +515 -0
  167. package/dist/tests/docker-install.test.js +120 -0
  168. package/dist/tests/e2e.test.js +1419 -0
  169. package/dist/tests/embedder.test.js +340 -0
  170. package/dist/tests/embedding-model-config.test.js +379 -0
  171. package/dist/tests/feedback-command.test.js +172 -0
  172. package/dist/tests/file-context.test.js +552 -0
  173. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  174. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  175. package/dist/tests/fixtures/stashes/load.js +166 -0
  176. package/dist/tests/fixtures/stashes/load.test.js +97 -0
  177. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  178. package/dist/tests/frontmatter.test.js +190 -0
  179. package/dist/tests/fts-field-weighting.test.js +254 -0
  180. package/dist/tests/fuzzy-search.test.js +230 -0
  181. package/dist/tests/git-provider-clone.test.js +45 -0
  182. package/dist/tests/github.test.js +161 -0
  183. package/dist/tests/graph-boost-ranking.test.js +305 -0
  184. package/dist/tests/graph-extraction.test.js +282 -0
  185. package/dist/tests/helpers/usage-events.js +8 -0
  186. package/dist/tests/index-pass-llm.test.js +161 -0
  187. package/dist/tests/indexer.test.js +570 -0
  188. package/dist/tests/info-command.test.js +166 -0
  189. package/dist/tests/init.test.js +69 -0
  190. package/dist/tests/install-script.test.js +246 -0
  191. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  192. package/dist/tests/issue-36-repro.test.js +304 -0
  193. package/dist/tests/issues-191-194.test.js +160 -0
  194. package/dist/tests/lesson-lint.test.js +111 -0
  195. package/dist/tests/llm-client.test.js +115 -0
  196. package/dist/tests/llm-feature-gate.test.js +151 -0
  197. package/dist/tests/llm.test.js +139 -0
  198. package/dist/tests/lockfile.test.js +216 -0
  199. package/dist/tests/manifest.test.js +205 -0
  200. package/dist/tests/markdown.test.js +126 -0
  201. package/dist/tests/matchers-unit.test.js +189 -0
  202. package/dist/tests/memory-inference.test.js +299 -0
  203. package/dist/tests/merge-scoring.test.js +136 -0
  204. package/dist/tests/metadata.test.js +313 -0
  205. package/dist/tests/migration-help.test.js +89 -0
  206. package/dist/tests/origin-resolve.test.js +124 -0
  207. package/dist/tests/output-baseline.test.js +218 -0
  208. package/dist/tests/output-shapes-unit.test.js +478 -0
  209. package/dist/tests/parallel-search.test.js +272 -0
  210. package/dist/tests/parameter-metadata.test.js +365 -0
  211. package/dist/tests/paths.test.js +177 -0
  212. package/dist/tests/progressive-disclosure.test.js +280 -0
  213. package/dist/tests/proposals.test.js +279 -0
  214. package/dist/tests/proposed-quality.test.js +271 -0
  215. package/dist/tests/provider-registry.test.js +32 -0
  216. package/dist/tests/ranking-regression.test.js +548 -0
  217. package/dist/tests/reflect-propose.test.js +455 -0
  218. package/dist/tests/registry-build-index.test.js +394 -0
  219. package/dist/tests/registry-cli.test.js +290 -0
  220. package/dist/tests/registry-index-v2.test.js +430 -0
  221. package/dist/tests/registry-install.test.js +728 -0
  222. package/dist/tests/registry-providers/parity.test.js +189 -0
  223. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  224. package/dist/tests/registry-providers/static-index.test.js +238 -0
  225. package/dist/tests/registry-resolve.test.js +126 -0
  226. package/dist/tests/registry-search.test.js +923 -0
  227. package/dist/tests/remember-frontmatter.test.js +378 -0
  228. package/dist/tests/remember-unit.test.js +123 -0
  229. package/dist/tests/ripgrep-install.test.js +251 -0
  230. package/dist/tests/ripgrep-resolve.test.js +108 -0
  231. package/dist/tests/ripgrep.test.js +163 -0
  232. package/dist/tests/save-command.test.js +94 -0
  233. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  234. package/dist/tests/scoring-pipeline.test.js +648 -0
  235. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  236. package/dist/tests/self-update.test.js +442 -0
  237. package/dist/tests/semantic-search-e2e.test.js +512 -0
  238. package/dist/tests/semantic-status.test.js +471 -0
  239. package/dist/tests/setup-run.integration.js +877 -0
  240. package/dist/tests/setup-wizard.test.js +198 -0
  241. package/dist/tests/setup.test.js +131 -0
  242. package/dist/tests/source-add.test.js +11 -0
  243. package/dist/tests/source-clone.test.js +254 -0
  244. package/dist/tests/source-manage.test.js +366 -0
  245. package/dist/tests/source-providers/filesystem.test.js +82 -0
  246. package/dist/tests/source-providers/git.test.js +252 -0
  247. package/dist/tests/source-providers/website.test.js +128 -0
  248. package/dist/tests/source-qa-fixes.test.js +286 -0
  249. package/dist/tests/source-registry.test.js +350 -0
  250. package/dist/tests/source-resolve.test.js +100 -0
  251. package/dist/tests/source-source.test.js +281 -0
  252. package/dist/tests/source.test.js +533 -0
  253. package/dist/tests/tar-utils-scan.test.js +73 -0
  254. package/dist/tests/toggle-components.test.js +73 -0
  255. package/dist/tests/usage-telemetry.test.js +265 -0
  256. package/dist/tests/utility-scoring.test.js +558 -0
  257. package/dist/tests/vault-load-error.test.js +78 -0
  258. package/dist/tests/vault-qa-fixes.test.js +194 -0
  259. package/dist/tests/vault.test.js +429 -0
  260. package/dist/tests/vector-search.test.js +608 -0
  261. package/dist/tests/walker.test.js +252 -0
  262. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  263. package/dist/tests/wave2-cluster-d.test.js +180 -0
  264. package/dist/tests/wave2-cluster-e.test.js +179 -0
  265. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  266. package/dist/tests/wiki.test.js +529 -0
  267. package/dist/tests/workflow-cli.test.js +271 -0
  268. package/dist/tests/workflow-markdown.test.js +171 -0
  269. package/dist/tests/workflow-path-escape.test.js +132 -0
  270. package/dist/tests/workflow-qa-fixes.test.js +395 -0
  271. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  272. package/docs/README.md +8 -0
  273. package/docs/migration/release-notes/0.7.0.md +244 -0
  274. package/package.json +2 -2
  275. package/dist/core/warn.js +0 -27
  276. package/dist/output/shapes.js +0 -212
  277. package/dist/output/text.js +0 -520
  278. /package/dist/{commands → src/commands}/completions.js +0 -0
  279. /package/dist/{commands → src/commands}/curate.js +0 -0
  280. /package/dist/{commands → src/commands}/info.js +0 -0
  281. /package/dist/{commands → src/commands}/init.js +0 -0
  282. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  283. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  284. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  285. /package/dist/{commands → src/commands}/vault.js +0 -0
  286. /package/dist/{core → src/core}/asset-registry.js +0 -0
  287. /package/dist/{core → src/core}/frontmatter.js +0 -0
  288. /package/dist/{core → src/core}/markdown.js +0 -0
  289. /package/dist/{core → src/core}/paths.js +0 -0
  290. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  291. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  292. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  293. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  294. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  295. /package/dist/{llm → src/llm}/embedder.js +0 -0
  296. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  297. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  298. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  299. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  300. /package/dist/{output → src/output}/context.js +0 -0
  301. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  302. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  303. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  304. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  305. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  306. /package/dist/{registry → src/registry}/types.js +0 -0
  307. /package/dist/{setup → src/setup}/detect.js +0 -0
  308. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  309. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  310. /package/dist/{setup → src/setup}/steps.js +0 -0
  311. /package/dist/{sources → src/sources}/include.js +0 -0
  312. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  313. /package/dist/{sources → src/sources}/provider.js +0 -0
  314. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  315. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  316. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  317. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  318. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  319. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  320. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  321. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  322. /package/dist/{sources → src/sources}/resolve.js +0 -0
  323. /package/dist/{sources → src/sources}/types.js +0 -0
  324. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  325. /package/dist/{version.js → src/version.js} +0 -0
  326. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  327. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  328. /package/dist/{workflows → src/workflows}/db.js +0 -0
  329. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  330. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  331. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  332. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  333. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Per-feature LLM gates (v1 spec §14).
3
+ *
4
+ * Every bounded in-tree LLM call site in akm is addressed by exactly one
5
+ * feature key under `llm.features.*`. This module is the single seam call
6
+ * sites use to ask "should I run?" and "if I run and fail, what do I return?"
7
+ *
8
+ * The seam is intentionally tiny:
9
+ *
10
+ * - `isLlmFeatureEnabled(config, feature)` — pure predicate, no side
11
+ * effects, no I/O. Returns `true` only when the feature flag is the
12
+ * literal boolean `true` in config. Defaults are `false` per v1
13
+ * spec §14 — adding a flag to the schema is a non-event until the user
14
+ * opts in.
15
+ * - `tryLlmFeature(feature, config, fn, fallback, opts?)` — single-call
16
+ * wrapper that runs `fn()` only when the gate is open, enforces a hard
17
+ * timeout (default 30s — overridable per call), and returns `fallback`
18
+ * on disablement, throw, or timeout. The wrapper is referentially
19
+ * transparent for any given (gate-state, fn-result) pair: no module
20
+ * state is mutated.
21
+ *
22
+ * Statelessness invariant (v1 spec §14.4): nothing in this module holds
23
+ * state across calls. There are no caches, no module-level singletons, no
24
+ * persistent connections. The architecture seam test
25
+ * (`tests/architecture/llm-stateless-seam.test.ts`) does not currently
26
+ * inspect this file but the same rule applies — keep all exports as pure
27
+ * functions.
28
+ */
29
+ /**
30
+ * Pure predicate: is the named feature gate explicitly enabled in `config`?
31
+ *
32
+ * Returns `false` when:
33
+ * - the LLM block is missing,
34
+ * - the `features` block is missing,
35
+ * - the key is absent (defaults are `false`),
36
+ * - the key is set to `false`.
37
+ */
38
+ export function isLlmFeatureEnabled(config, feature) {
39
+ if (!config?.llm?.features)
40
+ return false;
41
+ return config.llm.features[feature] === true;
42
+ }
43
+ const DEFAULT_TIMEOUT_MS = 30_000;
44
+ /**
45
+ * Run `fn()` only if `isLlmFeatureEnabled(config, feature)` is `true`. On
46
+ * disablement, throw, or timeout, return `fallback` (or — if it is a
47
+ * thunk — the value produced by calling it).
48
+ *
49
+ * The fallback may be a value or a synchronous/async function returning a
50
+ * value. The thunk form lets call sites encode "run the deterministic
51
+ * pipeline" without paying for it in the success path:
52
+ *
53
+ * ```ts
54
+ * const ranked = await tryLlmFeature(
55
+ * "curate_rerank",
56
+ * config,
57
+ * () => llmRerank(candidates),
58
+ * () => deterministicRerank(candidates),
59
+ * );
60
+ * ```
61
+ */
62
+ export async function tryLlmFeature(feature, config, fn, fallback, opts) {
63
+ const resolveFallback = async () => typeof fallback === "function" ? await fallback() : fallback;
64
+ if (!isLlmFeatureEnabled(config, feature)) {
65
+ opts?.onFallback?.({ feature, reason: "disabled" });
66
+ return resolveFallback();
67
+ }
68
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
69
+ try {
70
+ if (timeoutMs <= 0) {
71
+ return await fn();
72
+ }
73
+ return await runWithTimeout(fn, timeoutMs, feature);
74
+ }
75
+ catch (err) {
76
+ const error = err instanceof Error ? err : new Error(String(err));
77
+ const reason = error instanceof LlmFeatureTimeoutError ? "timeout" : "error";
78
+ opts?.onFallback?.({ feature, reason, error });
79
+ return resolveFallback();
80
+ }
81
+ }
82
+ /** Specific error class so call sites and the wrapper can tell timeouts apart from generic throws. */
83
+ export class LlmFeatureTimeoutError extends Error {
84
+ feature;
85
+ timeoutMs;
86
+ constructor(feature, timeoutMs) {
87
+ super(`LLM feature "${feature}" timed out after ${timeoutMs}ms.`);
88
+ this.name = "LlmFeatureTimeoutError";
89
+ this.feature = feature;
90
+ this.timeoutMs = timeoutMs;
91
+ Object.setPrototypeOf(this, new.target.prototype);
92
+ }
93
+ }
94
+ async function runWithTimeout(fn, timeoutMs, feature) {
95
+ let timer;
96
+ try {
97
+ return await new Promise((resolve, reject) => {
98
+ timer = setTimeout(() => reject(new LlmFeatureTimeoutError(feature, timeoutMs)), timeoutMs);
99
+ Promise.resolve()
100
+ .then(() => fn())
101
+ .then(resolve, reject);
102
+ });
103
+ }
104
+ finally {
105
+ if (timer)
106
+ clearTimeout(timer);
107
+ }
108
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * LLM helper for the `akm index` graph-extraction pass (#207).
3
+ *
4
+ * Given a single asset body (typically a `memory:` or `knowledge:` file),
5
+ * asks the configured LLM to surface the entities mentioned in it and the
6
+ * relations between them. The pass itself
7
+ * (`src/indexer/graph-extraction.ts`) is responsible for deciding which
8
+ * files to extract, persisting the resulting nodes/edges to a stash-local
9
+ * `graph.json` artifact, and feeding the artifact into the FTS5+boosts
10
+ * search pipeline as a single boost component.
11
+ *
12
+ * This module is intentionally tiny and stateless so tests can stub it via
13
+ * `mock.module("../src/llm/graph-extract", ...)` without hitting a network.
14
+ *
15
+ * Locked v1 contract (#208): the LLM connection always comes from the
16
+ * shared `akm.llm` block — never from a per-pass override. Callers obtain
17
+ * the connection via `resolveIndexPassLLM("graph", config)` and pass it
18
+ * straight through.
19
+ */
20
+ import { toErrorMessage } from "../core/common";
21
+ import { warn } from "../core/warn";
22
+ import { chatCompletion, parseJsonResponse } from "./client";
23
+ /** Hard cap on body chars sent to the model. */
24
+ const MAX_BODY_CHARS = 4000;
25
+ /** Hard cap on entities returned per asset — guards against runaway LLM output. */
26
+ const MAX_ENTITIES_PER_ASSET = 32;
27
+ /** Hard cap on relations returned per asset. */
28
+ const MAX_RELATIONS_PER_ASSET = 32;
29
+ /** Hard timeout for the LLM call; an `akm index` run must not hang on a misbehaving endpoint. */
30
+ const LLM_TIMEOUT_MS = 30_000;
31
+ const SYSTEM_PROMPT = "You extract a knowledge graph from developer notes. Return only valid JSON. " + "No prose, no markdown fences.";
32
+ const USER_PROMPT_PREFIX = `Extract entities and relations from the asset body below.
33
+
34
+ Rules:
35
+ - Output ONLY a JSON object: {"entities": ["Entity One", ...], "relations": [{"from": "A", "to": "B", "type": "uses"}, ...]}.
36
+ - Entities are short, canonical noun phrases (project names, services, tools, people, file/dir names, technical concepts).
37
+ - Relations connect two entities that both appear in the entities array.
38
+ - "type" is a short verb phrase (e.g. "uses", "depends on", "owns", "documents"). Optional; omit when unsure.
39
+ - Drop pleasantries, meta-commentary, and timestamps.
40
+ - Limit to at most ${MAX_ENTITIES_PER_ASSET} entities and ${MAX_RELATIONS_PER_ASSET} relations per asset.
41
+ - Return {"entities": [], "relations": []} if the body has no extractable graph content.
42
+
43
+ Asset body:
44
+ `;
45
+ /**
46
+ * Extract entities and relations from a single asset body via the configured LLM.
47
+ *
48
+ * Returns `{entities: [], relations: []}` on any failure (timeout, invalid
49
+ * JSON, empty response). Errors are logged via `warn()` but never thrown — a
50
+ * failed extraction for one asset must not abort the rest of the index pass.
51
+ */
52
+ export async function extractGraphFromBody(llmConfig, body) {
53
+ const empty = { entities: [], relations: [] };
54
+ const trimmedBody = body.trim();
55
+ if (!trimmedBody)
56
+ return empty;
57
+ const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody.slice(0, MAX_BODY_CHARS)}`;
58
+ let timeoutHandle;
59
+ try {
60
+ const raw = await Promise.race([
61
+ chatCompletion(llmConfig, [
62
+ { role: "system", content: SYSTEM_PROMPT },
63
+ { role: "user", content: userPrompt },
64
+ ], { maxTokens: 1024, temperature: 0.1 }),
65
+ new Promise((_, reject) => {
66
+ timeoutHandle = setTimeout(() => reject(new Error("graph extraction timed out")), LLM_TIMEOUT_MS);
67
+ }),
68
+ ]);
69
+ if (!raw)
70
+ return empty;
71
+ const parsed = parseJsonResponse(raw);
72
+ if (!parsed) {
73
+ warn("graph extraction: invalid JSON response from LLM; skipping asset.");
74
+ return empty;
75
+ }
76
+ const entities = Array.isArray(parsed.entities)
77
+ ? parsed.entities
78
+ .filter((e) => typeof e === "string")
79
+ .map((e) => e.trim())
80
+ .filter((e) => e.length > 0)
81
+ .slice(0, MAX_ENTITIES_PER_ASSET)
82
+ : [];
83
+ const entitySet = new Set(entities);
84
+ const relations = Array.isArray(parsed.relations)
85
+ ? parsed.relations
86
+ .filter((r) => typeof r === "object" && r !== null && !Array.isArray(r))
87
+ .map((r) => ({
88
+ from: typeof r.from === "string" ? r.from.trim() : "",
89
+ to: typeof r.to === "string" ? r.to.trim() : "",
90
+ type: typeof r.type === "string" && r.type.trim() ? r.type.trim() : undefined,
91
+ }))
92
+ // Both endpoints must be non-empty AND mentioned in entities[];
93
+ // dangling relations are noise and inflate the boost component.
94
+ .filter((r) => r.from && r.to && entitySet.has(r.from) && entitySet.has(r.to))
95
+ .slice(0, MAX_RELATIONS_PER_ASSET)
96
+ : [];
97
+ return { entities, relations };
98
+ }
99
+ catch (err) {
100
+ warn(`graph extraction failed: ${toErrorMessage(err)}`);
101
+ return empty;
102
+ }
103
+ finally {
104
+ if (timeoutHandle !== undefined)
105
+ clearTimeout(timeoutHandle);
106
+ }
107
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Per-pass LLM config resolution for `akm index`.
3
+ *
4
+ * Locked v1 contract (#208):
5
+ * - There is exactly one provider/model configuration: `akm.llm`.
6
+ * - Every LLM-using pass inside `akm index` defaults to that block.
7
+ * - A pass can be opted out individually with `index.<passName>.llm = false`.
8
+ * - Any attempt to supply provider/model fields under `index.<passName>` is
9
+ * rejected at config-load time by `parseIndexConfig` in
10
+ * {@link ../core/config.ts} (`ConfigError("INVALID_CONFIG_FILE")`).
11
+ *
12
+ * Passes plug in by calling {@link resolveIndexPassLLM} with their pass
13
+ * name (e.g. `"memory"` for #201's memory-inference pass, `"graph"` for
14
+ * #207's graph-extraction pass). They do not read `config.llm` directly.
15
+ * This keeps the config surface small and the wiring uniform.
16
+ */
17
+ /**
18
+ * Resolve the {@link LlmConnectionConfig} a single index pass should use, or
19
+ * `undefined` when the pass should run without an LLM.
20
+ *
21
+ * Returns `undefined` if any of:
22
+ * - No top-level `akm.llm` block is configured.
23
+ * - The pass is explicitly opted out (`index.<passName>.llm === false`).
24
+ *
25
+ * Otherwise returns the shared `akm.llm` config. There is no per-pass
26
+ * provider override; that decision is locked by §9 of the v1 spec.
27
+ */
28
+ export function resolveIndexPassLLM(passName, config) {
29
+ if (!config.llm)
30
+ return undefined;
31
+ const passConfig = config.index?.[passName];
32
+ if (passConfig?.llm === false)
33
+ return undefined;
34
+ return config.llm;
35
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * LLM helper for the `akm index` memory-inference pass (#201).
3
+ *
4
+ * Splits a single memory body into a list of atomic facts. The pass itself
5
+ * (in `src/indexer/memory-inference.ts`) is responsible for deciding which
6
+ * memories are pending, persisting the resulting atomic memories with the
7
+ * correct frontmatter (`inferred: true`, `source: <parent-ref>`), and
8
+ * marking the parent as processed for idempotency.
9
+ *
10
+ * This module is intentionally tiny and stateless so tests can stub it via
11
+ * `mock.module("../src/llm/memory-infer", ...)` without hitting a network.
12
+ *
13
+ * Locked v1 contract (#208): the LLM connection always comes from the
14
+ * shared `akm.llm` block — never from a per-pass override. Callers obtain
15
+ * the connection via `resolveIndexPassLLM("memory", config)` and pass it
16
+ * straight through.
17
+ */
18
+ import { toErrorMessage } from "../core/common";
19
+ import { warn } from "../core/warn";
20
+ import { chatCompletion, parseJsonResponse } from "./client";
21
+ /** Hard cap on body chars sent to the model — pragmatic and matches `runLlmEnrich`. */
22
+ const MAX_BODY_CHARS = 4000;
23
+ /** Hard cap on the number of atomic facts returned per memory. */
24
+ const MAX_FACTS_PER_MEMORY = 16;
25
+ /** Hard timeout for the LLM call. The index run must not hang on a misbehaving endpoint. */
26
+ const LLM_TIMEOUT_MS = 30_000;
27
+ const SYSTEM_PROMPT = "You split a developer memory into atomic, self-contained facts. " +
28
+ "Return only valid JSON. No prose, no markdown fences.";
29
+ const USER_PROMPT_PREFIX = `Split the memory below into a JSON array of short, self-contained atomic facts.
30
+
31
+ Rules:
32
+ - Output ONLY a JSON object: {"facts": ["fact one", "fact two", ...]}.
33
+ - Each fact is a single complete sentence, decontextualized so it stands alone.
34
+ - Drop pleasantries, meta-commentary, and timestamps.
35
+ - Preserve technical specifics (names, versions, identifiers) verbatim.
36
+ - If the memory is already a single atomic fact, return it as the only entry.
37
+ - Limit to at most ${MAX_FACTS_PER_MEMORY} facts.
38
+
39
+ Memory:
40
+ `;
41
+ /**
42
+ * Split a single memory body into atomic facts via the configured LLM.
43
+ *
44
+ * Returns `[]` on any failure (timeout, invalid JSON, empty response). Errors
45
+ * are logged via `warn()` but never thrown — a failed split for one memory
46
+ * must not abort the rest of the index pass.
47
+ */
48
+ export async function splitMemoryIntoAtomicFacts(llmConfig, body) {
49
+ const trimmedBody = body.trim();
50
+ if (!trimmedBody)
51
+ return [];
52
+ const userPrompt = `${USER_PROMPT_PREFIX}${trimmedBody.slice(0, MAX_BODY_CHARS)}`;
53
+ let timeoutHandle;
54
+ try {
55
+ const raw = await Promise.race([
56
+ chatCompletion(llmConfig, [
57
+ { role: "system", content: SYSTEM_PROMPT },
58
+ { role: "user", content: userPrompt },
59
+ ], { maxTokens: 768, temperature: 0.1 }),
60
+ new Promise((_, reject) => {
61
+ timeoutHandle = setTimeout(() => reject(new Error("memory inference timed out")), LLM_TIMEOUT_MS);
62
+ }),
63
+ ]);
64
+ if (!raw)
65
+ return [];
66
+ const parsed = parseJsonResponse(raw);
67
+ if (!parsed || !Array.isArray(parsed.facts)) {
68
+ warn("memory inference: invalid JSON response from LLM; skipping memory.");
69
+ return [];
70
+ }
71
+ const facts = parsed.facts
72
+ .filter((f) => typeof f === "string")
73
+ .map((f) => f.trim())
74
+ .filter((f) => f.length > 0)
75
+ .slice(0, MAX_FACTS_PER_MEMORY);
76
+ return facts;
77
+ }
78
+ catch (err) {
79
+ warn(`memory inference failed: ${toErrorMessage(err)}`);
80
+ return [];
81
+ }
82
+ finally {
83
+ if (timeoutHandle !== undefined)
84
+ clearTimeout(timeoutHandle);
85
+ }
86
+ }
@@ -11,6 +11,19 @@ const EMBEDDED_HINTS = `# akm CLI
11
11
 
12
12
  You have access to a searchable library of scripts, skills, commands, agents, knowledge documents, workflows, wikis, and memories via \`akm\`. Search your sources first before writing something from scratch.
13
13
 
14
+ ## Agent Task Loop
15
+
16
+ For any task, follow this loop:
17
+ 1. \`akm curate "<task>"\` — find the best matching asset
18
+ 2. \`akm show <ref>\` — read the schema (field names and structure)
19
+ 3. Edit the workspace file using schema field names + task-specific values from your README
20
+ 4. \`akm feedback <ref> --positive\` — record success
21
+
22
+ For workflow tasks:
23
+ 1. \`akm workflow next workflow:<name>\` — get current step instructions
24
+ 2. Do the step work in your workspace
25
+ 3. \`akm workflow complete <run-id> --step <step-id>\` — mark done, get next step
26
+
14
27
  ## Quick Reference
15
28
 
16
29
  \`\`\`sh
@@ -144,7 +157,7 @@ akm wiki list # List wikis (name, pages, raws,
144
157
  akm wiki create research # Scaffold a new wiki
145
158
  akm wiki register ics-docs ~/code/ics-documentation # Register an external wiki
146
159
  akm wiki show research # Path, description, counts, last 3 log entries
147
- akm wiki pages research # Page refs + descriptions (excludes schema/index/log/raw)
160
+ akm wiki pages research # Page refs + descriptions (excludes schema/index/log; includes raw/)
148
161
  akm wiki search research "attention" # Scoped search (equivalent to --type wiki --wiki research)
149
162
  akm wiki stash research ./paper.md # Copy source into raw/<slug>.md (never overwrites)
150
163
  echo "..." | akm wiki stash research - # stdin form
@@ -254,7 +267,7 @@ akm registry add <url> --provider skills-sh # Specify provider type
254
267
  akm registry remove <url-or-name> # Remove a registry
255
268
  akm registry search "<query>" # Search all registries
256
269
  akm registry search "<query>" --assets # Include asset-level results
257
- akm registry build-index # Build ./index.json
270
+ akm registry build-index # Build the default cache-backed index.json
258
271
  akm registry build-index --out dist/index.json # Build to a custom path
259
272
  \`\`\`
260
273
 
@@ -187,12 +187,14 @@ const skillMdRenderer = {
187
187
  name: "skill-md",
188
188
  buildShowResponse(ctx) {
189
189
  const name = deriveName(ctx);
190
+ const parsed = parseFrontmatter(ctx.content());
190
191
  return {
191
192
  type: "skill",
192
193
  name,
193
194
  path: ctx.absPath,
194
195
  action: "Read and follow the instructions below",
195
- content: ctx.content(),
196
+ description: toStringOrUndefined(parsed.data.description),
197
+ content: parsed.content,
196
198
  };
197
199
  },
198
200
  };
@@ -391,6 +393,64 @@ const wikiMdRenderer = {
391
393
  }
392
394
  },
393
395
  };
396
+ // ── 4c. lesson-md ────────────────────────────────────────────────────────────
397
+ /**
398
+ * Renderer for the `lesson` asset type (v1 spec §13).
399
+ *
400
+ * Lessons are markdown files with required `description` and `when_to_use`
401
+ * frontmatter. The renderer projects both fields explicitly so consumers can
402
+ * decide whether to apply a lesson without reading the full body. Lint
403
+ * (see `src/core/lesson-lint.ts`) is the contract enforcer; the renderer is
404
+ * intentionally tolerant — a lesson missing required fields will still render
405
+ * its body so the user has something to work with while they fix the file.
406
+ */
407
+ const lessonMdRenderer = {
408
+ name: "lesson-md",
409
+ buildShowResponse(ctx) {
410
+ const name = deriveName(ctx);
411
+ const parsed = parseFrontmatter(ctx.content());
412
+ const description = toStringOrUndefined(parsed.data.description);
413
+ const whenToUse = toStringOrUndefined(parsed.data.when_to_use);
414
+ const action = whenToUse
415
+ ? `Apply this lesson when: ${whenToUse}`
416
+ : "Apply this lesson when its `when_to_use` trigger matches the current task.";
417
+ return {
418
+ type: "lesson",
419
+ name,
420
+ path: ctx.absPath,
421
+ action,
422
+ description,
423
+ content: parsed.content,
424
+ };
425
+ },
426
+ extractMetadata(entry, ctx) {
427
+ try {
428
+ const parsed = parseFrontmatter(ctx.content());
429
+ const fm = parsed.data;
430
+ const desc = toStringOrUndefined(fm.description);
431
+ if (desc && !entry.description) {
432
+ entry.description = desc;
433
+ entry.source = "frontmatter";
434
+ entry.confidence = 0.9;
435
+ }
436
+ const whenToUse = toStringOrUndefined(fm.when_to_use);
437
+ if (whenToUse) {
438
+ const hints = new Set(entry.searchHints ?? []);
439
+ hints.add(`when_to_use:${whenToUse}`);
440
+ entry.searchHints = Array.from(hints).filter(Boolean);
441
+ }
442
+ if (Array.isArray(fm.tags) && fm.tags.length > 0) {
443
+ const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
444
+ if (fmTags.length > 0) {
445
+ entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
446
+ }
447
+ }
448
+ }
449
+ catch {
450
+ // Non-fatal: skip metadata extraction on parse error
451
+ }
452
+ },
453
+ };
394
454
  // ── 5. memory-md ─────────────────────────────────────────────────────────────
395
455
  const memoryMdRenderer = {
396
456
  name: "memory-md",
@@ -560,6 +620,7 @@ const builtinRenderers = [
560
620
  agentMdRenderer,
561
621
  knowledgeMdRenderer,
562
622
  wikiMdRenderer,
623
+ lessonMdRenderer,
563
624
  memoryMdRenderer,
564
625
  workflowMdRenderer,
565
626
  scriptSourceRenderer,
@@ -575,4 +636,4 @@ export function registerBuiltinRenderers() {
575
636
  }
576
637
  }
577
638
  // ── Named exports for testing ────────────────────────────────────────────────
578
- export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
639
+ export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, lessonMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };