@aiwg/cli 2026.10.1 → 2026.10.4

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 (361) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/capability-matrix.yaml +270 -0
  3. package/agentic/code/providers/model-capabilities.v1.json +3183 -24
  4. package/bin/aiwg.mjs +135 -47
  5. package/dist/src/a2a/codecs.js +9 -0
  6. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  7. package/dist/src/artifacts/browser-export.js +6 -0
  8. package/dist/src/artifacts/checksum-manifest.js +1 -0
  9. package/dist/src/artifacts/citation-parser.js +173 -92
  10. package/dist/src/artifacts/citation-table-schema.js +59 -0
  11. package/dist/src/artifacts/cli.js +57 -18
  12. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  13. package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
  14. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  15. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  16. package/dist/src/artifacts/discovery-eval.js +6 -4
  17. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  18. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  19. package/dist/src/artifacts/framework-capabilities.js +171 -0
  20. package/dist/src/artifacts/framework-index-paths.js +73 -0
  21. package/dist/src/artifacts/framework-source-digest.js +75 -0
  22. package/dist/src/artifacts/index-builder.js +285 -131
  23. package/dist/src/artifacts/index-files.js +132 -18
  24. package/dist/src/artifacts/index-preparation.js +65 -0
  25. package/dist/src/artifacts/index-refresh.js +576 -0
  26. package/dist/src/artifacts/index-worker.mjs +9 -0
  27. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  28. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  29. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  30. package/dist/src/artifacts/query-engine.js +410 -88
  31. package/dist/src/artifacts/relations-builder.js +168 -0
  32. package/dist/src/artifacts/relations.js +148 -0
  33. package/dist/src/artifacts/repair.js +9 -4
  34. package/dist/src/artifacts/stats.js +5 -2
  35. package/dist/src/artifacts/types.js +30 -10
  36. package/dist/src/artifacts/watcher.js +1 -1
  37. package/dist/src/auth/credential-store.js +2 -1
  38. package/dist/src/catalog/cli.d.mts +5 -0
  39. package/dist/src/channel/manager.d.mts +20 -0
  40. package/dist/src/channel/manager.mjs +78 -12
  41. package/dist/src/cli/child-process-result.js +40 -0
  42. package/dist/src/cli/command-log.js +2 -3
  43. package/dist/src/cli/env.js +38 -16
  44. package/dist/src/cli/flag-parsing.js +236 -0
  45. package/dist/src/cli/handlers/artifacts.js +3 -0
  46. package/dist/src/cli/handlers/auth.js +3 -0
  47. package/dist/src/cli/handlers/decision.js +218 -5
  48. package/dist/src/cli/handlers/diagnose.js +13 -5
  49. package/dist/src/cli/handlers/effect.js +4 -2
  50. package/dist/src/cli/handlers/evidence.js +3 -0
  51. package/dist/src/cli/handlers/feedback.js +14 -5
  52. package/dist/src/cli/handlers/help.js +1 -0
  53. package/dist/src/cli/handlers/index.js +3 -1
  54. package/dist/src/cli/handlers/init.js +248 -30
  55. package/dist/src/cli/handlers/lint.js +5 -0
  56. package/dist/src/cli/handlers/mc.js +23 -0
  57. package/dist/src/cli/handlers/models.js +169 -10
  58. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  59. package/dist/src/cli/handlers/ralph.js +3 -0
  60. package/dist/src/cli/handlers/refresh.js +28 -0
  61. package/dist/src/cli/handlers/regenerate.js +14 -0
  62. package/dist/src/cli/handlers/resource-versions.js +3 -0
  63. package/dist/src/cli/handlers/rules.js +195 -0
  64. package/dist/src/cli/handlers/run.js +4 -0
  65. package/dist/src/cli/handlers/runtime-info.js +25 -11
  66. package/dist/src/cli/handlers/script-runner.js +19 -8
  67. package/dist/src/cli/handlers/session.js +406 -62
  68. package/dist/src/cli/handlers/sessions.js +243 -76
  69. package/dist/src/cli/handlers/setup-manifest.js +474 -79
  70. package/dist/src/cli/handlers/setup.js +180 -39
  71. package/dist/src/cli/handlers/steward.js +11 -0
  72. package/dist/src/cli/handlers/subcommands.js +60 -3
  73. package/dist/src/cli/handlers/team.js +18 -8
  74. package/dist/src/cli/handlers/uhp.js +3 -0
  75. package/dist/src/cli/handlers/use.js +307 -55
  76. package/dist/src/cli/handlers/utilities.js +125 -26
  77. package/dist/src/cli/handlers/version.js +8 -0
  78. package/dist/src/cli/prompt-utils.js +72 -0
  79. package/dist/src/cli/provider-resolution.js +11 -0
  80. package/dist/src/cli/router.js +15 -1
  81. package/dist/src/cli/services/deployment-verification.js +19 -9
  82. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  83. package/dist/src/cli/skill-usage.js +2 -3
  84. package/dist/src/config/aiwg-config.js +349 -35
  85. package/dist/src/config/cli.js +256 -23
  86. package/dist/src/config/decision-config.js +106 -0
  87. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  88. package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
  89. package/dist/src/config/gitignore.js +50 -0
  90. package/dist/src/config/index-graph-overrides.js +36 -0
  91. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  92. package/dist/src/config/project-artifacts-health.mjs +14 -3
  93. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  94. package/dist/src/config/user-config-dir.d.mts +9 -0
  95. package/dist/src/config/user-config.js +38 -11
  96. package/dist/src/config/user-registry.js +23 -15
  97. package/dist/src/config/user-state.js +7 -0
  98. package/dist/src/decision/adapters/jev.js +75 -27
  99. package/dist/src/decision/ask.js +203 -43
  100. package/dist/src/decision/context-live-qualification.js +2 -2
  101. package/dist/src/decision/driver.js +14 -7
  102. package/dist/src/decision/ensemble/aggregate.js +23 -10
  103. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  104. package/dist/src/decision/entry.js +25 -6
  105. package/dist/src/decision/graph-live-qualification.js +1 -1
  106. package/dist/src/decision/graph-run.js +9 -0
  107. package/dist/src/decision/heldout/collector.js +15 -14
  108. package/dist/src/decision/heldout/contract.js +30 -7
  109. package/dist/src/decision/index.js +1 -0
  110. package/dist/src/decision/interop.js +60 -8
  111. package/dist/src/decision/jev-credentials.js +15 -1
  112. package/dist/src/decision/ledger.js +942 -0
  113. package/dist/src/decision/offload-activation.js +137 -0
  114. package/dist/src/decision/offload.js +550 -0
  115. package/dist/src/decision/qualification/load.js +4 -6
  116. package/dist/src/decision/savings.js +28 -0
  117. package/dist/src/decision/validate.js +6 -3
  118. package/dist/src/effects/dispatch-verifier.js +252 -0
  119. package/dist/src/effects/index.js +1 -0
  120. package/dist/src/effects/types.js +5 -1
  121. package/dist/src/effects/verifiers/git.js +2 -1
  122. package/dist/src/effects/verifiers/tracker.js +26 -14
  123. package/dist/src/extensions/commands/definitions.js +19 -0
  124. package/dist/src/extensions/project-local-remove.js +36 -1
  125. package/dist/src/features/installer.js +2 -2
  126. package/dist/src/flow/graph-metadata.js +5 -0
  127. package/dist/src/gates/providers/loader.js +64 -18
  128. package/dist/src/installation/manager-command.d.mts +18 -0
  129. package/dist/src/installation/manager-command.mjs +15 -3
  130. package/dist/src/installation/manager.d.mts +49 -0
  131. package/dist/src/installation/manager.mjs +95 -5
  132. package/dist/src/issues/cli.js +4 -4
  133. package/dist/src/issues/local.js +24 -5
  134. package/dist/src/jobs/runner.js +1 -1
  135. package/dist/src/lint/cli.js +27 -1
  136. package/dist/src/lint/loader.js +2 -1
  137. package/dist/src/lint/reporters.js +25 -1
  138. package/dist/src/lint/rule-activation.js +43 -0
  139. package/dist/src/lint/runner.js +137 -62
  140. package/dist/src/mcp/adapters/codex-runtime.js +366 -56
  141. package/dist/src/mcp/claude-install.d.mts +4 -0
  142. package/dist/src/mcp/claude-install.mjs +41 -0
  143. package/dist/src/mcp/cli.d.mts +1 -0
  144. package/dist/src/mcp/cli.mjs +620 -85
  145. package/dist/src/mcp/config-file.d.mts +28 -0
  146. package/dist/src/mcp/config-file.mjs +279 -0
  147. package/dist/src/mcp/config-layers.d.mts +36 -0
  148. package/dist/src/mcp/config-layers.mjs +348 -0
  149. package/dist/src/mcp/credentials.d.mts +51 -0
  150. package/dist/src/mcp/credentials.mjs +484 -0
  151. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  152. package/dist/src/mcp/grok-build-config.mjs +17 -27
  153. package/dist/src/mcp/helpers.mjs +109 -4
  154. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  155. package/dist/src/mcp/muse-native-config.mjs +1 -0
  156. package/dist/src/mcp/omp-config.d.mts +4 -0
  157. package/dist/src/mcp/omp-config.mjs +50 -29
  158. package/dist/src/mcp/profiles.js +97 -23
  159. package/dist/src/mcp/profiles.mjs +99 -27
  160. package/dist/src/mcp/registry.js +248 -131
  161. package/dist/src/mcp/registry.mjs +169 -100
  162. package/dist/src/mcp/server-config.d.mts +3 -0
  163. package/dist/src/mcp/server-config.mjs +89 -0
  164. package/dist/src/mcp/server.mjs +127 -82
  165. package/dist/src/mcp/test-simple.mjs +2 -1
  166. package/dist/src/mcp/toml-editor.d.mts +4 -0
  167. package/dist/src/mcp/toml-string.d.mts +1 -0
  168. package/dist/src/mcp/toml-string.mjs +9 -0
  169. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  170. package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
  171. package/dist/src/mcp/tool-filters.d.mts +46 -0
  172. package/dist/src/mcp/tool-filters.mjs +357 -0
  173. package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
  174. package/dist/src/mcp/tools/decision.mjs +22 -3
  175. package/dist/src/mcp/tools/discovery.mjs +62 -55
  176. package/dist/src/mcp/tools/orchestration.mjs +50 -2
  177. package/dist/src/mcp/tools/subsystems.mjs +39 -8
  178. package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
  179. package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
  180. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
  181. package/dist/src/memory/project-registry.js +2 -2
  182. package/dist/src/mission-protocol/codecs.js +647 -1
  183. package/dist/src/mission-protocol/types.js +110 -0
  184. package/dist/src/mission-protocol/worker-status.js +2 -0
  185. package/dist/src/models/config-loader.js +76 -14
  186. package/dist/src/models/grok-build-models.js +93 -41
  187. package/dist/src/models/model-capabilities.v1.json +3183 -24
  188. package/dist/src/models/model-discovery.js +931 -67
  189. package/dist/src/models/provider-policy.js +719 -11
  190. package/dist/src/models/qualification-digest.d.mts +2 -0
  191. package/dist/src/models/qualification-digest.mjs +17 -0
  192. package/dist/src/models/resolver.js +261 -0
  193. package/dist/src/models/worker-direct-readiness.js +550 -0
  194. package/dist/src/models/worker-dispatch-offer.js +169 -0
  195. package/dist/src/models/worker-health-readiness.js +144 -0
  196. package/dist/src/models/worker-installation.d.mts +39 -0
  197. package/dist/src/models/worker-installation.mjs +986 -0
  198. package/dist/src/models/wrapper-route.js +18 -0
  199. package/dist/src/ops/registry.js +9 -14
  200. package/dist/src/plugins/standalone-packager.js +4 -3
  201. package/dist/src/process/bounded-child-process.d.mts +84 -0
  202. package/dist/src/process/bounded-child-process.mjs +430 -0
  203. package/dist/src/process/cli-command.d.mts +16 -0
  204. package/dist/src/process/cli-command.mjs +75 -0
  205. package/dist/src/process/signal-child.d.mts +6 -0
  206. package/dist/src/process/signal-child.mjs +7 -0
  207. package/dist/src/providers/capability-matrix.js +8 -0
  208. package/dist/src/providers/capability-matrix.yaml +270 -0
  209. package/dist/src/providers/deployed-skill-roots.js +20 -0
  210. package/dist/src/providers/grok-build-acp.js +3 -2
  211. package/dist/src/providers/grok-build-headless.js +53 -23
  212. package/dist/src/providers/grok-build-inspect.js +1 -0
  213. package/dist/src/providers/omp-agent.d.mts +8 -0
  214. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  215. package/dist/src/providers/omp-paths.d.mts +13 -0
  216. package/dist/src/providers/provider-definitions.js +9 -1
  217. package/dist/src/providers/provider-definitions.mjs +9 -2
  218. package/dist/src/providers/skill-ownership.d.mts +9 -0
  219. package/dist/src/providers/skill-ownership.mjs +88 -0
  220. package/dist/src/rules/activation.d.mts +21 -0
  221. package/dist/src/rules/activation.mjs +62 -0
  222. package/dist/src/rules/inline-budget.d.mts +26 -0
  223. package/dist/src/rules/inline-budget.mjs +66 -0
  224. package/dist/src/rules/inventory.js +129 -0
  225. package/dist/src/rules/pin-state.js +73 -0
  226. package/dist/src/rules/pins.d.mts +14 -0
  227. package/dist/src/rules/pins.mjs +112 -0
  228. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  229. package/dist/src/serve/executor-registry.js +16 -24
  230. package/dist/src/serve/fleet-mission-conductor.js +9 -0
  231. package/dist/src/serve/mission-conductor.js +47 -12
  232. package/dist/src/serve/shared-host-scheduler.js +2 -0
  233. package/dist/src/serve/worker-invocation.js +558 -0
  234. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  235. package/dist/src/serve/worker-stdin-frame.mjs +107 -0
  236. package/dist/src/sessions/adapter-registry.js +49 -0
  237. package/dist/src/sessions/adapters/claude.js +39 -54
  238. package/dist/src/sessions/adapters/codex.js +82 -67
  239. package/dist/src/sessions/adapters/copilot.js +4 -4
  240. package/dist/src/sessions/adapters/cursor.js +40 -57
  241. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  242. package/dist/src/sessions/adapters/factory.js +31 -27
  243. package/dist/src/sessions/adapters/generic.js +43 -9
  244. package/dist/src/sessions/adapters/grok-build.js +11 -11
  245. package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
  246. package/dist/src/sessions/adapters/grokbot.js +434 -24
  247. package/dist/src/sessions/adapters/hermes.js +27 -29
  248. package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
  249. package/dist/src/sessions/adapters/muse.js +200 -104
  250. package/dist/src/sessions/adapters/omp.js +28 -14
  251. package/dist/src/sessions/adapters/openclaw.js +89 -79
  252. package/dist/src/sessions/adapters/opencode.js +27 -24
  253. package/dist/src/sessions/adapters/openhuman.js +18 -60
  254. package/dist/src/sessions/adapters/pi.js +30 -14
  255. package/dist/src/sessions/adapters/warp.js +3 -3
  256. package/dist/src/sessions/adapters/windsurf.js +19 -48
  257. package/dist/src/sessions/batch-contracts.js +25 -5
  258. package/dist/src/sessions/batch-import.js +72 -51
  259. package/dist/src/sessions/candidates.js +47 -11
  260. package/dist/src/sessions/contracts.js +75 -2
  261. package/dist/src/sessions/coverage-preflight.js +134 -0
  262. package/dist/src/sessions/discovery.js +3 -0
  263. package/dist/src/sessions/export-plan.js +4 -3
  264. package/dist/src/sessions/grok-build-cli.js +67 -5
  265. package/dist/src/sessions/import-exclusions.js +72 -0
  266. package/dist/src/sessions/import-lease.js +34 -18
  267. package/dist/src/sessions/import-limits.js +37 -0
  268. package/dist/src/sessions/importer.js +84 -46
  269. package/dist/src/sessions/index.js +2 -0
  270. package/dist/src/sessions/lifecycle.js +45 -0
  271. package/dist/src/sessions/policy.js +133 -25
  272. package/dist/src/sessions/readers.js +106 -53
  273. package/dist/src/sessions/refresh.js +106 -0
  274. package/dist/src/sessions/repository.js +199 -63
  275. package/dist/src/sessions/workspace-discovery.js +315 -88
  276. package/dist/src/sessions/workspace-matching.js +107 -0
  277. package/dist/src/skills/adapters/clawhub.js +10 -8
  278. package/dist/src/skills/adapters/openclaw.js +10 -7
  279. package/dist/src/skills/importer.js +1 -0
  280. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  281. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  282. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  283. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  284. package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
  285. package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
  286. package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
  287. package/dist/src/smiths/mcpsmith/example.js +1 -1
  288. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  289. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  290. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
  291. package/dist/src/storage/backends/fs.js +18 -76
  292. package/dist/src/storage/backends/logseq.js +68 -7
  293. package/dist/src/storage/backends/obsidian.js +1 -0
  294. package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
  295. package/dist/src/testing/generators/test-code-generator.js +57 -30
  296. package/dist/src/tracker/forge-http.js +2 -1
  297. package/dist/src/uhp/client.js +11 -1
  298. package/dist/src/uhp/mission.js +5 -0
  299. package/dist/src/update/checker.d.mts +2 -0
  300. package/dist/src/update/checker.mjs +5 -5
  301. package/dist/src/update/notifier.mjs +54 -2
  302. package/dist/src/update/service.d.mts +22 -0
  303. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  304. package/dist/src/utils/contained-filesystem.mjs +683 -0
  305. package/dist/src/utils/temporary-directory.js +36 -0
  306. package/dist/src/writing/example-generator.js +1 -1
  307. package/dist/src/writing/fidelity.js +18 -1
  308. package/package.json +32 -2
  309. package/schemas/mission-v1.schema.json +2429 -0
  310. package/schemas/models/model-catalog.v1.schema.json +301 -0
  311. package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
  312. package/schemas/models/model-policy.v1.schema.json +14 -0
  313. package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
  314. package/tools/_resolve-impl.mjs +39 -2
  315. package/tools/agents/deploy-agents.mjs +64 -23
  316. package/tools/agents/providers/base.mjs +153 -166
  317. package/tools/agents/providers/claude.mjs +8 -24
  318. package/tools/agents/providers/codex.mjs +18 -11
  319. package/tools/agents/providers/cursor.mjs +19 -18
  320. package/tools/agents/providers/grok-build.mjs +3 -1
  321. package/tools/agents/providers/grokbot.mjs +11 -12
  322. package/tools/agents/providers/hermes.mjs +62 -49
  323. package/tools/agents/providers/omp.mjs +5 -2
  324. package/tools/agents/providers/openclaw-translator.mjs +19 -5
  325. package/tools/agents/providers/openclaw.mjs +9 -4
  326. package/tools/agents/providers/pi.mjs +5 -0
  327. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  328. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  329. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  330. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  331. package/tools/agents/providers/warp.mjs +20 -26
  332. package/tools/cli/decision-offload-report.mjs +40 -0
  333. package/tools/cli/workspace-status.mjs +862 -0
  334. package/tools/plugin/package-plugins.mjs +186 -8
  335. package/tools/plugin/plugin-links.mjs +114 -0
  336. package/tools/providers/antigravity-transport.mjs +3 -1
  337. package/tools/providers/deepseek-harness-transport.mjs +360 -0
  338. package/tools/providers/omp-transport.mjs +194 -0
  339. package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
  340. package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
  341. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  342. package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
  343. package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
  344. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  345. package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
  346. package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
  347. package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
  348. package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
  349. package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
  350. package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
  351. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  352. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  353. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  354. package/tools/ralph-external/lib/worker-health.mjs +439 -0
  355. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  356. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  357. package/tools/ralph-external/process-monitor.mjs +1017 -0
  358. package/tools/ralph-external/session-launcher.mjs +5057 -0
  359. package/tools/scripts/resource-preflight.mjs +541 -0
  360. package/tools/skills/deploy-skills-codex.mjs +2 -1
  361. package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
@@ -1,3 +1,4 @@
1
+ import { validateDecisionConfig } from './decision-config.js';
1
2
  /**
2
3
  * Project-Level AIWG Config
3
4
  *
@@ -8,6 +9,18 @@
8
9
  *
9
10
  * @implements #621
10
11
  */
12
+ import { validateImportExclusions } from '../sessions/import-exclusions.js';
13
+ import { resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
14
+ export { DEFAULT_DISPATCH_HOST_THRESHOLDS, resolveDispatchHostThresholds } from './dispatch-thresholds-runtime.mjs';
15
+ import { validateIndexGraphOverrides } from './index-graph-overrides.js';
16
+ import { DEFAULT_RULES_INLINE_BUDGET_TOKENS } from '../rules/inline-budget.mjs';
17
+ export { validateRulesConfig, resolveRulesInlineBudget } from '../rules/inline-budget.mjs';
18
+ import { loadRulePinConfigs, installedRuleCapabilities, validateRulePinCapabilities } from '../rules/pins.mjs';
19
+ import Ajv from 'ajv';
20
+ import addFormats from 'ajv-formats';
21
+ import { readFileSync, existsSync } from 'node:fs';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { isCanonicalWorkerModelKey, loadProviderModelCapabilities, resolveWorkerInstallation } from '../models/provider-policy.js';
11
24
  import { readFile, writeFile, mkdir, access, readdir, rename, unlink } from 'fs/promises';
12
25
  import { createHash, randomBytes } from 'crypto';
13
26
  import { resolve, join, dirname, isAbsolute } from 'path';
@@ -162,6 +175,166 @@ export function resolveDelivery(delivery) {
162
175
  issue_comment_on_cycle: delivery?.issue_comment_on_cycle ?? true,
163
176
  };
164
177
  }
178
+ let parallelismValidator;
179
+ /** Offline shape and declaration validation. Qualification is checked at use time. */
180
+ export function validateParallelismConfig(value) {
181
+ if (value === undefined)
182
+ return [];
183
+ if (!parallelismValidator) {
184
+ const here = dirname(fileURLToPath(import.meta.url));
185
+ const candidates = [resolve(here, '../../vscode-extension/schemas/aiwg.config.v1.json'), resolve(here, '../../../vscode-extension/schemas/aiwg.config.v1.json')];
186
+ const file = candidates.find(existsSync);
187
+ if (!file)
188
+ throw new Error('Parallelism schema is unavailable');
189
+ const schema = JSON.parse(readFileSync(file, 'utf8'));
190
+ const ajv = addFormats(new Ajv({ strict: true, allErrors: true }));
191
+ parallelismValidator = ajv.compile({ ...schema.properties.parallelism, definitions: schema.definitions });
192
+ }
193
+ if (!parallelismValidator(value))
194
+ return (parallelismValidator.errors ?? []).map(error => `OG_CONFIG_REFERENCE /parallelism${error.instancePath}: ${error.message}`);
195
+ const policy = value;
196
+ const errors = [];
197
+ const error = (pointer, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/${pointer}: ${message}`); };
198
+ const builtinIds = new Set(PROVIDER_IDS.filter(id => id !== 'generic'));
199
+ for (const [id, installation] of Object.entries(policy.clis ?? {})) {
200
+ if (installation.adapter.startsWith('providers:') && !builtinIds.has(installation.adapter.slice(10)))
201
+ error(`clis/${id}/adapter`, 'Unknown provider binding');
202
+ }
203
+ for (const [id, group] of Object.entries(policy.groups ?? {})) {
204
+ const ids = new Set();
205
+ for (const member of group.members) {
206
+ if (ids.has(member.id))
207
+ error(`groups/${id}/members`, 'Duplicate member ID');
208
+ ids.add(member.id);
209
+ if (!policy.clis?.[member.cli] && !builtinIds.has(member.cli))
210
+ error(`groups/${id}/members/${member.id}/cli`, 'Unknown installation');
211
+ }
212
+ for (const member of group.fallback ?? [])
213
+ if (!ids.has(member))
214
+ error(`groups/${id}/fallback`, 'Unknown fallback member');
215
+ for (const role of group.requiredRoles ?? []) {
216
+ if (!group.members.some(m => m.enabled !== false && policy.clis?.[m.cli]?.enabled !== false && m.roles.includes(role)))
217
+ error(`groups/${id}/requiredRoles`, `No enabled member covers ${role}`);
218
+ }
219
+ }
220
+ const admission = policy.admission;
221
+ for (const group of Object.keys(admission?.groupCaps ?? {}))
222
+ if (!policy.groups?.[group])
223
+ error('admission/groupCaps', 'Unknown group');
224
+ for (const cli of Object.keys(admission?.cliCaps ?? {}))
225
+ if (!policy.clis?.[cli] && !builtinIds.has(cli))
226
+ error('admission/cliCaps', 'Unknown installation');
227
+ for (const key of Object.keys(admission?.memberCaps ?? {})) {
228
+ const [group, member, extra] = key.split('/');
229
+ if (extra !== undefined || !policy.groups?.[group]?.members.some(m => m.id === member))
230
+ error('admission/memberCaps', 'Unknown group/member');
231
+ }
232
+ for (const key of Object.keys(admission?.modelCaps ?? {}))
233
+ if (!isCanonicalWorkerModelKey(key))
234
+ error('admission/modelCaps', 'Noncanonical inference model key');
235
+ if (admission?.hostResources !== undefined) {
236
+ try {
237
+ resolveDispatchHostThresholds(undefined, admission.hostResources);
238
+ }
239
+ catch (validation) {
240
+ error('admission/hostResources', validation instanceof Error ? validation.message : String(validation));
241
+ }
242
+ }
243
+ errors.push(...validateWorkerHealthConfig(policy.health));
244
+ return errors;
245
+ }
246
+ /** Cross-field health preferences are validated offline; controls are qualified at dispatch. */
247
+ export function validateWorkerHealthConfig(health) {
248
+ if (health === undefined)
249
+ return [];
250
+ const errors = [];
251
+ const error = (field, message) => { errors.push(`OG_CONFIG_REFERENCE /parallelism/health/${field}: ${message}`); };
252
+ const checkSeconds = health.checkSeconds ?? 15;
253
+ if (checkSeconds > (health.startupSeconds ?? 60))
254
+ error('checkSeconds', 'Must not exceed startupSeconds');
255
+ if ((health.quietSeconds ?? 90) >= (health.stallSeconds ?? 300))
256
+ error('quietSeconds', 'Must be less than stallSeconds');
257
+ if ((health.cooldownSeconds ?? 120) > (health.maxCooldownSeconds ?? 900))
258
+ error('cooldownSeconds', 'Must not exceed maxCooldownSeconds');
259
+ for (const role of Object.keys(health.roleScale ?? {})) {
260
+ if (!['efficiency', 'coding', 'reasoning'].includes(role))
261
+ error('roleScale', `Unknown role ${role}`);
262
+ }
263
+ for (const field of ['modelScale', 'modelStartupSeconds']) {
264
+ for (const [key, value] of Object.entries(health[field] ?? {})) {
265
+ if (!isCanonicalWorkerModelKey(key))
266
+ error(field, 'Noncanonical inference model key');
267
+ if (field === 'modelStartupSeconds' && checkSeconds > value)
268
+ error(field, 'Model startup must cover checkSeconds');
269
+ }
270
+ }
271
+ return errors;
272
+ }
273
+ /** Resolve one enabled installation through trusted metadata, without probes.
274
+ * Implicit canonical IDs inherit current-account auth; no account/quota identity
275
+ * is invented. Profile qualification remains the invocation owner's obligation.
276
+ */
277
+ export function resolveWorkerGroupInstallation(parallelism, installationId, registry = loadProviderModelCapabilities()) {
278
+ const errors = validateParallelismConfig(parallelism);
279
+ if (errors.length)
280
+ throw new Error(errors.join('; '));
281
+ if (!parallelism.worker_dispatch)
282
+ throw new Error('OG_CONFIG_REFERENCE /parallelism/worker_dispatch: Worker dispatch is disabled');
283
+ const explicit = parallelism.clis?.[installationId];
284
+ const installation = explicit
285
+ ? structuredClone(explicit)
286
+ : { adapter: `providers:${installationId}`, enabled: true };
287
+ if (!explicit && (!PROVIDER_IDS.includes(installationId) || installationId === 'generic')) {
288
+ throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Unknown canonical installation`);
289
+ }
290
+ if (installation.enabled === false)
291
+ throw new Error(`OG_CONFIG_REFERENCE /parallelism/clis/${installationId}: Installation is disabled`);
292
+ try {
293
+ return { installation: { ...installation, enabled: true }, ...resolveWorkerInstallation(registry, installation) };
294
+ }
295
+ catch (error) {
296
+ throw new Error(`OG_ADAPTER_UNQUALIFIED /parallelism/clis/${installationId}: ${error}`);
297
+ }
298
+ }
299
+ /** Validate current model restrictions and group references before any write.
300
+ * Disabled/unbound projects retain their existing config-only edit behavior.
301
+ * This is shared validation, not Tier 1 coherent activation.
302
+ *
303
+ * `options.aiwgHome`, when provided, is passed through to `ConfigLoader`
304
+ * unchanged — it never reads or writes `process.env.AIWG_HOME`. Omitting it
305
+ * keeps `ConfigLoader`'s own default precedence (`AIWG_HOME` env, else the
306
+ * running package root).
307
+ */
308
+ export async function validateWorkerConfigCandidate(projectDir, config, options) {
309
+ const errors = validateParallelismConfig(config.parallelism);
310
+ if (errors.length)
311
+ throw new Error(errors.join('; '));
312
+ if (!config.parallelism?.worker_dispatch)
313
+ return;
314
+ const { ConfigLoader } = await import('../models/config-loader.js');
315
+ await new ConfigLoader(projectDir, options).load(config.parallelism);
316
+ }
317
+ /** Hash canonical JSON, treating absent optional model files as null. */
318
+ export function workerConfigDigest(value) {
319
+ const canonical = (node) => {
320
+ if (Array.isArray(node))
321
+ return node.map(canonical);
322
+ if (node !== null && typeof node === 'object')
323
+ return Object.fromEntries(Object.keys(node).sort().filter(key => node[key] !== undefined).map(key => [key, canonical(node[key])]));
324
+ return node ?? null;
325
+ };
326
+ return `sha256:${createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex')}`;
327
+ }
328
+ export function workerActivationDigests(config, projectModels, userModels, builtinModels) {
329
+ const candidate = structuredClone(config);
330
+ if (candidate.parallelism)
331
+ delete candidate.parallelism.activation;
332
+ return { configDigest: workerConfigDigest(candidate), projectModelsDigest: workerConfigDigest(projectModels), userModelsDigest: workerConfigDigest(userModels), builtinModelsDigest: workerConfigDigest(builtinModels) };
333
+ }
334
+ /** Pure inventory only: this does not activate, lock, synchronize, or admit work. */
335
+ export function workerActivationCopyInventory(projectDir, compatibilityConfig, userModels, builtinModels) {
336
+ return [...new Set([getConfigPath(projectDir), join(projectDir, 'models.json'), compatibilityConfig, userModels, builtinModels].map(file => resolve(file)))];
337
+ }
165
338
  /**
166
339
  * Per-provider parallelism defaults. Conservative numbers for Anthropic-backed
167
340
  * providers reflect Pro/Team-plan rate limits — operators on Enterprise tiers
@@ -235,6 +408,30 @@ export function resolveParallelism(parallelism, primaryProvider) {
235
408
  };
236
409
  if (parallelism?.rationale !== undefined)
237
410
  resolved.rationale = parallelism.rationale;
411
+ const errors = validateParallelismConfig(parallelism);
412
+ if (errors.length)
413
+ throw new Error(errors.join('; '));
414
+ if (parallelism?.worker_dispatch) {
415
+ Object.assign(resolved, Object.fromEntries(Object.entries(structuredClone(parallelism)).filter(([, value]) => value !== undefined)));
416
+ resolved.worker_mode ??= 'direct';
417
+ for (const group of Object.values(resolved.groups ?? {})) {
418
+ group.fallback ??= [];
419
+ group.requiredRoles ??= [];
420
+ group.execution = { isolation: 'worktree', sandbox: 'workspace-write', approval: 'never', network: 'deny', transport: 'shell', receipts: 'optional', ...group.execution };
421
+ for (const member of group.members) {
422
+ member.enabled ??= true;
423
+ member.priority ??= 50;
424
+ }
425
+ }
426
+ for (const cli of Object.values(resolved.clis ?? {}))
427
+ cli.enabled ??= true;
428
+ if (resolved.admission)
429
+ resolved.admission = { maxQueued: 128, queueTimeoutSeconds: 300, leaseSeconds: 60, authorityRef: 'project', ...resolved.admission };
430
+ if (resolved.budgets)
431
+ resolved.budgets = { enforcement: 'observed', unknownCost: 'deny', ...resolved.budgets };
432
+ if (resolved.retry)
433
+ resolved.retry = { maxAttempts: 3, on: ['rate-limit', 'unavailable', 'timeout'], baseDelaySeconds: 2, maxDelaySeconds: 60, ...resolved.retry };
434
+ }
238
435
  return resolved;
239
436
  }
240
437
  const GRAPH_BACKENDS = ['json', 'graphology', 'sqlite'];
@@ -265,37 +462,7 @@ export function validateIndexConfig(index) {
265
462
  errors.push(`index.graphBackend: must be one of ${GRAPH_BACKENDS.join(' | ')}`);
266
463
  }
267
464
  const isStringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === 'string');
268
- const graphOverrides = indexObject.graphOverrides;
269
- if (graphOverrides !== undefined) {
270
- if (typeof graphOverrides !== 'object' || graphOverrides === null || Array.isArray(graphOverrides)) {
271
- errors.push('index.graphOverrides: must be an object mapping supported built-in graph names to overrides');
272
- }
273
- else {
274
- for (const [name, rawOverride] of Object.entries(graphOverrides)) {
275
- const where = `index.graphOverrides.${name}`;
276
- if (name !== 'codebase') {
277
- errors.push(`${where}: unsupported built-in graph override (supported: codebase)`);
278
- continue;
279
- }
280
- if (typeof rawOverride !== 'object' || rawOverride === null || Array.isArray(rawOverride)) {
281
- errors.push(`${where}: must be an object`);
282
- continue;
283
- }
284
- const override = rawOverride;
285
- for (const field of Object.keys(override)) {
286
- if (field !== 'scanDirs' && field !== 'extensions') {
287
- errors.push(`${where}.${field}: unknown field (supported: scanDirs, extensions)`);
288
- }
289
- }
290
- if (override.scanDirs !== undefined && (!isStringArray(override.scanDirs) || override.scanDirs.length === 0)) {
291
- errors.push(`${where}.scanDirs: must be a non-empty array of strings`);
292
- }
293
- if (override.extensions !== undefined && (!isStringArray(override.extensions) || override.extensions.length === 0)) {
294
- errors.push(`${where}.extensions: must be a non-empty array of strings`);
295
- }
296
- }
297
- }
298
- }
465
+ errors.push(...validateIndexGraphOverrides(indexObject.graphOverrides));
299
466
  const graphs = indexObject.graphs;
300
467
  if (graphs === undefined)
301
468
  return errors; // index with no graphs is permissible
@@ -782,6 +949,7 @@ export function emptyConfig(providers = ['claude']) {
782
949
  threatAssessment: defaultThreatAssessmentConfig(),
783
950
  },
784
951
  artifact_outputs: defaultArtifactOutputs(),
952
+ rules: { inline_budget_tokens: DEFAULT_RULES_INLINE_BUDGET_TOKENS },
785
953
  delivery: {
786
954
  mode: 'pr-required',
787
955
  default_branch: 'main',
@@ -826,24 +994,35 @@ export function getProjectDir(ctx, args = []) {
826
994
  return targetValue || ctx?.cwd || process.cwd();
827
995
  }
828
996
  /**
829
- * Read .aiwg/aiwg.config.
997
+ * Read and independently validate .aiwg/aiwg.config EXCEPT for the
998
+ * worker-model defaults step (`validateWorkerConfigCandidate`), which needs
999
+ * its own `aiwgHome` resolution and can fail solely because the bundled
1000
+ * model defaults are unreachable. Callers that want to degrade gracefully
1001
+ * on that one failure mode (e.g. generated-context rendering) read the
1002
+ * candidate here, then call `validateWorkerConfigCandidate` themselves and
1003
+ * handle its failure explicitly. `readAiwgConfig` below is the composition
1004
+ * of both steps for callers that want the original all-or-nothing contract.
830
1005
  * Returns null if the file does not exist.
831
1006
  */
832
- export async function readAiwgConfig(projectDir) {
1007
+ export async function readAiwgConfigCandidate(projectDir) {
833
1008
  const localPath = getConfigPath(projectDir);
834
1009
  const artifactPath = projectAiwgPath(projectDir, CONFIG_FILENAME);
835
1010
  let filePath = localPath;
836
1011
  try {
837
1012
  await access(localPath);
838
1013
  }
839
- catch {
1014
+ catch (error) {
1015
+ if (error.code !== 'ENOENT')
1016
+ throw error;
840
1017
  if (artifactPath === localPath)
841
1018
  return null;
842
1019
  try {
843
1020
  await access(artifactPath);
844
1021
  filePath = artifactPath;
845
1022
  }
846
- catch {
1023
+ catch (error) {
1024
+ if (error.code !== 'ENOENT')
1025
+ throw error;
847
1026
  return null;
848
1027
  }
849
1028
  }
@@ -856,6 +1035,30 @@ export async function readAiwgConfig(projectDir) {
856
1035
  parsed.installed = {};
857
1036
  if (!parsed.scripts)
858
1037
  parsed.scripts = {};
1038
+ if (parsed.sessions !== undefined && (parsed.sessions === null || typeof parsed.sessions !== 'object' || Array.isArray(parsed.sessions)
1039
+ || (parsed.sessions.codex_roots !== undefined && (!Array.isArray(parsed.sessions.codex_roots)
1040
+ || !parsed.sessions.codex_roots.every((root) => typeof root === 'string' && root.trim().length > 0))))) {
1041
+ throw new Error('sessions.codex_roots must be an array of non-empty paths');
1042
+ }
1043
+ if (parsed.sessions?.discovery_manifests !== undefined && (!Array.isArray(parsed.sessions.discovery_manifests)
1044
+ || !parsed.sessions.discovery_manifests.every((file) => typeof file === 'string' && file.trim().length > 0))) {
1045
+ throw new Error('sessions.discovery_manifests must be an array of non-empty paths');
1046
+ }
1047
+ if (parsed.sessions?.import !== undefined) {
1048
+ const limits = parsed.sessions.import;
1049
+ if (limits === null || typeof limits !== 'object' || Array.isArray(limits)) {
1050
+ throw new Error('sessions.import must be an object');
1051
+ }
1052
+ if (limits.exclude !== undefined)
1053
+ validateImportExclusions(limits.exclude);
1054
+ for (const key of ['max_record_bytes', 'max_source_bytes']) {
1055
+ const value = limits[key];
1056
+ const minimum = key === 'max_source_bytes' ? 0 : 1;
1057
+ if (value !== undefined && (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum)) {
1058
+ throw new Error(`sessions.import.${key} must be a ${minimum ? 'positive' : 'non-negative'} integer`);
1059
+ }
1060
+ }
1061
+ }
859
1062
  const externalLinkErrors = validateExternalLinks(parsed.externalLinks);
860
1063
  if (externalLinkErrors.length > 0) {
861
1064
  throw new Error(`Invalid .aiwg/aiwg.config:\n${externalLinkErrors.join('\n')}`);
@@ -873,6 +1076,9 @@ export async function readAiwgConfig(projectDir) {
873
1076
  const threatAssessmentErrors = [
874
1077
  ...validateThreatAssessmentConfig(parsed.security?.threatAssessment),
875
1078
  ...validateProjectConfig(parsed.project),
1079
+ ...validateParallelismConfig(parsed.parallelism),
1080
+ ...validateDecisionConfig(parsed.decision),
1081
+ ...validateRulePinCapabilities(parsed.rules, installedRuleCapabilities(loadRulePinConfigs(projectDir, parsed)), loadRulePinConfigs(projectDir, parsed).user.rules?.pin_groups),
876
1082
  ];
877
1083
  if (threatAssessmentErrors.length > 0) {
878
1084
  throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
@@ -894,15 +1100,68 @@ export async function readAiwgConfig(projectDir) {
894
1100
  }
895
1101
  return parsed;
896
1102
  }
1103
+ /**
1104
+ * Read .aiwg/aiwg.config, including worker-model default validation.
1105
+ * Returns null if the file does not exist.
1106
+ *
1107
+ * `aiwgHome` (optional) resolves the bundled AIWG model-defaults location
1108
+ * passed through to `validateWorkerConfigCandidate` / `ConfigLoader`,
1109
+ * without ever reading or writing `process.env.AIWG_HOME`. Omit it to keep
1110
+ * the default precedence (`AIWG_HOME` env, else the user-home install).
1111
+ */
1112
+ export async function readAiwgConfig(projectDir, options) {
1113
+ const parsed = await readAiwgConfigCandidate(projectDir);
1114
+ if (!parsed)
1115
+ return null;
1116
+ await validateWorkerConfigCandidate(projectDir, parsed, options);
1117
+ return parsed;
1118
+ }
897
1119
  /**
898
1120
  * Write aiwg.config to the repository-local control plane. When a reachable
899
1121
  * external corpus also carries the compatibility control copy, keep it in
900
1122
  * sync so split-root health remains deterministic.
901
1123
  */
902
1124
  export async function writeAiwgConfig(projectDir, config) {
1125
+ // Uninstall is an explicit capability change. Retire project selectors for
1126
+ // removed capabilities before strict validation, and tell the operator.
1127
+ const previousFile = getConfigPath(projectDir);
1128
+ let previous = null;
1129
+ if (existsSync(previousFile)) {
1130
+ try {
1131
+ previous = JSON.parse(readFileSync(previousFile, 'utf8'));
1132
+ }
1133
+ catch (error) {
1134
+ if (!(error instanceof SyntaxError))
1135
+ throw error;
1136
+ }
1137
+ }
1138
+ const pinConfigs = loadRulePinConfigs(projectDir, config);
1139
+ const currentCapabilities = new Set(installedRuleCapabilities(pinConfigs));
1140
+ const removed = previous ? installedRuleCapabilities({ ...pinConfigs, project: previous }).filter(id => !currentCapabilities.has(id)) : [];
1141
+ if (removed.length && (config.rules?.always_load?.length || Object.keys(config.rules?.pin_groups ?? {}).length)) {
1142
+ const removedSet = new Set(removed);
1143
+ const rules = config.rules;
1144
+ const userGroups = loadRulePinConfigs(projectDir, config).user.rules?.pin_groups ?? {};
1145
+ const pinGroups = Object.fromEntries(Object.entries(rules.pin_groups ?? {})
1146
+ .map(([name, ids]) => [name, ids.filter(id => !removedSet.has(id))]).filter(([, ids]) => ids.length));
1147
+ const selectors = (rules.always_load ?? []).flatMap(selector => {
1148
+ if (selector.group && ((!Object.hasOwn(pinGroups, selector.group) && Object.hasOwn(rules.pin_groups ?? {}, selector.group))
1149
+ || (!Object.hasOwn(pinGroups, selector.group) && userGroups[selector.group]?.some(id => removedSet.has(id)))))
1150
+ return [];
1151
+ if (!selector.capabilities)
1152
+ return [selector];
1153
+ const capabilities = selector.capabilities.filter(id => !removedSet.has(id));
1154
+ return capabilities.length ? [{ ...selector, capabilities }] : [];
1155
+ });
1156
+ config = { ...config, rules: { ...config.rules, pin_groups: pinGroups, always_load: selectors } };
1157
+ console.warn(`Uninstalled ${removed.join(', ')}: their rules leave the effective pinned set; retired project capability selectors/groups. User pins remain visible as inactive warnings.`);
1158
+ }
903
1159
  const threatAssessmentErrors = [
904
1160
  ...validateThreatAssessmentConfig(config.security?.threatAssessment),
905
1161
  ...validateProjectConfig(config.project),
1162
+ ...validateParallelismConfig(config.parallelism),
1163
+ ...validateDecisionConfig(config.decision),
1164
+ ...validateRulePinCapabilities(config.rules, installedRuleCapabilities(loadRulePinConfigs(projectDir, config)), loadRulePinConfigs(projectDir, config).user.rules?.pin_groups),
906
1165
  ];
907
1166
  if (threatAssessmentErrors.length > 0) {
908
1167
  throw new Error(`Invalid .aiwg/aiwg.config:\n${threatAssessmentErrors.join('\n')}`);
@@ -916,6 +1175,7 @@ export async function writeAiwgConfig(projectDir, config) {
916
1175
  const gatesErrors = validateGatesConfig(config.gates);
917
1176
  if (gatesErrors.length > 0)
918
1177
  throw new Error(`Invalid .aiwg/aiwg.config:\n${gatesErrors.join('\n')}`);
1178
+ await validateWorkerConfigCandidate(projectDir, config);
919
1179
  const localPath = getConfigPath(projectDir);
920
1180
  const artifactDir = resolveProjectAiwgDir(projectDir);
921
1181
  const artifactPath = join(artifactDir, CONFIG_FILENAME);
@@ -1134,4 +1394,58 @@ export async function populateDeployedTo(config, projectDir) {
1134
1394
  }
1135
1395
  return config;
1136
1396
  }
1397
+ /** Shape validation alone cannot infer process/HTTP metadata or model authority. */
1398
+ export function validateWorkerInstallationBindings(parallelism, adapters) {
1399
+ const errors = [];
1400
+ for (const [id, cli] of Object.entries(parallelism.clis ?? {})) {
1401
+ if (cli.enabled === false)
1402
+ continue;
1403
+ const metadata = adapters[cli.adapter];
1404
+ if (!metadata)
1405
+ errors.push(`OG_ADAPTER_UNQUALIFIED /parallelism/clis/${id}/adapter: Unknown trusted binding`);
1406
+ else if (metadata.transport === 'http' ? (!cli.endpoint || cli.executable !== undefined) : (cli.endpoint !== undefined || !(cli.executable ?? metadata.executable)))
1407
+ errors.push(`OG_CONFIG_REFERENCE /parallelism/clis/${id}: Invalid transport binding`);
1408
+ }
1409
+ return errors;
1410
+ }
1411
+ export function validateWorkerGroupReferences(parallelism, selection, qualifiedRoles) {
1412
+ const errors = validateParallelismConfig(parallelism);
1413
+ if (errors.length)
1414
+ return errors;
1415
+ for (const [role, guidance] of Object.entries(selection.roleGuidance ?? {})) {
1416
+ for (const group of guidance.groups)
1417
+ if (!parallelism.groups?.[group])
1418
+ errors.push(`OG_CONFIG_REFERENCE /defaults/workerSelection/roleGuidance/${role}/groups: Unknown group ${group}`);
1419
+ }
1420
+ if (qualifiedRoles)
1421
+ errors.push(...validateWorkerGroupQualifications(parallelism, qualifiedRoles)
1422
+ .map(diagnostic => `${diagnostic.code} ${diagnostic.pointer}: ${diagnostic.message}`));
1423
+ return errors;
1424
+ }
1425
+ /** Structured qualification gaps retain role scope for readiness projections. */
1426
+ export function validateWorkerGroupQualifications(parallelism, qualifiedRoles) {
1427
+ const diagnostics = [];
1428
+ for (const [id, group] of Object.entries(parallelism.groups ?? {})) {
1429
+ for (const role of group.requiredRoles ?? []) {
1430
+ if (!group.members.some(m => m.enabled !== false && parallelism.clis?.[m.cli]?.enabled !== false
1431
+ && m.roles.includes(role) && qualifiedRoles[`${id}/${m.id}`]?.includes(role))) {
1432
+ diagnostics.push({ code: 'OG_ADAPTER_UNQUALIFIED', pointer: `/parallelism/groups/${id}/requiredRoles`,
1433
+ message: `No qualified route covers ${role}`, roles: [role] });
1434
+ }
1435
+ }
1436
+ }
1437
+ return diagnostics;
1438
+ }
1439
+ /** A configured hard ceiling is eligible only where a qualified control enforces it. */
1440
+ export function validateWorkerBudgetEnforcement(budgets, enforceable) {
1441
+ if (budgets?.enforcement !== 'hard')
1442
+ return [];
1443
+ const errors = [];
1444
+ for (const scope of ['perRun', 'perTask'])
1445
+ for (const dimension of ['tokens', 'costUsd', 'wallSeconds']) {
1446
+ if (budgets[scope]?.[dimension] !== undefined && !enforceable.includes(dimension))
1447
+ errors.push(`OG_BUDGET_UNMEASURABLE /parallelism/budgets/${scope}/${dimension}: No qualified enforcing control`);
1448
+ }
1449
+ return errors;
1450
+ }
1137
1451
  //# sourceMappingURL=aiwg-config.js.map