akm-cli 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +712 -34
  3. package/dist/{commands → src/commands}/config-cli.js +47 -4
  4. package/dist/src/commands/distill.js +283 -0
  5. package/dist/src/commands/events.js +108 -0
  6. package/dist/src/commands/history.js +191 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +1 -1
  8. package/dist/src/commands/proposal.js +119 -0
  9. package/dist/src/commands/propose.js +171 -0
  10. package/dist/src/commands/reflect.js +193 -0
  11. package/dist/{commands → src/commands}/registry-search.js +71 -7
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +104 -4
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +73 -0
  16. package/dist/{commands → src/commands}/source-add.js +5 -1
  17. package/dist/{commands → src/commands}/source-manage.js +7 -1
  18. package/dist/{core → src/core}/asset-ref.js +5 -5
  19. package/dist/{core → src/core}/asset-spec.js +12 -0
  20. package/dist/{core → src/core}/common.js +1 -1
  21. package/dist/{core → src/core}/config.js +203 -121
  22. package/dist/{core → src/core}/errors.js +4 -0
  23. package/dist/src/core/events.js +239 -0
  24. package/dist/src/core/lesson-lint.js +86 -0
  25. package/dist/src/core/proposals.js +406 -0
  26. package/dist/src/core/warn.js +72 -0
  27. package/dist/{core → src/core}/write-source.js +80 -5
  28. package/dist/{indexer → src/indexer}/db-search.js +114 -24
  29. package/dist/{indexer → src/indexer}/db.js +76 -23
  30. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  31. package/dist/src/indexer/graph-boost.js +179 -0
  32. package/dist/src/indexer/graph-extraction.js +212 -0
  33. package/dist/{indexer → src/indexer}/indexer.js +88 -7
  34. package/dist/{indexer → src/indexer}/matchers.js +1 -1
  35. package/dist/src/indexer/memory-inference.js +263 -0
  36. package/dist/{indexer → src/indexer}/metadata.js +111 -3
  37. package/dist/{indexer → src/indexer}/search-source.js +4 -2
  38. package/dist/src/integrations/agent/config.js +292 -0
  39. package/dist/src/integrations/agent/detect.js +94 -0
  40. package/dist/src/integrations/agent/index.js +17 -0
  41. package/dist/src/integrations/agent/profiles.js +65 -0
  42. package/dist/src/integrations/agent/prompts.js +167 -0
  43. package/dist/src/integrations/agent/spawn.js +272 -0
  44. package/dist/{integrations → src/integrations}/github.js +9 -3
  45. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  46. package/dist/{llm → src/llm}/client.js +33 -2
  47. package/dist/{llm → src/llm}/embedders/remote.js +37 -3
  48. package/dist/src/llm/feature-gate.js +108 -0
  49. package/dist/src/llm/graph-extract.js +107 -0
  50. package/dist/src/llm/index-passes.js +35 -0
  51. package/dist/src/llm/memory-infer.js +86 -0
  52. package/dist/{output → src/output}/cli-hints.js +15 -2
  53. package/dist/{output → src/output}/renderers.js +63 -2
  54. package/dist/src/output/shapes.js +523 -0
  55. package/dist/src/output/text.js +1116 -0
  56. package/dist/{registry → src/registry}/build-index.js +19 -8
  57. package/dist/{registry → src/registry}/factory.js +0 -8
  58. package/dist/{registry → src/registry}/providers/static-index.js +6 -3
  59. package/dist/{registry → src/registry}/resolve.js +68 -2
  60. package/dist/{setup → src/setup}/setup.js +52 -5
  61. package/dist/{sources → src/sources}/providers/git.js +7 -15
  62. package/dist/{wiki → src/wiki}/wiki.js +54 -6
  63. package/dist/{workflows → src/workflows}/runs.js +37 -3
  64. package/dist/tests/add-website-source.test.js +119 -0
  65. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  66. package/dist/tests/agent/agent-config.test.js +221 -0
  67. package/dist/tests/agent/agent-detect.test.js +100 -0
  68. package/dist/tests/agent/agent-spawn.test.js +234 -0
  69. package/dist/tests/agent-output.test.js +186 -0
  70. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  71. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  72. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  73. package/dist/tests/asset-ref.test.js +192 -0
  74. package/dist/tests/asset-registry.test.js +103 -0
  75. package/dist/tests/asset-spec.test.js +241 -0
  76. package/dist/tests/bench/attribution.test.js +996 -0
  77. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  78. package/dist/tests/bench/cleanup.js +234 -0
  79. package/dist/tests/bench/cleanup.test.js +166 -0
  80. package/dist/tests/bench/cli.js +1018 -0
  81. package/dist/tests/bench/cli.test.js +445 -0
  82. package/dist/tests/bench/compare.test.js +556 -0
  83. package/dist/tests/bench/corpus.js +317 -0
  84. package/dist/tests/bench/corpus.test.js +258 -0
  85. package/dist/tests/bench/doctor.js +525 -0
  86. package/dist/tests/bench/driver.js +401 -0
  87. package/dist/tests/bench/driver.test.js +584 -0
  88. package/dist/tests/bench/environment.js +233 -0
  89. package/dist/tests/bench/environment.test.js +199 -0
  90. package/dist/tests/bench/evolve-metrics.js +179 -0
  91. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  92. package/dist/tests/bench/evolve.js +647 -0
  93. package/dist/tests/bench/evolve.test.js +624 -0
  94. package/dist/tests/bench/failure-modes.test.js +349 -0
  95. package/dist/tests/bench/feedback-integrity.test.js +457 -0
  96. package/dist/tests/bench/leakage.test.js +228 -0
  97. package/dist/tests/bench/learning-curve.test.js +134 -0
  98. package/dist/tests/bench/metrics.js +2395 -0
  99. package/dist/tests/bench/metrics.test.js +1150 -0
  100. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  101. package/dist/tests/bench/opencode-config.js +194 -0
  102. package/dist/tests/bench/opencode-config.test.js +370 -0
  103. package/dist/tests/bench/report.js +1885 -0
  104. package/dist/tests/bench/report.test.js +1038 -0
  105. package/dist/tests/bench/run-config.js +355 -0
  106. package/dist/tests/bench/run-config.test.js +298 -0
  107. package/dist/tests/bench/run-curate-test.js +32 -0
  108. package/dist/tests/bench/run-failing-tasks.js +56 -0
  109. package/dist/tests/bench/run-full-bench.js +51 -0
  110. package/dist/tests/bench/run-items36-targeted.js +69 -0
  111. package/dist/tests/bench/run-nano-quick.js +42 -0
  112. package/dist/tests/bench/run-waveg-targeted.js +62 -0
  113. package/dist/tests/bench/runner.js +699 -0
  114. package/dist/tests/bench/runner.test.js +958 -0
  115. package/dist/tests/bench/search-bridge.test.js +331 -0
  116. package/dist/tests/bench/tmp.js +131 -0
  117. package/dist/tests/bench/trajectory.js +116 -0
  118. package/dist/tests/bench/trajectory.test.js +127 -0
  119. package/dist/tests/bench/verifier.js +114 -0
  120. package/dist/tests/bench/verifier.test.js +118 -0
  121. package/dist/tests/bench/workflow-evaluator.js +557 -0
  122. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  123. package/dist/tests/bench/workflow-spec.js +345 -0
  124. package/dist/tests/bench/workflow-spec.test.js +363 -0
  125. package/dist/tests/bench/workflow-trace.js +472 -0
  126. package/dist/tests/bench/workflow-trace.test.js +254 -0
  127. package/dist/tests/benchmark-search-quality.js +536 -0
  128. package/dist/tests/benchmark-suite.js +1441 -0
  129. package/dist/tests/capture-cli.test.js +112 -0
  130. package/dist/tests/cli-errors.test.js +204 -0
  131. package/dist/tests/commands/events.test.js +370 -0
  132. package/dist/tests/commands/history.test.js +418 -0
  133. package/dist/tests/commands/import.test.js +103 -0
  134. package/dist/tests/commands/proposal-cli.test.js +209 -0
  135. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  136. package/dist/tests/commands/remember.test.js +97 -0
  137. package/dist/tests/commands/scope-flags.test.js +300 -0
  138. package/dist/tests/commands/search.test.js +537 -0
  139. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  140. package/dist/tests/commands/show.test.js +294 -0
  141. package/dist/tests/common.test.js +266 -0
  142. package/dist/tests/completions.test.js +142 -0
  143. package/dist/tests/config-cli.test.js +193 -0
  144. package/dist/tests/config-llm-features.test.js +139 -0
  145. package/dist/tests/config.test.js +569 -0
  146. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  147. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  148. package/dist/tests/contracts/spec-helpers.js +46 -0
  149. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  150. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  151. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  152. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  153. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  154. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  155. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  156. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  157. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  158. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  159. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  160. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  161. package/dist/tests/core/write-source.test.js +366 -0
  162. package/dist/tests/curate-command.test.js +87 -0
  163. package/dist/tests/db-scoring.test.js +201 -0
  164. package/dist/tests/db.test.js +654 -0
  165. package/dist/tests/distill-cli-flag.test.js +208 -0
  166. package/dist/tests/distill.test.js +515 -0
  167. package/dist/tests/docker-install.test.js +120 -0
  168. package/dist/tests/e2e.test.js +1419 -0
  169. package/dist/tests/embedder.test.js +340 -0
  170. package/dist/tests/embedding-model-config.test.js +379 -0
  171. package/dist/tests/feedback-command.test.js +172 -0
  172. package/dist/tests/file-context.test.js +552 -0
  173. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  174. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  175. package/dist/tests/fixtures/stashes/load.js +166 -0
  176. package/dist/tests/fixtures/stashes/load.test.js +97 -0
  177. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  178. package/dist/tests/frontmatter.test.js +190 -0
  179. package/dist/tests/fts-field-weighting.test.js +254 -0
  180. package/dist/tests/fuzzy-search.test.js +230 -0
  181. package/dist/tests/git-provider-clone.test.js +45 -0
  182. package/dist/tests/github.test.js +161 -0
  183. package/dist/tests/graph-boost-ranking.test.js +305 -0
  184. package/dist/tests/graph-extraction.test.js +282 -0
  185. package/dist/tests/helpers/usage-events.js +8 -0
  186. package/dist/tests/index-pass-llm.test.js +161 -0
  187. package/dist/tests/indexer.test.js +570 -0
  188. package/dist/tests/info-command.test.js +166 -0
  189. package/dist/tests/init.test.js +69 -0
  190. package/dist/tests/install-script.test.js +246 -0
  191. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  192. package/dist/tests/issue-36-repro.test.js +304 -0
  193. package/dist/tests/issues-191-194.test.js +160 -0
  194. package/dist/tests/lesson-lint.test.js +111 -0
  195. package/dist/tests/llm-client.test.js +115 -0
  196. package/dist/tests/llm-feature-gate.test.js +151 -0
  197. package/dist/tests/llm.test.js +139 -0
  198. package/dist/tests/lockfile.test.js +216 -0
  199. package/dist/tests/manifest.test.js +205 -0
  200. package/dist/tests/markdown.test.js +126 -0
  201. package/dist/tests/matchers-unit.test.js +189 -0
  202. package/dist/tests/memory-inference.test.js +299 -0
  203. package/dist/tests/merge-scoring.test.js +136 -0
  204. package/dist/tests/metadata.test.js +313 -0
  205. package/dist/tests/migration-help.test.js +89 -0
  206. package/dist/tests/origin-resolve.test.js +124 -0
  207. package/dist/tests/output-baseline.test.js +218 -0
  208. package/dist/tests/output-shapes-unit.test.js +478 -0
  209. package/dist/tests/parallel-search.test.js +272 -0
  210. package/dist/tests/parameter-metadata.test.js +365 -0
  211. package/dist/tests/paths.test.js +177 -0
  212. package/dist/tests/progressive-disclosure.test.js +280 -0
  213. package/dist/tests/proposals.test.js +279 -0
  214. package/dist/tests/proposed-quality.test.js +271 -0
  215. package/dist/tests/provider-registry.test.js +32 -0
  216. package/dist/tests/ranking-regression.test.js +548 -0
  217. package/dist/tests/reflect-propose.test.js +455 -0
  218. package/dist/tests/registry-build-index.test.js +394 -0
  219. package/dist/tests/registry-cli.test.js +290 -0
  220. package/dist/tests/registry-index-v2.test.js +430 -0
  221. package/dist/tests/registry-install.test.js +728 -0
  222. package/dist/tests/registry-providers/parity.test.js +189 -0
  223. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  224. package/dist/tests/registry-providers/static-index.test.js +238 -0
  225. package/dist/tests/registry-resolve.test.js +126 -0
  226. package/dist/tests/registry-search.test.js +923 -0
  227. package/dist/tests/remember-frontmatter.test.js +378 -0
  228. package/dist/tests/remember-unit.test.js +123 -0
  229. package/dist/tests/ripgrep-install.test.js +251 -0
  230. package/dist/tests/ripgrep-resolve.test.js +108 -0
  231. package/dist/tests/ripgrep.test.js +163 -0
  232. package/dist/tests/save-command.test.js +94 -0
  233. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  234. package/dist/tests/scoring-pipeline.test.js +648 -0
  235. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  236. package/dist/tests/self-update.test.js +442 -0
  237. package/dist/tests/semantic-search-e2e.test.js +512 -0
  238. package/dist/tests/semantic-status.test.js +471 -0
  239. package/dist/tests/setup-run.integration.js +877 -0
  240. package/dist/tests/setup-wizard.test.js +198 -0
  241. package/dist/tests/setup.test.js +131 -0
  242. package/dist/tests/source-add.test.js +11 -0
  243. package/dist/tests/source-clone.test.js +254 -0
  244. package/dist/tests/source-manage.test.js +366 -0
  245. package/dist/tests/source-providers/filesystem.test.js +82 -0
  246. package/dist/tests/source-providers/git.test.js +252 -0
  247. package/dist/tests/source-providers/website.test.js +128 -0
  248. package/dist/tests/source-qa-fixes.test.js +286 -0
  249. package/dist/tests/source-registry.test.js +350 -0
  250. package/dist/tests/source-resolve.test.js +100 -0
  251. package/dist/tests/source-source.test.js +281 -0
  252. package/dist/tests/source.test.js +533 -0
  253. package/dist/tests/tar-utils-scan.test.js +73 -0
  254. package/dist/tests/toggle-components.test.js +73 -0
  255. package/dist/tests/usage-telemetry.test.js +265 -0
  256. package/dist/tests/utility-scoring.test.js +558 -0
  257. package/dist/tests/vault-load-error.test.js +78 -0
  258. package/dist/tests/vault-qa-fixes.test.js +194 -0
  259. package/dist/tests/vault.test.js +429 -0
  260. package/dist/tests/vector-search.test.js +608 -0
  261. package/dist/tests/walker.test.js +252 -0
  262. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  263. package/dist/tests/wave2-cluster-d.test.js +180 -0
  264. package/dist/tests/wave2-cluster-e.test.js +179 -0
  265. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  266. package/dist/tests/wiki.test.js +529 -0
  267. package/dist/tests/workflow-cli.test.js +271 -0
  268. package/dist/tests/workflow-markdown.test.js +171 -0
  269. package/dist/tests/workflow-path-escape.test.js +132 -0
  270. package/dist/tests/workflow-qa-fixes.test.js +395 -0
  271. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  272. package/docs/README.md +8 -0
  273. package/docs/migration/release-notes/0.7.0.md +244 -0
  274. package/package.json +2 -2
  275. package/dist/core/warn.js +0 -27
  276. package/dist/output/shapes.js +0 -212
  277. package/dist/output/text.js +0 -520
  278. /package/dist/{commands → src/commands}/completions.js +0 -0
  279. /package/dist/{commands → src/commands}/curate.js +0 -0
  280. /package/dist/{commands → src/commands}/info.js +0 -0
  281. /package/dist/{commands → src/commands}/init.js +0 -0
  282. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  283. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  284. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  285. /package/dist/{commands → src/commands}/vault.js +0 -0
  286. /package/dist/{core → src/core}/asset-registry.js +0 -0
  287. /package/dist/{core → src/core}/frontmatter.js +0 -0
  288. /package/dist/{core → src/core}/markdown.js +0 -0
  289. /package/dist/{core → src/core}/paths.js +0 -0
  290. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  291. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  292. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  293. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  294. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  295. /package/dist/{llm → src/llm}/embedder.js +0 -0
  296. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  297. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  298. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  299. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  300. /package/dist/{output → src/output}/context.js +0 -0
  301. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  302. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  303. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  304. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  305. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  306. /package/dist/{registry → src/registry}/types.js +0 -0
  307. /package/dist/{setup → src/setup}/detect.js +0 -0
  308. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  309. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  310. /package/dist/{setup → src/setup}/steps.js +0 -0
  311. /package/dist/{sources → src/sources}/include.js +0 -0
  312. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  313. /package/dist/{sources → src/sources}/provider.js +0 -0
  314. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  315. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  316. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  317. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  318. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  319. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  320. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  321. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  322. /package/dist/{sources → src/sources}/resolve.js +0 -0
  323. /package/dist/{sources → src/sources}/types.js +0 -0
  324. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  325. /package/dist/{version.js → src/version.js} +0 -0
  326. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  327. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  328. /package/dist/{workflows → src/workflows}/db.js +0 -0
  329. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  330. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  331. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  332. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  333. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * `akm history` — surfaces internal mutation/usage events for a single asset
3
+ * (`--ref`) or stash-wide.
4
+ *
5
+ * Event sources:
6
+ * - `usage_events` SQLite table: search, show, and feedback events recorded
7
+ * by the local indexer during normal CLI use.
8
+ * - `events.jsonl` append-only stream (opt-in via `--include-proposals`):
9
+ * proposal lifecycle events (`promoted`, `rejected`) emitted by
10
+ * `akm proposal accept` / `akm proposal reject`. Use this flag to see
11
+ * the full proposal review trail alongside usage events.
12
+ *
13
+ * The two sources are merged and sorted chronologically (oldest first) so
14
+ * consumers see a coherent lifecycle trail in a single output.
15
+ */
16
+ import { parseAssetRef } from "../core/asset-ref";
17
+ import { UsageError } from "../core/errors";
18
+ import { readEvents } from "../core/events";
19
+ import { closeDatabase, openDatabase } from "../indexer/db";
20
+ import { ensureUsageEventsSchema } from "../indexer/usage-events";
21
+ // Proposal lifecycle event types emitted by the proposal substrate (#225).
22
+ const PROPOSAL_EVENT_TYPES = new Set(["promoted", "rejected"]);
23
+ // ── Helpers ──────────────────────────────────────────────────────────────────
24
+ function normalizeSince(since) {
25
+ // Accept "YYYY-MM-DD", "YYYY-MM-DDTHH:MM:SSZ", epoch ms, or anything Date can parse.
26
+ const trimmed = since.trim();
27
+ if (!trimmed) {
28
+ throw new UsageError("--since cannot be empty.", "INVALID_FLAG_VALUE");
29
+ }
30
+ // Pure-digit input → epoch milliseconds
31
+ if (/^\d+$/.test(trimmed)) {
32
+ const ms = Number.parseInt(trimmed, 10);
33
+ const d = new Date(ms);
34
+ if (Number.isNaN(d.getTime())) {
35
+ throw new UsageError(`Invalid --since value: ${since}`, "INVALID_FLAG_VALUE");
36
+ }
37
+ return d
38
+ .toISOString()
39
+ .replace("T", " ")
40
+ .replace(/\.\d+Z$/, "");
41
+ }
42
+ const parsed = new Date(trimmed);
43
+ if (Number.isNaN(parsed.getTime())) {
44
+ throw new UsageError(`Invalid --since value: ${since}. Expected ISO timestamp (e.g. 2026-04-01T00:00:00Z) or epoch ms.`, "INVALID_FLAG_VALUE");
45
+ }
46
+ // Match the "YYYY-MM-DD HH:MM:SS" format SQLite's datetime('now') stores.
47
+ return parsed
48
+ .toISOString()
49
+ .replace("T", " ")
50
+ .replace(/\.\d+Z$/, "");
51
+ }
52
+ function parseMetadata(raw) {
53
+ if (!raw)
54
+ return null;
55
+ try {
56
+ return JSON.parse(raw);
57
+ }
58
+ catch {
59
+ return raw;
60
+ }
61
+ }
62
+ function toEntry(row) {
63
+ return {
64
+ id: row.id,
65
+ eventType: row.event_type,
66
+ ref: row.entry_ref,
67
+ entryId: row.entry_id,
68
+ query: row.query,
69
+ signal: row.signal,
70
+ metadata: parseMetadata(row.metadata),
71
+ createdAt: row.created_at,
72
+ };
73
+ }
74
+ /**
75
+ * Convert an ISO timestamp from events.jsonl ("2026-04-01T12:00:00.000Z")
76
+ * to the SQLite-style format used in HistoryEntry.createdAt
77
+ * ("2026-04-01 12:00:00") so entries sort consistently.
78
+ */
79
+ function isoToSqliteTimestamp(ts) {
80
+ // Normalise to the "YYYY-MM-DD HH:MM:SS" format used by usage_events rows.
81
+ return ts
82
+ .replace("T", " ")
83
+ .replace(/\.\d+Z$/, "")
84
+ .replace("Z", "");
85
+ }
86
+ // ── Main ─────────────────────────────────────────────────────────────────────
87
+ /**
88
+ * Read mutation/usage history. When `ref` is provided, results are filtered to
89
+ * that asset (validated via `parseAssetRef`). Always returns chronological
90
+ * order (oldest first) so consumers can display a lifecycle trail.
91
+ *
92
+ * When `includeProposals` is true, proposal lifecycle events (`promoted`,
93
+ * `rejected`) from events.jsonl are merged into the result set. This provides
94
+ * one coherent view of both usage signals and proposal review decisions.
95
+ */
96
+ export async function akmHistory(options = {}) {
97
+ let normalizedRef;
98
+ if (options.ref !== undefined) {
99
+ const trimmed = options.ref.trim();
100
+ if (!trimmed) {
101
+ throw new UsageError("--ref cannot be empty.", "INVALID_FLAG_VALUE");
102
+ }
103
+ // Validate the ref grammar; we still query by exact entry_ref string so
104
+ // the user gets back exactly what they asked for.
105
+ parseAssetRef(trimmed);
106
+ normalizedRef = trimmed;
107
+ }
108
+ const sinceNormalized = options.since !== undefined ? normalizeSince(options.since) : undefined;
109
+ const db = options.db ?? openDatabase();
110
+ const ownsDb = options.db === undefined;
111
+ try {
112
+ // The schema is normally created during `akm index`; ensure it exists so
113
+ // `akm history` works on a freshly-initialised stash that has never been
114
+ // indexed (and just returns an empty list rather than an error).
115
+ ensureUsageEventsSchema(db);
116
+ const conditions = [];
117
+ const params = [];
118
+ if (normalizedRef !== undefined) {
119
+ conditions.push("entry_ref = ?");
120
+ params.push(normalizedRef);
121
+ }
122
+ if (sinceNormalized !== undefined) {
123
+ conditions.push("created_at >= ?");
124
+ params.push(sinceNormalized);
125
+ }
126
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
127
+ const sql = `SELECT id, event_type, query, entry_id, entry_ref, signal, metadata, created_at
128
+ FROM usage_events ${where}
129
+ ORDER BY id ASC`;
130
+ const rows = db.prepare(sql).all(...params);
131
+ const usageEntries = rows.map(toEntry);
132
+ // ── Proposal lifecycle events (opt-in) ────────────────────────────────
133
+ const sources = ["usage_events"];
134
+ const proposalEntries = [];
135
+ if (options.includeProposals === true) {
136
+ sources.push("events.jsonl");
137
+ // Convert sinceNormalized ("YYYY-MM-DD HH:MM:SS") to ISO for readEvents
138
+ // which uses `ts >= since` where `ts` is ISO-8601.
139
+ const sinceIso = sinceNormalized !== undefined ? `${sinceNormalized.replace(" ", "T")}Z` : undefined;
140
+ const { events } = readEvents({
141
+ since: sinceIso,
142
+ ref: normalizedRef,
143
+ }, options.eventsCtx);
144
+ // Keep only proposal lifecycle event types.
145
+ let counter = -1_000_000; // negative ids mark proposal-stream entries
146
+ for (const event of events) {
147
+ if (!PROPOSAL_EVENT_TYPES.has(event.eventType))
148
+ continue;
149
+ const createdAt = event.ts ? isoToSqliteTimestamp(event.ts) : "";
150
+ // Skip if before `since` (readEvents already filters by ts >= since,
151
+ // but the isoToSqliteTimestamp conversion may introduce drift so we
152
+ // guard again with the normalised form).
153
+ if (sinceNormalized !== undefined && createdAt < sinceNormalized)
154
+ continue;
155
+ proposalEntries.push({
156
+ id: counter--,
157
+ eventType: event.eventType,
158
+ ref: event.ref ?? null,
159
+ entryId: null,
160
+ query: null,
161
+ signal: null,
162
+ metadata: event.metadata ?? null,
163
+ createdAt,
164
+ });
165
+ }
166
+ }
167
+ // ── Merge and sort ────────────────────────────────────────────────────
168
+ const entries = [...usageEntries, ...proposalEntries].sort((a, b) => {
169
+ // Primary sort: chronological by createdAt (string compare is safe for
170
+ // "YYYY-MM-DD HH:MM:SS" format). Secondary sort: id ascending for ties.
171
+ if (a.createdAt < b.createdAt)
172
+ return -1;
173
+ if (a.createdAt > b.createdAt)
174
+ return 1;
175
+ return a.id - b.id;
176
+ });
177
+ const response = {
178
+ schemaVersion: 1,
179
+ ...(normalizedRef !== undefined ? { ref: normalizedRef } : {}),
180
+ ...(sinceNormalized !== undefined ? { since: sinceNormalized } : {}),
181
+ totalCount: entries.length,
182
+ entries,
183
+ sources,
184
+ };
185
+ return response;
186
+ }
187
+ finally {
188
+ if (ownsDb)
189
+ closeDatabase(db);
190
+ }
191
+ }
@@ -333,7 +333,7 @@ function selectTargets(installed, target, all) {
333
333
  }
334
334
  throw new UsageError(`"${target}" is a local directory — it reflects your files in place. To refresh the search index, run: akm index`, "TARGET_NOT_UPDATABLE");
335
335
  }
336
- throw new NotFoundError(`No matching source for target: ${target}`);
336
+ throw new NotFoundError(`No matching source for target: ${target}`, "SOURCE_NOT_FOUND");
337
337
  }
338
338
  function tryResolveInstalledTarget(installed, target) {
339
339
  const byId = installed.find((entry) => entry.id === target);
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `akm proposal {list,show,accept,reject,diff}` — review surface for the
3
+ * proposal substrate (#225).
4
+ *
5
+ * Each function returns a plain JSON envelope; the CLI dispatcher in
6
+ * `src/cli.ts` flows the result through the standard
7
+ * `shapeForCommand` + `formatPlain` pipeline. There is no `JSON.stringify`
8
+ * fallback in the output layer — every shape is registered explicitly in
9
+ * `src/output/shapes.ts` and `src/output/text.ts`.
10
+ */
11
+ import { resolveStashDir } from "../core/common";
12
+ import { loadConfig } from "../core/config";
13
+ import { UsageError } from "../core/errors";
14
+ import { appendEvent } from "../core/events";
15
+ import { archiveProposal, createProposal, diffProposal, getProposal, listProposals, promoteProposal, validateProposal, } from "../core/proposals";
16
+ // ── Shared helpers ──────────────────────────────────────────────────────────
17
+ function resolveStash(stashDir) {
18
+ if (stashDir)
19
+ return stashDir;
20
+ return resolveStashDir();
21
+ }
22
+ export function akmProposalList(options = {}) {
23
+ const stash = resolveStash(options.stashDir);
24
+ // `--status accepted|rejected` implies archive-inclusion since the live
25
+ // queue only ever contains pending entries.
26
+ const includeArchive = options.includeArchive === true || options.status === "accepted" || options.status === "rejected";
27
+ const proposals = listProposals(stash, {
28
+ includeArchive,
29
+ status: options.status,
30
+ ref: options.ref,
31
+ });
32
+ return { schemaVersion: 1, totalCount: proposals.length, proposals };
33
+ }
34
+ export function akmProposalShow(options) {
35
+ const stash = resolveStash(options.stashDir);
36
+ const proposal = getProposal(stash, options.id);
37
+ return {
38
+ schemaVersion: 1,
39
+ proposal,
40
+ validation: validateProposal(proposal),
41
+ };
42
+ }
43
+ export async function akmProposalAccept(options) {
44
+ const stash = resolveStash(options.stashDir);
45
+ const config = options.config ?? loadConfig();
46
+ const result = await promoteProposal(stash, config, options.id, { target: options.target }, options.ctx);
47
+ // Emit `promoted` to the events stream so observers (audit, dashboards,
48
+ // sync) see the accept happen. Only emit on the happy path — promotion
49
+ // throws on validation failure, so reaching this point means the asset
50
+ // is committed.
51
+ appendEvent({
52
+ eventType: "promoted",
53
+ ref: result.ref,
54
+ metadata: {
55
+ proposalId: result.proposal.id,
56
+ source: result.proposal.source,
57
+ ...(result.proposal.sourceRun !== undefined ? { sourceRun: result.proposal.sourceRun } : {}),
58
+ assetPath: result.assetPath,
59
+ },
60
+ });
61
+ return {
62
+ schemaVersion: 1,
63
+ ok: true,
64
+ id: result.proposal.id,
65
+ ref: result.ref,
66
+ assetPath: result.assetPath,
67
+ proposal: result.proposal,
68
+ };
69
+ }
70
+ export function akmProposalReject(options) {
71
+ const stash = resolveStash(options.stashDir);
72
+ const existing = getProposal(stash, options.id);
73
+ if (existing.status !== "pending") {
74
+ throw new UsageError(`Proposal ${options.id} is not pending (current status: ${existing.status}). Only pending proposals can be rejected.`, "INVALID_FLAG_VALUE");
75
+ }
76
+ const updated = archiveProposal(stash, options.id, "rejected", options.reason, options.ctx);
77
+ appendEvent({
78
+ eventType: "rejected",
79
+ ref: updated.ref,
80
+ metadata: {
81
+ proposalId: updated.id,
82
+ source: updated.source,
83
+ ...(updated.sourceRun !== undefined ? { sourceRun: updated.sourceRun } : {}),
84
+ ...(options.reason !== undefined ? { reason: options.reason } : {}),
85
+ },
86
+ });
87
+ return {
88
+ schemaVersion: 1,
89
+ ok: true,
90
+ id: updated.id,
91
+ ref: updated.ref,
92
+ ...(options.reason !== undefined ? { reason: options.reason } : {}),
93
+ proposal: updated,
94
+ };
95
+ }
96
+ export function akmProposalDiff(options) {
97
+ const stash = resolveStash(options.stashDir);
98
+ const config = options.config ?? loadConfig();
99
+ const proposal = getProposal(stash, options.id);
100
+ const diff = diffProposal(stash, config, options.id, { target: options.target });
101
+ return {
102
+ schemaVersion: 1,
103
+ id: proposal.id,
104
+ ref: proposal.ref,
105
+ isNew: diff.isNew,
106
+ unified: diff.unified,
107
+ ...(diff.targetPath ? { targetPath: diff.targetPath } : {}),
108
+ };
109
+ }
110
+ export function akmProposalCreate(options) {
111
+ const stash = resolveStash(options.stashDir);
112
+ const proposal = createProposal(stash, {
113
+ ref: options.ref,
114
+ source: options.source,
115
+ ...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
116
+ payload: options.payload,
117
+ }, options.ctx);
118
+ return { schemaVersion: 1, ok: true, proposal };
119
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * `akm propose <type> <name> --task ...` — proposal-producing agent
3
+ * command (#226).
4
+ *
5
+ * Mirrors {@link akmReflect} but for fresh authoring. The agent receives a
6
+ * task description plus per-asset-type schema hints and is asked to author
7
+ * a brand-new asset payload. The output lands ONLY in the proposal queue.
8
+ *
9
+ * Failures use the same {@link AgentFailureReason} discriminants as
10
+ * `akm reflect`. `propose_invoked` is emitted at command entry.
11
+ */
12
+ import { parseAssetRef } from "../core/asset-ref";
13
+ import { TYPE_DIRS } from "../core/asset-spec";
14
+ import { resolveStashDir } from "../core/common";
15
+ import { loadConfig } from "../core/config";
16
+ import { ConfigError, UsageError } from "../core/errors";
17
+ import { appendEvent } from "../core/events";
18
+ import { createProposal } from "../core/proposals";
19
+ import { parseAgentConfig, requireAgentProfile, runAgent, } from "../integrations/agent";
20
+ import { buildProposePrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
21
+ function loadAgentConfigFromDisk() {
22
+ const config = loadConfig();
23
+ return parseAgentConfig(config.agent);
24
+ }
25
+ function resolveProfile(options) {
26
+ if (options.agentProfile)
27
+ return options.agentProfile;
28
+ const agent = options.agentConfig ?? loadAgentConfigFromDisk();
29
+ return requireAgentProfile(agent, options.profile);
30
+ }
31
+ function failureEnvelope(result, type, name, fallbackReason = "non_zero_exit") {
32
+ const reason = result.reason ?? fallbackReason;
33
+ return {
34
+ schemaVersion: 1,
35
+ ok: false,
36
+ reason,
37
+ error: result.error ?? `agent failure (${reason})`,
38
+ type,
39
+ name,
40
+ exitCode: result.exitCode,
41
+ ...(result.stdout ? { stdout: result.stdout } : {}),
42
+ ...(result.stderr ? { stderr: result.stderr } : {}),
43
+ };
44
+ }
45
+ export async function akmPropose(options) {
46
+ if (!options.type?.trim()) {
47
+ throw new UsageError("propose: <type> is required.", "MISSING_REQUIRED_ARGUMENT");
48
+ }
49
+ if (!options.name?.trim()) {
50
+ throw new UsageError("propose: <name> is required.", "MISSING_REQUIRED_ARGUMENT");
51
+ }
52
+ if (!options.task?.trim()) {
53
+ throw new UsageError("propose: --task is required.", "MISSING_REQUIRED_ARGUMENT");
54
+ }
55
+ if (!TYPE_DIRS[options.type]) {
56
+ throw new UsageError(`propose: unknown asset type "${options.type}". Known types: ${Object.keys(TYPE_DIRS).sort().join(", ")}.`, "INVALID_FLAG_VALUE");
57
+ }
58
+ const stash = options.stashDir ?? resolveStashDir();
59
+ // 1. Always emit `propose_invoked` at entry so observers see the attempt.
60
+ appendEvent({
61
+ eventType: "propose_invoked",
62
+ ref: `${options.type}:${options.name}`,
63
+ metadata: {
64
+ type: options.type,
65
+ name: options.name,
66
+ task: options.task,
67
+ ...(options.profile ? { profile: options.profile } : {}),
68
+ },
69
+ });
70
+ // 2. Resolve profile.
71
+ let profile;
72
+ try {
73
+ profile = resolveProfile(options);
74
+ }
75
+ catch (err) {
76
+ if (err instanceof ConfigError || err instanceof UsageError)
77
+ throw err;
78
+ throw err;
79
+ }
80
+ // 3. Build prompt.
81
+ const prompt = buildProposePrompt({
82
+ type: options.type,
83
+ name: options.name,
84
+ task: options.task,
85
+ });
86
+ // 4. Spawn the agent.
87
+ const runOptions = {
88
+ stdio: "captured",
89
+ parseOutput: "text",
90
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
91
+ ...(options.runAgentOptions ?? {}),
92
+ };
93
+ const result = await runAgent(profile, prompt, runOptions);
94
+ if (!result.ok) {
95
+ return failureEnvelope(result, options.type, options.name);
96
+ }
97
+ // 5. Parse the structured response.
98
+ let payload;
99
+ try {
100
+ payload = parseAgentProposalPayload(result.stdout);
101
+ }
102
+ catch (err) {
103
+ return {
104
+ schemaVersion: 1,
105
+ ok: false,
106
+ reason: "parse_error",
107
+ error: err instanceof Error ? err.message : String(err),
108
+ type: options.type,
109
+ name: options.name,
110
+ exitCode: result.exitCode,
111
+ stdout: result.stdout,
112
+ ...(result.stderr ? { stderr: result.stderr } : {}),
113
+ };
114
+ }
115
+ // 6. Insert the proposal. Note: we allow the agent's `ref` to normalise the
116
+ // asset name (e.g. path-cleanup), but only after validating that the ref is
117
+ // well-formed and the type still matches the requested type.
118
+ const expectedRef = `${options.type}:${options.name}`;
119
+ let ref = expectedRef;
120
+ if (payload.ref) {
121
+ let parsedRef;
122
+ try {
123
+ parsedRef = parseAssetRef(payload.ref);
124
+ }
125
+ catch (err) {
126
+ return {
127
+ schemaVersion: 1,
128
+ ok: false,
129
+ reason: "parse_error",
130
+ error: err instanceof Error ? err.message : String(err),
131
+ type: options.type,
132
+ name: options.name,
133
+ exitCode: result.exitCode,
134
+ stdout: result.stdout,
135
+ ...(result.stderr ? { stderr: result.stderr } : {}),
136
+ };
137
+ }
138
+ if (parsedRef.type !== options.type) {
139
+ return {
140
+ schemaVersion: 1,
141
+ ok: false,
142
+ reason: "parse_error",
143
+ error: `Agent returned ref type ${parsedRef.type} but expected ${options.type}`,
144
+ type: options.type,
145
+ name: options.name,
146
+ exitCode: result.exitCode,
147
+ stdout: result.stdout,
148
+ ...(result.stderr ? { stderr: result.stderr } : {}),
149
+ };
150
+ }
151
+ ref = `${parsedRef.type}:${parsedRef.name}`;
152
+ }
153
+ const createInput = {
154
+ ref,
155
+ source: "propose",
156
+ sourceRun: `propose-${Date.now()}`,
157
+ payload: {
158
+ content: payload.content,
159
+ ...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
160
+ },
161
+ };
162
+ const proposal = createProposal(stash, createInput, options.ctx);
163
+ return {
164
+ schemaVersion: 1,
165
+ ok: true,
166
+ proposal,
167
+ ref: proposal.ref,
168
+ agentProfile: profile.name,
169
+ durationMs: result.durationMs,
170
+ };
171
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * `akm reflect [ref]` — proposal-producing agent command (#226).
3
+ *
4
+ * Pipeline:
5
+ *
6
+ * 1. Emit `reflect_invoked` event at command entry (always, even on failure).
7
+ * 2. If `ref` is provided, look the asset up via the FTS index and read its
8
+ * content. Pull recent feedback (`feedback` events for that ref) and
9
+ * lesson-lint findings to surface as schema hints.
10
+ * 3. Build the prompt via {@link buildReflectPrompt}.
11
+ * 4. Spawn the configured agent profile via {@link runAgent}.
12
+ * 5. Parse the agent's stdout into a {@link AgentProposalPayload}.
13
+ * 6. Insert into the proposal queue via {@link createProposal} with
14
+ * `source: "reflect"`.
15
+ *
16
+ * Failures are surfaced as structured envelopes carrying an
17
+ * {@link AgentFailureReason} discriminant. Reflect NEVER calls
18
+ * `writeAssetToSource` directly — the proposal queue is the only path to
19
+ * a committed asset, and the `accept` flow is the bridge.
20
+ */
21
+ import fs from "node:fs";
22
+ import { parseAssetRef } from "../core/asset-ref";
23
+ import { resolveStashDir } from "../core/common";
24
+ import { loadConfig } from "../core/config";
25
+ import { ConfigError, UsageError } from "../core/errors";
26
+ import { appendEvent, readEvents } from "../core/events";
27
+ import { lintLessonContent } from "../core/lesson-lint";
28
+ import { createProposal } from "../core/proposals";
29
+ import { lookup } from "../indexer/indexer";
30
+ import { parseAgentConfig, requireAgentProfile, runAgent, } from "../integrations/agent";
31
+ import { buildReflectPrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
32
+ const MAX_FEEDBACK_LINES = 10;
33
+ const MAX_GLOBAL_FEEDBACK_LINES = 20;
34
+ /**
35
+ * Pull recent `feedback` events from events.jsonl. When `ref` is present we
36
+ * scope to that asset; otherwise we surface the most recent feedback across
37
+ * all assets so `akm reflect` can operate in a general "review recent
38
+ * signals" mode. Best-effort — a missing or empty events stream returns `[]`.
39
+ */
40
+ function readRecentFeedback(ref) {
41
+ try {
42
+ const result = readEvents({ type: "feedback", ...(ref ? { ref } : {}) });
43
+ const lines = [];
44
+ const limit = ref ? MAX_FEEDBACK_LINES : MAX_GLOBAL_FEEDBACK_LINES;
45
+ for (const event of result.events.slice(-limit)) {
46
+ const md = (event.metadata ?? {});
47
+ const signal = typeof md.signal === "string" ? md.signal : "?";
48
+ const note = typeof md.note === "string" ? md.note : typeof md.reason === "string" ? md.reason : "";
49
+ const details = note ? `[${signal}] ${note}` : `[${signal}]`;
50
+ lines.push(!ref && event.ref ? `${event.ref} ${details}` : details);
51
+ }
52
+ return lines;
53
+ }
54
+ catch {
55
+ return [];
56
+ }
57
+ }
58
+ /**
59
+ * Build schema/lint hints for the prompt. For lesson refs, run the lesson
60
+ * lint over the current content and surface any findings — they are a
61
+ * concrete starting point for the agent's revision.
62
+ */
63
+ function buildSchemaHints(type, content) {
64
+ if (!content)
65
+ return [];
66
+ if (type !== "lesson")
67
+ return [];
68
+ const report = lintLessonContent(content, "reflect");
69
+ return report.findings.map((f) => `[${f.kind}] ${f.message}`);
70
+ }
71
+ function loadAgentConfigFromDisk() {
72
+ const config = loadConfig();
73
+ return parseAgentConfig(config.agent);
74
+ }
75
+ function resolveProfile(options) {
76
+ if (options.agentProfile)
77
+ return options.agentProfile;
78
+ const agent = options.agentConfig ?? loadAgentConfigFromDisk();
79
+ return requireAgentProfile(agent, options.profile);
80
+ }
81
+ function failureEnvelope(result, ref, fallbackReason = "non_zero_exit") {
82
+ const reason = result.reason ?? fallbackReason;
83
+ return {
84
+ schemaVersion: 1,
85
+ ok: false,
86
+ reason,
87
+ error: result.error ?? `agent failure (${reason})`,
88
+ ...(ref ? { ref } : {}),
89
+ exitCode: result.exitCode,
90
+ ...(result.stdout ? { stdout: result.stdout } : {}),
91
+ ...(result.stderr ? { stderr: result.stderr } : {}),
92
+ };
93
+ }
94
+ export async function akmReflect(options = {}) {
95
+ const stash = options.stashDir ?? resolveStashDir();
96
+ // 1. Always emit `reflect_invoked` at command entry — observers see the
97
+ // attempt regardless of downstream success/failure.
98
+ appendEvent({
99
+ eventType: "reflect_invoked",
100
+ ...(options.ref ? { ref: options.ref } : {}),
101
+ metadata: {
102
+ ...(options.task ? { task: options.task } : {}),
103
+ ...(options.profile ? { profile: options.profile } : {}),
104
+ },
105
+ });
106
+ // 2. Resolve target asset content (if a ref is supplied).
107
+ let assetContent;
108
+ let parsedRef;
109
+ if (options.ref) {
110
+ parsedRef = parseAssetRef(options.ref);
111
+ try {
112
+ const entry = await lookup(parsedRef);
113
+ if (entry?.filePath && fs.existsSync(entry.filePath)) {
114
+ assetContent = fs.readFileSync(entry.filePath, "utf8");
115
+ }
116
+ }
117
+ catch {
118
+ // Index miss is non-fatal — the agent can still propose a fresh asset.
119
+ }
120
+ }
121
+ // 3. Resolve agent profile. ConfigError surfaces as a thrown error so the
122
+ // CLI dispatcher renders the standard envelope.
123
+ let profile;
124
+ try {
125
+ profile = resolveProfile(options);
126
+ }
127
+ catch (err) {
128
+ if (err instanceof ConfigError || err instanceof UsageError)
129
+ throw err;
130
+ throw err;
131
+ }
132
+ // 4. Build the prompt.
133
+ const feedback = readRecentFeedback(options.ref);
134
+ const schemaHints = buildSchemaHints(parsedRef?.type ?? "", assetContent);
135
+ const prompt = buildReflectPrompt({
136
+ ...(options.ref ? { ref: options.ref } : {}),
137
+ ...(parsedRef?.type ? { type: parsedRef.type } : {}),
138
+ ...(parsedRef?.name ? { name: parsedRef.name } : {}),
139
+ ...(assetContent !== undefined ? { assetContent } : {}),
140
+ ...(feedback.length > 0 ? { feedback } : {}),
141
+ ...(schemaHints.length > 0 ? { schemaHints } : {}),
142
+ ...(options.task ? { task: options.task } : {}),
143
+ });
144
+ // 5. Spawn the agent. Force captured stdio + JSON parse so we can extract
145
+ // the structured payload without confusing terminal control codes.
146
+ const runOptions = {
147
+ stdio: "captured",
148
+ parseOutput: "text",
149
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
150
+ ...(options.runAgentOptions ?? {}),
151
+ };
152
+ const result = await runAgent(profile, prompt, runOptions);
153
+ if (!result.ok) {
154
+ return failureEnvelope(result, options.ref);
155
+ }
156
+ // 6. Parse stdout into a proposal payload.
157
+ let payload;
158
+ try {
159
+ payload = parseAgentProposalPayload(result.stdout);
160
+ }
161
+ catch (err) {
162
+ return {
163
+ schemaVersion: 1,
164
+ ok: false,
165
+ reason: "parse_error",
166
+ error: err instanceof Error ? err.message : String(err),
167
+ ...(options.ref ? { ref: options.ref } : {}),
168
+ exitCode: result.exitCode,
169
+ stdout: result.stdout,
170
+ ...(result.stderr ? { stderr: result.stderr } : {}),
171
+ };
172
+ }
173
+ // 7. Create the proposal. The proposal queue is the ONLY thing reflect
174
+ // writes — promotion to a real asset is gated by `akm proposal accept`.
175
+ const createInput = {
176
+ ref: payload.ref,
177
+ source: "reflect",
178
+ sourceRun: `reflect-${Date.now()}`,
179
+ payload: {
180
+ content: payload.content,
181
+ ...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
182
+ },
183
+ };
184
+ const proposal = createProposal(stash, createInput, options.ctx);
185
+ return {
186
+ schemaVersion: 1,
187
+ ok: true,
188
+ proposal,
189
+ ref: proposal.ref,
190
+ agentProfile: profile.name,
191
+ durationMs: result.durationMs,
192
+ };
193
+ }