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
@@ -2,12 +2,12 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  /**
5
- * The `akm-task` adapter akm 0.9.0 format-family work item (#46).
5
+ * The `akm-task` adapter for strict task-v3 `.yml` sources.
6
6
  *
7
7
  * A native akm task-YAML bundle (spec §6/§7). A `.yml` file derives
8
8
  * `type: task`; conceptId strips the `.yml` extension. Tasks are AKM-native
9
9
  * YAML, NOT OKF markdown concepts. Recognition does NOT gate on validity — an
10
- * invalid task (e.g. two targets) is still RECOGNIZED; the `invalid-task-yaml`
10
+ * invalid task (e.g. `uses` plus `run`) is still RECOGNIZED; the `invalid-task-yaml`
11
11
  * violation surfaces only in `validate`.
12
12
  *
13
13
  * `.yaml` is the one extension `validate` inspects but `recognize` refuses: it
@@ -16,13 +16,11 @@
16
16
  *
17
17
  * ── validate (spec §6 task validation column) ──
18
18
  *
19
- * A task must declare `version: 2`, a `schedule`, and EXACTLY ONE target
20
- * (`prompt` XOR `workflow` XOR `command`). `enabled` is OPTIONAL — the parser
21
- * defaults it to `true`, so an omitted field means an ACTIVE task — but must
22
- * be a boolean when present. The akm adapter's `TaskLinter` port checks
23
- * "at least one" target; the native task family here is STRICTER — declaring
24
- * two targets is `invalid-task-yaml`. So this adapter owns a purpose-built
25
- * one-target check rather than reusing that port.
19
+ * Validation enters the canonical task-v3 source parser. That parser owns the
20
+ * closed key sets, executable-selector XOR, scheduling-source XOR, hostile
21
+ * YAML policy, trigger classification, built-in action validation, bounds,
22
+ * and physical `working-directory` containment. The adapter only translates a
23
+ * parser failure into the format-family diagnostic shape.
26
24
  *
27
25
  * Conformance oracle (authored, DO NOT modify): fixture
28
26
  * `tests/fixtures/bundles/akm-task/` + goldens
@@ -30,14 +28,12 @@
30
28
  */
31
29
  import fs from "node:fs";
32
30
  import path from "node:path";
33
- import { isPresentTarget, parseTaskYaml, TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskFieldProblems, taskYamlParseDetail, } from "../../../tasks/schema.js";
31
+ import { parseTaskV3Yaml, TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskV3SourceErrorDetail, } from "../../../tasks/source-v3.js";
34
32
  import { hashContent } from "./shared.js";
35
33
  /** A native task bundle is single-component; its one component is `main`. */
36
34
  const COMPONENT_ID = "main";
37
35
  /** The task YAML extension (spec §6 task row). */
38
36
  const TASK_EXT = TASK_EXTENSION;
39
- /** The mutually-exclusive task target keys (exactly one required). */
40
- const TARGET_KEYS = ["prompt", "workflow", "command"];
41
37
  /** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
42
38
  const MAX_CONTENT_CHARS = 100_000;
43
39
  function toPosix(p) {
@@ -62,29 +58,7 @@ function recognize(c, file) {
62
58
  content: raw.length > MAX_CONTENT_CHARS ? raw.slice(0, MAX_CONTENT_CHARS) : raw,
63
59
  };
64
60
  }
65
- /**
66
- * The native `invalid-task-yaml` check: the shared field rules
67
- * ({@link taskFieldProblems} — see its doc for the lint-vs-parser
68
- * reconciliation story) plus this adapter's stricter EXACTLY-ONE-target rule.
69
- */
70
- function taskDiagnostics(relPath, data) {
71
- if (Object.keys(data).length === 0)
72
- return [];
73
- const problems = taskFieldProblems(data);
74
- // Shared presence rule (src/tasks/schema.ts): an empty string or empty array
75
- // is not a target, matching the runtime parser.
76
- const targets = TARGET_KEYS.filter((k) => isPresentTarget(data[k]));
77
- if (targets.length === 0)
78
- problems.push("exactly one target (prompt, workflow, or command)");
79
- else if (targets.length > 1)
80
- problems.push(`exactly one target — declares ${targets.length} (${targets.join(", ")})`);
81
- if (problems.length === 0)
82
- return [];
83
- return [
84
- { file: relPath, issue: "invalid-task-yaml", detail: `task field errors: ${problems.join("; ")}`, fixed: false },
85
- ];
86
- }
87
- async function validate(_c, changes, ctx) {
61
+ async function validate(c, changes, ctx) {
88
62
  const diagnostics = [];
89
63
  for (const change of changes) {
90
64
  if (change.op === "delete")
@@ -106,27 +80,25 @@ async function validate(_c, changes, ctx) {
106
80
  detail: taskExtensionDetail(relPath),
107
81
  fixed: false,
108
82
  });
83
+ continue;
84
+ }
85
+ try {
86
+ parseTaskV3Yaml({ yaml: raw, filePath: relPath, workspaceRoot: c.root });
109
87
  }
110
- const parsed = parseTaskYaml(raw);
111
- if (!parsed.ok) {
112
- // Distinguish "unparseable" from "empty": `taskDiagnostics` returns []
113
- // for an empty mapping, so collapsing a parse failure onto `{}` made a
114
- // broken task file lint clean.
88
+ catch (cause) {
115
89
  diagnostics.push({
116
90
  file: relPath,
117
91
  issue: "invalid-task-yaml",
118
- detail: taskYamlParseDetail(parsed.error),
92
+ detail: taskV3SourceErrorDetail(cause),
119
93
  fixed: false,
120
94
  });
121
- continue;
122
95
  }
123
- diagnostics.push(...taskDiagnostics(relPath, parsed.data));
124
96
  }
125
97
  return diagnostics;
126
98
  }
127
99
  export const akmTaskAdapter = {
128
100
  id: "akm-task",
129
- version: "0.9.0",
101
+ version: "0.9.2",
130
102
  // `.yaml` is listed as a COLLECTION hint only — `recognize` still gates on
131
103
  // `.yml`, so a `.yaml` file is never indexed as a task. Listing it is what
132
104
  // routes the near-miss file into `validate`, where it is reported instead of
@@ -134,6 +106,13 @@ export const akmTaskAdapter = {
134
106
  extensions: [TASK_EXT, TASK_NEAR_MISS_EXTENSION],
135
107
  recognize,
136
108
  validate,
109
+ readCandidates(c, conceptId) {
110
+ const posix = toPosix(conceptId).replace(/\.ya?ml$/i, "");
111
+ return [
112
+ { path: path.join(c.root, `${posix}.yml`), conceptId: posix },
113
+ { path: path.join(c.root, `${posix}.yaml`), conceptId: posix },
114
+ ];
115
+ },
137
116
  /** A task places to `<conceptId>.yml`; an already-suffixed conceptId is idempotent. */
138
117
  placeNew(c, conceptId) {
139
118
  const posix = toPosix(conceptId);
@@ -144,9 +123,9 @@ export const akmTaskAdapter = {
144
123
  return ["."];
145
124
  },
146
125
  /**
147
- * Install-time probe (§1.2): a root holding a top-level `.yml` file that
148
- * parses as a task (a `schedule` key). The task-shape probe keeps it disjoint
149
- * from non-task YAML.
126
+ * Install-time probe (§1.2): a root holding a top-level, valid task-v3
127
+ * `.yml` file. The full parser keeps this disjoint from unrelated YAML and
128
+ * prevents probe semantics from drifting from validation semantics.
150
129
  */
151
130
  looksLikeRoot(root) {
152
131
  let entries;
@@ -166,9 +145,13 @@ export const akmTaskAdapter = {
166
145
  catch {
167
146
  continue;
168
147
  }
169
- const { data } = parseTaskYaml(raw);
170
- if (typeof data.schedule === "string" && data.schedule.trim() !== "")
148
+ try {
149
+ parseTaskV3Yaml({ yaml: raw, filePath: entry.name, workspaceRoot: root });
171
150
  return true;
151
+ }
152
+ catch {
153
+ // Continue probing the remaining top-level .yml files.
154
+ }
172
155
  }
173
156
  return false;
174
157
  },
@@ -4,13 +4,9 @@
4
4
  /**
5
5
  * The `akm-workflow` adapter — akm 0.9.0 format-family work item (#46).
6
6
  *
7
- * A native akm workflow bundle (spec §6/§7). One format now (workflow-format-
8
- * unification): `.md` files, orchestration graph in frontmatter, per-step
9
- * prose in the body. Recognition is frontmatter `type: workflow`, or since
10
- * this adapter's whole domain IS workflows — simply residing under this
11
- * bundle's root; no content sniffing. conceptId strips the `.md` extension. A
12
- * plain `.md` that opts out via an explicit non-workflow `type:` frontmatter
13
- * key is abstained on.
7
+ * A native akm workflow bundle. AKM Markdown and the strict local GitHub-shaped
8
+ * `.yml` subset are peer source formats. Both compile through the source IR;
9
+ * neither source is rewritten or projected onto the other.
14
10
  *
15
11
  * ── validate (spec §6 workflow row) ──
16
12
  *
@@ -25,13 +21,14 @@
25
21
  */
26
22
  import fs from "node:fs";
27
23
  import path from "node:path";
24
+ import { looksLikeGithubWorkflowSource } from "../../../workflows/source-ir/compile.js";
28
25
  import { parseFrontmatter } from "../../asset/frontmatter.js";
29
- import { perTypeValidateChecks } from "./akm-lint.js";
26
+ import { perTypeValidateChecks, workflowYamlSourceDiagnostics } from "./akm-lint.js";
30
27
  import { hashContent, nonEmptyString, readTags, runBaseValidateChecks } from "./shared.js";
31
28
  /** A native workflow bundle is single-component; its one component is `main`. */
32
29
  const COMPONENT_ID = "main";
33
- /** One recognized workflow extension now (workflow-format-unification): `.md`. */
34
- const WORKFLOW_EXTS = new Set([".md"]);
30
+ /** The two authoritative workflow source formats. `.yaml` is deliberately not accepted. */
31
+ const WORKFLOW_EXTS = new Set([".md", ".yml"]);
35
32
  /** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
36
33
  const MAX_CONTENT_CHARS = 100_000;
37
34
  function toPosix(p) {
@@ -39,7 +36,7 @@ function toPosix(p) {
39
36
  }
40
37
  /** Strip the recognized workflow extension from a component-root-relative path → conceptId. */
41
38
  function conceptIdOf(relPath) {
42
- return toPosix(relPath).replace(/\.md$/i, "");
39
+ return toPosix(relPath).replace(/\.(?:md|yml)$/i, "");
43
40
  }
44
41
  /** `README.md` (case-insensitive) is documentation, never the typed asset — mirrors the akm matcher stack's D-R6 reserved-file exclusion (`TYPED_DIR_DOC_FILES`). */
45
42
  function isReservedDocFile(fileName) {
@@ -63,14 +60,14 @@ function recognize(c, file) {
63
60
  if (isReservedDocFile(file.fileName))
64
61
  return null;
65
62
  const raw = file.content();
66
- if (!isWorkflowFile(raw))
63
+ if (file.ext === ".md" && !isWorkflowFile(raw))
67
64
  return null;
68
65
  const conceptId = conceptIdOf(file.relPath);
69
66
  const name = conceptId.split("/").pop() ?? conceptId;
70
67
  const parsed = parseFrontmatter(raw);
71
- const description = nonEmptyString(parsed.data.description);
72
- const body = parsed.content;
73
- const tags = readTags(parsed.data.tags);
68
+ const description = file.ext === ".md" ? nonEmptyString(parsed.data.description) : undefined;
69
+ const body = file.ext === ".md" ? parsed.content : raw;
70
+ const tags = file.ext === ".md" ? readTags(parsed.data.tags) : undefined;
74
71
  const doc = {
75
72
  ref: `${c.id}//${conceptId}`,
76
73
  bundle: c.id,
@@ -98,9 +95,15 @@ async function validate(c, changes, ctx) {
98
95
  if (typeof raw !== "string")
99
96
  continue;
100
97
  const ext = path.extname(change.path).toLowerCase();
101
- if (!WORKFLOW_EXTS.has(ext) || isReservedDocFile(path.basename(change.path)) || !isWorkflowFile(raw))
98
+ if (!WORKFLOW_EXTS.has(ext) || isReservedDocFile(path.basename(change.path)))
102
99
  continue;
103
100
  const relPath = toPosix(change.path);
101
+ if (ext === ".yml") {
102
+ diagnostics.push(...workflowYamlSourceDiagnostics(relPath, raw, relPath, c.root).errors);
103
+ continue;
104
+ }
105
+ if (!isWorkflowFile(raw))
106
+ continue;
104
107
  const p = parseFrontmatter(raw);
105
108
  const parsed = { data: p.data, content: p.content, frontmatter: p.frontmatter };
106
109
  diagnostics.push(...(await runBaseValidateChecks(relPath, parsed, c.root, ctx)));
@@ -141,21 +144,33 @@ function hasTopLevelWorkflowFile(root, entries) {
141
144
  catch {
142
145
  continue;
143
146
  }
144
- if (parseFrontmatter(raw).data.type === "workflow")
147
+ if (ext === ".yml" && looksLikeGithubWorkflowSource(raw))
148
+ return true;
149
+ if (ext === ".md" && parseFrontmatter(raw).data.type === "workflow")
145
150
  return true;
146
151
  }
147
152
  return false;
148
153
  }
149
154
  export const akmWorkflowAdapter = {
150
155
  id: "akm-workflow",
151
- version: "0.9.0",
152
- extensions: [".md"],
156
+ version: "0.9.2",
157
+ extensions: [".md", ".yml"],
153
158
  recognize,
154
159
  validate,
155
- /** Markdown placement; an explicit `.md` conceptId short-circuits to that extension. */
160
+ readCandidates(c, conceptId) {
161
+ const posix = toPosix(conceptId);
162
+ const canonical = posix.replace(/\.(?:md|yml)$/i, "");
163
+ return /\.(?:md|yml)$/i.test(posix)
164
+ ? [{ path: path.join(c.root, posix), conceptId: canonical }]
165
+ : [
166
+ { path: path.join(c.root, `${posix}.md`), conceptId: canonical },
167
+ { path: path.join(c.root, `${posix}.yml`), conceptId: canonical },
168
+ ];
169
+ },
170
+ /** Markdown remains the default; an explicit `.md`/`.yml` suffix is preserved. */
156
171
  placeNew(c, conceptId) {
157
172
  const posix = toPosix(conceptId);
158
- if (/\.md$/i.test(posix))
173
+ if (/\.(?:md|yml)$/i.test(posix))
159
174
  return path.join(c.root, posix);
160
175
  return path.join(c.root, `${posix}.md`);
161
176
  },
@@ -164,8 +179,8 @@ export const akmWorkflowAdapter = {
164
179
  return ["."];
165
180
  },
166
181
  /**
167
- * Install-time probe (§1.2): a root holding a workflow-shaped `.md` file at
168
- * top level (frontmatter `type: workflow` or no `type:` at all).
182
+ * Install-time probe (§1.2): a root holding an explicitly typed Markdown
183
+ * workflow or a complete GitHub-shaped YAML workflow at top level.
169
184
  */
170
185
  looksLikeRoot(root) {
171
186
  let entries;
@@ -96,6 +96,19 @@ function scanKeyNames(raw) {
96
96
  }
97
97
  return keys;
98
98
  }
99
+ function conceptIdForPath(type, relativePath) {
100
+ const posix = toPosix(relativePath);
101
+ if (type === "secret")
102
+ return posix;
103
+ const stripped = posix.replace(/\.env$/i, "");
104
+ return stripped.endsWith("/") ? `${stripped}default` : stripped;
105
+ }
106
+ function recognizePathCandidates(_c, file) {
107
+ const type = classify(file.relPath);
108
+ if (type === null || hasSensitiveMarker(file.absPath, type))
109
+ return [];
110
+ return [conceptIdForPath(type, file.relPath)];
111
+ }
99
112
  function recognize(c, file) {
100
113
  const type = classify(file.relPath);
101
114
  if (type === null)
@@ -106,7 +119,7 @@ function recognize(c, file) {
106
119
  const raw = file.content();
107
120
  if (type === "env") {
108
121
  // env: strip `.env`; surface KEY NAMES only (never values/comments/content).
109
- const conceptId = posix.replace(/\.env$/i, "");
122
+ const conceptId = conceptIdForPath(type, posix);
110
123
  const name = (conceptId.split("/").pop() ?? conceptId) || "default";
111
124
  const keys = scanKeyNames(raw);
112
125
  const doc = {
@@ -159,7 +172,23 @@ export const dotenvAdapter = {
159
172
  version: "0.9.0",
160
173
  extensions: [".env"],
161
174
  recognize,
175
+ recognizePathCandidates,
162
176
  validate,
177
+ readCandidates(c, conceptId) {
178
+ const posix = toPosix(conceptId);
179
+ const slash = posix.indexOf("/");
180
+ if (slash <= 0)
181
+ return [];
182
+ const head = posix.slice(0, slash);
183
+ const rest = posix.slice(slash + 1);
184
+ const type = typeForStashDir(head);
185
+ if ((type !== "env" && type !== "secret") || rest.length === 0)
186
+ return [];
187
+ const primary = assetPathForName(type, path.join(c.root, head), rest);
188
+ return (type === "env"
189
+ ? [primary, primary.replace(/\.env$/i, ".sensitive")]
190
+ : [primary, `${primary}.sensitive`, `${primary}.lock`]).map((candidatePath) => ({ path: candidatePath, conceptId: posix }));
191
+ },
163
192
  /**
164
193
  * env places to `env/<name>.env`, secret to `secrets/<name>` (identity join,
165
194
  * no extension logic) — reusing the shared `assetPathForName` so the env
@@ -102,6 +102,17 @@ export const genericFilesAdapter = {
102
102
  extensions: [".md", ".txt"],
103
103
  recognize,
104
104
  validate,
105
+ readCandidates(c, conceptId) {
106
+ const posix = toPosix(conceptId);
107
+ const extension = path.extname(posix).toLowerCase();
108
+ const documentCandidates = [...DOCUMENT_EXTENSIONS].map((candidateExtension) => ({
109
+ path: path.join(c.root, `${posix}${candidateExtension}`),
110
+ conceptId: posix,
111
+ }));
112
+ return DOCUMENT_EXTENSIONS.has(extension)
113
+ ? documentCandidates
114
+ : [{ path: path.join(c.root, posix), conceptId: posix }, ...documentCandidates];
115
+ },
105
116
  /**
106
117
  * IDENTITY placement (open-question-5): a conceptId that carries an extension
107
118
  * (`script`/`file`) places to itself; an extension-less conceptId (a
@@ -69,12 +69,3 @@ export const BUILTIN_ADAPTERS = Object.freeze([
69
69
  // Explicit-config fallback (never auto-selected) — last.
70
70
  genericFilesAdapter,
71
71
  ]);
72
- /**
73
- * DEPRECATED no-op retained for test back-compat only. The built-in set is now
74
- * the static, frozen {@link BUILTIN_ADAPTERS}, exposed by the registry at
75
- * module load (normative §12.6), so there is nothing to register. New code MUST
76
- * NOT call this — `getAdapters()` / `adapterForId()` are always populated.
77
- */
78
- export function registerBuiltinAdapters() {
79
- // intentionally empty — BUILTIN_ADAPTERS is static; see the doc comment above.
80
- }
@@ -395,6 +395,10 @@ export const llmWikiAdapter = {
395
395
  extensions: [".md"],
396
396
  recognize,
397
397
  validate,
398
+ readCandidates(c, conceptId) {
399
+ const canonical = toPosix(conceptId).replace(/\.md$/i, "");
400
+ return [{ path: path.join(c.root, `${canonical}.md`), conceptId: canonical }];
401
+ },
398
402
  /** `<c.root>/<conceptId>.md` — the conceptId already carries its `pages/`/`raw/` prefix (placement golden). */
399
403
  placeNew(c, conceptId) {
400
404
  return path.join(c.root, `${conceptId}.md`);
@@ -347,6 +347,10 @@ export const okfAdapter = {
347
347
  extensions: [".md"],
348
348
  recognize,
349
349
  validate,
350
+ readCandidates(c, conceptId) {
351
+ const canonical = conceptId.replace(/\\/g, "/").replace(/\.md$/i, "");
352
+ return [{ path: path.join(c.root, `${canonical}.md`), conceptId: canonical }];
353
+ },
350
354
  /** OKF concepts live anywhere under the component root (§5). */
351
355
  directoryList(_c) {
352
356
  return ["."];
@@ -9,10 +9,7 @@
9
9
  * https://opencode.ai/docs/{commands,agents,skills,rules}/): a root `AGENTS.md`
10
10
  * → `instruction`, `commands/*.md` → `command`, `agents/*.md` → `agent`,
11
11
  * `skills/<name>/SKILL.md` → `skill`, and ABSTAINS on `opencode.json` runtime
12
- * config. Per open-question-6 (RESOLVED, accept BOTH forms) the SINGULAR
13
- * `command/`/`agent/`/`skill/` dirs are accepted as backwards-compat aliases on
14
- * READ (the conceptId preserves the on-disk spelling, e.g. `command/legacy`);
15
- * WRITES normalize to the canonical plural (handled in the shared codec).
12
+ * config. Only OpenCode's canonical plural tool directories are accepted.
16
13
  *
17
14
  * All behavior lives in the shared tool-dir codec ({@link makeToolDirAdapter});
18
15
  * this module supplies only the `.opencode` layout + the install-time probe.
@@ -29,12 +26,12 @@ const LAYOUT = {
29
26
  componentId: ".opencode",
30
27
  instructionFile: "AGENTS.md",
31
28
  instructionConceptId: "AGENTS",
32
- commandDirs: new Set(["commands", "command"]),
33
- agentDirs: new Set(["agents", "agent"]),
34
- skillDirs: new Set(["skills", "skill"]),
29
+ commandDirs: new Set(["commands"]),
30
+ agentDirs: new Set(["agents"]),
31
+ skillDirs: new Set(["skills"]),
35
32
  };
36
33
  const CONFIG_FILES = ["opencode.json", "opencode.jsonc"];
37
- const TOOL_DIRS = ["commands", "command", "agents", "agent", "skills", "skill"];
34
+ const TOOL_DIRS = ["commands", "agents", "skills"];
38
35
  function fileExists(p) {
39
36
  try {
40
37
  return fs.existsSync(p);
@@ -8,10 +8,8 @@
8
8
  * open AKM `type` vocabulary (spec §6/§7): a root instruction file
9
9
  * (CLAUDE.md / AGENTS.md) → `instruction`, `commands/*.md` → `command`,
10
10
  * `agents/*.md` → `agent`, `skills/<name>/SKILL.md` → `skill` (item = the dir).
11
- * They differ only in three parameters carried on {@link ToolDirLayout}:
11
+ * They differ only in two parameters carried on {@link ToolDirLayout}:
12
12
  * - the instruction basename + its concept id,
13
- * - the accepted subdir spellings (opencode accepts the SINGULAR
14
- * `command/`/`agent/`/`skill/` aliases per open-question-6, claude does not),
15
13
  * - the adapter id + component id.
16
14
  *
17
15
  * The SKILL.md codec is shared with the `agent-skills` adapter as functions
@@ -49,7 +47,9 @@
49
47
  * Node builtins.
50
48
  */
51
49
  import path from "node:path";
50
+ import { createAdapterExtensions, } from "../../../execution/source.js";
52
51
  import { parseFrontmatter } from "../../asset/frontmatter.js";
52
+ import { executionDefaultsFromFrontmatter, renderMarkdownExecutionSource } from "../execution-source.js";
53
53
  import { skillDirectoryDiagnostics } from "./akm-lint.js";
54
54
  import { hashContent, nonEmptyString, readTags, runBaseValidateChecks } from "./shared.js";
55
55
  /** OKF reserved structural files (D-R6) — excluded at every depth, case-insensitive. */
@@ -85,7 +85,7 @@ function classify(relPath, layout) {
85
85
  // skill: <skillDir>/<name>/SKILL.md — the item is the DIRECTORY. Any other
86
86
  // file under a skill dir (bundled resources) is part of the item, not a concept.
87
87
  if (layout.skillDirs.has(head)) {
88
- if (segs.length >= 3 && base === SKILL_MANIFEST) {
88
+ if (segs.length === 3 && base === SKILL_MANIFEST) {
89
89
  return { type: "skill", conceptId: `${segs[0]}/${segs[1]}`, name: segs[1] };
90
90
  }
91
91
  return null;
@@ -155,6 +155,25 @@ export function placeNewToolDir(layout, c, conceptId) {
155
155
  }
156
156
  return path.join(c.root, `${posix}.md`);
157
157
  }
158
+ /** List native read spellings. */
159
+ export function readCandidatesToolDir(layout, c, conceptId) {
160
+ const posix = toPosix(conceptId);
161
+ if (posix === layout.instructionConceptId) {
162
+ return [{ path: path.join(c.root, layout.instructionFile), conceptId: posix }];
163
+ }
164
+ const segs = posix.split("/").filter((segment) => segment.length > 0);
165
+ const head = segs[0];
166
+ const rest = segs.slice(1).join("/");
167
+ if (!head || !rest)
168
+ return [];
169
+ if (layout.skillDirs.has(head)) {
170
+ return segs.length === 2 ? [{ path: path.join(c.root, head, rest, SKILL_MANIFEST), conceptId: posix }] : [];
171
+ }
172
+ if (layout.commandDirs.has(head) || layout.agentDirs.has(head)) {
173
+ return [{ path: path.join(c.root, head, `${rest}.md`), conceptId: posix }];
174
+ }
175
+ return [];
176
+ }
158
177
  /** LENIENT command/agent check: a diagnostic only when NEITHER a name/description NOR a type-shaped signal is present. */
159
178
  function nameOrSignalDiagnostics(type, relPath, data, body) {
160
179
  if (nonEmptyString(data.name) !== undefined || nonEmptyString(data.description) !== undefined)
@@ -187,8 +206,7 @@ export async function validateToolDir(layout, c, changes, ctx) {
187
206
  // The one coded skill check (missing-skill-md) fires on ANY change under a
188
207
  // `<skillDir>/<name>/…` package (self-gated + deduped), even a bundled
189
208
  // resource — mirrors the akm adapter's per-change SkillLinter.lintDirectory
190
- // pass. `layout.skillDirs` is passed so opencode's singular `skill/` alias
191
- // is checked identically to `skills/` (issue #774).
209
+ // pass.
192
210
  diagnostics.push(...(await skillDirectoryDiagnostics(relPath, seenSkillDirs, ctx, layout.skillDirs)));
193
211
  const cls = classify(change.path, layout);
194
212
  if (cls === null)
@@ -204,6 +222,43 @@ export async function validateToolDir(layout, c, changes, ctx) {
204
222
  }
205
223
  return diagnostics;
206
224
  }
225
+ function nativeExecutionExtensions(layout, cls, data) {
226
+ const values = {};
227
+ if (layout.adapterId === "claude" && cls.type === "command" && Object.hasOwn(data, "argument-hint")) {
228
+ if (typeof data["argument-hint"] !== "string") {
229
+ throw new TypeError("frontmatter.argument-hint must be a string");
230
+ }
231
+ values.argumentHint = data["argument-hint"];
232
+ }
233
+ if (layout.adapterId === "opencode" && Object.hasOwn(data, "mode")) {
234
+ if (typeof data.mode !== "string")
235
+ throw new TypeError("frontmatter.mode must be a string");
236
+ values.mode = data.mode;
237
+ }
238
+ return Object.keys(values).length > 0 ? createAdapterExtensions(layout.adapterId, values) : undefined;
239
+ }
240
+ /** Translate a native Claude/OpenCode command or agent without exposing raw frontmatter. */
241
+ export function renderToolDirExecutionSource(layout, c, file) {
242
+ const cls = classify(file.relPath, layout);
243
+ if (cls?.type !== "command" && cls?.type !== "agent")
244
+ return null;
245
+ const raw = file.content();
246
+ return renderMarkdownExecutionSource({
247
+ kind: cls.type === "command" ? "command" : "persona",
248
+ raw,
249
+ identity: {
250
+ ref: `${c.id}//${cls.conceptId}`,
251
+ bundle: c.id,
252
+ adapter: layout.adapterId,
253
+ file: file.relPath,
254
+ },
255
+ defaults: (data) => executionDefaultsFromFrontmatter(data, {
256
+ kind: cls.type === "command" ? "command" : "persona",
257
+ toolsKeys: layout.adapterId === "claude" && cls.type === "command" ? ["allowed-tools", "tools"] : ["tools"],
258
+ }),
259
+ extensions: (data) => nativeExecutionExtensions(layout, cls, data),
260
+ });
261
+ }
207
262
  /** Build the concrete `BundleAdapter` from a layout + a `looksLikeRoot` probe (claude/opencode share everything else). */
208
263
  export function makeToolDirAdapter(layout, looksLikeRoot) {
209
264
  return {
@@ -211,7 +266,9 @@ export function makeToolDirAdapter(layout, looksLikeRoot) {
211
266
  version: "0.9.0",
212
267
  extensions: [".md"],
213
268
  recognize: (c, file) => recognizeToolDir(layout, c, file),
269
+ renderExecutionSource: (c, file) => renderToolDirExecutionSource(layout, c, file),
214
270
  validate: (c, changes, ctx) => validateToolDir(layout, c, changes, ctx),
271
+ readCandidates: (c, conceptId) => readCandidatesToolDir(layout, c, conceptId),
215
272
  placeNew: (c, conceptId) => placeNewToolDir(layout, c, conceptId),
216
273
  directoryList: () => [CANONICAL_COMMAND_DIR, CANONICAL_AGENT_DIR, CANONICAL_SKILL_DIR],
217
274
  looksLikeRoot,
@@ -129,6 +129,10 @@ export const websiteSnapshotAdapter = {
129
129
  extensions: [".md"],
130
130
  recognize,
131
131
  validate,
132
+ readCandidates(c, conceptId) {
133
+ const canonical = toPosix(conceptId).replace(/\.md$/i, "");
134
+ return [{ path: path.join(c.root, PAGES_PREFIX, `${canonical}.md`), conceptId: canonical }];
135
+ },
132
136
  // No placeNew: the snapshot is READ-ONLY (Mode A). Export (Mode B) routes
133
137
  // content through the DESTINATION adapter, which owns placement.
134
138
  /**