akm-cli 0.9.1 → 0.9.2-alpha.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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -1,380 +0,0 @@
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
- * Parse a task YAML file into a {@link TaskDocument}.
6
- *
7
- * The on-disk shape is a pure YAML file at `<stash>/tasks/<id>.yml`:
8
- *
9
- * ```yaml
10
- * schedule: "0 9 * * *"
11
- * # one of:
12
- * workflow: workflows/daily-backup
13
- * params:
14
- * region: us-east-1
15
- * timeoutMs: 3600000 # whole-run bound; omit for the unattended
16
- * # default, `null` to opt out entirely
17
- * maxSteps: 20 # optional run bounds, same as the
18
- * maxRetries: 1 # `akm workflow run` flags
19
- * # ...or:
20
- * prompt: agents/my-agent # asset ref
21
- * # ...or:
22
- * prompt: ./prompts/my-prompt.md # relative file path
23
- * # ...or:
24
- * prompt: | # inline multi-line prompt (block scalar)
25
- * Do the thing.
26
- * And the other thing.
27
- * # ...or:
28
- * command: akm improve --strategy quick --limit 25
29
- * enabled: true # default true
30
- * name: Daily backup
31
- * description: …
32
- * when_to_use: …
33
- * tags: [scheduled, backup]
34
- * redact: [ACME_DEPLOY_TOKEN] # optional: env var NAMES (never values) to
35
- * # scrub from this task's persisted log
36
- * ```
37
- *
38
- * Validation lives in {@link validateTaskDocument}. The parser enforces the
39
- * strict source shape; cron syntax and target reachability are
40
- * checked separately so callers can choose how strictly to surface errors.
41
- */
42
- import path from "node:path";
43
- import { parse as parseYaml } from "yaml";
44
- import { isFullRefInput } from "../core/asset/resolve-ref.js";
45
- import { UsageError } from "../core/errors.js";
46
- import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-params.js";
47
- import { WORKFLOW_ENV_VAR_NAME_PATTERN, WORKFLOW_MAX_RETRIES } from "../workflows/resource-limits.js";
48
- import { TASK_MAX_REDACT_NAMES, TASK_MAX_TIMEOUT_MS, TASK_SCHEMA_VERSION, } from "./schema.js";
49
- import { validateTaskId } from "./task-id.js";
50
- export function parseTaskDocument(input) {
51
- const { yaml, filePath } = input;
52
- const id = validateTaskId(input.id);
53
- let data;
54
- try {
55
- const parsed = parseYaml(yaml);
56
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
57
- throw new UsageError(`Task "${id}" YAML must be a mapping (key: value pairs). File: ${filePath}`, "INVALID_FLAG_VALUE");
58
- }
59
- data = parsed;
60
- }
61
- catch (err) {
62
- if (err instanceof UsageError)
63
- throw err;
64
- throw new UsageError(`Task "${id}" has invalid YAML: ${err instanceof Error ? err.message : String(err)}. File: ${filePath}`, "INVALID_FLAG_VALUE");
65
- }
66
- requireVersion(data, id, filePath);
67
- rejectUnknownKeys(data, id, filePath);
68
- const schedule = requireString(data.schedule, "schedule", filePath);
69
- if (!schedule) {
70
- throw new UsageError(`Task "${id}" is missing a schedule (YAML key "schedule"). File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
71
- }
72
- const enabled = readEnabled(data.enabled, filePath);
73
- const name = optionalString(data.name, "name", filePath);
74
- const description = optionalString(data.description, "description", filePath);
75
- const when_to_use = optionalString(data.when_to_use, "when_to_use", filePath);
76
- const tags = readTags(data.tags, filePath);
77
- const hasWorkflow = "workflow" in data && data.workflow !== "" && data.workflow != null;
78
- const hasPrompt = "prompt" in data && data.prompt !== "" && data.prompt != null;
79
- const hasCommand = "command" in data && data.command !== "" && data.command != null;
80
- const targetCount = [hasWorkflow, hasPrompt, hasCommand].filter(Boolean).length;
81
- if (targetCount > 1) {
82
- throw new UsageError(`Task "${id}" sets more than one of \`workflow\`, \`prompt\`, \`command\`; pick exactly one. File: ${filePath}`, "INVALID_FLAG_VALUE");
83
- }
84
- if (targetCount === 0) {
85
- throw new UsageError(`Task "${id}" must set one of \`workflow\`, \`prompt\`, or \`command\`. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
86
- }
87
- let target;
88
- if (hasWorkflow) {
89
- rejectTargetFields(data, ["params", "timeoutMs", "maxSteps", "maxRetries"], id, filePath);
90
- const ref = requireString(data.workflow, "workflow", filePath);
91
- if (!ref) {
92
- throw new UsageError(`Task "${id}" has empty \`workflow\`. File: ${filePath}`, "INVALID_FLAG_VALUE");
93
- }
94
- // The three run bounds `akm workflow run` takes as flags, declared in the
95
- // task file instead: an unattended run gets the same abort path the
96
- // interactive CLI has. `timeoutMs` left unset falls back to the runner's
97
- // default (see DEFAULT_WORKFLOW_TASK_TIMEOUT_MS); `null` opts out.
98
- const workflowTimeoutMs = readTimeout(data.timeoutMs, filePath);
99
- const maxSteps = readBoundedInteger(data.maxSteps, "maxSteps", 1, undefined, filePath);
100
- const maxRetries = readBoundedInteger(data.maxRetries, "maxRetries", 0, WORKFLOW_MAX_RETRIES, filePath);
101
- target = {
102
- kind: "workflow",
103
- ref,
104
- params: readParams(data.params, filePath),
105
- ...(workflowTimeoutMs !== undefined ? { timeoutMs: workflowTimeoutMs } : {}),
106
- ...(maxSteps !== undefined ? { maxSteps } : {}),
107
- ...(maxRetries !== undefined ? { maxRetries } : {}),
108
- };
109
- }
110
- else if (hasCommand) {
111
- rejectTargetFields(data, ["timeoutMs"], id, filePath);
112
- target = { kind: "command", cmd: readCommand(data.command, filePath, id) };
113
- }
114
- else {
115
- rejectTargetFields(data, ["engine", "model", "timeoutMs", "llm"], id, filePath);
116
- const promptRaw = requireString(data.prompt, "prompt", filePath);
117
- if (!promptRaw) {
118
- throw new UsageError(`Task "${id}" has empty \`prompt\`. File: ${filePath}`, "INVALID_FLAG_VALUE");
119
- }
120
- const engine = optionalString(data.engine, "engine", filePath);
121
- const model = optionalString(data.model, "model", filePath);
122
- const timeoutMs = readTimeout(data.timeoutMs, filePath);
123
- const llm = readLlmOverrides(data.llm, filePath);
124
- target = {
125
- kind: "prompt",
126
- source: resolvePromptSource(promptRaw, filePath, id),
127
- ...(engine ? { engine } : {}),
128
- ...(model ? { model } : {}),
129
- ...(timeoutMs !== undefined ? { timeoutMs } : {}),
130
- ...(llm ? { llm } : {}),
131
- };
132
- }
133
- const timeoutMs = hasCommand ? readTimeout(data.timeoutMs, filePath) : undefined;
134
- const redact = readRedactNames(data.redact, filePath);
135
- return {
136
- version: TASK_SCHEMA_VERSION,
137
- schemaVersion: TASK_SCHEMA_VERSION,
138
- id,
139
- schedule,
140
- enabled,
141
- target,
142
- ...(name ? { name } : {}),
143
- ...(description ? { description } : {}),
144
- ...(when_to_use ? { when_to_use } : {}),
145
- ...(tags ? { tags } : {}),
146
- source: { path: filePath },
147
- timeoutMs,
148
- ...(redact ? { redact } : {}),
149
- };
150
- }
151
- const TASK_KEYS = new Set([
152
- "version",
153
- "name",
154
- "description",
155
- "when_to_use",
156
- "tags",
157
- "schedule",
158
- "enabled",
159
- "workflow",
160
- "prompt",
161
- "command",
162
- "params",
163
- "engine",
164
- "model",
165
- "timeoutMs",
166
- "maxSteps",
167
- "maxRetries",
168
- "llm",
169
- "redact",
170
- ]);
171
- const SHARED_KEYS = new Set([
172
- "version",
173
- "name",
174
- "description",
175
- "when_to_use",
176
- "tags",
177
- "schedule",
178
- "enabled",
179
- // `redact:` is target-agnostic: every kind funnels through the same log sink.
180
- "redact",
181
- ]);
182
- function requireVersion(data, id, filePath) {
183
- if (data.version === TASK_SCHEMA_VERSION)
184
- return;
185
- const actual = data.version === undefined ? "missing" : JSON.stringify(data.version);
186
- throw new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task "${id}" uses task schema version ${actual}; version: 2 is required. File: ${filePath}`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "Use task schema version: 2.");
187
- }
188
- function rejectUnknownKeys(data, id, filePath) {
189
- const unknown = Object.keys(data).filter((key) => !TASK_KEYS.has(key));
190
- if (unknown.length > 0) {
191
- throw new UsageError(`Task "${id}" has unknown key(s): ${unknown.join(", ")}. File: ${filePath}`, "INVALID_FLAG_VALUE");
192
- }
193
- }
194
- function rejectTargetFields(data, allowed, id, filePath) {
195
- const forbidden = Object.keys(data).filter((key) => !SHARED_KEYS.has(key) && !allowed.includes(key) && !["workflow", "prompt", "command"].includes(key));
196
- if (forbidden.length > 0) {
197
- throw new UsageError(`Task "${id}" has field(s) not valid for this target: ${forbidden.join(", ")}. File: ${filePath}`, "INVALID_FLAG_VALUE");
198
- }
199
- }
200
- /**
201
- * Resolve a `prompt:` value into a {@link TaskPromptSource} variant.
202
- *
203
- * • "[bundle//]<subdir>/<name>" (asset ref) → asset
204
- * • "./foo.md", "../foo.md", "/abs" → file
205
- * • "C:\\abs" (Windows absolute) → file
206
- * • anything else (including colon text and block scalars) → inline text
207
- */
208
- function resolvePromptSource(raw, filePath, id) {
209
- const trimmed = raw.trim();
210
- if (!trimmed) {
211
- throw new UsageError(`Task "${id}" has empty \`prompt\`. File: ${filePath}`, "MISSING_REQUIRED_ARGUMENT");
212
- }
213
- if (trimmed.startsWith("./") || trimmed.startsWith("../") || path.isAbsolute(trimmed)) {
214
- return { kind: "file", path: trimmed };
215
- }
216
- if (/^[A-Za-z]:[\\/]/.test(trimmed)) {
217
- return { kind: "file", path: trimmed };
218
- }
219
- // Canonical 0.9.0 asset ref: `[bundle//]<stash-subdir>/<name>`. Detection is
220
- // delegated to the canonical parser (never a hand-rolled regex) — only a
221
- // conceptId whose leading segment is a real stash subdir counts as a ref; a
222
- // bare `word/word` inline prompt (no known subdir) stays inline text (D-R3).
223
- if (isFullRefInput(trimmed)) {
224
- return { kind: "asset", ref: trimmed };
225
- }
226
- return { kind: "inline", text: trimmed };
227
- }
228
- function optionalString(value, key, filePath) {
229
- if (value === undefined || value === null)
230
- return undefined;
231
- if (typeof value === "string")
232
- return value;
233
- throw new UsageError(`Key "${key}" must be a string. File: ${filePath}`, "INVALID_FLAG_VALUE");
234
- }
235
- function requireString(value, key, filePath) {
236
- const result = optionalString(value, key, filePath);
237
- if (result === undefined || result.length === 0) {
238
- throw new UsageError(`Key "${key}" must be a non-empty string. File: ${filePath}`, "INVALID_FLAG_VALUE");
239
- }
240
- return result;
241
- }
242
- function readEnabled(value, filePath) {
243
- if (value === undefined)
244
- return true;
245
- if (typeof value !== "boolean")
246
- throw new UsageError(`Key "enabled" must be a boolean. File: ${filePath}`, "INVALID_FLAG_VALUE");
247
- return value;
248
- }
249
- function readTags(value, filePath) {
250
- if (value === undefined || value === null)
251
- return undefined;
252
- if (Array.isArray(value)) {
253
- if (!value.every((entry) => typeof entry === "string" && entry.length > 0)) {
254
- throw new UsageError(`Key "tags" must be an array of non-empty strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
255
- }
256
- return value;
257
- }
258
- throw new UsageError(`Key "tags" must be an array of strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
259
- }
260
- function readCommand(value, filePath, id) {
261
- if (Array.isArray(value)) {
262
- if (!value.every((entry) => typeof entry === "string" && entry.length > 0)) {
263
- throw new UsageError(`Task "${id}" command array must contain non-empty strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
264
- }
265
- const parts = value;
266
- if (parts.length === 0) {
267
- throw new UsageError(`Task "${id}" has empty \`command\` array. File: ${filePath}`, "INVALID_FLAG_VALUE");
268
- }
269
- return parts;
270
- }
271
- if (typeof value === "string") {
272
- const parts = value.trim().split(/\s+/).filter(Boolean);
273
- if (parts.length === 0) {
274
- throw new UsageError(`Task "${id}" has empty \`command\`. File: ${filePath}`, "INVALID_FLAG_VALUE");
275
- }
276
- return parts;
277
- }
278
- throw new UsageError(`Key "command" must be a string or array of strings. File: ${filePath}`, "INVALID_FLAG_VALUE");
279
- }
280
- function readParams(value, filePath) {
281
- if (value === undefined || value === null)
282
- return {};
283
- if (typeof value === "object" && !Array.isArray(value)) {
284
- return value;
285
- }
286
- throw new UsageError(`Key "params" must be a mapping. File: ${filePath}`, "INVALID_FLAG_VALUE");
287
- }
288
- function readTimeout(value, filePath) {
289
- if (value === undefined)
290
- return undefined;
291
- if (value === null)
292
- return null;
293
- // The ceiling is `setTimeout`'s, not a policy: a larger delay overflows and
294
- // fires immediately, turning a generous timeout into an instant abort.
295
- if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= TASK_MAX_TIMEOUT_MS)
296
- return value;
297
- throw new UsageError(`Key "timeoutMs" must be an integer from 1 through ${TASK_MAX_TIMEOUT_MS}, or null. File: ${filePath}`, "INVALID_FLAG_VALUE");
298
- }
299
- /**
300
- * Read the `redact:` opt-in list — environment variable NAMES whose values are
301
- * scrubbed from this task's persisted log (#755).
302
- *
303
- * A value that looks like a secret rather than a name is rejected outright, not
304
- * silently accepted: a literal in a task file would be indexed, searchable and
305
- * printed verbatim by `akm show`, so accepting one would leak the secret
306
- * through a wider channel than the redaction closes.
307
- */
308
- function readRedactNames(value, filePath) {
309
- if (value === undefined || value === null)
310
- return undefined;
311
- const invalid = (detail) => {
312
- throw new UsageError(`Key "redact" ${detail}. File: ${filePath}`, "INVALID_FLAG_VALUE");
313
- };
314
- if (!Array.isArray(value))
315
- return invalid("must be a list of environment variable names");
316
- if (value.length > TASK_MAX_REDACT_NAMES) {
317
- return invalid(`accepts at most ${TASK_MAX_REDACT_NAMES} names (got ${value.length})`);
318
- }
319
- const names = [];
320
- for (const entry of value) {
321
- if (typeof entry !== "string" || !WORKFLOW_ENV_VAR_NAME_PATTERN.test(entry)) {
322
- return invalid(`takes environment variable NAMES only (matching ${WORKFLOW_ENV_VAR_NAME_PATTERN.source}), not values — ` +
323
- `got ${JSON.stringify(entry)}. A secret written here would be indexed and printed by \`akm show\``);
324
- }
325
- if (!names.includes(entry))
326
- names.push(entry);
327
- }
328
- return names.length > 0 ? names : undefined;
329
- }
330
- function readBoundedInteger(value, key, minimum, maximum, filePath) {
331
- if (value === undefined || value === null)
332
- return undefined;
333
- if (typeof value === "number" &&
334
- Number.isSafeInteger(value) &&
335
- value >= minimum &&
336
- (maximum === undefined || value <= maximum)) {
337
- return value;
338
- }
339
- const range = maximum === undefined ? `at least ${minimum}` : `from ${minimum} through ${maximum}`;
340
- throw new UsageError(`Key "${key}" must be an integer ${range}. File: ${filePath}`, "INVALID_FLAG_VALUE");
341
- }
342
- function readLlmOverrides(value, filePath) {
343
- if (value === undefined)
344
- return undefined;
345
- if (!value || typeof value !== "object" || Array.isArray(value)) {
346
- throw new UsageError(`Key "llm" must be a mapping. File: ${filePath}`, "INVALID_FLAG_VALUE");
347
- }
348
- const data = value;
349
- const allowed = new Set([
350
- "temperature",
351
- "maxTokens",
352
- "supportsJsonSchema",
353
- "extraParams",
354
- "contextLength",
355
- "enableThinking",
356
- ]);
357
- const unknown = Object.keys(data).filter((key) => !allowed.has(key));
358
- if (unknown.length)
359
- throw new UsageError(`Key "llm" has unknown field(s): ${unknown.join(", ")}. File: ${filePath}`, "INVALID_FLAG_VALUE");
360
- if (data.temperature !== undefined && (typeof data.temperature !== "number" || !Number.isFinite(data.temperature))) {
361
- throw new UsageError(`Key "llm.temperature" must be a finite number. File: ${filePath}`, "INVALID_FLAG_VALUE");
362
- }
363
- for (const key of ["maxTokens", "contextLength"]) {
364
- if (data[key] !== undefined && (!Number.isInteger(data[key]) || data[key] <= 0)) {
365
- throw new UsageError(`Key "llm.${key}" must be a positive integer. File: ${filePath}`, "INVALID_FLAG_VALUE");
366
- }
367
- }
368
- for (const key of ["supportsJsonSchema", "enableThinking"]) {
369
- if (data[key] !== undefined && typeof data[key] !== "boolean") {
370
- throw new UsageError(`Key "llm.${key}" must be a boolean. File: ${filePath}`, "INVALID_FLAG_VALUE");
371
- }
372
- }
373
- if (data.extraParams !== undefined) {
374
- const issue = validateExtraParams(data.extraParams)[0];
375
- if (issue) {
376
- throw new UsageError(`${formatExtraParamsIssue('Key "llm.extraParams"', issue)}. File: ${filePath}`, "INVALID_FLAG_VALUE");
377
- }
378
- }
379
- return data;
380
- }
@@ -1,123 +0,0 @@
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
- * Task asset schema. A task pairs a cron-style schedule with exactly one of:
6
- *
7
- * • a workflow target — executed via `runWorkflowSteps()`
8
- * • a prompt target — invoked via `runAgent()` against the configured
9
- * agent harness (e.g. `opencode run`)
10
- * • a command target — invoked directly via `Bun.spawn()`, no AI agent
11
- *
12
- * Tasks are stored as pure YAML files at `<stash>/tasks/<id>.yml`. Multi-line
13
- * inline prompts use a YAML block scalar (`prompt: |`).
14
- */
15
- import { parse as parseYaml } from "yaml";
16
- import { WORKFLOW_MAX_EXEC_PASS_ENV, WORKFLOW_MAX_TIMEOUT_MS } from "../workflows/resource-limits.js";
17
- export const TASK_SCHEMA_VERSION = 2;
18
- /** The ONE recognized on-disk task extension (spec §6 task row). */
19
- export const TASK_EXTENSION = ".yml";
20
- /**
21
- * The near-miss spelling. `.yaml` is NOT a task extension: the indexer's
22
- * `tasks` matcher (`indexer/walk/matchers.ts`) gates on `.yml`, so a
23
- * `tasks/<id>.yaml` file is never indexed, never scheduled, and never runs.
24
- * It is recognized HERE only so lint can say so out loud instead of walking
25
- * past it (issue #760).
26
- */
27
- export const TASK_NEAR_MISS_EXTENSION = ".yaml";
28
- /**
29
- * Largest expressible `timeoutMs` — `setTimeout`'s 32-bit signed ceiling
30
- * (2^31-1, ~24.8 days). A larger delay overflows and fires almost immediately,
31
- * which would silently abort a run seconds after it started instead of hours
32
- * later. One definition with the workflow bound (`WORKFLOW_MAX_TIMEOUT_MS`) —
33
- * it is a platform fact, not a per-surface policy. Mirrored as `maximum` on
34
- * `timeoutMs` in `schemas/akm-task.json`.
35
- */
36
- export const TASK_MAX_TIMEOUT_MS = WORKFLOW_MAX_TIMEOUT_MS;
37
- /**
38
- * Most names a task's `redact:` list may carry. Shares its bound with exec
39
- * units' `pass_env:` — both are "name the one or two the defaults miss", not a
40
- * way to declare the whole environment secret. Mirrored as `maxItems` on
41
- * `redact` in `schemas/akm-task.json`.
42
- */
43
- export const TASK_MAX_REDACT_NAMES = WORKFLOW_MAX_EXEC_PASS_ENV;
44
- /**
45
- * Lint-level shape problems for a parsed task YAML mapping: the field rules
46
- * `src/tasks/parser.ts` enforces at load time, phrased as diagnostics. The ONE
47
- * definition shared by both task linters (`core/adapter/adapters/akm-lint.ts`
48
- * and `akm-task-adapter.ts`) so lint and runtime cannot disagree — they
49
- * previously did, in both directions: lint demanded `enabled` (which the
50
- * parser defaults to `true`, so a runnable task was flagged) and never checked
51
- * `version` (which the parser hard-requires as `2`, so a lint-clean task died
52
- * at runtime with TASK_SCHEMA_VERSION_UNSUPPORTED). `schemas/akm-task.json`
53
- * agrees with the parser: `required: [version, schedule]`, `version:
54
- * {const: 2}`, `enabled` optional but boolean. Target-arity rules stay with
55
- * each caller (they legitimately differ: at-least-one vs exactly-one), but
56
- * what COUNTS as a target is shared — see {@link isPresentTarget}.
57
- */
58
- /**
59
- * Whether a task-target field counts as declared.
60
- *
61
- * The arity rules differ between the two linters, but the presence test must
62
- * not: the runtime parser treats `""` as absent, so a key-existence check let
63
- * `workflow: ""` lint clean and then die with MISSING_REQUIRED_ARGUMENT. An
64
- * array target (`command`) counts only when it has entries, for the same
65
- * reason. Kept beside {@link taskFieldProblems} so all three definitions of
66
- * "valid task" stay in one file.
67
- */
68
- export function isPresentTarget(value) {
69
- if (value === undefined || value === null)
70
- return false;
71
- if (typeof value === "string")
72
- return value.trim() !== "";
73
- if (Array.isArray(value))
74
- return value.length > 0;
75
- return true;
76
- }
77
- export function taskFieldProblems(data) {
78
- const problems = [];
79
- if (data.version !== TASK_SCHEMA_VERSION)
80
- problems.push(`version (must be ${TASK_SCHEMA_VERSION})`);
81
- if (typeof data.schedule !== "string" || data.schedule.trim() === "")
82
- problems.push("schedule");
83
- if ("enabled" in data && typeof data.enabled !== "boolean")
84
- problems.push("enabled (must be a boolean when present)");
85
- return problems;
86
- }
87
- /**
88
- * Parse a task YAML document into a plain mapping. Non-mapping documents (a
89
- * scalar, a sequence, an empty file) are NOT a parse failure — they parse fine
90
- * and simply carry no fields, which the field rules above already describe.
91
- *
92
- * The ONE parse used by all three task-lint surfaces (`commands/lint/index.ts`'s
93
- * akm sweep, the `akm` adapter's `validate`, and the `akm-task` adapter) so a
94
- * malformed file cannot be a finding on one surface and silence on another.
95
- */
96
- export function parseTaskYaml(raw) {
97
- try {
98
- const doc = parseYaml(raw);
99
- if (doc && typeof doc === "object" && !Array.isArray(doc))
100
- return { ok: true, data: doc };
101
- return { ok: true, data: {} };
102
- }
103
- catch (e) {
104
- // yaml's errors carry a multi-line source excerpt; keep the first line so
105
- // the diagnostic stays one readable finding.
106
- const message = (e instanceof Error ? e.message : String(e)).split("\n")[0]?.trim() ?? "unknown parse error";
107
- return { ok: false, data: {}, error: message };
108
- }
109
- }
110
- /** The `invalid-task-yaml` detail for a file whose YAML could not be parsed. */
111
- export function taskYamlParseDetail(error) {
112
- return `task YAML does not parse: ${error}`;
113
- }
114
- /**
115
- * The `invalid-task-yaml` detail for a task file using the `.yaml` near-miss
116
- * spelling. See {@link TASK_NEAR_MISS_EXTENSION} for why this is an error and
117
- * not a style nit.
118
- */
119
- export function taskExtensionDetail(relPath) {
120
- const base = relPath.replace(/\.yaml$/i, "");
121
- return (`task file uses the ${TASK_NEAR_MISS_EXTENSION} extension; akm recognizes tasks only as ` +
122
- `${TASK_EXTENSION}, so this file is never indexed or scheduled — rename it to ${base}${TASK_EXTENSION}.`);
123
- }
@@ -1,80 +0,0 @@
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
- * Validate a parsed {@link TaskDocument} for runnability.
6
- *
7
- * Enforces:
8
- * • the schedule is parseable and translates to the active backend
9
- * • the workflow ref resolves (workflow targets)
10
- * • the asset/file source exists (prompt targets)
11
- * • the selected engine resolves (prompt targets)
12
- *
13
- * Validation is deliberately split from parsing: callers that only inspect
14
- * task metadata (e.g. `akm show`) can skip these checks, while
15
- * `task add` and `task run` should always run them.
16
- */
17
- import fs from "node:fs";
18
- import path from "node:path";
19
- import { placementSpecFor } from "../core/asset/asset-placement.js";
20
- import { parseRefInput } from "../core/asset/resolve-ref.js";
21
- import { loadConfig } from "../core/config/config.js";
22
- import { NotFoundError } from "../core/errors.js";
23
- import { NO_ENGINE_MESSAGE_SUFFIX, withEngineFallback } from "../integrations/agent/engine-fallback.js";
24
- import { resolveEngine } from "../integrations/agent/engine-resolution.js";
25
- import { resolveAssetPath } from "../sources/resolve.js";
26
- import { parseSchedule } from "./schedule.js";
27
- export async function validateTaskDocument(task, options) {
28
- // Schedule must parse and translate.
29
- parseSchedule(task.schedule, options.backend);
30
- if (task.target.kind === "workflow") {
31
- const stashDir = options.stashDir;
32
- const ref = parseRefInput(task.target.ref);
33
- if (ref.type !== "workflow") {
34
- throw new NotFoundError(`Task "${task.id}" workflow target must be a workflow ref (got "${task.target.ref}").`, "WORKFLOW_NOT_FOUND");
35
- }
36
- await resolveAssetPath(stashDir, "workflow", ref.name);
37
- return;
38
- }
39
- if (task.target.kind !== "prompt") {
40
- return;
41
- }
42
- // Prompt target. Resolve the engine unconditionally — when no engine is
43
- // set on the task, defaults.engine is required. Catching this at
44
- // `task add` / `task sync` time is much more useful than failing only
45
- // when the OS scheduler fires.
46
- // Resolve against the SAME effective config the runner will use
47
- // (`tasks/runner.ts`), or validation rejects at `task add` / `task sync` a
48
- // task the runner would have happily executed via the implicit fallback.
49
- const { config } = withEngineFallback(loadConfig());
50
- const engine = task.target.engine ?? config.defaults?.engine;
51
- if (!engine)
52
- throw new NotFoundError(`Task "${task.id}" ${NO_ENGINE_MESSAGE_SUFFIX}`, "ASSET_NOT_FOUND");
53
- const resolved = resolveEngine(engine, config);
54
- if (task.target.llm !== undefined && resolved.kind !== "llm") {
55
- throw new NotFoundError(`Task "${task.id}" uses llm overrides with non-LLM engine "${engine}".`, "ASSET_NOT_FOUND");
56
- }
57
- const src = task.target.source;
58
- if (src.kind === "asset") {
59
- const stashDir = options.stashDir;
60
- const ref = parseRefInput(src.ref);
61
- // D11 — `parseRefInput` now also accepts an opaque adapter conceptId (a
62
- // ref whose leading segment is not an AKM placement dir), but
63
- // `resolveAssetPath` (src/sources/resolve.ts) is placement-dir-only: it
64
- // has no stash-subdir to route an opaque type through. Surface a clean
65
- // domain error here rather than let that helper crash on a missing
66
- // directory mapping.
67
- if (placementSpecFor(ref.type) === undefined) {
68
- throw new NotFoundError(`Task "${task.id}" prompt asset ref "${src.ref}" is not an AKM-placed asset — adapter-owned (opaque) prompt sources are not resolvable as task inputs yet.`, "ASSET_NOT_FOUND");
69
- }
70
- await resolveAssetPath(stashDir, ref.type, ref.name);
71
- }
72
- else if (src.kind === "file") {
73
- const taskDir = path.dirname(task.source.path);
74
- const resolved = path.isAbsolute(src.path) ? src.path : path.resolve(taskDir, src.path);
75
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
76
- throw new NotFoundError(`Prompt file not found for task "${task.id}": ${src.path} (resolved to ${resolved}).`, "FILE_NOT_FOUND");
77
- }
78
- }
79
- // inline source is always valid post-parse.
80
- }