akm-cli 0.8.7 → 0.8.14

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 (324) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/tasks/core/backup.yml +4 -0
  9. package/dist/assets/tasks/core/extract.yml +4 -0
  10. package/dist/assets/tasks/core/improve.yml +4 -0
  11. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  12. package/dist/assets/tasks/core/sync.yml +4 -0
  13. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  14. package/dist/assets/tasks/core/version-check.yml +4 -0
  15. package/dist/assets/templates/html/default.html +78 -0
  16. package/dist/assets/templates/html/health.html +560 -0
  17. package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
  18. package/dist/cli/config-migrate.js +6 -6
  19. package/dist/cli/config-validate.js +4 -4
  20. package/dist/cli/confirm.js +3 -3
  21. package/dist/cli/parse-args.js +1 -1
  22. package/dist/cli/shared.js +72 -19
  23. package/dist/cli-node.mjs +26 -0
  24. package/dist/cli.js +206 -3866
  25. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  26. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  27. package/dist/commands/agent/contribute-cli.js +200 -0
  28. package/dist/commands/completions.js +1 -1
  29. package/dist/commands/config-cli.js +230 -3
  30. package/dist/commands/db-cli.js +2 -2
  31. package/dist/commands/env/env-cli.js +529 -0
  32. package/dist/commands/env/env.js +410 -0
  33. package/dist/commands/env/secret-cli.js +259 -0
  34. package/dist/commands/{secret.js → env/secret.js} +6 -47
  35. package/dist/commands/events.js +4 -4
  36. package/dist/commands/feedback-cli.js +18 -34
  37. package/dist/commands/graph/graph-cli.js +132 -0
  38. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  39. package/dist/commands/health/checks.js +279 -0
  40. package/dist/commands/health/html-report.js +448 -0
  41. package/dist/commands/health.js +189 -266
  42. package/dist/commands/{consolidate.js → improve/consolidate.js} +48 -36
  43. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  44. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  45. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  46. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  47. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  48. package/dist/commands/{extract.js → improve/extract.js} +221 -26
  49. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
  50. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
  51. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  52. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
  53. package/dist/commands/{improve.js → improve/improve.js} +672 -292
  54. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  55. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  56. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  57. package/dist/commands/improve/reflect-noise.js +0 -0
  58. package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
  59. package/dist/commands/improve/session-asset.js +248 -0
  60. package/dist/commands/lint/agent-linter.js +1 -1
  61. package/dist/commands/lint/base-linter.js +55 -37
  62. package/dist/commands/lint/command-linter.js +1 -1
  63. package/dist/commands/lint/default-linter.js +1 -1
  64. package/dist/commands/lint/env-key-rules.js +1 -1
  65. package/dist/commands/lint/index.js +19 -25
  66. package/dist/commands/lint/knowledge-linter.js +1 -1
  67. package/dist/commands/lint/memory-linter.js +1 -1
  68. package/dist/commands/lint/registry.js +8 -8
  69. package/dist/commands/lint/skill-linter.js +1 -1
  70. package/dist/commands/lint/task-linter.js +1 -1
  71. package/dist/commands/lint/workflow-linter.js +1 -1
  72. package/dist/commands/lint.js +1 -1
  73. package/dist/commands/observability-cli.js +244 -0
  74. package/dist/commands/proposal/drain-policies.js +3 -3
  75. package/dist/commands/proposal/drain.js +87 -15
  76. package/dist/commands/proposal/proposal-cli.js +490 -0
  77. package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
  78. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  79. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  80. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  81. package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
  82. package/dist/commands/{curate.js → read/curate.js} +7 -7
  83. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  84. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  85. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  86. package/dist/commands/read/search-cli.js +207 -0
  87. package/dist/commands/{search.js → read/search.js} +22 -27
  88. package/dist/commands/{show.js → read/show.js} +31 -45
  89. package/dist/commands/registry-cli.js +8 -8
  90. package/dist/commands/remember.js +14 -10
  91. package/dist/commands/sources/add-cli.js +293 -0
  92. package/dist/commands/{history.js → sources/history.js} +27 -25
  93. package/dist/commands/{info.js → sources/info.js} +6 -6
  94. package/dist/commands/{init.js → sources/init.js} +6 -6
  95. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  96. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  97. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  98. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  99. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  100. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  101. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  102. package/dist/commands/sources/sources-cli.js +305 -0
  103. package/dist/commands/sources/stash-cli.js +219 -0
  104. package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
  105. package/dist/commands/tasks/default-tasks.js +173 -0
  106. package/dist/commands/tasks/tasks-cli.js +210 -0
  107. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  108. package/dist/commands/wiki-cli.js +307 -0
  109. package/dist/commands/workflow-cli.js +329 -0
  110. package/dist/core/action-contributors.js +1 -1
  111. package/dist/core/assert.js +40 -0
  112. package/dist/core/asset/asset-create.js +54 -0
  113. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  114. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  115. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  116. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  117. package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
  118. package/dist/core/best-effort.js +64 -0
  119. package/dist/core/common.js +32 -18
  120. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  121. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  122. package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
  123. package/dist/core/config/config-types.js +16 -0
  124. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  125. package/dist/core/{config.js → config/config.js} +10 -8
  126. package/dist/core/env-secret-ref.js +90 -0
  127. package/dist/core/errors.js +13 -3
  128. package/dist/core/events.js +27 -4
  129. package/dist/core/file-lock.js +1 -1
  130. package/dist/core/improve-types.js +48 -0
  131. package/dist/core/lesson-lint.js +2 -2
  132. package/dist/core/logs-db.js +304 -0
  133. package/dist/core/paths.js +2 -2
  134. package/dist/core/ripgrep/install.js +2 -2
  135. package/dist/core/ripgrep/resolve.js +2 -2
  136. package/dist/core/state-db.js +195 -60
  137. package/dist/core/text-truncation.js +148 -0
  138. package/dist/core/time.js +1 -1
  139. package/dist/core/write-source.js +98 -85
  140. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  141. package/dist/indexer/{db.js → db/db.js} +128 -118
  142. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  143. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  144. package/dist/indexer/ensure-index.js +4 -4
  145. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  146. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  147. package/dist/indexer/indexer.js +37 -30
  148. package/dist/indexer/init.js +54 -0
  149. package/dist/indexer/manifest.js +10 -10
  150. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
  151. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  152. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  153. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  154. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  155. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  156. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  157. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  158. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  159. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  160. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  161. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  162. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  163. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  164. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  165. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  166. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  167. package/dist/integrations/agent/builder-shared.js +39 -0
  168. package/dist/integrations/agent/builders.js +14 -81
  169. package/dist/integrations/agent/config.js +6 -4
  170. package/dist/integrations/agent/detect.js +1 -1
  171. package/dist/integrations/agent/index.js +23 -8
  172. package/dist/integrations/agent/prompts.js +2 -3
  173. package/dist/integrations/agent/runner.js +22 -3
  174. package/dist/integrations/agent/spawn.js +9 -10
  175. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  176. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  177. package/dist/integrations/harnesses/claude/index.js +64 -0
  178. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
  179. package/dist/integrations/harnesses/index.js +144 -0
  180. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  181. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  182. package/dist/integrations/harnesses/opencode/index.js +59 -0
  183. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  184. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  185. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  186. package/dist/integrations/harnesses/types.js +43 -0
  187. package/dist/integrations/lockfile.js +7 -16
  188. package/dist/integrations/session-logs/index.js +82 -9
  189. package/dist/llm/call-ai.js +4 -4
  190. package/dist/llm/client.js +146 -6
  191. package/dist/llm/embedder.js +6 -6
  192. package/dist/llm/embedders/local.js +9 -22
  193. package/dist/llm/embedders/remote.js +2 -2
  194. package/dist/llm/embedders/types.js +1 -1
  195. package/dist/llm/graph-extract.js +31 -12
  196. package/dist/llm/index-passes.js +1 -1
  197. package/dist/llm/memory-infer.js +12 -5
  198. package/dist/llm/metadata-enhance.js +2 -2
  199. package/dist/llm/usage-persist.js +77 -0
  200. package/dist/llm/usage-telemetry.js +103 -0
  201. package/dist/output/context.js +9 -46
  202. package/dist/output/html-render.js +73 -0
  203. package/dist/output/renderers.js +88 -58
  204. package/dist/output/shapes/curate.js +7 -3
  205. package/dist/output/shapes/distill.js +7 -3
  206. package/dist/output/shapes/env-list.js +18 -16
  207. package/dist/output/shapes/events.js +5 -4
  208. package/dist/output/shapes/helpers.js +19 -5
  209. package/dist/output/shapes/history.js +7 -3
  210. package/dist/output/shapes/passthrough.js +8 -11
  211. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  212. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  213. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  214. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  215. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  216. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  217. package/dist/output/shapes/registry-search.js +7 -3
  218. package/dist/output/shapes/registry.js +12 -0
  219. package/dist/output/shapes/search.js +7 -3
  220. package/dist/output/shapes/secret-list.js +18 -16
  221. package/dist/output/shapes/show.js +7 -3
  222. package/dist/output/shapes.js +55 -30
  223. package/dist/output/text/add.js +2 -3
  224. package/dist/output/text/clone.js +2 -3
  225. package/dist/output/text/config.js +2 -3
  226. package/dist/output/text/curate.js +4 -3
  227. package/dist/output/text/distill.js +2 -3
  228. package/dist/output/text/enable-disable.js +5 -4
  229. package/dist/output/text/env.js +13 -0
  230. package/dist/output/text/events.js +5 -4
  231. package/dist/output/text/feedback.js +4 -3
  232. package/dist/output/text/helpers.js +123 -40
  233. package/dist/output/text/history.js +2 -3
  234. package/dist/output/text/import.js +2 -3
  235. package/dist/output/text/index.js +2 -3
  236. package/dist/output/text/info.js +2 -3
  237. package/dist/output/text/init.js +2 -3
  238. package/dist/output/text/list.js +2 -3
  239. package/dist/output/text/proposal/producer.js +9 -0
  240. package/dist/output/text/proposal/proposal.js +13 -0
  241. package/dist/output/text/registry-commands.js +8 -7
  242. package/dist/output/text/registry.js +12 -0
  243. package/dist/output/text/remember.js +4 -3
  244. package/dist/output/text/remove.js +2 -3
  245. package/dist/output/text/save.js +2 -3
  246. package/dist/output/text/search.js +4 -3
  247. package/dist/output/text/show.js +4 -3
  248. package/dist/output/text/update.js +2 -3
  249. package/dist/output/text/upgrade.js +2 -3
  250. package/dist/output/text/wiki.js +12 -11
  251. package/dist/output/text/workflow.js +12 -10
  252. package/dist/output/text.js +66 -32
  253. package/dist/registry/build-index.js +11 -10
  254. package/dist/registry/factory.js +1 -1
  255. package/dist/registry/origin-resolve.js +1 -1
  256. package/dist/registry/providers/index.js +2 -2
  257. package/dist/registry/providers/skills-sh.js +91 -72
  258. package/dist/registry/providers/static-index.js +75 -52
  259. package/dist/registry/resolve.js +3 -3
  260. package/dist/runtime.js +242 -0
  261. package/dist/scripts/migrate-storage.js +1654 -683
  262. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
  263. package/dist/setup/detect.js +311 -9
  264. package/dist/setup/harness-config-import.js +6 -120
  265. package/dist/setup/setup.js +454 -43
  266. package/dist/sources/include.js +1 -1
  267. package/dist/sources/provider-factory.js +2 -2
  268. package/dist/sources/providers/filesystem.js +3 -3
  269. package/dist/sources/providers/git.js +9 -9
  270. package/dist/sources/providers/index.js +4 -4
  271. package/dist/sources/providers/npm.js +6 -6
  272. package/dist/sources/providers/provider-utils.js +13 -20
  273. package/dist/sources/providers/sync-from-ref.js +5 -5
  274. package/dist/sources/providers/tar-utils.js +2 -2
  275. package/dist/sources/providers/website.js +2 -2
  276. package/dist/sources/resolve.js +5 -5
  277. package/dist/sources/website-ingest.js +5 -5
  278. package/dist/storage/database.js +102 -0
  279. package/dist/storage/engines/sqlite-migrations.js +42 -0
  280. package/dist/storage/locations.js +25 -0
  281. package/dist/storage/repositories/index-db.js +43 -0
  282. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  283. package/dist/tasks/backends/cron.js +4 -4
  284. package/dist/tasks/backends/exec-utils.js +32 -0
  285. package/dist/tasks/backends/index.js +3 -3
  286. package/dist/tasks/backends/launchd.js +7 -14
  287. package/dist/tasks/backends/schtasks.js +7 -16
  288. package/dist/tasks/embedded.js +71 -0
  289. package/dist/tasks/parser.js +2 -2
  290. package/dist/tasks/resolveAkmBin.js +1 -1
  291. package/dist/tasks/runner.js +127 -31
  292. package/dist/tasks/schedule.js +1 -1
  293. package/dist/tasks/validator.js +7 -7
  294. package/dist/text-import-hook.mjs +51 -0
  295. package/dist/version.js +2 -1
  296. package/dist/wiki/wiki.js +7 -7
  297. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  298. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  299. package/dist/workflows/cli.js +1 -1
  300. package/dist/workflows/db.js +54 -32
  301. package/dist/workflows/parser.js +4 -4
  302. package/dist/workflows/renderer.js +5 -5
  303. package/dist/workflows/runtime/agent-identity.js +56 -0
  304. package/dist/workflows/runtime/checkin.js +57 -0
  305. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  306. package/dist/workflows/validate-summary.js +82 -0
  307. package/docs/README.md +1 -1
  308. package/docs/data-and-telemetry.md +6 -6
  309. package/package.json +17 -8
  310. package/dist/commands/add-cli.js +0 -279
  311. package/dist/commands/env.js +0 -213
  312. package/dist/integrations/agent/sdk-runner.js +0 -126
  313. package/dist/output/shapes/vault-list.js +0 -19
  314. package/dist/output/text/proposal-producer.js +0 -8
  315. package/dist/output/text/proposal.js +0 -12
  316. package/dist/output/text/vault.js +0 -16
  317. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  318. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  319. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  320. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  321. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  322. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  323. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  324. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -0,0 +1,490 @@
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
+ * `akm proposal` command family (#225). Extracted verbatim from src/cli.ts
6
+ * (WS6) so the God Module shrinks; the `main.subCommands.proposal` key and
7
+ * every subcommand's args/output shape are byte-identical. Leaf handlers are
8
+ * migrated to `defineJsonCommand`, which wraps the body in `runWithJsonErrors`
9
+ * and emits the same JSON envelope (stdout/stderr/exit-code) as the inline
10
+ * `runWithJsonErrors` form it replaces.
11
+ */
12
+ import { defineCommand } from "citty";
13
+ import { hasSubcommand, parsePositiveIntFlag } from "../../cli/parse-args.js";
14
+ import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
15
+ import { resolveStashDir } from "../../core/common.js";
16
+ import { loadConfig } from "../../core/config/config.js";
17
+ import { UsageError } from "../../core/errors.js";
18
+ import { resolveTriageJudgmentRunner } from "../../integrations/agent/runner.js";
19
+ import { installLlmUsagePersistenceIfAbsent } from "../../llm/usage-persist.js";
20
+ import { withLlmStage } from "../../llm/usage-telemetry.js";
21
+ import { resolveImproveProfile } from "../improve/improve-profiles.js";
22
+ import { drainProposals } from "./drain.js";
23
+ import { resolveDrainPolicy } from "./drain-policies.js";
24
+ import { akmProposalAccept, akmProposalDiff, akmProposalList, akmProposalReject, akmProposalRevert, akmProposalShow, } from "./proposal.js";
25
+ function parseProposalStatus(raw) {
26
+ if (raw === undefined)
27
+ return undefined;
28
+ const trimmed = raw.trim();
29
+ if (!trimmed)
30
+ return undefined;
31
+ if (trimmed === "pending" || trimmed === "accepted" || trimmed === "rejected" || trimmed === "reverted") {
32
+ return trimmed;
33
+ }
34
+ throw new UsageError(`Invalid --status value: "${raw}". Expected one of: pending, accepted, rejected, reverted.`, "INVALID_FLAG_VALUE");
35
+ }
36
+ const proposalListCommand = defineJsonCommand({
37
+ meta: { name: "list", description: "List proposal queue entries" },
38
+ args: {
39
+ status: {
40
+ type: "string",
41
+ description: "Filter by status (pending|accepted|rejected|reverted)",
42
+ },
43
+ ref: { type: "string", description: "Filter by asset ref (type:name)" },
44
+ type: { type: "string", description: "Filter by asset type" },
45
+ },
46
+ run({ args }) {
47
+ const status = parseProposalStatus(args.status);
48
+ const result = akmProposalList({
49
+ status,
50
+ ref: args.ref,
51
+ type: args.type,
52
+ includeArchive: status === "accepted" || status === "rejected" || status === "reverted",
53
+ });
54
+ output("proposal-list", result);
55
+ },
56
+ });
57
+ const proposalAcceptCommand = defineJsonCommand({
58
+ meta: { name: "accept", description: "Accept a proposal and promote it into the stash" },
59
+ args: {
60
+ id: {
61
+ type: "positional",
62
+ description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream). Optional when --generator is provided.",
63
+ required: false,
64
+ },
65
+ target: { type: "string", description: "Override the write target by source name" },
66
+ // F-6 / #393: Batch accept by generator, diff size, or age.
67
+ generator: {
68
+ type: "string",
69
+ description: "F-6: Bulk-accept all pending proposals from this generator (e.g. reflect, distill). Requires no positional id.",
70
+ },
71
+ "max-diff-lines": {
72
+ type: "string",
73
+ description: "F-6: When bulk-accepting, only accept proposals whose content is <= this many lines. Skips larger proposals.",
74
+ },
75
+ "older-than": {
76
+ type: "string",
77
+ description: "F-6: When bulk-accepting, only accept proposals created more than this many days ago (e.g. '7' for 7 days).",
78
+ },
79
+ "dry-run": {
80
+ type: "boolean",
81
+ description: "F-6: List proposals that would be bulk-accepted without accepting them.",
82
+ default: false,
83
+ },
84
+ yes: {
85
+ type: "boolean",
86
+ alias: "y",
87
+ description: "Skip confirmation prompt (required in non-interactive mode for bulk accept)",
88
+ default: false,
89
+ },
90
+ },
91
+ async run({ args }) {
92
+ const generator = args.generator;
93
+ // F-6 / #393: Bulk-accept when --generator is provided without a positional id.
94
+ if (generator && !args.id) {
95
+ const { confirmDestructive } = await import("../../cli/confirm.js");
96
+ const confirmed = await confirmDestructive(`Bulk-accept all matching proposals from generator "${generator}"? This cannot be undone.`, { yes: args.yes === true || args["dry-run"] === true });
97
+ if (!confirmed) {
98
+ process.stderr.write("Aborted.\n");
99
+ return;
100
+ }
101
+ const { listProposals } = await import("./validators/proposals.js");
102
+ const stashDir = resolveStashDir();
103
+ const rawMaxDiff = args["max-diff-lines"] ? Number.parseInt(String(args["max-diff-lines"]), 10) : undefined;
104
+ if (rawMaxDiff !== undefined && (Number.isNaN(rawMaxDiff) || rawMaxDiff < 0)) {
105
+ throw new UsageError("--max-diff-lines must be a non-negative integer", "INVALID_FLAG_VALUE");
106
+ }
107
+ const rawOlderThan = args["older-than"] ? Number.parseInt(String(args["older-than"]), 10) : undefined;
108
+ if (rawOlderThan !== undefined && (Number.isNaN(rawOlderThan) || rawOlderThan < 0)) {
109
+ throw new UsageError("--older-than must be a non-negative integer (days)", "INVALID_FLAG_VALUE");
110
+ }
111
+ const maxDiffLines = rawMaxDiff;
112
+ const olderThanMs = rawOlderThan !== undefined ? rawOlderThan * 86_400_000 : undefined;
113
+ const pending = listProposals(stashDir, { status: "pending" }).filter((p) => {
114
+ if (p.source !== generator)
115
+ return false;
116
+ if (maxDiffLines !== undefined) {
117
+ const lines = (p.payload.content ?? "").split("\n").length;
118
+ if (lines > maxDiffLines)
119
+ return false;
120
+ }
121
+ if (olderThanMs !== undefined) {
122
+ const age = Date.now() - new Date(p.createdAt).getTime();
123
+ if (age < olderThanMs)
124
+ return false;
125
+ }
126
+ return true;
127
+ });
128
+ const results = [];
129
+ for (const proposal of pending) {
130
+ if (args["dry-run"]) {
131
+ results.push({ id: proposal.id, ref: proposal.ref, source: proposal.source, dryRun: true });
132
+ }
133
+ else {
134
+ const result = await akmProposalAccept({ id: proposal.id, target: args.target });
135
+ results.push(result);
136
+ }
137
+ }
138
+ output("proposal-accept-batch", { accepted: results.length, results, dryRun: args["dry-run"] });
139
+ return;
140
+ }
141
+ if (!args.id) {
142
+ throw new UsageError("Usage: akm proposal accept <id> OR akm proposal accept --generator <generator>", "MISSING_REQUIRED_ARGUMENT");
143
+ }
144
+ const result = await akmProposalAccept({ id: args.id, target: args.target });
145
+ output("proposal-accept", result);
146
+ },
147
+ });
148
+ const proposalRejectCommand = defineJsonCommand({
149
+ meta: { name: "reject", description: "Reject a proposal and record the reason" },
150
+ args: {
151
+ id: {
152
+ type: "positional",
153
+ description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream). Optional when --generator is provided.",
154
+ required: false,
155
+ },
156
+ reason: { type: "string", description: "Reason for rejection (required)" },
157
+ // F-6 / #393: Batch reject by generator, diff size, or age.
158
+ generator: {
159
+ type: "string",
160
+ description: "F-6: Bulk-reject all pending proposals from this generator (e.g. reflect, distill). Requires no positional id.",
161
+ },
162
+ "max-diff-lines": {
163
+ type: "string",
164
+ description: "F-6: When bulk-rejecting, only reject proposals whose content is <= this many lines. Skips larger proposals.",
165
+ },
166
+ "older-than": {
167
+ type: "string",
168
+ description: "F-6: When bulk-rejecting, only reject proposals created more than this many days ago (e.g. '7' for 7 days).",
169
+ },
170
+ "dry-run": {
171
+ type: "boolean",
172
+ description: "F-6: List proposals that would be bulk-rejected without rejecting them.",
173
+ default: false,
174
+ },
175
+ yes: {
176
+ type: "boolean",
177
+ alias: "y",
178
+ description: "Skip confirmation prompt (required in non-interactive mode)",
179
+ default: false,
180
+ },
181
+ },
182
+ async run({ args }) {
183
+ const generator = args.generator;
184
+ if (!args.reason || !String(args.reason).trim()) {
185
+ throw new UsageError("Usage: akm proposal reject <id> --reason '<reason>' OR akm proposal reject --generator <generator> --reason '<reason>'", "MISSING_REQUIRED_ARGUMENT");
186
+ }
187
+ // F-6 / #393: Bulk-reject when --generator is provided without a positional id.
188
+ if (generator && !args.id) {
189
+ const { confirmDestructive } = await import("../../cli/confirm.js");
190
+ const confirmed = await confirmDestructive(`Bulk-reject all matching proposals from generator "${generator}"? This cannot be undone.`, { yes: args.yes === true || args["dry-run"] === true });
191
+ if (!confirmed) {
192
+ process.stderr.write("Aborted.\n");
193
+ return;
194
+ }
195
+ const { listProposals } = await import("./validators/proposals.js");
196
+ const stashDir = resolveStashDir();
197
+ const rawMaxDiff = args["max-diff-lines"] ? Number.parseInt(String(args["max-diff-lines"]), 10) : undefined;
198
+ if (rawMaxDiff !== undefined && (Number.isNaN(rawMaxDiff) || rawMaxDiff < 0)) {
199
+ throw new UsageError("--max-diff-lines must be a non-negative integer", "INVALID_FLAG_VALUE");
200
+ }
201
+ const rawOlderThan = args["older-than"] ? Number.parseInt(String(args["older-than"]), 10) : undefined;
202
+ if (rawOlderThan !== undefined && (Number.isNaN(rawOlderThan) || rawOlderThan < 0)) {
203
+ throw new UsageError("--older-than must be a non-negative integer (days)", "INVALID_FLAG_VALUE");
204
+ }
205
+ const maxDiffLines = rawMaxDiff;
206
+ const olderThanMs = rawOlderThan !== undefined ? rawOlderThan * 86_400_000 : undefined;
207
+ const pending = listProposals(stashDir, { status: "pending" }).filter((p) => {
208
+ if (p.source !== generator)
209
+ return false;
210
+ if (maxDiffLines !== undefined) {
211
+ const lines = (p.payload.content ?? "").split("\n").length;
212
+ if (lines > maxDiffLines)
213
+ return false;
214
+ }
215
+ if (olderThanMs !== undefined) {
216
+ const age = Date.now() - new Date(p.createdAt).getTime();
217
+ if (age < olderThanMs)
218
+ return false;
219
+ }
220
+ return true;
221
+ });
222
+ const results = [];
223
+ for (const proposal of pending) {
224
+ if (args["dry-run"]) {
225
+ results.push({ id: proposal.id, ref: proposal.ref, source: proposal.source, dryRun: true });
226
+ }
227
+ else {
228
+ const result = akmProposalReject({ id: proposal.id, reason: String(args.reason) });
229
+ results.push(result);
230
+ }
231
+ }
232
+ output("proposal-reject-batch", { rejected: results.length, results, dryRun: args["dry-run"] });
233
+ return;
234
+ }
235
+ if (!args.id) {
236
+ throw new UsageError("Usage: akm proposal reject <id> --reason '<reason>' OR akm proposal reject --generator <generator> --reason '<reason>'", "MISSING_REQUIRED_ARGUMENT");
237
+ }
238
+ const { confirmDestructive } = await import("../../cli/confirm.js");
239
+ const confirmed = await confirmDestructive(`Reject proposal "${args.id}"? This cannot be undone.`, {
240
+ yes: args.yes === true,
241
+ });
242
+ if (!confirmed) {
243
+ process.stderr.write("Aborted.\n");
244
+ return;
245
+ }
246
+ const result = akmProposalReject({ id: args.id, reason: String(args.reason) });
247
+ output("proposal-reject", result);
248
+ },
249
+ });
250
+ const proposalDiffCommand = defineJsonCommand({
251
+ meta: { name: "diff", description: "Show the diff for a proposal (accepts full UUID, UUID prefix, or asset ref)" },
252
+ args: {
253
+ id: {
254
+ type: "positional",
255
+ description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream)",
256
+ required: true,
257
+ },
258
+ target: { type: "string", description: "Override the write target by source name" },
259
+ },
260
+ run({ args }) {
261
+ const result = akmProposalDiff({ id: args.id, target: args.target });
262
+ output("proposal-diff", result);
263
+ },
264
+ });
265
+ // Phase 6C (Advantage D6c): revert an accepted proposal.
266
+ //
267
+ // Exit codes (mapped by `runWithJsonErrors` from the typed errors thrown by
268
+ // `akmProposalRevert` / `revertProposal`):
269
+ // 0 — success; prior content restored.
270
+ // 1 — generic error (also used by `UsageError("INVALID_FLAG_VALUE")` and
271
+ // `UsageError("MISSING_REQUIRED_ARGUMENT")` when the proposal is not
272
+ // accepted, or no backup is available).
273
+ // 1 — `NotFoundError("FILE_NOT_FOUND")` when the proposal id does not resolve.
274
+ const proposalRevertCommand = defineJsonCommand({
275
+ meta: {
276
+ name: "revert",
277
+ description: "Revert an accepted proposal: restore the prior asset content from the backup captured at promotion time. " +
278
+ "Errors if the proposal is not accepted or has no backup (new-asset proposals leave no backup). " +
279
+ "Accepts the full proposal UUID or the asset ref. UUID prefixes are not supported for archived proposals — use the full UUID.",
280
+ },
281
+ args: {
282
+ id: {
283
+ type: "positional",
284
+ description: "Proposal id (full uuid) or asset ref (e.g. skill:akm-dream). UUID prefixes are not supported for archived proposals — use the full UUID.",
285
+ required: true,
286
+ },
287
+ target: { type: "string", description: "Override the write target by source name" },
288
+ },
289
+ async run({ args }) {
290
+ const result = await akmProposalRevert({
291
+ id: args.id,
292
+ target: args.target,
293
+ });
294
+ output("proposal-revert", result);
295
+ },
296
+ });
297
+ // `proposal show` (#225): show a single proposal with its validation findings.
298
+ // `akmProposalShow` already backs `akm show proposal <id>` (now deprecated); this
299
+ // is the canonical noun-group entry point.
300
+ const proposalShowCommand = defineJsonCommand({
301
+ meta: { name: "show", description: "Show a single proposal and its validation findings" },
302
+ args: {
303
+ id: {
304
+ type: "positional",
305
+ description: "Proposal id (uuid / prefix) or asset ref (e.g. skill:akm-dream)",
306
+ required: true,
307
+ },
308
+ },
309
+ run({ args }) {
310
+ const result = akmProposalShow({ id: args.id });
311
+ output("proposal-show", result);
312
+ },
313
+ });
314
+ const proposalDrainCommand = defineJsonCommand({
315
+ meta: {
316
+ name: "drain",
317
+ description: "Drain the standing pending proposal backlog using a deterministic triage policy",
318
+ },
319
+ args: {
320
+ policy: {
321
+ type: "string",
322
+ description: "Built-in preset (personal-stash|conservative|manual) or path to a policy file",
323
+ },
324
+ "dry-run": {
325
+ type: "boolean",
326
+ description: "List what would be accepted/rejected/deferred without writing.",
327
+ default: false,
328
+ },
329
+ yes: {
330
+ type: "boolean",
331
+ alias: "y",
332
+ description: "Skip confirmation prompt (required in non-interactive mode for promotion).",
333
+ default: false,
334
+ },
335
+ "max-accepts": {
336
+ type: "string",
337
+ description: "Hard per-run accept ceiling. Accepts beyond this are reported as skippedByCap.",
338
+ },
339
+ "max-diff-lines": {
340
+ type: "string",
341
+ description: "Defer (never promote) accepts whose proposed content exceeds this many lines.",
342
+ },
343
+ "older-than": {
344
+ type: "string",
345
+ description: "Only consider proposals created more than this many days ago.",
346
+ },
347
+ promote: {
348
+ type: "boolean",
349
+ description: "Promote (accept) matching proposals. Default is queue mode (stage only, no writes to assets).",
350
+ default: false,
351
+ },
352
+ judgment: {
353
+ type: "boolean",
354
+ description: "Opt into the judgment tier (llm by default; agent/sdk per config) for deferred items. No-op with a logged triage_deferred summary when no runner is configured.",
355
+ default: false,
356
+ },
357
+ profile: {
358
+ type: "string",
359
+ description: "Read the triage block (policy, applyMode, ceilings, judgment) from this improve profile.",
360
+ },
361
+ },
362
+ async run({ args }) {
363
+ const stashDir = resolveStashDir();
364
+ const cfg = loadConfig();
365
+ // Phase 2: read the triage block from the named improve profile. CLI flags
366
+ // always override config; config supplies defaults for any flag omitted.
367
+ const triageConfig = args.profile !== undefined ? resolveImproveProfile(args.profile, cfg).processes?.triage : undefined;
368
+ const policy = resolveDrainPolicy(args.policy ?? triageConfig?.policy);
369
+ const dryRun = args["dry-run"] === true;
370
+ const applyMode = args.promote === true ? "promote" : (triageConfig?.applyMode ?? "queue");
371
+ const maxAccepts = parsePositiveIntFlag(args["max-accepts"], "--max-accepts") ??
372
+ triageConfig?.maxAcceptsPerRun ??
373
+ 25;
374
+ const maxDiffLines = parsePositiveIntFlag(args["max-diff-lines"], "--max-diff-lines") ??
375
+ triageConfig?.maxDiffLines;
376
+ const rawOlderThan = parsePositiveIntFlag(args["older-than"], "--older-than");
377
+ const olderThanMs = rawOlderThan !== undefined ? rawOlderThan * 86_400_000 : undefined;
378
+ // Promotion in promote mode is destructive (commits to git, no batch revert).
379
+ if (applyMode === "promote" && !dryRun) {
380
+ const { confirmDestructive } = await import("../../cli/confirm.js");
381
+ const confirmed = await confirmDestructive(`Drain and promote matching pending proposals under policy "${policy.name}"? Promotions commit to git and cannot be batch-reverted.`, { yes: args.yes === true });
382
+ if (!confirmed) {
383
+ process.stderr.write("Aborted.\n");
384
+ return;
385
+ }
386
+ }
387
+ // `--older-than` is applied here as a pre-filter on excludeIds: ids that
388
+ // are too fresh are excluded so the engine never touches them. This reads
389
+ // the pending set once here; drainProposals reads the pending set again
390
+ // internally, so a future engine-level olderThan option could remove this
391
+ // second read (engine API owned by another agent — not changed here).
392
+ let excludeIds;
393
+ if (olderThanMs !== undefined) {
394
+ const { listProposals } = await import("./validators/proposals.js");
395
+ const now = Date.now();
396
+ excludeIds = new Set(listProposals(stashDir, { status: "pending" })
397
+ // Fail SAFE: exclude a proposal when its age cannot be computed
398
+ // (NaN createdAt) OR it is too fresh. An unparseable createdAt must
399
+ // never be treated as old enough to drain/promote.
400
+ .filter((proposal) => {
401
+ const age = now - new Date(proposal.createdAt).getTime();
402
+ return Number.isNaN(age) || age < olderThanMs;
403
+ })
404
+ .map((proposal) => proposal.id));
405
+ }
406
+ // Phase 3: resolve the judgment runner when --judgment is set. Default
407
+ // mode is llm; falls back to defaults.llm when the triage block sets
408
+ // neither mode nor profile (mirrors resolveValidationRunner). null when
409
+ // nothing is configured → the engine leaves deferred items unresolved and
410
+ // emits triage_deferred.
411
+ const judgment = args.judgment === true ? resolveTriageJudgmentRunner(triageConfig?.judgment, cfg) : null;
412
+ // #576: persist + attribute per-call LLM usage for the standalone drain
413
+ // path. `IfAbsent` keeps an enclosing `akm improve` sink in charge when
414
+ // drain runs as a sub-step; the disposer clears only a sink we installed.
415
+ const disposeDrainUsageSink = installLlmUsagePersistenceIfAbsent();
416
+ let result;
417
+ try {
418
+ result = await withLlmStage("drain", () => drainProposals({
419
+ stashDir,
420
+ policy,
421
+ applyMode,
422
+ maxAccepts,
423
+ dryRun,
424
+ ...(maxDiffLines !== undefined ? { maxDiffLines } : {}),
425
+ ...(excludeIds ? { excludeIds } : {}),
426
+ judgment,
427
+ }));
428
+ }
429
+ finally {
430
+ disposeDrainUsageSink();
431
+ }
432
+ output("proposal-drain", {
433
+ schemaVersion: 1,
434
+ ok: true,
435
+ policy: policy.name,
436
+ applyMode,
437
+ dryRun,
438
+ promoted: result.promoted,
439
+ rejected: result.rejected,
440
+ deferred: result.deferred,
441
+ skippedByCap: result.skippedByCap,
442
+ });
443
+ },
444
+ });
445
+ // ── proposal noun group (#225 / 0.8 CLI stabilization) ────────────────────────
446
+ //
447
+ // `akm proposal <verb>` is the canonical grammar in 0.8. The flat verbs
448
+ // (`proposals`/`accept`/`reject`/`diff`/`revert`) remain as deprecated aliases
449
+ // that warn to stderr and delegate to the same command bodies; they are removed
450
+ // in 0.9.0. Bare `akm proposal` behaves as `proposal list` (mirrors `akm env`).
451
+ // Single source of truth: the routing set is derived from the subCommands keys
452
+ // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
453
+ const proposalSubCommands = {
454
+ list: proposalListCommand,
455
+ show: proposalShowCommand,
456
+ diff: proposalDiffCommand,
457
+ accept: proposalAcceptCommand,
458
+ reject: proposalRejectCommand,
459
+ revert: proposalRevertCommand,
460
+ drain: proposalDrainCommand,
461
+ };
462
+ const PROPOSAL_SUBCOMMAND_SET = new Set(Object.keys(proposalSubCommands));
463
+ export const proposalCommand = defineCommand({
464
+ meta: { name: "proposal", description: "Manage the proposal queue: list, show, diff, accept, reject, revert" },
465
+ args: {
466
+ status: {
467
+ type: "string",
468
+ description: "Filter by status (pending|accepted|rejected|reverted)",
469
+ },
470
+ ref: { type: "string", description: "Filter by asset ref (type:name)" },
471
+ type: { type: "string", description: "Filter by asset type" },
472
+ },
473
+ subCommands: proposalSubCommands,
474
+ run({ args }) {
475
+ return runWithJsonErrors(() => {
476
+ // citty runs the group body even after a subcommand; short-circuit so the
477
+ // default-to-list body only fires for bare `akm proposal [--status …]`.
478
+ if (hasSubcommand(args, PROPOSAL_SUBCOMMAND_SET))
479
+ return;
480
+ const status = parseProposalStatus(args.status);
481
+ const result = akmProposalList({
482
+ status,
483
+ ref: args.ref,
484
+ type: args.type,
485
+ includeArchive: status === "accepted" || status === "rejected" || status === "reverted",
486
+ });
487
+ output("proposal-list", result);
488
+ });
489
+ },
490
+ });
@@ -11,17 +11,28 @@
11
11
  * fallback in the output layer — every shape is registered explicitly in
12
12
  * `src/output/shapes.ts` and `src/output/text.ts`.
13
13
  */
14
- import { resolveStashDir } from "../core/common";
15
- import { loadConfig } from "../core/config";
16
- import { UsageError } from "../core/errors";
17
- import { appendEvent } from "../core/events";
18
- import { archiveProposal, createProposal, diffProposal, getProposal, isProposalSkipped, listProposals, promoteProposal, resolveProposalId, revertProposal, validateProposal, } from "../core/proposals";
14
+ import { resolveStashDir } from "../../core/common.js";
15
+ import { loadConfig } from "../../core/config/config.js";
16
+ import { UsageError } from "../../core/errors.js";
17
+ import { appendEvent } from "../../core/events.js";
18
+ import { archiveProposal, createProposal, diffProposal, getProposal, isProposalSkipped, listProposals, promoteProposal, resolveProposalId, revertProposal, validateProposal, } from "./validators/proposals.js";
19
19
  // ── Shared helpers ──────────────────────────────────────────────────────────
20
20
  function resolveStash(stashDir) {
21
21
  if (stashDir)
22
22
  return stashDir;
23
23
  return resolveStashDir();
24
24
  }
25
+ /**
26
+ * Thin in-process read of the pending proposal queue, used by the health HTML
27
+ * report builder (#582) so it never shells out to `akm proposal list`.
28
+ *
29
+ * Deliberately narrow (one optional arg, returns the storage-layer rows) so
30
+ * the parallel proposal-storage-to-SQLite consolidation only has to swap this
31
+ * one function's body.
32
+ */
33
+ export function listPendingProposals(stashDir) {
34
+ return listProposals(resolveStash(stashDir), { status: "pending" });
35
+ }
25
36
  export function akmProposalList(options = {}) {
26
37
  const stash = resolveStash(options.stashDir);
27
38
  // `--status accepted|rejected|reverted` implies archive-inclusion since the
@@ -141,7 +152,7 @@ export function akmProposalCreate(options) {
141
152
  * (raised by `resolveProposalId` / `getProposal`).
142
153
  * - Proposal is not `status === "accepted"` → `UsageError("INVALID_FLAG_VALUE")`
143
154
  * with message `"only accepted proposals can be reverted ..."`.
144
- * - No `backup` field, or the backup file is missing on disk
155
+ * - No backup content on the record (new-asset proposals capture none)
145
156
  * `UsageError` with message `"no backup available for this proposal ..."`.
146
157
  *
147
158
  * On success, emits a `proposal_reverted` event for observability, mirroring
@@ -13,17 +13,17 @@
13
13
  * `akm reflect`. `propose_invoked` is emitted at command entry.
14
14
  */
15
15
  import fs from "node:fs";
16
- import { parseAssetRef } from "../core/asset-ref";
17
- import { TYPE_DIRS } from "../core/asset-spec";
18
- import { resolveStashDir } from "../core/common";
19
- import { ConfigError, UsageError } from "../core/errors";
20
- import { appendEvent } from "../core/events";
21
- import { createProposal, isProposalSkipped, } from "../core/proposals";
22
- import { runAgent, } from "../integrations/agent";
23
- import { resolveProcessAgentProfile } from "../integrations/agent/config";
24
- import { buildProposePrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
25
- import { runAgentSdk } from "../integrations/agent/sdk-runner";
26
- import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "./agent-support";
16
+ import { parseAssetRef } from "../../core/asset/asset-ref.js";
17
+ import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
18
+ import { resolveStashDir } from "../../core/common.js";
19
+ import { ConfigError, UsageError } from "../../core/errors.js";
20
+ import { appendEvent } from "../../core/events.js";
21
+ import { runAgent, } from "../../integrations/agent/index.js";
22
+ import { resolveProcessAgentProfile } from "../../integrations/agent/config.js";
23
+ import { buildProposePrompt, parseAgentProposalPayload } from "../../integrations/agent/prompts.js";
24
+ import { runAgentSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
25
+ import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "../agent/agent-support.js";
26
+ import { createProposal, isProposalSkipped, } from "./validators/proposals.js";
27
27
  function failureEnvelope(result, type, name, fallbackReason = "non_zero_exit") {
28
28
  return {
29
29
  ...baseFailureFields(result, fallbackReason),
@@ -58,8 +58,8 @@
58
58
  * assets where the absolute ceiling takes over.
59
59
  */
60
60
  // ── Reflect-size guard ───────────────────────────────────────────────────────
61
- import { parseFrontmatter } from "./frontmatter";
62
- import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "./text-truncation";
61
+ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
62
+ import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
63
63
  // ── Description / when_to_use shape ─────────────────────────────────────────
64
64
  export const HEADING_FRAGMENT_PATTERNS = [
65
65
  /^for example\b/i,
@@ -115,7 +115,11 @@ export function isValidDescription(value, inputRef, options = {}) {
115
115
  reason: `description has ${backtickCount} backticks (unbalanced); likely contains a malformed code fragment`,
116
116
  };
117
117
  if (/^when\b/i.test(v))
118
- return { ok: false, reason: "description starts with 'When' — that pattern belongs in when_to_use" };
118
+ return {
119
+ ok: false,
120
+ reason: "description starts with 'When' — that pattern belongs in when_to_use",
121
+ severity: "warn",
122
+ };
119
123
  if (!options.skipRefTailCheck) {
120
124
  const refTail = inputRef.split(":").pop()?.toLowerCase() ?? "";
121
125
  if (refTail.length >= 6 && v.toLowerCase().includes(refTail) && v.length < refTail.length + 40)
@@ -299,6 +303,7 @@ const lessonContentQualityValidator = {
299
303
  findings.push({
300
304
  kind: "invalid-description",
301
305
  message: `Lesson proposal ${proposal.id} (${proposal.ref}) has an invalid description: ${descCheck.reason}.`,
306
+ ...(descCheck.severity ? { severity: descCheck.severity } : {}),
302
307
  });
303
308
  const wtuCheck = isValidWhenToUse(fm.when_to_use, proposal.ref);
304
309
  if (!wtuCheck.ok)
@@ -1,10 +1,10 @@
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
- import { parseAssetRef } from "./asset-ref";
5
- import { parseFrontmatter } from "./frontmatter";
6
- import { lintLessonContent } from "./lesson-lint";
7
- import { defaultProposalQualityValidators } from "./proposal-quality-validators";
4
+ import { parseAssetRef } from "../../../core/asset/asset-ref.js";
5
+ import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
6
+ import { lintLessonContent } from "../../../core/lesson-lint.js";
7
+ import { defaultProposalQualityValidators } from "./proposal-quality-validators.js";
8
8
  const genericProposalValidator = {
9
9
  name: "generic-proposal-validator",
10
10
  appliesTo: () => true,
@@ -65,5 +65,5 @@ export function runProposalValidators(proposal, validators = defaultProposalVali
65
65
  if (ctx.stop)
66
66
  break;
67
67
  }
68
- return { ok: findings.length === 0, findings };
68
+ return { ok: findings.every((f) => f.severity === "warn"), findings };
69
69
  }