akm-cli 0.9.0-rc.9 → 0.9.0

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