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
@@ -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
  }
@@ -160,6 +169,9 @@ export function updateConfig(partial) {
160
169
  */
161
170
  function pickKnownKeys(raw) {
162
171
  const config = {};
172
+ if (Array.isArray(raw.stashes)) {
173
+ throw new ConfigError("The legacy `stashes[]` config key is no longer supported; rename it to `sources[]`.", "INVALID_CONFIG_FILE", `Edit ${_getConfigPath()} and replace \`stashes\` with \`sources\`.`);
174
+ }
163
175
  if (typeof raw.stashDir === "string" && raw.stashDir.trim()) {
164
176
  config.stashDir = raw.stashDir.trim();
165
177
  }
@@ -170,59 +182,28 @@ function pickKnownKeys(raw) {
170
182
  else if (raw.semanticSearchMode === "off" || raw.semanticSearchMode === "auto") {
171
183
  config.semanticSearchMode = raw.semanticSearchMode;
172
184
  }
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
185
  const embedding = parseEmbeddingConfig(raw.embedding);
192
186
  if (embedding)
193
187
  config.embedding = embedding;
194
188
  const llm = parseLlmConfig(raw.llm);
195
189
  if (llm)
196
190
  config.llm = llm;
191
+ const index = parseIndexConfig(raw.index);
192
+ if (index)
193
+ config.index = index;
197
194
  const installed = parseInstalledEntries(raw.installed);
198
195
  if (installed)
199
196
  config.installed = installed;
200
197
  const registries = parseRegistriesConfig(raw.registries);
201
198
  if (registries)
202
199
  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
200
  if (raw.stashInheritance === "replace" || raw.stashInheritance === "merge") {
206
201
  config.stashInheritance = raw.stashInheritance;
207
202
  }
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);
203
+ const sources = parseSourcesConfig(raw.sources);
214
204
  if (sources) {
215
205
  config.sources = sources;
216
206
  }
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
207
  const security = parseSecurityConfig(raw.security);
227
208
  if (security)
228
209
  config.security = security;
@@ -235,6 +216,11 @@ function pickKnownKeys(raw) {
235
216
  if (typeof raw.defaultWriteTarget === "string" && raw.defaultWriteTarget.trim()) {
236
217
  config.defaultWriteTarget = raw.defaultWriteTarget.trim();
237
218
  }
219
+ if ("agent" in raw) {
220
+ const agent = parseAgentConfig(raw.agent);
221
+ if (agent)
222
+ config.agent = agent;
223
+ }
238
224
  if (typeof raw.search === "object" && raw.search !== null && !Array.isArray(raw.search)) {
239
225
  const searchRaw = raw.search;
240
226
  const searchConfig = {};
@@ -248,16 +234,14 @@ function pickKnownKeys(raw) {
248
234
  }
249
235
  function readNormalizedConfig(configPath) {
250
236
  const raw = readConfigObject(configPath);
251
- const migrated = raw ? maybeAutoMigrateLegacyStashes(configPath, raw) : undefined;
252
- const expanded = migrated ? expandEnvVars(migrated) : undefined;
237
+ const expanded = raw ? expandEnvVars(raw) : undefined;
253
238
  return expanded ? pickKnownKeys(expanded) : undefined;
254
239
  }
255
- function readNormalizedConfigFromText(configPath, text) {
240
+ function readNormalizedConfigFromText(text) {
256
241
  const raw = parseConfigObjectFromText(text);
257
242
  if (!raw)
258
243
  return undefined;
259
- const migrated = maybeAutoMigrateLegacyStashes(configPath, raw);
260
- const expanded = expandEnvVars(migrated);
244
+ const expanded = expandEnvVars(raw);
261
245
  return pickKnownKeys(expanded);
262
246
  }
263
247
  function parseOutputConfig(value) {
@@ -337,31 +321,6 @@ function parseConfigObjectFromText(text) {
337
321
  return undefined;
338
322
  }
339
323
  }
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
324
  function writeConfigObject(configPath, config) {
366
325
  const tmpPath = `${configPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
367
326
  try {
@@ -443,7 +402,7 @@ function parseEmbeddingConfig(value) {
443
402
  return undefined;
444
403
  }
445
404
  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}"`);
405
+ warn(`[akm] Ignoring embedding config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
447
406
  // Still return localModel-only config if localModel was set
448
407
  if (localModel) {
449
408
  return { endpoint: "", model: "", localModel };
@@ -453,7 +412,7 @@ function parseEmbeddingConfig(value) {
453
412
  if (typeof obj.model !== "string" || !obj.model) {
454
413
  // No remote model, but localModel may still be valid
455
414
  if (localModel) {
456
- console.warn(`[akm] Embedding endpoint "${obj.endpoint}" ignored: model is required for remote embeddings. Using local model only.`);
415
+ warn(`[akm] Embedding endpoint "${obj.endpoint}" ignored: model is required for remote embeddings. Using local model only.`);
457
416
  return { endpoint: "", model: "", localModel };
458
417
  }
459
418
  return undefined;
@@ -480,6 +439,28 @@ function parseEmbeddingConfig(value) {
480
439
  if (localModel) {
481
440
  result.localModel = localModel;
482
441
  }
442
+ if ("contextLength" in obj) {
443
+ if (typeof obj.contextLength !== "number" ||
444
+ !Number.isFinite(obj.contextLength) ||
445
+ !Number.isInteger(obj.contextLength) ||
446
+ obj.contextLength <= 0) {
447
+ return undefined;
448
+ }
449
+ result.contextLength = obj.contextLength;
450
+ }
451
+ if (typeof obj.ollamaOptions === "object" && obj.ollamaOptions !== null && !Array.isArray(obj.ollamaOptions)) {
452
+ const opts = obj.ollamaOptions;
453
+ const parsed = {};
454
+ if (typeof opts.num_ctx === "number" &&
455
+ Number.isFinite(opts.num_ctx) &&
456
+ Number.isInteger(opts.num_ctx) &&
457
+ opts.num_ctx > 0) {
458
+ parsed.num_ctx = opts.num_ctx;
459
+ }
460
+ if (Object.keys(parsed).length > 0) {
461
+ result.ollamaOptions = parsed;
462
+ }
463
+ }
483
464
  return result;
484
465
  }
485
466
  function parseLlmConfig(value) {
@@ -489,7 +470,7 @@ function parseLlmConfig(value) {
489
470
  if (typeof obj.endpoint !== "string" || !obj.endpoint)
490
471
  return undefined;
491
472
  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}"`);
473
+ warn(`[akm] Ignoring llm config: endpoint must start with http:// or https://, got "${obj.endpoint}"`);
493
474
  return undefined;
494
475
  }
495
476
  const model = typeof obj.model === "string" ? obj.model : "";
@@ -515,26 +496,122 @@ function parseLlmConfig(value) {
515
496
  if (typeof obj.apiKey === "string" && obj.apiKey) {
516
497
  result.apiKey = obj.apiKey;
517
498
  }
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
499
  if (typeof obj.capabilities === "object" && obj.capabilities !== null && !Array.isArray(obj.capabilities)) {
525
500
  const capsRaw = obj.capabilities;
526
501
  const caps = {};
527
502
  if (typeof capsRaw.structuredOutput === "boolean")
528
503
  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
504
  if (Object.keys(caps).length > 0)
534
505
  result.capabilities = caps;
535
506
  }
507
+ if (typeof obj.features === "object" && obj.features !== null && !Array.isArray(obj.features)) {
508
+ const features = parseLlmFeatures(obj.features);
509
+ if (Object.keys(features).length > 0)
510
+ result.features = features;
511
+ }
536
512
  return result;
537
513
  }
514
+ /**
515
+ * v1 spec §14 — locked feature keys. Defined here so unknown keys can
516
+ * be warn-and-ignored at load time (per spec §14.3 / §9.2). The set is
517
+ * deliberately the *full* locked table even though only a subset has
518
+ * runtime parsing today; this lets users author future-flagged configs
519
+ * without spurious warnings.
520
+ */
521
+ const LOCKED_LLM_FEATURE_KEYS = new Set([
522
+ "curate_rerank",
523
+ "feedback_distillation",
524
+ "memory_inference",
525
+ "graph_extraction",
526
+ ]);
527
+ function parseLlmFeatures(raw) {
528
+ const out = {};
529
+ for (const [key, value] of Object.entries(raw)) {
530
+ if (!LOCKED_LLM_FEATURE_KEYS.has(key)) {
531
+ warn(`[akm] Ignoring unknown llm.features key "${key}".`);
532
+ continue;
533
+ }
534
+ if (typeof value !== "boolean") {
535
+ warn(`[akm] Ignoring llm.features.${key}: expected boolean, got ${typeof value}.`);
536
+ continue;
537
+ }
538
+ switch (key) {
539
+ case "memory_inference":
540
+ out.memory_inference = value;
541
+ break;
542
+ case "graph_extraction":
543
+ out.graph_extraction = value;
544
+ break;
545
+ case "curate_rerank":
546
+ out.curate_rerank = value;
547
+ break;
548
+ case "feedback_distillation":
549
+ out.feedback_distillation = value;
550
+ break;
551
+ // No default: LOCKED_LLM_FEATURE_KEYS is the source of truth for which
552
+ // keys are accepted. Adding a new locked key requires an arm here AND a
553
+ // field on LlmFeatureFlags above.
554
+ }
555
+ }
556
+ return out;
557
+ }
558
+ /**
559
+ * Keys that, if present anywhere under `index.<pass>`, indicate the user is
560
+ * trying to supply a parallel LLM provider configuration. Per #208 this is
561
+ * deliberately rejected at load time so there is exactly one place to
562
+ * configure the LLM (`akm.llm`).
563
+ */
564
+ const PROVIDER_CONFIG_KEYS = new Set([
565
+ "endpoint",
566
+ "model",
567
+ "provider",
568
+ "apiKey",
569
+ "baseUrl",
570
+ "temperature",
571
+ "maxTokens",
572
+ "capabilities",
573
+ ]);
574
+ /**
575
+ * Parse the `index` config block. Each entry is a pass name → small object
576
+ * `{ llm?: boolean }`. Anything richer (a parallel provider config, unknown
577
+ * keys, non-boolean `llm`) throws `ConfigError("INVALID_CONFIG_FILE")` at
578
+ * load time so the failure is visible at startup, not on the next index run.
579
+ */
580
+ function parseIndexConfig(value) {
581
+ if (value === undefined || value === null)
582
+ return undefined;
583
+ if (typeof value !== "object" || Array.isArray(value)) {
584
+ throw new ConfigError('Invalid `index` config: expected an object keyed by pass name (e.g. `{ "enrichment": { "llm": false } }`).', "INVALID_CONFIG_FILE");
585
+ }
586
+ const out = {};
587
+ for (const [passName, raw] of Object.entries(value)) {
588
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
589
+ throw new ConfigError(`Invalid \`index.${passName}\` config: expected an object like \`{ "llm": false }\`.`, "INVALID_CONFIG_FILE");
590
+ }
591
+ const passRaw = raw;
592
+ // Reject any provider-shaped key — there must be exactly one place to
593
+ // configure the LLM (#208). This is the duplicate-provider guard.
594
+ for (const key of Object.keys(passRaw)) {
595
+ if (PROVIDER_CONFIG_KEYS.has(key)) {
596
+ throw new ConfigError(`Duplicate LLM provider configuration: \`index.${passName}.${key}\` is not allowed. ` +
597
+ "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.');
598
+ }
599
+ if (key !== "llm") {
600
+ throw new ConfigError(`Unknown key \`index.${passName}.${key}\`. Per-pass entries only support \`llm\` (boolean opt-out).`, "INVALID_CONFIG_FILE");
601
+ }
602
+ }
603
+ const passConfig = {};
604
+ if ("llm" in passRaw) {
605
+ const llmFlag = passRaw.llm;
606
+ if (typeof llmFlag !== "boolean") {
607
+ 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.");
608
+ }
609
+ passConfig.llm = llmFlag;
610
+ }
611
+ out[passName] = passConfig;
612
+ }
613
+ return out;
614
+ }
538
615
  function parseInstalledEntries(value) {
539
616
  if (!Array.isArray(value))
540
617
  return undefined;
@@ -567,6 +644,9 @@ function parseInstalledStashEntry(value) {
567
644
  };
568
645
  if (typeof obj.writable === "boolean")
569
646
  entry.writable = obj.writable;
647
+ if (entry.writable === true && entry.source !== "git") {
648
+ throw new ConfigError(`writable: true is only supported on filesystem and git sources (got "${entry.source}" on installed entry "${entry.id}").`, "INVALID_CONFIG_FILE", "Remove `writable: true` from the installed entry or re-add it as a git source instead.");
649
+ }
570
650
  const resolvedVersion = asNonEmptyString(obj.resolvedVersion);
571
651
  if (resolvedVersion)
572
652
  entry.resolvedVersion = resolvedVersion;
@@ -604,7 +684,7 @@ function parseRegistriesConfig(value) {
604
684
  // which overrides the default. Only return undefined if the field was not an array.
605
685
  return entries;
606
686
  }
607
- function parseStashesConfig(value) {
687
+ function parseSourcesConfig(value) {
608
688
  if (!Array.isArray(value))
609
689
  return undefined;
610
690
  const entries = value
@@ -669,28 +749,17 @@ function parseInstallAuditAllowedFinding(value) {
669
749
  finding.reason = reason;
670
750
  return finding;
671
751
  }
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
752
  function parseSourceConfigEntry(value) {
683
753
  if (typeof value !== "object" || value === null || Array.isArray(value))
684
754
  return undefined;
685
755
  const obj = value;
686
- const rawType = asNonEmptyString(obj.type);
687
- if (!rawType)
756
+ const type = asNonEmptyString(obj.type);
757
+ if (!type)
688
758
  return undefined;
689
- if (rawType === "openviking") {
759
+ if (type === "openviking") {
690
760
  const name = asNonEmptyString(obj.name) ?? "unnamed";
691
761
  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
762
  }
693
- const type = STASH_TYPE_ALIASES[rawType] ?? rawType;
694
763
  const entry = { type };
695
764
  const entryPath = asNonEmptyString(obj.path);
696
765
  if (entryPath)
@@ -746,19 +815,15 @@ function deriveStashEntryName(entry) {
746
815
  * entry is missing the fields its provider type requires (e.g. a
747
816
  * `filesystem` entry with no `path`); callers should drop or warn for those.
748
817
  *
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.
818
+ * Unknown provider types fall back to `{ type: "filesystem", path: ... }` when
819
+ * a `path` is supplied, so future provider types still produce a usable
820
+ * runtime value.
752
821
  */
753
822
  export function parseSourceSpec(entry) {
754
823
  switch (entry.type) {
755
824
  case "filesystem":
756
825
  return entry.path ? { type: "filesystem", path: entry.path } : undefined;
757
826
  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
827
  return entry.url ? { type: "git", url: entry.url } : undefined;
763
828
  case "website":
764
829
  return entry.url
@@ -792,11 +857,9 @@ export function parseSourceSpec(entry) {
792
857
  */
793
858
  export function resolveConfiguredSources(config) {
794
859
  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 ?? [];
860
+ const sources = config.sources ?? [];
798
861
  // (1) Primary entry: explicit `primary: true` wins; fall back to top-level stashDir.
799
- let primary = stashes.find((entry) => entry.primary === true);
862
+ let primary = sources.find((entry) => entry.primary === true);
800
863
  if (!primary && config.stashDir) {
801
864
  primary = { type: "filesystem", path: config.stashDir, primary: true };
802
865
  }
@@ -805,8 +868,8 @@ export function resolveConfiguredSources(config) {
805
868
  if (runtime)
806
869
  entries.push(runtime);
807
870
  }
808
- // (2) Declared stashes (skip the primary entry — already added).
809
- for (const entry of stashes) {
871
+ // (2) Declared sources (skip the primary entry — already added).
872
+ for (const entry of sources) {
810
873
  if (entry === primary)
811
874
  continue;
812
875
  const runtime = toConfiguredSource(entry, false);
@@ -862,6 +925,20 @@ function parseRegistryConfigEntry(value) {
862
925
  }
863
926
  return entry;
864
927
  }
928
+ function mergeAgentConfig(base, override) {
929
+ const merged = { ...base, ...override };
930
+ const baseProfiles = base.profiles;
931
+ const overrideProfiles = override.profiles;
932
+ if (baseProfiles && overrideProfiles) {
933
+ const profiles = { ...baseProfiles };
934
+ for (const [name, entry] of Object.entries(overrideProfiles)) {
935
+ const existing = baseProfiles[name];
936
+ profiles[name] = existing ? { ...existing, ...entry } : entry;
937
+ }
938
+ merged.profiles = profiles;
939
+ }
940
+ return merged;
941
+ }
865
942
  function mergeSecurityConfig(base, override) {
866
943
  if (!base && !override)
867
944
  return undefined;
@@ -882,9 +959,8 @@ function mergeInstallAuditConfig(base, override) {
882
959
  *
883
960
  * Scalar fields follow normal override semantics. Known nested objects are
884
961
  * 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.
962
+ * clobbering sibling settings. `sources` are additive by default, but a later
963
+ * layer can set `stashInheritance: "replace"` to drop inherited sources first.
888
964
  */
889
965
  function mergeLoadedConfig(base, override) {
890
966
  if (!override)
@@ -902,18 +978,26 @@ function mergeLoadedConfig(base, override) {
902
978
  if (base.llm && override.llm) {
903
979
  merged.llm = { ...base.llm, ...override.llm };
904
980
  }
981
+ if (base.index || override.index) {
982
+ // Deep-merge per-pass entries so a project layer can opt one pass out
983
+ // without dropping siblings configured in user config.
984
+ const mergedIndex = { ...(base.index ?? {}) };
985
+ for (const [passName, passOverride] of Object.entries(override.index ?? {})) {
986
+ mergedIndex[passName] = { ...(mergedIndex[passName] ?? {}), ...passOverride };
987
+ }
988
+ if (Object.keys(mergedIndex).length > 0)
989
+ merged.index = mergedIndex;
990
+ }
905
991
  if (base.security && override.security) {
906
992
  merged.security = mergeSecurityConfig(base.security, override.security);
907
993
  }
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) {
994
+ if (base.agent && override.agent) {
995
+ merged.agent = mergeAgentConfig(base.agent, override.agent);
996
+ }
997
+ const replaceSources = override.stashInheritance === "replace";
998
+ const overrideSources = override.sources ?? [];
999
+ const baseSources = base.sources ?? [];
1000
+ if (replaceSources) {
917
1001
  merged.sources = [...overrideSources];
918
1002
  }
919
1003
  else if (overrideSources.length > 0) {
@@ -922,8 +1006,6 @@ function mergeLoadedConfig(base, override) {
922
1006
  else if (baseSources.length > 0) {
923
1007
  merged.sources = [...baseSources];
924
1008
  }
925
- // Clear deprecated stashes field on the merged result — sources is canonical.
926
- delete merged.stashes;
927
1009
  return merged;
928
1010
  }
929
1011
  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 {