akm-cli 0.7.5 → 0.8.0-rc.11

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 (300) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +192 -2
  2. package/README.md +22 -6
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli/shared.js +129 -0
  9. package/dist/cli.js +2569 -1449
  10. package/dist/commands/add-cli.js +279 -0
  11. package/dist/commands/agent-dispatch.js +110 -0
  12. package/dist/commands/agent-support.js +68 -0
  13. package/dist/commands/completions.js +3 -0
  14. package/dist/commands/config-cli.js +130 -534
  15. package/dist/commands/consolidate.js +2122 -0
  16. package/dist/commands/curate.js +44 -3
  17. package/dist/commands/db-cli.js +23 -0
  18. package/dist/commands/distill-promotion-policy.js +660 -0
  19. package/dist/commands/distill.js +1075 -77
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +5 -23
  23. package/dist/commands/extract-cli.js +127 -0
  24. package/dist/commands/extract-prompt.js +204 -0
  25. package/dist/commands/extract.js +477 -0
  26. package/dist/commands/feedback-cli.js +331 -0
  27. package/dist/commands/graph.js +477 -0
  28. package/dist/commands/health.js +1302 -0
  29. package/dist/commands/help/help-accept.md +12 -0
  30. package/dist/commands/help/help-improve.md +69 -0
  31. package/dist/commands/help/help-proposals.md +18 -0
  32. package/dist/commands/help/help-propose.md +17 -0
  33. package/dist/commands/help/help-reject.md +11 -0
  34. package/dist/commands/history.js +54 -46
  35. package/dist/commands/improve-auto-accept.js +97 -0
  36. package/dist/commands/improve-cli.js +217 -0
  37. package/dist/commands/improve-profiles.js +166 -0
  38. package/dist/commands/improve-result-file.js +167 -0
  39. package/dist/commands/improve.js +2373 -0
  40. package/dist/commands/info.js +5 -2
  41. package/dist/commands/init.js +50 -2
  42. package/dist/commands/installed-stashes.js +102 -139
  43. package/dist/commands/knowledge.js +136 -0
  44. package/dist/commands/lint/agent-linter.js +49 -0
  45. package/dist/commands/lint/base-linter.js +479 -0
  46. package/dist/commands/lint/command-linter.js +49 -0
  47. package/dist/commands/lint/default-linter.js +16 -0
  48. package/dist/commands/lint/env-key-rules.js +154 -0
  49. package/dist/commands/lint/index.js +196 -0
  50. package/dist/commands/lint/knowledge-linter.js +16 -0
  51. package/dist/commands/lint/markdown-insertion.js +343 -0
  52. package/dist/commands/lint/memory-linter.js +61 -0
  53. package/dist/commands/lint/registry.js +36 -0
  54. package/dist/commands/lint/skill-linter.js +45 -0
  55. package/dist/commands/lint/task-linter.js +50 -0
  56. package/dist/commands/lint/types.js +4 -0
  57. package/dist/commands/lint/workflow-linter.js +56 -0
  58. package/dist/commands/lint.js +4 -0
  59. package/dist/commands/migration-help.js +5 -2
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +86 -31
  62. package/dist/commands/reflect.js +1091 -73
  63. package/dist/commands/registry-cli.js +150 -0
  64. package/dist/commands/registry-search.js +5 -2
  65. package/dist/commands/remember-cli.js +257 -0
  66. package/dist/commands/remember.js +69 -6
  67. package/dist/commands/schema-repair.js +203 -0
  68. package/dist/commands/search.js +115 -14
  69. package/dist/commands/secret.js +173 -0
  70. package/dist/commands/self-update.js +3 -0
  71. package/dist/commands/show.js +148 -25
  72. package/dist/commands/source-add.js +17 -45
  73. package/dist/commands/source-clone.js +3 -0
  74. package/dist/commands/source-manage.js +14 -19
  75. package/dist/commands/tasks.js +437 -0
  76. package/dist/commands/url-checker.js +42 -0
  77. package/dist/core/action-contributors.js +28 -0
  78. package/dist/core/asset-ref.js +17 -2
  79. package/dist/core/asset-registry.js +12 -17
  80. package/dist/core/asset-serialize.js +88 -0
  81. package/dist/core/asset-spec.js +67 -1
  82. package/dist/core/common.js +182 -0
  83. package/dist/core/concurrent.js +25 -0
  84. package/dist/core/config-io.js +347 -0
  85. package/dist/core/config-migration.js +622 -0
  86. package/dist/core/config-schema.js +534 -0
  87. package/dist/core/config-sources.js +108 -0
  88. package/dist/core/config-types.js +4 -0
  89. package/dist/core/config-walker.js +337 -0
  90. package/dist/core/config.js +364 -981
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +91 -138
  93. package/dist/core/file-lock.js +104 -0
  94. package/dist/core/frontmatter.js +75 -8
  95. package/dist/core/lesson-lint.js +3 -0
  96. package/dist/core/markdown.js +20 -0
  97. package/dist/core/memory-belief.js +62 -0
  98. package/dist/core/memory-contradiction-detect.js +274 -0
  99. package/dist/core/memory-improve.js +806 -0
  100. package/dist/core/parse.js +158 -0
  101. package/dist/core/paths.js +280 -14
  102. package/dist/core/proposal-quality-validators.js +380 -0
  103. package/dist/core/proposal-validators.js +69 -0
  104. package/dist/core/proposals.js +512 -42
  105. package/dist/core/state-db.js +1068 -0
  106. package/dist/core/text-truncation.js +107 -0
  107. package/dist/core/time.js +54 -0
  108. package/dist/core/tty.js +59 -0
  109. package/dist/core/warn.js +64 -1
  110. package/dist/core/write-source.js +3 -0
  111. package/dist/indexer/db-backup.js +391 -0
  112. package/dist/indexer/db-search.js +178 -256
  113. package/dist/indexer/db.js +975 -103
  114. package/dist/indexer/ensure-index.js +64 -0
  115. package/dist/indexer/file-context.js +3 -0
  116. package/dist/indexer/graph-boost.js +376 -101
  117. package/dist/indexer/graph-db.js +391 -0
  118. package/dist/indexer/graph-dedup.js +95 -0
  119. package/dist/indexer/graph-extraction.js +550 -124
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +523 -301
  122. package/dist/indexer/llm-cache.js +52 -0
  123. package/dist/indexer/manifest.js +3 -0
  124. package/dist/indexer/matchers.js +167 -160
  125. package/dist/indexer/memory-inference.js +152 -74
  126. package/dist/indexer/metadata-contributors.js +29 -0
  127. package/dist/indexer/metadata.js +275 -196
  128. package/dist/indexer/path-resolver.js +92 -0
  129. package/dist/indexer/project-context.js +192 -0
  130. package/dist/indexer/ranking-contributors.js +331 -0
  131. package/dist/indexer/ranking.js +81 -0
  132. package/dist/indexer/search-fields.js +5 -9
  133. package/dist/indexer/search-hit-enrichers.js +111 -0
  134. package/dist/indexer/search-source.js +44 -10
  135. package/dist/indexer/semantic-status.js +6 -17
  136. package/dist/indexer/staleness-detect.js +447 -0
  137. package/dist/indexer/usage-events.js +12 -9
  138. package/dist/indexer/walker.js +28 -0
  139. package/dist/integrations/agent/builders.js +135 -0
  140. package/dist/integrations/agent/config.js +122 -230
  141. package/dist/integrations/agent/detect.js +3 -0
  142. package/dist/integrations/agent/index.js +7 -13
  143. package/dist/integrations/agent/model-aliases.js +55 -0
  144. package/dist/integrations/agent/profiles.js +70 -5
  145. package/dist/integrations/agent/prompts.js +214 -80
  146. package/dist/integrations/agent/runner.js +151 -0
  147. package/dist/integrations/agent/sdk-runner.js +126 -0
  148. package/dist/integrations/agent/spawn.js +118 -23
  149. package/dist/integrations/github.js +3 -0
  150. package/dist/integrations/lockfile.js +32 -69
  151. package/dist/integrations/session-logs/index.js +69 -0
  152. package/dist/integrations/session-logs/inline-refs.js +35 -0
  153. package/dist/integrations/session-logs/pre-filter.js +152 -0
  154. package/dist/integrations/session-logs/providers/claude-code.js +282 -0
  155. package/dist/integrations/session-logs/providers/opencode.js +258 -0
  156. package/dist/integrations/session-logs/types.js +4 -0
  157. package/dist/llm/call-ai.js +62 -0
  158. package/dist/llm/client.js +77 -124
  159. package/dist/llm/embedder.js +20 -29
  160. package/dist/llm/embedders/cache.js +3 -7
  161. package/dist/llm/embedders/local.js +42 -1
  162. package/dist/llm/embedders/remote.js +20 -8
  163. package/dist/llm/embedders/types.js +3 -7
  164. package/dist/llm/feature-gate.js +95 -48
  165. package/dist/llm/graph-extract.js +676 -70
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +77 -71
  168. package/dist/llm/metadata-enhance.js +42 -29
  169. package/dist/llm/prompts/extract-session.md +80 -0
  170. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  171. package/dist/output/cli-hints-full.md +292 -0
  172. package/dist/output/cli-hints-short.md +66 -0
  173. package/dist/output/cli-hints.js +7 -320
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +300 -257
  176. package/dist/output/shapes/curate.js +56 -0
  177. package/dist/output/shapes/distill.js +10 -0
  178. package/dist/output/shapes/env-list.js +19 -0
  179. package/dist/output/shapes/events.js +11 -0
  180. package/dist/output/shapes/helpers.js +424 -0
  181. package/dist/output/shapes/history.js +7 -0
  182. package/dist/output/shapes/passthrough.js +102 -0
  183. package/dist/output/shapes/proposal-accept.js +7 -0
  184. package/dist/output/shapes/proposal-diff.js +7 -0
  185. package/dist/output/shapes/proposal-list.js +7 -0
  186. package/dist/output/shapes/proposal-producer.js +11 -0
  187. package/dist/output/shapes/proposal-reject.js +7 -0
  188. package/dist/output/shapes/proposal-show.js +7 -0
  189. package/dist/output/shapes/registry-search.js +6 -0
  190. package/dist/output/shapes/registry.js +30 -0
  191. package/dist/output/shapes/search.js +6 -0
  192. package/dist/output/shapes/secret-list.js +19 -0
  193. package/dist/output/shapes/show.js +6 -0
  194. package/dist/output/shapes/vault-list.js +19 -0
  195. package/dist/output/shapes.js +51 -516
  196. package/dist/output/text/add.js +6 -0
  197. package/dist/output/text/clone.js +6 -0
  198. package/dist/output/text/config.js +6 -0
  199. package/dist/output/text/curate.js +6 -0
  200. package/dist/output/text/distill.js +7 -0
  201. package/dist/output/text/enable-disable.js +7 -0
  202. package/dist/output/text/events.js +10 -0
  203. package/dist/output/text/feedback.js +6 -0
  204. package/dist/output/text/helpers.js +1039 -0
  205. package/dist/output/text/history.js +7 -0
  206. package/dist/output/text/import.js +6 -0
  207. package/dist/output/text/index.js +6 -0
  208. package/dist/output/text/info.js +6 -0
  209. package/dist/output/text/init.js +6 -0
  210. package/dist/output/text/list.js +6 -0
  211. package/dist/output/text/proposal-producer.js +8 -0
  212. package/dist/output/text/proposal.js +11 -0
  213. package/dist/output/text/registry-commands.js +11 -0
  214. package/dist/output/text/registry.js +30 -0
  215. package/dist/output/text/remember.js +6 -0
  216. package/dist/output/text/remove.js +6 -0
  217. package/dist/output/text/save.js +6 -0
  218. package/dist/output/text/search.js +6 -0
  219. package/dist/output/text/show.js +6 -0
  220. package/dist/output/text/update.js +6 -0
  221. package/dist/output/text/upgrade.js +6 -0
  222. package/dist/output/text/vault.js +16 -0
  223. package/dist/output/text/wiki.js +15 -0
  224. package/dist/output/text/workflow.js +14 -0
  225. package/dist/output/text.js +44 -1092
  226. package/dist/registry/build-index.js +3 -0
  227. package/dist/registry/create-provider-registry.js +3 -0
  228. package/dist/registry/factory.js +4 -1
  229. package/dist/registry/origin-resolve.js +3 -0
  230. package/dist/registry/providers/index.js +3 -0
  231. package/dist/registry/providers/skills-sh.js +71 -50
  232. package/dist/registry/providers/static-index.js +53 -48
  233. package/dist/registry/providers/types.js +3 -24
  234. package/dist/registry/resolve.js +11 -16
  235. package/dist/registry/types.js +3 -0
  236. package/dist/scripts/migrate-storage.js +17750 -0
  237. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
  238. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  239. package/dist/setup/detect.js +3 -0
  240. package/dist/setup/ripgrep-install.js +3 -0
  241. package/dist/setup/ripgrep-resolve.js +3 -0
  242. package/dist/setup/setup.js +775 -37
  243. package/dist/setup/steps.js +3 -15
  244. package/dist/sources/include.js +3 -0
  245. package/dist/sources/provider-factory.js +5 -12
  246. package/dist/sources/provider.js +3 -20
  247. package/dist/sources/providers/filesystem.js +19 -23
  248. package/dist/sources/providers/git.js +138 -21
  249. package/dist/sources/providers/index.js +3 -0
  250. package/dist/sources/providers/install-types.js +3 -13
  251. package/dist/sources/providers/npm.js +3 -4
  252. package/dist/sources/providers/provider-utils.js +3 -0
  253. package/dist/sources/providers/sync-from-ref.js +3 -11
  254. package/dist/sources/providers/tar-utils.js +3 -0
  255. package/dist/sources/providers/website.js +18 -22
  256. package/dist/sources/resolve.js +3 -0
  257. package/dist/sources/types.js +3 -0
  258. package/dist/sources/website-ingest.js +7 -0
  259. package/dist/tasks/backends/cron.js +203 -0
  260. package/dist/tasks/backends/exec-utils.js +28 -0
  261. package/dist/tasks/backends/index.js +24 -0
  262. package/dist/tasks/backends/launchd-template.xml +19 -0
  263. package/dist/tasks/backends/launchd.js +187 -0
  264. package/dist/tasks/backends/schtasks-template.xml +29 -0
  265. package/dist/tasks/backends/schtasks.js +215 -0
  266. package/dist/tasks/parser.js +211 -0
  267. package/dist/tasks/resolveAkmBin.js +87 -0
  268. package/dist/tasks/runner.js +458 -0
  269. package/dist/tasks/schedule.js +227 -0
  270. package/dist/tasks/schema.js +15 -0
  271. package/dist/tasks/validator.js +62 -0
  272. package/dist/version.js +3 -0
  273. package/dist/wiki/index-template.md +12 -0
  274. package/dist/wiki/ingest-workflow-template.md +54 -0
  275. package/dist/wiki/log-template.md +8 -0
  276. package/dist/wiki/schema-template.md +61 -0
  277. package/dist/wiki/wiki-templates.js +15 -0
  278. package/dist/wiki/wiki.js +13 -61
  279. package/dist/workflows/authoring.js +8 -25
  280. package/dist/workflows/cli.js +3 -0
  281. package/dist/workflows/db.js +140 -10
  282. package/dist/workflows/document-cache.js +3 -10
  283. package/dist/workflows/parser.js +3 -0
  284. package/dist/workflows/renderer.js +11 -3
  285. package/dist/workflows/runs.js +77 -92
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +3 -0
  288. package/dist/workflows/validator.js +4 -8
  289. package/dist/workflows/workflow-template.md +24 -0
  290. package/docs/README.md +10 -2
  291. package/docs/data-and-telemetry.md +225 -0
  292. package/docs/migration/release-notes/0.7.0.md +1 -1
  293. package/docs/migration/release-notes/0.7.5.md +2 -2
  294. package/docs/migration/release-notes/0.8.0.md +48 -0
  295. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  296. package/package.json +30 -12
  297. package/.github/LICENSE +0 -374
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -328
  300. package/dist/templates/wiki-templates.js +0 -100
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * `akm proposal {list,show,accept,reject,diff}` — review surface for the
3
6
  * proposal substrate (#225).
@@ -12,7 +15,7 @@ import { resolveStashDir } from "../core/common";
12
15
  import { loadConfig } from "../core/config";
13
16
  import { UsageError } from "../core/errors";
14
17
  import { appendEvent } from "../core/events";
15
- import { archiveProposal, createProposal, diffProposal, getProposal, listProposals, promoteProposal, validateProposal, } from "../core/proposals";
18
+ import { archiveProposal, createProposal, diffProposal, getProposal, isProposalSkipped, listProposals, promoteProposal, resolveProposalId, revertProposal, validateProposal, } from "../core/proposals";
16
19
  // ── Shared helpers ──────────────────────────────────────────────────────────
17
20
  function resolveStash(stashDir) {
18
21
  if (stashDir)
@@ -21,13 +24,17 @@ function resolveStash(stashDir) {
21
24
  }
22
25
  export function akmProposalList(options = {}) {
23
26
  const stash = resolveStash(options.stashDir);
24
- // `--status accepted|rejected` implies archive-inclusion since the live
25
- // queue only ever contains pending entries.
26
- const includeArchive = options.includeArchive === true || options.status === "accepted" || options.status === "rejected";
27
+ // `--status accepted|rejected|reverted` implies archive-inclusion since the
28
+ // live queue only ever contains pending entries.
29
+ const includeArchive = options.includeArchive === true ||
30
+ options.status === "accepted" ||
31
+ options.status === "rejected" ||
32
+ options.status === "reverted";
27
33
  const proposals = listProposals(stash, {
28
34
  includeArchive,
29
35
  status: options.status,
30
36
  ref: options.ref,
37
+ type: options.type,
31
38
  });
32
39
  return { schemaVersion: 1, totalCount: proposals.length, proposals };
33
40
  }
@@ -43,7 +50,8 @@ export function akmProposalShow(options) {
43
50
  export async function akmProposalAccept(options) {
44
51
  const stash = resolveStash(options.stashDir);
45
52
  const config = options.config ?? loadConfig();
46
- const result = await promoteProposal(stash, config, options.id, { target: options.target }, options.ctx);
53
+ const resolvedId = resolveProposalId(stash, options.id).id;
54
+ const result = await promoteProposal(stash, config, resolvedId, { target: options.target }, options.ctx);
47
55
  // Emit `promoted` to the events stream so observers (audit, dashboards,
48
56
  // sync) see the accept happen. Only emit on the happy path — promotion
49
57
  // throws on validation failure, so reaching this point means the asset
@@ -69,11 +77,11 @@ export async function akmProposalAccept(options) {
69
77
  }
70
78
  export function akmProposalReject(options) {
71
79
  const stash = resolveStash(options.stashDir);
72
- const existing = getProposal(stash, options.id);
80
+ const existing = resolveProposalId(stash, options.id);
73
81
  if (existing.status !== "pending") {
74
- throw new UsageError(`Proposal ${options.id} is not pending (current status: ${existing.status}). Only pending proposals can be rejected.`, "INVALID_FLAG_VALUE");
82
+ throw new UsageError(`Proposal ${existing.id} is not pending (current status: ${existing.status}). Only pending proposals can be rejected.`, "INVALID_FLAG_VALUE");
75
83
  }
76
- const updated = archiveProposal(stash, options.id, "rejected", options.reason, options.ctx);
84
+ const updated = archiveProposal(stash, existing.id, "rejected", options.reason, options.ctx);
77
85
  appendEvent({
78
86
  eventType: "rejected",
79
87
  ref: updated.ref,
@@ -96,8 +104,8 @@ export function akmProposalReject(options) {
96
104
  export function akmProposalDiff(options) {
97
105
  const stash = resolveStash(options.stashDir);
98
106
  const config = options.config ?? loadConfig();
99
- const proposal = getProposal(stash, options.id);
100
- const diff = diffProposal(stash, config, options.id, { target: options.target });
107
+ const proposal = resolveProposalId(stash, options.id);
108
+ const diff = diffProposal(stash, config, proposal.id, { target: options.target });
101
109
  return {
102
110
  schemaVersion: 1,
103
111
  id: proposal.id,
@@ -109,11 +117,58 @@ export function akmProposalDiff(options) {
109
117
  }
110
118
  export function akmProposalCreate(options) {
111
119
  const stash = resolveStash(options.stashDir);
112
- const proposal = createProposal(stash, {
120
+ // Manual proposal creation (via `akm proposal create`) always bypasses
121
+ // dedup/cooldown guards — the operator is explicitly requesting a proposal.
122
+ const result = createProposal(stash, {
113
123
  ref: options.ref,
114
124
  source: options.source,
115
125
  ...(options.sourceRun !== undefined ? { sourceRun: options.sourceRun } : {}),
116
126
  payload: options.payload,
127
+ force: true,
117
128
  }, options.ctx);
118
- return { schemaVersion: 1, ok: true, proposal };
129
+ if (isProposalSkipped(result)) {
130
+ // Should never happen with force:true — defensive only.
131
+ throw new Error(`Unexpected proposal skip: ${result.message}`);
132
+ }
133
+ return { schemaVersion: 1, ok: true, proposal: result };
134
+ }
135
+ /**
136
+ * Restore an accepted proposal's prior content from the backup captured at
137
+ * promotion time (Advantage D6c / Phase 6C).
138
+ *
139
+ * Failure modes (all surface as typed errors so the CLI can map exit codes):
140
+ * - Proposal id does not resolve → `NotFoundError("FILE_NOT_FOUND")`
141
+ * (raised by `resolveProposalId` / `getProposal`).
142
+ * - Proposal is not `status === "accepted"` → `UsageError("INVALID_FLAG_VALUE")`
143
+ * with message `"only accepted proposals can be reverted ..."`.
144
+ * - No `backup` field, or the backup file is missing on disk →
145
+ * `UsageError` with message `"no backup available for this proposal ..."`.
146
+ *
147
+ * On success, emits a `proposal_reverted` event for observability, mirroring
148
+ * how `akmProposalAccept` emits `promoted` and `akmProposalReject` emits
149
+ * `rejected`.
150
+ */
151
+ export async function akmProposalRevert(options) {
152
+ const stash = resolveStash(options.stashDir);
153
+ const config = options.config ?? loadConfig();
154
+ const resolvedId = resolveProposalId(stash, options.id).id;
155
+ const result = await revertProposal(stash, config, resolvedId, { target: options.target }, options.ctx);
156
+ appendEvent({
157
+ eventType: "proposal_reverted",
158
+ ref: result.ref,
159
+ metadata: {
160
+ proposalId: result.proposal.id,
161
+ source: result.proposal.source,
162
+ ...(result.proposal.sourceRun !== undefined ? { sourceRun: result.proposal.sourceRun } : {}),
163
+ assetPath: result.assetPath,
164
+ },
165
+ });
166
+ return {
167
+ schemaVersion: 1,
168
+ ok: true,
169
+ id: result.proposal.id,
170
+ ref: result.ref,
171
+ assetPath: result.assetPath,
172
+ proposal: result.proposal,
173
+ };
119
174
  }
@@ -1,3 +1,6 @@
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/.
1
4
  /**
2
5
  * `akm propose <type> <name> --task ...` — proposal-producing agent
3
6
  * command (#226).
@@ -9,37 +12,23 @@
9
12
  * Failures use the same {@link AgentFailureReason} discriminants as
10
13
  * `akm reflect`. `propose_invoked` is emitted at command entry.
11
14
  */
15
+ import fs from "node:fs";
12
16
  import { parseAssetRef } from "../core/asset-ref";
13
17
  import { TYPE_DIRS } from "../core/asset-spec";
14
18
  import { resolveStashDir } from "../core/common";
15
- import { loadConfig } from "../core/config";
16
19
  import { ConfigError, UsageError } from "../core/errors";
17
20
  import { appendEvent } from "../core/events";
18
- import { createProposal } from "../core/proposals";
19
- import { parseAgentConfig, requireAgentProfile, runAgent, } from "../integrations/agent";
21
+ import { createProposal, isProposalSkipped, } from "../core/proposals";
22
+ import { runAgent, } from "../integrations/agent";
23
+ import { resolveProcessAgentProfile } from "../integrations/agent/config";
20
24
  import { buildProposePrompt, parseAgentProposalPayload } from "../integrations/agent/prompts";
21
- function loadAgentConfigFromDisk() {
22
- const config = loadConfig();
23
- return parseAgentConfig(config.agent);
24
- }
25
- function resolveProfile(options) {
26
- if (options.agentProfile)
27
- return options.agentProfile;
28
- const agent = options.agentConfig ?? loadAgentConfigFromDisk();
29
- return requireAgentProfile(agent, options.profile);
30
- }
25
+ import { runAgentSdk } from "../integrations/agent/sdk-runner";
26
+ import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "./agent-support";
31
27
  function failureEnvelope(result, type, name, fallbackReason = "non_zero_exit") {
32
- const reason = result.reason ?? fallbackReason;
33
28
  return {
34
- schemaVersion: 1,
35
- ok: false,
36
- reason,
37
- error: result.error ?? `agent failure (${reason})`,
29
+ ...baseFailureFields(result, fallbackReason),
38
30
  type,
39
31
  name,
40
- exitCode: result.exitCode,
41
- ...(result.stdout ? { stdout: result.stdout } : {}),
42
- ...(result.stderr ? { stderr: result.stderr } : {}),
43
32
  };
44
33
  }
45
34
  export async function akmPropose(options) {
@@ -68,9 +57,31 @@ export async function akmPropose(options) {
68
57
  },
69
58
  });
70
59
  // 2. Resolve profile.
60
+ // When an explicit --profile flag is given, honour it directly (existing
61
+ // behaviour). Otherwise use resolveProcessAgentProfile so that per-process
62
+ // agent config (agent.processes["propose"]) is picked up automatically.
71
63
  let profile;
64
+ let resolvedTimeoutMs = options.timeoutMs;
72
65
  try {
73
- profile = resolveProfile(options);
66
+ if (options.agentProfile) {
67
+ // Test seam: injected profile bypasses all config.
68
+ profile = options.agentProfile;
69
+ }
70
+ else if (options.profile) {
71
+ // Explicit --profile flag wins over process config.
72
+ profile = resolveAgentProfile(options);
73
+ }
74
+ else {
75
+ // Use per-process config resolution (falls back to agent.default).
76
+ const agent = options.agentConfig ?? loadAgentConfigFromDisk();
77
+ const processName = options.agentProcess ?? "propose";
78
+ const resolved = resolveProcessAgentProfile(processName, agent);
79
+ profile = resolved.profile;
80
+ // Only apply process-resolved timeoutMs when caller didn't supply one.
81
+ if (resolvedTimeoutMs === undefined) {
82
+ resolvedTimeoutMs = resolved.timeoutMs;
83
+ }
84
+ }
74
85
  }
75
86
  catch (err) {
76
87
  if (err instanceof ConfigError || err instanceof UsageError)
@@ -91,20 +102,40 @@ export async function akmPropose(options) {
91
102
  // 4. Spawn the agent.
92
103
  // Real agent runs use interactive mode so file tools can write the draft.
93
104
  // Injected/custom spawns still need captured stdout for JSON payload tests.
94
- const runOptions = {
95
- stdio: options.runAgentOptions?.spawn ? "captured" : "interactive",
96
- parseOutput: "text",
97
- ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
98
- ...(options.runAgentOptions ?? {}),
99
- };
100
- const result = await runAgent(profile, prompt, runOptions);
105
+ // Use callAi for the unified AI dispatch path (agent CLI preferred, LLM HTTP fallback).
106
+ const useCustomSpawn = Boolean(options.runAgentOptions?.spawn);
107
+ let result;
108
+ if (useCustomSpawn) {
109
+ // Test seam: use raw runAgent with injected spawn so tests remain deterministic.
110
+ const runOptions = {
111
+ stdio: "captured",
112
+ parseOutput: "text",
113
+ ...(resolvedTimeoutMs !== undefined ? { timeoutMs: resolvedTimeoutMs } : {}),
114
+ ...(options.runAgentOptions ?? {}),
115
+ };
116
+ result = await runAgent(profile, prompt, runOptions);
117
+ }
118
+ else {
119
+ // Production path: dispatch directly to the appropriate runner.
120
+ const runOptions = {
121
+ stdio: resolvedDraftPath ? "interactive" : "captured",
122
+ parseOutput: "text",
123
+ ...(resolvedTimeoutMs !== undefined ? { timeoutMs: resolvedTimeoutMs } : {}),
124
+ };
125
+ result = profile.sdkMode
126
+ ? await runAgentSdk(profile, prompt ?? "", runOptions)
127
+ : await runAgent(profile, prompt, runOptions);
128
+ }
101
129
  if (!result.ok) {
130
+ // B3: ENOENT / not-found gives an actionable hint.
131
+ if (isEnoentFailure(result)) {
132
+ return { ...failureEnvelope(result, options.type, options.name), error: enoentHintMessage(profile.bin) };
133
+ }
102
134
  return failureEnvelope(result, options.type, options.name);
103
135
  }
104
136
  // 5. Resolve the proposal content.
105
137
  // Path A: opencode wrote the draft file — read it directly (no stdout parse).
106
138
  // Path B: fallback to stdout JSON parse for non-file-writing agents.
107
- const fs = await import("node:fs");
108
139
  let payload;
109
140
  if (fs.existsSync(resolvedDraftPath)) {
110
141
  const draftContent = fs.readFileSync(resolvedDraftPath, "utf8");
@@ -115,6 +146,21 @@ export async function akmPropose(options) {
115
146
  };
116
147
  }
117
148
  else {
149
+ // B1: When interactive mode was used and stdout is empty, the agent did not
150
+ // write the draft file and stdout was not captured — surface an actionable error.
151
+ const stdioWasInteractive = !useCustomSpawn;
152
+ if (stdioWasInteractive && (result.stdout ?? "") === "") {
153
+ return {
154
+ schemaVersion: 1,
155
+ ok: false,
156
+ reason: "parse_error",
157
+ error: "Agent did not write draft file and stdout was not captured (interactive mode). Check that the agent CLI understood the file-write instruction, or configure a headless profile with stdio: 'captured'.",
158
+ type: options.type,
159
+ name: options.name,
160
+ exitCode: result.exitCode,
161
+ ...(result.stderr ? { stderr: result.stderr } : {}),
162
+ };
163
+ }
118
164
  try {
119
165
  payload = parseAgentProposalPayload(result.stdout ?? "");
120
166
  }
@@ -174,12 +220,21 @@ export async function akmPropose(options) {
174
220
  ref,
175
221
  source: "propose",
176
222
  sourceRun: `propose-${Date.now()}`,
223
+ // User-initiated proposals always bypass dedup/cooldown guards — the
224
+ // operator is explicitly asking for a new proposal.
225
+ force: true,
177
226
  payload: {
178
227
  content: payload.content,
179
228
  ...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
180
229
  },
181
230
  };
182
- const proposal = createProposal(stash, createInput, options.ctx);
231
+ const proposalResult = createProposal(stash, createInput, options.ctx);
232
+ // With force:true, the result is always a Proposal (never skipped).
233
+ if (isProposalSkipped(proposalResult)) {
234
+ // Should never happen when force:true, but be defensive.
235
+ throw new Error(`Unexpected skip in propose command: ${proposalResult.message}`);
236
+ }
237
+ const proposal = proposalResult;
183
238
  return {
184
239
  schemaVersion: 1,
185
240
  ok: true,