akm-cli 0.6.0 → 0.7.0-rc1

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 (319) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +672 -29
  3. package/dist/{commands → src/commands}/config-cli.js +5 -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 +120 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +28 -2
  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 +2 -1
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +74 -1
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +67 -2
  16. package/dist/{core → src/core}/asset-ref.js +5 -5
  17. package/dist/{core → src/core}/asset-spec.js +12 -0
  18. package/dist/{core → src/core}/common.js +1 -1
  19. package/dist/{core → src/core}/config.js +175 -121
  20. package/dist/{core → src/core}/errors.js +4 -0
  21. package/dist/src/core/events.js +239 -0
  22. package/dist/src/core/lesson-lint.js +86 -0
  23. package/dist/src/core/proposals.js +406 -0
  24. package/dist/src/core/warn.js +72 -0
  25. package/dist/{core → src/core}/write-source.js +80 -5
  26. package/dist/{indexer → src/indexer}/db-search.js +119 -27
  27. package/dist/{indexer → src/indexer}/db.js +76 -23
  28. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  29. package/dist/src/indexer/graph-boost.js +179 -0
  30. package/dist/src/indexer/graph-extraction.js +212 -0
  31. package/dist/{indexer → src/indexer}/indexer.js +73 -6
  32. package/dist/src/indexer/memory-inference.js +263 -0
  33. package/dist/{indexer → src/indexer}/metadata.js +114 -11
  34. package/dist/src/integrations/agent/config.js +292 -0
  35. package/dist/src/integrations/agent/detect.js +94 -0
  36. package/dist/src/integrations/agent/index.js +17 -0
  37. package/dist/src/integrations/agent/profiles.js +65 -0
  38. package/dist/src/integrations/agent/prompts.js +167 -0
  39. package/dist/src/integrations/agent/spawn.js +221 -0
  40. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  41. package/dist/{llm → src/llm}/client.js +33 -2
  42. package/dist/src/llm/feature-gate.js +108 -0
  43. package/dist/src/llm/graph-extract.js +107 -0
  44. package/dist/src/llm/index-passes.js +35 -0
  45. package/dist/src/llm/memory-infer.js +86 -0
  46. package/dist/{output → src/output}/renderers.js +60 -1
  47. package/dist/src/output/shapes.js +516 -0
  48. package/dist/{output → src/output}/text.js +447 -4
  49. package/dist/{registry → src/registry}/build-index.js +14 -4
  50. package/dist/{registry → src/registry}/factory.js +0 -8
  51. package/dist/{registry → src/registry}/providers/static-index.js +3 -2
  52. package/dist/{registry → src/registry}/resolve.js +68 -2
  53. package/dist/{setup → src/setup}/setup.js +43 -5
  54. package/dist/{sources → src/sources}/providers/git.js +7 -15
  55. package/dist/{wiki → src/wiki}/wiki.js +9 -11
  56. package/dist/tests/add-website-source.test.js +119 -0
  57. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  58. package/dist/tests/agent/agent-config.test.js +221 -0
  59. package/dist/tests/agent/agent-detect.test.js +100 -0
  60. package/dist/tests/agent/agent-spawn.test.js +234 -0
  61. package/dist/tests/agent-output.test.js +186 -0
  62. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  63. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  64. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  65. package/dist/tests/asset-ref.test.js +192 -0
  66. package/dist/tests/asset-registry.test.js +103 -0
  67. package/dist/tests/asset-spec.test.js +241 -0
  68. package/dist/tests/bench/attribution.test.js +995 -0
  69. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  70. package/dist/tests/bench/cleanup.js +203 -0
  71. package/dist/tests/bench/cleanup.test.js +166 -0
  72. package/dist/tests/bench/cli.js +683 -0
  73. package/dist/tests/bench/cli.test.js +177 -0
  74. package/dist/tests/bench/compare.test.js +556 -0
  75. package/dist/tests/bench/corpus.js +314 -0
  76. package/dist/tests/bench/corpus.test.js +258 -0
  77. package/dist/tests/bench/driver.js +346 -0
  78. package/dist/tests/bench/driver.test.js +443 -0
  79. package/dist/tests/bench/evolve-metrics.js +179 -0
  80. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  81. package/dist/tests/bench/evolve.js +580 -0
  82. package/dist/tests/bench/evolve.test.js +616 -0
  83. package/dist/tests/bench/failure-modes.test.js +300 -0
  84. package/dist/tests/bench/feedback-integrity.test.js +456 -0
  85. package/dist/tests/bench/leakage.test.js +125 -0
  86. package/dist/tests/bench/learning-curve.test.js +133 -0
  87. package/dist/tests/bench/metrics.js +2319 -0
  88. package/dist/tests/bench/metrics.test.js +1144 -0
  89. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  90. package/dist/tests/bench/report.js +1821 -0
  91. package/dist/tests/bench/report.test.js +989 -0
  92. package/dist/tests/bench/runner.js +536 -0
  93. package/dist/tests/bench/runner.test.js +958 -0
  94. package/dist/tests/bench/search-bridge.test.js +331 -0
  95. package/dist/tests/bench/tmp.js +41 -0
  96. package/dist/tests/bench/trajectory.js +116 -0
  97. package/dist/tests/bench/trajectory.test.js +127 -0
  98. package/dist/tests/bench/verifier.js +109 -0
  99. package/dist/tests/bench/verifier.test.js +118 -0
  100. package/dist/tests/bench/workflow-evaluator.js +557 -0
  101. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  102. package/dist/tests/bench/workflow-spec.js +358 -0
  103. package/dist/tests/bench/workflow-spec.test.js +363 -0
  104. package/dist/tests/bench/workflow-trace.js +438 -0
  105. package/dist/tests/bench/workflow-trace.test.js +254 -0
  106. package/dist/tests/benchmark-search-quality.js +536 -0
  107. package/dist/tests/benchmark-suite.js +1441 -0
  108. package/dist/tests/capture-cli.test.js +112 -0
  109. package/dist/tests/cli-errors.test.js +203 -0
  110. package/dist/tests/commands/events.test.js +370 -0
  111. package/dist/tests/commands/history.test.js +223 -0
  112. package/dist/tests/commands/import.test.js +103 -0
  113. package/dist/tests/commands/proposal-cli.test.js +209 -0
  114. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  115. package/dist/tests/commands/remember.test.js +97 -0
  116. package/dist/tests/commands/scope-flags.test.js +300 -0
  117. package/dist/tests/commands/search.test.js +537 -0
  118. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  119. package/dist/tests/commands/show.test.js +294 -0
  120. package/dist/tests/common.test.js +266 -0
  121. package/dist/tests/completions.test.js +142 -0
  122. package/dist/tests/config-cli.test.js +193 -0
  123. package/dist/tests/config-llm-features.test.js +139 -0
  124. package/dist/tests/config.test.js +544 -0
  125. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  126. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  127. package/dist/tests/contracts/spec-helpers.js +46 -0
  128. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  129. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  130. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  131. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  132. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  133. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  134. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  135. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  136. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  137. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  138. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  139. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  140. package/dist/tests/core/write-source.test.js +366 -0
  141. package/dist/tests/curate-command.test.js +87 -0
  142. package/dist/tests/db-scoring.test.js +201 -0
  143. package/dist/tests/db.test.js +654 -0
  144. package/dist/tests/distill-cli-flag.test.js +208 -0
  145. package/dist/tests/distill.test.js +515 -0
  146. package/dist/tests/docker-install.test.js +120 -0
  147. package/dist/tests/e2e.test.js +1398 -0
  148. package/dist/tests/embedder.test.js +340 -0
  149. package/dist/tests/embedding-model-config.test.js +379 -0
  150. package/dist/tests/feedback-command.test.js +172 -0
  151. package/dist/tests/file-context.test.js +552 -0
  152. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  153. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  154. package/dist/tests/fixtures/stashes/load.js +166 -0
  155. package/dist/tests/fixtures/stashes/load.test.js +88 -0
  156. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  157. package/dist/tests/frontmatter.test.js +190 -0
  158. package/dist/tests/fts-field-weighting.test.js +254 -0
  159. package/dist/tests/fuzzy-search.test.js +230 -0
  160. package/dist/tests/git-provider-clone.test.js +45 -0
  161. package/dist/tests/github.test.js +161 -0
  162. package/dist/tests/graph-boost-ranking.test.js +305 -0
  163. package/dist/tests/graph-extraction.test.js +282 -0
  164. package/dist/tests/helpers/usage-events.js +8 -0
  165. package/dist/tests/index-pass-llm.test.js +161 -0
  166. package/dist/tests/indexer.test.js +559 -0
  167. package/dist/tests/info-command.test.js +166 -0
  168. package/dist/tests/init.test.js +69 -0
  169. package/dist/tests/install-script.test.js +246 -0
  170. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  171. package/dist/tests/issue-36-repro.test.js +304 -0
  172. package/dist/tests/issues-191-194.test.js +160 -0
  173. package/dist/tests/lesson-lint.test.js +111 -0
  174. package/dist/tests/llm-client.test.js +115 -0
  175. package/dist/tests/llm-feature-gate.test.js +151 -0
  176. package/dist/tests/llm.test.js +139 -0
  177. package/dist/tests/lockfile.test.js +216 -0
  178. package/dist/tests/manifest.test.js +205 -0
  179. package/dist/tests/markdown.test.js +126 -0
  180. package/dist/tests/matchers-unit.test.js +189 -0
  181. package/dist/tests/memory-inference.test.js +299 -0
  182. package/dist/tests/merge-scoring.test.js +136 -0
  183. package/dist/tests/metadata.test.js +313 -0
  184. package/dist/tests/migration-help.test.js +89 -0
  185. package/dist/tests/origin-resolve.test.js +124 -0
  186. package/dist/tests/output-baseline.test.js +217 -0
  187. package/dist/tests/output-shapes-unit.test.js +476 -0
  188. package/dist/tests/parallel-search.test.js +272 -0
  189. package/dist/tests/parameter-metadata.test.js +365 -0
  190. package/dist/tests/paths.test.js +177 -0
  191. package/dist/tests/progressive-disclosure.test.js +280 -0
  192. package/dist/tests/proposals.test.js +279 -0
  193. package/dist/tests/proposed-quality.test.js +271 -0
  194. package/dist/tests/provider-registry.test.js +32 -0
  195. package/dist/tests/ranking-regression.test.js +548 -0
  196. package/dist/tests/reflect-propose.test.js +455 -0
  197. package/dist/tests/registry-build-index.test.js +378 -0
  198. package/dist/tests/registry-cli.test.js +290 -0
  199. package/dist/tests/registry-index-v2.test.js +430 -0
  200. package/dist/tests/registry-install.test.js +728 -0
  201. package/dist/tests/registry-providers/parity.test.js +189 -0
  202. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  203. package/dist/tests/registry-providers/static-index.test.js +204 -0
  204. package/dist/tests/registry-resolve.test.js +126 -0
  205. package/dist/tests/registry-search.test.js +723 -0
  206. package/dist/tests/remember-frontmatter.test.js +380 -0
  207. package/dist/tests/remember-unit.test.js +123 -0
  208. package/dist/tests/ripgrep-install.test.js +251 -0
  209. package/dist/tests/ripgrep-resolve.test.js +108 -0
  210. package/dist/tests/ripgrep.test.js +163 -0
  211. package/dist/tests/save-command.test.js +94 -0
  212. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  213. package/dist/tests/scoring-pipeline.test.js +648 -0
  214. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  215. package/dist/tests/self-update.test.js +442 -0
  216. package/dist/tests/semantic-search-e2e.test.js +512 -0
  217. package/dist/tests/semantic-status.test.js +471 -0
  218. package/dist/tests/setup-run.integration.js +877 -0
  219. package/dist/tests/setup-wizard.test.js +198 -0
  220. package/dist/tests/setup.test.js +131 -0
  221. package/dist/tests/source-add.test.js +11 -0
  222. package/dist/tests/source-clone.test.js +254 -0
  223. package/dist/tests/source-manage.test.js +366 -0
  224. package/dist/tests/source-providers/filesystem.test.js +82 -0
  225. package/dist/tests/source-providers/git.test.js +252 -0
  226. package/dist/tests/source-providers/website.test.js +128 -0
  227. package/dist/tests/source-qa-fixes.test.js +268 -0
  228. package/dist/tests/source-registry.test.js +350 -0
  229. package/dist/tests/source-resolve.test.js +100 -0
  230. package/dist/tests/source-source.test.js +221 -0
  231. package/dist/tests/source.test.js +533 -0
  232. package/dist/tests/tar-utils-scan.test.js +73 -0
  233. package/dist/tests/toggle-components.test.js +73 -0
  234. package/dist/tests/usage-telemetry.test.js +265 -0
  235. package/dist/tests/utility-scoring.test.js +558 -0
  236. package/dist/tests/vault-load-error.test.js +78 -0
  237. package/dist/tests/vault-qa-fixes.test.js +194 -0
  238. package/dist/tests/vault.test.js +429 -0
  239. package/dist/tests/vector-search.test.js +608 -0
  240. package/dist/tests/walker.test.js +252 -0
  241. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  242. package/dist/tests/wave2-cluster-d.test.js +180 -0
  243. package/dist/tests/wave2-cluster-e.test.js +179 -0
  244. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  245. package/dist/tests/wiki.test.js +529 -0
  246. package/dist/tests/workflow-cli.test.js +271 -0
  247. package/dist/tests/workflow-markdown.test.js +171 -0
  248. package/dist/tests/workflow-path-escape.test.js +132 -0
  249. package/dist/tests/workflow-qa-fixes.test.js +377 -0
  250. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  251. package/docs/README.md +8 -0
  252. package/docs/migration/release-notes/0.7.0.md +244 -0
  253. package/package.json +2 -2
  254. package/dist/core/warn.js +0 -27
  255. package/dist/output/shapes.js +0 -212
  256. /package/dist/{commands → src/commands}/completions.js +0 -0
  257. /package/dist/{commands → src/commands}/curate.js +0 -0
  258. /package/dist/{commands → src/commands}/info.js +0 -0
  259. /package/dist/{commands → src/commands}/init.js +0 -0
  260. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  261. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  262. /package/dist/{commands → src/commands}/source-add.js +0 -0
  263. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  264. /package/dist/{commands → src/commands}/source-manage.js +0 -0
  265. /package/dist/{commands → src/commands}/vault.js +0 -0
  266. /package/dist/{core → src/core}/asset-registry.js +0 -0
  267. /package/dist/{core → src/core}/frontmatter.js +0 -0
  268. /package/dist/{core → src/core}/markdown.js +0 -0
  269. /package/dist/{core → src/core}/paths.js +0 -0
  270. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  271. /package/dist/{indexer → src/indexer}/matchers.js +0 -0
  272. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  273. /package/dist/{indexer → src/indexer}/search-source.js +0 -0
  274. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  275. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  276. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  277. /package/dist/{integrations → src/integrations}/github.js +0 -0
  278. /package/dist/{llm → src/llm}/embedder.js +0 -0
  279. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  280. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  281. /package/dist/{llm → src/llm}/embedders/remote.js +0 -0
  282. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  283. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  284. /package/dist/{output → src/output}/cli-hints.js +0 -0
  285. /package/dist/{output → src/output}/context.js +0 -0
  286. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  287. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  288. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  289. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  290. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  291. /package/dist/{registry → src/registry}/types.js +0 -0
  292. /package/dist/{setup → src/setup}/detect.js +0 -0
  293. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  294. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  295. /package/dist/{setup → src/setup}/steps.js +0 -0
  296. /package/dist/{sources → src/sources}/include.js +0 -0
  297. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  298. /package/dist/{sources → src/sources}/provider.js +0 -0
  299. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  300. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  301. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  302. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  303. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  304. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  305. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  306. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  307. /package/dist/{sources → src/sources}/resolve.js +0 -0
  308. /package/dist/{sources → src/sources}/types.js +0 -0
  309. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  310. /package/dist/{version.js → src/version.js} +0 -0
  311. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  312. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  313. /package/dist/{workflows → src/workflows}/db.js +0 -0
  314. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  315. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  316. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  317. /package/dist/{workflows → src/workflows}/runs.js +0 -0
  318. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  319. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -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
+ }
@@ -391,6 +391,64 @@ const wikiMdRenderer = {
391
391
  }
392
392
  },
393
393
  };
394
+ // ── 4c. lesson-md ────────────────────────────────────────────────────────────
395
+ /**
396
+ * Renderer for the `lesson` asset type (v1 spec §13).
397
+ *
398
+ * Lessons are markdown files with required `description` and `when_to_use`
399
+ * frontmatter. The renderer projects both fields explicitly so consumers can
400
+ * decide whether to apply a lesson without reading the full body. Lint
401
+ * (see `src/core/lesson-lint.ts`) is the contract enforcer; the renderer is
402
+ * intentionally tolerant — a lesson missing required fields will still render
403
+ * its body so the user has something to work with while they fix the file.
404
+ */
405
+ const lessonMdRenderer = {
406
+ name: "lesson-md",
407
+ buildShowResponse(ctx) {
408
+ const name = deriveName(ctx);
409
+ const parsed = parseFrontmatter(ctx.content());
410
+ const description = toStringOrUndefined(parsed.data.description);
411
+ const whenToUse = toStringOrUndefined(parsed.data.when_to_use);
412
+ const action = whenToUse
413
+ ? `Apply this lesson when: ${whenToUse}`
414
+ : "Apply this lesson when its `when_to_use` trigger matches the current task.";
415
+ return {
416
+ type: "lesson",
417
+ name,
418
+ path: ctx.absPath,
419
+ action,
420
+ description,
421
+ content: parsed.content,
422
+ };
423
+ },
424
+ extractMetadata(entry, ctx) {
425
+ try {
426
+ const parsed = parseFrontmatter(ctx.content());
427
+ const fm = parsed.data;
428
+ const desc = toStringOrUndefined(fm.description);
429
+ if (desc && !entry.description) {
430
+ entry.description = desc;
431
+ entry.source = "frontmatter";
432
+ entry.confidence = 0.9;
433
+ }
434
+ const whenToUse = toStringOrUndefined(fm.when_to_use);
435
+ if (whenToUse) {
436
+ const hints = new Set(entry.searchHints ?? []);
437
+ hints.add(`when_to_use:${whenToUse}`);
438
+ entry.searchHints = Array.from(hints).filter(Boolean);
439
+ }
440
+ if (Array.isArray(fm.tags) && fm.tags.length > 0) {
441
+ const fmTags = fm.tags.filter((t) => typeof t === "string" && t.trim().length > 0);
442
+ if (fmTags.length > 0) {
443
+ entry.tags = Array.from(new Set([...(entry.tags ?? []), ...fmTags]));
444
+ }
445
+ }
446
+ }
447
+ catch {
448
+ // Non-fatal: skip metadata extraction on parse error
449
+ }
450
+ },
451
+ };
394
452
  // ── 5. memory-md ─────────────────────────────────────────────────────────────
395
453
  const memoryMdRenderer = {
396
454
  name: "memory-md",
@@ -560,6 +618,7 @@ const builtinRenderers = [
560
618
  agentMdRenderer,
561
619
  knowledgeMdRenderer,
562
620
  wikiMdRenderer,
621
+ lessonMdRenderer,
563
622
  memoryMdRenderer,
564
623
  workflowMdRenderer,
565
624
  scriptSourceRenderer,
@@ -575,4 +634,4 @@ export function registerBuiltinRenderers() {
575
634
  }
576
635
  }
577
636
  // ── Named exports for testing ────────────────────────────────────────────────
578
- export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };
637
+ export { agentMdRenderer, commandMdRenderer, INTERPRETER_MAP, knowledgeMdRenderer, lessonMdRenderer, memoryMdRenderer, SETUP_SIGNALS, scriptSourceRenderer, skillMdRenderer, vaultEnvRenderer, wikiMdRenderer, workflowMdRenderer, };