@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
@@ -8,7 +8,7 @@
8
8
  * validate — Validate all config files
9
9
  * reset [<key>] — Reset key or all config to defaults
10
10
  * path — Print the active config directory path
11
- * edit — Open config in $EDITOR
11
+ * edit [--print-path] — Open config in $EDITOR/$VISUAL, or print its path headlessly
12
12
  * gitignore — Show/check/fix .gitignore for AIWG runtime dirs
13
13
  *
14
14
  * Global flags:
@@ -19,10 +19,36 @@
19
19
  */
20
20
  import { fileURLToPath } from 'url';
21
21
  import path from 'path';
22
+ import { isDeepStrictEqual } from 'node:util';
23
+ import { normalizeProviderDefinitionId } from '../providers/provider-definitions.js';
22
24
  import { UserConfig } from './user-config.js';
23
25
  import { AiwgError, EXIT_CODES } from '../cli/errors.js';
26
+ import { isInteractive } from '../cli/env.js';
27
+ import { exitCodeForSpawnResult } from '../cli/child-process-result.js';
24
28
  import { projectControlPath, resolveProjectAiwgDir } from './project-artifacts.js';
25
29
  const _scriptDir = path.dirname(fileURLToPath(import.meta.url));
30
+ function warnStaleParallelism(cfg, projectDir, key, before) {
31
+ if (key !== 'parallelism' && !key.startsWith('parallelism.'))
32
+ return;
33
+ if (isDeepStrictEqual(before, getDottedPath(cfg, key)))
34
+ return;
35
+ const configured = (cfg.providers ?? []).map(normalizeProviderDefinitionId).filter((id) => !!id);
36
+ const recorded = Object.values(cfg.installed ?? {}).flatMap(entry => Object.keys(entry.deployedTo ?? {}))
37
+ .map(normalizeProviderDefinitionId).filter((id) => !!id).sort();
38
+ const providers = [...new Set([...configured, ...recorded])];
39
+ const targets = providers.length ? providers : ['<provider>'];
40
+ const lines = [
41
+ `WARNING: ${key} changed project configuration; generated workspace/provider guidance may now be stale.`,
42
+ `From project root ${path.resolve(projectDir)}, preview and refresh every configured or recorded provider context:`,
43
+ ...targets.flatMap(provider => [
44
+ ` aiwg regenerate --workspace --provider ${provider} --dry-run`,
45
+ ` aiwg regenerate --workspace --provider ${provider}`,
46
+ ]),
47
+ ' aiwg workspace-context doctor',
48
+ `Shared parallelism defaults and labels use primary provider '${cfg.providers?.[0] ?? 'unknown'}' (providers[0]); --provider selects the adapter/context target.`,
49
+ ];
50
+ console.error(lines.join('\n'));
51
+ }
26
52
  /**
27
53
  * Main CLI entry point for `aiwg config <subcommand> [args]`
28
54
  */
@@ -63,7 +89,7 @@ export async function main(args) {
63
89
  handlePath(config);
64
90
  break;
65
91
  case 'edit':
66
- await handleEdit(config);
92
+ await handleEdit(config, subArgs);
67
93
  break;
68
94
  case 'gitignore':
69
95
  await handleGitignore(subArgs);
@@ -86,7 +112,7 @@ export async function main(args) {
86
112
  }
87
113
  async function handleGet(config, args) {
88
114
  const isProject = args.includes('--project');
89
- const positional = args.filter(a => a !== '--project' && a !== '--user');
115
+ const positional = configPositionals(args);
90
116
  const key = positional[0];
91
117
  if (!key) {
92
118
  throw new AiwgError({
@@ -100,6 +126,11 @@ async function handleGet(config, args) {
100
126
  await projectConfigGet(key, args);
101
127
  return;
102
128
  }
129
+ if (key === 'rules' || key.startsWith('rules.')) {
130
+ const { loadRulePinConfigs } = await import('../rules/pins.mjs');
131
+ console.log(JSON.stringify(getDottedPath(loadRulePinConfigs(process.cwd(), undefined, config.getPath()).user, key)) ?? '(not set)');
132
+ return;
133
+ }
103
134
  const value = await config.get(key);
104
135
  if (value === undefined) {
105
136
  console.log(`(not set)`);
@@ -113,7 +144,7 @@ async function handleGet(config, args) {
113
144
  }
114
145
  async function handleSet(config, args) {
115
146
  const isProject = args.includes('--project');
116
- const positional = args.filter(a => a !== '--project' && a !== '--user');
147
+ const positional = configPositionals(args);
117
148
  const key = positional[0];
118
149
  const value = positional[1];
119
150
  if (!key || value === undefined) {
@@ -128,6 +159,11 @@ async function handleSet(config, args) {
128
159
  await projectConfigSet(key, value, args);
129
160
  return;
130
161
  }
162
+ if (key === 'rules' || key.startsWith('rules.')) {
163
+ await setRuleConfigKey(process.cwd(), 'user', key, value, config.getPath());
164
+ console.log(`Set --user ${key} = ${value}`);
165
+ return;
166
+ }
131
167
  await config.set(key, value);
132
168
  console.log(`Set ${key} = ${value}`);
133
169
  }
@@ -167,8 +203,14 @@ const ENUM_RULES = {
167
203
  'security.threatAssessment.mode': ['off', 'audit', 'enforce'],
168
204
  'artifact_outputs.canonical': ['aiwg'],
169
205
  'artifact_outputs.provider_native': ['disabled', 'explicit-only', 'project-default'],
206
+ // #3050: a setup/onboarding preference, not worker policy — kept out of
207
+ // the frozen `parallelism` contract fragment (B-OG-29). String enum (not
208
+ // boolean) so a future state (e.g. "snoozed") can be added without a
209
+ // breaking schema change.
210
+ 'setup.worker_dispatch_offer': ['declined'],
170
211
  };
171
212
  const BOOLEAN_FIELDS = new Set([
213
+ 'decision.enabled',
172
214
  'delivery.delete_branch_on_merge',
173
215
  'delivery.require_ci_green',
174
216
  'delivery.require_signed_commits',
@@ -178,6 +220,7 @@ const BOOLEAN_FIELDS = new Set([
178
220
  'telemetry.skill_usage.enabled',
179
221
  ]);
180
222
  const STRING_ARRAY_FIELDS = new Set([
223
+ 'providers',
181
224
  'remotes.tracker_actor.forbid_actors',
182
225
  'remotes.customer_tracker_actor.forbid_actors',
183
226
  'command_log.scopes',
@@ -186,6 +229,10 @@ const STRING_ARRAY_FIELDS = new Set([
186
229
  // Integer fields with valid range constraints. Validated at `aiwg config set`.
187
230
  // Bounds mirror the JSON schema (vscode-extension/schemas/aiwg.config.v1.json).
188
231
  const INTEGER_FIELDS = {
232
+ 'decision.ask.passes': { min: 1, max: Number.MAX_SAFE_INTEGER },
233
+ 'decision.ask.escalation_passes': { min: 1, max: Number.MAX_SAFE_INTEGER },
234
+ 'decision.ask.savings.reasoning_tokens': { min: 0, max: 1_000_000 },
235
+ 'decision.ask.savings.output_tokens': { min: 0, max: 1_000_000 },
189
236
  'parallelism.max_parallel_subagents': { min: 1, max: 50 },
190
237
  'parallelism.max_parallel_ralph_loops': { min: 1, max: 20 },
191
238
  'parallelism.max_parallel_mc_missions': { min: 1, max: 20 },
@@ -193,6 +240,7 @@ const INTEGER_FIELDS = {
193
240
  'telemetry.skill_usage.max_bytes': { min: 1024, max: 104_857_600 },
194
241
  };
195
242
  async function projectConfigGet(key, args) {
243
+ assertSafeConfigPath(key);
196
244
  const { readAiwgConfig, getProjectDir } = await import('./aiwg-config.js');
197
245
  const projectDir = getProjectDir(undefined, args);
198
246
  const cfg = await readAiwgConfig(projectDir);
@@ -204,6 +252,21 @@ async function projectConfigGet(key, args) {
204
252
  exitCode: EXIT_CODES.CONFIG,
205
253
  });
206
254
  }
255
+ // Model preferences keep their existing file owner. Inspection uses the same
256
+ // merged policy and pre-merge origins as worker resolution, never aiwg.config.
257
+ if (key === 'defaults.workerSelection' || key.startsWith('defaults.workerSelection.')) {
258
+ const { ConfigLoader } = await import('../models/config-loader.js');
259
+ const loader = new ConfigLoader(projectDir);
260
+ const models = await loader.load(cfg.parallelism);
261
+ const value = getDottedPath(models, key);
262
+ const pointer = `/${key.split('.').join('/')}`;
263
+ const origins = Object.fromEntries(Object.entries(models.workerPolicyOrigins ?? {})
264
+ .filter(([field]) => field === pointer || field.startsWith(`${pointer}/`)));
265
+ console.log(args.includes('--origins')
266
+ ? JSON.stringify({ value: value ?? null, origins, digests: loader.getInputDigests() }, null, 2)
267
+ : value === undefined ? '(not set)' : JSON.stringify(value, null, 2));
268
+ return;
269
+ }
207
270
  const value = getDottedPath(cfg, key);
208
271
  if (value === undefined) {
209
272
  console.log('(not set)');
@@ -215,7 +278,16 @@ async function projectConfigGet(key, args) {
215
278
  console.log(String(value));
216
279
  }
217
280
  }
218
- async function projectConfigSet(key, raw, args) {
281
+ async function projectConfigSet(key, raw, args, quiet = false) {
282
+ assertSafeConfigPath(key);
283
+ if (key === 'defaults' || key.startsWith('defaults.')) {
284
+ throw new AiwgError({
285
+ code: 'ERR_INVALID_KEY',
286
+ message: `Project model preferences '${key}' belong in models.json, not aiwg.config.`,
287
+ hint: 'Edit project-root models.json to change defaults, including defaults.workerSelection.',
288
+ exitCode: EXIT_CODES.USAGE,
289
+ });
290
+ }
219
291
  const { readAiwgConfig, writeAiwgConfig, getProjectDir, emptyConfig, validateExternalLinks, } = await import('./aiwg-config.js');
220
292
  const projectDir = getProjectDir(undefined, args);
221
293
  // Validate enum fields before writing. Deprecated spellings normalize forward with a
@@ -237,6 +309,12 @@ async function projectConfigSet(key, raw, args) {
237
309
  }
238
310
  // Coerce booleans for known boolean fields
239
311
  let value = raw;
312
+ if (key === 'rules' || key.startsWith('rules.') || key === 'parallelism' || /^parallelism\.(worker_dispatch|worker_mode|groups|clis|admission|budgets|health|retry|activation)(\.|$)/.test(key)) {
313
+ try {
314
+ value = JSON.parse(raw);
315
+ }
316
+ catch { /* String leaves are validated by the whole-object writer. */ }
317
+ }
240
318
  if (/^externalLinks\.[^.]+$/.test(key) || key === 'security.threatAssessment') {
241
319
  try {
242
320
  value = JSON.parse(raw);
@@ -267,10 +345,24 @@ async function projectConfigSet(key, raw, args) {
267
345
  }
268
346
  }
269
347
  if (STRING_ARRAY_FIELDS.has(key)) {
270
- value = raw
271
- .split(',')
272
- .map(item => item.trim())
273
- .filter(Boolean);
348
+ let items = raw.split(',');
349
+ if (raw.trimStart().startsWith('[')) {
350
+ try {
351
+ items = JSON.parse(raw);
352
+ }
353
+ catch {
354
+ items = undefined;
355
+ }
356
+ if (!Array.isArray(items) || items.some(item => typeof item !== 'string')) {
357
+ throw new AiwgError({
358
+ code: 'ERR_INVALID_VALUE',
359
+ message: `${key} must be a comma-separated list or a JSON array of strings`,
360
+ hint: `Try: aiwg config set --project ${key} '["a","b"]' (or a,b)`,
361
+ exitCode: EXIT_CODES.USAGE,
362
+ });
363
+ }
364
+ }
365
+ value = items.map(item => item.trim()).filter(Boolean);
274
366
  if (key === 'command_log.scopes' || key === 'telemetry.skill_usage.scopes') {
275
367
  const invalid = value.filter(item => item !== 'project' && item !== 'global');
276
368
  if (invalid.length > 0) {
@@ -287,7 +379,7 @@ async function projectConfigSet(key, raw, args) {
287
379
  const intRule = INTEGER_FIELDS[key];
288
380
  if (intRule) {
289
381
  const n = Number(raw);
290
- if (!Number.isInteger(n) || n < intRule.min || n > intRule.max) {
382
+ if (!raw.trim() || !Number.isInteger(n) || n < intRule.min || n > intRule.max) {
291
383
  throw new AiwgError({
292
384
  code: 'ERR_INVALID_VALUE',
293
385
  message: `${key} must be an integer between ${intRule.min} and ${intRule.max}, got '${raw}'`,
@@ -297,9 +389,32 @@ async function projectConfigSet(key, raw, args) {
297
389
  }
298
390
  value = n;
299
391
  }
392
+ if (key === 'decision.ask.min_agreement' || key === 'decision.ask.agreement_confidence_floor') {
393
+ const n = Number(raw);
394
+ if (!raw.trim() || !Number.isFinite(n) || n < 0 || n > 1) {
395
+ throw new AiwgError({
396
+ code: 'ERR_INVALID_VALUE',
397
+ message: `${key} must be a probability from 0 to 1, got '${raw}'`,
398
+ exitCode: EXIT_CODES.USAGE,
399
+ });
400
+ }
401
+ value = n;
402
+ }
403
+ if (key === 'decision.ask.cost.llm_per_mtok' || key === 'decision.ask.cost.jev_per_mtok') {
404
+ const n = Number(raw);
405
+ if (!raw.trim() || !Number.isFinite(n) || n < 0 || n > 1_000_000) {
406
+ throw new AiwgError({ code: 'ERR_INVALID_VALUE', message: `${key} must be a non-negative number <= 1000000`, exitCode: EXIT_CODES.USAGE });
407
+ }
408
+ value = n;
409
+ }
300
410
  // Read-modify-write — preserve unrelated fields. emptyConfig() seeds the
301
411
  // base shape when no config file exists yet (e.g. brand-new project).
302
- const cfg = (await readAiwgConfig(projectDir)) ?? emptyConfig();
412
+ const { existsSync, readFileSync } = await import('node:fs');
413
+ const file = projectControlPath(projectDir, 'aiwg.config');
414
+ const cfg = key === 'rules' || key.startsWith('rules.')
415
+ ? (existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : emptyConfig())
416
+ : (await readAiwgConfig(projectDir)) ?? emptyConfig();
417
+ const before = structuredClone(getDottedPath(cfg, key));
303
418
  setDottedPath(cfg, key, value);
304
419
  const externalLinkErrors = validateExternalLinks(cfg.externalLinks);
305
420
  if (externalLinkErrors.length > 0) {
@@ -321,7 +436,10 @@ async function projectConfigSet(key, raw, args) {
321
436
  });
322
437
  }
323
438
  await writeAiwgConfig(projectDir, cfg);
324
- console.log(`Set --project ${key} = ${raw}`);
439
+ if (!quiet)
440
+ console.log(`Set --project ${key} = ${raw}`);
441
+ if (!quiet)
442
+ warnStaleParallelism(cfg, projectDir, key, before);
325
443
  }
326
444
  /** Read a dotted path from a nested record. Returns undefined when any segment is missing. */
327
445
  function getDottedPath(obj, key) {
@@ -336,6 +454,7 @@ function getDottedPath(obj, key) {
336
454
  }
337
455
  /** Write a dotted path into a nested record, creating intermediate objects. */
338
456
  function setDottedPath(obj, key, value) {
457
+ assertSafeConfigPath(key);
339
458
  const parts = key.split('.');
340
459
  const last = parts.pop();
341
460
  let cur = obj;
@@ -348,6 +467,25 @@ function setDottedPath(obj, key, value) {
348
467
  }
349
468
  cur[last] = value;
350
469
  }
470
+ function assertSafeConfigPath(key) {
471
+ if (key.split('.').some(part => !part || ['__proto__', 'prototype', 'constructor'].includes(part))) {
472
+ throw new AiwgError({ code: 'ERR_INVALID_KEY', message: 'Unsafe configuration path', exitCode: EXIT_CODES.USAGE });
473
+ }
474
+ }
475
+ /** Scope/inspection flags are not dotted keys, irrespective of flag order. */
476
+ function configPositionals(args) {
477
+ const result = [];
478
+ for (let i = 0; i < args.length; i++) {
479
+ if (['--target', '--prefix'].includes(args[i])) {
480
+ i++;
481
+ continue;
482
+ }
483
+ if (['--project', '--user', '--origins'].includes(args[i]))
484
+ continue;
485
+ result.push(args[i]);
486
+ }
487
+ return result;
488
+ }
351
489
  async function handleList(config) {
352
490
  const allConfig = await config.list();
353
491
  console.log(`Config directory: ${config.getPath()}\n`);
@@ -368,6 +506,16 @@ async function handleValidate(config, args) {
368
506
  return;
369
507
  }
370
508
  const issues = await config.validate();
509
+ try {
510
+ const { loadRulePinConfigs, installedRuleCapabilities, validateRulePinCapabilities } = await import('../rules/pins.mjs');
511
+ const configs = loadRulePinConfigs(process.cwd(), undefined, config.getPath());
512
+ for (const message of validateRulePinCapabilities(configs.user.rules, installedRuleCapabilities(configs), configs.project.rules?.pin_groups)) {
513
+ issues.push({ file: 'aiwg.config', severity: 'error', message });
514
+ }
515
+ }
516
+ catch (error) {
517
+ issues.push({ file: 'aiwg.config', severity: 'error', message: error instanceof Error ? error.message : String(error) });
518
+ }
371
519
  console.log(`Config directory: ${config.getPath()}\n`);
372
520
  if (issues.length === 0) {
373
521
  console.log('✓ All config files valid');
@@ -392,7 +540,7 @@ async function handleValidate(config, args) {
392
540
  }
393
541
  }
394
542
  async function handleProjectValidate(args) {
395
- const { getProjectDir, readAiwgConfig, resolveIssueLabels, validateExternalLinks, validateIndexConfig, validateIssueLabels, } = await import('./aiwg-config.js');
543
+ const { getProjectDir, readAiwgConfig, resolveIssueLabels, validateExternalLinks, validateIndexConfig, validateIssueLabels, validateWorkerConfigCandidate, } = await import('./aiwg-config.js');
396
544
  const projectDir = getProjectDir(undefined, args);
397
545
  const cfg = await readAiwgConfig(projectDir);
398
546
  if (!cfg) {
@@ -403,6 +551,7 @@ async function handleProjectValidate(args) {
403
551
  exitCode: EXIT_CODES.CONFIG,
404
552
  });
405
553
  }
554
+ await validateWorkerConfigCandidate(projectDir, cfg);
406
555
  const providerIndex = args.indexOf('--provider');
407
556
  const providerArg = providerIndex >= 0 ? args[providerIndex + 1] : undefined;
408
557
  const allowedProviders = ['gitea', 'github', 'local'];
@@ -463,7 +612,7 @@ async function handleProjectValidate(args) {
463
612
  }
464
613
  async function handleReset(config, args) {
465
614
  const isProject = args.includes('--project');
466
- const positional = args.filter(a => a !== '--project');
615
+ const positional = configPositionals(args);
467
616
  const key = positional[0];
468
617
  if (isProject) {
469
618
  await projectConfigReset(key, args);
@@ -496,6 +645,7 @@ async function projectConfigReset(key, args) {
496
645
  exitCode: EXIT_CODES.USAGE,
497
646
  });
498
647
  }
648
+ assertSafeConfigPath(key);
499
649
  const projectDir = getProjectDir(undefined, args);
500
650
  const cfg = await readAiwgConfig(projectDir);
501
651
  if (!cfg) {
@@ -508,6 +658,7 @@ async function projectConfigReset(key, args) {
508
658
  }
509
659
  const primary = cfg.providers[0];
510
660
  const defaults = getProviderParallelismDefaults(primary);
661
+ const before = structuredClone(getDottedPath(cfg, key));
511
662
  if (key === 'parallelism') {
512
663
  cfg.parallelism = {
513
664
  max_parallel_subagents: defaults.max_parallel_subagents,
@@ -517,15 +668,31 @@ async function projectConfigReset(key, args) {
517
668
  };
518
669
  await writeAiwgConfig(projectDir, cfg);
519
670
  console.log(`Reset --project parallelism to provider default (${primary ?? 'unknown'})`);
671
+ warnStaleParallelism(cfg, projectDir, key, before);
520
672
  return;
521
673
  }
522
674
  if (key.startsWith('parallelism.')) {
675
+ const workerField = key.split('.')[1];
676
+ if (['worker_dispatch', 'worker_mode', 'clis', 'groups', 'admission', 'budgets', 'retry', 'health', 'activation'].includes(workerField)) {
677
+ const parts = key.split('.').slice(1);
678
+ const last = parts.pop();
679
+ let current = cfg.parallelism;
680
+ for (const part of parts)
681
+ current = current?.[part];
682
+ if (current)
683
+ delete current[last];
684
+ await writeAiwgConfig(projectDir, cfg);
685
+ console.log(`Reset --project ${key}`);
686
+ warnStaleParallelism(cfg, projectDir, key, before);
687
+ return;
688
+ }
523
689
  const field = key.slice('parallelism.'.length);
524
690
  if (field in defaults) {
525
691
  cfg.parallelism = cfg.parallelism ?? {};
526
692
  cfg.parallelism[field] = defaults[field];
527
693
  await writeAiwgConfig(projectDir, cfg);
528
694
  console.log(`Reset --project ${key} to provider default (${defaults[field]})`);
695
+ warnStaleParallelism(cfg, projectDir, key, before);
529
696
  return;
530
697
  }
531
698
  }
@@ -539,13 +706,40 @@ async function projectConfigReset(key, args) {
539
706
  function handlePath(config) {
540
707
  console.log(config.getPath());
541
708
  }
542
- async function handleEdit(config) {
543
- const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
709
+ /**
710
+ * `aiwg config edit [--print-path]` (#3062). `--print-path` is the headless
711
+ * equivalent: it prints the resolved config file path and exits, so scripts
712
+ * and agents can read/edit the file themselves (or compose with
713
+ * `aiwg config get`/`set`) instead of needing $EDITOR. Without a terminal
714
+ * and without `--print-path`, this refuses instead of spawning an editor
715
+ * that would hang waiting for input nobody can supply.
716
+ */
717
+ async function handleEdit(config, args = []) {
544
718
  const configPath = `${config.getPath()}/config.yaml`;
719
+ if (args.includes('--print-path')) {
720
+ // Ensure the config file exists before pointing a caller at it.
721
+ await config.ensureDir();
722
+ console.log(configPath);
723
+ return;
724
+ }
725
+ if (!isInteractive()) {
726
+ throw new AiwgError({
727
+ code: 'ERR_NON_INTERACTIVE_EDIT',
728
+ message: 'aiwg config edit requires an interactive terminal to open $EDITOR/$VISUAL.',
729
+ hint: 'Use `aiwg config set <key> <value>` to change a value headlessly, or `aiwg config edit --print-path` to get the file path and edit it yourself.',
730
+ exitCode: EXIT_CODES.USAGE,
731
+ });
732
+ }
733
+ const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
545
734
  // Ensure the config file exists before opening
546
735
  await config.ensureDir();
547
736
  const { execSync } = await import('child_process');
548
737
  try {
738
+ // sync-spawn: interactive editor-launch — launches $EDITOR/$VISUAL on the
739
+ // config file; the user interacts with it directly (see tools/testing/
740
+ // sync-spawn-allowlist.json). Reached only when isInteractive() is true
741
+ // (#3062) — the no-terminal path above refuses before spawning anything.
742
+ // Not a candidate for a bounded timeout.
549
743
  execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
550
744
  }
551
745
  catch (err) {
@@ -584,7 +778,7 @@ For project-level config: aiwg config show --project [--json]
584
778
  }
585
779
  // Lazy-import to keep startup cost low for unrelated subcommands.
586
780
  const { readAiwgConfig, resolveRemotes, getProjectDir } = await import('./aiwg-config.js');
587
- const { spawnSync } = await import('child_process');
781
+ const { boundedSpawnSync } = await import('../process/bounded-child-process.mjs');
588
782
  const projectDir = getProjectDir(undefined, args);
589
783
  const cfg = await readAiwgConfig(projectDir);
590
784
  if (!cfg) {
@@ -598,7 +792,7 @@ For project-level config: aiwg config show --project [--json]
598
792
  const resolvedRemotes = resolveRemotes(cfg.remotes);
599
793
  // Resolve each declared remote name to its URL via git. Best-effort.
600
794
  function getUrl(name) {
601
- const r = spawnSync('git', ['-C', projectDir, 'remote', 'get-url', name], {
795
+ const r = boundedSpawnSync('git', ['-C', projectDir, 'remote', 'get-url', name], 'quick', {
602
796
  encoding: 'utf-8',
603
797
  stdio: ['ignore', 'pipe', 'ignore'],
604
798
  });
@@ -691,14 +885,24 @@ For project-level config: aiwg config show --project [--json]
691
885
  console.log('');
692
886
  }
693
887
  async function handleGitignore(args) {
694
- const { spawnSync } = await import('child_process');
888
+ const { boundedSpawnSync } = await import('../process/bounded-child-process.mjs');
695
889
  // Locate the gitignore CLI script relative to this compiled module
696
890
  // (#1228). At runtime this module lives at `dist/src/config/cli.js`,
697
891
  // so we need three `..` segments to reach the package root where
698
892
  // `tools/cli/` is shipped (see package.json `files`).
699
893
  const scriptPath = path.resolve(_scriptDir, '../../../tools/cli/config-gitignore.mjs');
700
- const result = spawnSync(process.execPath, [scriptPath, ...args], { stdio: 'inherit' });
894
+ // Local script editing .gitignore blocks; quick tier (#3062).
895
+ const result = boundedSpawnSync(process.execPath, [scriptPath, ...args], 'quick', { stdio: 'inherit' });
701
896
  if (result.status !== 0) {
897
+ // #3062 review item 7: already correctly treats a null status
898
+ // (timeout/signal) as failure (`process.exit(result.status ?? 1)`
899
+ // below); surface the bounded helper's diagnostic too — `stdio:
900
+ // 'inherit'` means a normal script failure already printed its own
901
+ // output, but a bounded-helper timeout/kill would otherwise never be
902
+ // reported anywhere.
903
+ const { message } = exitCodeForSpawnResult(result, 'aiwg config gitignore');
904
+ if (message)
905
+ console.error(message);
702
906
  process.exit(result.status ?? 1);
703
907
  }
704
908
  }
@@ -707,8 +911,8 @@ function printUsage() {
707
911
 
708
912
  Subcommands:
709
913
  get <key> Read a user config value
710
- get --project <key> Read a project config value (default .aiwg/aiwg.config;
711
- AIWG_ARTIFACTS_PATH may override the artifact root)
914
+ get --project <key> Read a project config value or merged defaults.workerSelection
915
+ [--origins] Include model policy sources, pointers and input digests
712
916
  set <key> <value> Write a user config value
713
917
  set --project <key> <value> Write a project config value (validates enums)
714
918
  list Show all user config
@@ -718,7 +922,8 @@ Subcommands:
718
922
  [--provider gitea|github|local] [--available-label NAME ...]
719
923
  reset [<key>] Reset key or all config to defaults
720
924
  path Print config directory path
721
- edit Open config in $EDITOR
925
+ edit Open config in $EDITOR/$VISUAL (requires a terminal)
926
+ edit --print-path Print the config file path instead of opening an editor
722
927
  gitignore Show/check/fix .gitignore AIWG entries
723
928
 
724
929
  Global flags:
@@ -735,6 +940,7 @@ Examples:
735
940
  aiwg config set --project delivery.mode pr-required
736
941
  aiwg config set --project delivery.merge_style squash
737
942
  aiwg config get --project parallelism
943
+ aiwg config get --project defaults.workerSelection --origins
738
944
  aiwg config set --project parallelism.max_parallel_subagents 6
739
945
  aiwg config reset --project parallelism
740
946
  aiwg config validate
@@ -745,4 +951,31 @@ Examples:
745
951
  aiwg config gitignore --fix
746
952
  aiwg config gitignore --check`);
747
953
  }
954
+ /** Same configuration owner and validation used by the rules command wrappers. */
955
+ export async function setRuleConfigKey(cwd, source, key, raw, userConfigDir) {
956
+ assertSafeConfigPath(key);
957
+ if (source === 'project') {
958
+ await projectConfigSet(key, raw, ['--project', '--target', cwd], true);
959
+ return;
960
+ }
961
+ const { loadRulePinConfigs, rulePinConfigPath, installedRuleCapabilities, validateRulePinCapabilities } = await import('../rules/pins.mjs');
962
+ const { mkdir, writeFile, rename } = await import('node:fs/promises');
963
+ const configs = loadRulePinConfigs(cwd, undefined, userConfigDir);
964
+ let value;
965
+ try {
966
+ value = JSON.parse(raw);
967
+ }
968
+ catch {
969
+ value = raw;
970
+ }
971
+ setDottedPath(configs.user, key, value);
972
+ const errors = validateRulePinCapabilities(configs.user.rules, installedRuleCapabilities(configs), configs.project.rules?.pin_groups);
973
+ if (errors.length)
974
+ throw new Error(errors.join('\n'));
975
+ const file = rulePinConfigPath(cwd, 'user', userConfigDir);
976
+ await mkdir(path.dirname(file), { recursive: true });
977
+ const temp = `${file}.${process.pid}.tmp`;
978
+ await writeFile(temp, JSON.stringify(configs.user, null, 2) + '\n');
979
+ await rename(temp, file);
980
+ }
748
981
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,106 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseYamlSimple } from './user-config.js';
4
+ import { resolveUserConfigDir } from './user-config-dir.mjs';
5
+ export function validateDecisionConfig(value) {
6
+ if (value === undefined)
7
+ return [];
8
+ if (!value || typeof value !== 'object' || Array.isArray(value))
9
+ return ['decision must be an object'];
10
+ const errors = [];
11
+ const { enabled, ask, offload_report: report } = value;
12
+ if (enabled !== undefined && typeof enabled !== 'boolean')
13
+ errors.push('decision.enabled must be a boolean');
14
+ if (report !== undefined) {
15
+ if (!report || typeof report !== 'object' || Array.isArray(report))
16
+ errors.push('decision.offload_report must be an object');
17
+ else {
18
+ for (const key of ['window_hours', 'minimum_eligible', 'repeated_fallbacks']) {
19
+ const configured = report[key];
20
+ if (configured !== undefined && (!Number.isSafeInteger(configured) || configured < (key === 'minimum_eligible' ? 0 : 1))) {
21
+ errors.push(`decision.offload_report.${key} must be a ${key === 'minimum_eligible' ? 'non-negative' : 'positive'} safe integer`);
22
+ }
23
+ }
24
+ if (report.minimum_attempt_rate !== undefined && (typeof report.minimum_attempt_rate !== 'number'
25
+ || !Number.isFinite(report.minimum_attempt_rate) || report.minimum_attempt_rate < 0 || report.minimum_attempt_rate > 1)) {
26
+ errors.push('decision.offload_report.minimum_attempt_rate must be a probability from 0 to 1');
27
+ }
28
+ for (const key of Object.keys(report))
29
+ if (!['window_hours', 'minimum_eligible', 'minimum_attempt_rate', 'repeated_fallbacks'].includes(key)) {
30
+ errors.push(`unknown decision.offload_report.${key}`);
31
+ }
32
+ }
33
+ }
34
+ if (ask === undefined)
35
+ return errors;
36
+ if (!ask || typeof ask !== 'object' || Array.isArray(ask))
37
+ return [...errors, 'decision.ask must be an object'];
38
+ const sampling = [];
39
+ for (const key of ['passes', 'escalation_passes']) {
40
+ if (ask[key] !== undefined && (!Number.isSafeInteger(ask[key]) || ask[key] <= 0)) {
41
+ sampling.push(`decision.ask.${key} must be a positive safe integer`);
42
+ }
43
+ }
44
+ if (!sampling.length && ask.escalation_passes !== undefined && ask.escalation_passes < (ask.passes ?? 5)) {
45
+ sampling.push('decision.ask.escalation_passes must be >= decision.ask.passes');
46
+ }
47
+ for (const key of ['min_agreement', 'agreement_confidence_floor']) {
48
+ if (ask[key] !== undefined && (typeof ask[key] !== 'number' || !Number.isFinite(ask[key]) || ask[key] < 0 || ask[key] > 1)) {
49
+ sampling.push(`decision.ask.${key} must be a probability from 0 to 1`);
50
+ }
51
+ }
52
+ for (const section of ['cost', 'savings']) {
53
+ const settings = ask[section];
54
+ if (settings === undefined)
55
+ continue;
56
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
57
+ sampling.push(`decision.ask.${section} must be an object`);
58
+ continue;
59
+ }
60
+ const allowed = section === 'cost' ? ['llm_per_mtok', 'jev_per_mtok'] : ['caller_model_class', 'reasoning_tokens', 'output_tokens'];
61
+ for (const [key, value] of Object.entries(settings)) {
62
+ if (!allowed.includes(key))
63
+ sampling.push(`unknown decision.ask.${section}.${key}`);
64
+ else if (key === 'caller_model_class') {
65
+ if (value !== 'standard' && value !== 'reasoning')
66
+ sampling.push('decision.ask.savings.caller_model_class must be standard or reasoning');
67
+ }
68
+ else if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1_000_000
69
+ || (section === 'savings' && !Number.isSafeInteger(value))) {
70
+ sampling.push(`decision.ask.${section}.${key} must be a non-negative ${section === 'savings' ? 'integer' : 'number'} <= 1000000`);
71
+ }
72
+ }
73
+ }
74
+ return [...errors, ...sampling];
75
+ }
76
+ function readEnabled(filePath, yaml = false) {
77
+ let content;
78
+ try {
79
+ content = readFileSync(filePath, 'utf8');
80
+ }
81
+ catch (error) {
82
+ if (error.code === 'ENOENT')
83
+ return undefined;
84
+ throw error;
85
+ }
86
+ const config = (yaml ? parseYamlSimple(content) : JSON.parse(content));
87
+ const errors = validateDecisionConfig(config.decision);
88
+ if (errors.length)
89
+ throw new Error(`${filePath}: ${errors.join('; ')}`);
90
+ return config.decision?.enabled;
91
+ }
92
+ /** Resolve only enablement; never read credentials or probe a backend. */
93
+ export function resolveDecisionEnablement(options = {}) {
94
+ const env = options.env ?? process.env;
95
+ if (env.AIWG_DECISION_ENABLED === '0' || env.AIWG_DECISION_ENABLED === '1') {
96
+ return { enabled: env.AIWG_DECISION_ENABLED === '1', source: 'env' };
97
+ }
98
+ const project = readEnabled(path.join(options.cwd ?? process.cwd(), '.aiwg', 'aiwg.config'));
99
+ if (project !== undefined)
100
+ return { enabled: project, source: 'project' };
101
+ const user = readEnabled(path.join(resolveUserConfigDir({ env, homeDir: options.homeDir }), 'config.yaml'), true);
102
+ if (user !== undefined)
103
+ return { enabled: user, source: 'user' };
104
+ return { enabled: false, source: 'default' };
105
+ }
106
+ //# sourceMappingURL=decision-config.js.map
@@ -0,0 +1,8 @@
1
+ import type { DispatchHostThresholds } from './aiwg-config.js';
2
+
3
+ export const DEFAULT_DISPATCH_HOST_THRESHOLDS: Readonly<Required<Omit<DispatchHostThresholds, 'max_workers'>>>;
4
+ export function resolveDispatchHostThresholds(
5
+ buildDispatch?: DispatchHostThresholds,
6
+ admissionHostResources?: DispatchHostThresholds,
7
+ overlay?: DispatchHostThresholds,
8
+ ): Required<Omit<DispatchHostThresholds, 'max_workers'>> & Pick<DispatchHostThresholds, 'max_workers'>;