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

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 (262) hide show
  1. package/CHANGELOG.md +339 -2
  2. package/SECURITY.md +23 -24
  3. package/dist/assets/help/help-improve.md +10 -10
  4. package/dist/assets/hints/cli-hints-full.md +44 -10
  5. package/dist/assets/hints/cli-hints-short.md +6 -2
  6. package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
  7. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
  8. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
  9. package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
  10. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  11. package/dist/assets/stash-skeleton/README.md +28 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
  24. package/dist/assets/tasks/core/backup.yml +1 -0
  25. package/dist/assets/tasks/core/extract.yml +1 -0
  26. package/dist/assets/tasks/core/improve.yml +1 -0
  27. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  28. package/dist/assets/tasks/core/sync.yml +1 -0
  29. package/dist/assets/tasks/core/version-check.yml +1 -0
  30. package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
  31. package/dist/assets/templates/html/health.html +5 -1
  32. package/dist/cli/config-migrate.js +31 -138
  33. package/dist/cli/config-validate.js +10 -8
  34. package/dist/cli.js +48 -14
  35. package/dist/commands/agent/agent-dispatch.js +17 -16
  36. package/dist/commands/agent/agent-support.js +0 -24
  37. package/dist/commands/agent/contribute-cli.js +5 -15
  38. package/dist/commands/backup-cli.js +54 -0
  39. package/dist/commands/config-cli.js +45 -159
  40. package/dist/commands/env/env-binding.js +95 -0
  41. package/dist/commands/env/env-cli.js +8 -65
  42. package/dist/commands/env/secret.js +8 -5
  43. package/dist/commands/health/checks.js +130 -83
  44. package/dist/commands/health/html-report.js +4 -0
  45. package/dist/commands/health/improve-metrics.js +30 -32
  46. package/dist/commands/health/llm-usage.js +19 -19
  47. package/dist/commands/health/md-report.js +4 -0
  48. package/dist/commands/health/metrics.js +2 -1
  49. package/dist/commands/health/surfaces.js +5 -4
  50. package/dist/commands/health.js +1 -1
  51. package/dist/commands/improve/consolidate/chunking.js +2 -2
  52. package/dist/commands/improve/consolidate.js +28 -25
  53. package/dist/commands/improve/distill/promote-memory.js +5 -12
  54. package/dist/commands/improve/distill/quality-gate.js +5 -7
  55. package/dist/commands/improve/distill.js +16 -5
  56. package/dist/commands/improve/eligibility.js +22 -12
  57. package/dist/commands/improve/extract-cli.js +47 -19
  58. package/dist/commands/improve/extract.js +110 -62
  59. package/dist/commands/improve/improve-cli.js +38 -16
  60. package/dist/commands/improve/improve-result-file.js +30 -24
  61. package/dist/commands/improve/improve-strategies.js +137 -0
  62. package/dist/commands/improve/improve.js +60 -30
  63. package/dist/commands/improve/locks.js +66 -45
  64. package/dist/commands/improve/loop-stages.js +75 -33
  65. package/dist/commands/improve/memory/memory-belief.js +79 -7
  66. package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
  67. package/dist/commands/improve/preparation.js +71 -73
  68. package/dist/commands/improve/procedural.js +3 -2
  69. package/dist/commands/improve/recombine.js +2 -1
  70. package/dist/commands/improve/reflect.js +119 -214
  71. package/dist/commands/improve/shared.js +11 -5
  72. package/dist/commands/lint/base-linter.js +152 -42
  73. package/dist/commands/mv-cli.js +809 -0
  74. package/dist/commands/proposal/proposal-cli.js +18 -8
  75. package/dist/commands/proposal/propose.js +64 -69
  76. package/dist/commands/read/knowledge.js +436 -4
  77. package/dist/commands/read/remember-cli.js +39 -2
  78. package/dist/commands/read/search-cli.js +6 -1
  79. package/dist/commands/registry-cli.js +29 -14
  80. package/dist/commands/remember.js +2 -0
  81. package/dist/commands/sources/init.js +13 -14
  82. package/dist/commands/sources/migration-help.js +7 -4
  83. package/dist/commands/sources/schema-repair.js +2 -4
  84. package/dist/commands/sources/source-add.js +62 -73
  85. package/dist/commands/sources/source-manage.js +50 -46
  86. package/dist/commands/sources/stash-cli.js +41 -4
  87. package/dist/commands/tasks/default-tasks.js +12 -12
  88. package/dist/commands/tasks/tasks-cli.js +7 -3
  89. package/dist/commands/tasks/tasks.js +113 -18
  90. package/dist/commands/wiki-cli.js +9 -10
  91. package/dist/commands/workflow-cli.js +276 -12
  92. package/dist/core/asset/asset-spec.js +58 -1
  93. package/dist/core/asset/frontmatter.js +12 -2
  94. package/dist/core/common.js +5 -3
  95. package/dist/core/config/config-io.js +28 -17
  96. package/dist/core/config/config-schema.js +379 -66
  97. package/dist/core/config/config-types.js +3 -3
  98. package/dist/core/config/config-version.js +29 -0
  99. package/dist/core/config/config-walker.js +98 -27
  100. package/dist/core/config/config.js +132 -266
  101. package/dist/core/config/deep-merge.js +41 -0
  102. package/dist/core/config/engine-semantics.js +32 -0
  103. package/dist/core/errors.js +2 -2
  104. package/dist/core/extra-params.js +61 -0
  105. package/dist/core/file-lock.js +201 -56
  106. package/dist/core/improve-result.js +178 -0
  107. package/dist/core/json-schema.js +142 -0
  108. package/dist/core/maintenance-barrier.js +119 -0
  109. package/dist/core/migration-backup.js +416 -0
  110. package/dist/core/paths.js +3 -0
  111. package/dist/core/redaction.js +358 -0
  112. package/dist/core/state/migrations.js +17 -2
  113. package/dist/core/state-db.js +44 -1
  114. package/dist/indexer/db/db.js +118 -2
  115. package/dist/indexer/graph/graph-extraction.js +28 -16
  116. package/dist/indexer/index-writer-lock.js +31 -24
  117. package/dist/indexer/index-written-assets.js +15 -6
  118. package/dist/indexer/indexer.js +47 -2
  119. package/dist/indexer/passes/memory-inference.js +10 -6
  120. package/dist/indexer/passes/metadata.js +250 -0
  121. package/dist/indexer/search/db-search.js +111 -44
  122. package/dist/indexer/search/fts-query.js +41 -0
  123. package/dist/indexer/search/ranking-contributors.js +48 -0
  124. package/dist/indexer/search/ranking.js +36 -23
  125. package/dist/indexer/search/search-fields.js +11 -1
  126. package/dist/indexer/walk/matchers.js +39 -0
  127. package/dist/integrations/agent/builder-shared.js +7 -0
  128. package/dist/integrations/agent/builders.js +5 -50
  129. package/dist/integrations/agent/config.js +3 -143
  130. package/dist/integrations/agent/detect.js +17 -2
  131. package/dist/integrations/agent/engine-resolution.js +202 -0
  132. package/dist/integrations/agent/index.js +1 -2
  133. package/dist/integrations/agent/model-aliases.js +16 -2
  134. package/dist/integrations/agent/profiles.js +36 -62
  135. package/dist/integrations/agent/runner-dispatch.js +91 -4
  136. package/dist/integrations/agent/runner.js +76 -207
  137. package/dist/integrations/agent/spawn.js +141 -20
  138. package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
  139. package/dist/integrations/harnesses/aider/index.js +57 -0
  140. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  141. package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
  142. package/dist/integrations/harnesses/amazonq/index.js +58 -0
  143. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  144. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  145. package/dist/integrations/harnesses/claude/index.js +25 -25
  146. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  147. package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
  148. package/dist/integrations/harnesses/codex/index.js +62 -0
  149. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  150. package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
  151. package/dist/integrations/harnesses/copilot/index.js +59 -0
  152. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  153. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  154. package/dist/integrations/harnesses/gemini/index.js +59 -0
  155. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  156. package/dist/integrations/harnesses/index.js +27 -28
  157. package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
  158. package/dist/integrations/harnesses/opencode/index.js +15 -22
  159. package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
  160. package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
  161. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  162. package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
  163. package/dist/integrations/harnesses/openhands/index.js +57 -0
  164. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  165. package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
  166. package/dist/integrations/harnesses/pi/index.js +57 -0
  167. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  168. package/dist/integrations/harnesses/types.js +8 -32
  169. package/dist/integrations/lockfile.js +32 -21
  170. package/dist/integrations/session-logs/index.js +24 -11
  171. package/dist/llm/client.js +48 -14
  172. package/dist/llm/feature-gate.js +15 -47
  173. package/dist/llm/graph-extract.js +1 -1
  174. package/dist/llm/index-passes.js +8 -42
  175. package/dist/llm/memory-infer-impl.js +1 -1
  176. package/dist/llm/usage-persist.js +4 -0
  177. package/dist/llm/usage-telemetry.js +35 -5
  178. package/dist/output/renderers.js +3 -2
  179. package/dist/output/shapes/helpers.js +2 -1
  180. package/dist/output/shapes/passthrough.js +6 -0
  181. package/dist/output/text/helpers.js +215 -2
  182. package/dist/output/text/workflow.js +3 -1
  183. package/dist/schemas/akm-config.json +16638 -0
  184. package/dist/schemas/akm-task.json +87 -0
  185. package/dist/schemas/akm-workflow.json +372 -0
  186. package/dist/scripts/migrate-storage.js +10944 -8801
  187. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
  188. package/dist/setup/detected-engines.js +142 -0
  189. package/dist/setup/engine-config.js +89 -0
  190. package/dist/setup/setup.js +236 -132
  191. package/dist/setup/steps/connection.js +61 -32
  192. package/dist/setup/steps/platforms.js +4 -4
  193. package/dist/setup/steps.js +3 -2
  194. package/dist/storage/database.js +13 -1
  195. package/dist/storage/engines/sqlite-migrations.js +1 -0
  196. package/dist/storage/repositories/improve-runs-repository.js +5 -5
  197. package/dist/storage/repositories/task-history-repository.js +78 -0
  198. package/dist/storage/repositories/workflow-runs-repository.js +190 -1
  199. package/dist/tasks/parser.js +138 -52
  200. package/dist/tasks/runner.js +71 -75
  201. package/dist/tasks/schema.js +1 -1
  202. package/dist/tasks/validator.js +11 -6
  203. package/dist/text-import-hook.mjs +1 -1
  204. package/dist/wiki/wiki.js +9 -8
  205. package/dist/workflows/authoring/authoring.js +123 -10
  206. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  207. package/dist/workflows/cli.js +4 -0
  208. package/dist/workflows/concurrency-policy.js +15 -0
  209. package/dist/workflows/db.js +200 -13
  210. package/dist/workflows/exec/brief.js +478 -0
  211. package/dist/workflows/exec/frozen-judge.js +47 -0
  212. package/dist/workflows/exec/native-executor.js +1034 -0
  213. package/dist/workflows/exec/param-secrets.js +115 -0
  214. package/dist/workflows/exec/report.js +1355 -0
  215. package/dist/workflows/exec/run-workflow.js +609 -0
  216. package/dist/workflows/exec/scheduler.js +71 -0
  217. package/dist/workflows/exec/step-work.js +1212 -0
  218. package/dist/workflows/exec/unit-writer.js +23 -0
  219. package/dist/workflows/exec/watch.js +116 -0
  220. package/dist/workflows/exec/worktree.js +171 -0
  221. package/dist/workflows/ir/compile.js +375 -0
  222. package/dist/workflows/ir/freeze.js +243 -0
  223. package/dist/workflows/ir/params.js +54 -0
  224. package/dist/workflows/ir/plan-hash.js +68 -0
  225. package/dist/workflows/ir/schema.js +545 -0
  226. package/dist/workflows/parser.js +10 -1
  227. package/dist/workflows/program/expressions.js +369 -0
  228. package/dist/workflows/program/parser.js +869 -0
  229. package/dist/workflows/program/project.js +104 -0
  230. package/dist/workflows/program/schema.js +54 -0
  231. package/dist/workflows/renderer.js +82 -5
  232. package/dist/workflows/resource-limits.js +20 -0
  233. package/dist/workflows/runtime/agent-identity.js +59 -14
  234. package/dist/workflows/runtime/plan-classifier.js +187 -0
  235. package/dist/workflows/runtime/runs.js +246 -69
  236. package/dist/workflows/runtime/unit-checkin.js +45 -0
  237. package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
  238. package/dist/workflows/validate-summary.js +24 -3
  239. package/dist/workflows/validator.js +26 -1
  240. package/docs/data-and-telemetry.md +4 -3
  241. package/docs/migration/release-notes/0.6.0.md +1 -1
  242. package/docs/migration/release-notes/0.7.0.md +5 -4
  243. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  244. package/docs/migration/v0.8-to-v0.9.md +401 -0
  245. package/package.json +4 -2
  246. package/schemas/akm-config.json +16638 -0
  247. package/schemas/akm-task.json +87 -0
  248. package/schemas/akm-workflow.json +372 -0
  249. package/dist/commands/improve/improve-profiles.js +0 -168
  250. package/dist/core/config/config-migration.js +0 -602
  251. package/dist/core/deep-merge.js +0 -38
  252. package/dist/llm/call-ai.js +0 -62
  253. package/dist/setup/legacy-config.js +0 -106
  254. package/docs/README.md +0 -104
  255. /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
  256. /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
  257. /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
  258. /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
  259. /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
  260. /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
  261. /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
  262. /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
@@ -17,7 +17,6 @@ import { loadConfig } from "../../core/config/config.js";
17
17
  import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
18
18
  import { getTaskHistoryDir, getTaskLogDir } from "../../core/paths.js";
19
19
  import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
20
- import { listAgentProfileNames } from "../../integrations/agent/index.js";
21
20
  import { resolveAssetPath } from "../../sources/resolve.js";
22
21
  import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
23
22
  import { parseTaskDocument } from "../../tasks/parser.js";
@@ -25,7 +24,7 @@ import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
25
24
  import { exitCodeForStatus, readTaskHistory, runTask } from "../../tasks/runner.js";
26
25
  import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT, translateToCron } from "../../tasks/schedule.js";
27
26
  import { validateTaskDocument } from "../../tasks/validator.js";
28
- import { resolveImproveProfile } from "../improve/improve-profiles.js";
27
+ import { resolveImproveStrategy } from "../improve/improve-strategies.js";
29
28
  export async function akmTasksAdd(input) {
30
29
  const id = normaliseTaskId(input.id);
31
30
  const hasCommand = input.command !== undefined &&
@@ -36,6 +35,12 @@ export async function akmTasksAdd(input) {
36
35
  if (targetCount !== 1) {
37
36
  throw new UsageError("Pass exactly one of --workflow <ref>, --prompt <asset-ref|./file.md|text>, or --command <shell-command>.", "INVALID_FLAG_VALUE");
38
37
  }
38
+ if (input.workflow && (input.engine !== undefined || input.model !== undefined || input.timeoutMs !== undefined)) {
39
+ throw new UsageError("Workflow tasks accept only --params; engine, model, and timeout are prompt-task fields.", "INVALID_FLAG_VALUE");
40
+ }
41
+ if (hasCommand && (input.engine !== undefined || input.model !== undefined)) {
42
+ throw new UsageError("Command tasks accept --timeout-ms but not --engine or --model.", "INVALID_FLAG_VALUE");
43
+ }
39
44
  // Validate the schedule for the active backend before writing anything.
40
45
  const backend = backendNameForPlatform();
41
46
  parseSchedule(input.schedule, backend);
@@ -56,7 +61,9 @@ export async function akmTasksAdd(input) {
56
61
  workflow: input.workflow,
57
62
  prompt: input.prompt,
58
63
  command: input.command,
59
- profile: input.profile,
64
+ engine: input.engine,
65
+ model: input.model,
66
+ timeoutMs: input.timeoutMs,
60
67
  params: input.params,
61
68
  name: input.name,
62
69
  description: input.description,
@@ -137,11 +144,12 @@ export async function akmTasksList() {
137
144
  const stashDir = resolveStashDir();
138
145
  const typeRoot = path.join(stashDir, "tasks");
139
146
  if (!fs.existsSync(typeRoot))
140
- return { tasks: [] };
147
+ return { tasks: [], stale: [] };
141
148
  const entries = fs.readdirSync(typeRoot);
142
149
  warnLegacyMdTaskFiles(typeRoot);
143
150
  const files = entries.filter((f) => f.endsWith(".yml"));
144
151
  const tasks = [];
152
+ const stale = [];
145
153
  for (const file of files) {
146
154
  const id = file.slice(0, -4);
147
155
  const filePath = path.join(typeRoot, file);
@@ -149,7 +157,9 @@ export async function akmTasksList() {
149
157
  try {
150
158
  task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
151
159
  }
152
- catch {
160
+ catch (err) {
161
+ if (isStaleTaskError(err))
162
+ stale.push(id);
153
163
  continue; // skip malformed files; `akm tasks show <id>` will surface the error
154
164
  }
155
165
  tasks.push({
@@ -165,7 +175,9 @@ export async function akmTasksList() {
165
175
  tags: task.tags,
166
176
  });
167
177
  }
168
- return { tasks };
178
+ if (stale.length > 0)
179
+ warnStaleTaskFiles(stale);
180
+ return { tasks, stale };
169
181
  }
170
182
  export async function akmTasksShow(id) {
171
183
  const normalised = normaliseTaskId(id);
@@ -234,6 +246,9 @@ export async function akmTasksSetEnabled(id, enabled) {
234
246
  warnLegacyMdTaskFiles(typeRoot);
235
247
  const filePath = await resolveAssetPath(stashDir, "task", normalised);
236
248
  const yaml = fs.readFileSync(filePath, "utf8");
249
+ // Parse before writing so stale v1 tasks are diagnosed without changing the
250
+ // source file or its installed scheduler entry.
251
+ parseTaskDocument({ yaml, filePath, id: normalised });
237
252
  const updated = setEnabledInYaml(yaml, enabled);
238
253
  const ref = taskAssetRef(normalised);
239
254
  await writeAssetToSource(target.source, target.config, ref, updated);
@@ -374,16 +389,15 @@ export async function akmTasksDoctor() {
374
389
  }
375
390
  const backend = backendNameForPlatform();
376
391
  const config = loadConfig();
377
- // v2: prefer profiles.agent / defaults.agent; fall back to legacy agent.default
378
- const defaultProfile = config.defaults?.agent;
379
- const profiles = config.profiles?.agent ? Object.keys(config.profiles.agent) : listAgentProfileNames(config);
392
+ const defaultEngine = config.defaults?.engine;
393
+ const engines = Object.keys(config.engines ?? {});
380
394
  // §6.1: surface the effective triage settings for the default improve
381
- // profile. The struct is a fixed shape, so this is a deliberate addition.
382
- const improveProfileName = typeof config.defaults?.improve === "string" ? config.defaults.improve : "default";
383
- const triage = resolveImproveProfile(config.defaults?.improve, config).processes?.triage;
395
+ // strategy. The struct is a fixed shape, so this is a deliberate addition.
396
+ const improveStrategyName = typeof config.defaults?.improveStrategy === "string" ? config.defaults.improveStrategy : "default";
397
+ const triage = resolveImproveStrategy(config.defaults?.improveStrategy, config).config.processes?.triage;
384
398
  const improveTriage = triage
385
399
  ? {
386
- defaultProfile: improveProfileName,
400
+ defaultStrategy: improveStrategyName,
387
401
  enabled: triage.enabled === true,
388
402
  applyMode: triage.applyMode ?? "queue",
389
403
  policy: triage.policy ?? "personal-stash",
@@ -394,7 +408,9 @@ export async function akmTasksDoctor() {
394
408
  akm: invocation,
395
409
  logDir: getTaskLogDir(),
396
410
  historyDir: getTaskHistoryDir(),
397
- agent: { defaultProfile, available: profiles },
411
+ engine: { defaultEngine, available: engines },
412
+ stale: collectStaleTaskIds(),
413
+ staleGeneratedCommands: collectStaleGeneratedCommands(),
398
414
  scheduleSubset: SCHEDULE_SUPPORTED_SUBSET_HINT,
399
415
  warnings,
400
416
  ...(improveTriage ? { improveTriage } : {}),
@@ -421,7 +437,7 @@ function resolveTaskWriteTarget() {
421
437
  return resolveWriteTarget(loadConfig());
422
438
  }
423
439
  function renderTaskYaml(input) {
424
- const obj = { schedule: input.schedule };
440
+ const obj = { version: 2, schedule: input.schedule, enabled: input.enabled };
425
441
  if (input.workflow) {
426
442
  obj.workflow = input.workflow;
427
443
  if (input.params) {
@@ -430,16 +446,21 @@ function renderTaskYaml(input) {
430
446
  }
431
447
  else if (input.prompt) {
432
448
  obj.prompt = input.prompt;
433
- if (input.profile)
434
- obj.profile = input.profile;
449
+ if (input.engine)
450
+ obj.engine = input.engine;
451
+ if (input.model)
452
+ obj.model = input.model;
453
+ if (input.timeoutMs !== undefined)
454
+ obj.timeoutMs = input.timeoutMs;
435
455
  }
436
456
  else if (input.command !== undefined) {
437
457
  // Emit a string when given a string, an array when given an array. The
438
458
  // parser accepts both forms; preserving the caller's shape keeps the YAML
439
459
  // ergonomic for humans editing the file later.
440
460
  obj.command = input.command;
461
+ if (input.timeoutMs !== undefined)
462
+ obj.timeoutMs = input.timeoutMs;
441
463
  }
442
- obj.enabled = input.enabled;
443
464
  if (input.name)
444
465
  obj.name = input.name;
445
466
  if (input.description)
@@ -450,6 +471,80 @@ function renderTaskYaml(input) {
450
471
  obj.tags = input.tags;
451
472
  return yamlStringify(obj);
452
473
  }
474
+ function isStaleTaskError(err) {
475
+ return err instanceof UsageError && err.code === "TASK_SCHEMA_VERSION_UNSUPPORTED";
476
+ }
477
+ function warnStaleTaskFiles(ids) {
478
+ process.stderr.write(`WARNING: ${ids.length} task file(s) use retired task schema v1 and were not loaded.\n` +
479
+ ` Rewrite them with version: 2 and replace profile with engine. See docs/migration/v0.8-to-v0.9.md#task-assets.\n` +
480
+ ` Affected: ${ids.map((id) => `tasks/${id}.yml`).join(", ")}\n`);
481
+ }
482
+ function collectStaleTaskIds() {
483
+ const typeRoot = path.join(resolveStashDir(), "tasks");
484
+ if (!fs.existsSync(typeRoot))
485
+ return [];
486
+ const stale = [];
487
+ for (const file of fs.readdirSync(typeRoot)) {
488
+ if (!file.endsWith(".yml"))
489
+ continue;
490
+ const id = file.slice(0, -4);
491
+ try {
492
+ parseTaskDocument({
493
+ yaml: fs.readFileSync(path.join(typeRoot, file), "utf8"),
494
+ filePath: path.join(typeRoot, file),
495
+ id,
496
+ });
497
+ }
498
+ catch (err) {
499
+ if (isStaleTaskError(err))
500
+ stale.push(id);
501
+ }
502
+ }
503
+ return stale;
504
+ }
505
+ const STALE_GENERATED_COMMANDS = {
506
+ "akm-improve-frequent": {
507
+ command: "akm improve --profile frequent --auto-accept safe",
508
+ replacement: "akm improve --strategy frequent --auto-accept safe",
509
+ },
510
+ "akm-improve-consolidate": {
511
+ command: "akm improve --profile consolidate --auto-accept safe",
512
+ replacement: "akm improve --strategy consolidate --auto-accept safe",
513
+ },
514
+ "akm-improve-nightly": {
515
+ command: "akm improve --profile thorough --auto-accept safe",
516
+ replacement: "akm improve --strategy thorough --auto-accept safe",
517
+ },
518
+ "akm-improve-catchup": {
519
+ command: "akm improve --profile catchup --auto-accept safe",
520
+ replacement: "akm improve --strategy catchup --auto-accept safe",
521
+ },
522
+ "akm-graph-refresh-weekly": {
523
+ command: "akm improve --profile graph-refresh --auto-accept safe",
524
+ replacement: "akm improve --strategy graph-refresh --auto-accept safe",
525
+ },
526
+ };
527
+ function collectStaleGeneratedCommands() {
528
+ const typeRoot = path.join(resolveStashDir(), "tasks");
529
+ if (!fs.existsSync(typeRoot))
530
+ return [];
531
+ const stale = [];
532
+ for (const [id, expected] of Object.entries(STALE_GENERATED_COMMANDS)) {
533
+ const filePath = path.join(typeRoot, `${id}.yml`);
534
+ if (!fs.existsSync(filePath))
535
+ continue;
536
+ try {
537
+ const task = parseTaskDocument({ yaml: fs.readFileSync(filePath, "utf8"), filePath, id });
538
+ if (task.target.kind === "command" && task.target.cmd.join(" ") === expected.command) {
539
+ stale.push({ id, replacement: expected.replacement });
540
+ }
541
+ }
542
+ catch {
543
+ // Stale-schema reporting owns files that cannot be parsed.
544
+ }
545
+ }
546
+ return stale;
547
+ }
453
548
  function parseJsonObjectArg(raw) {
454
549
  let parsed;
455
550
  try {
@@ -220,13 +220,13 @@ const wikiLintCommand = defineCommand({
220
220
  const wikiIngestCommand = defineJsonCommand({
221
221
  meta: {
222
222
  name: "ingest",
223
- description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --profile or config.defaults.agent.",
223
+ description: "Dispatch an agent to execute the ingest workflow for this wiki. Uses --engine or config.defaults.engine.",
224
224
  },
225
225
  args: {
226
226
  name: { type: "positional", description: "Wiki name", required: true },
227
- profile: {
227
+ engine: {
228
228
  type: "string",
229
- description: "Agent profile to use (default: config.defaults.agent).",
229
+ description: "Agent engine to use (default: config.defaults.engine).",
230
230
  },
231
231
  model: {
232
232
  type: "string",
@@ -239,17 +239,15 @@ const wikiIngestCommand = defineJsonCommand({
239
239
  const stashDir = resolveStashDir();
240
240
  const built = buildIngestWorkflow(stashDir, args.name);
241
241
  const config = loadConfig();
242
- const profileName = getStringArg(args, "profile") ?? config.defaults?.agent;
243
- if (!profileName) {
244
- throw new UsageError("akm wiki ingest requires an agent profile. Pass --profile <name> or set defaults.agent in config.", "MISSING_REQUIRED_ARGUMENT", "Available profiles are listed under profiles.agent in your config. Run `akm config get profiles.agent` to inspect.");
242
+ const engine = getStringArg(args, "engine") ?? config.defaults?.engine;
243
+ if (!engine) {
244
+ throw new UsageError("akm wiki ingest requires an agent engine. Pass --engine <name> or set defaults.engine in config.", "MISSING_REQUIRED_ARGUMENT", "Available engines are listed under engines in your config. Run `akm config get engines` to inspect.");
245
245
  }
246
246
  const timeoutMs = parsePositiveIntFlag(args["timeout-ms"], "--timeout-ms");
247
247
  const model = getStringArg(args, "model");
248
- const { getDefaultLlmConfig } = await import("../core/config/config.js");
249
248
  const dispatchResult = await akmAgentDispatch({
250
- profileName,
249
+ engine,
251
250
  agentConfig: config,
252
- llmConfig: getDefaultLlmConfig(config),
253
251
  prompt: built.workflow,
254
252
  dispatch: {
255
253
  prompt: built.workflow,
@@ -258,11 +256,12 @@ const wikiIngestCommand = defineJsonCommand({
258
256
  ...(timeoutMs !== undefined && Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
259
257
  });
260
258
  output("wiki-ingest", {
259
+ schemaVersion: 2,
261
260
  wiki: built.wiki,
262
261
  path: built.path,
263
262
  schemaPath: built.schemaPath,
264
263
  dispatched: true,
265
- profile: profileName,
264
+ engine,
266
265
  agentResult: dispatchResult,
267
266
  });
268
267
  },
@@ -12,6 +12,7 @@
12
12
  * `looksLikeWorkflowRunId` and `resolveWorkflowFilePath` move with the family.
13
13
  */
14
14
  import { defineCommand } from "citty";
15
+ import { getStringArg } from "../cli/parse-args.js";
15
16
  import { defineJsonCommand, output, runWithJsonErrors } from "../cli/shared.js";
16
17
  import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../core/asset/asset-create.js";
17
18
  import { parseAssetRef } from "../core/asset/asset-ref.js";
@@ -22,8 +23,9 @@ import { resolveSourceEntries } from "../indexer/search/search-source.js";
22
23
  import { hasBooleanFlag } from "../output/context.js";
23
24
  import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
24
25
  import { resolveAssetPath } from "../sources/resolve.js";
25
- import { createWorkflowAsset, formatWorkflowErrors, getWorkflowTemplate, validateWorkflowSource, } from "../workflows/authoring/authoring.js";
26
+ import { createWorkflowAsset, formatWorkflowErrors, getWorkflowProgramTemplate, getWorkflowTemplate, validateWorkflowProgramSource, validateWorkflowSource, } from "../workflows/authoring/authoring.js";
26
27
  import { hasWorkflowSubcommand, parseWorkflowJsonObject, parseWorkflowStepState, WORKFLOW_STEP_STATES, } from "../workflows/cli.js";
28
+ import { isWorkflowProgramPath } from "../workflows/program/project.js";
27
29
  import { abandonWorkflowRun, completeWorkflowStep, getNextWorkflowStep, getWorkflowStatus, listWorkflowRuns, resumeWorkflowRun, startWorkflowRun, } from "../workflows/runtime/runs.js";
28
30
  const workflowStartCommand = defineJsonCommand({
29
31
  meta: {
@@ -140,9 +142,16 @@ const workflowStatusCommand = defineJsonCommand({
140
142
  },
141
143
  args: {
142
144
  target: { type: "positional", description: "Workflow run id or workflow ref (workflow:<name>)", required: true },
145
+ units: {
146
+ type: "boolean",
147
+ description: "Also list per-unit rows from the run journal (unit id, status, failure_reason, and any result/error " +
148
+ "diagnostic text). Diagnostics only — step evidence stays deterministic and is unaffected (#22).",
149
+ default: false,
150
+ },
143
151
  },
144
152
  async run({ args }) {
145
153
  const target = args.target;
154
+ const includeUnits = args.units === true;
146
155
  // Check if target looks like a workflow ref
147
156
  const parsed = (() => {
148
157
  try {
@@ -161,11 +170,11 @@ const workflowStatusCommand = defineJsonCommand({
161
170
  const mostRecent = runs[0];
162
171
  if (!mostRecent)
163
172
  throw new NotFoundError(`No workflow runs found for ${ref}`, "WORKFLOW_NOT_FOUND");
164
- const result = await getWorkflowStatus(mostRecent.id);
173
+ const result = await getWorkflowStatus(mostRecent.id, { includeUnits });
165
174
  output("workflow-status", result);
166
175
  }
167
176
  else {
168
- const result = await getWorkflowStatus(target);
177
+ const result = await getWorkflowStatus(target, { includeUnits });
169
178
  output("workflow-status", result);
170
179
  }
171
180
  },
@@ -187,19 +196,22 @@ const workflowListCommand = defineJsonCommand({
187
196
  const workflowCreateCommand = defineJsonCommand({
188
197
  meta: {
189
198
  name: "create",
190
- description: "Create a workflow markdown document in the working stash",
199
+ description: "Create a workflow in the working stash (markdown document by default; a .yaml/.yml name writes a YAML program)",
191
200
  },
192
201
  args: {
193
202
  name: {
194
203
  type: "positional",
195
- description: "Workflow name (flat, no '/'; use --path for a subdirectory)",
204
+ description: "Workflow name (flat, no '/'; use --path for a subdirectory). A .yaml/.yml suffix creates a YAML program.",
196
205
  required: true,
197
206
  },
198
207
  path: {
199
208
  type: "string",
200
209
  description: "Relative subdirectory under workflows/ to place the workflow in (e.g. 'release'). The filename comes from the name.",
201
210
  },
202
- from: { type: "string", description: "Import and validate markdown from an existing file" },
211
+ from: {
212
+ type: "string",
213
+ description: "Import and validate content from an existing file (parsed per the destination extension)",
214
+ },
203
215
  force: {
204
216
  type: "boolean",
205
217
  description: "Overwrite an existing workflow (requires --from or --reset)",
@@ -237,26 +249,54 @@ const workflowCreateCommand = defineJsonCommand({
237
249
  const workflowTemplateCommand = defineCommand({
238
250
  meta: {
239
251
  name: "template",
240
- description: "Print a valid workflow markdown template",
252
+ description: "Print a valid workflow template (markdown by default, --yaml for a YAML program)",
253
+ },
254
+ args: {
255
+ yaml: {
256
+ type: "boolean",
257
+ description: "Print a minimal valid YAML workflow program instead of the markdown template",
258
+ default: false,
259
+ },
241
260
  },
242
- run() {
243
- process.stdout.write(getWorkflowTemplate());
261
+ run({ args }) {
262
+ process.stdout.write(args.yaml ? getWorkflowProgramTemplate() : getWorkflowTemplate());
244
263
  },
245
264
  });
246
265
  const workflowValidateCommand = defineJsonCommand({
247
266
  meta: {
248
267
  name: "validate",
249
- description: "Validate a workflow markdown file or ref and print any errors",
268
+ description: "Validate a workflow file or ref (markdown document or YAML program) and print any errors",
250
269
  },
251
270
  args: {
252
271
  target: {
253
272
  type: "positional",
254
- description: "Workflow ref (workflow:<name>) or filesystem path to a workflow .md",
273
+ description: "Workflow ref (workflow:<name>) or filesystem path to a workflow .md/.yaml",
255
274
  required: true,
256
275
  },
257
276
  },
258
277
  async run({ args }) {
259
278
  const filePath = await resolveWorkflowFilePath(args.target);
279
+ // YAML programs (redesign addendum, R1) validate through the program
280
+ // parser AND compiler so expression/reference errors surface at lint
281
+ // time; both error lists carry line numbers. Markdown is unchanged.
282
+ if (isWorkflowProgramPath(filePath)) {
283
+ const { result } = validateWorkflowProgramSource(filePath);
284
+ if (!result.ok) {
285
+ throw new UsageError(formatWorkflowErrors(filePath, result.errors));
286
+ }
287
+ // Non-fatal WARNINGS ride the envelope additively — `ok` stays true. The
288
+ // text formatter renders them clearly marked for humans; the JSON key is
289
+ // the machine channel. Empty array when the program is fully typed/declared.
290
+ output("workflow-validate", {
291
+ ok: true,
292
+ path: filePath,
293
+ format: "program",
294
+ title: result.program.name,
295
+ stepCount: result.program.steps.length,
296
+ warnings: result.warnings.map((w) => ({ line: w.line, message: w.message })),
297
+ });
298
+ return;
299
+ }
260
300
  const { parse } = validateWorkflowSource(filePath);
261
301
  if (parse.ok) {
262
302
  output("workflow-validate", {
@@ -271,7 +311,15 @@ const workflowValidateCommand = defineJsonCommand({
271
311
  },
272
312
  });
273
313
  async function resolveWorkflowFilePath(target) {
274
- if (!target.startsWith("workflow:"))
314
+ // A bare (`workflow:<name>`) OR origin-qualified (`<origin>//workflow:<name>`)
315
+ // ref resolves through the source search, exactly like `workflow start` /
316
+ // `status` / `next`. Anything else is treated as a filesystem path. Detecting
317
+ // the origin-qualified form here (not just the bare prefix) keeps `validate`'s
318
+ // ref contract in lockstep with the rest of the workflow command family — an
319
+ // `extra//workflow:foo` ref validates the file that `extra//workflow:foo`
320
+ // starts, rather than being mistaken for a relative path that does not exist.
321
+ const looksLikeWorkflowRef = target.startsWith("workflow:") || target.includes("//workflow:");
322
+ if (!looksLikeWorkflowRef)
275
323
  return target;
276
324
  const parsed = parseAssetRef(target);
277
325
  if (parsed.type !== "workflow") {
@@ -290,6 +338,218 @@ async function resolveWorkflowFilePath(target) {
290
338
  }
291
339
  throw new UsageError(`Workflow not found for ref: workflow:${parsed.name}`);
292
340
  }
341
+ const workflowRunCommand = defineJsonCommand({
342
+ meta: {
343
+ name: "run",
344
+ description: "EXPERIMENTAL: execute a workflow's steps with the native engine — akm dispatches each step's units " +
345
+ "(fan-out, schema output) to the configured runner and advances the run through the normal completion gates",
346
+ },
347
+ args: {
348
+ target: { type: "positional", description: "Workflow run id or workflow ref (auto-starts a run)", required: true },
349
+ params: { type: "string", description: "Workflow parameters as a JSON object (only for auto-started runs)" },
350
+ "max-steps": { type: "string", description: "Stop after executing this many steps" },
351
+ "require-gates": {
352
+ type: "boolean",
353
+ description: "Treat every criteria-bearing completion gate as required: if no LLM judge is available, BLOCK the step " +
354
+ "(for a human to resolve via `akm workflow resume`) instead of failing open. A per-step `gate.required: true` " +
355
+ "in the workflow does the same on every surface; this is the run-wide override (#18).",
356
+ default: false,
357
+ },
358
+ },
359
+ async run({ args }) {
360
+ const { runWorkflowSteps } = await import("../workflows/exec/run-workflow.js");
361
+ const rawMaxSteps = getStringArg(args, "max-steps");
362
+ let maxSteps;
363
+ if (rawMaxSteps !== undefined) {
364
+ maxSteps = Number.parseInt(rawMaxSteps, 10);
365
+ if (!/^\d+$/.test(rawMaxSteps) || maxSteps <= 0) {
366
+ throw new UsageError(`--max-steps must be a positive integer, got "${rawMaxSteps}".`, "INVALID_FLAG_VALUE");
367
+ }
368
+ }
369
+ const result = await runWorkflowSteps({
370
+ target: args.target,
371
+ ...(args.params ? { params: parseWorkflowJsonObject(args.params, "--params") } : {}),
372
+ ...(maxSteps !== undefined ? { maxSteps } : {}),
373
+ ...(args["require-gates"] === true ? { requireGates: true } : {}),
374
+ });
375
+ output("workflow-run", result);
376
+ },
377
+ });
378
+ const workflowBriefCommand = defineJsonCommand({
379
+ meta: {
380
+ name: "brief",
381
+ description: "EXPERIMENTAL: describe a run's active step as an executable work-list for ANY agent session (the " +
382
+ "harness-neutral driver protocol) — read-only, takes no engine lease, mutates nothing; prints per-unit " +
383
+ "instructions, output schema, env binding names, and the exact `akm workflow report` command lines",
384
+ },
385
+ args: {
386
+ target: {
387
+ type: "positional",
388
+ description: "Workflow run id (or a workflow ref with an active run)",
389
+ required: true,
390
+ },
391
+ },
392
+ async run({ args }) {
393
+ const { buildWorkflowBrief } = await import("../workflows/exec/brief.js");
394
+ const result = await buildWorkflowBrief(args.target);
395
+ output("workflow-brief", result);
396
+ },
397
+ });
398
+ const WORKFLOW_REPORT_STATES = ["completed", "failed", "running"];
399
+ const workflowReportCommand = defineJsonCommand({
400
+ meta: {
401
+ name: "report",
402
+ description: "EXPERIMENTAL: report a unit's result back into a run (the mutating half of the harness-neutral driver " +
403
+ "protocol) — ingested through the SAME shared step semantics the engine uses. --status running claims/" +
404
+ "heartbeats a unit; completed/failed records it and, when the step's work-list is fully terminal, runs the " +
405
+ "engine's completion path (reducer, artifact + schema validation, gate). --settle (no --unit) advances a run " +
406
+ "parked on a route-only/empty step. Refused while a live engine lease exists",
407
+ },
408
+ args: {
409
+ target: {
410
+ type: "positional",
411
+ description: "Workflow run id (or a workflow ref with an active run)",
412
+ required: true,
413
+ },
414
+ unit: {
415
+ type: "string",
416
+ description: "Content-derived unit id from `akm workflow brief` (copy it verbatim). Omit with --settle.",
417
+ },
418
+ settle: {
419
+ type: "boolean",
420
+ description: "Advance/finalize a run whose active step has NO unit left to report: a non-dispatching step (params-based route, empty fan-out, all-unresolvable) OR a fully-terminal step still needing finalization (every unit ran but the gate never judged — e.g. after resuming a required-gate block). Runs the deterministic completion path. Mutually exclusive with --unit; refused when the step still has genuinely pending units",
421
+ default: false,
422
+ },
423
+ "expect-step": {
424
+ type: "string",
425
+ description: "Guard: the step id you briefed against. Refuses the report if the run's active step has since moved (from the `brief` report/settle command line)",
426
+ },
427
+ status: { type: "string", description: `Unit status: ${WORKFLOW_REPORT_STATES.join(", ")}` },
428
+ result: { type: "string", description: "Result payload (JSON for a schema unit, else text). completed only." },
429
+ "result-file": { type: "string", description: "Read the result payload from this file instead of --result/stdin" },
430
+ tokens: { type: "string", description: "Tokens spent on this unit (counts against a declared budget)" },
431
+ "session-id": { type: "string", description: "Harness-native session id revealed while executing the unit" },
432
+ "failure-reason": { type: "string", description: "Structured failure vocabulary for a --status failed report" },
433
+ note: { type: "string", description: "Short progress note for a --status running heartbeat (not persisted)" },
434
+ rerun: {
435
+ type: "boolean",
436
+ description: "Re-run an already-FAILED unit: record a NEW attempt (re-applies budget) instead of refusing a differing re-report",
437
+ default: false,
438
+ },
439
+ },
440
+ async run({ args }) {
441
+ // --settle: the unit-less verb that advances a run parked on a
442
+ // non-dispatching step. Mutually exclusive with the per-unit report flags.
443
+ if (args.settle === true) {
444
+ if (getStringArg(args, "unit") !== undefined || getStringArg(args, "status") !== undefined) {
445
+ throw new UsageError("--settle advances a route-only/empty step and takes no --unit or --status. Drop them, or report a " +
446
+ "specific unit with `--unit <id> --status <state>` instead.", "INVALID_FLAG_VALUE");
447
+ }
448
+ const { settleWorkflowSpine } = await import("../workflows/exec/report.js");
449
+ const result = await settleWorkflowSpine({
450
+ target: args.target,
451
+ ...(getStringArg(args, "expect-step") !== undefined ? { expectStep: getStringArg(args, "expect-step") } : {}),
452
+ });
453
+ output("workflow-report", result);
454
+ return;
455
+ }
456
+ const status = args.status;
457
+ if (!status) {
458
+ throw new UsageError("--status is required (completed | failed | running), or pass --settle to advance a non-dispatching step.", "MISSING_REQUIRED_ARGUMENT");
459
+ }
460
+ if (!WORKFLOW_REPORT_STATES.includes(status)) {
461
+ throw new UsageError(`Invalid --status "${status}". Expected one of: ${WORKFLOW_REPORT_STATES.join(", ")}.`, "INVALID_FLAG_VALUE");
462
+ }
463
+ const unitId = getStringArg(args, "unit");
464
+ if (!unitId) {
465
+ throw new UsageError("--unit is required (the content-derived unit id from `akm workflow brief`), or pass --settle for a route-only/empty step.", "MISSING_REQUIRED_ARGUMENT");
466
+ }
467
+ let tokens;
468
+ const rawTokens = getStringArg(args, "tokens");
469
+ if (rawTokens !== undefined) {
470
+ tokens = Number.parseInt(rawTokens, 10);
471
+ if (!/^\d+$/.test(rawTokens)) {
472
+ throw new UsageError(`--tokens must be a non-negative integer, got "${rawTokens}".`, "INVALID_FLAG_VALUE");
473
+ }
474
+ }
475
+ // Result payload precedence: --result, then --result-file, then stdin
476
+ // (completed/failed only; a running heartbeat carries no result).
477
+ let resultRaw;
478
+ if (status !== "running") {
479
+ const resultFile = getStringArg(args, "result-file");
480
+ if (args.result !== undefined && resultFile !== undefined) {
481
+ throw new UsageError("Pass at most one of --result or --result-file.", "INVALID_FLAG_VALUE");
482
+ }
483
+ if (args.result !== undefined) {
484
+ resultRaw = String(args.result);
485
+ }
486
+ else if (resultFile !== undefined) {
487
+ const fs = await import("node:fs");
488
+ resultRaw = fs.readFileSync(resultFile, "utf8");
489
+ }
490
+ else if (!process.stdin.isTTY) {
491
+ resultRaw = await readStdin();
492
+ }
493
+ }
494
+ const { reportWorkflowUnit } = await import("../workflows/exec/report.js");
495
+ const result = await reportWorkflowUnit({
496
+ target: args.target,
497
+ unitId,
498
+ status: status,
499
+ ...(getStringArg(args, "expect-step") !== undefined ? { expectStep: getStringArg(args, "expect-step") } : {}),
500
+ ...(resultRaw !== undefined ? { resultRaw } : {}),
501
+ ...(tokens !== undefined ? { tokens } : {}),
502
+ ...(args.rerun === true ? { rerun: true } : {}),
503
+ ...(getStringArg(args, "session-id") !== undefined ? { sessionId: getStringArg(args, "session-id") } : {}),
504
+ ...(getStringArg(args, "failure-reason") !== undefined
505
+ ? { failureReason: getStringArg(args, "failure-reason") }
506
+ : {}),
507
+ ...(getStringArg(args, "note") !== undefined ? { note: getStringArg(args, "note") } : {}),
508
+ });
509
+ output("workflow-report", result);
510
+ },
511
+ });
512
+ async function readStdin() {
513
+ const chunks = [];
514
+ for await (const chunk of process.stdin)
515
+ chunks.push(chunk);
516
+ return Buffer.concat(chunks).toString("utf8");
517
+ }
518
+ const workflowWatchCommand = defineJsonCommand({
519
+ meta: {
520
+ name: "watch",
521
+ description: "Print a run's workflow_* events (state.db events table) as NDJSON and exit; --stream polls in the " +
522
+ "foreground until the run reaches a terminal status (no daemon)",
523
+ },
524
+ args: {
525
+ runId: { type: "positional", description: "Workflow run id", required: true },
526
+ stream: {
527
+ type: "boolean",
528
+ description: "Keep polling for new events until the run leaves 'active' (completed/failed/blocked)",
529
+ default: false,
530
+ },
531
+ "interval-ms": { type: "string", description: "Poll interval in milliseconds for --stream (default: 1000)" },
532
+ },
533
+ async run({ args }) {
534
+ const rawInterval = getStringArg(args, "interval-ms");
535
+ let intervalMs;
536
+ if (rawInterval !== undefined) {
537
+ intervalMs = Number.parseInt(rawInterval, 10);
538
+ if (!/^\d+$/.test(rawInterval) || intervalMs <= 0) {
539
+ throw new UsageError(`--interval-ms must be a positive integer, got "${rawInterval}".`, "INVALID_FLAG_VALUE");
540
+ }
541
+ }
542
+ const { watchWorkflowRun } = await import("../workflows/exec/watch.js");
543
+ const result = await watchWorkflowRun({
544
+ runId: args.runId,
545
+ stream: args.stream === true,
546
+ ...(intervalMs !== undefined ? { intervalMs } : {}),
547
+ });
548
+ // The event lines above are raw NDJSON on stdout; this trailing envelope
549
+ // is the machine-readable command result (counts + terminal status).
550
+ output("workflow-watch", { ok: true, ...result });
551
+ },
552
+ });
293
553
  const workflowAbandonCommand = defineJsonCommand({
294
554
  meta: {
295
555
  name: "abandon",
@@ -332,6 +592,10 @@ export const workflowCommand = defineCommand({
332
592
  resume: workflowResumeCommand,
333
593
  abandon: workflowAbandonCommand,
334
594
  validate: workflowValidateCommand,
595
+ run: workflowRunCommand,
596
+ brief: workflowBriefCommand,
597
+ report: workflowReportCommand,
598
+ watch: workflowWatchCommand,
335
599
  },
336
600
  run({ args }) {
337
601
  return runWithJsonErrors(async () => {