akm-cli 0.7.4 → 0.8.0-rc.10

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/CHANGELOG.md +224 -1
  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 +2631 -1440
  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 +45 -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 +1081 -73
  20. package/dist/commands/env.js +213 -0
  21. package/dist/commands/eval-cases.js +43 -0
  22. package/dist/commands/events.js +15 -24
  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 +3 -0
  60. package/dist/commands/proposal.js +67 -12
  61. package/dist/commands/propose.js +120 -45
  62. package/dist/commands/reflect.js +1104 -60
  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 +70 -7
  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 +158 -60
  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 -968
  91. package/dist/core/errors.js +42 -20
  92. package/dist/core/events.js +105 -135
  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 +198 -489
  113. package/dist/indexer/db.js +990 -108
  114. package/dist/indexer/ensure-index.js +136 -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 -114
  120. package/dist/indexer/index-context.js +4 -0
  121. package/dist/indexer/indexer.js +547 -309
  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 +250 -36
  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 +183 -35
  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 +79 -88
  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 -72
  166. package/dist/llm/index-passes.js +44 -29
  167. package/dist/llm/memory-infer.js +80 -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 -311
  174. package/dist/output/context.js +60 -8
  175. package/dist/output/renderers.js +306 -258
  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 -511
  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 -1093
  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 +179 -20
  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 +141 -2
  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 +91 -89
  286. package/dist/workflows/schema.js +3 -0
  287. package/dist/workflows/scope-key.js +79 -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.4.md +1 -1
  294. package/docs/migration/release-notes/0.7.5.md +20 -0
  295. package/docs/migration/release-notes/0.8.0.md +48 -0
  296. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  297. package/package.json +29 -11
  298. package/dist/commands/install-audit.js +0 -381
  299. package/dist/commands/vault.js +0 -333
  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)
@@ -78,40 +89,95 @@ export async function akmPropose(options) {
78
89
  throw err;
79
90
  }
80
91
  // 3. Build prompt.
92
+ // Synthesize a temp draft path so opencode can write the asset content
93
+ // directly using its file tools rather than returning JSON via stdout.
94
+ const draftFilePath = import("node:os").then((os) => import("node:path").then((path) => path.join(os.tmpdir(), `akm-propose-${options.type}-${options.name.replace(/[^a-z0-9_-]/gi, "_")}-${Date.now()}.md`)));
95
+ const resolvedDraftPath = await draftFilePath;
81
96
  const prompt = buildProposePrompt({
82
97
  type: options.type,
83
98
  name: options.name,
84
99
  task: options.task,
100
+ draftFilePath: resolvedDraftPath,
85
101
  });
86
102
  // 4. Spawn the agent.
87
- const runOptions = {
88
- stdio: "captured",
89
- parseOutput: "text",
90
- ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
91
- ...(options.runAgentOptions ?? {}),
92
- };
93
- const result = await runAgent(profile, prompt, runOptions);
103
+ // Real agent runs use interactive mode so file tools can write the draft.
104
+ // Injected/custom spawns still need captured stdout for JSON payload tests.
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
+ }
94
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
+ }
95
134
  return failureEnvelope(result, options.type, options.name);
96
135
  }
97
- // 5. Parse the structured response.
136
+ // 5. Resolve the proposal content.
137
+ // Path A: opencode wrote the draft file — read it directly (no stdout parse).
138
+ // Path B: fallback to stdout JSON parse for non-file-writing agents.
98
139
  let payload;
99
- try {
100
- payload = parseAgentProposalPayload(result.stdout);
101
- }
102
- catch (err) {
103
- return {
104
- schemaVersion: 1,
105
- ok: false,
106
- reason: "parse_error",
107
- error: err instanceof Error ? err.message : String(err),
108
- type: options.type,
109
- name: options.name,
110
- exitCode: result.exitCode,
111
- stdout: result.stdout,
112
- ...(result.stderr ? { stderr: result.stderr } : {}),
140
+ if (fs.existsSync(resolvedDraftPath)) {
141
+ const draftContent = fs.readFileSync(resolvedDraftPath, "utf8");
142
+ fs.unlinkSync(resolvedDraftPath);
143
+ payload = {
144
+ ref: `${options.type}:${options.name}`,
145
+ content: draftContent,
113
146
  };
114
147
  }
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
+ }
164
+ try {
165
+ payload = parseAgentProposalPayload(result.stdout ?? "");
166
+ }
167
+ catch (err) {
168
+ return {
169
+ schemaVersion: 1,
170
+ ok: false,
171
+ reason: "parse_error",
172
+ error: err instanceof Error ? err.message : String(err),
173
+ type: options.type,
174
+ name: options.name,
175
+ exitCode: result.exitCode,
176
+ stdout: result.stdout,
177
+ ...(result.stderr ? { stderr: result.stderr } : {}),
178
+ };
179
+ }
180
+ }
115
181
  // 6. Insert the proposal. Note: we allow the agent's `ref` to normalise the
116
182
  // asset name (e.g. path-cleanup), but only after validating that the ref is
117
183
  // well-formed and the type still matches the requested type.
@@ -154,12 +220,21 @@ export async function akmPropose(options) {
154
220
  ref,
155
221
  source: "propose",
156
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,
157
226
  payload: {
158
227
  content: payload.content,
159
228
  ...(payload.frontmatter ? { frontmatter: payload.frontmatter } : {}),
160
229
  },
161
230
  };
162
- 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;
163
238
  return {
164
239
  schemaVersion: 1,
165
240
  ok: true,