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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (598) hide show
  1. package/CHANGELOG.md +1283 -22
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -11,46 +11,106 @@
11
11
  * helpers only resolve refs to absolute paths and guard against directory
12
12
  * traversal.
13
13
  */
14
+ import fs from "node:fs";
14
15
  import path from "node:path";
15
16
  import { resolveSourceEntries } from "../indexer/search/search-source.js";
17
+ import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
16
18
  import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "./asset/asset-create.js";
17
- import { parseAssetRef } from "./asset/asset-ref.js";
18
- import { resolveAssetPathFromName } from "./asset/asset-spec.js";
19
+ import { assetPathForName } from "./asset/asset-placement.js";
20
+ import { displayRef, isFullRefInput, parseRefInput } from "./asset/resolve-ref.js";
19
21
  import { isWithin } from "./common.js";
20
22
  import { loadConfig } from "./config/config.js";
21
23
  import { NotFoundError, UsageError } from "./errors.js";
24
+ import { resolveMutationTarget } from "./mutation-target.js";
25
+ import { formatRefForMessage, withWriteTargetMutation } from "./write-source.js";
26
+ /**
27
+ * The `vault` asset type was removed in 0.9.0. The env/secret input path no
28
+ * longer routes through the legacy stored-ref parser (which carries the removal
29
+ * signpost), so a `vault:`/`vault/` leading token would otherwise be silently
30
+ * qualified into an `env/vault:…` not-found. Detect it here and re-emit the
31
+ * migration signpost so 0.8→0.9 muscle memory still gets pointed at env/secret.
32
+ */
33
+ function assertNotRemovedVaultRef(ref) {
34
+ const boundary = ref.indexOf("//");
35
+ const bare = boundary >= 0 ? ref.slice(boundary + 2) : ref;
36
+ if (/^vault[:/]/.test(bare.trim())) {
37
+ throw new UsageError("The `vault` asset type was removed in 0.9.0 — use `env/` (whole .env config) or `secrets/` (a single value).", "INVALID_FLAG_VALUE");
38
+ }
39
+ }
40
+ /**
41
+ * Q-08 ruling: the pre-0.9.0 `type:name` ref grammar taught `env:<name>` /
42
+ * `secret:<name>` (help text and docs also implied `environment:`/`secrets:`
43
+ * variants) as the way to address a single env/secret. That grammar is GONE —
44
+ * NO alias, no re-acceptance (same rule as the retired `vault:` prefix above).
45
+ * Left unchecked, a colon-prefixed ref does not error here at all: it falls
46
+ * through to the "bare name" convenience below and gets silently qualified
47
+ * into a literal `env/env:name` (or `secrets/secret:name`) file that can never
48
+ * exist — a confusing not-found that hides the real mistake instead of naming
49
+ * it. Reject it here, loudly, before that happens.
50
+ */
51
+ function assertNotColonRef(ref, aliases, replacement) {
52
+ const boundary = ref.indexOf("//");
53
+ const bare = (boundary >= 0 ? ref.slice(boundary + 2) : ref).trim();
54
+ const colon = bare.indexOf(":");
55
+ if (colon <= 0)
56
+ return;
57
+ const head = bare.slice(0, colon).toLowerCase();
58
+ if (!aliases.includes(head))
59
+ return;
60
+ const name = bare.slice(colon + 1);
61
+ throw new UsageError(`The \`${head}:\` ref spelling was removed in 0.9.0 — use the slash form instead: \`${replacement}${name}\`.`, "INVALID_FLAG_VALUE");
62
+ }
22
63
  export function parseEnvRef(ref) {
23
- return parseAssetRef(ref.includes(":") ? ref : `env:${ref}`);
64
+ // Accept a bare env name (`prod`, `sub/prod`) or the new-grammar
65
+ // `[bundle//]env/name` conceptId. A bare name's leading segment maps to no
66
+ // asset type, so it is qualified with the `env/` conceptId prefix; anything
67
+ // already a full new-grammar ref is parsed as-is.
68
+ assertNotRemovedVaultRef(ref);
69
+ assertNotColonRef(ref, ["env", "environment"], "env/");
70
+ return parseRefInput(isFullRefInput(ref) ? ref : `env/${ref}`);
24
71
  }
25
- export function findEnvSource(origin) {
72
+ export function findEnvSource(origin, type, name) {
26
73
  const sources = resolveSourceEntries(undefined, loadConfig());
27
74
  if (sources.length === 0) {
28
- throw new UsageError("No stashes configured. Run `akm init` to create your working stash.");
75
+ throw new UsageError("No bundles configured. Run `akm bundle create` to create your working bundle.");
76
+ }
77
+ const candidates = origin ? resolveSourcesForOrigin(origin, sources) : sources;
78
+ const typeDir = type === "env" ? "env" : "secrets";
79
+ const member = candidates.find((source) => fs.existsSync(assetPathForName(type, path.join(source.path, typeDir), name)));
80
+ if (member)
81
+ return member;
82
+ if (!origin) {
83
+ const fallback = candidates[0];
84
+ if (fallback)
85
+ return fallback;
86
+ throw new UsageError("No bundles configured. Run `akm bundle create` to create your working bundle.");
29
87
  }
30
- if (!origin || origin === "local")
31
- return sources[0];
32
- const named = sources.find((source) => source.registryId === origin);
88
+ const named = candidates[0];
33
89
  if (!named) {
34
90
  throw new NotFoundError(`Source not found for origin: ${origin}`);
35
91
  }
36
92
  return named;
37
93
  }
38
94
  export function makeEnvRef(name, source) {
39
- return source?.registryId ? `${source.registryId}//env:${name}` : `env:${name}`;
95
+ // F4b output-spelling flip: `env/name` in the primary stash, `bundle//env/name`
96
+ // for a slug-clean named source.
97
+ return displayRef({ type: "env", name, bundleId: source?.registryId }, displayDefaultBundle(source));
40
98
  }
41
99
  /**
42
- * Resolve an env ref to an absolute `.env` path. Accepts `env:` and
43
- * `environment:` (alias) refs as well as bare names. The path is returned even
44
- * when the file does not yet exist (so `create` writes under `env/`).
100
+ * Resolve an env ref to an absolute `.env` path. Accepts the `env/<name>`
101
+ * conceptId (or a bare name, auto-qualified into it) — the retired
102
+ * `env:`/`environment:` colon spelling is rejected loudly (Q-08), never
103
+ * silently resolved. The path is returned even when the file does not yet
104
+ * exist (so `create` writes under `env/`).
45
105
  */
46
106
  export function resolveEnvPath(ref) {
47
107
  const parsed = parseEnvRef(ref);
48
108
  if (parsed.type !== "env") {
49
- throw new UsageError(`Expected an env ref (env:<name>); got "${ref}".`);
109
+ throw new UsageError(`Expected an env ref (env/<name>); got "${ref}".`);
50
110
  }
51
- const source = findEnvSource(parsed.origin);
111
+ const source = findEnvSource(parsed.origin, "env", parsed.name);
52
112
  const envRoot = path.join(source.path, "env");
53
- const envPath = resolveAssetPathFromName("env", envRoot, parsed.name);
113
+ const envPath = assetPathForName("env", envRoot, parsed.name);
54
114
  // Defense-in-depth: ensure the resolved path stays inside the env directory.
55
115
  // validateName already rejects traversal patterns like "../../foo", but an
56
116
  // absolute-path override or symlink-based attack could still escape without
@@ -61,10 +121,23 @@ export function resolveEnvPath(ref) {
61
121
  return { name: parsed.name, absPath: envPath, source, parsedRef: parsed, dir: "env" };
62
122
  }
63
123
  export function parseSecretRef(ref) {
64
- return parseAssetRef(ref.includes(":") ? ref : `secret:${ref}`);
124
+ // Same bare-name-vs-full-ref rule as parseEnvRef; a bare name is qualified
125
+ // with the `secrets/` conceptId prefix (secret's stash subdir).
126
+ assertNotRemovedVaultRef(ref);
127
+ assertNotColonRef(ref, ["secret", "secrets"], "secrets/");
128
+ return parseRefInput(isFullRefInput(ref) ? ref : `secrets/${ref}`);
65
129
  }
66
130
  export function makeSecretRef(name, source) {
67
- return source?.registryId ? `${source.registryId}//secret:${name}` : `secret:${name}`;
131
+ // F4b output-spelling flip: `secrets/name` in the primary stash,
132
+ // `bundle//secrets/name` for a slug-clean named source.
133
+ return displayRef({ type: "secret", name, bundleId: source?.registryId }, displayDefaultBundle(source));
134
+ }
135
+ function displayDefaultBundle(source) {
136
+ const config = loadConfig();
137
+ if (config.defaultBundle || !source)
138
+ return config.defaultBundle;
139
+ const primary = resolveSourceEntries(undefined, config)[0];
140
+ return primary && path.resolve(primary.path) === path.resolve(source.path) ? source.registryId : undefined;
68
141
  }
69
142
  export function resolveSecretPath(ref,
70
143
  // Create-only (`secret set`): enforce a flat ref name and apply `--path` as
@@ -72,19 +145,81 @@ export function resolveSecretPath(ref,
72
145
  create) {
73
146
  const parsed = parseSecretRef(ref);
74
147
  if (parsed.type !== "secret") {
75
- throw new UsageError(`Expected a secret ref (secret:<name>); got "${ref}".`);
148
+ throw new UsageError(`Expected a secret ref (secrets/<name>); got "${ref}".`);
76
149
  }
77
150
  if (create) {
78
151
  assertFlatAssetName(parsed.name);
79
152
  parsed.name = combineCreatePath(normalizeCreateSubPath(create.subPath), parsed.name);
80
153
  }
81
154
  // Source resolution is identical for every asset type; reuse the env helper.
82
- const source = findEnvSource(parsed.origin);
155
+ const source = findEnvSource(parsed.origin, "secret", parsed.name);
83
156
  const typeRoot = path.join(source.path, "secrets");
84
- const absPath = resolveAssetPathFromName("secret", typeRoot, parsed.name);
157
+ const absPath = assetPathForName("secret", typeRoot, parsed.name);
85
158
  // Defense-in-depth: ensure the resolved path stays inside the secrets dir.
86
159
  if (!isWithin(absPath, typeRoot)) {
87
160
  throw new UsageError(`Secret name "${parsed.name}" escapes the secrets directory.`);
88
161
  }
89
162
  return { name: parsed.name, absPath, source };
90
163
  }
164
+ /**
165
+ * Resolve the destination for an env mutation. Mirrors {@link resolveEnvPath}
166
+ * but selects the source via {@link resolveWriteTarget} (writability-checked)
167
+ * instead of the read-side {@link findEnvSource}. `create` enforces a flat ref
168
+ * name and applies `--path` as the subdirectory (matching `env create`).
169
+ */
170
+ export function resolveEnvWriteTarget(ref, writeTarget, create) {
171
+ const parsed = parseEnvRef(ref);
172
+ if (parsed.type !== "env") {
173
+ throw new UsageError(`Expected an env ref (env/<name>); got "${ref}".`);
174
+ }
175
+ if (create) {
176
+ assertFlatAssetName(parsed.name);
177
+ parsed.name = combineCreatePath(normalizeCreateSubPath(create.subPath), parsed.name);
178
+ }
179
+ const resolved = resolveMutationTarget(loadConfig(), parsed, writeTarget, { allowedAdapters: ["akm", "dotenv"] });
180
+ const { target } = resolved;
181
+ const envRoot = path.join(target.source.path, "env");
182
+ const absPath = assetPathForName("env", envRoot, parsed.name);
183
+ if (!isWithin(absPath, envRoot)) {
184
+ throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
185
+ }
186
+ return { name: parsed.name, absPath, target, parsedRef: resolved.ref, ref: resolved.displayRef };
187
+ }
188
+ /**
189
+ * Resolve the destination for a secret mutation. Mirrors
190
+ * {@link resolveSecretPath} but selects the source via
191
+ * {@link resolveWriteTarget} (writability-checked) instead of the read-side
192
+ * {@link findEnvSource}.
193
+ */
194
+ export function resolveSecretWriteTarget(ref, writeTarget, create) {
195
+ const parsed = parseSecretRef(ref);
196
+ if (parsed.type !== "secret") {
197
+ throw new UsageError(`Expected a secret ref (secrets/<name>); got "${ref}".`);
198
+ }
199
+ if (create) {
200
+ assertFlatAssetName(parsed.name);
201
+ parsed.name = combineCreatePath(normalizeCreateSubPath(create.subPath), parsed.name);
202
+ }
203
+ const resolved = resolveMutationTarget(loadConfig(), parsed, writeTarget, { allowedAdapters: ["akm", "dotenv"] });
204
+ const { target } = resolved;
205
+ const typeRoot = path.join(target.source.path, "secrets");
206
+ const absPath = assetPathForName("secret", typeRoot, parsed.name);
207
+ if (!isWithin(absPath, typeRoot)) {
208
+ throw new UsageError(`Secret name "${parsed.name}" escapes the secrets directory.`);
209
+ }
210
+ return { name: parsed.name, absPath, target, ref: resolved.displayRef };
211
+ }
212
+ /**
213
+ * Land an env/secret mutation on its write target's git boundary. Mirrors the
214
+ * tasks/knowledge write path: record each mutated path, then fire the single
215
+ * batch-at-boundary commit. Both steps are no-ops for filesystem targets (the
216
+ * primary stash included) and for `env/` paths a stash `.gitignore` excludes, so
217
+ * callers invoke it unconditionally after every create/ingest/set/remove.
218
+ */
219
+ export function withEnvSecretWrite(target, ref, op, paths, mutate) {
220
+ return withWriteTargetMutation(target, paths, {
221
+ ignored: "local-only",
222
+ purpose: `${ref.type}-${op.toLowerCase()}`,
223
+ message: `${op} ${formatRefForMessage({ type: ref.type, name: ref.name, origin: target.source.name })}`,
224
+ }, mutate);
225
+ }
@@ -6,32 +6,34 @@
6
6
  * imperative. Returning undefined means "no canned hint".
7
7
  */
8
8
  const CONFIG_HINTS = {
9
- STASH_DIR_NOT_FOUND: "Run `akm setup` to create and configure your stash, or set stashDir in your config.",
10
- STASH_DIR_NOT_A_DIRECTORY: "The configured stashDir exists but isn't a directory. Update stashDir to point at a folder.",
11
- STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update stashDir.",
9
+ STASH_DIR_NOT_FOUND: "Run `akm setup` to create and configure your bundle, or configure a defaultBundle path.",
10
+ STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
11
+ STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
12
12
  EMBEDDING_NOT_CONFIGURED: 'Run `akm config set embedding \'{"endpoint":"...","model":"..."}\'` to enable embeddings.',
13
- LLM_NOT_CONFIGURED: 'Run `akm setup` or `akm config set profiles.llm.default \'{"endpoint":"...","model":"..."}\' to configure an LLM profile.',
14
- TEST_ISOLATION_MISSING: "Under bun test, when AKM_STASH_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
15
- SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_STASH_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
16
- UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The stash directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
17
- UNKNOWN_IMPROVE_PROFILE: "Pass one of the listed profile names to `--profile`, or define it under `profiles.improve` in your config. Names are case-sensitive.",
13
+ LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
14
+ TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
15
+ SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_BUNDLE_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
16
+ UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The bundle directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
17
+ UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
18
+ WORKFLOW_ENGINE_NOT_ENABLED: "Run `akm config set experimental.workflowEngine true` to enable it.",
18
19
  };
19
20
  /** Default hint for each UsageError code. */
20
21
  const USAGE_HINTS = {
21
22
  INVALID_FLAG_VALUE: "Run `akm <command> --help` to see accepted values.",
22
- INVALID_SOURCE_VALUE: "Pick one of: stash, registry, both.",
23
- INVALID_FORMAT_VALUE: "Pick one of: json, jsonl, text, yaml.",
23
+ INVALID_SOURCE_VALUE: "Pick one of: local, registry, all, or a configured source name.",
24
+ INVALID_FORMAT_VALUE: "Pick one of: json, jsonl, yaml, text, md, html.",
24
25
  INVALID_DETAIL_VALUE: "Pick one of: brief, normal, full. For agent/summary projections use --shape.",
25
26
  INVALID_SHAPE_VALUE: "Pick one of: human, agent, summary (summary is only valid on `akm show`).",
26
27
  INVALID_JSON_CONFIG_VALUE: 'Quote JSON values in your shell, for example: akm config set embedding \'{"endpoint":"http://localhost:11434/v1/embeddings","model":"nomic-embed-text"}\'.',
27
- MISSING_OR_AMBIGUOUS_TARGET: "Use `akm update --all` or pass a target like `akm update npm:@scope/pkg` (not both).",
28
- TARGET_NOT_UPDATABLE: "Run `akm list` to view your sources, then retry with one of those values.",
29
- MISSING_REQUIRED_ARGUMENT: "Refs use the form type:name, e.g. `akm show skill:deploy` or `akm show knowledge:guide.md`.",
28
+ MISSING_OR_AMBIGUOUS_TARGET: "Use `akm bundle update --all` or pass a target like `akm bundle update npm:@scope/pkg` (not both).",
29
+ TARGET_NOT_UPDATABLE: "Run `akm bundle list` to view your sources, then retry with one of those values.",
30
+ MISSING_REQUIRED_ARGUMENT: "Refs use the form [bundle//]conceptId, e.g. `akm show knowledge/guide.md` or `akm show skills/deploy`.",
31
+ UNKNOWN_COMMAND: "Run `akm --help` to see available commands.",
30
32
  };
31
33
  /** Default hint for each NotFoundError code. */
32
34
  const NOT_FOUND_HINTS = {
33
35
  ASSET_NOT_FOUND: "Run `akm search <query>` or `akm index` to refresh the index.",
34
- SOURCE_NOT_FOUND: "Run `akm list` to view your sources, then retry with one of those values.",
36
+ SOURCE_NOT_FOUND: "Run `akm bundle list` to view your sources, then retry with one of those values.",
35
37
  WORKFLOW_NOT_FOUND: "Run `akm workflow list --active` to see runs.",
36
38
  FILE_NOT_FOUND: "Check the path exists and is readable.",
37
39
  };
@@ -97,7 +99,7 @@ export class NotFoundError extends AkmError {
97
99
  * Test-isolation guard helper.
98
100
  *
99
101
  * `src/core/paths.ts` throws `ConfigError("TEST_ISOLATION_MISSING")` under
100
- * `bun test` when `AKM_STASH_DIR` is set without a paired data-dir or
102
+ * `bun test` when `AKM_BUNDLE_DIR` is set without a paired data-dir or
101
103
  * state-dir override. That throw must never be swallowed by best-effort
102
104
  * catches around DB/data-dir operations — otherwise the guard's loud failure
103
105
  * silently degrades into a "no result" outcome (cold cache, missing snapshot,
@@ -0,0 +1,4 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ export {};
@@ -1,52 +1,19 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /**
5
- * Append-only events stream — backed by state.db (#204, Phase 3).
6
- *
7
- * Every mutating CLI verb funnels through `appendEvent` so external
8
- * observers (sync, replication, audit, dashboards) can react to stash
9
- * changes. Events are stored in the `events` table in `state.db`
10
- * (SQLite, WAL mode) instead of a flat `events.jsonl` file.
11
- *
12
- * The helper is the only thing in akm that writes to the events table. It
13
- * accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
14
- * tmpdir without any global mutation.
15
- *
16
- * Format (each EventEnvelope):
17
- * { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
18
- * "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
19
- *
20
- * - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
21
- * as a durable cursor for `--since` resumption (replaces the old byte-offset
22
- * cursor). The public API still surfaces this as `nextOffset` (an opaque
23
- * number) for backward compatibility with callers that stored byte-offset
24
- * cursors.
25
- * - `ts` is ISO-8601 (UTC, millisecond precision).
26
- */
27
- import path from "node:path";
28
4
  import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
29
5
  import { rethrowIfTestIsolationError } from "./errors.js";
30
- import { getDataDir } from "./paths.js";
31
- import { openStateDatabase, withStateDb } from "./state-db.js";
6
+ import { getStateDbPath, openStateDatabase, withStateDb } from "./state-db.js";
32
7
  import { error } from "./warn.js";
33
- /**
34
- * Legacy events.jsonl path — used only by the migration script
35
- * (`scripts/migrate-storage.ts`) to import existing event history into
36
- * state.db. No events are written here by akm v0.9+.
37
- */
38
- export function getEventsPath() {
39
- return path.join(getDataDir(), "events.jsonl");
40
- }
41
8
  /**
42
9
  * Resolve the state.db path from context:
43
10
  * 1. `ctx.dbPath` — explicit override (test seam)
44
- * 2. default — `<dataDir>/state.db`
11
+ * 2. default — the canonical state.db path
45
12
  */
46
13
  function resolveDbPath(ctx) {
47
14
  if (ctx?.dbPath)
48
15
  return ctx.dbPath;
49
- return path.join(getDataDir(), "state.db");
16
+ return getStateDbPath();
50
17
  }
51
18
  function resolveNow(ctx) {
52
19
  return ctx?.now ?? Date.now;
@@ -100,6 +67,17 @@ export function appendEvent(input, ctx) {
100
67
  error(`akm: appendEvent failed: ${String(err)}`);
101
68
  }
102
69
  }
70
+ /**
71
+ * 0.9.0 breaking change (owner ruling 12): `akm sync` used to persist
72
+ * `eventType: "save"`; it now writes `"sync"` instead (matching the command
73
+ * name). Existing `state.db` rows — and any user script running
74
+ * `akm log --type save` — still carry the old spelling. Rather than
75
+ * rewriting historical rows (a migration users never asked for, on data we
76
+ * don't get to touch at rest), reads treat the two names as synonyms: asking
77
+ * for either "save" or "sync" returns rows written under both names. Only
78
+ * the WRITE path (sources-cli.ts's `runSyncBody`) changed.
79
+ */
80
+ const SAVE_SYNC_EVENT_TYPE_ALIASES = new Set(["save", "sync"]);
103
81
  /**
104
82
  * Read all events matching the filter. Returns a `nextOffset` that callers
105
83
  * can persist between processes for monotonic resumption.
@@ -117,14 +95,37 @@ export function readEvents(options = {}, ctx) {
117
95
  return { events: [], nextOffset: 0 };
118
96
  }
119
97
  try {
98
+ // A "save"/"sync" query can't be expressed as a single SQL `event_type =
99
+ // ?` match (see SAVE_SYNC_EVENT_TYPE_ALIASES above), so widen the SQL
100
+ // filter to "no type filter" for that one case and apply the alias match
101
+ // client-side alongside the existing tag post-filter below.
102
+ const typeIsAliased = options.type !== undefined && SAVE_SYNC_EVENT_TYPE_ALIASES.has(options.type);
103
+ // D-38: a JS-side post-filter (the type alias above, or the tag filters
104
+ // below) runs AFTER the SQL read, so a SQL-level LIMIT applied before it
105
+ // could drop rows the post-filter would have kept out anyway, silently
106
+ // returning fewer than `limit` (or the wrong — oldest-in-the-SQL-window —
107
+ // events). Only push `limit` into SQL (readStateEvents) when nothing
108
+ // downstream can shrink the result further; otherwise read unbounded (the
109
+ // pre-existing behavior) and apply `limit` ourselves, below, AFTER the
110
+ // post-filter runs.
111
+ const needsPostFilter = typeIsAliased ||
112
+ (options.excludeTags?.length ?? 0) > 0 ||
113
+ (options.includeTags?.length ?? 0) > 0 ||
114
+ options.runId !== undefined;
115
+ const pushLimitToSql = options.limit !== undefined && !needsPostFilter;
120
116
  const { events: rawEvents, nextId } = readStateEvents(db, {
121
117
  sinceId: options.sinceOffset,
122
118
  since: options.since,
123
- type: options.type,
119
+ type: typeIsAliased ? undefined : options.type,
124
120
  ref: options.ref,
121
+ ...(pushLimitToSql ? { limit: options.limit } : {}),
125
122
  });
126
- // Apply tag filters in application code (same as the old JSONL implementation).
127
- const events = rawEvents.filter((envelope) => {
123
+ const filtered = rawEvents.filter((envelope) => {
124
+ if (typeIsAliased && !SAVE_SYNC_EVENT_TYPE_ALIASES.has(envelope.eventType))
125
+ return false;
126
+ if (options.runId !== undefined && envelope.metadata?.runId !== options.runId)
127
+ return false;
128
+ // Apply tag filters after the indexed state.db read.
128
129
  const tags = envelope.metadata?.tags ?? [];
129
130
  if (options.excludeTags?.some((t) => tags.includes(t)))
130
131
  return false;
@@ -132,98 +133,15 @@ export function readEvents(options = {}, ctx) {
132
133
  return false;
133
134
  return true;
134
135
  });
136
+ // `filtered` is ascending by id; slicing off the end keeps the MOST
137
+ // RECENT `limit` events post-filter, matching the SQL-pushdown path's
138
+ // semantics exactly. `nextOffset` intentionally stays `nextId` — the
139
+ // durable resume cursor tracks the underlying SQL read, not this
140
+ // display-only truncation.
141
+ const events = options.limit !== undefined && !pushLimitToSql ? filtered.slice(-options.limit) : filtered;
135
142
  return { events, nextOffset: nextId };
136
143
  }
137
144
  finally {
138
145
  db.close();
139
146
  }
140
147
  }
141
- /**
142
- * Follow the events table in state.db. Polls at `intervalMs` (default 75ms)
143
- * and emits every new event to `onEvent`. Resolves when `signal` aborts, when
144
- * `maxEvents` events have been observed, or when `maxDurationMs` elapses.
145
- *
146
- * The polling cursor is a monotonic SQLite rowid so concurrent writers cannot
147
- * cause skips: between two reads we always pick up everything inserted since
148
- * the last `nextOffset`.
149
- */
150
- export async function tailEvents(options = {}, ctx) {
151
- const intervalMs = options.intervalMs ?? 75;
152
- const collected = [];
153
- let cursor = options.sinceOffset ?? 0;
154
- // Seed the cursor: if the caller passed --since (timestamp) but no
155
- // sinceOffset, do an initial filtered read so they see history before
156
- // we start polling. This matches the documented behaviour of `tail
157
- // --since`: emit existing events that match, then follow.
158
- if (options.sinceOffset === undefined) {
159
- const initial = readEvents({
160
- since: options.since,
161
- type: options.type,
162
- ref: options.ref,
163
- excludeTags: options.excludeTags,
164
- includeTags: options.includeTags,
165
- }, ctx);
166
- for (const event of initial.events) {
167
- collected.push(event);
168
- options.onEvent?.(event);
169
- if (options.maxEvents !== undefined && collected.length >= options.maxEvents) {
170
- return { events: collected, nextOffset: initial.nextOffset, reason: "maxEvents" };
171
- }
172
- }
173
- cursor = initial.nextOffset;
174
- }
175
- const startedAt = Date.now();
176
- return new Promise((resolve) => {
177
- let resolved = false;
178
- let timer;
179
- function finish(reason) {
180
- if (resolved)
181
- return;
182
- resolved = true;
183
- if (timer)
184
- clearInterval(timer);
185
- resolve({ events: collected, nextOffset: cursor, reason });
186
- }
187
- function tick() {
188
- try {
189
- const result = readEvents({
190
- sinceOffset: cursor,
191
- type: options.type,
192
- ref: options.ref,
193
- excludeTags: options.excludeTags,
194
- includeTags: options.includeTags,
195
- }, ctx);
196
- cursor = result.nextOffset;
197
- for (const event of result.events) {
198
- // Apply --since filter inside the polling loop too — the cursor is
199
- // rowid-based so it can hand us events the user filtered out.
200
- if (options.since && event.ts && event.ts < options.since)
201
- continue;
202
- collected.push(event);
203
- options.onEvent?.(event);
204
- if (options.maxEvents !== undefined && collected.length >= options.maxEvents) {
205
- finish("maxEvents");
206
- return;
207
- }
208
- }
209
- }
210
- catch {
211
- // Non-fatal: stay in the loop.
212
- }
213
- if (options.maxDurationMs !== undefined && Date.now() - startedAt >= options.maxDurationMs) {
214
- finish("maxDuration");
215
- }
216
- }
217
- if (options.signal) {
218
- if (options.signal.aborted) {
219
- finish("signal");
220
- return;
221
- }
222
- options.signal.addEventListener("abort", () => finish("signal"), { once: true });
223
- }
224
- timer = setInterval(tick, intervalMs);
225
- // Run one tick immediately so callers don't have to wait an interval
226
- // for events written in the same tick as the tail starts.
227
- tick();
228
- });
229
- }
@@ -0,0 +1,62 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ export const EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS = [
5
+ "model",
6
+ "messages",
7
+ "temperature",
8
+ "maxtokens",
9
+ "responseformat",
10
+ "stream",
11
+ "streamoptions",
12
+ "enablethinking",
13
+ "chattemplatekwargs",
14
+ ];
15
+ export const EXTRA_PARAMS_CREDENTIAL_KEYS = [
16
+ "authorization",
17
+ "headers",
18
+ "apikey",
19
+ "token",
20
+ "password",
21
+ "secret",
22
+ "cookie",
23
+ "setcookie",
24
+ ];
25
+ const PROTECTED_TOP_LEVEL_KEYS = new Set(EXTRA_PARAMS_PROTECTED_TOP_LEVEL_KEYS);
26
+ const CREDENTIAL_KEYS = new Set(EXTRA_PARAMS_CREDENTIAL_KEYS);
27
+ export function normalizeExtraParamKey(key) {
28
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
29
+ }
30
+ /** Validate provider extras without allowing them to override AKM fields or carry credentials. */
31
+ export function validateExtraParams(value) {
32
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
33
+ return [{ path: [], message: "must be an object" }];
34
+ }
35
+ const issues = [];
36
+ const visit = (entry, path) => {
37
+ if (Array.isArray(entry)) {
38
+ entry.forEach((child, index) => {
39
+ visit(child, [...path, index]);
40
+ });
41
+ return;
42
+ }
43
+ if (!entry || typeof entry !== "object")
44
+ return;
45
+ for (const [key, child] of Object.entries(entry)) {
46
+ const normalized = normalizeExtraParamKey(key);
47
+ if (path.length === 0 && PROTECTED_TOP_LEVEL_KEYS.has(normalized)) {
48
+ issues.push({ path: [key], message: `${key} is protected by AKM` });
49
+ }
50
+ if (CREDENTIAL_KEYS.has(normalized)) {
51
+ issues.push({ path: [...path, key], message: `${key} cannot carry credentials` });
52
+ }
53
+ visit(child, [...path, key]);
54
+ }
55
+ };
56
+ visit(value, []);
57
+ return issues;
58
+ }
59
+ export function formatExtraParamsIssue(label, issue) {
60
+ const suffix = issue.path.map((part) => (typeof part === "number" ? `[${part}]` : `.${part}`)).join("");
61
+ return `${label}${suffix} ${issue.message}`;
62
+ }
@@ -0,0 +1,17 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * The content of a proposal's PRIMARY change (WI-6.2 envelope read path).
6
+ *
7
+ * By construction `changes[0].after === payload.content`; consumers read
8
+ * through this accessor so the single-content assumption lives in ONE place
9
+ * once multi-file proposals (consolidate ops) ride the envelope. Typed
10
+ * structurally (not against `Proposal`) so this module stays dependency-free.
11
+ */
12
+ export function proposalContent(p) {
13
+ const content = p.changes[0]?.after;
14
+ if (content === undefined)
15
+ throw new Error("Proposal primary change has no content.");
16
+ return content;
17
+ }