akm-cli 0.6.1 → 0.7.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +620 -26
  3. package/dist/{commands → src/commands}/config-cli.js +5 -4
  4. package/dist/src/commands/distill.js +283 -0
  5. package/dist/src/commands/events.js +108 -0
  6. package/dist/src/commands/history.js +120 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +1 -1
  8. package/dist/src/commands/proposal.js +119 -0
  9. package/dist/src/commands/propose.js +171 -0
  10. package/dist/src/commands/reflect.js +193 -0
  11. package/dist/{commands → src/commands}/registry-search.js +2 -1
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +74 -1
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +44 -0
  16. package/dist/{core → src/core}/asset-ref.js +5 -5
  17. package/dist/{core → src/core}/asset-spec.js +12 -0
  18. package/dist/{core → src/core}/common.js +1 -1
  19. package/dist/{core → src/core}/config.js +175 -121
  20. package/dist/{core → src/core}/errors.js +4 -0
  21. package/dist/src/core/events.js +239 -0
  22. package/dist/src/core/lesson-lint.js +86 -0
  23. package/dist/src/core/proposals.js +406 -0
  24. package/dist/src/core/warn.js +72 -0
  25. package/dist/{core → src/core}/write-source.js +80 -5
  26. package/dist/{indexer → src/indexer}/db-search.js +113 -24
  27. package/dist/{indexer → src/indexer}/db.js +76 -23
  28. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  29. package/dist/src/indexer/graph-boost.js +179 -0
  30. package/dist/src/indexer/graph-extraction.js +212 -0
  31. package/dist/{indexer → src/indexer}/indexer.js +73 -6
  32. package/dist/src/indexer/memory-inference.js +263 -0
  33. package/dist/{indexer → src/indexer}/metadata.js +111 -3
  34. package/dist/src/integrations/agent/config.js +292 -0
  35. package/dist/src/integrations/agent/detect.js +94 -0
  36. package/dist/src/integrations/agent/index.js +17 -0
  37. package/dist/src/integrations/agent/profiles.js +65 -0
  38. package/dist/src/integrations/agent/prompts.js +167 -0
  39. package/dist/src/integrations/agent/spawn.js +221 -0
  40. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  41. package/dist/{llm → src/llm}/client.js +33 -2
  42. package/dist/src/llm/feature-gate.js +108 -0
  43. package/dist/src/llm/graph-extract.js +107 -0
  44. package/dist/src/llm/index-passes.js +35 -0
  45. package/dist/src/llm/memory-infer.js +86 -0
  46. package/dist/{output → src/output}/renderers.js +60 -1
  47. package/dist/src/output/shapes.js +516 -0
  48. package/dist/{output → src/output}/text.js +447 -4
  49. package/dist/{registry → src/registry}/build-index.js +14 -4
  50. package/dist/{registry → src/registry}/factory.js +0 -8
  51. package/dist/{registry → src/registry}/providers/static-index.js +3 -2
  52. package/dist/{registry → src/registry}/resolve.js +68 -2
  53. package/dist/{setup → src/setup}/setup.js +43 -5
  54. package/dist/{sources → src/sources}/providers/git.js +7 -15
  55. package/dist/tests/add-website-source.test.js +119 -0
  56. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  57. package/dist/tests/agent/agent-config.test.js +221 -0
  58. package/dist/tests/agent/agent-detect.test.js +100 -0
  59. package/dist/tests/agent/agent-spawn.test.js +234 -0
  60. package/dist/tests/agent-output.test.js +186 -0
  61. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  62. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  63. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  64. package/dist/tests/asset-ref.test.js +192 -0
  65. package/dist/tests/asset-registry.test.js +103 -0
  66. package/dist/tests/asset-spec.test.js +241 -0
  67. package/dist/tests/bench/attribution.test.js +995 -0
  68. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  69. package/dist/tests/bench/cleanup.js +203 -0
  70. package/dist/tests/bench/cleanup.test.js +166 -0
  71. package/dist/tests/bench/cli.js +683 -0
  72. package/dist/tests/bench/cli.test.js +177 -0
  73. package/dist/tests/bench/compare.test.js +556 -0
  74. package/dist/tests/bench/corpus.js +314 -0
  75. package/dist/tests/bench/corpus.test.js +258 -0
  76. package/dist/tests/bench/driver.js +346 -0
  77. package/dist/tests/bench/driver.test.js +443 -0
  78. package/dist/tests/bench/evolve-metrics.js +179 -0
  79. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  80. package/dist/tests/bench/evolve.js +580 -0
  81. package/dist/tests/bench/evolve.test.js +616 -0
  82. package/dist/tests/bench/failure-modes.test.js +300 -0
  83. package/dist/tests/bench/feedback-integrity.test.js +456 -0
  84. package/dist/tests/bench/leakage.test.js +125 -0
  85. package/dist/tests/bench/learning-curve.test.js +133 -0
  86. package/dist/tests/bench/metrics.js +2319 -0
  87. package/dist/tests/bench/metrics.test.js +1144 -0
  88. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  89. package/dist/tests/bench/report.js +1821 -0
  90. package/dist/tests/bench/report.test.js +989 -0
  91. package/dist/tests/bench/runner.js +536 -0
  92. package/dist/tests/bench/runner.test.js +958 -0
  93. package/dist/tests/bench/search-bridge.test.js +331 -0
  94. package/dist/tests/bench/tmp.js +41 -0
  95. package/dist/tests/bench/trajectory.js +116 -0
  96. package/dist/tests/bench/trajectory.test.js +127 -0
  97. package/dist/tests/bench/verifier.js +109 -0
  98. package/dist/tests/bench/verifier.test.js +118 -0
  99. package/dist/tests/bench/workflow-evaluator.js +557 -0
  100. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  101. package/dist/tests/bench/workflow-spec.js +358 -0
  102. package/dist/tests/bench/workflow-spec.test.js +363 -0
  103. package/dist/tests/bench/workflow-trace.js +438 -0
  104. package/dist/tests/bench/workflow-trace.test.js +254 -0
  105. package/dist/tests/benchmark-search-quality.js +536 -0
  106. package/dist/tests/benchmark-suite.js +1441 -0
  107. package/dist/tests/capture-cli.test.js +112 -0
  108. package/dist/tests/cli-errors.test.js +203 -0
  109. package/dist/tests/commands/events.test.js +370 -0
  110. package/dist/tests/commands/history.test.js +223 -0
  111. package/dist/tests/commands/import.test.js +103 -0
  112. package/dist/tests/commands/proposal-cli.test.js +209 -0
  113. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  114. package/dist/tests/commands/remember.test.js +97 -0
  115. package/dist/tests/commands/scope-flags.test.js +300 -0
  116. package/dist/tests/commands/search.test.js +537 -0
  117. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  118. package/dist/tests/commands/show.test.js +294 -0
  119. package/dist/tests/common.test.js +266 -0
  120. package/dist/tests/completions.test.js +142 -0
  121. package/dist/tests/config-cli.test.js +193 -0
  122. package/dist/tests/config-llm-features.test.js +139 -0
  123. package/dist/tests/config.test.js +544 -0
  124. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  125. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  126. package/dist/tests/contracts/spec-helpers.js +46 -0
  127. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  128. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  129. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  130. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  131. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  132. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  133. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  134. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  135. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  136. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  137. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  138. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  139. package/dist/tests/core/write-source.test.js +366 -0
  140. package/dist/tests/curate-command.test.js +87 -0
  141. package/dist/tests/db-scoring.test.js +201 -0
  142. package/dist/tests/db.test.js +654 -0
  143. package/dist/tests/distill-cli-flag.test.js +208 -0
  144. package/dist/tests/distill.test.js +515 -0
  145. package/dist/tests/docker-install.test.js +120 -0
  146. package/dist/tests/e2e.test.js +1398 -0
  147. package/dist/tests/embedder.test.js +340 -0
  148. package/dist/tests/embedding-model-config.test.js +379 -0
  149. package/dist/tests/feedback-command.test.js +172 -0
  150. package/dist/tests/file-context.test.js +552 -0
  151. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  152. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  153. package/dist/tests/fixtures/stashes/load.js +166 -0
  154. package/dist/tests/fixtures/stashes/load.test.js +88 -0
  155. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  156. package/dist/tests/frontmatter.test.js +190 -0
  157. package/dist/tests/fts-field-weighting.test.js +254 -0
  158. package/dist/tests/fuzzy-search.test.js +230 -0
  159. package/dist/tests/git-provider-clone.test.js +45 -0
  160. package/dist/tests/github.test.js +161 -0
  161. package/dist/tests/graph-boost-ranking.test.js +305 -0
  162. package/dist/tests/graph-extraction.test.js +282 -0
  163. package/dist/tests/helpers/usage-events.js +8 -0
  164. package/dist/tests/index-pass-llm.test.js +161 -0
  165. package/dist/tests/indexer.test.js +559 -0
  166. package/dist/tests/info-command.test.js +166 -0
  167. package/dist/tests/init.test.js +69 -0
  168. package/dist/tests/install-script.test.js +246 -0
  169. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  170. package/dist/tests/issue-36-repro.test.js +304 -0
  171. package/dist/tests/issues-191-194.test.js +160 -0
  172. package/dist/tests/lesson-lint.test.js +111 -0
  173. package/dist/tests/llm-client.test.js +115 -0
  174. package/dist/tests/llm-feature-gate.test.js +151 -0
  175. package/dist/tests/llm.test.js +139 -0
  176. package/dist/tests/lockfile.test.js +216 -0
  177. package/dist/tests/manifest.test.js +205 -0
  178. package/dist/tests/markdown.test.js +126 -0
  179. package/dist/tests/matchers-unit.test.js +189 -0
  180. package/dist/tests/memory-inference.test.js +299 -0
  181. package/dist/tests/merge-scoring.test.js +136 -0
  182. package/dist/tests/metadata.test.js +313 -0
  183. package/dist/tests/migration-help.test.js +89 -0
  184. package/dist/tests/origin-resolve.test.js +124 -0
  185. package/dist/tests/output-baseline.test.js +217 -0
  186. package/dist/tests/output-shapes-unit.test.js +476 -0
  187. package/dist/tests/parallel-search.test.js +272 -0
  188. package/dist/tests/parameter-metadata.test.js +365 -0
  189. package/dist/tests/paths.test.js +177 -0
  190. package/dist/tests/progressive-disclosure.test.js +280 -0
  191. package/dist/tests/proposals.test.js +279 -0
  192. package/dist/tests/proposed-quality.test.js +271 -0
  193. package/dist/tests/provider-registry.test.js +32 -0
  194. package/dist/tests/ranking-regression.test.js +548 -0
  195. package/dist/tests/reflect-propose.test.js +455 -0
  196. package/dist/tests/registry-build-index.test.js +378 -0
  197. package/dist/tests/registry-cli.test.js +290 -0
  198. package/dist/tests/registry-index-v2.test.js +430 -0
  199. package/dist/tests/registry-install.test.js +728 -0
  200. package/dist/tests/registry-providers/parity.test.js +189 -0
  201. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  202. package/dist/tests/registry-providers/static-index.test.js +204 -0
  203. package/dist/tests/registry-resolve.test.js +126 -0
  204. package/dist/tests/registry-search.test.js +723 -0
  205. package/dist/tests/remember-frontmatter.test.js +380 -0
  206. package/dist/tests/remember-unit.test.js +123 -0
  207. package/dist/tests/ripgrep-install.test.js +251 -0
  208. package/dist/tests/ripgrep-resolve.test.js +108 -0
  209. package/dist/tests/ripgrep.test.js +163 -0
  210. package/dist/tests/save-command.test.js +94 -0
  211. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  212. package/dist/tests/scoring-pipeline.test.js +648 -0
  213. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  214. package/dist/tests/self-update.test.js +442 -0
  215. package/dist/tests/semantic-search-e2e.test.js +512 -0
  216. package/dist/tests/semantic-status.test.js +471 -0
  217. package/dist/tests/setup-run.integration.js +877 -0
  218. package/dist/tests/setup-wizard.test.js +198 -0
  219. package/dist/tests/setup.test.js +131 -0
  220. package/dist/tests/source-add.test.js +11 -0
  221. package/dist/tests/source-clone.test.js +254 -0
  222. package/dist/tests/source-manage.test.js +366 -0
  223. package/dist/tests/source-providers/filesystem.test.js +82 -0
  224. package/dist/tests/source-providers/git.test.js +252 -0
  225. package/dist/tests/source-providers/website.test.js +128 -0
  226. package/dist/tests/source-qa-fixes.test.js +268 -0
  227. package/dist/tests/source-registry.test.js +350 -0
  228. package/dist/tests/source-resolve.test.js +100 -0
  229. package/dist/tests/source-source.test.js +221 -0
  230. package/dist/tests/source.test.js +533 -0
  231. package/dist/tests/tar-utils-scan.test.js +73 -0
  232. package/dist/tests/toggle-components.test.js +73 -0
  233. package/dist/tests/usage-telemetry.test.js +265 -0
  234. package/dist/tests/utility-scoring.test.js +558 -0
  235. package/dist/tests/vault-load-error.test.js +78 -0
  236. package/dist/tests/vault-qa-fixes.test.js +194 -0
  237. package/dist/tests/vault.test.js +429 -0
  238. package/dist/tests/vector-search.test.js +608 -0
  239. package/dist/tests/walker.test.js +252 -0
  240. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  241. package/dist/tests/wave2-cluster-d.test.js +180 -0
  242. package/dist/tests/wave2-cluster-e.test.js +179 -0
  243. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  244. package/dist/tests/wiki.test.js +529 -0
  245. package/dist/tests/workflow-cli.test.js +271 -0
  246. package/dist/tests/workflow-markdown.test.js +171 -0
  247. package/dist/tests/workflow-path-escape.test.js +132 -0
  248. package/dist/tests/workflow-qa-fixes.test.js +377 -0
  249. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  250. package/docs/README.md +8 -0
  251. package/docs/migration/release-notes/0.7.0.md +244 -0
  252. package/package.json +2 -2
  253. package/dist/core/warn.js +0 -27
  254. package/dist/output/shapes.js +0 -212
  255. /package/dist/{commands → src/commands}/completions.js +0 -0
  256. /package/dist/{commands → src/commands}/curate.js +0 -0
  257. /package/dist/{commands → src/commands}/info.js +0 -0
  258. /package/dist/{commands → src/commands}/init.js +0 -0
  259. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  260. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  261. /package/dist/{commands → src/commands}/source-add.js +0 -0
  262. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  263. /package/dist/{commands → src/commands}/source-manage.js +0 -0
  264. /package/dist/{commands → src/commands}/vault.js +0 -0
  265. /package/dist/{core → src/core}/asset-registry.js +0 -0
  266. /package/dist/{core → src/core}/frontmatter.js +0 -0
  267. /package/dist/{core → src/core}/markdown.js +0 -0
  268. /package/dist/{core → src/core}/paths.js +0 -0
  269. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  270. /package/dist/{indexer → src/indexer}/matchers.js +0 -0
  271. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  272. /package/dist/{indexer → src/indexer}/search-source.js +0 -0
  273. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  274. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  275. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  276. /package/dist/{integrations → src/integrations}/github.js +0 -0
  277. /package/dist/{llm → src/llm}/embedder.js +0 -0
  278. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  279. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  280. /package/dist/{llm → src/llm}/embedders/remote.js +0 -0
  281. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  282. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  283. /package/dist/{output → src/output}/cli-hints.js +0 -0
  284. /package/dist/{output → src/output}/context.js +0 -0
  285. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  286. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  287. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  288. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  289. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  290. /package/dist/{registry → src/registry}/types.js +0 -0
  291. /package/dist/{setup → src/setup}/detect.js +0 -0
  292. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  293. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  294. /package/dist/{setup → src/setup}/steps.js +0 -0
  295. /package/dist/{sources → src/sources}/include.js +0 -0
  296. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  297. /package/dist/{sources → src/sources}/provider.js +0 -0
  298. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  299. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  300. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  301. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  302. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  303. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  304. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  305. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  306. /package/dist/{sources → src/sources}/resolve.js +0 -0
  307. /package/dist/{sources → src/sources}/types.js +0 -0
  308. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  309. /package/dist/{version.js → src/version.js} +0 -0
  310. /package/dist/{wiki → src/wiki}/wiki.js +0 -0
  311. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  312. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  313. /package/dist/{workflows → src/workflows}/db.js +0 -0
  314. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  315. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  316. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  317. /package/dist/{workflows → src/workflows}/runs.js +0 -0
  318. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  319. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -5,13 +5,19 @@ import { defineCommand, runMain } from "citty";
5
5
  import { generateBashCompletions, installBashCompletions } from "./commands/completions";
6
6
  import { getConfigValue, listConfig, setConfigValue, unsetConfigValue } from "./commands/config-cli";
7
7
  import { akmCurate } from "./commands/curate";
8
+ import { akmDistill } from "./commands/distill";
9
+ import { akmEventsList, akmEventsTail } from "./commands/events";
10
+ import { akmHistory } from "./commands/history";
8
11
  import { assembleInfo } from "./commands/info";
9
12
  import { akmInit } from "./commands/init";
10
13
  import { akmListSources, akmRemove, akmUpdate } from "./commands/installed-stashes";
11
14
  import { renderMigrationHelp } from "./commands/migration-help";
15
+ import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalShow, } from "./commands/proposal";
16
+ import { akmPropose } from "./commands/propose";
17
+ import { akmReflect } from "./commands/reflect";
12
18
  import { searchRegistry } from "./commands/registry-search";
13
19
  import { buildMemoryFrontmatter, parseDuration, readMemoryContent, runAutoHeuristics, runLlmEnrich, } from "./commands/remember";
14
- import { akmSearch, parseSearchSource } from "./commands/search";
20
+ import { akmSearch, parseScopeFilterFlags, parseSearchSource } from "./commands/search";
15
21
  import { checkForUpdate, performUpgrade } from "./commands/self-update";
16
22
  import { akmShowUnified } from "./commands/show";
17
23
  import { akmAdd } from "./commands/source-add";
@@ -22,8 +28,9 @@ import { deriveCanonicalAssetName, resolveAssetPathFromName } from "./core/asset
22
28
  import { isWithin, resolveStashDir, tryReadStdinText } from "./core/common";
23
29
  import { DEFAULT_CONFIG, getConfigPath, loadConfig, loadUserConfig, saveConfig } from "./core/config";
24
30
  import { ConfigError, NotFoundError, UsageError } from "./core/errors";
31
+ import { appendEvent } from "./core/events";
25
32
  import { getCacheDir, getDbPath, getDefaultStashDir } from "./core/paths";
26
- import { setQuiet, warn } from "./core/warn";
33
+ import { setQuiet, setVerbose, warn } from "./core/warn";
27
34
  import { resolveWriteTarget, writeAssetToSource } from "./core/write-source";
28
35
  import { closeDatabase, findEntryIdByRef, openDatabase } from "./indexer/db";
29
36
  import { akmIndex } from "./indexer/indexer";
@@ -32,7 +39,7 @@ import { insertUsageEvent } from "./indexer/usage-events";
32
39
  import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./output/cli-hints";
33
40
  import { getHyphenatedArg, getHyphenatedBoolean, getOutputMode, initOutputMode, parseFlagValue, } from "./output/context";
34
41
  import { shapeForCommand } from "./output/shapes";
35
- import { formatPlain, outputJsonl } from "./output/text";
42
+ import { formatEventLine, formatPlain, outputJsonl } from "./output/text";
36
43
  import { buildRegistryIndex, writeRegistryIndex } from "./registry/build-index";
37
44
  import { resolveSourcesForOrigin } from "./registry/origin-resolve";
38
45
  import { saveGitStash } from "./sources/providers/git";
@@ -58,6 +65,10 @@ function parseAllFlagValues(flag) {
58
65
  const arg = process.argv[i];
59
66
  if (arg === flag && i + 1 < process.argv.length) {
60
67
  values.push(process.argv[i + 1]);
68
+ // BUG-M4: skip the value index so `--tag --tag` (literal `--tag`
69
+ // value) does not double-count the second `--tag` as a separate
70
+ // flag occurrence.
71
+ i++;
61
72
  }
62
73
  else if (arg.startsWith(`${flag}=`)) {
63
74
  values.push(arg.slice(flag.length + 1));
@@ -96,7 +107,7 @@ function output(command, result) {
96
107
  const setupCommand = defineCommand({
97
108
  meta: {
98
109
  name: "setup",
99
- description: "Interactive configuration wizard for embeddings, LLM, registries, and stash sources",
110
+ description: "Interactive configuration wizard: detects services and walks you through embeddings, LLM, registries, sources, and agent profiles. Writes config once at the end.",
100
111
  },
101
112
  async run() {
102
113
  await runWithJsonErrors(async () => {
@@ -154,10 +165,19 @@ const searchCommand = defineCommand({
154
165
  query: { type: "positional", description: "Search query (omit to list all assets)", required: false, default: "" },
155
166
  type: {
156
167
  type: "string",
157
- description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, or any). Use workflow to find step-by-step task assets.",
168
+ description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, lesson, or any). Use workflow to find step-by-step task assets.",
158
169
  },
159
170
  limit: { type: "string", description: "Maximum number of results" },
160
171
  source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
172
+ filter: {
173
+ type: "string",
174
+ description: "Scope filter (repeatable): --filter user=<id> --filter agent=<id> --filter run=<id> --filter channel=<name>. Narrows results without changing ranking.",
175
+ },
176
+ "include-proposed": {
177
+ type: "boolean",
178
+ description: 'Include entries with quality:"proposed" in the result set. Excluded by default (v1 spec §4.2).',
179
+ default: false,
180
+ },
161
181
  format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
162
182
  detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
163
183
  },
@@ -165,7 +185,7 @@ const searchCommand = defineCommand({
165
185
  await runWithJsonErrors(async () => {
166
186
  const query = (args.query ?? "").trim();
167
187
  if (!query) {
168
- throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT");
188
+ throw new UsageError('A search query is required. Usage: akm search "<query>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", "Provide a query string. Filter by type with --type skill|command|...; limit results with --limit N.");
169
189
  }
170
190
  const type = args.type;
171
191
  const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
@@ -174,7 +194,12 @@ const searchCommand = defineCommand({
174
194
  }
175
195
  const limit = limitRaw;
176
196
  const source = parseSearchSource(args.source);
177
- const result = await akmSearch({ query, type, limit, source });
197
+ // Repeatable; citty exposes only the last `--filter` value, so read all
198
+ // occurrences directly from argv (same pattern as `--tag`).
199
+ const filterTokens = parseAllFlagValues("--filter");
200
+ const filters = parseScopeFilterFlags(filterTokens, "--filter");
201
+ const includeProposed = args["include-proposed"] === true;
202
+ const result = await akmSearch({ query, type, limit, source, filters, includeProposed });
178
203
  output("search", result);
179
204
  });
180
205
  },
@@ -182,16 +207,22 @@ const searchCommand = defineCommand({
182
207
  const curateCommand = defineCommand({
183
208
  meta: { name: "curate", description: "Curate the best matching assets for a task or prompt" },
184
209
  args: {
185
- query: { type: "positional", description: "Task or prompt to curate assets for", required: true },
210
+ // Optional in citty so run() is invoked when omitted; we re-validate
211
+ // below to surface a structured UsageError (exit 2) instead of citty's
212
+ // default help-banner exit-0.
213
+ query: { type: "positional", description: "Task or prompt to curate assets for", required: false },
186
214
  type: {
187
215
  type: "string",
188
- description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, or any). Use workflow to curate step-by-step task assets.",
216
+ description: "Asset type filter (skill, command, agent, knowledge, workflow, script, memory, vault, wiki, lesson, or any). Use workflow to curate step-by-step task assets.",
189
217
  },
190
218
  limit: { type: "string", description: "Maximum number of curated results", default: "4" },
191
219
  source: { type: "string", description: "Search source (stash|registry|both)", default: "stash" },
192
220
  },
193
221
  async run({ args }) {
194
222
  await runWithJsonErrors(async () => {
223
+ if (!args.query || !String(args.query).trim()) {
224
+ throw new UsageError('A curate query is required. Usage: akm curate "<task or prompt>" [--type <type>] [--limit <n>]', "MISSING_REQUIRED_ARGUMENT", 'Describe the task you want assets for, e.g. `akm curate "deploy to prod"`.');
225
+ }
195
226
  const type = args.type;
196
227
  const limitRaw = args.limit ? parseInt(args.limit, 10) : undefined;
197
228
  if (limitRaw !== undefined && Number.isNaN(limitRaw)) {
@@ -234,14 +265,24 @@ const addCommand = defineCommand({
234
265
  },
235
266
  "max-pages": { type: "string", description: "Maximum pages to crawl for website sources (default: 50)" },
236
267
  "max-depth": { type: "string", description: "Maximum crawl depth for website sources (default: 3)" },
268
+ "allow-insecure": {
269
+ type: "boolean",
270
+ description: "Allow a plain HTTP source URL (otherwise rejected for non-localhost hosts)",
271
+ default: false,
272
+ },
237
273
  },
238
274
  async run({ args }) {
239
275
  await runWithJsonErrors(async () => {
240
276
  const ref = args.ref.trim();
277
+ const allowInsecure = getHyphenatedBoolean(args, "allow-insecure");
241
278
  // URL with --provider → stash source (remote or git provider)
242
279
  if (args.provider) {
243
280
  if (shouldWarnOnPlainHttp(ref)) {
244
- warn("Warning: source URL uses plain HTTP (not HTTPS). For security, prefer https:// to protect against eavesdropping and tampering.");
281
+ if (!allowInsecure) {
282
+ throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
283
+ "Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
284
+ }
285
+ warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
245
286
  }
246
287
  let parsedOptions;
247
288
  if (args.options) {
@@ -265,11 +306,19 @@ const addCommand = defineCommand({
265
306
  options: parsedOptions,
266
307
  writable: args.writable,
267
308
  });
309
+ appendEvent({
310
+ eventType: "add",
311
+ metadata: { target: ref, provider: args.provider, name: args.name ?? null, writable: args.writable === true },
312
+ });
268
313
  output("add", result);
269
314
  return;
270
315
  }
271
316
  if (shouldWarnOnPlainHttp(ref)) {
272
- warn("Warning: source URL uses plain HTTP (not HTTPS). For security, prefer https:// to protect against eavesdropping and tampering.");
317
+ if (!allowInsecure) {
318
+ throw new UsageError("Source URL uses plain HTTP (not HTTPS). An on-path attacker could substitute a malicious payload. " +
319
+ "Use https:// or pass --allow-insecure if you have explicitly accepted the risk.", "INVALID_FLAG_VALUE", "Re-run with `--allow-insecure` only after confirming the URL is trusted.");
320
+ }
321
+ warn("Warning: source URL uses plain HTTP (not HTTPS). --allow-insecure was set; an on-path attacker could substitute a malicious payload.");
273
322
  }
274
323
  const websiteOptions = buildWebsiteOptions(args);
275
324
  if (args.type === "wiki") {
@@ -281,6 +330,10 @@ const addCommand = defineCommand({
281
330
  trustThisInstall: args.trust,
282
331
  writable: args.writable,
283
332
  });
333
+ appendEvent({
334
+ eventType: "add",
335
+ metadata: { target: ref, type: "wiki", name: args.name ?? null, writable: args.writable === true },
336
+ });
284
337
  output("add", result);
285
338
  return;
286
339
  }
@@ -292,6 +345,15 @@ const addCommand = defineCommand({
292
345
  trustThisInstall: args.trust,
293
346
  writable: args.writable,
294
347
  });
348
+ appendEvent({
349
+ eventType: "add",
350
+ metadata: {
351
+ target: ref,
352
+ name: args.name ?? null,
353
+ overrideType: args.type ?? null,
354
+ writable: args.writable === true,
355
+ },
356
+ });
295
357
  output("add", result);
296
358
  });
297
359
  },
@@ -353,6 +415,14 @@ const removeCommand = defineCommand({
353
415
  async run({ args }) {
354
416
  await runWithJsonErrors(async () => {
355
417
  const result = await akmRemove({ target: args.target });
418
+ appendEvent({
419
+ eventType: "remove",
420
+ metadata: {
421
+ target: args.target,
422
+ ref: typeof result.removed?.ref === "string" ? result.removed.ref : null,
423
+ id: typeof result.removed?.id === "string" ? result.removed.id : null,
424
+ },
425
+ });
356
426
  output("remove", result);
357
427
  });
358
428
  },
@@ -367,6 +437,17 @@ const updateCommand = defineCommand({
367
437
  async run({ args }) {
368
438
  await runWithJsonErrors(async () => {
369
439
  const result = await akmUpdate({ target: args.target, all: args.all, force: args.force });
440
+ appendEvent({
441
+ eventType: "update",
442
+ metadata: {
443
+ target: args.target ?? null,
444
+ all: args.all === true,
445
+ force: args.force === true,
446
+ processed: Array.isArray(result.processed)
447
+ ? result.processed.length
448
+ : 0,
449
+ },
450
+ });
370
451
  output("update", result);
371
452
  });
372
453
  },
@@ -407,9 +488,17 @@ const showCommand = defineCommand({
407
488
  description: "Show a stash asset by ref (e.g. akm show knowledge:guide.md toc, akm show knowledge:guide.md section 'Auth')",
408
489
  },
409
490
  args: {
410
- ref: { type: "positional", description: "Asset ref (type:name)", required: true },
491
+ ref: {
492
+ type: "positional",
493
+ description: 'Asset ref ([origin//]type:name) optionally followed by a view mode. View modes: `toc` (table of contents), `section "Heading"` (extract one section), `lines <start> <end>` (line range), `frontmatter` (YAML metadata only), `full` (raw file). Example: `akm show knowledge:guide.md section "Auth"`.',
494
+ required: true,
495
+ },
411
496
  format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
412
497
  detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
498
+ scope: {
499
+ type: "string",
500
+ description: "Scope filter (repeatable): --scope user=<id> --scope agent=<id> --scope run=<id> --scope channel=<name>. Narrows resolution to assets whose frontmatter scope matches.",
501
+ },
413
502
  },
414
503
  async run({ args }) {
415
504
  await runWithJsonErrors(async () => {
@@ -447,7 +536,11 @@ const showCommand = defineCommand({
447
536
  const cliDetail = getOutputMode().detail;
448
537
  const explicitDetail = parseFlagValue(process.argv, "--detail");
449
538
  const showDetail = explicitDetail === "brief" ? "brief" : cliDetail === "summary" ? "summary" : undefined;
450
- const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail });
539
+ // `--scope` is repeatable citty only exposes the last value, so read
540
+ // every occurrence directly from argv (same pattern as `--filter`).
541
+ const scopeTokens = parseAllFlagValues("--scope");
542
+ const scope = parseScopeFilterFlags(scopeTokens, "--scope");
543
+ const result = await akmShowUnified({ ref: args.ref, view, detail: showDetail, scope });
451
544
  output("show", result);
452
545
  });
453
546
  },
@@ -588,6 +681,14 @@ const saveCommand = defineCommand({
588
681
  writable = cfg.writable === true ? true : undefined;
589
682
  }
590
683
  const result = saveGitStash(effectiveName, args.message, writable);
684
+ appendEvent({
685
+ eventType: "save",
686
+ metadata: {
687
+ name: effectiveName ?? null,
688
+ message: args.message ?? null,
689
+ ok: result.ok !== false,
690
+ },
691
+ });
591
692
  output("save", result);
592
693
  });
593
694
  },
@@ -794,16 +895,19 @@ const feedbackCommand = defineCommand({
794
895
  description: "Record positive or negative feedback for any indexed stash asset",
795
896
  },
796
897
  args: {
797
- ref: { type: "positional", description: "Asset ref (type:name)", required: true },
898
+ // Optional in citty so run() is invoked even when omitted; we re-validate
899
+ // and throw a structured UsageError below so exit code is 2 (USAGE) rather
900
+ // than citty's default 0 (help banner).
901
+ ref: { type: "positional", description: "Asset ref (type:name)", required: false },
798
902
  positive: { type: "boolean", description: "Record positive feedback", default: false },
799
903
  negative: { type: "boolean", description: "Record negative feedback", default: false },
800
904
  note: { type: "string", description: "Optional note to attach to the feedback" },
801
905
  },
802
906
  run({ args }) {
803
907
  return runWithJsonErrors(() => {
804
- const ref = args.ref.trim();
908
+ const ref = (args.ref ?? "").trim();
805
909
  if (!ref) {
806
- throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative");
910
+ throw new UsageError("Asset ref is required. Usage: akm feedback <ref> --positive|--negative", "MISSING_REQUIRED_ARGUMENT", "Pass a ref like `skill:deploy` and either --positive or --negative.");
807
911
  }
808
912
  parseAssetRef(ref);
809
913
  if (args.positive && args.negative) {
@@ -831,10 +935,35 @@ const feedbackCommand = defineCommand({
831
935
  finally {
832
936
  closeDatabase(db);
833
937
  }
938
+ appendEvent({
939
+ eventType: "feedback",
940
+ ref,
941
+ metadata: { signal, ...(args.note ? { note: args.note } : {}) },
942
+ });
834
943
  output("feedback", { ok: true, ref, signal, note: args.note ?? null });
835
944
  });
836
945
  },
837
946
  });
947
+ const historyCommand = defineCommand({
948
+ meta: {
949
+ name: "history",
950
+ description: "Show mutation/usage history for a single asset (--ref) or stash-wide. Backed by the internal usage_events log.",
951
+ },
952
+ args: {
953
+ ref: { type: "string", description: "Asset ref (type:name). Omit for stash-wide history." },
954
+ since: { type: "string", description: "ISO timestamp or epoch ms — only events on/after this time" },
955
+ format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
956
+ },
957
+ run({ args }) {
958
+ return runWithJsonErrors(async () => {
959
+ const result = await akmHistory({
960
+ ref: args.ref,
961
+ since: args.since,
962
+ });
963
+ output("history", result);
964
+ });
965
+ },
966
+ });
838
967
  function normalizeMarkdownAssetName(name, fallback) {
839
968
  const trimmed = (name ?? fallback)
840
969
  .trim()
@@ -944,11 +1073,40 @@ const workflowNextCommand = defineCommand({
944
1073
  async run({ args }) {
945
1074
  await runWithJsonErrors(async () => {
946
1075
  const parsedParams = args.params ? parseWorkflowJsonObject(args.params, "--params") : undefined;
1076
+ // If the target looks like a UUID-style run id (no `:` and matches the
1077
+ // run-id shape), short-circuit with a structured WORKFLOW_NOT_FOUND
1078
+ // error before parseAssetRef gets to throw an unhelpful ref-parse error.
1079
+ if (looksLikeWorkflowRunId(args.target)) {
1080
+ const { listWorkflowRuns: listRuns } = await import("./workflows/runs.js");
1081
+ const { runs: existingRuns } = listRuns({});
1082
+ if (!existingRuns.some((r) => r.id === args.target)) {
1083
+ throw new NotFoundError(`Workflow run "${args.target}" not found.`, "WORKFLOW_NOT_FOUND", "Run `akm workflow list --active` to see runs.");
1084
+ }
1085
+ }
947
1086
  const result = await getNextWorkflowStep(args.target, parsedParams);
948
1087
  output("workflow-next", result);
949
1088
  });
950
1089
  },
951
1090
  });
1091
+ /**
1092
+ * Heuristic: a workflow run id is a UUID-shaped or hex-id-shaped string with
1093
+ * no `:` separator (refs always contain a colon: `workflow:<name>` or
1094
+ * `<origin>//workflow:<name>`). When this matches we can give a much better
1095
+ * error than parseAssetRef's "Invalid asset type" failure.
1096
+ */
1097
+ function looksLikeWorkflowRunId(target) {
1098
+ if (target.includes(":"))
1099
+ return false;
1100
+ if (target.includes("/"))
1101
+ return false;
1102
+ // UUID v4-ish: 8-4-4-4-12 hex digits separated by dashes.
1103
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target))
1104
+ return true;
1105
+ // Bare hex/alphanumeric run ids of >=8 chars (covers shortened ids).
1106
+ if (/^[0-9a-z][0-9a-z_-]{7,}$/i.test(target) && /[0-9]/.test(target))
1107
+ return true;
1108
+ return false;
1109
+ }
952
1110
  const workflowCompleteCommand = defineCommand({
953
1111
  meta: {
954
1112
  name: "complete",
@@ -1220,6 +1378,22 @@ const rememberCommand = defineCommand({
1220
1378
  type: "string",
1221
1379
  description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
1222
1380
  },
1381
+ user: {
1382
+ type: "string",
1383
+ description: "Scope this memory to a user id (persisted as `scope_user` frontmatter)",
1384
+ },
1385
+ agent: {
1386
+ type: "string",
1387
+ description: "Scope this memory to an agent id (persisted as `scope_agent` frontmatter)",
1388
+ },
1389
+ run: {
1390
+ type: "string",
1391
+ description: "Scope this memory to a run id (persisted as `scope_run` frontmatter)",
1392
+ },
1393
+ channel: {
1394
+ type: "string",
1395
+ description: "Scope this memory to a channel name (persisted as `scope_channel` frontmatter)",
1396
+ },
1223
1397
  },
1224
1398
  async run({ args }) {
1225
1399
  return runWithJsonErrors(async () => {
@@ -1228,7 +1402,21 @@ const rememberCommand = defineCommand({
1228
1402
  // Collect all --tag occurrences directly from process.argv because citty
1229
1403
  // only exposes the last value for repeated string flags.
1230
1404
  const rawTags = parseAllFlagValues("--tag");
1231
- const hasStructuredArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description || args.auto || args.enrich;
1405
+ // Collect scope flags. Scope alone counts as structured metadata so we
1406
+ // emit frontmatter, but it does NOT trigger the "tags required" check —
1407
+ // memory + scope (no tags) is a valid combination for multi-tenant use.
1408
+ const scopeFields = {};
1409
+ if (typeof args.user === "string" && args.user.trim())
1410
+ scopeFields.user = args.user.trim();
1411
+ if (typeof args.agent === "string" && args.agent.trim())
1412
+ scopeFields.agent = args.agent.trim();
1413
+ if (typeof args.run === "string" && args.run.trim())
1414
+ scopeFields.run = args.run.trim();
1415
+ if (typeof args.channel === "string" && args.channel.trim())
1416
+ scopeFields.channel = args.channel.trim();
1417
+ const hasScope = Object.keys(scopeFields).length > 0;
1418
+ const hasTagRequiringArgs = rawTags.length > 0 || !!args.expires || !!args.source || !!args.description || args.auto || args.enrich;
1419
+ const hasStructuredArgs = hasTagRequiringArgs || hasScope;
1232
1420
  if (!hasStructuredArgs) {
1233
1421
  const result = await writeMarkdownAsset({
1234
1422
  type: "memory",
@@ -1238,6 +1426,11 @@ const rememberCommand = defineCommand({
1238
1426
  force: args.force,
1239
1427
  target: args.target,
1240
1428
  });
1429
+ appendEvent({
1430
+ eventType: "remember",
1431
+ ref: result.ref,
1432
+ metadata: { path: result.path, force: args.force === true },
1433
+ });
1241
1434
  output("remember", { ok: true, ...result });
1242
1435
  return;
1243
1436
  }
@@ -1283,8 +1476,12 @@ const rememberCommand = defineCommand({
1283
1476
  observed_at = enriched.observed_at;
1284
1477
  }
1285
1478
  // ── Required-field check (before any write) ───────────────────────────
1479
+ // Tags remain required when the user opted into a tag-producing mode
1480
+ // (--tag / --auto / --enrich / --description / --source / --expires).
1481
+ // Scope-only writes (`akm remember "..." --user u1`) skip this check —
1482
+ // scope is independent metadata and a memory with only scope is valid.
1286
1483
  const missing = [];
1287
- if (tags.length === 0)
1484
+ if (hasTagRequiringArgs && tags.length === 0)
1288
1485
  missing.push("tags");
1289
1486
  if (missing.length > 0) {
1290
1487
  throw new UsageError(`Memory is missing required frontmatter field(s): ${missing.join(", ")}. ` +
@@ -1298,6 +1495,7 @@ const rememberCommand = defineCommand({
1298
1495
  observed_at,
1299
1496
  expires,
1300
1497
  subjective,
1498
+ ...(hasScope ? { scope: scopeFields } : {}),
1301
1499
  });
1302
1500
  const contentWithFrontmatter = `${frontmatterBlock}\n${body}`;
1303
1501
  const result = await writeMarkdownAsset({
@@ -1308,6 +1506,18 @@ const rememberCommand = defineCommand({
1308
1506
  force: args.force,
1309
1507
  target: args.target,
1310
1508
  });
1509
+ appendEvent({
1510
+ eventType: "remember",
1511
+ ref: result.ref,
1512
+ metadata: {
1513
+ path: result.path,
1514
+ force: args.force === true,
1515
+ tagCount: tags.length,
1516
+ enriched: args.enrich === true,
1517
+ auto: args.auto === true,
1518
+ ...(hasScope ? { scope: scopeFields } : {}),
1519
+ },
1520
+ });
1311
1521
  output("remember", { ok: true, ...result });
1312
1522
  });
1313
1523
  },
@@ -1349,6 +1559,11 @@ const importKnowledgeCommand = defineCommand({
1349
1559
  force: args.force,
1350
1560
  target: args.target,
1351
1561
  });
1562
+ appendEvent({
1563
+ eventType: "import",
1564
+ ref: result.ref,
1565
+ metadata: { source: args.source, path: result.path, force: args.force === true },
1566
+ });
1352
1567
  output("import", { ok: true, source: args.source, ...result });
1353
1568
  });
1354
1569
  },
@@ -1359,7 +1574,11 @@ const hintsCommand = defineCommand({
1359
1574
  description: "Print agent instructions on how to use akm, use --detail full for a complete guide",
1360
1575
  },
1361
1576
  args: {
1362
- detail: { type: "string", description: "Detail level (normal|full)", default: "normal" },
1577
+ detail: {
1578
+ type: "string",
1579
+ description: "Hints detail level — accepts only `normal` or `full`. Differs from the global --detail flag (brief|normal|full|summary|agent); other values are rejected with INVALID_DETAIL_VALUE.",
1580
+ default: "normal",
1581
+ },
1363
1582
  },
1364
1583
  run({ args }) {
1365
1584
  if (args.detail !== "normal" && args.detail !== "full") {
@@ -1380,14 +1599,22 @@ const helpCommand = defineCommand({
1380
1599
  description: "Print release notes and migration guidance for a version. Bundled notes live in docs/migration/release-notes/<version>.md; an unknown version lists what's available.",
1381
1600
  },
1382
1601
  args: {
1602
+ // Optional in citty so run() is invoked even when omitted; we
1603
+ // re-validate below to surface a structured UsageError (exit 2)
1604
+ // instead of citty's default help-banner exit-0.
1383
1605
  version: {
1384
1606
  type: "positional",
1385
1607
  description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
1386
- required: true,
1608
+ required: false,
1387
1609
  },
1388
1610
  },
1389
1611
  run({ args }) {
1390
- process.stdout.write(renderMigrationHelp(args.version));
1612
+ return runWithJsonErrors(() => {
1613
+ if (!args.version || !String(args.version).trim()) {
1614
+ throw new UsageError("Usage: akm help migrate <version>.", "MISSING_REQUIRED_ARGUMENT", "Pass a version like `0.6.0`, `v0.6.0`, `0.6.0-rc1`, or `latest`.");
1615
+ }
1616
+ process.stdout.write(renderMigrationHelp(args.version));
1617
+ });
1391
1618
  },
1392
1619
  }),
1393
1620
  },
@@ -1428,9 +1655,6 @@ function normalizeToggleTarget(target) {
1428
1655
  const normalized = target.trim().toLowerCase();
1429
1656
  if (normalized === "skills.sh" || normalized === "skills-sh")
1430
1657
  return "skills.sh";
1431
- if (normalized === "context-hub") {
1432
- throw new UsageError('The "context-hub" component is no longer toggleable. Run `akm add github:andrewyng/context-hub --name context-hub` to add it as a git stash.');
1433
- }
1434
1658
  throw new UsageError(`Unsupported target "${target}". Supported targets: skills.sh`);
1435
1659
  }
1436
1660
  function toggleSkillsShRegistry(enabled) {
@@ -1698,6 +1922,22 @@ const vaultLoadCommand = defineCommand({
1698
1922
  // metacharacters like $, backticks, or $(...).
1699
1923
  const script = buildShellExportScript(absPath);
1700
1924
  // Write to a mode-0600 temp file the shell can source.
1925
+ //
1926
+ // INTENTIONAL: this site uses `os.tmpdir()` (i.e. `/tmp` on Unix)
1927
+ // rather than `${getCacheDir()}/vault/`. The temp file is written
1928
+ // mode-0600, sourced by the parent shell via `eval`, and immediately
1929
+ // `rm -f`'d on the same line of the emitted snippet. `/tmp` is the
1930
+ // conventional location for short-lived shell-eval scratch files and
1931
+ // benefits from tmp-cleanup-on-reboot semantics, which operators
1932
+ // expect for ephemeral secret material. Moving to `~/.cache/akm/`
1933
+ // would surprise those operators and also persist the file across
1934
+ // reboots if the eval is interrupted before the inline `rm -f` runs.
1935
+ // The bench/registry-build rationale (#276/#284) — orphan dirs
1936
+ // accumulating under `/tmp` from long-running builds — does not
1937
+ // apply here: the file is single-shot, a few hundred bytes, and
1938
+ // removed by the same shell command that sources it.
1939
+ // Regression test: tests/vault-load-error.test.ts verifies the
1940
+ // emitted snippet contains both `. <path>` and `rm -f <path>`.
1701
1941
  const tmpPath = path.join(os.tmpdir(), `akm-vault-${crypto.randomBytes(12).toString("hex")}.sh`);
1702
1942
  fs.writeFileSync(tmpPath, script, { mode: 0o600, encoding: "utf8" });
1703
1943
  try {
@@ -1991,6 +2231,343 @@ const wikiCommand = defineCommand({
1991
2231
  });
1992
2232
  },
1993
2233
  });
2234
+ // ── `akm events` ────────────────────────────────────────────────────────────
2235
+ // Append-only events stream surface (#204). `list` reads `events.jsonl`
2236
+ // with optional --since/--type/--ref filters; `tail` follows the file via
2237
+ // a polling loop and prints each event as a single JSONL line.
2238
+ const eventsListCommand = defineCommand({
2239
+ meta: { name: "list", description: "List events from the append-only events.jsonl stream" },
2240
+ args: {
2241
+ since: {
2242
+ type: "string",
2243
+ description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
2244
+ },
2245
+ type: { type: "string", description: "Filter by event type (add, remove, remember, feedback, ...)" },
2246
+ ref: { type: "string", description: "Filter by asset ref (type:name)" },
2247
+ },
2248
+ run({ args }) {
2249
+ return runWithJsonErrors(() => {
2250
+ const result = akmEventsList({ since: args.since, type: args.type, ref: args.ref });
2251
+ output("events-list", result);
2252
+ });
2253
+ },
2254
+ });
2255
+ const eventsTailCommand = defineCommand({
2256
+ meta: { name: "tail", description: "Follow the append-only events.jsonl stream (polling)" },
2257
+ args: {
2258
+ since: {
2259
+ type: "string",
2260
+ description: "ISO timestamp / epoch ms, OR `@offset:<bytes>` for a durable byte-cursor (resume across processes)",
2261
+ },
2262
+ type: { type: "string", description: "Filter by event type" },
2263
+ ref: { type: "string", description: "Filter by asset ref (type:name)" },
2264
+ "interval-ms": { type: "string", description: "Polling interval in ms (default: 75)" },
2265
+ "max-duration-ms": { type: "string", description: "Stop after this many ms (default: never)" },
2266
+ "max-events": { type: "string", description: "Stop after observing this many events" },
2267
+ },
2268
+ async run({ args }) {
2269
+ await runWithJsonErrors(async () => {
2270
+ const intervalMs = parsePositiveInt(getHyphenatedArg(args, "interval-ms"), "--interval-ms");
2271
+ const maxDurationMs = parsePositiveInt(getHyphenatedArg(args, "max-duration-ms"), "--max-duration-ms");
2272
+ const maxEvents = parsePositiveInt(getHyphenatedArg(args, "max-events"), "--max-events");
2273
+ const mode = getOutputMode();
2274
+ // In streaming text mode we want each event to print as soon as it
2275
+ // arrives. The polling loop emits via `onEvent`; the final result is
2276
+ // also rendered through the standard output() pipeline so JSON
2277
+ // consumers always get the canonical envelope.
2278
+ const stream = mode.format === "text" || mode.format === "jsonl";
2279
+ const result = await akmEventsTail({
2280
+ since: args.since,
2281
+ type: args.type,
2282
+ ref: args.ref,
2283
+ intervalMs,
2284
+ maxDurationMs,
2285
+ maxEvents,
2286
+ onEvent: stream
2287
+ ? (event) => {
2288
+ if (mode.format === "jsonl") {
2289
+ console.log(JSON.stringify(event));
2290
+ }
2291
+ else {
2292
+ console.log(formatEventLine(event));
2293
+ }
2294
+ }
2295
+ : undefined,
2296
+ });
2297
+ // Emit the canonical envelope last (JSON/YAML modes rely on this;
2298
+ // streaming modes already printed each event but we still emit a
2299
+ // trailer so callers can persist the resumable cursor).
2300
+ if (!stream) {
2301
+ output("events-tail", result);
2302
+ }
2303
+ else if (mode.format === "jsonl") {
2304
+ // Final discriminated trailer row so jsonl consumers can resume.
2305
+ const trailer = {
2306
+ _kind: "trailer",
2307
+ schemaVersion: 1,
2308
+ nextOffset: result.nextOffset,
2309
+ totalCount: result.totalCount,
2310
+ reason: result.reason,
2311
+ };
2312
+ console.log(JSON.stringify(trailer));
2313
+ }
2314
+ else {
2315
+ // text mode: keep stdout pristine for line-oriented parsers and
2316
+ // emit the trailer on stderr.
2317
+ process.stderr.write(`[events-tail] reason=${result.reason} nextOffset=${result.nextOffset} total=${result.totalCount}\n`);
2318
+ }
2319
+ });
2320
+ },
2321
+ });
2322
+ function parsePositiveInt(raw, flag) {
2323
+ if (raw === undefined)
2324
+ return undefined;
2325
+ const trimmed = raw.trim();
2326
+ if (!trimmed)
2327
+ return undefined;
2328
+ const value = Number.parseInt(trimmed, 10);
2329
+ if (Number.isNaN(value) || value <= 0) {
2330
+ throw new UsageError(`Invalid ${flag} value: "${raw}". Must be a positive integer.`, "INVALID_FLAG_VALUE");
2331
+ }
2332
+ return value;
2333
+ }
2334
+ const eventsCommand = defineCommand({
2335
+ meta: {
2336
+ name: "events",
2337
+ description: "Read or follow the append-only events.jsonl stream (mutations, feedback, indexing)",
2338
+ },
2339
+ subCommands: {
2340
+ list: eventsListCommand,
2341
+ tail: eventsTailCommand,
2342
+ },
2343
+ });
2344
+ // ── proposal substrate (#225) ────────────────────────────────────────────────
2345
+ const proposalListCommand = defineCommand({
2346
+ meta: { name: "list", description: "List pending proposals (use --include-archive to see decided ones)" },
2347
+ args: {
2348
+ status: { type: "string", description: "Filter by status (pending|accepted|rejected)" },
2349
+ ref: { type: "string", description: "Filter by asset ref (type:name)" },
2350
+ "include-archive": {
2351
+ type: "boolean",
2352
+ description: "Include accepted/rejected proposals from the archive",
2353
+ default: false,
2354
+ },
2355
+ },
2356
+ run({ args }) {
2357
+ return runWithJsonErrors(() => {
2358
+ const status = parseProposalStatus(args.status);
2359
+ const result = akmProposalList({
2360
+ status,
2361
+ ref: args.ref,
2362
+ includeArchive: getHyphenatedBoolean(args, "include-archive"),
2363
+ });
2364
+ output("proposal-list", result);
2365
+ });
2366
+ },
2367
+ });
2368
+ const proposalShowCommand = defineCommand({
2369
+ meta: { name: "show", description: "Show a proposal's metadata, payload, and validation report" },
2370
+ args: {
2371
+ id: { type: "positional", description: "Proposal id (uuid)", required: true },
2372
+ },
2373
+ run({ args }) {
2374
+ return runWithJsonErrors(() => {
2375
+ const result = akmProposalShow({ id: args.id });
2376
+ output("proposal-show", result);
2377
+ });
2378
+ },
2379
+ });
2380
+ const proposalAcceptCommand = defineCommand({
2381
+ meta: { name: "accept", description: "Validate and promote a proposal to a real asset" },
2382
+ args: {
2383
+ id: { type: "positional", description: "Proposal id (uuid)", required: true },
2384
+ target: { type: "string", description: "Override the write target by source name" },
2385
+ },
2386
+ async run({ args }) {
2387
+ await runWithJsonErrors(async () => {
2388
+ const result = await akmProposalAccept({ id: args.id, target: args.target });
2389
+ output("proposal-accept", result);
2390
+ });
2391
+ },
2392
+ });
2393
+ const proposalRejectCommand = defineCommand({
2394
+ meta: { name: "reject", description: "Archive a pending proposal with an optional reason" },
2395
+ args: {
2396
+ id: { type: "positional", description: "Proposal id (uuid)", required: true },
2397
+ reason: { type: "string", description: "Reason for rejection (recorded in the archived proposal)" },
2398
+ },
2399
+ run({ args }) {
2400
+ return runWithJsonErrors(() => {
2401
+ const result = akmProposalReject({ id: args.id, reason: args.reason });
2402
+ output("proposal-reject", result);
2403
+ });
2404
+ },
2405
+ });
2406
+ const proposalDiffCommand = defineCommand({
2407
+ meta: { name: "diff", description: "Show the diff between an existing asset and a pending proposal" },
2408
+ args: {
2409
+ id: { type: "positional", description: "Proposal id (uuid)", required: true },
2410
+ target: { type: "string", description: "Override the write target by source name" },
2411
+ },
2412
+ run({ args }) {
2413
+ return runWithJsonErrors(() => {
2414
+ const result = akmProposalDiff({ id: args.id, target: args.target });
2415
+ output("proposal-diff", result);
2416
+ });
2417
+ },
2418
+ });
2419
+ const proposalCommand = defineCommand({
2420
+ meta: {
2421
+ name: "proposal",
2422
+ description: "Review and promote queued asset proposals (durable storage under .akm/proposals/)",
2423
+ },
2424
+ subCommands: {
2425
+ list: proposalListCommand,
2426
+ show: proposalShowCommand,
2427
+ accept: proposalAcceptCommand,
2428
+ reject: proposalRejectCommand,
2429
+ diff: proposalDiffCommand,
2430
+ },
2431
+ });
2432
+ // ── distill (#228) ──────────────────────────────────────────────────────────
2433
+ const distillCommand = defineCommand({
2434
+ meta: {
2435
+ name: "distill",
2436
+ description: "Distil feedback for an asset into a queued lesson proposal (gated on llm.features.feedback_distillation)",
2437
+ },
2438
+ args: {
2439
+ ref: { type: "positional", description: "Asset ref (type:name) to distil from", required: true },
2440
+ "source-run": {
2441
+ type: "string",
2442
+ description: "Optional run id propagated onto the queued proposal for traceability",
2443
+ },
2444
+ "exclude-feedback-from": {
2445
+ type: "string",
2446
+ description: "Comma-separated asset refs whose feedback events MUST be filtered out before the LLM input is built. Falls back to AKM_DISTILL_EXCLUDE_FEEDBACK_FROM when omitted.",
2447
+ },
2448
+ },
2449
+ async run({ args }) {
2450
+ await runWithJsonErrors(async () => {
2451
+ const excludeFlag = getHyphenatedArg(args, "exclude-feedback-from");
2452
+ const excludeEnv = process.env.AKM_DISTILL_EXCLUDE_FEEDBACK_FROM;
2453
+ // CLI flag takes precedence over the env var when both are present.
2454
+ const excludeRaw = excludeFlag ?? excludeEnv;
2455
+ const excludeFeedbackFromRefs = parseExcludeFeedbackFromRefs(excludeRaw);
2456
+ const result = await akmDistill({
2457
+ ref: args.ref,
2458
+ sourceRun: getHyphenatedArg(args, "source-run"),
2459
+ ...(excludeFeedbackFromRefs.length > 0 ? { excludeFeedbackFromRefs } : {}),
2460
+ });
2461
+ output("distill", result);
2462
+ });
2463
+ },
2464
+ });
2465
+ /**
2466
+ * Parse a comma-separated list of asset refs (#267 — `--exclude-feedback-from`
2467
+ * and `AKM_DISTILL_EXCLUDE_FEEDBACK_FROM`). Each entry is validated against
2468
+ * the canonical `[origin//]type:name` grammar via `parseAssetRef`; an
2469
+ * invalid entry surfaces as a UsageError → exit 2.
2470
+ */
2471
+ function parseExcludeFeedbackFromRefs(raw) {
2472
+ if (raw === undefined || raw.trim() === "")
2473
+ return [];
2474
+ const refs = raw
2475
+ .split(",")
2476
+ .map((part) => part.trim())
2477
+ .filter((part) => part.length > 0);
2478
+ for (const ref of refs) {
2479
+ try {
2480
+ parseAssetRef(ref);
2481
+ }
2482
+ catch (err) {
2483
+ const message = err instanceof Error ? err.message : String(err);
2484
+ throw new UsageError(`Invalid --exclude-feedback-from ref "${ref}": ${message}`, "INVALID_FLAG_VALUE", "Each ref must match `[origin//]type:name`, e.g. skill:deploy or team//memory:auth-tips.");
2485
+ }
2486
+ }
2487
+ return refs;
2488
+ }
2489
+ function parseProposalStatus(raw) {
2490
+ if (raw === undefined)
2491
+ return undefined;
2492
+ const trimmed = raw.trim();
2493
+ if (!trimmed)
2494
+ return undefined;
2495
+ if (trimmed === "pending" || trimmed === "accepted" || trimmed === "rejected")
2496
+ return trimmed;
2497
+ throw new UsageError(`Invalid --status value: "${raw}". Expected one of: pending, accepted, rejected.`, "INVALID_FLAG_VALUE");
2498
+ }
2499
+ // ── reflect / propose (agent proposal-producers, #226) ──────────────────────
2500
+ const reflectCommand = defineCommand({
2501
+ meta: {
2502
+ name: "reflect",
2503
+ description: "Ask the configured agent CLI to review an asset (or recent feedback) and queue a revised proposal",
2504
+ },
2505
+ args: {
2506
+ ref: {
2507
+ type: "positional",
2508
+ description: "Asset ref (type:name) to reflect on. Optional — omit to reflect across recent feedback.",
2509
+ required: false,
2510
+ },
2511
+ task: { type: "string", description: "Optional task hint passed into the reflection prompt" },
2512
+ profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
2513
+ "timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
2514
+ },
2515
+ async run({ args }) {
2516
+ await runWithJsonErrors(async () => {
2517
+ const timeoutRaw = args["timeout-ms"];
2518
+ const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
2519
+ const result = await akmReflect({
2520
+ ref: typeof args.ref === "string" && args.ref.trim() ? args.ref : undefined,
2521
+ task: typeof args.task === "string" && args.task.trim() ? args.task : undefined,
2522
+ profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
2523
+ ...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
2524
+ });
2525
+ output("reflect", result);
2526
+ if (result.ok === false) {
2527
+ process.exit(EXIT_GENERAL);
2528
+ }
2529
+ });
2530
+ },
2531
+ });
2532
+ const proposeCommand = defineCommand({
2533
+ meta: {
2534
+ name: "propose",
2535
+ description: "Ask the configured agent CLI to author a brand-new asset and queue it as a proposal",
2536
+ },
2537
+ args: {
2538
+ // Optional in citty so run() is invoked when omitted; we re-validate
2539
+ // below to surface a structured UsageError (exit 2) instead of citty's
2540
+ // default help-banner exit-0.
2541
+ type: { type: "positional", description: "Asset type (skill, command, knowledge, lesson, ...)", required: false },
2542
+ name: { type: "positional", description: "Asset name (slug or path under the type dir)", required: false },
2543
+ task: { type: "string", description: "Task description for the agent (what should the asset do?)" },
2544
+ profile: { type: "string", description: "Override the agent profile (defaults to agent.default)" },
2545
+ "timeout-ms": { type: "string", description: "Override the agent CLI timeout in milliseconds" },
2546
+ },
2547
+ async run({ args }) {
2548
+ await runWithJsonErrors(async () => {
2549
+ // citty silently shows help and exits 0 when required positionals are
2550
+ // omitted. Re-validate explicitly so the exit code is 2 (USAGE) and a
2551
+ // structured JSON error reaches scripted callers.
2552
+ if (!args.type || !args.name || !args.task) {
2553
+ throw new UsageError("Usage: akm propose <type> <name> --task '<task>'.", "MISSING_REQUIRED_ARGUMENT", "Provide the asset type, name, and a --task description, e.g. `akm propose skill deploy --task 'Deploy a service'`.");
2554
+ }
2555
+ const timeoutRaw = args["timeout-ms"];
2556
+ const timeoutMs = typeof timeoutRaw === "string" && timeoutRaw.trim() ? Number.parseInt(timeoutRaw, 10) : undefined;
2557
+ const result = await akmPropose({
2558
+ type: String(args.type),
2559
+ name: String(args.name),
2560
+ task: String(args.task ?? ""),
2561
+ profile: typeof args.profile === "string" && args.profile.trim() ? args.profile : undefined,
2562
+ ...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
2563
+ });
2564
+ output("propose", result);
2565
+ if (result.ok === false) {
2566
+ process.exit(EXIT_GENERAL);
2567
+ }
2568
+ });
2569
+ },
2570
+ });
1994
2571
  const main = defineCommand({
1995
2572
  meta: {
1996
2573
  name: "akm",
@@ -1998,9 +2575,14 @@ const main = defineCommand({
1998
2575
  description: "Agent Kit Manager — search, show, and manage assets from your stash.",
1999
2576
  },
2000
2577
  args: {
2001
- format: { type: "string", description: "Output format (json|jsonl|text|yaml)" },
2002
- detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)" },
2578
+ format: { type: "string", description: "Output format (json|jsonl|text|yaml)", default: "json" },
2579
+ detail: { type: "string", description: "Detail level (brief|normal|full|summary|agent)", default: "brief" },
2003
2580
  quiet: { type: "boolean", alias: "q", description: "Suppress stderr warnings", default: false },
2581
+ verbose: {
2582
+ type: "boolean",
2583
+ description: "Print per-spec diagnostics to stderr (also honours AKM_VERBOSE env var)",
2584
+ default: false,
2585
+ },
2004
2586
  },
2005
2587
  subCommands: {
2006
2588
  setup: setupCommand,
@@ -2025,6 +2607,12 @@ const main = defineCommand({
2025
2607
  enable: enableCommand,
2026
2608
  disable: disableCommand,
2027
2609
  feedback: feedbackCommand,
2610
+ history: historyCommand,
2611
+ events: eventsCommand,
2612
+ proposal: proposalCommand,
2613
+ reflect: reflectCommand,
2614
+ propose: proposeCommand,
2615
+ distill: distillCommand,
2028
2616
  help: helpCommand,
2029
2617
  hints: hintsCommand,
2030
2618
  completions: completionsCommand,
@@ -2088,6 +2676,12 @@ async function runWithJsonErrors(fn) {
2088
2676
  if (process.argv.includes("--quiet") || process.argv.includes("-q")) {
2089
2677
  setQuiet(true);
2090
2678
  }
2679
+ // Apply --verbose flag early so per-spec diagnostics (gated behind
2680
+ // `isVerbose()` in src/core/warn.ts) are restored. The `AKM_VERBOSE`
2681
+ // env var still wins regardless — see warn.ts for the precedence rule.
2682
+ if (process.argv.includes("--verbose")) {
2683
+ setVerbose(true);
2684
+ }
2091
2685
  await fn();
2092
2686
  }
2093
2687
  catch (error) {