akm-cli 0.6.1 → 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} +620 -26
  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 +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 +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 +44 -0
  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 +113 -24
  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 +111 -3
  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/tests/add-website-source.test.js +119 -0
  56. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  57. package/dist/tests/agent/agent-config.test.js +221 -0
  58. package/dist/tests/agent/agent-detect.test.js +100 -0
  59. package/dist/tests/agent/agent-spawn.test.js +234 -0
  60. package/dist/tests/agent-output.test.js +186 -0
  61. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  62. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  63. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  64. package/dist/tests/asset-ref.test.js +192 -0
  65. package/dist/tests/asset-registry.test.js +103 -0
  66. package/dist/tests/asset-spec.test.js +241 -0
  67. package/dist/tests/bench/attribution.test.js +995 -0
  68. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  69. package/dist/tests/bench/cleanup.js +203 -0
  70. package/dist/tests/bench/cleanup.test.js +166 -0
  71. package/dist/tests/bench/cli.js +683 -0
  72. package/dist/tests/bench/cli.test.js +177 -0
  73. package/dist/tests/bench/compare.test.js +556 -0
  74. package/dist/tests/bench/corpus.js +314 -0
  75. package/dist/tests/bench/corpus.test.js +258 -0
  76. package/dist/tests/bench/driver.js +346 -0
  77. package/dist/tests/bench/driver.test.js +443 -0
  78. package/dist/tests/bench/evolve-metrics.js +179 -0
  79. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  80. package/dist/tests/bench/evolve.js +580 -0
  81. package/dist/tests/bench/evolve.test.js +616 -0
  82. package/dist/tests/bench/failure-modes.test.js +300 -0
  83. package/dist/tests/bench/feedback-integrity.test.js +456 -0
  84. package/dist/tests/bench/leakage.test.js +125 -0
  85. package/dist/tests/bench/learning-curve.test.js +133 -0
  86. package/dist/tests/bench/metrics.js +2319 -0
  87. package/dist/tests/bench/metrics.test.js +1144 -0
  88. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  89. package/dist/tests/bench/report.js +1821 -0
  90. package/dist/tests/bench/report.test.js +989 -0
  91. package/dist/tests/bench/runner.js +536 -0
  92. package/dist/tests/bench/runner.test.js +958 -0
  93. package/dist/tests/bench/search-bridge.test.js +331 -0
  94. package/dist/tests/bench/tmp.js +41 -0
  95. package/dist/tests/bench/trajectory.js +116 -0
  96. package/dist/tests/bench/trajectory.test.js +127 -0
  97. package/dist/tests/bench/verifier.js +109 -0
  98. package/dist/tests/bench/verifier.test.js +118 -0
  99. package/dist/tests/bench/workflow-evaluator.js +557 -0
  100. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  101. package/dist/tests/bench/workflow-spec.js +358 -0
  102. package/dist/tests/bench/workflow-spec.test.js +363 -0
  103. package/dist/tests/bench/workflow-trace.js +438 -0
  104. package/dist/tests/bench/workflow-trace.test.js +254 -0
  105. package/dist/tests/benchmark-search-quality.js +536 -0
  106. package/dist/tests/benchmark-suite.js +1441 -0
  107. package/dist/tests/capture-cli.test.js +112 -0
  108. package/dist/tests/cli-errors.test.js +203 -0
  109. package/dist/tests/commands/events.test.js +370 -0
  110. package/dist/tests/commands/history.test.js +223 -0
  111. package/dist/tests/commands/import.test.js +103 -0
  112. package/dist/tests/commands/proposal-cli.test.js +209 -0
  113. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  114. package/dist/tests/commands/remember.test.js +97 -0
  115. package/dist/tests/commands/scope-flags.test.js +300 -0
  116. package/dist/tests/commands/search.test.js +537 -0
  117. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  118. package/dist/tests/commands/show.test.js +294 -0
  119. package/dist/tests/common.test.js +266 -0
  120. package/dist/tests/completions.test.js +142 -0
  121. package/dist/tests/config-cli.test.js +193 -0
  122. package/dist/tests/config-llm-features.test.js +139 -0
  123. package/dist/tests/config.test.js +544 -0
  124. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  125. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  126. package/dist/tests/contracts/spec-helpers.js +46 -0
  127. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  128. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  129. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  130. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  131. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  132. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  133. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  134. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  135. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  136. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  137. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  138. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  139. package/dist/tests/core/write-source.test.js +366 -0
  140. package/dist/tests/curate-command.test.js +87 -0
  141. package/dist/tests/db-scoring.test.js +201 -0
  142. package/dist/tests/db.test.js +654 -0
  143. package/dist/tests/distill-cli-flag.test.js +208 -0
  144. package/dist/tests/distill.test.js +515 -0
  145. package/dist/tests/docker-install.test.js +120 -0
  146. package/dist/tests/e2e.test.js +1398 -0
  147. package/dist/tests/embedder.test.js +340 -0
  148. package/dist/tests/embedding-model-config.test.js +379 -0
  149. package/dist/tests/feedback-command.test.js +172 -0
  150. package/dist/tests/file-context.test.js +552 -0
  151. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  152. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  153. package/dist/tests/fixtures/stashes/load.js +166 -0
  154. package/dist/tests/fixtures/stashes/load.test.js +88 -0
  155. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  156. package/dist/tests/frontmatter.test.js +190 -0
  157. package/dist/tests/fts-field-weighting.test.js +254 -0
  158. package/dist/tests/fuzzy-search.test.js +230 -0
  159. package/dist/tests/git-provider-clone.test.js +45 -0
  160. package/dist/tests/github.test.js +161 -0
  161. package/dist/tests/graph-boost-ranking.test.js +305 -0
  162. package/dist/tests/graph-extraction.test.js +282 -0
  163. package/dist/tests/helpers/usage-events.js +8 -0
  164. package/dist/tests/index-pass-llm.test.js +161 -0
  165. package/dist/tests/indexer.test.js +559 -0
  166. package/dist/tests/info-command.test.js +166 -0
  167. package/dist/tests/init.test.js +69 -0
  168. package/dist/tests/install-script.test.js +246 -0
  169. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  170. package/dist/tests/issue-36-repro.test.js +304 -0
  171. package/dist/tests/issues-191-194.test.js +160 -0
  172. package/dist/tests/lesson-lint.test.js +111 -0
  173. package/dist/tests/llm-client.test.js +115 -0
  174. package/dist/tests/llm-feature-gate.test.js +151 -0
  175. package/dist/tests/llm.test.js +139 -0
  176. package/dist/tests/lockfile.test.js +216 -0
  177. package/dist/tests/manifest.test.js +205 -0
  178. package/dist/tests/markdown.test.js +126 -0
  179. package/dist/tests/matchers-unit.test.js +189 -0
  180. package/dist/tests/memory-inference.test.js +299 -0
  181. package/dist/tests/merge-scoring.test.js +136 -0
  182. package/dist/tests/metadata.test.js +313 -0
  183. package/dist/tests/migration-help.test.js +89 -0
  184. package/dist/tests/origin-resolve.test.js +124 -0
  185. package/dist/tests/output-baseline.test.js +217 -0
  186. package/dist/tests/output-shapes-unit.test.js +476 -0
  187. package/dist/tests/parallel-search.test.js +272 -0
  188. package/dist/tests/parameter-metadata.test.js +365 -0
  189. package/dist/tests/paths.test.js +177 -0
  190. package/dist/tests/progressive-disclosure.test.js +280 -0
  191. package/dist/tests/proposals.test.js +279 -0
  192. package/dist/tests/proposed-quality.test.js +271 -0
  193. package/dist/tests/provider-registry.test.js +32 -0
  194. package/dist/tests/ranking-regression.test.js +548 -0
  195. package/dist/tests/reflect-propose.test.js +455 -0
  196. package/dist/tests/registry-build-index.test.js +378 -0
  197. package/dist/tests/registry-cli.test.js +290 -0
  198. package/dist/tests/registry-index-v2.test.js +430 -0
  199. package/dist/tests/registry-install.test.js +728 -0
  200. package/dist/tests/registry-providers/parity.test.js +189 -0
  201. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  202. package/dist/tests/registry-providers/static-index.test.js +204 -0
  203. package/dist/tests/registry-resolve.test.js +126 -0
  204. package/dist/tests/registry-search.test.js +723 -0
  205. package/dist/tests/remember-frontmatter.test.js +380 -0
  206. package/dist/tests/remember-unit.test.js +123 -0
  207. package/dist/tests/ripgrep-install.test.js +251 -0
  208. package/dist/tests/ripgrep-resolve.test.js +108 -0
  209. package/dist/tests/ripgrep.test.js +163 -0
  210. package/dist/tests/save-command.test.js +94 -0
  211. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  212. package/dist/tests/scoring-pipeline.test.js +648 -0
  213. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  214. package/dist/tests/self-update.test.js +442 -0
  215. package/dist/tests/semantic-search-e2e.test.js +512 -0
  216. package/dist/tests/semantic-status.test.js +471 -0
  217. package/dist/tests/setup-run.integration.js +877 -0
  218. package/dist/tests/setup-wizard.test.js +198 -0
  219. package/dist/tests/setup.test.js +131 -0
  220. package/dist/tests/source-add.test.js +11 -0
  221. package/dist/tests/source-clone.test.js +254 -0
  222. package/dist/tests/source-manage.test.js +366 -0
  223. package/dist/tests/source-providers/filesystem.test.js +82 -0
  224. package/dist/tests/source-providers/git.test.js +252 -0
  225. package/dist/tests/source-providers/website.test.js +128 -0
  226. package/dist/tests/source-qa-fixes.test.js +268 -0
  227. package/dist/tests/source-registry.test.js +350 -0
  228. package/dist/tests/source-resolve.test.js +100 -0
  229. package/dist/tests/source-source.test.js +221 -0
  230. package/dist/tests/source.test.js +533 -0
  231. package/dist/tests/tar-utils-scan.test.js +73 -0
  232. package/dist/tests/toggle-components.test.js +73 -0
  233. package/dist/tests/usage-telemetry.test.js +265 -0
  234. package/dist/tests/utility-scoring.test.js +558 -0
  235. package/dist/tests/vault-load-error.test.js +78 -0
  236. package/dist/tests/vault-qa-fixes.test.js +194 -0
  237. package/dist/tests/vault.test.js +429 -0
  238. package/dist/tests/vector-search.test.js +608 -0
  239. package/dist/tests/walker.test.js +252 -0
  240. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  241. package/dist/tests/wave2-cluster-d.test.js +180 -0
  242. package/dist/tests/wave2-cluster-e.test.js +179 -0
  243. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  244. package/dist/tests/wiki.test.js +529 -0
  245. package/dist/tests/workflow-cli.test.js +271 -0
  246. package/dist/tests/workflow-markdown.test.js +171 -0
  247. package/dist/tests/workflow-path-escape.test.js +132 -0
  248. package/dist/tests/workflow-qa-fixes.test.js +377 -0
  249. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  250. package/docs/README.md +8 -0
  251. package/docs/migration/release-notes/0.7.0.md +244 -0
  252. package/package.json +2 -2
  253. package/dist/core/warn.js +0 -27
  254. package/dist/output/shapes.js +0 -212
  255. /package/dist/{commands → src/commands}/completions.js +0 -0
  256. /package/dist/{commands → src/commands}/curate.js +0 -0
  257. /package/dist/{commands → src/commands}/info.js +0 -0
  258. /package/dist/{commands → src/commands}/init.js +0 -0
  259. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  260. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  261. /package/dist/{commands → src/commands}/source-add.js +0 -0
  262. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  263. /package/dist/{commands → src/commands}/source-manage.js +0 -0
  264. /package/dist/{commands → src/commands}/vault.js +0 -0
  265. /package/dist/{core → src/core}/asset-registry.js +0 -0
  266. /package/dist/{core → src/core}/frontmatter.js +0 -0
  267. /package/dist/{core → src/core}/markdown.js +0 -0
  268. /package/dist/{core → src/core}/paths.js +0 -0
  269. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  270. /package/dist/{indexer → src/indexer}/matchers.js +0 -0
  271. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  272. /package/dist/{indexer → src/indexer}/search-source.js +0 -0
  273. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  274. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  275. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  276. /package/dist/{integrations → src/integrations}/github.js +0 -0
  277. /package/dist/{llm → src/llm}/embedder.js +0 -0
  278. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  279. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  280. /package/dist/{llm → src/llm}/embedders/remote.js +0 -0
  281. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  282. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  283. /package/dist/{output → src/output}/cli-hints.js +0 -0
  284. /package/dist/{output → src/output}/context.js +0 -0
  285. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  286. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  287. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  288. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  289. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  290. /package/dist/{registry → src/registry}/types.js +0 -0
  291. /package/dist/{setup → src/setup}/detect.js +0 -0
  292. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  293. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  294. /package/dist/{setup → src/setup}/steps.js +0 -0
  295. /package/dist/{sources → src/sources}/include.js +0 -0
  296. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  297. /package/dist/{sources → src/sources}/provider.js +0 -0
  298. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  299. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  300. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  301. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  302. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  303. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  304. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  305. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  306. /package/dist/{sources → src/sources}/resolve.js +0 -0
  307. /package/dist/{sources → src/sources}/types.js +0 -0
  308. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  309. /package/dist/{version.js → src/version.js} +0 -0
  310. /package/dist/{wiki → src/wiki}/wiki.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
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { parseAgentConfig } from "../integrations/agent/config";
4
5
  import { filterNonEmptyStrings } from "./common";
5
6
  import { ConfigError } from "./errors";
6
7
  import { getConfigDir as _getConfigDir, getConfigPath as _getConfigPath } from "./paths";
@@ -73,7 +74,7 @@ export function loadUserConfig() {
73
74
  cachedUserConfig.contentHash === contentHash) {
74
75
  return cachedUserConfig.config;
75
76
  }
76
- const config = mergeLoadedConfig(DEFAULT_CONFIG, readNormalizedConfigFromText(configPath, text));
77
+ const config = mergeLoadedConfig(DEFAULT_CONFIG, readNormalizedConfigFromText(text));
77
78
  const finalConfig = applyRuntimeEnvApiKeys(config);
78
79
  cachedUserConfig = {
79
80
  config: finalConfig,
@@ -144,6 +145,14 @@ export function updateConfig(partial) {
144
145
  if (current.llm && partial.llm && partial.llm !== current.llm) {
145
146
  merged.llm = { ...current.llm, ...partial.llm };
146
147
  }
148
+ // Deep-merge index per-pass entries so partial updates don't wipe siblings.
149
+ if (current.index && partial.index && partial.index !== current.index) {
150
+ const mergedIndex = { ...current.index };
151
+ for (const [passName, passOverride] of Object.entries(partial.index)) {
152
+ mergedIndex[passName] = { ...(mergedIndex[passName] ?? {}), ...passOverride };
153
+ }
154
+ merged.index = mergedIndex;
155
+ }
147
156
  if (current.security && partial.security && partial.security !== current.security) {
148
157
  merged.security = mergeSecurityConfig(current.security, partial.security);
149
158
  }
@@ -170,59 +179,28 @@ function pickKnownKeys(raw) {
170
179
  else if (raw.semanticSearchMode === "off" || raw.semanticSearchMode === "auto") {
171
180
  config.semanticSearchMode = raw.semanticSearchMode;
172
181
  }
173
- else if (typeof raw.semanticSearch === "boolean") {
174
- // Legacy config: older versions used `semanticSearch` (boolean) instead of `semanticSearchMode`
175
- const legacySemanticSearch = raw.semanticSearch;
176
- config.semanticSearchMode = legacySemanticSearch ? "auto" : "off";
177
- }
178
- // Migrate legacy searchPaths into sources
179
- if (Array.isArray(raw.searchPaths)) {
180
- const legacyPaths = raw.searchPaths.filter((d) => typeof d === "string");
181
- if (legacyPaths.length > 0) {
182
- const existing = config.sources ?? [];
183
- const migrated = legacyPaths
184
- .filter((p) => !existing.some((s) => s.type === "filesystem" && s.path === p))
185
- .map((p) => ({ type: "filesystem", path: p }));
186
- if (migrated.length > 0) {
187
- config.sources = [...existing, ...migrated];
188
- }
189
- }
190
- }
191
182
  const embedding = parseEmbeddingConfig(raw.embedding);
192
183
  if (embedding)
193
184
  config.embedding = embedding;
194
185
  const llm = parseLlmConfig(raw.llm);
195
186
  if (llm)
196
187
  config.llm = llm;
188
+ const index = parseIndexConfig(raw.index);
189
+ if (index)
190
+ config.index = index;
197
191
  const installed = parseInstalledEntries(raw.installed);
198
192
  if (installed)
199
193
  config.installed = installed;
200
194
  const registries = parseRegistriesConfig(raw.registries);
201
195
  if (registries)
202
196
  config.registries = registries;
203
- // Prefer the new `stashInheritance` field; fall back to the legacy boolean
204
- // `disableGlobalStashes` so existing config files keep working unchanged.
205
197
  if (raw.stashInheritance === "replace" || raw.stashInheritance === "merge") {
206
198
  config.stashInheritance = raw.stashInheritance;
207
199
  }
208
- else if (typeof raw.disableGlobalStashes === "boolean") {
209
- config.stashInheritance = raw.disableGlobalStashes ? "replace" : "merge";
210
- config.disableGlobalStashes = raw.disableGlobalStashes;
211
- }
212
- // Load `sources` (new key) first, then fall back to legacy `stashes` key.
213
- const sources = parseStashesConfig(raw.sources);
200
+ const sources = parseSourcesConfig(raw.sources);
214
201
  if (sources) {
215
202
  config.sources = sources;
216
203
  }
217
- else {
218
- const legacyStashes = parseStashesConfig(raw.stashes);
219
- if (legacyStashes) {
220
- // Backwards-compat fallback: configs that still carry `stashes[]` are
221
- // normalized to `sources[]` after the raw file loader has had a chance to
222
- // auto-migrate the on-disk key.
223
- config.sources = legacyStashes;
224
- }
225
- }
226
204
  const security = parseSecurityConfig(raw.security);
227
205
  if (security)
228
206
  config.security = security;
@@ -235,6 +213,11 @@ function pickKnownKeys(raw) {
235
213
  if (typeof raw.defaultWriteTarget === "string" && raw.defaultWriteTarget.trim()) {
236
214
  config.defaultWriteTarget = raw.defaultWriteTarget.trim();
237
215
  }
216
+ if ("agent" in raw) {
217
+ const agent = parseAgentConfig(raw.agent);
218
+ if (agent)
219
+ config.agent = agent;
220
+ }
238
221
  if (typeof raw.search === "object" && raw.search !== null && !Array.isArray(raw.search)) {
239
222
  const searchRaw = raw.search;
240
223
  const searchConfig = {};
@@ -248,16 +231,14 @@ function pickKnownKeys(raw) {
248
231
  }
249
232
  function readNormalizedConfig(configPath) {
250
233
  const raw = readConfigObject(configPath);
251
- const migrated = raw ? maybeAutoMigrateLegacyStashes(configPath, raw) : undefined;
252
- const expanded = migrated ? expandEnvVars(migrated) : undefined;
234
+ const expanded = raw ? expandEnvVars(raw) : undefined;
253
235
  return expanded ? pickKnownKeys(expanded) : undefined;
254
236
  }
255
- function readNormalizedConfigFromText(configPath, text) {
237
+ function readNormalizedConfigFromText(text) {
256
238
  const raw = parseConfigObjectFromText(text);
257
239
  if (!raw)
258
240
  return undefined;
259
- const migrated = maybeAutoMigrateLegacyStashes(configPath, raw);
260
- const expanded = expandEnvVars(migrated);
241
+ const expanded = expandEnvVars(raw);
261
242
  return pickKnownKeys(expanded);
262
243
  }
263
244
  function parseOutputConfig(value) {
@@ -337,31 +318,6 @@ function parseConfigObjectFromText(text) {
337
318
  return undefined;
338
319
  }
339
320
  }
340
- /**
341
- * Best-effort on-disk config migration for the legacy `stashes` key.
342
- *
343
- * When a config file still uses `stashes` and does not already define
344
- * `sources`, rewrite the file in place with `sources` replacing `stashes`,
345
- * emit a one-time notice on success, and return the migrated object. If the
346
- * rewrite fails, emit a warning and return the original object so the loader
347
- * can still continue with an in-memory fallback.
348
- */
349
- function maybeAutoMigrateLegacyStashes(configPath, raw) {
350
- if (Object.hasOwn(raw, "sources") || !Object.hasOwn(raw, "stashes")) {
351
- return raw;
352
- }
353
- const migrated = Object.fromEntries(Object.entries(raw).map(([key, value]) => (key === "stashes" ? ["sources", value] : [key, value])));
354
- try {
355
- writeConfigObject(configPath, migrated);
356
- warn('Config migrated: "stashes" → "sources" in config.json');
357
- return migrated;
358
- }
359
- catch {
360
- warn('Failed to migrate "stashes" → "sources" in config.json; continuing with the legacy key in memory. ' +
361
- "Check file permissions or rename the key manually if this persists.");
362
- return raw;
363
- }
364
- }
365
321
  function writeConfigObject(configPath, config) {
366
322
  const tmpPath = `${configPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
367
323
  try {
@@ -443,7 +399,7 @@ function parseEmbeddingConfig(value) {
443
399
  return undefined;
444
400
  }
445
401
  if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
446
- console.warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
402
+ warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
447
403
  // Still return localModel-only config if localModel was set
448
404
  if (localModel) {
449
405
  return { endpoint: "", model: "", localModel };
@@ -453,7 +409,7 @@ function parseEmbeddingConfig(value) {
453
409
  if (typeof obj.model !== "string" || !obj.model) {
454
410
  // No remote model, but localModel may still be valid
455
411
  if (localModel) {
456
- console.warn(`[akm] Embedding endpoint "${obj.endpoint}" ignored: model is required for remote embeddings. Using local model only.`);
412
+ warn(`[akm] Embedding endpoint "${obj.endpoint}" ignored: model is required for remote embeddings. Using local model only.`);
457
413
  return { endpoint: "", model: "", localModel };
458
414
  }
459
415
  return undefined;
@@ -489,7 +445,7 @@ function parseLlmConfig(value) {
489
445
  if (typeof obj.endpoint !== "string" || !obj.endpoint)
490
446
  return undefined;
491
447
  if (!obj.endpoint.startsWith("http://") && !obj.endpoint.startsWith("https://")) {
492
- console.warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
448
+ warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
493
449
  return undefined;
494
450
  }
495
451
  const model = typeof obj.model === "string" ? obj.model : "";
@@ -515,26 +471,122 @@ function parseLlmConfig(value) {
515
471
  if (typeof obj.apiKey === "string" && obj.apiKey) {
516
472
  result.apiKey = obj.apiKey;
517
473
  }
518
- if (typeof obj.contextWindow === "number" &&
519
- Number.isFinite(obj.contextWindow) &&
520
- Number.isInteger(obj.contextWindow) &&
521
- obj.contextWindow > 0) {
522
- result.contextWindow = obj.contextWindow;
523
- }
524
474
  if (typeof obj.capabilities === "object" && obj.capabilities !== null && !Array.isArray(obj.capabilities)) {
525
475
  const capsRaw = obj.capabilities;
526
476
  const caps = {};
527
477
  if (typeof capsRaw.structuredOutput === "boolean")
528
478
  caps.structuredOutput = capsRaw.structuredOutput;
529
- if (typeof capsRaw.longContext === "boolean")
530
- caps.longContext = capsRaw.longContext;
531
- if (typeof capsRaw.toolUse === "boolean")
532
- caps.toolUse = capsRaw.toolUse;
533
479
  if (Object.keys(caps).length > 0)
534
480
  result.capabilities = caps;
535
481
  }
482
+ if (typeof obj.features === "object" && obj.features !== null && !Array.isArray(obj.features)) {
483
+ const features = parseLlmFeatures(obj.features);
484
+ if (Object.keys(features).length > 0)
485
+ result.features = features;
486
+ }
536
487
  return result;
537
488
  }
489
+ /**
490
+ * v1 spec §14 — locked feature keys. Defined here so unknown keys can
491
+ * be warn-and-ignored at load time (per spec §14.3 / §9.2). The set is
492
+ * deliberately the *full* locked table even though only a subset has
493
+ * runtime parsing today; this lets users author future-flagged configs
494
+ * without spurious warnings.
495
+ */
496
+ const LOCKED_LLM_FEATURE_KEYS = new Set([
497
+ "curate_rerank",
498
+ "feedback_distillation",
499
+ "memory_inference",
500
+ "graph_extraction",
501
+ ]);
502
+ function parseLlmFeatures(raw) {
503
+ const out = {};
504
+ for (const [key, value] of Object.entries(raw)) {
505
+ if (!LOCKED_LLM_FEATURE_KEYS.has(key)) {
506
+ warn(`[akm] Ignoring unknown llm.features key "${key}".`);
507
+ continue;
508
+ }
509
+ if (typeof value !== "boolean") {
510
+ warn(`[akm] Ignoring llm.features.${key}: expected boolean, got ${typeof value}.`);
511
+ continue;
512
+ }
513
+ switch (key) {
514
+ case "memory_inference":
515
+ out.memory_inference = value;
516
+ break;
517
+ case "graph_extraction":
518
+ out.graph_extraction = value;
519
+ break;
520
+ case "curate_rerank":
521
+ out.curate_rerank = value;
522
+ break;
523
+ case "feedback_distillation":
524
+ out.feedback_distillation = value;
525
+ break;
526
+ // No default: LOCKED_LLM_FEATURE_KEYS is the source of truth for which
527
+ // keys are accepted. Adding a new locked key requires an arm here AND a
528
+ // field on LlmFeatureFlags above.
529
+ }
530
+ }
531
+ return out;
532
+ }
533
+ /**
534
+ * Keys that, if present anywhere under `index.<pass>`, indicate the user is
535
+ * trying to supply a parallel LLM provider configuration. Per #208 this is
536
+ * deliberately rejected at load time so there is exactly one place to
537
+ * configure the LLM (`akm.llm`).
538
+ */
539
+ const PROVIDER_CONFIG_KEYS = new Set([
540
+ "endpoint",
541
+ "model",
542
+ "provider",
543
+ "apiKey",
544
+ "baseUrl",
545
+ "temperature",
546
+ "maxTokens",
547
+ "capabilities",
548
+ ]);
549
+ /**
550
+ * Parse the `index` config block. Each entry is a pass name → small object
551
+ * `{ llm?: boolean }`. Anything richer (a parallel provider config, unknown
552
+ * keys, non-boolean `llm`) throws `ConfigError("INVALID_CONFIG_FILE")` at
553
+ * load time so the failure is visible at startup, not on the next index run.
554
+ */
555
+ function parseIndexConfig(value) {
556
+ if (value === undefined || value === null)
557
+ return undefined;
558
+ if (typeof value !== "object" || Array.isArray(value)) {
559
+ throw new ConfigError('Invalid `index` config: expected an object keyed by pass name (e.g. `{ "enrichment": { "llm": false } }`).', "INVALID_CONFIG_FILE");
560
+ }
561
+ const out = {};
562
+ for (const [passName, raw] of Object.entries(value)) {
563
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
564
+ throw new ConfigError(`Invalid \`index.${passName}\` config: expected an object like \`{ "llm": false }\`.`, "INVALID_CONFIG_FILE");
565
+ }
566
+ const passRaw = raw;
567
+ // Reject any provider-shaped key — there must be exactly one place to
568
+ // configure the LLM (#208). This is the duplicate-provider guard.
569
+ for (const key of Object.keys(passRaw)) {
570
+ if (PROVIDER_CONFIG_KEYS.has(key)) {
571
+ throw new ConfigError(`Duplicate LLM provider configuration: \`index.${passName}.${key}\` is not allowed. ` +
572
+ "Configure provider/model/endpoint under top-level `llm` only; per-pass entries support `{ llm: false }` opt-out.", "INVALID_CONFIG_FILE", 'Move provider settings to the top-level "llm" block, then set `index.<pass>.llm = false` to opt a single pass out.');
573
+ }
574
+ if (key !== "llm") {
575
+ throw new ConfigError(`Unknown key \`index.${passName}.${key}\`. Per-pass entries only support \`llm\` (boolean opt-out).`, "INVALID_CONFIG_FILE");
576
+ }
577
+ }
578
+ const passConfig = {};
579
+ if ("llm" in passRaw) {
580
+ const llmFlag = passRaw.llm;
581
+ if (typeof llmFlag !== "boolean") {
582
+ throw new ConfigError(`Invalid \`index.${passName}.llm\`: expected a boolean (true to use \`akm.llm\`, false to opt out). Got ${typeof llmFlag}.`, "INVALID_CONFIG_FILE", "Per-pass alternative provider config is intentionally unsupported in v1 (#208). Use `false` to disable LLM for this pass.");
583
+ }
584
+ passConfig.llm = llmFlag;
585
+ }
586
+ out[passName] = passConfig;
587
+ }
588
+ return out;
589
+ }
538
590
  function parseInstalledEntries(value) {
539
591
  if (!Array.isArray(value))
540
592
  return undefined;
@@ -604,7 +656,7 @@ function parseRegistriesConfig(value) {
604
656
  // which overrides the default. Only return undefined if the field was not an array.
605
657
  return entries;
606
658
  }
607
- function parseStashesConfig(value) {
659
+ function parseSourcesConfig(value) {
608
660
  if (!Array.isArray(value))
609
661
  return undefined;
610
662
  const entries = value
@@ -669,28 +721,17 @@ function parseInstallAuditAllowedFinding(value) {
669
721
  finding.reason = reason;
670
722
  return finding;
671
723
  }
672
- /**
673
- * Legacy stash type aliases that are normalized to canonical types at
674
- * config-load time. Both "context-hub" and "github" were never distinct
675
- * provider types — they were always git stashes — so we normalize them in
676
- * memory to "git" without rewriting `config.json` on disk.
677
- */
678
- const STASH_TYPE_ALIASES = {
679
- "context-hub": "git",
680
- github: "git",
681
- };
682
724
  function parseSourceConfigEntry(value) {
683
725
  if (typeof value !== "object" || value === null || Array.isArray(value))
684
726
  return undefined;
685
727
  const obj = value;
686
- const rawType = asNonEmptyString(obj.type);
687
- if (!rawType)
728
+ const type = asNonEmptyString(obj.type);
729
+ if (!type)
688
730
  return undefined;
689
- if (rawType === "openviking") {
731
+ if (type === "openviking") {
690
732
  const name = asNonEmptyString(obj.name) ?? "unnamed";
691
733
  throw new ConfigError(`openviking is not supported in akm v1. API-backed sources will return as a\nseparate QuerySource tier post-v1. Remove the source named "${name}" from your config file\nor downgrade to 0.6.x. See docs/migration/v1.md.`, "INVALID_CONFIG_FILE", `Run \`akm remove ${name}\` then re-run, or edit your config file directly at ${_getConfigPath()} to remove the openviking entry.`);
692
734
  }
693
- const type = STASH_TYPE_ALIASES[rawType] ?? rawType;
694
735
  const entry = { type };
695
736
  const entryPath = asNonEmptyString(obj.path);
696
737
  if (entryPath)
@@ -746,19 +787,15 @@ function deriveStashEntryName(entry) {
746
787
  * entry is missing the fields its provider type requires (e.g. a
747
788
  * `filesystem` entry with no `path`); callers should drop or warn for those.
748
789
  *
749
- * Unknown provider types fall back to `{ type: "filesystem", path: ... }` so
750
- * legacy aliases (`"context-hub"`, `"github"` for git) still produce a usable
751
- * runtime value when a path/url is supplied.
790
+ * Unknown provider types fall back to `{ type: "filesystem", path: ... }` when
791
+ * a `path` is supplied, so future provider types still produce a usable
792
+ * runtime value.
752
793
  */
753
794
  export function parseSourceSpec(entry) {
754
795
  switch (entry.type) {
755
796
  case "filesystem":
756
797
  return entry.path ? { type: "filesystem", path: entry.path } : undefined;
757
798
  case "git":
758
- case "context-hub":
759
- case "github":
760
- // Note: a configured `github` provider entry historically meant "git
761
- // repo over the GitHub web URL", not the registry-install `github:` ref.
762
799
  return entry.url ? { type: "git", url: entry.url } : undefined;
763
800
  case "website":
764
801
  return entry.url
@@ -792,11 +829,9 @@ export function parseSourceSpec(entry) {
792
829
  */
793
830
  export function resolveConfiguredSources(config) {
794
831
  const entries = [];
795
- // `sources` is the canonical key. `stashes` is the legacy key that the loader
796
- // migrates in-memory; only one of the two should be set at runtime.
797
- const stashes = config.sources ?? config.stashes ?? [];
832
+ const sources = config.sources ?? [];
798
833
  // (1) Primary entry: explicit `primary: true` wins; fall back to top-level stashDir.
799
- let primary = stashes.find((entry) => entry.primary === true);
834
+ let primary = sources.find((entry) => entry.primary === true);
800
835
  if (!primary && config.stashDir) {
801
836
  primary = { type: "filesystem", path: config.stashDir, primary: true };
802
837
  }
@@ -805,8 +840,8 @@ export function resolveConfiguredSources(config) {
805
840
  if (runtime)
806
841
  entries.push(runtime);
807
842
  }
808
- // (2) Declared stashes (skip the primary entry — already added).
809
- for (const entry of stashes) {
843
+ // (2) Declared sources (skip the primary entry — already added).
844
+ for (const entry of sources) {
810
845
  if (entry === primary)
811
846
  continue;
812
847
  const runtime = toConfiguredSource(entry, false);
@@ -862,6 +897,20 @@ function parseRegistryConfigEntry(value) {
862
897
  }
863
898
  return entry;
864
899
  }
900
+ function mergeAgentConfig(base, override) {
901
+ const merged = { ...base, ...override };
902
+ const baseProfiles = base.profiles;
903
+ const overrideProfiles = override.profiles;
904
+ if (baseProfiles && overrideProfiles) {
905
+ const profiles = { ...baseProfiles };
906
+ for (const [name, entry] of Object.entries(overrideProfiles)) {
907
+ const existing = baseProfiles[name];
908
+ profiles[name] = existing ? { ...existing, ...entry } : entry;
909
+ }
910
+ merged.profiles = profiles;
911
+ }
912
+ return merged;
913
+ }
865
914
  function mergeSecurityConfig(base, override) {
866
915
  if (!base && !override)
867
916
  return undefined;
@@ -882,9 +931,8 @@ function mergeInstallAuditConfig(base, override) {
882
931
  *
883
932
  * Scalar fields follow normal override semantics. Known nested objects are
884
933
  * deep-merged so project config files can override individual fields without
885
- * clobbering sibling settings. `stashes` are additive by default, but a later
886
- * layer can set `stashInheritance: "replace"` (or the legacy
887
- * `disableGlobalStashes: true`) to drop inherited stashes first.
934
+ * clobbering sibling settings. `sources` are additive by default, but a later
935
+ * layer can set `stashInheritance: "replace"` to drop inherited sources first.
888
936
  */
889
937
  function mergeLoadedConfig(base, override) {
890
938
  if (!override)
@@ -902,18 +950,26 @@ function mergeLoadedConfig(base, override) {
902
950
  if (base.llm && override.llm) {
903
951
  merged.llm = { ...base.llm, ...override.llm };
904
952
  }
953
+ if (base.index || override.index) {
954
+ // Deep-merge per-pass entries so a project layer can opt one pass out
955
+ // without dropping siblings configured in user config.
956
+ const mergedIndex = { ...(base.index ?? {}) };
957
+ for (const [passName, passOverride] of Object.entries(override.index ?? {})) {
958
+ mergedIndex[passName] = { ...(mergedIndex[passName] ?? {}), ...passOverride };
959
+ }
960
+ if (Object.keys(mergedIndex).length > 0)
961
+ merged.index = mergedIndex;
962
+ }
905
963
  if (base.security && override.security) {
906
964
  merged.security = mergeSecurityConfig(base.security, override.security);
907
965
  }
908
- // The new `stashInheritance` field wins; fall back to the legacy
909
- // `disableGlobalStashes` boolean so old config files behave identically.
910
- const replaceStashes = override.stashInheritance === "replace" ||
911
- (override.stashInheritance === undefined && override.disableGlobalStashes === true);
912
- // Merge `sources` (canonical key). Legacy `stashes` key is handled via the
913
- // pickKnownKeys migration which promotes it to `sources` at load time.
914
- const overrideSources = override.sources ?? override.stashes ?? [];
915
- const baseSources = base.sources ?? base.stashes ?? [];
916
- if (replaceStashes) {
966
+ if (base.agent && override.agent) {
967
+ merged.agent = mergeAgentConfig(base.agent, override.agent);
968
+ }
969
+ const replaceSources = override.stashInheritance === "replace";
970
+ const overrideSources = override.sources ?? [];
971
+ const baseSources = base.sources ?? [];
972
+ if (replaceSources) {
917
973
  merged.sources = [...overrideSources];
918
974
  }
919
975
  else if (overrideSources.length > 0) {
@@ -922,8 +978,6 @@ function mergeLoadedConfig(base, override) {
922
978
  else if (baseSources.length > 0) {
923
979
  merged.sources = [...baseSources];
924
980
  }
925
- // Clear deprecated stashes field on the merged result — sources is canonical.
926
- delete merged.stashes;
927
981
  return merged;
928
982
  }
929
983
  function applyRuntimeEnvApiKeys(config) {
@@ -28,6 +28,7 @@ const CONFIG_HINTS = {
28
28
  };
29
29
  /** Default hint for each UsageError code. */
30
30
  const USAGE_HINTS = {
31
+ INVALID_FLAG_VALUE: "Run `akm <command> --help` to see accepted values.",
31
32
  INVALID_SOURCE_VALUE: "Pick one of: stash, registry, both.",
32
33
  INVALID_FORMAT_VALUE: "Pick one of: json, jsonl, text, yaml.",
33
34
  INVALID_DETAIL_VALUE: "Pick one of: brief, normal, full, summary, agent.",
@@ -38,7 +39,10 @@ const USAGE_HINTS = {
38
39
  };
39
40
  /** Default hint for each NotFoundError code. */
40
41
  const NOT_FOUND_HINTS = {
42
+ ASSET_NOT_FOUND: "Run `akm search <query>` or `akm index` to refresh the index.",
41
43
  SOURCE_NOT_FOUND: "Run `akm list` to view your sources, then retry with one of those values.",
44
+ WORKFLOW_NOT_FOUND: "Run `akm workflow list --active` to see runs.",
45
+ FILE_NOT_FOUND: "Check the path exists and is readable.",
42
46
  };
43
47
  /** Raised when configuration or environment is invalid or missing. */
44
48
  export class ConfigError extends Error {