akm-cli 0.6.1 → 0.7.0

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 (333) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{cli.js → src/cli.js} +712 -34
  3. package/dist/{commands → src/commands}/config-cli.js +47 -4
  4. package/dist/src/commands/distill.js +283 -0
  5. package/dist/src/commands/events.js +108 -0
  6. package/dist/src/commands/history.js +191 -0
  7. package/dist/{commands → src/commands}/installed-stashes.js +1 -1
  8. package/dist/src/commands/proposal.js +119 -0
  9. package/dist/src/commands/propose.js +171 -0
  10. package/dist/src/commands/reflect.js +193 -0
  11. package/dist/{commands → src/commands}/registry-search.js +71 -7
  12. package/dist/{commands → src/commands}/remember.js +12 -0
  13. package/dist/{commands → src/commands}/search.js +104 -4
  14. package/dist/{commands → src/commands}/self-update.js +4 -3
  15. package/dist/{commands → src/commands}/show.js +73 -0
  16. package/dist/{commands → src/commands}/source-add.js +5 -1
  17. package/dist/{commands → src/commands}/source-manage.js +7 -1
  18. package/dist/{core → src/core}/asset-ref.js +5 -5
  19. package/dist/{core → src/core}/asset-spec.js +12 -0
  20. package/dist/{core → src/core}/common.js +1 -1
  21. package/dist/{core → src/core}/config.js +203 -121
  22. package/dist/{core → src/core}/errors.js +4 -0
  23. package/dist/src/core/events.js +239 -0
  24. package/dist/src/core/lesson-lint.js +86 -0
  25. package/dist/src/core/proposals.js +406 -0
  26. package/dist/src/core/warn.js +72 -0
  27. package/dist/{core → src/core}/write-source.js +80 -5
  28. package/dist/{indexer → src/indexer}/db-search.js +114 -24
  29. package/dist/{indexer → src/indexer}/db.js +76 -23
  30. package/dist/{indexer → src/indexer}/file-context.js +0 -3
  31. package/dist/src/indexer/graph-boost.js +179 -0
  32. package/dist/src/indexer/graph-extraction.js +212 -0
  33. package/dist/{indexer → src/indexer}/indexer.js +88 -7
  34. package/dist/{indexer → src/indexer}/matchers.js +1 -1
  35. package/dist/src/indexer/memory-inference.js +263 -0
  36. package/dist/{indexer → src/indexer}/metadata.js +111 -3
  37. package/dist/{indexer → src/indexer}/search-source.js +4 -2
  38. package/dist/src/integrations/agent/config.js +292 -0
  39. package/dist/src/integrations/agent/detect.js +94 -0
  40. package/dist/src/integrations/agent/index.js +17 -0
  41. package/dist/src/integrations/agent/profiles.js +65 -0
  42. package/dist/src/integrations/agent/prompts.js +167 -0
  43. package/dist/src/integrations/agent/spawn.js +272 -0
  44. package/dist/{integrations → src/integrations}/github.js +9 -3
  45. package/dist/{integrations → src/integrations}/lockfile.js +0 -26
  46. package/dist/{llm → src/llm}/client.js +33 -2
  47. package/dist/{llm → src/llm}/embedders/remote.js +37 -3
  48. package/dist/src/llm/feature-gate.js +108 -0
  49. package/dist/src/llm/graph-extract.js +107 -0
  50. package/dist/src/llm/index-passes.js +35 -0
  51. package/dist/src/llm/memory-infer.js +86 -0
  52. package/dist/{output → src/output}/cli-hints.js +15 -2
  53. package/dist/{output → src/output}/renderers.js +63 -2
  54. package/dist/src/output/shapes.js +523 -0
  55. package/dist/src/output/text.js +1116 -0
  56. package/dist/{registry → src/registry}/build-index.js +19 -8
  57. package/dist/{registry → src/registry}/factory.js +0 -8
  58. package/dist/{registry → src/registry}/providers/static-index.js +6 -3
  59. package/dist/{registry → src/registry}/resolve.js +68 -2
  60. package/dist/{setup → src/setup}/setup.js +52 -5
  61. package/dist/{sources → src/sources}/providers/git.js +7 -15
  62. package/dist/{wiki → src/wiki}/wiki.js +54 -6
  63. package/dist/{workflows → src/workflows}/runs.js +37 -3
  64. package/dist/tests/add-website-source.test.js +119 -0
  65. package/dist/tests/agent/agent-config-loader.test.js +70 -0
  66. package/dist/tests/agent/agent-config.test.js +221 -0
  67. package/dist/tests/agent/agent-detect.test.js +100 -0
  68. package/dist/tests/agent/agent-spawn.test.js +234 -0
  69. package/dist/tests/agent-output.test.js +186 -0
  70. package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
  71. package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
  72. package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
  73. package/dist/tests/asset-ref.test.js +192 -0
  74. package/dist/tests/asset-registry.test.js +103 -0
  75. package/dist/tests/asset-spec.test.js +241 -0
  76. package/dist/tests/bench/attribution.test.js +996 -0
  77. package/dist/tests/bench/cleanup-sigint.test.js +83 -0
  78. package/dist/tests/bench/cleanup.js +234 -0
  79. package/dist/tests/bench/cleanup.test.js +166 -0
  80. package/dist/tests/bench/cli.js +1018 -0
  81. package/dist/tests/bench/cli.test.js +445 -0
  82. package/dist/tests/bench/compare.test.js +556 -0
  83. package/dist/tests/bench/corpus.js +317 -0
  84. package/dist/tests/bench/corpus.test.js +258 -0
  85. package/dist/tests/bench/doctor.js +525 -0
  86. package/dist/tests/bench/driver.js +401 -0
  87. package/dist/tests/bench/driver.test.js +584 -0
  88. package/dist/tests/bench/environment.js +233 -0
  89. package/dist/tests/bench/environment.test.js +199 -0
  90. package/dist/tests/bench/evolve-metrics.js +179 -0
  91. package/dist/tests/bench/evolve-metrics.test.js +187 -0
  92. package/dist/tests/bench/evolve.js +647 -0
  93. package/dist/tests/bench/evolve.test.js +624 -0
  94. package/dist/tests/bench/failure-modes.test.js +349 -0
  95. package/dist/tests/bench/feedback-integrity.test.js +457 -0
  96. package/dist/tests/bench/leakage.test.js +228 -0
  97. package/dist/tests/bench/learning-curve.test.js +134 -0
  98. package/dist/tests/bench/metrics.js +2395 -0
  99. package/dist/tests/bench/metrics.test.js +1150 -0
  100. package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
  101. package/dist/tests/bench/opencode-config.js +194 -0
  102. package/dist/tests/bench/opencode-config.test.js +370 -0
  103. package/dist/tests/bench/report.js +1885 -0
  104. package/dist/tests/bench/report.test.js +1038 -0
  105. package/dist/tests/bench/run-config.js +355 -0
  106. package/dist/tests/bench/run-config.test.js +298 -0
  107. package/dist/tests/bench/run-curate-test.js +32 -0
  108. package/dist/tests/bench/run-failing-tasks.js +56 -0
  109. package/dist/tests/bench/run-full-bench.js +51 -0
  110. package/dist/tests/bench/run-items36-targeted.js +69 -0
  111. package/dist/tests/bench/run-nano-quick.js +42 -0
  112. package/dist/tests/bench/run-waveg-targeted.js +62 -0
  113. package/dist/tests/bench/runner.js +699 -0
  114. package/dist/tests/bench/runner.test.js +958 -0
  115. package/dist/tests/bench/search-bridge.test.js +331 -0
  116. package/dist/tests/bench/tmp.js +131 -0
  117. package/dist/tests/bench/trajectory.js +116 -0
  118. package/dist/tests/bench/trajectory.test.js +127 -0
  119. package/dist/tests/bench/verifier.js +114 -0
  120. package/dist/tests/bench/verifier.test.js +118 -0
  121. package/dist/tests/bench/workflow-evaluator.js +557 -0
  122. package/dist/tests/bench/workflow-evaluator.test.js +421 -0
  123. package/dist/tests/bench/workflow-spec.js +345 -0
  124. package/dist/tests/bench/workflow-spec.test.js +363 -0
  125. package/dist/tests/bench/workflow-trace.js +472 -0
  126. package/dist/tests/bench/workflow-trace.test.js +254 -0
  127. package/dist/tests/benchmark-search-quality.js +536 -0
  128. package/dist/tests/benchmark-suite.js +1441 -0
  129. package/dist/tests/capture-cli.test.js +112 -0
  130. package/dist/tests/cli-errors.test.js +204 -0
  131. package/dist/tests/commands/events.test.js +370 -0
  132. package/dist/tests/commands/history.test.js +418 -0
  133. package/dist/tests/commands/import.test.js +103 -0
  134. package/dist/tests/commands/proposal-cli.test.js +209 -0
  135. package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
  136. package/dist/tests/commands/remember.test.js +97 -0
  137. package/dist/tests/commands/scope-flags.test.js +300 -0
  138. package/dist/tests/commands/search.test.js +537 -0
  139. package/dist/tests/commands/show-indexer-parity.test.js +117 -0
  140. package/dist/tests/commands/show.test.js +294 -0
  141. package/dist/tests/common.test.js +266 -0
  142. package/dist/tests/completions.test.js +142 -0
  143. package/dist/tests/config-cli.test.js +193 -0
  144. package/dist/tests/config-llm-features.test.js +139 -0
  145. package/dist/tests/config.test.js +569 -0
  146. package/dist/tests/contracts/migration-baseline.test.js +43 -0
  147. package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
  148. package/dist/tests/contracts/spec-helpers.js +46 -0
  149. package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
  150. package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
  151. package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
  152. package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
  153. package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
  154. package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
  155. package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
  156. package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
  157. package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
  158. package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
  159. package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
  160. package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
  161. package/dist/tests/core/write-source.test.js +366 -0
  162. package/dist/tests/curate-command.test.js +87 -0
  163. package/dist/tests/db-scoring.test.js +201 -0
  164. package/dist/tests/db.test.js +654 -0
  165. package/dist/tests/distill-cli-flag.test.js +208 -0
  166. package/dist/tests/distill.test.js +515 -0
  167. package/dist/tests/docker-install.test.js +120 -0
  168. package/dist/tests/e2e.test.js +1419 -0
  169. package/dist/tests/embedder.test.js +340 -0
  170. package/dist/tests/embedding-model-config.test.js +379 -0
  171. package/dist/tests/feedback-command.test.js +172 -0
  172. package/dist/tests/file-context.test.js +552 -0
  173. package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
  174. package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
  175. package/dist/tests/fixtures/stashes/load.js +166 -0
  176. package/dist/tests/fixtures/stashes/load.test.js +97 -0
  177. package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
  178. package/dist/tests/frontmatter.test.js +190 -0
  179. package/dist/tests/fts-field-weighting.test.js +254 -0
  180. package/dist/tests/fuzzy-search.test.js +230 -0
  181. package/dist/tests/git-provider-clone.test.js +45 -0
  182. package/dist/tests/github.test.js +161 -0
  183. package/dist/tests/graph-boost-ranking.test.js +305 -0
  184. package/dist/tests/graph-extraction.test.js +282 -0
  185. package/dist/tests/helpers/usage-events.js +8 -0
  186. package/dist/tests/index-pass-llm.test.js +161 -0
  187. package/dist/tests/indexer.test.js +570 -0
  188. package/dist/tests/info-command.test.js +166 -0
  189. package/dist/tests/init.test.js +69 -0
  190. package/dist/tests/install-script.test.js +246 -0
  191. package/dist/tests/integration/agent-real-profile.test.js +94 -0
  192. package/dist/tests/issue-36-repro.test.js +304 -0
  193. package/dist/tests/issues-191-194.test.js +160 -0
  194. package/dist/tests/lesson-lint.test.js +111 -0
  195. package/dist/tests/llm-client.test.js +115 -0
  196. package/dist/tests/llm-feature-gate.test.js +151 -0
  197. package/dist/tests/llm.test.js +139 -0
  198. package/dist/tests/lockfile.test.js +216 -0
  199. package/dist/tests/manifest.test.js +205 -0
  200. package/dist/tests/markdown.test.js +126 -0
  201. package/dist/tests/matchers-unit.test.js +189 -0
  202. package/dist/tests/memory-inference.test.js +299 -0
  203. package/dist/tests/merge-scoring.test.js +136 -0
  204. package/dist/tests/metadata.test.js +313 -0
  205. package/dist/tests/migration-help.test.js +89 -0
  206. package/dist/tests/origin-resolve.test.js +124 -0
  207. package/dist/tests/output-baseline.test.js +218 -0
  208. package/dist/tests/output-shapes-unit.test.js +478 -0
  209. package/dist/tests/parallel-search.test.js +272 -0
  210. package/dist/tests/parameter-metadata.test.js +365 -0
  211. package/dist/tests/paths.test.js +177 -0
  212. package/dist/tests/progressive-disclosure.test.js +280 -0
  213. package/dist/tests/proposals.test.js +279 -0
  214. package/dist/tests/proposed-quality.test.js +271 -0
  215. package/dist/tests/provider-registry.test.js +32 -0
  216. package/dist/tests/ranking-regression.test.js +548 -0
  217. package/dist/tests/reflect-propose.test.js +455 -0
  218. package/dist/tests/registry-build-index.test.js +394 -0
  219. package/dist/tests/registry-cli.test.js +290 -0
  220. package/dist/tests/registry-index-v2.test.js +430 -0
  221. package/dist/tests/registry-install.test.js +728 -0
  222. package/dist/tests/registry-providers/parity.test.js +189 -0
  223. package/dist/tests/registry-providers/skills-sh.test.js +309 -0
  224. package/dist/tests/registry-providers/static-index.test.js +238 -0
  225. package/dist/tests/registry-resolve.test.js +126 -0
  226. package/dist/tests/registry-search.test.js +923 -0
  227. package/dist/tests/remember-frontmatter.test.js +378 -0
  228. package/dist/tests/remember-unit.test.js +123 -0
  229. package/dist/tests/ripgrep-install.test.js +251 -0
  230. package/dist/tests/ripgrep-resolve.test.js +108 -0
  231. package/dist/tests/ripgrep.test.js +163 -0
  232. package/dist/tests/save-command.test.js +94 -0
  233. package/dist/tests/save-trust-qa-fixes.test.js +270 -0
  234. package/dist/tests/scoring-pipeline.test.js +648 -0
  235. package/dist/tests/search-include-proposed-cli.test.js +118 -0
  236. package/dist/tests/self-update.test.js +442 -0
  237. package/dist/tests/semantic-search-e2e.test.js +512 -0
  238. package/dist/tests/semantic-status.test.js +471 -0
  239. package/dist/tests/setup-run.integration.js +877 -0
  240. package/dist/tests/setup-wizard.test.js +198 -0
  241. package/dist/tests/setup.test.js +131 -0
  242. package/dist/tests/source-add.test.js +11 -0
  243. package/dist/tests/source-clone.test.js +254 -0
  244. package/dist/tests/source-manage.test.js +366 -0
  245. package/dist/tests/source-providers/filesystem.test.js +82 -0
  246. package/dist/tests/source-providers/git.test.js +252 -0
  247. package/dist/tests/source-providers/website.test.js +128 -0
  248. package/dist/tests/source-qa-fixes.test.js +286 -0
  249. package/dist/tests/source-registry.test.js +350 -0
  250. package/dist/tests/source-resolve.test.js +100 -0
  251. package/dist/tests/source-source.test.js +281 -0
  252. package/dist/tests/source.test.js +533 -0
  253. package/dist/tests/tar-utils-scan.test.js +73 -0
  254. package/dist/tests/toggle-components.test.js +73 -0
  255. package/dist/tests/usage-telemetry.test.js +265 -0
  256. package/dist/tests/utility-scoring.test.js +558 -0
  257. package/dist/tests/vault-load-error.test.js +78 -0
  258. package/dist/tests/vault-qa-fixes.test.js +194 -0
  259. package/dist/tests/vault.test.js +429 -0
  260. package/dist/tests/vector-search.test.js +608 -0
  261. package/dist/tests/walker.test.js +252 -0
  262. package/dist/tests/wave2-cluster-bc.test.js +228 -0
  263. package/dist/tests/wave2-cluster-d.test.js +180 -0
  264. package/dist/tests/wave2-cluster-e.test.js +179 -0
  265. package/dist/tests/wiki-qa-fixes.test.js +270 -0
  266. package/dist/tests/wiki.test.js +529 -0
  267. package/dist/tests/workflow-cli.test.js +271 -0
  268. package/dist/tests/workflow-markdown.test.js +171 -0
  269. package/dist/tests/workflow-path-escape.test.js +132 -0
  270. package/dist/tests/workflow-qa-fixes.test.js +395 -0
  271. package/dist/tests/workflows/indexer-rejection.test.js +213 -0
  272. package/docs/README.md +8 -0
  273. package/docs/migration/release-notes/0.7.0.md +244 -0
  274. package/package.json +2 -2
  275. package/dist/core/warn.js +0 -27
  276. package/dist/output/shapes.js +0 -212
  277. package/dist/output/text.js +0 -520
  278. /package/dist/{commands → src/commands}/completions.js +0 -0
  279. /package/dist/{commands → src/commands}/curate.js +0 -0
  280. /package/dist/{commands → src/commands}/info.js +0 -0
  281. /package/dist/{commands → src/commands}/init.js +0 -0
  282. /package/dist/{commands → src/commands}/install-audit.js +0 -0
  283. /package/dist/{commands → src/commands}/migration-help.js +0 -0
  284. /package/dist/{commands → src/commands}/source-clone.js +0 -0
  285. /package/dist/{commands → src/commands}/vault.js +0 -0
  286. /package/dist/{core → src/core}/asset-registry.js +0 -0
  287. /package/dist/{core → src/core}/frontmatter.js +0 -0
  288. /package/dist/{core → src/core}/markdown.js +0 -0
  289. /package/dist/{core → src/core}/paths.js +0 -0
  290. /package/dist/{indexer → src/indexer}/manifest.js +0 -0
  291. /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
  292. /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
  293. /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
  294. /package/dist/{indexer → src/indexer}/walker.js +0 -0
  295. /package/dist/{llm → src/llm}/embedder.js +0 -0
  296. /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
  297. /package/dist/{llm → src/llm}/embedders/local.js +0 -0
  298. /package/dist/{llm → src/llm}/embedders/types.js +0 -0
  299. /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
  300. /package/dist/{output → src/output}/context.js +0 -0
  301. /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
  302. /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
  303. /package/dist/{registry → src/registry}/providers/index.js +0 -0
  304. /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
  305. /package/dist/{registry → src/registry}/providers/types.js +0 -0
  306. /package/dist/{registry → src/registry}/types.js +0 -0
  307. /package/dist/{setup → src/setup}/detect.js +0 -0
  308. /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
  309. /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
  310. /package/dist/{setup → src/setup}/steps.js +0 -0
  311. /package/dist/{sources → src/sources}/include.js +0 -0
  312. /package/dist/{sources → src/sources}/provider-factory.js +0 -0
  313. /package/dist/{sources → src/sources}/provider.js +0 -0
  314. /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
  315. /package/dist/{sources → src/sources}/providers/index.js +0 -0
  316. /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
  317. /package/dist/{sources → src/sources}/providers/npm.js +0 -0
  318. /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
  319. /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
  320. /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
  321. /package/dist/{sources → src/sources}/providers/website.js +0 -0
  322. /package/dist/{sources → src/sources}/resolve.js +0 -0
  323. /package/dist/{sources → src/sources}/types.js +0 -0
  324. /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
  325. /package/dist/{version.js → src/version.js} +0 -0
  326. /package/dist/{workflows → src/workflows}/authoring.js +0 -0
  327. /package/dist/{workflows → src/workflows}/cli.js +0 -0
  328. /package/dist/{workflows → src/workflows}/db.js +0 -0
  329. /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
  330. /package/dist/{workflows → src/workflows}/parser.js +0 -0
  331. /package/dist/{workflows → src/workflows}/renderer.js +0 -0
  332. /package/dist/{workflows → src/workflows}/schema.js +0 -0
  333. /package/dist/{workflows → src/workflows}/validator.js +0 -0
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Declarative workflow specs for AKM compliance checks.
3
+ *
4
+ * Specs are authored as YAML in `tests/fixtures/bench/workflows/*.yaml` and
5
+ * describe expected agent behaviour over the normalized event stream from
6
+ * `workflow-trace.ts` (issue #254). This module owns:
7
+ *
8
+ * - `WorkflowSpec` type
9
+ * - `loadWorkflowSpec(path, root?)` — parses + validates one file
10
+ * - `loadAllWorkflowSpecs(dir)` — walks a workflows directory
11
+ *
12
+ * Event names are validated against `WORKFLOW_TRACE_EVENT_NAMES` imported from
13
+ * `workflow-trace.ts` — single source of truth, no dual-maintenance hazard.
14
+ *
15
+ * Asset refs (e.g. `gold_ref`) are validated via `parseAssetRef` from
16
+ * `src/core/asset-ref.ts` — never reinvent ref validation.
17
+ */
18
+ import { readdirSync, readFileSync, statSync } from "node:fs";
19
+ import path from "node:path";
20
+ import { parse as parseYaml } from "yaml";
21
+ import { parseAssetRef } from "../../src/core/asset-ref";
22
+ import { WORKFLOW_TRACE_EVENT_NAMES } from "./workflow-trace";
23
+ // ── Event-name set (derived from workflow-trace.ts — single source of truth) ─
24
+ /**
25
+ * Allowlist of known event names, derived from `WORKFLOW_TRACE_EVENT_NAMES` in
26
+ * `workflow-trace.ts`. Using the exported runtime Set eliminates the dual-
27
+ * maintenance hazard: add a new event type once in `workflow-trace.ts` and
28
+ * both the normalizer and the spec validator see it automatically.
29
+ *
30
+ * `first_workspace_write` is a synthetic marker (the first `workspace_write`
31
+ * for a run) and is included so specs can talk about it directly.
32
+ */
33
+ export const KNOWN_EVENT_NAMES = WORKFLOW_TRACE_EVENT_NAMES;
34
+ const EVENT_NAME_SET = KNOWN_EVENT_NAMES;
35
+ function isKnownEvent(name) {
36
+ return typeof name === "string" && EVENT_NAME_SET.has(name);
37
+ }
38
+ // ── Errors ─────────────────────────────────────────────────────────────────
39
+ export class WorkflowSpecError extends Error {
40
+ specPath;
41
+ code = "WORKFLOW_SPEC_INVALID";
42
+ constructor(message, specPath) {
43
+ super(`${message} (in ${specPath})`);
44
+ this.specPath = specPath;
45
+ this.name = "WorkflowSpecError";
46
+ }
47
+ }
48
+ // ── Helpers ────────────────────────────────────────────────────────────────
49
+ function isPlainObject(value) {
50
+ return typeof value === "object" && value !== null && !Array.isArray(value);
51
+ }
52
+ function requireString(obj, key, specPath) {
53
+ const v = obj[key];
54
+ if (typeof v !== "string" || v.length === 0) {
55
+ throw new WorkflowSpecError(`Missing or non-string field "${key}"`, specPath);
56
+ }
57
+ return v;
58
+ }
59
+ function optionalString(obj, key, specPath) {
60
+ const v = obj[key];
61
+ if (v === undefined || v === null)
62
+ return undefined;
63
+ if (typeof v !== "string") {
64
+ throw new WorkflowSpecError(`Field "${key}" must be a string`, specPath);
65
+ }
66
+ return v;
67
+ }
68
+ function optionalStringArray(obj, key, specPath) {
69
+ const v = obj[key];
70
+ if (v === undefined || v === null)
71
+ return undefined;
72
+ if (!Array.isArray(v) || !v.every((x) => typeof x === "string")) {
73
+ throw new WorkflowSpecError(`Field "${key}" must be a string[]`, specPath);
74
+ }
75
+ return v;
76
+ }
77
+ function requireNumber(obj, key, specPath) {
78
+ const v = obj[key];
79
+ if (typeof v !== "number" || !Number.isFinite(v)) {
80
+ throw new WorkflowSpecError(`Missing or non-numeric field "${key}"`, specPath);
81
+ }
82
+ return v;
83
+ }
84
+ function validateEventName(name, specPath, where) {
85
+ if (!isKnownEvent(name)) {
86
+ throw new WorkflowSpecError(`Unknown event name "${String(name)}" in ${where}. ` + `Allowed: ${[...KNOWN_EVENT_NAMES].join(", ")}`, specPath);
87
+ }
88
+ return name;
89
+ }
90
+ function validatePolarity(value, specPath, where) {
91
+ if (value !== "positive" && value !== "negative") {
92
+ throw new WorkflowSpecError(`Field "polarity" in ${where} must be "positive" or "negative"`, specPath);
93
+ }
94
+ return value;
95
+ }
96
+ function parseAppliesTo(raw, specPath) {
97
+ if (raw === undefined || raw === null)
98
+ return undefined;
99
+ if (!isPlainObject(raw)) {
100
+ throw new WorkflowSpecError('Field "applies_to" must be an object', specPath);
101
+ }
102
+ const out = {};
103
+ const arms = optionalStringArray(raw, "arms", specPath);
104
+ if (arms)
105
+ out.arms = arms;
106
+ const domains = optionalStringArray(raw, "task_domains", specPath);
107
+ if (domains)
108
+ out.task_domains = domains;
109
+ const outcomes = optionalStringArray(raw, "outcomes", specPath);
110
+ if (outcomes)
111
+ out.outcomes = outcomes;
112
+ if (raw.requires_gold_ref !== undefined) {
113
+ if (typeof raw.requires_gold_ref !== "boolean") {
114
+ throw new WorkflowSpecError('Field "applies_to.requires_gold_ref" must be a boolean', specPath);
115
+ }
116
+ out.requires_gold_ref = raw.requires_gold_ref;
117
+ }
118
+ if (raw.min_repeated_failures !== undefined) {
119
+ if (typeof raw.min_repeated_failures !== "number" || !Number.isFinite(raw.min_repeated_failures)) {
120
+ throw new WorkflowSpecError('Field "applies_to.min_repeated_failures" must be a number', specPath);
121
+ }
122
+ out.min_repeated_failures = raw.min_repeated_failures;
123
+ }
124
+ return out;
125
+ }
126
+ function parseSequenceStep(raw, specPath, index) {
127
+ if (!isPlainObject(raw)) {
128
+ throw new WorkflowSpecError(`required_sequence[${index}] must be an object`, specPath);
129
+ }
130
+ const where = `required_sequence[${index}]`;
131
+ const event = validateEventName(raw.event, specPath, where);
132
+ const step = { event };
133
+ if (raw.before !== undefined) {
134
+ step.before = validateEventName(raw.before, specPath, `${where}.before`);
135
+ }
136
+ if (raw.after !== undefined) {
137
+ step.after = validateEventName(raw.after, specPath, `${where}.after`);
138
+ }
139
+ if (raw.required_if !== undefined) {
140
+ if (typeof raw.required_if !== "string" || raw.required_if.length === 0) {
141
+ throw new WorkflowSpecError(`${where}.required_if must be a non-empty string`, specPath);
142
+ }
143
+ step.required_if = raw.required_if;
144
+ }
145
+ if (raw.min_count !== undefined) {
146
+ if (typeof raw.min_count !== "number" || !Number.isFinite(raw.min_count) || raw.min_count < 1) {
147
+ throw new WorkflowSpecError(`${where}.min_count must be a positive number`, specPath);
148
+ }
149
+ step.min_count = raw.min_count;
150
+ }
151
+ if (raw.polarity !== undefined) {
152
+ step.polarity = validatePolarity(raw.polarity, specPath, where);
153
+ }
154
+ if (raw.ref_must_equal !== undefined) {
155
+ if (typeof raw.ref_must_equal !== "string" || raw.ref_must_equal.length === 0) {
156
+ throw new WorkflowSpecError(`${where}.ref_must_equal must be a non-empty string`, specPath);
157
+ }
158
+ step.ref_must_equal = raw.ref_must_equal;
159
+ }
160
+ return step;
161
+ }
162
+ function parseForbiddenStep(raw, specPath, index) {
163
+ if (!isPlainObject(raw)) {
164
+ throw new WorkflowSpecError(`forbidden[${index}] must be an object`, specPath);
165
+ }
166
+ const where = `forbidden[${index}]`;
167
+ const step = {
168
+ event: validateEventName(raw.event, specPath, where),
169
+ };
170
+ if (raw.before !== undefined) {
171
+ step.before = validateEventName(raw.before, specPath, `${where}.before`);
172
+ }
173
+ if (raw.after !== undefined) {
174
+ step.after = validateEventName(raw.after, specPath, `${where}.after`);
175
+ }
176
+ if (raw.polarity !== undefined) {
177
+ step.polarity = validatePolarity(raw.polarity, specPath, where);
178
+ }
179
+ return step;
180
+ }
181
+ function parseScoring(raw, specPath) {
182
+ if (!isPlainObject(raw)) {
183
+ throw new WorkflowSpecError('Field "scoring" must be an object', specPath);
184
+ }
185
+ const required_steps_weight = requireNumber(raw, "required_steps_weight", specPath);
186
+ const forbidden_steps_weight = requireNumber(raw, "forbidden_steps_weight", specPath);
187
+ const evidence_quality_weight = requireNumber(raw, "evidence_quality_weight", specPath);
188
+ for (const [key, val] of Object.entries({
189
+ required_steps_weight,
190
+ forbidden_steps_weight,
191
+ evidence_quality_weight,
192
+ })) {
193
+ if (val < 0 || val > 1) {
194
+ throw new WorkflowSpecError(`scoring.${key} must be in [0, 1] (got ${val})`, specPath);
195
+ }
196
+ }
197
+ const sum = required_steps_weight + forbidden_steps_weight + evidence_quality_weight;
198
+ if (Math.abs(sum - 1) > 1e-6) {
199
+ throw new WorkflowSpecError(`scoring weights must sum to 1.0 (got ${sum})`, specPath);
200
+ }
201
+ return { required_steps_weight, forbidden_steps_weight, evidence_quality_weight };
202
+ }
203
+ // ── Loader ─────────────────────────────────────────────────────────────────
204
+ const MAX_SPEC_BYTES = 1 << 20; // 1 MiB — workflow specs are small.
205
+ /**
206
+ * Load and validate a single workflow spec from a YAML file.
207
+ *
208
+ * If `root` is provided, the resolved absolute path of `specPath` MUST be
209
+ * contained within `root` (path-traversal guard). Resolution uses
210
+ * `path.resolve` + a `path.relative` containment check.
211
+ */
212
+ export function loadWorkflowSpec(specPath, root) {
213
+ const absSpec = path.resolve(specPath);
214
+ if (root !== undefined) {
215
+ const absRoot = path.resolve(root);
216
+ const rel = path.relative(absRoot, absSpec);
217
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || rel.length === 0 || path.isAbsolute(rel)) {
218
+ throw new WorkflowSpecError(`Spec path resolves outside workflows root "${absRoot}"`, absSpec);
219
+ }
220
+ }
221
+ let stat;
222
+ try {
223
+ stat = statSync(absSpec);
224
+ }
225
+ catch (err) {
226
+ throw new WorkflowSpecError(`Cannot stat spec file: ${err.message}`, absSpec);
227
+ }
228
+ if (!stat.isFile()) {
229
+ throw new WorkflowSpecError("Spec path is not a regular file", absSpec);
230
+ }
231
+ if (stat.size > MAX_SPEC_BYTES) {
232
+ throw new WorkflowSpecError(`Spec file too large (${stat.size} > ${MAX_SPEC_BYTES} bytes)`, absSpec);
233
+ }
234
+ const text = readFileSync(absSpec, "utf8");
235
+ let raw;
236
+ try {
237
+ raw = parseYaml(text);
238
+ }
239
+ catch (err) {
240
+ throw new WorkflowSpecError(`YAML parse error: ${err.message}`, absSpec);
241
+ }
242
+ if (!isPlainObject(raw)) {
243
+ throw new WorkflowSpecError("Top-level YAML must be a mapping", absSpec);
244
+ }
245
+ const id = requireString(raw, "id", absSpec);
246
+ const title = requireString(raw, "title", absSpec);
247
+ const description = optionalString(raw, "description", absSpec);
248
+ const applies_to = parseAppliesTo(raw.applies_to, absSpec);
249
+ const goldRefRaw = optionalString(raw, "gold_ref", absSpec);
250
+ if (goldRefRaw !== undefined) {
251
+ try {
252
+ parseAssetRef(goldRefRaw);
253
+ }
254
+ catch (err) {
255
+ throw new WorkflowSpecError(`gold_ref "${goldRefRaw}" is not a valid asset ref: ${err.message}`, absSpec);
256
+ }
257
+ }
258
+ if (!Array.isArray(raw.required_sequence) || raw.required_sequence.length === 0) {
259
+ throw new WorkflowSpecError("required_sequence must be a non-empty array", absSpec);
260
+ }
261
+ const required_sequence = raw.required_sequence.map((step, i) => parseSequenceStep(step, absSpec, i));
262
+ let forbidden;
263
+ if (raw.forbidden !== undefined && raw.forbidden !== null) {
264
+ if (!Array.isArray(raw.forbidden)) {
265
+ throw new WorkflowSpecError("forbidden must be an array", absSpec);
266
+ }
267
+ forbidden = raw.forbidden.map((step, i) => parseForbiddenStep(step, absSpec, i));
268
+ }
269
+ const scoring = parseScoring(raw.scoring, absSpec);
270
+ const spec = {
271
+ id,
272
+ title,
273
+ required_sequence,
274
+ scoring,
275
+ sourcePath: absSpec,
276
+ };
277
+ if (description !== undefined)
278
+ spec.description = description;
279
+ if (applies_to !== undefined)
280
+ spec.applies_to = applies_to;
281
+ if (goldRefRaw !== undefined)
282
+ spec.gold_ref = goldRefRaw;
283
+ if (forbidden !== undefined)
284
+ spec.forbidden = forbidden;
285
+ return spec;
286
+ }
287
+ /**
288
+ * Load every `*.yaml` / `*.yml` file in `dir` (non-recursive) as a
289
+ * `WorkflowSpec`. Each file is path-traversal-checked against `dir`.
290
+ *
291
+ * Throws `WorkflowSpecError` on the first malformed spec — fail-fast.
292
+ * Duplicate `id` values across the directory are also rejected.
293
+ */
294
+ export function loadAllWorkflowSpecs(dir) {
295
+ const absDir = path.resolve(dir);
296
+ let entries;
297
+ try {
298
+ entries = readdirSync(absDir);
299
+ }
300
+ catch (err) {
301
+ throw new WorkflowSpecError(`Cannot read workflows directory: ${err.message}`, absDir);
302
+ }
303
+ const yamlFiles = entries.filter((e) => e.endsWith(".yaml") || e.endsWith(".yml")).sort();
304
+ const specs = [];
305
+ const ids = new Set();
306
+ for (const f of yamlFiles) {
307
+ const spec = loadWorkflowSpec(path.join(absDir, f), absDir);
308
+ if (ids.has(spec.id)) {
309
+ throw new WorkflowSpecError(`Duplicate workflow spec id "${spec.id}"`, spec.sourcePath);
310
+ }
311
+ ids.add(spec.id);
312
+ specs.push(spec);
313
+ }
314
+ return specs;
315
+ }
316
+ /**
317
+ * Returns true iff the spec's `applies_to` filter matches `ctx`. A spec with
318
+ * no `applies_to` matches everything.
319
+ *
320
+ * Domain match: the first '/'-separated segment of `taskId` must appear in
321
+ * `applies_to.task_domains` (matches the convention used by #260).
322
+ */
323
+ export function specApplies(spec, ctx) {
324
+ const a = spec.applies_to;
325
+ if (!a)
326
+ return true;
327
+ if (a.arms && a.arms.length > 0 && !a.arms.includes(ctx.arm))
328
+ return false;
329
+ if (a.task_domains && a.task_domains.length > 0) {
330
+ const domain = ctx.taskId.split("/")[0] ?? "";
331
+ if (!a.task_domains.includes(domain))
332
+ return false;
333
+ }
334
+ if (a.outcomes && a.outcomes.length > 0) {
335
+ if (!ctx.outcome || !a.outcomes.includes(ctx.outcome))
336
+ return false;
337
+ }
338
+ if (a.requires_gold_ref && !ctx.hasGoldRef)
339
+ return false;
340
+ if (a.min_repeated_failures !== undefined) {
341
+ if ((ctx.repeatedFailures ?? 0) < a.min_repeated_failures)
342
+ return false;
343
+ }
344
+ return true;
345
+ }