akm-cli 0.9.0-rc.0 → 0.9.0-rc.13

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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
package/dist/cli.js CHANGED
@@ -2,28 +2,26 @@
2
2
  // This Source Code Form is subject to the terms of the Mozilla Public
3
3
  // License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- // Runtime guard: akm-cli 0.9 runs on Bun (primary) and Node.js >= 20 (#465,
6
- // #560). The runtime boundary (src/runtime.ts, src/storage/database.ts) makes
7
- // the Node path additive. Under Node the CLI must be launched via the
5
+ // Runtime guard: the akm-cli npm package bootstraps with Node.js >= 22
6
+ // (#465, #560), then its launcher prefers a working Bun >= 1.0 when available.
7
+ // The runtime boundary (src/runtime.ts, src/storage/database.ts) supports both.
8
+ // Under Node the CLI must be launched via the
8
9
  // `dist/cli-node.mjs` wrapper, which registers the text-import loader hook
9
10
  // before this module graph loads; running `node dist/cli.js` directly still
10
11
  // works for code paths that touch no embedded text asset, but the wrapper is
11
- // the supported entry. The hard floor is Node 20.12: `@clack/core` (prompts) imports
12
+ // the supported entry. The hard floor is Node 22 (Node 20 support dropped 2026-07; `@clack/core` imports
12
13
  // `node:util`'s `styleText` (added in Node 20.12) — Node 18 (EOL) throws at import.
13
14
  {
14
15
  const isBun = typeof globalThis.Bun !== "undefined";
15
16
  if (!isBun) {
16
- const [major = 0, minor = 0, patch = 0] = (process.versions.node ?? "0")
17
- .split(".")
18
- .map((part) => Number.parseInt(part, 10) || 0);
19
- const nodeOk = major > 20 || (major === 20 && (minor > 12 || (minor === 12 && patch >= 0)));
17
+ const [major = 0] = (process.versions.node ?? "0").split(".").map((part) => Number.parseInt(part, 10) || 0);
18
+ const nodeOk = major >= 22;
20
19
  if (!nodeOk) {
21
- console.error("\n ERROR: akm-cli requires the Bun runtime (https://bun.sh) or Node.js >= 20.12.\n" +
20
+ console.error("\n ERROR: the akm-cli npm package requires Node.js >= 22.\n" +
22
21
  ` Detected Node.js ${process.versions.node ?? "unknown"}.\n` +
23
- " Install options:\n" +
24
- " 1. Bun: curl -fsSL https://bun.sh/install | bash && bun install -g akm-cli\n" +
25
- " 2. Node: upgrade to Node.js 20.12 or newer (https://nodejs.org)\n" +
26
- " 3. Binary: curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\n");
22
+ " Bun >= 1.0 is optional for execution; it does not replace the Node.js bootstrap.\n" +
23
+ " Upgrade Node.js (https://nodejs.org), or install the runtime-free standalone binary:\n" +
24
+ " curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\n");
27
25
  process.exit(1);
28
26
  }
29
27
  }
@@ -44,7 +42,7 @@ process.on("unhandledRejection", (reason) => {
44
42
  }, null, 2));
45
43
  if (process.env.AKM_DEBUG === "1" && err.stack)
46
44
  console.error(err.stack);
47
- process.exit(1);
45
+ process.exit(EXIT_CODES.INTERNAL);
48
46
  });
49
47
  process.on("uncaughtException", (err) => {
50
48
  console.error(JSON.stringify({
@@ -55,92 +53,61 @@ process.on("uncaughtException", (err) => {
55
53
  }, null, 2));
56
54
  if (process.env.AKM_DEBUG === "1" && err.stack)
57
55
  console.error(err.stack);
58
- process.exit(1);
56
+ process.exit(EXIT_CODES.INTERNAL);
59
57
  });
60
58
  import fs from "node:fs";
61
- import path from "node:path";
62
- import { defineCommand, runMain } from "citty";
63
- import { findCittyTopLevelCommand } from "./cli/parse-args.js";
64
- import { EXIT_CODES, emitJsonError, output, parseAllFlagValues, runWithJsonErrors } from "./cli/shared.js";
65
- import { agentCommand, lintCommand, proposeCommand } from "./commands/agent/contribute-cli.js";
59
+ import { defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "citty";
60
+ import { findCittyTopLevelCommand, findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
61
+ import { retiredCommandHint } from "./cli/retired-commands.js";
62
+ import { defineGroupCommand, EXIT_CODES, emitJsonError, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors, } from "./cli/shared.js";
63
+ import { agentCommand, lintCommand } from "./commands/agent/contribute-cli.js";
66
64
  import { generateBashCompletions, installBashCompletions } from "./commands/completions.js";
67
65
  import { configCommand } from "./commands/config-cli.js";
68
66
  import { envCommand } from "./commands/env/env-cli.js";
69
67
  import { secretCommand } from "./commands/env/secret-cli.js";
70
68
  import { feedbackCommand } from "./commands/feedback-cli.js";
71
- import { graphCommand } from "./commands/graph/graph-cli.js";
72
69
  import { akmHealth } from "./commands/health.js";
73
- import { renderRunsDetailMd, renderWindowCompareMd } from "./commands/health/md-report.js";
70
+ import "./commands/health/renderers.js";
74
71
  import { parseWindowSpec } from "./commands/health/windows.js";
75
- import { extractCommand } from "./commands/improve/extract-cli.js";
76
72
  import { improveCommand } from "./commands/improve/improve-cli.js";
77
- import { hintsCommand, lessonsCommand, logCommand } from "./commands/observability-cli.js";
73
+ import { migrateCommand } from "./commands/migrate-cli.js";
74
+ import { logCommand } from "./commands/observability-cli.js";
78
75
  import { proposalCommand } from "./commands/proposal/proposal-cli.js";
79
76
  import { rememberCommand } from "./commands/read/remember-cli.js";
80
77
  import { curateCommand, searchCommand, showCommand } from "./commands/read/search-cli.js";
81
- import { normalizeShowArgv } from "./commands/read/show.js";
82
78
  import { registryCommand } from "./commands/registry-cli.js";
83
- import { addCommand } from "./commands/sources/add-cli.js";
79
+ import { bundleCommand } from "./commands/sources/bundle-cli.js";
84
80
  import { renderMigrationHelp } from "./commands/sources/migration-help.js";
85
- import { cloneCommand, historyCommand, listCommand, removeCommand, syncCommand, updateCommand, upgradeCommand, } from "./commands/sources/sources-cli.js";
86
- import { importKnowledgeCommand, indexCommand, infoCommand, initCommand } from "./commands/sources/stash-cli.js";
87
- import { tasksCommand } from "./commands/tasks/tasks-cli.js";
88
- import { wikiCommand } from "./commands/wiki-cli.js";
81
+ import { cloneCommand, syncCommand, upgradeCommand } from "./commands/sources/sources-cli.js";
82
+ import { importKnowledgeCommand, indexCommand, infoCommand } from "./commands/sources/stash-cli.js";
83
+ import { taskCommand } from "./commands/tasks/tasks-cli.js";
89
84
  import { workflowCommand } from "./commands/workflow-cli.js";
90
- import { bestEffort } from "./core/best-effort.js";
91
- import { loadConfig } from "./core/config/config.js";
85
+ import { DEFAULT_CONFIG, loadConfig } from "./core/config/config.js";
92
86
  import { UsageError } from "./core/errors.js";
93
- import { getCacheDir, getConfigPath, getDbPath } from "./core/paths.js";
87
+ import { assertNoPendingMigrationOperation } from "./core/migration-operation.js";
88
+ import { getConfigPath } from "./core/paths.js";
89
+ import { DURATION_UNITS, parseDuration } from "./core/time.js";
94
90
  import { plainize } from "./core/tty.js";
95
91
  import { info, isQuiet, setQuiet, setVerbose, warn } from "./core/warn.js";
96
- import { getHyphenatedBoolean, getOutputMode, initOutputMode, parseFlagValue } from "./output/context.js";
97
- import { deliverRendered, renderHtml, resolveTemplatePath } from "./output/html-render.js";
92
+ import { disposeDispatchResources } from "./integrations/agent/runner-dispatch.js";
93
+ import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "./output/cli-hints.js";
94
+ import { getOutputMode, initOutputMode, parseDetailLevel } from "./output/context.js";
95
+ import { isFormatExemptCommand } from "./output/format-exempt.js";
96
+ import { consumeSchedulerContextArg } from "./tasks/scheduler-invocation.js";
98
97
  import { pkgVersion } from "./version.js";
99
98
  function applyEarlyStderrFlags(argv) {
100
- if (argv.includes("--quiet") || argv.includes("-q")) {
99
+ const separator = argv.indexOf("--");
100
+ const ownArgv = separator === -1 ? argv : argv.slice(0, separator);
101
+ if (ownArgv.includes("--quiet") || ownArgv.includes("-q")) {
101
102
  setQuiet(true);
102
103
  }
103
- if (argv.includes("--verbose")) {
104
+ if (ownArgv.includes("--verbose")) {
104
105
  setVerbose(true);
105
106
  }
106
107
  }
107
- function resolveHelpMigrateVersionArg(version) {
108
- if (version === undefined)
109
- return undefined;
110
- const parsedFormat = parseFlagValue(process.argv, "--format");
111
- if (parsedFormat !== undefined &&
112
- version === parsedFormat &&
113
- wasHelpMigrateFlagValueConsumedAsVersion(version, parsedFormat, "--format")) {
114
- return undefined;
115
- }
116
- const parsedDetail = parseFlagValue(process.argv, "--detail");
117
- if (parsedDetail !== undefined &&
118
- version === parsedDetail &&
119
- wasHelpMigrateFlagValueConsumedAsVersion(version, parsedDetail, "--detail")) {
120
- return undefined;
121
- }
122
- return version;
123
- }
124
- function wasHelpMigrateFlagValueConsumedAsVersion(version, flagValue, flagName) {
125
- const argv = process.argv.slice(2);
126
- const helpIndex = argv.indexOf("help");
127
- const tokens = helpIndex >= 0 ? argv.slice(helpIndex + 1) : argv;
128
- const migrateIndex = tokens.indexOf("migrate");
129
- const relevant = migrateIndex >= 0 ? tokens.slice(migrateIndex + 1) : tokens;
130
- let flagIndex = -1;
131
- for (let i = 0; i < relevant.length; i += 1) {
132
- const token = relevant[i];
133
- if (token === flagName || token === `${flagName}=${flagValue}`) {
134
- flagIndex = i;
135
- break;
136
- }
137
- }
138
- if (flagIndex === -1)
139
- return false;
140
- if (relevant.slice(0, flagIndex).includes(version))
141
- return false;
142
- return relevant[flagIndex] === flagName ? relevant[flagIndex + 1] === version : true;
143
- }
108
+ // resolveHelpMigrateVersionArg moved to ./cli/invocation (chunk-9 WI-9.9
109
+ // argv-normalization fold — it re-scanned process.argv, same as
110
+ // findCittyTopLevelCommand and parseAllFlagValues below).
144
111
  /**
145
112
  * Stderr-only human-friendly hint after a non-interactive `setup` invocation.
146
113
  * Default --format is `json`, so a CI or piped consumer sees only the JSON on
@@ -160,10 +127,10 @@ function printSetupTtyHint(result) {
160
127
  return;
161
128
  if (isQuiet())
162
129
  return;
163
- if (!result?.stashDir)
130
+ if (!result?.bundleDir)
164
131
  return;
165
- console.error(plainize(`\n✓ Stash created at ${result.stashDir}\n` +
166
- ` Next: \`akm add github:itlackey/akm-stash\` then \`akm index\` to populate the stash.`));
132
+ console.error(plainize(`\n✓ Bundle created at ${result.bundleDir}\n` +
133
+ ' Next: `akm bundle add <source>`, `akm index`, `akm search "<query>"`, `akm help agents`'));
167
134
  }
168
135
  /**
169
136
  * Module Naming:
@@ -178,9 +145,14 @@ const setupCommand = defineCommand({
178
145
  description: "Interactive configuration wizard. Configures embeddings/LLM connections (for indexing/enrichment), agent profiles (CLI agent, embedded SDK, or none), sources, and registries. Shows which features are enabled at the end. Use --config <json> or --yes for non-interactive/scripting mode.",
179
146
  },
180
147
  args: {
148
+ // R-051/S11: `setup` is a raw `defineCommand` (not `defineJsonCommand`),
149
+ // so it does not get `GLOBAL_OUTPUT_ARGS` for free — same gap `health`
150
+ // already had. Three of its four run branches call `output()`; spreading
151
+ // this in is a `--help` visibility fix, not a behavior change.
152
+ ...GLOBAL_OUTPUT_ARGS,
181
153
  config: {
182
154
  type: "string",
183
- description: 'Config JSON to apply non-interactively, e.g. \'{"llm":{"endpoint":"...","model":"..."}}\'',
155
+ description: 'Config JSON to apply non-interactively, e.g. \'{"engines":{"local":{"kind":"llm","endpoint":"...","model":"..."}},"defaults":{"llmEngine":"local"}}\'',
184
156
  },
185
157
  from: {
186
158
  type: "string",
@@ -193,43 +165,29 @@ const setupCommand = defineCommand({
193
165
  },
194
166
  dir: {
195
167
  type: "string",
196
- description: "Stash directory path (overrides stashDir in config or --config JSON)",
168
+ description: "Bundle directory path (overrides defaultBundle in config or --config JSON)",
197
169
  },
198
- probe: {
170
+ // Declared as the POSITIVE name with `default: true` so citty's native
171
+ // `--no-<name>` negation (it strips a leading `--no-` from ANY token and
172
+ // negates the remainder BEFORE consulting the declared-args table) does
173
+ // the work, matching the `sync --push/--no-push` pattern. A flag
174
+ // DECLARED as `no-init` can never be negated: `--no-init` parses as
175
+ // "negate `init`", a name nothing declared, leaving the real key at its
176
+ // default forever — see `search --no-project-context`'s identical fix.
177
+ init: {
199
178
  type: "boolean",
200
- default: false,
201
- description: "Probe LLM/embedding endpoints after writing config to verify connectivity",
179
+ default: true,
180
+ description: "Scaffold the bundle directory. Use --no-init to write configuration without scaffolding it.",
202
181
  },
203
- "detect-only": {
204
- type: "boolean",
205
- default: false,
206
- description: "Run environment detection only and print the result (no prompts, no writes). Pair with --format json.",
207
- },
208
- "reset-recommended": {
182
+ probe: {
209
183
  type: "boolean",
210
184
  default: false,
211
- description: "Merge opinionated, detection-derived defaults into the existing config without removing custom keys.",
185
+ description: "Probe LLM/embedding endpoints before writing config to verify connectivity",
212
186
  },
213
187
  },
214
188
  async run({ args }) {
215
189
  await runWithJsonErrors(async () => {
216
- const noInit = getHyphenatedBoolean(args, "no-init");
217
- const detectOnly = args["detect-only"];
218
- const resetRecommended = args["reset-recommended"];
219
- if (detectOnly) {
220
- // Detection only: no prompts, no writes.
221
- const { runDetectOnly } = await import("./setup/setup.js");
222
- const detection = await runDetectOnly();
223
- output("setup", detection);
224
- return;
225
- }
226
- if (resetRecommended) {
227
- const { runResetRecommended } = await import("./setup/setup.js");
228
- const result = await runResetRecommended({ dir: args.dir, noInit, probe: args.probe });
229
- output("setup", result);
230
- printSetupTtyHint(result);
231
- return;
232
- }
190
+ const noInit = !args.init;
233
191
  if (args.from && args.config) {
234
192
  throw new UsageError("Pass either --from <file> or --config <json>, not both.", "INVALID_FLAG_VALUE");
235
193
  }
@@ -286,8 +244,17 @@ const setupCommand = defineCommand({
286
244
  },
287
245
  });
288
246
  const healthCommand = defineCommand({
289
- meta: { name: "health", description: "Check akm runtime health, artifacts, and improve metrics" },
247
+ meta: {
248
+ name: "health",
249
+ description: "Check akm runtime health, artifacts, and improve metrics",
250
+ },
290
251
  args: {
252
+ // R-051: `health` is a raw `defineCommand` (not `defineJsonCommand`), so
253
+ // it does not get `GLOBAL_OUTPUT_ARGS` for free. `--format`/`--detail`/
254
+ // `--shape`/`--output` already parsed correctly here (this command has
255
+ // no positional for a stray value to fall into), so this is purely a
256
+ // `--help` visibility / consistency fix, not a behavior change.
257
+ ...GLOBAL_OUTPUT_ARGS,
291
258
  since: {
292
259
  type: "string",
293
260
  description: "Rolling window start (ISO timestamp, date, epoch ms, or shorthand like 24h / 7d)",
@@ -304,95 +271,228 @@ const healthCommand = defineCommand({
304
271
  type: "string",
305
272
  description: "Explicit comparison window 'name=...,since=ISO,until=ISO' (repeatable, up to 4; mutually exclusive with --window-compare)",
306
273
  },
307
- compare: {
308
- type: "string",
309
- description: "Comparison window for the --format html report's trend deltas (default: 24h)",
274
+ report: {
275
+ type: "boolean",
276
+ description: "Fetch the full report dataset: per-run rows, trend deltas vs the prior window, and the pending proposal queue. Renders as the rich report under --format md/html and as complete data under any other format.",
277
+ default: false,
310
278
  },
311
279
  },
312
280
  async run({ args }) {
313
281
  let resultStatus;
282
+ const exitCodeBeforeRun = process.exitCode;
314
283
  await runWithJsonErrors(async () => {
315
284
  // citty only surfaces the last value of a repeated flag, so read --windows
316
285
  // directly from argv to support multi-window comparison.
317
286
  const rawWindows = parseAllFlagValues("--windows");
318
287
  const windows = rawWindows.length > 0 ? rawWindows.map((raw) => parseWindowSpec(raw)) : undefined;
319
288
  const groupBy = args["group-by"];
320
- const windowCompareRaw = args["window-compare"];
321
- const mode = getOutputMode();
322
- // `--format html` is health-specific: render the full HTML health
323
- // report (charts, KPI cards, advisories) from the bespoke template.
324
- // Mirrors the `md` intercept below. Two reads, exactly like the
325
- // retired akm-health-report skill: the canonical per-run window plus a
326
- // window-compare read for the trend deltas (defaults to 24h,
327
- // overridable via --compare).
328
- if (mode.format === "html") {
329
- // Default the compare window to the report's own `--since` window so the
330
- // trend deltas are like-for-like (e.g. last 7d vs the prior 7d). A fixed
331
- // 24h default made a `--since 7d` report compare its 7-day totals against
332
- // a 24-hour prior window, producing meaningless deltas.
333
- const compare = args.compare ?? windowCompareRaw ?? args.since ?? "24h";
334
- const result = akmHealth({ since: args.since, groupBy: "run", windowCompare: compare });
335
- resultStatus = result.status;
336
- const deltas = result.deltas;
337
- const { buildHealthHtmlReplacements } = await import("./commands/health/html-report.js");
338
- const { listPendingProposals } = await import("./commands/proposal/proposal.js");
339
- const replacements = buildHealthHtmlReplacements(result, {
340
- window: args.since ?? "24h",
341
- compare,
342
- proposals: listPendingProposals(),
343
- deltas,
344
- });
345
- deliverRendered(renderHtml(resolveTemplatePath("health"), replacements), mode.outputPath);
346
- return;
347
- }
348
- const result = akmHealth({
289
+ const report = args.report === true;
290
+ // `--report` is a DATA flag: it selects the richer read (per-run rows +
291
+ // window-compare deltas + the proposal queue) and nothing about the read
292
+ // depends on --format. The registered md/html renderers are pure
293
+ // functions of the result — a report-shaped result renders as the rich
294
+ // report, any other shape falls through to the generic rendering.
295
+ //
296
+ // The compare window defaults to the report's own `--since` window so the
297
+ // deltas are like-for-like (e.g. last 7d vs the prior 7d). A fixed 24h
298
+ // default made a `--since 7d` report compare its 7-day totals against a
299
+ // 24-hour prior window, producing meaningless deltas.
300
+ // Comparison-window precedence. An explicit `--window-compare` always
301
+ // wins. Otherwise `--report` seeds a like-for-like comparison from
302
+ // `--since`, but only when `--since` is a DURATION: `resolveWindowCompare`
303
+ // parses durations only, so feeding it an absolute date, ISO timestamp, or
304
+ // epoch value throws. And explicit `--windows` gets no implicit value at
305
+ // all — the two are mutually exclusive, so synthesizing one turned a valid
306
+ // invocation into a usage error.
307
+ const explicitWindows = windows !== undefined && windows.length > 0;
308
+ const sinceIsDuration = args.since !== undefined && parseDuration(args.since, DURATION_UNITS) !== null;
309
+ const implicitCompare = explicitWindows ? undefined : ((sinceIsDuration ? args.since : undefined) ?? "24h");
310
+ const windowCompare = report ? (args["window-compare"] ?? implicitCompare) : args["window-compare"];
311
+ const base = akmHealth({
349
312
  since: args.since,
350
- groupBy: groupBy,
351
- windowCompare: windowCompareRaw,
313
+ groupBy: report ? "run" : groupBy,
314
+ windowCompare,
352
315
  windows,
353
316
  });
354
- resultStatus = result.status;
355
- // `--format md` is health-specific: render a TSV-shaped per-run or
356
- // window-compare table to stdout instead of going through the JSON
357
- // envelope. Other modes fall through to the standard output() path.
358
- if (mode.format === "md") {
359
- if (result.windows && result.windows.length > 0) {
360
- deliverRendered(renderWindowCompareMd(result.windows, result.deltas), mode.outputPath);
361
- }
362
- else if (result.runs) {
363
- deliverRendered(renderRunsDetailMd(result.runs), mode.outputPath);
364
- }
365
- else {
366
- output("health", result);
367
- }
368
- }
369
- else {
370
- output("health", result);
317
+ const reportCompare = windowCompare ??
318
+ (explicitWindows
319
+ ? [...(base.windows ?? [])]
320
+ .sort((a, b) => new Date(a.since).getTime() - new Date(b.since).getTime())
321
+ .map((window) => window.name)
322
+ .join(" → ")
323
+ : undefined) ??
324
+ "24h";
325
+ resultStatus = base.status;
326
+ if (report) {
327
+ const { listPendingProposals } = await import("./commands/proposal/proposal.js");
328
+ const { computeAcceptRateBySource } = await import("./commands/health/accept-rate.js");
329
+ output("health", {
330
+ ...base,
331
+ report: {
332
+ window: args.since ?? "24h",
333
+ compare: reportCompare,
334
+ comparisonMode: explicitWindows ? "custom" : "duration",
335
+ pendingProposals: listPendingProposals().map(({ ref, source, createdAt }) => ({ ref, source, createdAt })),
336
+ acceptRateBySource: computeAcceptRateBySource(),
337
+ },
338
+ });
339
+ return;
371
340
  }
341
+ output("health", base);
372
342
  });
343
+ // R-067: `emitJsonError` (src/cli/shared.ts) no longer force-exits on the
344
+ // error path — it sets `process.exitCode` and returns, so a `--report`
345
+ // failure thrown AFTER `resultStatus` was already assigned (e.g. the
346
+ // proposal-queue read above) would otherwise leave `resultStatus`
347
+ // populated here too. Skip the status-derived exit entirely once
348
+ // `runWithJsonErrors` has already recorded a classified failure, so it is
349
+ // never clobbered by a mismatched health status.
350
+ if (process.exitCode !== exitCodeBeforeRun)
351
+ return;
373
352
  if (resultStatus === "fail") {
374
- process.exit(EXIT_GENERAL);
353
+ process.exitCode = EXIT_GENERAL;
375
354
  }
376
355
  if (resultStatus === "warn") {
377
- process.exit(EXIT_HEALTH_WARN);
356
+ process.exitCode = EXIT_HEALTH_WARN;
378
357
  }
379
358
  },
380
359
  });
381
- const helpCommand = defineCommand({
360
+ function loadAgentHints(full) {
361
+ return full ? EMBEDDED_HINTS_FULL : EMBEDDED_HINTS;
362
+ }
363
+ const hintsCommand = defineCommand({
364
+ meta: {
365
+ name: "hints",
366
+ description: "Print agent instructions on how to use akm — the complete guide by default; pass --detail brief for the short one",
367
+ },
368
+ args: {
369
+ detail: {
370
+ type: "string",
371
+ description: "Hints detail level (brief|normal|full). `brief` prints the short guide; `normal`/`full` print the complete guide.",
372
+ default: "normal",
373
+ },
374
+ },
375
+ run({ args }) {
376
+ return runWithJsonErrors(() => {
377
+ const detail = parseDetailLevel(args.detail) ?? "normal";
378
+ process.stdout.write(loadAgentHints(detail !== "brief"));
379
+ });
380
+ },
381
+ });
382
+ const completionsCommand = defineCommand({
383
+ meta: {
384
+ name: "completions",
385
+ description: "Generate or install shell completion script",
386
+ },
387
+ args: {
388
+ install: {
389
+ type: "boolean",
390
+ description: "Install completions to the appropriate directory",
391
+ default: false,
392
+ },
393
+ shell: {
394
+ type: "string",
395
+ description: "Shell type (bash)",
396
+ default: "bash",
397
+ },
398
+ },
399
+ run({ args }) {
400
+ // R-052(b): this was a bare `run()` throwing directly, so an unsupported
401
+ // `--shell` value escaped straight to citty's top-level error handling
402
+ // instead of the standard JSON envelope — exit 1 with a raw stack trace
403
+ // instead of the classified exit-2 usage error every other command
404
+ // produces (`completions` is format-exempt, so it stays a raw
405
+ // `defineCommand` rather than `defineJsonCommand`, but still needs the
406
+ // same error-classification wrapper other bare `defineCommand`s in this
407
+ // file use, e.g. `help migrate` below).
408
+ return runWithJsonErrors(() => {
409
+ if (args.shell !== "bash") {
410
+ throw new UsageError(`Unsupported shell: ${args.shell}. Only bash is supported.`);
411
+ }
412
+ const script = generateBashCompletions(main);
413
+ if (args.install) {
414
+ const dest = installBashCompletions(script);
415
+ info(`Completions installed to ${dest}`);
416
+ info(`Restart your shell or run: source ${dest}`);
417
+ }
418
+ else {
419
+ process.stdout.write(script);
420
+ }
421
+ });
422
+ },
423
+ });
424
+ const commands = {
425
+ setup: setupCommand,
426
+ index: indexCommand,
427
+ health: healthCommand,
428
+ info: infoCommand,
429
+ bundle: bundleCommand,
430
+ upgrade: upgradeCommand,
431
+ search: searchCommand,
432
+ curate: curateCommand,
433
+ show: showCommand,
434
+ workflow: workflowCommand,
435
+ remember: rememberCommand,
436
+ import: importKnowledgeCommand,
437
+ sync: syncCommand,
438
+ clone: cloneCommand,
439
+ registry: registryCommand,
440
+ migrate: migrateCommand,
441
+ config: configCommand,
442
+ feedback: feedbackCommand,
443
+ log: logCommand,
444
+ agent: agentCommand,
445
+ lint: lintCommand,
446
+ improve: improveCommand,
447
+ proposal: proposalCommand,
448
+ completions: completionsCommand,
449
+ env: envCommand,
450
+ secret: secretCommand,
451
+ task: taskCommand,
452
+ hints: hintsCommand,
453
+ };
454
+ function commandHelpTopic(name, command) {
455
+ return defineCommand({
456
+ meta: { name, description: `Print help for akm ${name}` },
457
+ async run() {
458
+ await showUsage(command, buildUsageParentForPath([name]));
459
+ },
460
+ });
461
+ }
462
+ const commandHelpTopics = Object.fromEntries(Object.entries(commands)
463
+ .filter(([name]) => name !== "migrate")
464
+ .map(([name, command]) => [name, commandHelpTopic(name, command)]));
465
+ const helpCommand = defineGroupCommand({
382
466
  meta: {
383
467
  name: "help",
384
- description: "Print focused help topics such as migration guidance for a release",
468
+ description: "Print the command overview, detailed help for a command, agent instructions, or a release's migration guidance",
385
469
  },
386
470
  subCommands: {
471
+ ...commandHelpTopics,
472
+ agents: defineCommand({
473
+ meta: {
474
+ name: "agents",
475
+ description: "Print agent instructions on how to use akm — the short guide by default; pass --full for the complete guide",
476
+ },
477
+ args: {
478
+ full: {
479
+ type: "boolean",
480
+ default: false,
481
+ description: "Print the complete guide instead of the short one.",
482
+ },
483
+ },
484
+ run({ args }) {
485
+ return runWithJsonErrors(() => {
486
+ process.stdout.write(loadAgentHints(args.full === true));
487
+ });
488
+ },
489
+ }),
387
490
  migrate: defineCommand({
388
491
  meta: {
389
492
  name: "migrate",
390
493
  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.",
391
494
  },
392
495
  args: {
393
- // Optional in citty so run() is invoked even when omitted; we
394
- // re-validate below to surface a structured UsageError (exit 2)
395
- // instead of citty's default help-banner exit-0.
396
496
  version: {
397
497
  type: "positional",
398
498
  description: "Version to review (for example 0.6.0, v0.6.0, 0.6.0-rc1, or latest)",
@@ -410,123 +510,76 @@ const helpCommand = defineCommand({
410
510
  },
411
511
  }),
412
512
  },
413
- });
414
- const completionsCommand = defineCommand({
415
- meta: {
416
- name: "completions",
417
- description: "Generate or install shell completion script",
418
- },
419
- args: {
420
- install: {
421
- type: "boolean",
422
- description: "Install completions to the appropriate directory",
423
- default: false,
424
- },
425
- shell: {
426
- type: "string",
427
- description: "Shell type (bash)",
428
- default: "bash",
429
- },
430
- },
431
- run({ args }) {
432
- if (args.shell !== "bash") {
433
- throw new UsageError(`Unsupported shell: ${args.shell}. Only bash is supported.`);
434
- }
435
- const script = generateBashCompletions(main);
436
- if (args.install) {
437
- const dest = installBashCompletions(script);
438
- info(`Completions installed to ${dest}`);
439
- info(`Restart your shell or run: source ${dest}`);
440
- }
441
- else {
442
- process.stdout.write(script);
443
- }
513
+ async defaultRun() {
514
+ process.stdout.write(`${await renderSectionedRootHelp()}\n`);
444
515
  },
445
516
  });
446
517
  export const main = defineCommand({
447
518
  meta: {
448
519
  name: "akm",
449
520
  version: pkgVersion,
450
- description: "Agent Knowledge Management — search, show, and manage assets from your stash.\n\n" +
521
+ description: "Agent Knowledge Manager — search, show, and manage assets from your bundle.\n\n" +
451
522
  "Exit codes:\n" +
452
523
  " 0 success\n" +
453
- " 1 general error / not found\n" +
524
+ " 1 not found / command-reported failure\n" +
454
525
  " 2 usage error\n" +
455
526
  " 4 health warn (akm health only)\n" +
527
+ " 70 internal / unclassified error\n" +
456
528
  " 78 config error",
457
529
  },
458
530
  args: {
459
- format: { type: "string", description: "Output format (json|jsonl|text|yaml|md|html)", default: "json" },
460
- output: {
461
- type: "string",
462
- description: "Write rendered output to a file instead of stdout (all formats except jsonl)",
463
- },
464
- detail: {
465
- type: "string",
466
- description: "Detail level (verbosity): brief|normal|full. Default: brief.",
467
- default: "brief",
468
- },
469
- shape: {
470
- type: "string",
471
- description: "Output projection: human|agent|summary. 'agent' trims to agent-essential fields; " +
472
- "'summary' is only valid on 'akm show'. Default: human.",
473
- },
474
- quiet: {
475
- type: "boolean",
476
- alias: "q",
477
- description: "Suppress non-essential stderr output (banners, spinners, progress info). " +
478
- "Safety-critical output is never suppressed: errors, destructive-action confirmation prompts, " +
479
- "and auto-migration banners always appear regardless of --quiet.",
480
- default: false,
481
- },
482
- verbose: {
483
- type: "boolean",
484
- description: "Print per-spec diagnostics to stderr (also honours AKM_VERBOSE env var)",
485
- default: false,
486
- },
531
+ // Single-sourced from GLOBAL_OUTPUT_ARGS (src/cli/shared.ts) so root help
532
+ // and leaf help state identical text. format/detail get their `default`
533
+ // added here only — the description text itself is never redeclared.
534
+ ...GLOBAL_OUTPUT_ARGS,
535
+ format: { ...GLOBAL_OUTPUT_ARGS.format, default: "json" },
536
+ detail: { ...GLOBAL_OUTPUT_ARGS.detail, default: "brief" },
487
537
  },
488
538
  subCommands: {
489
- setup: setupCommand,
490
- init: initCommand,
491
- index: indexCommand,
492
- health: healthCommand,
493
- info: infoCommand,
494
- graph: graphCommand,
495
- add: addCommand,
496
- list: listCommand,
497
- remove: removeCommand,
498
- update: updateCommand,
499
- upgrade: upgradeCommand,
500
- search: searchCommand,
501
- curate: curateCommand,
502
- show: showCommand,
503
- workflow: workflowCommand,
504
- remember: rememberCommand,
505
- import: importKnowledgeCommand,
506
- sync: syncCommand,
507
- clone: cloneCommand,
508
- registry: registryCommand,
509
- config: configCommand,
510
- feedback: feedbackCommand,
511
- history: historyCommand,
512
- log: logCommand,
513
- lessons: lessonsCommand,
514
- agent: agentCommand,
515
- lint: lintCommand,
516
- improve: improveCommand,
517
- extract: extractCommand,
518
- propose: proposeCommand,
519
- proposal: proposalCommand,
539
+ ...commands,
520
540
  help: helpCommand,
521
- hints: hintsCommand,
522
- completions: completionsCommand,
523
- env: envCommand,
524
- secret: secretCommand,
525
- wiki: wikiCommand,
526
- tasks: tasksCommand,
527
541
  },
528
542
  });
529
543
  const MAIN_TOP_LEVEL_ARGS = main.args;
544
+ function isTaskRunWithId(argv) {
545
+ const args = argv.slice(2);
546
+ const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
547
+ const command = commandIndex >= 0 ? args[commandIndex] : undefined;
548
+ if (command !== "task")
549
+ return false;
550
+ const taskArgs = args.slice(commandIndex + 1);
551
+ if (taskArgs[0] !== "run")
552
+ return false;
553
+ const runCommand = taskCommand.subCommands?.run;
554
+ if (!runCommand?.args)
555
+ return false;
556
+ try {
557
+ const parsed = parseArgs(taskArgs.slice(1), runCommand.args);
558
+ return typeof parsed.id === "string" && parsed.id.length > 0;
559
+ }
560
+ catch {
561
+ return false;
562
+ }
563
+ }
564
+ /** Recovery/setup surfaces must remain reachable when config.json is invalid. */
565
+ export function shouldBypassConfigStartup(argv) {
566
+ const userArgs = argv.slice(2);
567
+ const separator = userArgs.indexOf("--");
568
+ const args = separator === -1 ? userArgs : userArgs.slice(0, separator);
569
+ if (args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-v"))
570
+ return true;
571
+ const commandIndex = findCittyTopLevelCommandIndex(args, MAIN_TOP_LEVEL_ARGS);
572
+ const command = commandIndex >= 0 ? args[commandIndex] : undefined;
573
+ if (command === "setup" || command === "migrate")
574
+ return true;
575
+ if (isTaskRunWithId(argv))
576
+ return true;
577
+ if (command !== "config")
578
+ return false;
579
+ const configIndex = args.indexOf("config");
580
+ const subcommand = args.slice(configIndex + 1).find((arg) => !arg.startsWith("-"));
581
+ return subcommand === "path";
582
+ }
530
583
  // ── Exit codes ──────────────────────────────────────────────────────────────
531
584
  // Canonical table lives in `src/cli/shared.ts` (EXIT_CODES). These aliases keep
532
585
  // the local call sites terse. EXIT_HEALTH_WARN (4) is the `akm health` "warn"
@@ -534,21 +587,339 @@ const MAIN_TOP_LEVEL_ARGS = main.args;
534
587
  // GENERAL (1) and USAGE (2). CI monitors can map: 0=pass, 4=warn, 1=fail.
535
588
  const EXIT_GENERAL = EXIT_CODES.GENERAL;
536
589
  const EXIT_HEALTH_WARN = EXIT_CODES.HEALTH_WARN;
537
- // Only run the CLI when this module is the direct entry point. When it is
538
- // imported (e.g. by the in-process test harness in tests/_helpers/cli.ts),
539
- // `import.meta.main` is false and we skip all startup side effects (argv
540
- // mutation, output-mode init, index cleanup, banner, runMain) so importers
541
- // can drive the `main` command themselves without the process exiting.
590
+ // ── Top-level driver (replaces citty's `runMain`) ───────────────────────────
542
591
  //
543
- // Node path: this module carries a `#!/usr/bin/env bun` shebang and is launched
544
- // under Node via the `dist/cli-node.mjs` wrapper, which `import()`s this file
545
- // (so `import.meta.main` is false here even though the CLI is the real entry).
546
- // The wrapper sets `AKM_NODE_ENTRY=1` to opt into the startup block. The test
547
- // harness never sets it, so importing cli.ts under Bun stays inert as before.
548
- if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
549
- // citty reads process.argv directly and does not accept a custom argv array,
550
- // so we must replace process.argv with the normalized version before runMain.
551
- process.argv = normalizeShowArgv(process.argv);
592
+ // R-032: citty's own `runMain` catches EVERY error escaping `runCommand` —
593
+ // including its unexported `CLIError`, thrown for "Unknown command …", "No
594
+ // command specified.", "Missing required argument/positional …", and invalid
595
+ // enum values — and unconditionally calls `process.exit(1)`, regardless of
596
+ // error kind (node_modules/citty/dist/index.mjs). That collapsed usage
597
+ // mistakes (`akm totally-bogus`, `akm wiki list`, bare `akm log`) onto exit
598
+ // code 1 instead of the documented usage-error code 2 (STABILITY.md's
599
+ // exit-code table), and there is no way to override it from outside
600
+ // `runMain`'s own call frame: once it calls `process.exit`, nothing run
601
+ // afterward — including a `finally` further up the stack — gets a chance to
602
+ // execute. So the CLI drives citty's exported `runCommand` directly instead
603
+ // of `runMain`, replicating `runMain`'s `--help` / `--version`
604
+ // short-circuits and its CLIError → usage-banner rendering, but classifying
605
+ // a CLIError as USAGE (2) instead of GENERAL (1). Every other error escaping
606
+ // this boundary keeps the previous GENERAL (1) mapping — this only
607
+ // reclassifies the one error family citty itself throws before any of our
608
+ // own command bodies (and their `runWithJsonErrors` / `emitJsonError`
609
+ // classification) ever run.
610
+ const HELP_FLAGS = ["--help", "-h"];
611
+ const VERSION_FLAGS = ["--version", "-v"];
612
+ /**
613
+ * Duck-types citty's internal, unexported `CLIError`
614
+ * (node_modules/citty/dist/index.mjs) — the class `runCommand` throws for
615
+ * "Unknown command …", "No command specified.", "Missing required
616
+ * argument/positional …", and invalid enum values. citty does not export
617
+ * this class, so `instanceof` isn't available; `name` is set in its
618
+ * constructor (`this.name = "CLIError"`) and is stable across the pinned
619
+ * `citty@^0.2.2` dependency.
620
+ */
621
+ function isCittyCliError(error) {
622
+ return error instanceof Error && error.name === "CLIError";
623
+ }
624
+ function findCittySubCommandByName(subCommands, name) {
625
+ if (name in subCommands)
626
+ return subCommands[name];
627
+ for (const sub of Object.values(subCommands)) {
628
+ const alias = sub.meta?.alias;
629
+ const aliases = Array.isArray(alias) ? alias : alias ? [alias] : [];
630
+ if (aliases.includes(name))
631
+ return sub;
632
+ }
633
+ return undefined;
634
+ }
635
+ /**
636
+ * Re-implementation of citty's own (unexported) `resolveSubCommand`: walks
637
+ * `rawArgs` down the subcommand tree the same way its private
638
+ * `findSubCommandIndex` / `_findSubCommand` do, so an explicit `--help`
639
+ * renders the deepest command the user was actually invoking, matching what
640
+ * citty's own `runMain` would have shown.
641
+ */
642
+ function resolveDeepestCittyCommand(cmd, rawArgs) {
643
+ const subCommands = cmd.subCommands;
644
+ if (subCommands && Object.keys(subCommands).length > 0) {
645
+ const idx = findCittyTopLevelCommandIndex(rawArgs, (cmd.args ?? {}));
646
+ const name = idx >= 0 ? rawArgs[idx] : undefined;
647
+ if (name !== undefined) {
648
+ const sub = findCittySubCommandByName(subCommands, name);
649
+ if (sub)
650
+ return resolveDeepestCittyCommand(sub, rawArgs.slice(idx + 1));
651
+ }
652
+ }
653
+ return cmd;
654
+ }
655
+ function resolveCittyCommandPath(cmd, rawArgs, path = []) {
656
+ const subCommands = cmd.subCommands;
657
+ if (!subCommands || Object.keys(subCommands).length === 0)
658
+ return [...path];
659
+ const index = findCittyTopLevelCommandIndex(rawArgs, (cmd.args ?? {}));
660
+ const token = index >= 0 ? rawArgs[index] : undefined;
661
+ if (token === undefined)
662
+ return [...path];
663
+ const sub = findCittySubCommandByName(subCommands, token);
664
+ if (!sub)
665
+ return [...path];
666
+ const name = Object.entries(subCommands).find(([, candidate]) => candidate === sub)?.[0] ?? token;
667
+ return resolveCittyCommandPath(sub, rawArgs.slice(index + 1), [...path, name]);
668
+ }
669
+ /**
670
+ * `showUsage`/`renderUsage` (citty) render a subcommand's own USAGE line as
671
+ * `${parentMeta.name} ${cmdMeta.name}` using only the DIRECT parent — for a
672
+ * two-deep command (`akm task run`) that renders `task run`, silently
673
+ * dropping the `akm ` root prefix every top-level command's `--help` already
674
+ * shows. Building a synthetic parent whose `meta.name` is the full prefix
675
+ * (`akm task`) fixes it for any depth without patching the vendored
676
+ * dependency (S11 item 4).
677
+ */
678
+ function buildUsageParentForPath(path) {
679
+ // `version` carries through too — citty's renderUsage falls back to
680
+ // `parentMeta.version` when the resolved command itself declares none
681
+ // (true of every subcommand here), and the real parent it substitutes for
682
+ // always resolves to `main`, which does declare one.
683
+ return {
684
+ meta: {
685
+ name: ["akm", ...path.slice(0, -1)].join(" "),
686
+ version: pkgVersion,
687
+ },
688
+ };
689
+ }
690
+ // ── Sectioned root help (S11) ────────────────────────────────────────────────
691
+ //
692
+ // citty's own generated COMMANDS list is a flat, unordered dump of every
693
+ // top-level command — fine for a handful of commands, not for the ~28 this
694
+ // CLI has grown to. Groups them instead under fixed sections mirroring
695
+ // how they're actually used, reusing citty's own `renderUsage` for the
696
+ // banner/USAGE/OPTIONS portion (so it stays byte-identical to every
697
+ // subcommand's own `--help`) and replacing only the COMMANDS section.
698
+ const HELP_SECTIONS = [
699
+ {
700
+ title: "AGENT LOOP",
701
+ commands: ["curate", "search", "show", "feedback", "remember"],
702
+ },
703
+ {
704
+ title: "ASSETS",
705
+ commands: ["import", "clone", "bundle", "env", "secret", "sync", "proposal"],
706
+ },
707
+ { title: "AUTOMATION", commands: ["improve", "agent", "workflow", "task"] },
708
+ {
709
+ title: "SYSTEM",
710
+ commands: [
711
+ "setup",
712
+ "index",
713
+ "lint",
714
+ "health",
715
+ "config",
716
+ "registry",
717
+ "info",
718
+ "log",
719
+ "help",
720
+ "hints",
721
+ "upgrade",
722
+ "completions",
723
+ ],
724
+ },
725
+ ];
726
+ /** Abbreviations whose trailing period does not end a sentence. */
727
+ const NON_TERMINAL_ABBREVIATIONS = ["e.g.", "i.e.", "etc.", "vs."];
728
+ /**
729
+ * First sentence of a description, for the root command list.
730
+ *
731
+ * Several commands carry multi-paragraph descriptions that are correct on
732
+ * `akm <cmd> --help` but turn the root listing back into the undifferentiated
733
+ * wall this section replaced (a single command's description ran past 1k
734
+ * characters on one row).
735
+ */
736
+ function firstSentence(text) {
737
+ const line = text.split("\n", 1)[0]?.trim() ?? "";
738
+ for (let i = 0; i < line.length; i++) {
739
+ if (line[i] !== "." && line[i] !== "!" && line[i] !== "?")
740
+ continue;
741
+ const candidate = line.slice(0, i + 1);
742
+ const next = line[i + 1];
743
+ if (next !== undefined && next !== " ")
744
+ continue;
745
+ if (NON_TERMINAL_ABBREVIATIONS.some((abbr) => candidate.toLowerCase().endsWith(abbr)))
746
+ continue;
747
+ return candidate;
748
+ }
749
+ return line;
750
+ }
751
+ function topLevelCommandDescription(name) {
752
+ const sub = main.subCommands?.[name];
753
+ return firstSentence(sub?.meta?.description ?? "");
754
+ }
755
+ function formatCommandRows(names) {
756
+ const width = Math.max(...names.map((name) => name.length));
757
+ return names.map((name) => ` ${name.padEnd(width)} ${topLevelCommandDescription(name)}`).join("\n");
758
+ }
759
+ function renderCommandSections() {
760
+ return HELP_SECTIONS.map(({ title, commands }) => `${title}\n${formatCommandRows(commands)}`).join("\n\n");
761
+ }
762
+ /**
763
+ * Sectioned replacement for citty's root COMMANDS list (S11 item 1). Reuses
764
+ * `renderUsage(main)` for the banner + exit-code table (kept verbatim) +
765
+ * USAGE line + global OPTIONS, then cuts the string before citty's own
766
+ * "COMMANDS" heading (present because `main` has `subCommands`) and appends
767
+ * the grouped sections plus a one-line bundle/ref-grammar tagline and the
768
+ * `akm help agents` pointer.
769
+ */
770
+ async function renderSectionedRootHelp() {
771
+ const base = await renderUsage(main, undefined);
772
+ const commandsHeadingIndex = base.indexOf("COMMANDS");
773
+ // Cut at the START of the heading's own line, not the word itself — the
774
+ // word is preceded by citty's own ANSI bold/underline escape codes, and
775
+ // slicing mid-line would leave a dangling open escape sequence.
776
+ const cutIndex = commandsHeadingIndex === -1 ? -1 : base.lastIndexOf("\n", commandsHeadingIndex);
777
+ const head = (cutIndex === -1 ? base : base.slice(0, cutIndex)).replace(/\n+$/, "");
778
+ const epilogue = [
779
+ 'A "bundle" is your managed directory of assets (skills, agents, memories, workflows, ...). Refs use the ' +
780
+ "grammar [bundle//]conceptId[#fragment], where the concept id is the bundle's own path for the asset — " +
781
+ "e.g. `akm show skills/deploy`, or `akm show work//skills/deploy` for a named bundle. Copy ids from " +
782
+ "`akm search` output rather than synthesizing them.",
783
+ "",
784
+ "Run `akm help <command>` or `akm <command> --help` for details on any command.",
785
+ "Agents: run `akm hints` for the complete guide or `akm help agents` for the short guide.",
786
+ ].join("\n");
787
+ return [head, "", renderCommandSections(), "", epilogue].join("\n");
788
+ }
789
+ /**
790
+ * Walk down the subcommand tree the same way {@link resolveDeepestCittyCommand}
791
+ * does, but stop and report the first token that fails to resolve instead of
792
+ * silently giving up — the attempted spelling plus its sibling candidate set,
793
+ * for the did-you-mean suggestion below. Returns undefined when every token
794
+ * resolved (e.g. "no command specified", or a missing positional/flag on an
795
+ * otherwise-valid command) — there is no unknown NAME to suggest a fix for.
796
+ */
797
+ function findUnknownCommandAttempt(rawArgs) {
798
+ let cmd = main;
799
+ let args = rawArgs;
800
+ const parentPath = [];
801
+ for (;;) {
802
+ const subCommands = cmd.subCommands;
803
+ if (!subCommands || Object.keys(subCommands).length === 0)
804
+ return undefined;
805
+ const idx = findCittyTopLevelCommandIndex(args, (cmd.args ?? {}));
806
+ const token = idx >= 0 ? args[idx] : undefined;
807
+ if (token === undefined)
808
+ return undefined;
809
+ const sub = findCittySubCommandByName(subCommands, token);
810
+ if (!sub)
811
+ return {
812
+ attempted: token,
813
+ candidates: Object.keys(subCommands),
814
+ parentPath,
815
+ };
816
+ parentPath.push(token);
817
+ cmd = sub;
818
+ args = args.slice(idx + 1);
819
+ }
820
+ }
821
+ /**
822
+ * Standard edit-distance DP, single-row-at-a-time (sizes here are always
823
+ * short command-name strings). Builds each row left-to-right, appending as
824
+ * it goes, so every index read below is already-populated — the `?? 0`
825
+ * fallbacks only satisfy `noUncheckedIndexedAccess`, they never fire.
826
+ */
827
+ function levenshteinDistance(a, b) {
828
+ let previousRow = Array.from({ length: b.length + 1 }, (_, j) => j);
829
+ for (let i = 1; i <= a.length; i++) {
830
+ const currentRow = [i];
831
+ for (let j = 1; j <= b.length; j++) {
832
+ const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
833
+ const deletion = (previousRow[j] ?? 0) + 1;
834
+ const insertion = (currentRow[j - 1] ?? 0) + 1;
835
+ const substitution = (previousRow[j - 1] ?? 0) + substitutionCost;
836
+ currentRow.push(Math.min(deletion, insertion, substitution));
837
+ }
838
+ previousRow = currentRow;
839
+ }
840
+ return previousRow[b.length] ?? 0;
841
+ }
842
+ /** Closest candidate within a length-scaled distance threshold, or undefined when nothing is close enough to be worth suggesting. */
843
+ function closestCommandMatch(attempted, candidates) {
844
+ let best;
845
+ let bestDistance = Number.POSITIVE_INFINITY;
846
+ for (const candidate of candidates) {
847
+ const distance = levenshteinDistance(attempted, candidate);
848
+ if (distance < bestDistance) {
849
+ bestDistance = distance;
850
+ best = candidate;
851
+ }
852
+ }
853
+ const threshold = Math.max(2, Math.ceil(attempted.length / 2));
854
+ return best !== undefined && bestDistance <= threshold ? best : undefined;
855
+ }
856
+ const CLI_HELP_POINTER = "Run `akm --help` for usage.";
857
+ /**
858
+ * citty's `CLIError` carries its own stable `.code` (not exported in its
859
+ * types, but present on every instance — see {@link isCittyCliError}'s doc
860
+ * comment): `E_UNKNOWN_COMMAND` for an unrecognized command/subcommand name,
861
+ * `EARG`/`E_NO_COMMAND` for a missing required argument/positional or a bare
862
+ * group with no `run`. Maps that to one of this CLI's own stable
863
+ * `UsageErrorCode`s so the reclassified error (below) carries a real code
864
+ * instead of the generic fallback.
865
+ */
866
+ function cittyCliErrorUsageCode(error) {
867
+ switch (error.code) {
868
+ case "E_UNKNOWN_COMMAND":
869
+ return "UNKNOWN_COMMAND";
870
+ case "EARG":
871
+ case "E_NO_COMMAND":
872
+ return "MISSING_REQUIRED_ARGUMENT";
873
+ default:
874
+ return "INVALID_FLAG_VALUE";
875
+ }
876
+ }
877
+ /**
878
+ * Route a citty `CLIError` through the standard JSON envelope instead of
879
+ * citty's own usage-banner + raw `console.error(message)` (S11 item 3): the
880
+ * one-line diagnosis is the error's own message (e.g. "Unknown command
881
+ * foo"), and the hint is a short pointer — with a did-you-mean suggestion
882
+ * prepended when the unresolved token is close to a real sibling command —
883
+ * rather than the full ~46KB usage dump citty would otherwise print.
884
+ */
885
+ function toUsageErrorFromCliError(error, rawArgs) {
886
+ const code = cittyCliErrorUsageCode(error);
887
+ const attempt = code === "UNKNOWN_COMMAND" ? findUnknownCommandAttempt(rawArgs) : undefined;
888
+ // Retired spellings from the 0.9 hard break get their replacement, not a
889
+ // did-you-mean: edit distance suggests the WRONG command for most of them
890
+ // (`init`→`info`, `update`→`upgrade`), and agents follow suggestions.
891
+ const retired = attempt ? retiredCommandHint(attempt.parentPath, attempt.attempted) : undefined;
892
+ const suggestion = retired === undefined && attempt ? closestCommandMatch(attempt.attempted, attempt.candidates) : undefined;
893
+ const hint = retired ?? (suggestion ? `Did you mean \`${suggestion}\`? ${CLI_HELP_POINTER}` : CLI_HELP_POINTER);
894
+ // citty colorizes error.message with ANSI escapes even when stdout/stderr
895
+ // is not a TTY. Strip them so the JSON envelope's `error` field is plain
896
+ // text instead of embedding raw escape sequences.
897
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — strip ANSI escape codes
898
+ const message = error.message.replace(/\x1b\[[0-9;]*m/g, "");
899
+ return new UsageError(message, code, hint);
900
+ }
901
+ /**
902
+ * The CLI's real startup sequence, extracted into a function so error paths
903
+ * can `return` early — top-level `return` is a syntax error in an ES module,
904
+ * and this used to rely on `emitJsonError`'s `never` return type (a
905
+ * synchronous `process.exit`) to stop execution instead. Now that
906
+ * `emitJsonError` (src/cli/shared.ts, R-067) only records `process.exitCode`
907
+ * and returns, every direct call site here needs its own explicit `return;`
908
+ * to stop the rest of startup from running after a fatal early error.
909
+ */
910
+ async function runCli() {
911
+ try {
912
+ process.argv = consumeSchedulerContextArg(process.argv);
913
+ }
914
+ catch (error) {
915
+ emitJsonError(error);
916
+ return;
917
+ }
918
+ // Mint the ParsedInvocation singleton from the (normalized) argv — the ONE
919
+ // place argv is parsed for the whole process (plan §10.7 / chunk-9 WI-9.9).
920
+ // Every out-of-cli.ts command module reads argv state through
921
+ // `getParsedInvocation()` from here on instead of re-scanning process.argv.
922
+ setParsedInvocation(process.argv);
552
923
  // Resolve output mode once at startup from the (normalized) argv and persisted
553
924
  // config. All subsequent output() calls read from this in-memory singleton.
554
925
  // `initOutputMode` can throw a UsageError when --format/--detail values are
@@ -556,10 +927,14 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
556
927
  // rather than letting the raw exception escape with a stack trace.
557
928
  try {
558
929
  applyEarlyStderrFlags(process.argv);
559
- initOutputMode(process.argv, loadConfig().output ?? {});
930
+ if (isTaskRunWithId(process.argv))
931
+ assertNoPendingMigrationOperation();
932
+ const bypassConfig = shouldBypassConfigStartup(process.argv);
933
+ initOutputMode(process.argv, bypassConfig ? (DEFAULT_CONFIG.output ?? {}) : (loadConfig().output ?? {}));
560
934
  }
561
935
  catch (error) {
562
936
  emitJsonError(error);
937
+ return;
563
938
  }
564
939
  // `--shape summary` is only meaningful on `akm show`. Reject it up front for
565
940
  // every other command so a write command (e.g. `akm proposal accept …`)
@@ -567,23 +942,23 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
567
942
  // output-shaping time after the side effect has already happened. The
568
943
  // shape-registry gate in shapeForCommand() remains as defense-in-depth (and
569
944
  // covers the in-process test harness, which skips this startup block).
570
- const topLevelCommand = findCittyTopLevelCommand(process.argv.slice(2), MAIN_TOP_LEVEL_ARGS);
945
+ const commandPath = resolveCittyCommandPath(main, process.argv.slice(2));
946
+ const topLevelCommand = commandPath[0] ?? findCittyTopLevelCommand(process.argv.slice(2), MAIN_TOP_LEVEL_ARGS);
571
947
  if (getOutputMode().shape === "summary" && topLevelCommand !== "show") {
572
948
  emitJsonError(new UsageError("'--shape summary' is only valid on 'akm show'.", "INVALID_SHAPE_VALUE"));
949
+ return;
950
+ }
951
+ // D7 — every command that renders through output() honours all six --format
952
+ // values. The declared exempt set (src/output/format-exempt.ts) does not
953
+ // render an envelope at all, so warn rather than pretend: silently ignoring
954
+ // the flag is what made the old md/html behaviour so hard to discover. A
955
+ // warning, not an error, because the flag is harmless here and scripts that
956
+ // pass --format globally to a mixed batch of commands should still work.
957
+ const invocation = getParsedInvocation();
958
+ if ((invocation.hasFlag("--format") || invocation.getFlagValue("--format") !== undefined) &&
959
+ isFormatExemptCommand(commandPath)) {
960
+ warn(`[output] '--format' has no effect on 'akm ${commandPath.join(" ")}' — its output is not a result envelope.`);
573
961
  }
574
- // One-time cleanup of stale 0.7.x index file at the old cache location.
575
- // 0.8.0 moved the index to $XDG_DATA_HOME/akm/index.db (getDataDir()).
576
- // If the old file exists at $XDG_CACHE_HOME/akm/index.db, remove it so the
577
- // user isn't confused by a phantom DB. Best-effort; never fatal.
578
- bestEffort(() => {
579
- const oldIndexPath = path.join(getCacheDir(), "index.db");
580
- if (fs.existsSync(oldIndexPath)) {
581
- fs.rmSync(oldIndexPath, { force: true });
582
- fs.rmSync(`${oldIndexPath}-shm`, { force: true });
583
- fs.rmSync(`${oldIndexPath}-wal`, { force: true });
584
- warn(`Cleaned up stale 0.7.x index from ${oldIndexPath}. Canonical path is now ${getDbPath()}.`);
585
- }
586
- }, "stale 0.7.x index cleanup is non-fatal");
587
962
  // First-time-user breadcrumb: when run with no subcommand AND no config
588
963
  // exists yet AND stderr is a TTY, print a friendly pointer to `akm setup`
589
964
  // above citty's auto-generated usage block. Triggers only when stdin/stderr
@@ -608,5 +983,84 @@ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1") {
608
983
  }
609
984
  console.error(plainize("👋 First time with akm? Run `akm setup` to get started.\n Docs: https://github.com/itlackey/akm#readme\n"));
610
985
  })();
611
- runMain(main);
986
+ const rawArgs = process.argv.slice(2);
987
+ try {
988
+ if (rawArgs.length === 0) {
989
+ process.stdout.write(`${await renderSectionedRootHelp()}\n`);
990
+ return;
991
+ }
992
+ // Mirrors citty's own builtin-flag short-circuit in `runMain` (main's own
993
+ // args never declare `help`/`h`/`version`/`v`, so both stay the fixed
994
+ // defaults citty would have computed too).
995
+ //
996
+ // Scan only akm's OWN arguments: everything after a literal `--` belongs to
997
+ // the child process (`akm env run <ref> -- tool --help`, `akm secret run
998
+ // <ref> -- tool -h`). Scanning the tail printed akm's usage and returned
999
+ // without ever launching the requested command.
1000
+ const passthroughAt = rawArgs.indexOf("--");
1001
+ const ownArgs = passthroughAt === -1 ? rawArgs : rawArgs.slice(0, passthroughAt);
1002
+ if (HELP_FLAGS.some((flag) => ownArgs.includes(flag))) {
1003
+ const resolved = resolveDeepestCittyCommand(main, rawArgs);
1004
+ if (resolved === main) {
1005
+ // Root `--help` (S11 item 1): the sectioned overview, not citty's
1006
+ // flat COMMANDS dump.
1007
+ process.stdout.write(`${await renderSectionedRootHelp()}\n`);
1008
+ }
1009
+ else {
1010
+ const path = resolveCittyCommandPath(main, rawArgs);
1011
+ await showUsage(resolved, buildUsageParentForPath(path));
1012
+ }
1013
+ return;
1014
+ }
1015
+ if (rawArgs.length === 1 && VERSION_FLAGS.includes(rawArgs[0])) {
1016
+ console.log(pkgVersion);
1017
+ return;
1018
+ }
1019
+ await runCommand(main, { rawArgs });
1020
+ }
1021
+ catch (error) {
1022
+ if (isCittyCliError(error)) {
1023
+ // R-032/S11: reclassify citty's own "unknown command" / "no command
1024
+ // specified" / "missing required argument" family as USAGE (2) and
1025
+ // route it through the SAME JSON envelope every other command's
1026
+ // failure uses (`emitJsonError`), rather than citty's own usage-banner
1027
+ // + raw `console.error(message)` — a one-line diagnosis plus a short
1028
+ // hint (with a did-you-mean suggestion when applicable), not a ~46KB
1029
+ // usage dump.
1030
+ emitJsonError(toUsageErrorFromCliError(error, rawArgs));
1031
+ return;
1032
+ }
1033
+ // Anything else escaping here is a genuinely unexpected failure outside
1034
+ // any command's own error handling — every command wraps its body in
1035
+ // `runWithJsonErrors`, `defineJsonCommand`, or `defineGroupCommand`, all
1036
+ // three of which route thrown errors through `emitJsonError` before they
1037
+ // could ever reach this boundary. Route it the same way rather than
1038
+ // hard-coding GENERAL(1): the CLI contract reserves 1 for general/not-found
1039
+ // and requires a non-`AkmError` throw to render the JSON failure envelope
1040
+ // with INTERNAL(70) (AGENTS.md "CLI Contract"), which is what lets
1041
+ // automation tell an internal defect apart from an ordinary failure.
1042
+ // `emitJsonError` classifies and sets `process.exitCode` itself.
1043
+ emitJsonError(error);
1044
+ }
1045
+ finally {
1046
+ await disposeDispatchResources();
1047
+ }
1048
+ }
1049
+ // Only run the CLI when this module is the direct entry point. When it is
1050
+ // imported (e.g. by the in-process test harness in tests/_helpers/cli.ts),
1051
+ // `import.meta.main` is false and we skip all startup side effects (argv
1052
+ // mutation, output-mode init, index cleanup, banner, command dispatch) so
1053
+ // importers can drive the `main` command themselves without the process
1054
+ // exiting.
1055
+ //
1056
+ // Node path: this module carries a `#!/usr/bin/env bun` shebang and is launched
1057
+ // under Node via the `dist/cli-node.mjs` wrapper, which `import()`s this file
1058
+ // (so `import.meta.main` is false here even though the CLI is the real entry).
1059
+ // The wrapper sets `AKM_NODE_ENTRY=1` to opt into the startup block. Compiled
1060
+ // standalone binaries are the same shape: their entry is
1061
+ // `scripts/akm-standalone.ts` (which also embeds the akm-migrate tool), and it
1062
+ // sets `AKM_STANDALONE_ENTRY=1` before importing this file. The test harness
1063
+ // sets neither, so importing cli.ts under Bun stays inert as before.
1064
+ if (import.meta.main || process.env.AKM_NODE_ENTRY === "1" || process.env.AKM_STANDALONE_ENTRY === "1") {
1065
+ await runCli();
612
1066
  }