akm-cli 0.9.31 → 0.10.26101002-alpha

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 (236) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/STABILITY.md +79 -53
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +13 -27
  7. package/dist/assets/hints/cli-hints-short.md +1 -1
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/cli/shared.js +31 -48
  11. package/dist/cli/unknown-flags.js +6 -43
  12. package/dist/cli.js +8 -34
  13. package/dist/commands/agent/contribute-cli.js +1 -1
  14. package/dist/commands/completions.js +1 -2
  15. package/dist/commands/config-cli.js +5 -28
  16. package/dist/commands/env/env-cli.js +5 -5
  17. package/dist/commands/env/secret-cli.js +2 -2
  18. package/dist/commands/health/checks.js +41 -16
  19. package/dist/commands/health/plugin-staleness.js +52 -22
  20. package/dist/commands/improve/autonomy-gate.js +5 -3
  21. package/dist/commands/improve/consolidate/pair-pass.js +7 -1
  22. package/dist/commands/improve/consolidate.js +6 -35
  23. package/dist/commands/improve/distill/lesson-update.js +91 -0
  24. package/dist/commands/improve/distill-guards.js +1 -43
  25. package/dist/commands/improve/distill.js +140 -20
  26. package/dist/commands/improve/eligibility.js +14 -1
  27. package/dist/commands/improve/extract-cli.js +1 -1
  28. package/dist/commands/improve/improve-cli.js +87 -102
  29. package/dist/commands/improve/improve-strategies.js +8 -12
  30. package/dist/commands/improve/improve.js +2 -2
  31. package/dist/commands/improve/loop-stages.js +0 -1
  32. package/dist/commands/improve/planner.js +0 -1
  33. package/dist/commands/improve/preparation.js +40 -99
  34. package/dist/commands/improve/proactive-maintenance.js +2 -2
  35. package/dist/commands/improve/reflect-noise.js +3 -55
  36. package/dist/commands/improve/reflect.js +3 -8
  37. package/dist/commands/improve/retrieval-scope.js +2 -2
  38. package/dist/commands/improve/salience.js +2 -9
  39. package/dist/commands/improve/stage.js +14 -9
  40. package/dist/commands/lint/index.js +5 -20
  41. package/dist/commands/observability-cli.js +3 -9
  42. package/dist/commands/proposal/drain.js +39 -6
  43. package/dist/commands/proposal/proposal-cli.js +11 -31
  44. package/dist/commands/proposal/repository.js +3 -3
  45. package/dist/commands/read/knowledge.js +3 -3
  46. package/dist/commands/read/remember-cli.js +0 -13
  47. package/dist/commands/read/search-cli.js +29 -97
  48. package/dist/commands/read/search.js +0 -11
  49. package/dist/commands/read/show.js +0 -19
  50. package/dist/commands/sources/plugin-upgrade.js +164 -101
  51. package/dist/commands/sources/sources-cli.js +1 -15
  52. package/dist/commands/sources/stash-cli.js +4 -12
  53. package/dist/commands/tasks/tasks-cli.js +4 -115
  54. package/dist/commands/workflow/plan.js +11 -33
  55. package/dist/commands/workflow-cli.js +2 -7
  56. package/dist/core/adapter/adapter-ids.js +1 -2
  57. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  58. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  59. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  60. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  61. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  62. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  63. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  64. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  65. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  66. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  67. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  68. package/dist/core/adapter/recognize-match.js +3 -4
  69. package/dist/core/common.js +18 -2
  70. package/dist/core/config/config-schema.js +12 -15
  71. package/dist/core/config/config-types.js +6 -15
  72. package/dist/core/config/config-walker.js +6 -23
  73. package/dist/core/config/config.js +17 -80
  74. package/dist/core/config/engine-semantics.js +7 -3
  75. package/dist/core/config/schema/engines.js +7 -7
  76. package/dist/core/config/schema/improve-processes.js +8 -61
  77. package/dist/core/config/schema/improve.js +2 -21
  78. package/dist/core/config/schema/index-config.js +6 -39
  79. package/dist/core/env-secret-ref.js +2 -45
  80. package/dist/core/errors.js +1 -2
  81. package/dist/core/extra-params.js +2 -86
  82. package/dist/core/improve-result.js +11 -1
  83. package/dist/core/mutation-target.js +2 -2
  84. package/dist/core/recognition-util.js +4 -5
  85. package/dist/core/warn.js +21 -4
  86. package/dist/core/write-source.js +3 -3
  87. package/dist/execution/target-ref.js +1 -2
  88. package/dist/indexer/search/db-search.js +4 -19
  89. package/dist/indexer/search/fts-query.js +0 -19
  90. package/dist/integrations/agent/engine-resolution.js +5 -2
  91. package/dist/integrations/agent/index.js +1 -1
  92. package/dist/integrations/agent/profiles.js +41 -98
  93. package/dist/integrations/agent/request-lowering.js +3 -3
  94. package/dist/integrations/harnesses/aider/index.js +5 -0
  95. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  96. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  97. package/dist/integrations/harnesses/claude/index.js +5 -0
  98. package/dist/integrations/harnesses/codex/index.js +1 -0
  99. package/dist/integrations/harnesses/copilot/index.js +1 -0
  100. package/dist/integrations/harnesses/gemini/index.js +1 -0
  101. package/dist/integrations/harnesses/index.js +18 -39
  102. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  103. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  104. package/dist/integrations/harnesses/opencode/index.js +22 -0
  105. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  106. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  107. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  108. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  109. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  110. package/dist/integrations/harnesses/opencode/version.js +98 -0
  111. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  112. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  113. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  114. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
  115. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  116. package/dist/integrations/harnesses/openhands/index.js +1 -0
  117. package/dist/integrations/harnesses/pi/index.js +1 -0
  118. package/dist/integrations/harnesses/shared.js +2 -0
  119. package/dist/integrations/harnesses/types.js +2 -0
  120. package/dist/llm/client.js +41 -11
  121. package/dist/output/cli-hints.js +1 -1
  122. package/dist/output/context.js +10 -19
  123. package/dist/output/format-exempt.js +5 -8
  124. package/dist/output/shapes/helpers.js +1 -22
  125. package/dist/output/shapes/passthrough.js +0 -1
  126. package/dist/output/shapes.js +3 -31
  127. package/dist/output/stdout.js +5 -1
  128. package/dist/output/text/command-format.js +6 -1
  129. package/dist/output/text/workflow-format.js +4 -22
  130. package/dist/scripts/akm-migrate-node.js +13049 -14013
  131. package/dist/scripts/akm-migrate.js +16316 -17276
  132. package/dist/setup/detect.js +10 -4
  133. package/dist/setup/harness-config-import.js +5 -14
  134. package/dist/setup/setup.js +1 -1
  135. package/dist/setup/steps/tasks.js +12 -13
  136. package/dist/storage/repositories/task-history-repository.js +0 -5
  137. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  138. package/dist/tasks/embedded.js +2 -1
  139. package/dist/tasks/run/task-history.js +0 -2
  140. package/dist/tasks/schedule-gates.js +39 -0
  141. package/dist/tasks/scheduler-binding.js +0 -16
  142. package/dist/tasks/scheduler-sync.js +3 -97
  143. package/dist/tasks/source/bounded-document.js +1 -1
  144. package/dist/tasks/source/task-source-v4.js +2 -12
  145. package/dist/tasks/task-run-reserved-flags.js +2 -31
  146. package/dist/workflows/authoring/authoring.js +6 -6
  147. package/dist/workflows/compile.js +8 -65
  148. package/dist/workflows/exec/child-workflow.js +22 -3
  149. package/dist/workflows/exec/native-executor.js +12 -147
  150. package/dist/workflows/exec/run-workflow.js +31 -103
  151. package/dist/workflows/exec/step-work.js +9 -178
  152. package/dist/workflows/exec/unit-dispatch.js +0 -10
  153. package/dist/workflows/freeze/environment.js +3 -44
  154. package/dist/workflows/freeze/freeze.js +8 -41
  155. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  156. package/dist/workflows/freeze/targets/command.js +2 -16
  157. package/dist/workflows/freeze/targets/shell.js +0 -2
  158. package/dist/workflows/freeze/task-bindings.js +2 -28
  159. package/dist/workflows/ir/params.js +12 -2
  160. package/dist/workflows/parser.js +82 -232
  161. package/dist/workflows/plan.js +1 -1
  162. package/dist/workflows/renderer.js +10 -19
  163. package/dist/workflows/runtime/run-plan.js +11 -53
  164. package/dist/workflows/runtime/runs.js +4 -27
  165. package/dist/workflows/source-files.js +12 -16
  166. package/dist/workflows/source-semantics.js +1 -93
  167. package/docs/README.md +1 -1
  168. package/docs/agents/README.md +3 -3
  169. package/docs/agents/agent-install.md +1 -1
  170. package/docs/architecture/adapters.md +17 -31
  171. package/docs/architecture/architecture.md +4 -5
  172. package/docs/architecture/improvement.md +52 -21
  173. package/docs/architecture/internals/improve-workflow.md +15 -8
  174. package/docs/architecture/internals/indexing.md +1 -1
  175. package/docs/architecture/internals/search.md +1 -2
  176. package/docs/architecture/internals/storage-locations.md +5 -5
  177. package/docs/architecture/persisted-data-compat.md +13 -4
  178. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  179. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  180. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  181. package/docs/architecture/testing/manual-testing-checklist.md +20 -29
  182. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  183. package/docs/architecture/testing/testing-workflow.md +1 -1
  184. package/docs/architecture/workflow-engine.md +5 -56
  185. package/docs/guides/author-workflows.md +39 -69
  186. package/docs/guides/capture-knowledge.md +1 -1
  187. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  188. package/docs/guides/discover-and-load.md +4 -4
  189. package/docs/guides/getting-started.md +1 -1
  190. package/docs/guides/improve-the-library.md +7 -7
  191. package/docs/guides/recipes/headless-install.md +2 -2
  192. package/docs/guides/run-workflows.md +1 -1
  193. package/docs/guides/use-with-any-agent.md +49 -4
  194. package/docs/integration/bundling-akm.md +7 -6
  195. package/docs/maintainers/curate-workmap.md +1 -2
  196. package/docs/maintainers/release-checklist.md +30 -1
  197. package/docs/posts/akm-command-tour.md +3 -3
  198. package/docs/product-surface.md +1 -1
  199. package/docs/reference/README.md +1 -0
  200. package/docs/reference/api.md +65 -0
  201. package/docs/reference/asset-types.md +3 -5
  202. package/docs/reference/bundle-types.md +2 -4
  203. package/docs/reference/cli.md +129 -171
  204. package/docs/reference/configuration.md +84 -19
  205. package/docs/reference/data-and-telemetry.md +1 -1
  206. package/docs/reference/env-and-secrets.md +2 -2
  207. package/docs/reference/supported-formats.md +3 -4
  208. package/docs/reference/tasks.md +12 -19
  209. package/docs/reference/website-sources.md +1 -1
  210. package/docs/reference/workflow-schema.md +89 -317
  211. package/docs/reference/workflows.md +20 -48
  212. package/package.json +12 -2
  213. package/schemas/akm-config.json +2 -243
  214. package/schemas/akm-task.json +2 -3
  215. package/schemas/akm-workflow.json +47 -84
  216. package/dist/assets/improve-strategies/catchup.json +0 -41
  217. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  218. package/dist/assets/improve-strategies/quick.json +0 -17
  219. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  220. package/dist/assets/improve-strategies/thorough.json +0 -49
  221. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  222. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  223. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  224. package/dist/cli/retired-commands.js +0 -118
  225. package/dist/commands/tasks/validate.js +0 -176
  226. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  227. package/dist/integrations/harnesses/ids.js +0 -28
  228. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  229. package/dist/tasks/source/task-to-v4.js +0 -863
  230. package/dist/workflows/exec/worktree.js +0 -372
  231. package/dist/workflows/freeze/child-output-references.js +0 -83
  232. package/dist/workflows/freeze/identity.js +0 -22
  233. package/dist/workflows/freeze/targets/script.js +0 -50
  234. package/dist/workflows/freeze/targets/task.js +0 -124
  235. package/dist/workflows/github-yaml.js +0 -506
  236. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -14,6 +14,7 @@ import { runManagedSubprocess } from "../core/subprocess.js";
14
14
  import { defaultWhich } from "../integrations/agent/detect.js";
15
15
  import { getBuiltinAgentProfile, OPENCODE_SDK_SERVER_BIN } from "../integrations/agent/profiles.js";
16
16
  import { AGENT_DISPATCH_HARNESSES, SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
17
+ import { detectOpencodeMajor, resolveOpencodeBin } from "../integrations/harnesses/opencode/version.js";
17
18
  import { detectHarnessConfigs } from "./harness-config-import.js";
18
19
  let detectOverrides;
19
20
  /** TEST-ONLY. Swap the network/host probes; pass undefined to restore. */
@@ -342,7 +343,8 @@ function findGitRepoRoot(start) {
342
343
  * 3. none.
343
344
  *
344
345
  * The SDK import resolving is NOT evidence the SDK path can run: `@opencode-ai/sdk`
345
- * is a hard dependency of akm-cli itself, so `import()` always succeeds and an
346
+ * (OpenCode 1) and `@opencode/client` (OpenCode 2; the binary's detected major
347
+ * selects) are hard dependencies of akm-cli itself, so `import()` always succeeds and an
346
348
  * import-only check would report `opencode-sdk` on every machine — including
347
349
  * ones where the first agentic command then dies with spawn ENOENT. The SDK
348
350
  * runner spawns `opencode serve` (see `harnesses/opencode-sdk/sdk-runner.ts`),
@@ -355,10 +357,14 @@ function findGitRepoRoot(start) {
355
357
  export async function detectHarness(whichFn = defaultWhich) {
356
358
  // Probed once; reused below so the `opencode` CLI fallback doesn't repeat
357
359
  // the identical full-PATH walk this just performed.
358
- const opencodePath = whichFn(OPENCODE_SDK_SERVER_BIN);
360
+ const opencodePath = whichFn(resolveOpencodeBin(undefined, whichFn));
359
361
  if (opencodePath) {
360
362
  try {
361
- await import("@opencode-ai/sdk");
363
+ // The client package of the major the binary reports.
364
+ if (detectOpencodeMajor(opencodePath).major === 1)
365
+ await import("@opencode-ai/sdk");
366
+ else
367
+ await import("@opencode/client");
362
368
  return "opencode-sdk";
363
369
  }
364
370
  catch {
@@ -368,7 +374,7 @@ export async function detectHarness(whichFn = defaultWhich) {
368
374
  }
369
375
  for (const harness of AGENT_DISPATCH_HARNESSES) {
370
376
  // The SDK harness dispatches without a CLI profile and was decided above;
371
- // an explicit skip so a future BUILTINS entry for it can't silently
377
+ // an explicit skip so a future `profile` for it can't silently
372
378
  // reintroduce it here after the import guard already rejected it.
373
379
  if (harness.id === "opencode-sdk")
374
380
  continue;
@@ -8,8 +8,8 @@
8
8
  * no network) and, if so, reads its config to extract LLM connection details.
9
9
  * API key VALUES are never stored — only the env var names that hold them.
10
10
  *
11
- * To add a new harness: implement {@link HarnessConfigImporter} and append it
12
- * to {@link HARNESS_CONFIG_IMPORTERS}.
11
+ * To add a new harness importer: declare `configImporter` on the harness
12
+ * descriptor (`integrations/harnesses/<id>/index.ts`).
13
13
  *
14
14
  * NOTE: The `detect()` method in each importer overlaps intentionally with
15
15
  * `detectAgentPlatforms()` in `detect.ts`. That function scans for harness
@@ -17,18 +17,9 @@
17
17
  * further by reading and parsing the harness config. They serve different
18
18
  * purposes and should not be deduplicated.
19
19
  */
20
- import { claudeCodeImporter } from "../integrations/harnesses/claude/config-import.js";
21
- import { openCodeImporter } from "../integrations/harnesses/opencode/config-import.js";
22
- // The Claude Code importer was migrated to its harness directory in #563
23
- // (`harnesses/claude/config-import.ts`) and the OpenCode importer in #564
24
- // (`harnesses/opencode/config-import.ts`). Both are imported back into
25
- // HARNESS_CONFIG_IMPORTERS below so detection order is unchanged.
26
- // ── Registry ─────────────────────────────────────────────────────────────────
27
- /**
28
- * Registry of all supported harness config importers.
29
- * To add a new harness: implement {@link HarnessConfigImporter} and append here.
30
- */
31
- export const HARNESS_CONFIG_IMPORTERS = [claudeCodeImporter, openCodeImporter];
20
+ import { HARNESS_REGISTRY } from "../integrations/harnesses/index.js";
21
+ /** Importers declared by the registered harnesses, in registry order. */
22
+ export const HARNESS_CONFIG_IMPORTERS = HARNESS_REGISTRY.flatMap((h) => h.configImporter ? [h.configImporter] : []);
32
23
  /**
33
24
  * Run all importers whose `detect()` returns `true` and collect their configs.
34
25
  *
@@ -625,7 +625,7 @@ export async function runSetupWizard(opts) {
625
625
  }
626
626
  }
627
627
  p.outro(`Configuration saved to ${configPath}\n` +
628
- 'Next: `akm bundle add <source>`, `akm index`, `akm search "<query>"`, `akm help agents`');
628
+ 'Next: `akm bundle add <source>`, `akm index`, `akm search "<query>"`, `akm hints`');
629
629
  }
630
630
  // ── Non-interactive / scripting entry points ─────────────────────────────────
631
631
  /**
@@ -19,13 +19,12 @@ import { parseSchedule } from "../../tasks/schedule.js";
19
19
  import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
20
20
  import { prompt } from "../prompt.js";
21
21
  /**
22
- * A scheduled server-only nightly full sweep exists among the embedded
23
- * improve-schedule templates (folded in from the retired
24
- * `registerDefaultTasks`/`akm tasks init` path in 0.9, S6). This id is
25
- * preselected in the review multiselect on a detected server install — the
26
- * "battery heuristic" {@link detectServerDefault} preserved from that path.
22
+ * The nightly improve template (`core/improve`). This id is preselected in the
23
+ * review multiselect on a detected server install — the "battery heuristic"
24
+ * {@link detectServerDefault} preserved from the retired
25
+ * `registerDefaultTasks`/`akm tasks init` path (0.9, S6).
27
26
  */
28
- const SERVER_SUGGESTED_TASK_ID = "akm-improve-nightly";
27
+ const SERVER_SUGGESTED_TASK_ID = "improve";
29
28
  let scheduledTasksEnvOverrides;
30
29
  /** TEST-ONLY. Swap the CI/server-detection probes; pass undefined to restore. */
31
30
  export function _setScheduledTasksEnvForTests(fakes) {
@@ -66,7 +65,7 @@ export function detectServerDefault() {
66
65
  }
67
66
  catch {
68
67
  // If we cannot read power-supply info, prefer the safe server default on
69
- // Linux (the nightly sweep is low-impact and re-runnable).
68
+ // Linux (the nightly improve run is low-impact and re-runnable).
70
69
  return true;
71
70
  }
72
71
  }
@@ -200,11 +199,11 @@ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS, op
200
199
  p.log.info("Non-interactive setup leaves task files and scheduler state unchanged. Run `akm setup` interactively to review tasks.");
201
200
  return;
202
201
  }
203
- // ALL templates are offered, including ships-disabled ones (e.g. the
204
- // manual-recovery catchup task): an unselected template is still PREPARED,
205
- // so its YAML exists for `akm task run <id>` while its ref remains absent
206
- // from local scheduler activation. Filtering on `task.enabled`
207
- // here would make ships-disabled templates invisible and unpreparable.
202
+ // ALL templates are offered, including ships-disabled ones: an unselected
203
+ // template is still PREPARED, so its YAML exists for `akm task run <id>`
204
+ // while its ref remains absent from local scheduler activation. Filtering on
205
+ // `task.enabled` here would make ships-disabled templates invisible and
206
+ // unpreparable.
208
207
  const embedded = listEmbeddedTasks();
209
208
  if (embedded.length === 0)
210
209
  return;
@@ -228,7 +227,7 @@ export async function stepScheduledTasks(deps = DEFAULT_SCHEDULED_TASKS_DEPS, op
228
227
  byId.set(normaliseTaskIdForMatch(task.id), task);
229
228
  const preChecked = embedded.filter((task) => byId.get(task.id)?.enabled === true).map((task) => task.id);
230
229
  // Battery heuristic preserved from the retired `registerDefaultTasks` path
231
- // (S6): suggest the nightly full sweep on a detected server install, same
230
+ // (S6): suggest the nightly improve task on a detected server install, same
232
231
  // as every other embedded template, still gated behind the confirmation
233
232
  // below.
234
233
  if (!byId.has(SERVER_SUGGESTED_TASK_ID) &&
@@ -67,10 +67,6 @@ export function decodeTaskHistoryMetadata(input) {
67
67
  // A malformed `engine` (wrong type) is dropped like any other unrecognized
68
68
  // field rather than rejecting the whole row over a non-essential value.
69
69
  const engine = parsed.engine === null || typeof parsed.engine === "string" ? parsed.engine : undefined;
70
- if (parsed.targetVocab !== undefined && parsed.targetVocab !== 2) {
71
- warnOnce(`task-history-target-vocab:${String(parsed.targetVocab)}`, `task_history row has targetVocab ${String(parsed.targetVocab)}, newer than this akm's 2 — falling back to ` +
72
- "the legacy target_kind mapping rather than rejecting the row.");
73
- }
74
70
  validateDetail(detail);
75
71
  const cleanDetail = detail
76
72
  ? {
@@ -85,7 +81,6 @@ export function decodeTaskHistoryMetadata(input) {
85
81
  durationMs: parsed.durationMs,
86
82
  detail: cleanDetail,
87
83
  ...(engine !== undefined ? { engine } : {}),
88
- ...(parsed.targetVocab === 2 ? { targetVocab: 2 } : {}),
89
84
  };
90
85
  }
91
86
  /**
@@ -353,15 +353,6 @@ export class WorkflowRunsRepository {
353
353
  return db.prepare("SELECT * FROM workflow_runs WHERE id = ?").get(input.run.id);
354
354
  });
355
355
  }
356
- /**
357
- * Persist a run's resolved declared `outputs:` (migration 024, P3b §4.3).
358
- * Called from INSIDE `completeWorkflowStep`'s own write transaction — this
359
- * method opens none of its own, matching `updateStepCompletion` /
360
- * `updateRunState` immediately above.
361
- */
362
- setRunOutputs(runId, outputsJson) {
363
- this.db.prepare("UPDATE workflow_runs SET outputs_json = ? WHERE id = ?").run(outputsJson, runId);
364
- }
365
356
  /** Every child run published under `parentRunId`, oldest first. `[]` when none. */
366
357
  childRunsOf(parentRunId) {
367
358
  return this.db
@@ -432,13 +423,13 @@ export class WorkflowRunsRepository {
432
423
  const dispatchId = randomUUID();
433
424
  db.prepare(`INSERT INTO workflow_run_unit_attempts (
434
425
  run_id, unit_id, attempt, dispatch_id, step_id, node_id, phase,
435
- runner, engine, model, input_hash, status, worktree_path, started_at,
426
+ runner, engine, model, input_hash, status, started_at,
436
427
  claim_holder, claim_expires_at
437
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?)`).run(input.runId, input.unitId, attemptNumber, dispatchId, input.stepId, input.nodeId, input.phase, input.runner, input.engine, input.model, input.inputHash, input.worktreePath ?? null, input.now, holder, input.now);
428
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`).run(input.runId, input.unitId, attemptNumber, dispatchId, input.stepId, input.nodeId, input.phase, input.runner, input.engine, input.model, input.inputHash, input.now, holder, input.now);
438
429
  db.prepare(`INSERT INTO workflow_run_units (
439
430
  run_id, unit_id, step_id, node_id, parent_unit_id, phase, runner, engine, model,
440
- status, input_hash, worktree_path, started_at, claim_holder, claim_expires_at, attempts
441
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?, ?, ?)
431
+ status, input_hash, started_at, claim_holder, claim_expires_at, attempts
432
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?, ?, ?)
442
433
  ON CONFLICT(run_id, unit_id) DO UPDATE SET
443
434
  step_id = excluded.step_id,
444
435
  node_id = excluded.node_id,
@@ -449,7 +440,6 @@ export class WorkflowRunsRepository {
449
440
  model = excluded.model,
450
441
  status = 'running',
451
442
  input_hash = excluded.input_hash,
452
- worktree_path = excluded.worktree_path,
453
443
  started_at = excluded.started_at,
454
444
  claim_holder = excluded.claim_holder,
455
445
  claim_expires_at = excluded.claim_expires_at,
@@ -458,7 +448,7 @@ export class WorkflowRunsRepository {
458
448
  failure_reason = NULL,
459
449
  session_id = NULL,
460
450
  finished_at = NULL,
461
- attempts = excluded.attempts`).run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId ?? null, input.phase, input.runner, input.engine, input.model, input.inputHash, input.worktreePath ?? null, input.now, holder, input.now, attemptNumber);
451
+ attempts = excluded.attempts`).run(input.runId, input.unitId, input.stepId, input.nodeId, input.parentUnitId ?? null, input.phase, input.runner, input.engine, input.model, input.inputHash, input.now, holder, input.now, attemptNumber);
462
452
  insertEventStrict(db, {
463
453
  eventType: "workflow_unit_started",
464
454
  ts: input.now,
@@ -25,7 +25,8 @@ import { getDirname } from "../runtime.js";
25
25
  import { parseTaskSource } from "./source/parse-task-source.js";
26
26
  /** Directory holding the bundled task template categories. */
27
27
  const TASKS_ASSETS_DIR = path.join(getDirname(import.meta.url), "../assets/tasks");
28
- const DEFAULT_DISABLED_TASKS = new Set(["improve/akm-improve-catchup"]);
28
+ /** Templates setup offers but ships disabled (`category/id`). None ship disabled since catchup was removed (#1130). */
29
+ const DEFAULT_DISABLED_TASKS = new Set();
29
30
  /**
30
31
  * Enumerate the embedded task templates from every category subdirectory of
31
32
  * the bundled assets directory. Sorted by category then id for deterministic
@@ -49,8 +49,6 @@ export function appendHistory(result, historyReserved = false) {
49
49
  metadataVersion: 2,
50
50
  durationMs: result.durationMs,
51
51
  detail: result.detail ?? null,
52
- // D8 (spec §5.3): every NEW row carries the vocabulary marker.
53
- targetVocab: 2,
54
52
  ...(result.target.kind === "command" ? { engine: result.target.engine } : {}),
55
53
  }),
56
54
  };
@@ -0,0 +1,39 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * The two per-source schedule gates `akm task sync` runs before it installs a
6
+ * schedule, kept in a leaf module so `akm lint` can run them without importing
7
+ * the scheduler's task-preparation graph (which reaches the adapter registry
8
+ * that lint itself is part of).
9
+ */
10
+ import { UsageError } from "../core/errors.js";
11
+ import { applyInputDefaults, validateInputs } from "../execution/input-contract.js";
12
+ import { parseSchedule } from "./schedule.js";
13
+ /**
14
+ * Validate every v4 `schedule:` entry's `inputs` against the task's own
15
+ * declared contract with defaults applied — the exact set of values a
16
+ * compiled invocation delivers — so a violation is reported at sync rather
17
+ * than when the scheduler fires. Shared with `akm lint`.
18
+ */
19
+ export function assertTaskScheduleInputsSatisfyContract(v4, refLabel) {
20
+ const contract = v4.inputs ?? {};
21
+ for (const scheduleEntry of v4.schedule) {
22
+ const defaultedInputs = applyInputDefaults(contract, { ...scheduleEntry.inputs });
23
+ const errors = validateInputs(contract, defaultedInputs);
24
+ if (errors.length > 0) {
25
+ throw new UsageError(`Task ${JSON.stringify(refLabel)} schedule[${scheduleEntry.ordinal}].inputs does not satisfy ` +
26
+ `its declared inputs once defaults are applied: ${errors.join("; ")}`, "TASK_SOURCE_INVALID");
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Validate every v4 `schedule:` entry's `cron` against the active backend's
32
+ * dialect: cron is the most permissive of the three, so a task authored on
33
+ * Linux can carry an expression launchd/schtasks cannot translate. Shared
34
+ * with `akm lint`.
35
+ */
36
+ export function assertTaskScheduleCronValid(v4, backend) {
37
+ for (const scheduleEntry of v4.schedule)
38
+ parseSchedule(scheduleEntry.cron, backend);
39
+ }
@@ -62,22 +62,6 @@ function schedulerInputFlagValueText(value) {
62
62
  return String(value);
63
63
  return canonicalInputJson(value);
64
64
  }
65
- export function compileWorkflowSchedulerBindings(input) {
66
- const ref = assertQualifiedRef(input.qualifiedRef, "workflow");
67
- return Object.freeze(input.schedules.map((schedule) => {
68
- const bindingId = digestBindingId("workflow", ref, schedule.ordinal);
69
- return freezeBinding({
70
- id: bindingId,
71
- nativeId: bindingId,
72
- logicalSource: { kind: "workflow", ref },
73
- cron: schedule.cron,
74
- source: schedule.source,
75
- ordinal: schedule.ordinal,
76
- enabled: true,
77
- invocation: ["workflow", "run", ref],
78
- });
79
- }));
80
- }
81
65
  /**
82
66
  * Map a logical binding id to the flat portable token persisted by native
83
67
  * schedulers. Flat ids are byte-for-byte stable; only nested standalone
@@ -3,7 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
5
  * Scheduler reconciliation: compile the desired bindings from a bundle's
6
- * task and workflow sources, then diff them against the rows the native
6
+ * task sources, then diff them against the rows the native
7
7
  * scheduler holds.
8
8
  *
9
9
  * `compileSchedulerSources` reads each source once. One source that fails
@@ -17,14 +17,9 @@ import fs from "node:fs";
17
17
  import path from "node:path";
18
18
  import { makeBundleRef, parseBundleRef } from "../core/asset/asset-ref.js";
19
19
  import { compareCodePoints, toPosix } from "../core/common.js";
20
- import { UsageError } from "../core/errors.js";
21
- import { canonicalizeWorkflowName, WORKFLOW_EXTENSIONS } from "../core/recognition-util.js";
22
- import { applyInputDefaults, validateInputs } from "../execution/input-contract.js";
23
- import { checkWorkflowPlan, compileWorkflowSource } from "../workflows/compile.js";
24
- import { WorkflowSourceCollisionError, WorkflowSourceNameError, workflowNameForSourcePath, } from "../workflows/source-files.js";
25
20
  import { prepareTaskV3Execution } from "./prepare/prepare.js";
26
- import { parseSchedule } from "./schedule.js";
27
- import { compileTaskSchedulerBindings, compileWorkflowSchedulerBindings, schedulerBindingNativeId, schedulerNativeBindingId, } from "./scheduler-binding.js";
21
+ import { assertTaskScheduleCronValid, assertTaskScheduleInputsSatisfyContract } from "./schedule-gates.js";
22
+ import { compileTaskSchedulerBindings, schedulerBindingNativeId, schedulerNativeBindingId, } from "./scheduler-binding.js";
28
23
  import { parseTaskSource } from "./source/parse-task-source.js";
29
24
  import { projectTaskSourceV4 } from "./source/project-v4.js";
30
25
  import { taskSourceErrorDetail } from "./source-v3.js";
@@ -37,7 +32,6 @@ export async function compileSchedulerSources(input) {
37
32
  const desired = [];
38
33
  const failures = [];
39
34
  await compileTaskSources({ ...input, sourceRoot }, desired, failures);
40
- compileWorkflowSources({ ...input, sourceRoot }, desired, failures);
41
35
  return Object.freeze({
42
36
  desired: Object.freeze(desired),
43
37
  failures: Object.freeze(failures.sort((left, right) => compareCodePoints(left.path, right.path))),
@@ -107,67 +101,6 @@ function taskSourceFiles(sourceRoot, adapterId) {
107
101
  return walkFiles(sourceRoot).filter((file) => file.endsWith(".yml"));
108
102
  return [];
109
103
  }
110
- function compileWorkflowSources(input, desired, failures) {
111
- if (input.adapterId !== "akm" && input.adapterId !== "akm-workflow")
112
- return;
113
- const root = input.adapterId === "akm" ? path.join(input.sourceRoot, "workflows") : input.sourceRoot;
114
- const byName = new Map();
115
- for (const file of walkFiles(root)) {
116
- if (path.basename(file).toLowerCase() === "readme.md")
117
- continue;
118
- const authoredName = workflowNameForSourcePath(input.sourceRoot, input.adapterId, file);
119
- if (authoredName === undefined)
120
- continue;
121
- const extension = path.posix.extname(authoredName).toLowerCase();
122
- const stem = authoredName.slice(0, -extension.length).toLowerCase();
123
- const nestedSuffix = WORKFLOW_EXTENSIONS.find((suffix) => stem.endsWith(suffix));
124
- if (nestedSuffix) {
125
- const relative = toPosix(path.relative(input.sourceRoot, file));
126
- failures.push({ path: file, reason: new WorkflowSourceNameError(relative, nestedSuffix).message });
127
- continue;
128
- }
129
- const canonicalName = canonicalizeWorkflowName(authoredName);
130
- byName.set(canonicalName, [...(byName.get(canonicalName) ?? []), file]);
131
- }
132
- for (const [canonicalName, sources] of [...byName].sort(([left], [right]) => compareCodePoints(left, right))) {
133
- const conceptId = input.adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName;
134
- const ref = sourceRef(input.bundleName, conceptId);
135
- if (ref === undefined || (input.enabledRefs && !input.enabledRefs.has(ref)))
136
- continue;
137
- const file = sources[0];
138
- try {
139
- if (sources.length > 1) {
140
- throw new WorkflowSourceCollisionError(conceptId, sources.map((source) => toPosix(path.relative(input.sourceRoot, source))));
141
- }
142
- const relative = toPosix(path.relative(input.sourceRoot, file));
143
- const compiled = compileWorkflowSource(fs.readFileSync(file, "utf8"), {
144
- path: relative,
145
- workspaceRoot: input.sourceRoot,
146
- });
147
- if (!compiled.ok) {
148
- throw new UsageError(compiled.errors
149
- .map((error) => `${error.path}:${error.line ?? 1} [${error.code}] ${error.message}`)
150
- .join("; "), "WORKFLOW_SOURCE_INVALID");
151
- }
152
- const planDraft = checkWorkflowPlan(compiled.plan);
153
- if (!planDraft.ok) {
154
- throw new UsageError(planDraft.errors.map((error) => `${relative}:${error.line} ${error.message}`).join("; "), "WORKFLOW_SOURCE_INVALID");
155
- }
156
- const schedules = (compiled.plan.schedules ?? []).map((schedule) => ({
157
- cron: schedule.cron,
158
- source: `${relative}:${schedule.line}`,
159
- ordinal: schedule.ordinal,
160
- }));
161
- for (const binding of compileWorkflowSchedulerBindings({ qualifiedRef: ref, schedules })) {
162
- parseSchedule(binding.cron, input.backend);
163
- desired.push(binding);
164
- }
165
- }
166
- catch (cause) {
167
- failures.push({ path: file, ref, reason: errorMessage(cause) });
168
- }
169
- }
170
- }
171
104
  /** A file whose name cannot form a ref (a `#` in it) cannot be enabled, so it is not this sync's. */
172
105
  function sourceRef(bundleName, conceptId) {
173
106
  try {
@@ -213,33 +146,6 @@ function statOrUndefined(file) {
213
146
  return undefined;
214
147
  }
215
148
  }
216
- /**
217
- * Validate every v4 `schedule:` entry's `inputs` against the task's own
218
- * declared contract with defaults applied — the exact set of values a
219
- * compiled invocation delivers — so a violation is reported at sync rather
220
- * than when the scheduler fires. Shared with `akm task validate`.
221
- */
222
- export function assertTaskScheduleInputsSatisfyContract(v4, refLabel) {
223
- const contract = v4.inputs ?? {};
224
- for (const scheduleEntry of v4.schedule) {
225
- const defaultedInputs = applyInputDefaults(contract, { ...scheduleEntry.inputs });
226
- const errors = validateInputs(contract, defaultedInputs);
227
- if (errors.length > 0) {
228
- throw new UsageError(`Task ${JSON.stringify(refLabel)} schedule[${scheduleEntry.ordinal}].inputs does not satisfy ` +
229
- `its declared inputs once defaults are applied: ${errors.join("; ")}`, "TASK_SOURCE_INVALID");
230
- }
231
- }
232
- }
233
- /**
234
- * Validate every v4 `schedule:` entry's `cron` against the active backend's
235
- * dialect: cron is the most permissive of the three, so a task authored on
236
- * Linux can carry an expression launchd/schtasks cannot translate. Shared
237
- * with `akm task validate`.
238
- */
239
- export function assertTaskScheduleCronValid(v4, backend) {
240
- for (const scheduleEntry of v4.schedule)
241
- parseSchedule(scheduleEntry.cron, backend);
242
- }
243
149
  export function installedRowNativeId(row) {
244
150
  return row.nativeId ?? schedulerNativeBindingId(row.id);
245
151
  }
@@ -380,7 +380,7 @@ export function assertBoundedTaskYamlDocument(document, options) {
380
380
  * P4 (docs/plans/specs/p4-deletions-closeout.md §3.2) deleted task v3
381
381
  * acceptance from `src` entirely — `parseTaskV3Yaml` and its `"task v3
382
382
  * source"` label no longer exist here (the grammar survives only in the
383
- * vendored, frozen `src/tasks/source/task-source-v3-frozen.ts`
383
+ * vendored, frozen `scripts/akm-migrate/migrate/task-source-v3-frozen.ts`
384
384
  * copy, which does not call this function). The two live `src` callers
385
385
  * today: `parseTaskSourceV4`'s standalone YAML-string entry
386
386
  * (`task-source-v4.ts:790`) passes `sourceLabel: "task source v4"`; the
@@ -44,7 +44,7 @@ import { classifyTargetRef } from "../../execution/target-ref.js";
44
44
  import { detectSecretShapedParams } from "../../workflows/exec/param-secrets.js";
45
45
  import { WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_SCHEMA_BYTES } from "../../workflows/resource-limits.js";
46
46
  import { TASK_V3_HOST_SHELLS, TASK_V3_MAX_SCHEDULES } from "../source-v3.js";
47
- import { TASK_RUN_RESERVED_FLAG_NAMES, TASK_RUN_SELF_DIAGNOSED_FLAGS } from "../task-run-reserved-flags.js";
47
+ import { TASK_RUN_RESERVED_FLAG_NAMES } from "../task-run-reserved-flags.js";
48
48
  import { asRecord, checkKeys, cloneBoundedJson, noGithubExpression, own, parseEnvironment, parseStringArray, presentJsonValue, readBoundedTaskSourceYaml, sourceError, stringField, TASK_V3_MAX_REDACT_NAMES, utf8Bytes, validateWorkingDirectory, } from "./bounded-document.js";
49
49
  // ── Closed constants (D1, D2-N3, D2-N7) ─────────────────────────────────────
50
50
  export const TASK_SOURCE_V4_VERSION = 4;
@@ -391,18 +391,8 @@ function parseInputDeclarations(value, ctx) {
391
391
  // checked against this same contract below, so a banned name can never
392
392
  // reach compileTaskSchedulerBindings's invocation tail either) — see
393
393
  // ../task-run-reserved-flags.ts's own header.
394
- //
395
- // 0.9.2 review round 2: `target` joins that set from the CLI's DIAGNOSTIC
396
- // side rather than its declared-arg side. `rejectRetiredTaskTargetFlag`
397
- // (../../commands/tasks/tasks-cli.ts) throws the 0.9 `--target` ->
398
- // `--bundle` rename hint for every spelling of the name, before
399
- // parseTaskInputFlags scans argv at all, so an input declared under that
400
- // name could never be supplied either — it is the same unusable
401
- // declaration, reached by a different route (TASK_RUN_SELF_DIAGNOSED_FLAGS).
402
394
  if (TASK_RUN_RESERVED_FLAG_NAMES.has(name)) {
403
- sourceError(ctx, ["inputs", name], TASK_RUN_SELF_DIAGNOSED_FLAGS.includes(name)
404
- ? `collides with the retired \`akm task --${name}\` spelling, which every task subcommand still rejects with a rename hint, so no --${name} flag can ever reach this input; declare the input under a different name.`
405
- : `collides with akm task run's own --${name} flag; declare the input under a different name.`);
395
+ sourceError(ctx, ["inputs", name], `collides with akm task run's own --${name} flag; declare the input under a different name.`);
406
396
  }
407
397
  result[name] = parseInputDeclaration(name, input[name], ctx);
408
398
  }
@@ -26,9 +26,7 @@
26
26
  * `src/commands/tasks/tasks-cli.ts` re-exports `TASK_RUN_VALUE_FLAGS` /
27
27
  * `TASK_RUN_BOOLEAN_FLAGS` from here unchanged, so this module is the single
28
28
  * source of truth both the CLI's own argv scanner and the source parser read
29
- * — the two can never drift apart. `TASK_RUN_SELF_DIAGNOSED_FLAGS` (below)
30
- * covers the third category the scanner sets cannot express: a name the CLI
31
- * claims by REJECTING it rather than by declaring it. This file imports
29
+ * — the two can never drift apart. This file imports
32
30
  * nothing, and nothing it
33
31
  * exports depends on IO, config, or any other `src/tasks/**` module, so it
34
32
  * can be imported from either side of that boundary without participating in
@@ -45,35 +43,8 @@ export const TASK_RUN_BOOLEAN_FLAGS = [
45
43
  "no-quiet",
46
44
  "no-verbose",
47
45
  ];
48
- /**
49
- * Flag names `akm task` claims WITHOUT declaring them as citty args, so they
50
- * appear in neither list above and must be reserved separately.
51
- *
52
- * `target` is the only member: the 0.9 rename of `akm task --target` to
53
- * `--bundle` (S8.4) is self-diagnosed rather than merely dropped —
54
- * `rejectRetiredTaskTargetFlag` (`../commands/tasks/tasks-cli.ts`) throws the
55
- * rename hint for a `--target` in ANY spelling, bare or `--target=<value>`,
56
- * and the generic pre-dispatch gate exempts the name on every `task`
57
- * subcommand precisely so that handler can answer (`../cli/unknown-flags.ts`'s
58
- * `SELF_DIAGNOSED_FLAGS`). That rejection runs before `parseTaskInputFlags`
59
- * ever scans argv, so a declared input named `target` is unreachable through
60
- * every spelling of its own flag — the exact value-misrouting hole the union
61
- * below exists to close, just reached from the CLI's diagnostic side instead
62
- * of from its declared-arg side (0.9.2 review round 2). Rejecting the
63
- * DECLARATION keeps the rename hint intact for everyone typing the retired
64
- * spelling while making the unusable declaration impossible to author, rather
65
- * than trading one silent failure for another.
66
- *
67
- * It deliberately stays OUT of `TASK_RUN_VALUE_FLAGS` /
68
- * `TASK_RUN_BOOLEAN_FLAGS`: those two are `parseTaskInputFlags`' own scanner
69
- * sets (a value flag makes the scanner swallow the following token), and
70
- * `--target` must keep reaching the rename diagnostic rather than being
71
- * silently consumed as one of `akm task run`'s own flags.
72
- */
73
- export const TASK_RUN_SELF_DIAGNOSED_FLAGS = ["target"];
74
- /** The union of all three lists above, for a single membership check against a candidate input name. */
46
+ /** The union of the two lists above, for a single membership check against a candidate input name. */
75
47
  export const TASK_RUN_RESERVED_FLAG_NAMES = new Set([
76
48
  ...TASK_RUN_VALUE_FLAGS,
77
49
  ...TASK_RUN_BOOLEAN_FLAGS,
78
- ...TASK_RUN_SELF_DIAGNOSED_FLAGS,
79
50
  ]);
@@ -41,13 +41,13 @@ function validateWorkflowContent(content, sourcePath) {
41
41
  throw new UsageError(formatWorkflowErrors(sourcePath, compiled.errors));
42
42
  }
43
43
  }
44
- /** Peer YAML is executable, but `akm workflow create` emits the Markdown authoring form only. */
44
+ /** Workflows are Markdown; `akm workflow create` rejects a YAML suffix. */
45
45
  const YAML_SUFFIX_RE = /\.ya?ml$/i;
46
46
  export function assertWorkflowMarkdownName(name) {
47
47
  if (!YAML_SUFFIX_RE.test(name.trim()))
48
48
  return;
49
49
  throw new UsageError(`akm workflow create is markdown-only: it emits Markdown and cannot create "${name}". ` +
50
- `Use a plain name (no ".yaml"/".yml" suffix), or author a peer GitHub-shaped ".yml" workflow directly.`);
50
+ `Use a plain name (no ".yaml"/".yml" suffix).`);
51
51
  }
52
52
  // 0.9.2 review round 2 (P4 sweep criterion-21 caveat, spec
53
53
  // docs/plans/specs/p4-deletions-closeout.md §4.1 row B-49): this return's
@@ -75,7 +75,7 @@ export function createWorkflowAsset(input) {
75
75
  path.isAbsolute(relativeAssetPath)) {
76
76
  throw new UsageError(`Resolved workflow path escapes the bundle: "${normalizedName}"`, "PATH_ESCAPE_VIOLATION");
77
77
  }
78
- // A peer `.md` or `.yml` file whose canonical name matches already owns this
78
+ // A `.md` file whose canonical name matches already owns this
79
79
  // ref. Case variants (`upper.MD` vs `upper.md`) may also name one physical
80
80
  // file on a case-insensitive filesystem, so writing the Markdown create
81
81
  // target would silently clobber or shadow the existing asset.
@@ -160,9 +160,9 @@ export function formatWorkflowErrors(path, errors) {
160
160
  }
161
161
  /**
162
162
  * Every file under `typeRoot` whose canonical workflow name equals
163
- * `normalizedName`. Peer `.md`/`.yml` extension matching is case-INSENSITIVE,
164
- * so both cross-format owners and case variants are returned for the create
165
- * path to refuse rather than silently clobber or shadow them.
163
+ * `normalizedName`. `.md` extension matching is case-INSENSITIVE, so case
164
+ * variants are returned for the create path to refuse rather than silently
165
+ * clobber or shadow them.
166
166
  */
167
167
  function findExistingWorkflowPaths(typeRoot, normalizedName) {
168
168
  const parent = path.join(typeRoot, path.dirname(normalizedName));