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
@@ -182,10 +182,446 @@ export function formatPlain(command, result, detail) {
182
182
  const over = r.overwritten ? " (overwritten)" : "";
183
183
  return `Cloned${remote} → ${dst}${over}`;
184
184
  }
185
+ // Output shape registration for `akm history` — paired with the shape function in shapes.ts.
186
+ case "history": {
187
+ return formatHistoryPlain(r);
188
+ }
189
+ // Output shape registration for `akm events list` / `akm events tail`
190
+ // (#204). Both share a renderer; `events-tail` is also called per-event
191
+ // by the streaming code path via `formatEventLine`.
192
+ case "events-list":
193
+ case "events-tail": {
194
+ return formatEventsPlain(r);
195
+ }
196
+ // Output shape registration for `akm proposal *` (#225).
197
+ case "proposal-list": {
198
+ return formatProposalListPlain(r);
199
+ }
200
+ case "proposal-show": {
201
+ return formatProposalShowPlain(r);
202
+ }
203
+ case "proposal-accept": {
204
+ return formatProposalAcceptPlain(r);
205
+ }
206
+ case "proposal-reject": {
207
+ return formatProposalRejectPlain(r);
208
+ }
209
+ case "proposal-diff": {
210
+ return formatProposalDiffPlain(r);
211
+ }
212
+ // Output shape registration for `akm reflect` / `akm propose` (#226).
213
+ case "reflect":
214
+ case "propose": {
215
+ return formatProposalProducerPlain(command, r);
216
+ }
217
+ // Output shape registration for `akm distill <ref>` (#228). Three branches
218
+ // mirror the three terminal `outcome` values.
219
+ case "distill": {
220
+ return formatDistillPlain(r);
221
+ }
222
+ case "info":
223
+ return formatInfoPlain(r);
224
+ case "config":
225
+ return formatConfigPlain(r);
226
+ case "feedback":
227
+ return formatFeedbackPlain(r);
228
+ case "remember":
229
+ return formatRememberPlain(r);
230
+ case "import":
231
+ return formatImportPlain(r);
232
+ case "save":
233
+ return formatSavePlain(r);
234
+ case "enable":
235
+ case "disable":
236
+ return formatToggleComponentPlain(command, r);
237
+ case "registry-list":
238
+ return formatRegistryListPlain(r);
239
+ case "registry-add":
240
+ return formatRegistryAddPlain(r);
241
+ case "registry-remove":
242
+ return formatRegistryRemovePlain(r);
243
+ case "registry-search":
244
+ return formatRegistrySearchPlain(r, detail);
245
+ case "registry-build-index":
246
+ return formatRegistryBuildIndexPlain(r);
247
+ case "vault-list":
248
+ return formatVaultListPlain(r);
249
+ case "vault-create":
250
+ return `Created vault ${String(r.ref ?? "?")} at ${String(r.path ?? "?")}`;
251
+ case "vault-set":
252
+ return `Set ${String(r.key ?? "?")} in ${String(r.ref ?? "?")} (value not displayed)`;
253
+ case "vault-unset": {
254
+ const removed = r.removed === true;
255
+ const head = removed
256
+ ? `Removed ${String(r.key ?? "?")} from ${String(r.ref ?? "?")}`
257
+ : `Key ${String(r.key ?? "?")} was not present in ${String(r.ref ?? "?")}`;
258
+ return head;
259
+ }
260
+ case "wiki-register":
261
+ return formatWikiRegisterPlain(r);
262
+ case "workflow-resume":
263
+ return formatWorkflowStatusPlain(r) ?? `Resumed workflow run ${String(r.id ?? r.runId ?? "?")}`;
264
+ case "workflow-validate":
265
+ return formatWorkflowValidatePlain(r);
185
266
  default:
186
267
  return null; // fall through to YAML
187
268
  }
188
269
  }
270
+ export function formatInfoPlain(r) {
271
+ const lines = [];
272
+ if (r.version)
273
+ lines.push(`version: ${String(r.version)}`);
274
+ if (r.stashDir)
275
+ lines.push(`stashDir: ${String(r.stashDir)}`);
276
+ if (r.configPath)
277
+ lines.push(`configPath: ${String(r.configPath)}`);
278
+ if (r.cacheDir)
279
+ lines.push(`cacheDir: ${String(r.cacheDir)}`);
280
+ if (r.dbPath)
281
+ lines.push(`dbPath: ${String(r.dbPath)}`);
282
+ const capabilities = r.capabilities;
283
+ if (capabilities) {
284
+ lines.push("capabilities:");
285
+ for (const [k, v] of Object.entries(capabilities)) {
286
+ lines.push(` ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
287
+ }
288
+ }
289
+ const indexStats = r.index;
290
+ if (indexStats) {
291
+ lines.push("index:");
292
+ for (const [k, v] of Object.entries(indexStats)) {
293
+ lines.push(` ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
294
+ }
295
+ }
296
+ if (lines.length === 0)
297
+ return JSON.stringify(r, null, 2);
298
+ return lines.join("\n");
299
+ }
300
+ export function formatConfigPlain(r) {
301
+ // Recursive flattener: prints `key=value` lines, and nested objects as
302
+ // `parent.child=value`. Arrays render as JSON for compactness.
303
+ const lines = [];
304
+ const walk = (obj, prefix) => {
305
+ for (const [k, v] of Object.entries(obj)) {
306
+ const path = prefix ? `${prefix}.${k}` : k;
307
+ if (v === null || v === undefined) {
308
+ lines.push(`${path}=`);
309
+ }
310
+ else if (Array.isArray(v)) {
311
+ lines.push(`${path}=${JSON.stringify(v)}`);
312
+ }
313
+ else if (typeof v === "object") {
314
+ walk(v, path);
315
+ }
316
+ else {
317
+ lines.push(`${path}=${String(v)}`);
318
+ }
319
+ }
320
+ };
321
+ walk(r, "");
322
+ if (lines.length === 0)
323
+ return "(empty config)";
324
+ return lines.join("\n");
325
+ }
326
+ export function formatFeedbackPlain(r) {
327
+ const ref = String(r.ref ?? "?");
328
+ const signal = String(r.signal ?? "?");
329
+ const note = typeof r.note === "string" && r.note ? ` — ${r.note}` : "";
330
+ return `Recorded ${signal} feedback for ${ref}${note}`;
331
+ }
332
+ export function formatRememberPlain(r) {
333
+ const ref = String(r.ref ?? "?");
334
+ const pathValue = String(r.path ?? "?");
335
+ return `Saved ${ref} at ${pathValue}`;
336
+ }
337
+ export function formatImportPlain(r) {
338
+ const ref = String(r.ref ?? "?");
339
+ const source = String(r.source ?? "?");
340
+ const pathValue = String(r.path ?? "?");
341
+ return `Imported ${source} → ${ref} at ${pathValue}`;
342
+ }
343
+ export function formatSavePlain(r) {
344
+ if (r.ok === false) {
345
+ const reason = typeof r.reason === "string" ? r.reason : "unknown";
346
+ return `save: failed (${reason})`;
347
+ }
348
+ const name = typeof r.name === "string" ? r.name : "primary stash";
349
+ const committed = r.committed === true;
350
+ const pushed = r.pushed === true;
351
+ const parts = [`save: ${name}`];
352
+ parts.push(committed ? "committed" : "no changes");
353
+ if (pushed)
354
+ parts.push("pushed");
355
+ return parts.join(" — ");
356
+ }
357
+ export function formatToggleComponentPlain(command, r) {
358
+ const verb = command === "enable" ? "Enabled" : "Disabled";
359
+ const component = String(r.component ?? "?");
360
+ const changed = r.changed === true;
361
+ return changed ? `${verb} ${component}` : `${component} was already ${command}d`;
362
+ }
363
+ export function formatRegistryListPlain(r) {
364
+ const registries = Array.isArray(r.registries) ? r.registries : [];
365
+ if (registries.length === 0) {
366
+ return "No registries configured. Add one with `akm registry add <url>`.";
367
+ }
368
+ const lines = [];
369
+ for (const reg of registries) {
370
+ const url = String(reg.url ?? "?");
371
+ const name = typeof reg.name === "string" ? reg.name : "";
372
+ const provider = typeof reg.provider === "string" ? ` (${reg.provider})` : "";
373
+ const enabled = reg.enabled === false ? " [disabled]" : "";
374
+ const head = name ? `${name}: ${url}` : url;
375
+ lines.push(`${head}${provider}${enabled}`);
376
+ }
377
+ return lines.join("\n");
378
+ }
379
+ export function formatRegistryAddPlain(r) {
380
+ if (r.added === false) {
381
+ return typeof r.message === "string" ? r.message : "Registry already configured.";
382
+ }
383
+ const registries = Array.isArray(r.registries) ? r.registries.length : 0;
384
+ return `Registry added (${registries} total).`;
385
+ }
386
+ export function formatRegistryRemovePlain(r) {
387
+ if (r.removed === false) {
388
+ return typeof r.message === "string" ? r.message : "No matching registry found.";
389
+ }
390
+ const entry = r.entry;
391
+ const url = entry ? String(entry.url ?? entry.name ?? "?") : "?";
392
+ return `Removed registry ${url}`;
393
+ }
394
+ export function formatRegistrySearchPlain(r, detail) {
395
+ // Reuse the same renderer as `search` — both share `hits` / `registryHits`.
396
+ return formatSearchPlain(r, detail);
397
+ }
398
+ export function formatRegistryBuildIndexPlain(r) {
399
+ const outPath = String(r.outPath ?? "?");
400
+ const total = typeof r.totalKits === "number" ? r.totalKits : 0;
401
+ const version = typeof r.version === "number" ? `v${r.version}` : "";
402
+ return `Wrote registry index ${version} (${total} kits) → ${outPath}`.replace(/\s+/g, " ").trim();
403
+ }
404
+ export function formatVaultListPlain(r) {
405
+ // Single-vault listing: { ref, path, entries: [{ key, comment? }, ...] }
406
+ if (typeof r.ref === "string" && Array.isArray(r.entries)) {
407
+ const ref = r.ref;
408
+ const entries = r.entries;
409
+ if (entries.length === 0) {
410
+ return `No keys in ${ref}. Set one with \`akm vault set ${ref} KEY=VALUE\`.`;
411
+ }
412
+ const lines = [ref];
413
+ for (const e of entries) {
414
+ const key = String(e.key ?? "?");
415
+ const comment = typeof e.comment === "string" && e.comment ? ` # ${e.comment}` : "";
416
+ lines.push(` ${key}${comment}`);
417
+ }
418
+ return lines.join("\n");
419
+ }
420
+ // Multi-vault listing: { vaults: [{ ref, path, keyCount }, ...] }
421
+ const vaults = Array.isArray(r.vaults) ? r.vaults : [];
422
+ if (vaults.length === 0) {
423
+ return "No vaults. Create one with `akm vault create <name>` then `akm vault set vault:<name> KEY=VALUE`.";
424
+ }
425
+ const lines = [];
426
+ for (const v of vaults) {
427
+ const ref = String(v.ref ?? "?");
428
+ const keyCount = typeof v.keyCount === "number" ? v.keyCount : 0;
429
+ lines.push(`${ref}\t${keyCount} key(s)`);
430
+ }
431
+ return lines.join("\n");
432
+ }
433
+ export function formatWikiRegisterPlain(r) {
434
+ const name = String(r.name ?? r.wiki ?? "?");
435
+ const ref = String(r.ref ?? r.path ?? r.url ?? "?");
436
+ return `Registered wiki ${name} → ${ref}`;
437
+ }
438
+ export function formatWorkflowValidatePlain(r) {
439
+ const ok = r.ok !== false;
440
+ const pathValue = String(r.path ?? "?");
441
+ if (!ok)
442
+ return `workflow validate: failed (${pathValue})`;
443
+ const title = typeof r.title === "string" ? r.title : "";
444
+ const stepCount = typeof r.stepCount === "number" ? r.stepCount : 0;
445
+ return `workflow validate: ok — ${title || pathValue} (${stepCount} step(s))`;
446
+ }
447
+ export function formatProposalProducerPlain(command, r) {
448
+ if (r.ok === false) {
449
+ const reason = String(r.reason ?? "unknown");
450
+ const error = typeof r.error === "string" ? r.error : "";
451
+ const lines = [`${command}: failed (${reason})`];
452
+ if (error)
453
+ lines.push(` error: ${error}`);
454
+ if (r.ref)
455
+ lines.push(` ref: ${String(r.ref)}`);
456
+ if (r.exitCode !== undefined && r.exitCode !== null) {
457
+ lines.push(` exitCode: ${String(r.exitCode)}`);
458
+ }
459
+ return lines.join("\n");
460
+ }
461
+ const proposal = r.proposal ?? {};
462
+ const id = String(proposal.id ?? "?");
463
+ const ref = String(r.ref ?? proposal.ref ?? "?");
464
+ const status = String(proposal.status ?? "pending");
465
+ return `${command}: queued proposal ${id} (${ref}) [${status}]`;
466
+ }
467
+ export function formatProposalListPlain(r) {
468
+ const proposals = Array.isArray(r.proposals) ? r.proposals : [];
469
+ const total = typeof r.totalCount === "number" ? r.totalCount : proposals.length;
470
+ if (proposals.length === 0) {
471
+ return `${total} proposal(s).\nNo proposals.\nGenerate one with \`akm reflect <ref>\`, \`akm propose <type> <name> --task ...\`, or \`akm distill <ref>\`.`;
472
+ }
473
+ const lines = [`${total} proposal(s)`, ""];
474
+ for (const p of proposals) {
475
+ const id = String(p.id ?? "?");
476
+ const ref = String(p.ref ?? "?");
477
+ const status = String(p.status ?? "?");
478
+ const source = String(p.source ?? "?");
479
+ const created = String(p.createdAt ?? "?");
480
+ lines.push(`${id} [${status}] ${ref} source=${source} ${created}`);
481
+ }
482
+ return lines.join("\n").trimEnd();
483
+ }
484
+ export function formatProposalShowPlain(r) {
485
+ const p = r.proposal ?? {};
486
+ const lines = [];
487
+ lines.push(`# proposal ${String(p.id ?? "?")}`);
488
+ lines.push(`ref: ${String(p.ref ?? "?")}`);
489
+ lines.push(`status: ${String(p.status ?? "?")}`);
490
+ lines.push(`source: ${String(p.source ?? "?")}`);
491
+ if (p.sourceRun)
492
+ lines.push(`sourceRun: ${String(p.sourceRun)}`);
493
+ if (p.createdAt)
494
+ lines.push(`createdAt: ${String(p.createdAt)}`);
495
+ if (p.updatedAt)
496
+ lines.push(`updatedAt: ${String(p.updatedAt)}`);
497
+ const review = p.review;
498
+ if (review) {
499
+ lines.push(`review.outcome: ${String(review.outcome ?? "?")}`);
500
+ if (review.reason)
501
+ lines.push(`review.reason: ${String(review.reason)}`);
502
+ if (review.decidedAt)
503
+ lines.push(`review.decidedAt: ${String(review.decidedAt)}`);
504
+ }
505
+ const validation = r.validation;
506
+ if (validation) {
507
+ const ok = validation.ok === true;
508
+ const findings = Array.isArray(validation.findings) ? validation.findings : [];
509
+ lines.push("");
510
+ lines.push(`validation: ${ok ? "ok" : `${findings.length} finding(s)`}`);
511
+ for (const f of findings) {
512
+ lines.push(` - [${String(f.kind)}] ${String(f.message)}`);
513
+ }
514
+ }
515
+ const payload = p.payload;
516
+ if (payload && typeof payload.content === "string") {
517
+ lines.push("");
518
+ lines.push("payload:");
519
+ lines.push(payload.content);
520
+ }
521
+ return lines.join("\n").trimEnd();
522
+ }
523
+ export function formatProposalAcceptPlain(r) {
524
+ return `Accepted proposal ${String(r.id ?? "?")} → ${String(r.ref ?? "?")} at ${String(r.assetPath ?? "?")}`;
525
+ }
526
+ export function formatProposalRejectPlain(r) {
527
+ const reason = r.reason ? ` (${String(r.reason)})` : "";
528
+ return `Rejected proposal ${String(r.id ?? "?")} (${String(r.ref ?? "?")})${reason}`;
529
+ }
530
+ export function formatDistillPlain(r) {
531
+ const outcome = String(r.outcome ?? "unknown");
532
+ const inputRef = String(r.inputRef ?? "?");
533
+ const lessonRef = String(r.lessonRef ?? "?");
534
+ if (outcome === "queued") {
535
+ const id = String(r.proposalId ?? "?");
536
+ return `Distilled ${inputRef} → proposal ${id} (${lessonRef}). Run \`akm proposal show ${id}\` to review.`;
537
+ }
538
+ if (outcome === "validation_failed") {
539
+ const findings = Array.isArray(r.findings) ? r.findings : [];
540
+ const lines = [`Distillation produced an invalid lesson for ${inputRef}; no proposal queued.`];
541
+ for (const f of findings) {
542
+ lines.push(` - ${String(f.message ?? f.kind ?? "validation finding")}`);
543
+ }
544
+ return lines.join("\n");
545
+ }
546
+ // skipped
547
+ const message = typeof r.message === "string" ? r.message : "feature disabled or LLM unavailable";
548
+ return `Distill skipped for ${inputRef}: ${message}`;
549
+ }
550
+ export function formatProposalDiffPlain(r) {
551
+ const header = r.isNew
552
+ ? `# proposal ${String(r.id ?? "?")} (new asset: ${String(r.ref ?? "?")})`
553
+ : `# proposal ${String(r.id ?? "?")} (update: ${String(r.ref ?? "?")})`;
554
+ const unified = typeof r.unified === "string" ? r.unified : "";
555
+ if (!unified)
556
+ return `${header}\n(no changes)`;
557
+ return `${header}\n${unified}`;
558
+ }
559
+ export function formatEventsPlain(r) {
560
+ const events = Array.isArray(r.events) ? r.events : [];
561
+ const headerParts = [];
562
+ if (typeof r.ref === "string" && r.ref)
563
+ headerParts.push(`ref: ${r.ref}`);
564
+ if (typeof r.type === "string" && r.type)
565
+ headerParts.push(`type: ${r.type}`);
566
+ if (typeof r.since === "string" && r.since)
567
+ headerParts.push(`since: ${r.since}`);
568
+ const totalCount = typeof r.totalCount === "number" ? r.totalCount : events.length;
569
+ headerParts.push(`${totalCount} event(s)`);
570
+ const header = headerParts.join(" ");
571
+ if (events.length === 0) {
572
+ return `${header}\nNo events.`;
573
+ }
574
+ const lines = [header, ""];
575
+ for (const event of events) {
576
+ lines.push(formatEventLine(event));
577
+ }
578
+ return lines.join("\n").trimEnd();
579
+ }
580
+ export function formatEventLine(event) {
581
+ const ts = String(event.ts ?? "?");
582
+ const eventType = String(event.eventType ?? "?");
583
+ const ref = event.ref ? String(event.ref) : null;
584
+ const head = ref ? `${ts} [${eventType}] ${ref}` : `${ts} [${eventType}]`;
585
+ if (event.metadata != null && event.metadata !== "") {
586
+ const meta = typeof event.metadata === "string" ? event.metadata : JSON.stringify(event.metadata);
587
+ return `${head}\n metadata: ${meta}`;
588
+ }
589
+ return head;
590
+ }
591
+ export function formatHistoryPlain(r) {
592
+ const entries = Array.isArray(r.entries) ? r.entries : [];
593
+ const headerParts = [];
594
+ if (typeof r.ref === "string" && r.ref)
595
+ headerParts.push(`ref: ${r.ref}`);
596
+ if (typeof r.since === "string" && r.since)
597
+ headerParts.push(`since: ${r.since}`);
598
+ const totalCount = typeof r.totalCount === "number" ? r.totalCount : entries.length;
599
+ headerParts.push(`${totalCount} event(s)`);
600
+ const header = headerParts.join(" ");
601
+ if (entries.length === 0) {
602
+ const scope = typeof r.ref === "string" && r.ref ? ` for ${r.ref}` : "";
603
+ return `${header}\nNo history${scope}.`;
604
+ }
605
+ const lines = [header, ""];
606
+ for (const entry of entries) {
607
+ const created = String(entry.createdAt ?? "?");
608
+ const eventType = String(entry.eventType ?? "?");
609
+ const ref = entry.ref ? String(entry.ref) : null;
610
+ const signal = entry.signal ? String(entry.signal) : null;
611
+ const query = entry.query ? String(entry.query) : null;
612
+ const head = ref ? `${created} [${eventType}] ${ref}` : `${created} [${eventType}]`;
613
+ lines.push(head);
614
+ if (signal)
615
+ lines.push(` signal: ${signal}`);
616
+ if (query)
617
+ lines.push(` query: ${query}`);
618
+ if (entry.metadata != null && entry.metadata !== "") {
619
+ const meta = typeof entry.metadata === "string" ? entry.metadata : JSON.stringify(entry.metadata);
620
+ lines.push(` metadata: ${meta}`);
621
+ }
622
+ }
623
+ return lines.join("\n").trimEnd();
624
+ }
189
625
  function formatShowPlain(r, detail) {
190
626
  const lines = [];
191
627
  if (r.type || r.name) {
@@ -260,8 +696,9 @@ function formatShowPlain(r, detail) {
260
696
  }
261
697
  export function formatWorkflowListPlain(result) {
262
698
  const runs = Array.isArray(result.runs) ? result.runs : [];
263
- if (runs.length === 0)
264
- return "No workflow runs found.";
699
+ if (runs.length === 0) {
700
+ return "No workflow runs. Start one with `akm workflow next workflow:<name>` or author one with `akm workflow create <name>`.";
701
+ }
265
702
  return runs
266
703
  .map((run) => {
267
704
  const id = typeof run.id === "string" ? run.id : "unknown";
@@ -351,8 +788,14 @@ export function formatSearchPlain(r, detail) {
351
788
  lines.push(` run: ${String(hit.run)}`);
352
789
  if (Array.isArray(hit.tags) && hit.tags.length > 0)
353
790
  lines.push(` tags: ${hit.tags.join(", ")}`);
354
- if (hit.curated !== undefined)
355
- lines.push(` curated: ${String(hit.curated)}`);
791
+ // Optional v1 spec §4.2 quality marker (e.g. "curated" / "proposed").
792
+ if (typeof hit.quality === "string" && hit.quality)
793
+ lines.push(` quality: ${hit.quality}`);
794
+ // Surface optional hit-level warnings (v1 spec §4.2). The legacy
795
+ // `curated` boolean was removed in v1.
796
+ if (Array.isArray(hit.warnings) && hit.warnings.length > 0) {
797
+ lines.push(` warnings: ${hit.warnings.join("; ")}`);
798
+ }
356
799
  if (detail === "full") {
357
800
  if (hit.path)
358
801
  lines.push(` path: ${String(hit.path)}`);
@@ -8,9 +8,9 @@
8
8
  * updated together with this builder.
9
9
  */
10
10
  import fs from "node:fs";
11
- import os from "node:os";
12
11
  import path from "node:path";
13
12
  import { fetchWithRetry, jsonWithByteCap } from "../core/common";
13
+ import { getCacheDir } from "../core/paths";
14
14
  import { generateMetadataFlat, loadStashFile } from "../indexer/metadata";
15
15
  import { walkStashFlat } from "../indexer/walker";
16
16
  import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
@@ -173,7 +173,16 @@ async function scanGithub(githubApiBase) {
173
173
  return stashes;
174
174
  }
175
175
  async function inspectArchive(url, headers) {
176
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-registry-build-"));
176
+ // Route registry-build scratch space under the akm cache dir instead of
177
+ // the system temp directory. Long-running registry builds that crash
178
+ // mid-flight previously left orphan dirs under `/tmp`, which can fill
179
+ // the OS partition. Pinning to `${getCacheDir()}/registry-build/` keeps
180
+ // a single `rm -rf` of that directory sufficient to reclaim the space
181
+ // and leaves the operator's `/tmp` untouched. See #276 for the
182
+ // bench-tmp precedent and #284 for this non-bench rollout.
183
+ const tmpRoot = path.join(getCacheDir(), "registry-build");
184
+ fs.mkdirSync(tmpRoot, { recursive: true });
185
+ const tempDir = fs.mkdtempSync(path.join(tmpRoot, "build-"));
177
186
  const archivePath = path.join(tempDir, "archive.tgz");
178
187
  const extractDir = path.join(tempDir, "extract");
179
188
  try {
@@ -283,7 +292,9 @@ async function loadManualEntries(manualEntriesPath) {
283
292
  const parsed = parseRegistryIndex({ version: 3, updatedAt: new Date().toISOString(), stashes: candidateKits });
284
293
  if (!parsed)
285
294
  return [];
286
- return parsed.stashes.map((stash) => normalizeStash({ ...stash, curated: stash.curated ?? true }));
295
+ // Legacy `curated` flag on input entries is ignored (v1 spec §4.2). The
296
+ // builder no longer emits it on the resulting index.
297
+ return parsed.stashes.map((stash) => normalizeStash(stash));
287
298
  }
288
299
  catch {
289
300
  return [];
@@ -327,7 +338,6 @@ function mergeEntries(a, b) {
327
338
  author: a.author ?? b.author,
328
339
  license: a.license ?? b.license,
329
340
  latestVersion: a.latestVersion ?? b.latestVersion,
330
- curated: a.curated || b.curated || undefined,
331
341
  });
332
342
  }
333
343
  function mergeAssets(a, b) {
@@ -23,11 +23,3 @@ export function registerProvider(type, factory) {
23
23
  export function resolveProviderFactory(type) {
24
24
  return registry.resolve(type);
25
25
  }
26
- /**
27
- * Iterate over all registered registry providers. Used by the orchestrator
28
- * (`src/commands/registry-search.ts`) to fan out queries through the same
29
- * `RegistryProvider` interface regardless of provider kind.
30
- */
31
- export function listProviderTypes() {
32
- return registry.list();
33
- }
@@ -213,6 +213,9 @@ function parseStashEntry(raw) {
213
213
  const source = asSource(obj.source);
214
214
  if (!id || !name || !ref || !source)
215
215
  return null;
216
+ // The legacy registry boolean `curated` is removed in v1. Legacy index JSON
217
+ // containing a `curated` key parses normally; the key is silently ignored
218
+ // (v1 spec §4.2, docs/migration/v1.md).
216
219
  return {
217
220
  id,
218
221
  name,
@@ -226,7 +229,6 @@ function parseStashEntry(raw) {
226
229
  author: asString(obj.author),
227
230
  license: asString(obj.license),
228
231
  latestVersion: asString(obj.latestVersion),
229
- curated: obj.curated === true ? true : undefined,
230
232
  };
231
233
  }
232
234
  // ── Scoring ─────────────────────────────────────────────────────────────────
@@ -294,7 +296,6 @@ function toSearchHit(stash, score, registryName) {
294
296
  homepage: stash.homepage,
295
297
  score: Math.round(score * 1000) / 1000,
296
298
  metadata,
297
- curated: stash.curated,
298
299
  registryName,
299
300
  };
300
301
  }
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { fetchWithRetry, jsonWithByteCap } from "../core/common";
7
- import { UsageError } from "../core/errors";
7
+ import { NotFoundError, UsageError } from "../core/errors";
8
8
  import { asRecord, asString, GITHUB_API_BASE, githubHeaders } from "../integrations/github";
9
9
  /**
10
10
  * Validate that a URL is safe to pass to git.
@@ -200,7 +200,7 @@ function tryParseLocalRef(rawRef, explicitPath) {
200
200
  catch {
201
201
  // Explicit paths (./foo, ../bar, /abs) should throw on missing
202
202
  if (explicitPath) {
203
- throw new Error(`Local path not found: ${resolvedPath}`);
203
+ throw new NotFoundError(`Local path not found: ${resolvedPath}`, "FILE_NOT_FOUND", "Check the path exists and is readable.");
204
204
  }
205
205
  // Bare names that don't exist on disk — let caller fall through to npm/github
206
206
  return undefined;
@@ -231,6 +231,71 @@ function isPathLikeRef(ref) {
231
231
  }
232
232
  return ref.includes("/") || ref.includes("\\");
233
233
  }
234
+ /** Default public npm registry host. */
235
+ const DEFAULT_NPM_REGISTRY_HOST = "registry.npmjs.org";
236
+ /**
237
+ * Typed error raised when the npm registry returns a tarball URL on a host
238
+ * that is not the public registry or the operator-configured mirror. Carries
239
+ * a stable `.code` so callers (and JSON envelope output) can branch on it
240
+ * without parsing the message string.
241
+ */
242
+ export class UntrustedNpmTarballError extends Error {
243
+ code = "UNTRUSTED_NPM_TARBALL";
244
+ _hint;
245
+ constructor(msg, hint) {
246
+ super(msg);
247
+ this.name = "UntrustedNpmTarballError";
248
+ this._hint = hint;
249
+ Object.setPrototypeOf(this, new.target.prototype);
250
+ }
251
+ hint() {
252
+ return (this._hint ??
253
+ "Set AKM_NPM_REGISTRY to your private npm mirror's base URL if you install from a non-default registry.");
254
+ }
255
+ }
256
+ /**
257
+ * Resolve the set of npm registry hosts whose tarballs are considered trusted.
258
+ * Always includes the public npm registry, plus the host of an operator-set
259
+ * `AKM_NPM_REGISTRY` environment variable (if it parses to a valid URL).
260
+ */
261
+ export function trustedNpmTarballHosts() {
262
+ const hosts = new Set([DEFAULT_NPM_REGISTRY_HOST]);
263
+ const override = process.env.AKM_NPM_REGISTRY?.trim();
264
+ if (override) {
265
+ try {
266
+ const overrideHost = new URL(override).hostname.toLowerCase();
267
+ if (overrideHost)
268
+ hosts.add(overrideHost);
269
+ }
270
+ catch {
271
+ // Ignore unparseable overrides; the default host still applies.
272
+ }
273
+ }
274
+ return hosts;
275
+ }
276
+ /**
277
+ * Validate that an npm tarball URL points at a trusted registry host. A
278
+ * compromised mirror could otherwise return an attacker-controlled
279
+ * `dist.tarball` field, redirecting installs to a third-party host.
280
+ */
281
+ export function validateNpmTarballUrl(tarballUrl, packageRef) {
282
+ let url;
283
+ try {
284
+ url = new URL(tarballUrl);
285
+ }
286
+ catch {
287
+ throw new UntrustedNpmTarballError(`npm package ${packageRef} returned an invalid tarball URL: ${tarballUrl}`);
288
+ }
289
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
290
+ throw new UntrustedNpmTarballError(`npm package ${packageRef} returned a tarball with disallowed scheme "${url.protocol}".`);
291
+ }
292
+ const trusted = trustedNpmTarballHosts();
293
+ const host = url.hostname.toLowerCase();
294
+ if (!trusted.has(host)) {
295
+ const allowed = Array.from(trusted).join(", ");
296
+ throw new UntrustedNpmTarballError(`npm package ${packageRef} returned a tarball URL on untrusted host "${host}" (allowed: ${allowed}).`);
297
+ }
298
+ }
234
299
  async function resolveNpmArtifact(parsed) {
235
300
  const encodedName = encodeURIComponent(parsed.packageName);
236
301
  const metadata = await fetchJson(`https://registry.npmjs.org/${encodedName}`);
@@ -262,6 +327,7 @@ async function resolveNpmArtifact(parsed) {
262
327
  if (!tarballUrl) {
263
328
  throw new Error(`npm package ${parsed.packageName}@${resolvedVersion} does not expose a tarball URL.`);
264
329
  }
330
+ validateNpmTarballUrl(tarballUrl, `${parsed.packageName}@${resolvedVersion}`);
265
331
  const resolvedRevision = asString(dist.shasum) ?? asString(dist.integrity);
266
332
  return {
267
333
  id: parsed.id,