@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
@@ -11,10 +11,19 @@
11
11
  import realFs from 'fs';
12
12
  import path from 'path';
13
13
  import os from 'os';
14
+ import yaml from 'yaml';
14
15
  import { classifyModelRole } from './model-role.mjs';
15
16
  import { createHash } from 'crypto';
16
17
  import { createRequire } from 'module';
17
- import { execSync as nodeExecSync } from 'child_process';
18
+ import { importImpl } from '../../_resolve-impl.mjs';
19
+
20
+ const { parseRuleFrontmatter, inferRuleActivation } = await importImpl(import.meta.url, 'rules/activation.mjs');
21
+ const { resolveRulesInlineBudget, DEFAULT_RULES_INLINE_BUDGET_TOKENS } = await importImpl(import.meta.url, 'rules/inline-budget.mjs');
22
+ export { DEFAULT_RULES_INLINE_BUDGET_TOKENS, parseRuleFrontmatter as parseRuleActivation };
23
+ const { boundedExecFileSync } = await importImpl(import.meta.url, 'process/bounded-child-process.mjs');
24
+ const { extractFrameworkSlug, sameSkillOwner, canDeploySkill, pruneRecordedSkillFiles, releaseSkillOwnership } =
25
+ await importImpl(import.meta.url, 'providers/skill-ownership.mjs');
26
+ export { extractFrameworkSlug, sameSkillOwner, pruneRecordedSkillFiles, releaseSkillOwnership };
18
27
 
19
28
  // Use graceful-fs to prevent EMFILE crashes on systems with low ulimit.
20
29
  // graceful-fs queues open() calls when FD pressure is detected and retries
@@ -240,8 +249,9 @@ export function updateSidecarManifest(dir, deployedEntries, opts) {
240
249
  const existing = readSidecarManifest(dir) || { managed: {} };
241
250
 
242
251
  for (const entry of deployedEntries) {
243
- const { filename, hash, frameworkSlug, kind } = entry;
252
+ const { filename, hash, frameworkSlug, kind, sourcePath } = entry;
244
253
  const sidecarEntry = { hash: `sha256:${hash}`, source, version };
254
+ if (sourcePath) sidecarEntry.sourcePath = sourcePath;
245
255
  if (frameworkSlug) sidecarEntry.frameworkSlug = frameworkSlug;
246
256
  // `kind` marks artifacts whose lifecycle is governed elsewhere — currently
247
257
  // only `skill-command` wrappers, which follow their source skill (#2507).
@@ -252,26 +262,6 @@ export function updateSidecarManifest(dir, deployedEntries, opts) {
252
262
  writeSidecarManifest(dir, existing, dryRun);
253
263
  }
254
264
 
255
- // ============================================================================
256
- // Cross-Framework Collision Detection (#1169)
257
- // ============================================================================
258
-
259
- /**
260
- * Extract the framework slug from a source file path.
261
- *
262
- * Recognizes paths under `agentic/code/frameworks/<slug>/...` and
263
- * `agentic/code/addons/<slug>/...`. Returns null for paths outside
264
- * those namespaces (operator-authored bundles, addons under
265
- * `.aiwg/addons/`, etc.) — those don't get collision-tracked.
266
- */
267
- export function extractFrameworkSlug(srcPath) {
268
- if (typeof srcPath !== 'string') return null;
269
- // Normalize separators for cross-platform matching
270
- const normalized = srcPath.replace(/\\/g, '/');
271
- const m = normalized.match(/agentic\/code\/(?:frameworks|addons)\/([^/]+)\//);
272
- return m ? m[1] : null;
273
- }
274
-
275
265
  // ============================================================================
276
266
  // Model Configuration
277
267
  // ============================================================================
@@ -376,41 +366,38 @@ const PROVIDER_TO_PLATFORM = {
376
366
 
377
367
  /**
378
368
  * Parse the platforms: field from a SKILL.md frontmatter block.
379
- * Handles both inline array (platforms: [a, b]) and multi-line list formats.
380
- * Returns null if no platforms field is present (= deploy to all providers).
381
- * Returns an empty array only if the field is explicitly empty.
369
+ * Accepts a string or a list of strings; all alone means unrestricted.
370
+ * Returns null if platforms is missing, or no frontmatter is present.
371
+ * Returns an empty array for malformed frontmatter or invalid platforms.
382
372
  */
383
373
  export function parseSkillPlatforms(content) {
384
- const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
385
- if (!fmMatch) return null;
386
-
387
- const fm = fmMatch[1];
388
-
389
- // Inline array: platforms: [claude-code, codex] or platforms: [all]
390
- const inlineMatch = fm.match(/^platforms:\s*\[([^\]]*)\]/m);
391
- if (inlineMatch) {
392
- const items = inlineMatch[1].split(',').map(s => s.trim()).filter(Boolean);
393
- if (items.length === 0 || (items.length === 1 && items[0] === 'all')) return null;
394
- return items;
374
+ // A leading UTF-8 BOM must not hide the frontmatter (and its restrictions).
375
+ if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
376
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)^---[ \t]*(?:\r?\n|$)/m);
377
+ if (!fmMatch || fmMatch.index !== 0) {
378
+ return /^---[ \t]*(?:\r?\n|$)/.test(content) ? [] : null;
395
379
  }
396
380
 
397
- // Multi-line list:
398
- // platforms:
399
- // - claude-code
400
- // - hermes
401
- const multiMatch = fm.match(/^platforms:\s*\n((?:[ \t]+-[ \t]+\S[^\n]*\n?)+)/m);
402
- if (multiMatch) {
403
- const items = multiMatch[1]
404
- .split('\n')
405
- .map(line => line.match(/^[ \t]+-[ \t]+(\S+)/)?.[1])
406
- .filter(Boolean);
407
- return items.length > 0 ? items : null;
381
+ let metadata;
382
+ try {
383
+ const document = yaml.parseDocument(fmMatch[1]);
384
+ if (document.errors.length > 0) return [];
385
+ if (!document.contents) return null; // Empty frontmatter has no restrictions.
386
+ metadata = document.toJS();
387
+ } catch {
388
+ return [];
408
389
  }
390
+ if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return [];
391
+ // The yaml package leaves merge keys unresolved, so platforms could arrive only
392
+ // through `<<`; without an explicit key that is ambiguous, so fail closed.
393
+ if (!Object.hasOwn(metadata, 'platforms')) return Object.hasOwn(metadata, '<<') ? [] : null;
409
394
 
410
- // platforms: key present but empty
411
- if (/^platforms:\s*$/m.test(fm)) return null;
412
-
413
- return null; // Field absent = deploy to all
395
+ const platforms = metadata.platforms;
396
+ const items = typeof platforms === 'string' ? [platforms] : platforms;
397
+ if (!Array.isArray(items) || !items.every(item => typeof item === 'string')) return [];
398
+ const normalized = items.map(item => item.trim());
399
+ if (normalized.some(item => !item)) return [];
400
+ return normalized.length === 1 && normalized[0] === 'all' ? null : normalized;
414
401
  }
415
402
 
416
403
  /**
@@ -762,7 +749,7 @@ export function deployFiles(files, destDir, opts, transformFn) {
762
749
 
763
750
  // Read and transform source content (needed for content-equality check
764
751
  // and the collision-vs-duplicate distinction)
765
- const srcContent = fs.readFileSync(f, 'utf8');
752
+ const srcContent = injectBoundRules(fs.readFileSync(f, 'utf8'), f, opts);
766
753
  let transformedContent = transformFn ? transformFn(f, srcContent, opts) : srcContent;
767
754
 
768
755
  // Rule triggers are index metadata, not something the reading agent needs.
@@ -901,7 +888,7 @@ export function deployFiles(files, destDir, opts, transformFn) {
901
888
  if (dryRun) console.log(`[dry-run] deploy ${a.src} -> ${a.dest} (${a.reason})`);
902
889
  else writeFile(a.dest, a.content, false);
903
890
  if (verbose) console.log(`deployed ${path.basename(a.src)} -> ${path.relative(process.cwd(), a.dest)} (${a.reason})`);
904
- deployedEntries.push({ filename: path.basename(a.dest), hash: a.hash, frameworkSlug: a.frameworkSlug });
891
+ deployedEntries.push({ filename: path.basename(a.dest), hash: a.hash, frameworkSlug: a.frameworkSlug, sourcePath: path.resolve(a.src) });
905
892
  } else if (a.type === 'skip') {
906
893
  if (verbose) console.log(`skip (${a.reason}): ${path.basename(a.dest)}`);
907
894
  // Preserve existing sidecar entries for skipped files
@@ -1113,7 +1100,7 @@ export function deploySkillsWithKernelRouting(
1113
1100
  // Standard-dir cleanup (#1217): when we're NOT copying standard
1114
1101
  // skills, anything AIWG-named under standardDestDir is a legacy
1115
1102
  // mirror from a rc.13-or-earlier deploy. Prune to clean up.
1116
- let prunedFromStandardDir = 0;
1103
+ let prunedFromStandardDir = pruneMissingStandardSkills(standardDestDir, opts);
1117
1104
  if (
1118
1105
  !copyStandardSkills &&
1119
1106
  standardDestDir &&
@@ -1550,6 +1537,32 @@ export function pruneStaleAiwgFiles(destDir, desiredStems, opts = {}) {
1550
1537
  return removed;
1551
1538
  }
1552
1539
 
1540
+ /** Source-scoped cleanup also works when a removed standard skill is absent from this deploy's input. */
1541
+ function pruneMissingStandardSkills(destDir, opts) {
1542
+ if (!destDir || !opts?.srcRoot || opts.dryRun || !fs.existsSync(destDir)) return 0;
1543
+ const sourceRoot = path.resolve(opts.srcRoot);
1544
+ let pruned = 0;
1545
+ for (const entry of fs.readdirSync(destDir, { withFileTypes: true })) {
1546
+ if (!entry.isDirectory()) continue;
1547
+ const skillDir = path.join(destDir, entry.name);
1548
+ const receipt = readSidecarManifest(skillDir);
1549
+ if (!receipt?.skillSource || !receipt.managed?.['SKILL.md']) continue;
1550
+ const relative = path.relative(sourceRoot, receipt.skillSource);
1551
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) continue;
1552
+ try {
1553
+ fs.statSync(path.join(receipt.skillSource, 'SKILL.md'));
1554
+ continue;
1555
+ } catch (error) {
1556
+ if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') continue;
1557
+ }
1558
+ if (releaseSkillOwnership(skillDir, {
1559
+ frameworkSlug: receipt.managed['SKILL.md'].frameworkSlug,
1560
+ skillSource: receipt.skillSource,
1561
+ }, opts)) pruned++;
1562
+ }
1563
+ return pruned;
1564
+ }
1565
+
1553
1566
  /**
1554
1567
  * Deploy a skill directory (copy recursively).
1555
1568
  *
@@ -1574,6 +1587,10 @@ export function deploySkillDir(skillDir, destDir, opts) {
1574
1587
  }
1575
1588
 
1576
1589
  const destSkillDir = path.join(destDir, skillName);
1590
+ const previous = readSidecarManifest(destSkillDir);
1591
+ const frameworkSlug = extractFrameworkSlug(skillMdPath);
1592
+ if (!canDeploySkill(previous, skillDir, opts)) return;
1593
+ const managed = {};
1577
1594
  if (!dryRun) ensureDir(destSkillDir);
1578
1595
 
1579
1596
  function copyRecursive(src, dest) {
@@ -1601,6 +1618,15 @@ export function deploySkillDir(skillDir, destDir, opts) {
1601
1618
  }
1602
1619
  }
1603
1620
 
1621
+ if (entry.name === 'SKILL.md') srcContent = injectBoundRules(srcContent, srcPath, opts);
1622
+
1623
+ const relative = path.relative(destSkillDir, destPath);
1624
+ managed[relative] = {
1625
+ hash: `sha256:${contentHash(srcContent)}`,
1626
+ source: opts.deploySource || 'bundled',
1627
+ version: opts.deployVersion || 'unknown',
1628
+ ...(frameworkSlug ? { frameworkSlug } : {}),
1629
+ };
1604
1630
  if (fs.existsSync(destPath)) {
1605
1631
  const destContent = fs.readFileSync(destPath, 'utf8');
1606
1632
  if (destContent === srcContent && !force) {
@@ -1620,6 +1646,8 @@ export function deploySkillDir(skillDir, destDir, opts) {
1620
1646
  }
1621
1647
 
1622
1648
  copyRecursive(skillDir, destSkillDir);
1649
+ pruneRecordedSkillFiles(destSkillDir, previous?.managed, new Set(Object.keys(managed)), opts);
1650
+ writeSidecarManifest(destSkillDir, { managed, skillSource: path.resolve(skillDir) }, dryRun);
1623
1651
 
1624
1652
  // Drop a `.aiwg-managed` marker so future cleanup runs can identify
1625
1653
  // AIWG-deployed skills regardless of frontmatter shape (some providers
@@ -1811,7 +1839,7 @@ export function buildRemotesTopologyBlock(targetDir) {
1811
1839
 
1812
1840
  function getUrl(remote) {
1813
1841
  try {
1814
- return nodeExecSync(`git -C ${JSON.stringify(targetDir)} remote get-url ${JSON.stringify(remote)}`, {
1842
+ return boundedExecFileSync('git', ['-C', targetDir, 'remote', 'get-url', '--', remote], 'quick', {
1815
1843
  stdio: ['ignore', 'pipe', 'ignore'],
1816
1844
  encoding: 'utf8',
1817
1845
  }).trim();
@@ -2350,7 +2378,7 @@ export function getAddonCommandFiles(srcRoot, excludeAddons = [], options = {})
2350
2378
  }
2351
2379
 
2352
2380
  /**
2353
- * Get all skill directories from all addons
2381
+ * Get skill directories from a direct addon/framework source or all addons
2354
2382
  * @param {string} srcRoot - Source root directory
2355
2383
  * @param {string[]} excludeAddons - Addon names to exclude (default: none)
2356
2384
  * @returns {string[]} - Array of skill directory paths
@@ -2358,6 +2386,8 @@ export function getAddonCommandFiles(srcRoot, excludeAddons = [], options = {})
2358
2386
  export function getAddonSkillDirs(srcRoot, excludeAddons = []) {
2359
2387
  const addons = discoverAddons(srcRoot);
2360
2388
  const dirs = [];
2389
+ const directDir = getDirectSourceDir(srcRoot, excludeAddons);
2390
+ if (directDir) dirs.push(...listSkillDirs(directDir));
2361
2391
 
2362
2392
  for (const addon of addons) {
2363
2393
  if (excludeAddons.includes(addon.name)) continue;
@@ -2371,6 +2401,21 @@ export function getAddonSkillDirs(srcRoot, excludeAddons = []) {
2371
2401
  return dirs;
2372
2402
  }
2373
2403
 
2404
+ // `aiwg use <addon>` passes the component itself as --source. Keep bulk
2405
+ // repository discovery unchanged, even if the repo has a top-level skills/.
2406
+ function getDirectSourceDir(srcRoot, excludeAddons) {
2407
+ if (fs.existsSync(path.join(srcRoot, 'agentic', 'code')) ||
2408
+ excludeAddons.includes(path.basename(path.resolve(srcRoot)))) return null;
2409
+ const dir = path.join(srcRoot, 'skills');
2410
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return null;
2411
+ const relative = path.relative(fs.realpathSync(srcRoot), fs.realpathSync(dir));
2412
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
2413
+ console.warn(`Warning: Skipping direct skills directory ${dir}: resolves outside source root ${srcRoot}`);
2414
+ return null;
2415
+ }
2416
+ return dir;
2417
+ }
2418
+
2374
2419
  /**
2375
2420
  * Get all rule files from all addons
2376
2421
  * @param {string} srcRoot - Source root directory
@@ -2400,15 +2445,8 @@ export function ruleEnforcementLevel(content) {
2400
2445
  * deployment dispatchable; `AIWG_RULES_INLINE_BUDGET_TOKENS=0` disables the
2401
2446
  * budget, any other value overrides it.
2402
2447
  */
2403
- export const DEFAULT_RULES_INLINE_BUDGET_TOKENS = 64_000;
2404
- const RULE_CHARS_PER_TOKEN = 4;
2405
-
2406
2448
  export function resolveRulesInlineBudgetTokens(env = process.env) {
2407
- const raw = env.AIWG_RULES_INLINE_BUDGET_TOKENS;
2408
- if (raw === undefined || raw === '') return DEFAULT_RULES_INLINE_BUDGET_TOKENS;
2409
- const parsed = Number(raw);
2410
- if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_RULES_INLINE_BUDGET_TOKENS;
2411
- return parsed;
2449
+ return resolveRulesInlineBudget({}, env);
2412
2450
  }
2413
2451
 
2414
2452
  /** Sidecar recording which rules the inline budget moved on demand (#2562). */
@@ -2424,96 +2462,13 @@ export function readRuleBudgetSidecar(destDir) {
2424
2462
  }
2425
2463
  }
2426
2464
 
2427
- /**
2428
- * Keep a provider rule directory under the inline budget (#2562).
2429
- *
2430
- * Rules reach the directory over several deploy passes (the framework pass,
2431
- * then one per addon), so the budget is enforced against what is actually on
2432
- * disk after each pass rather than against any single pass's file list. When
2433
- * the inlined set exceeds the budget, the largest HIGH rules (then unlabelled
2434
- * ones; never CRITICAL or the indexes) are removed from the directory and
2435
- * recorded in the sidecar; the next pass skips re-deploying them and the
2436
- * on-demand index lists them as binding rules fetched via `aiwg show rule`.
2437
- * A changed budget invalidates the sidecar so the set is recomputed.
2438
- *
2439
- * Returns `{ demoted, removed }` — all demoted stems (sidecar state) and the
2440
- * stems removed by this call.
2441
- */
2442
- export function reconcileInlineRuleBudget(destDir, budgetTokens = resolveRulesInlineBudgetTokens(), opts = {}) {
2443
- const sidecarPath = path.join(destDir, RULE_BUDGET_SIDECAR);
2444
- if (!budgetTokens || budgetTokens <= 0) {
2445
- if (fs.existsSync(sidecarPath) && !opts.dryRun) fs.rmSync(sidecarPath, { force: true });
2446
- return { demoted: [], removed: [] };
2447
- }
2448
- const previous = readRuleBudgetSidecar(destDir);
2449
- const carried = previous && previous.budgetTokens === budgetTokens ? previous.demoted : [];
2450
- let files = [];
2451
- try {
2452
- files = fs.readdirSync(destDir, { withFileTypes: true })
2453
- .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
2454
- .map((entry) => path.join(destDir, entry.name))
2455
- .filter((file) => {
2456
- const base = path.basename(file);
2457
- if (base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md') return false;
2458
- try { return MANAGED_MARKER_RE.test(fs.readFileSync(file, 'utf8')); } catch { return false; }
2459
- });
2460
- } catch {
2461
- return { demoted: carried, removed: [] };
2462
- }
2463
- const { demoted } = applyInlineRuleBudget(files, budgetTokens);
2464
- const removed = demoted.map((file) => artifactStem(path.basename(file)));
2465
- if (!opts.dryRun) {
2466
- for (const file of demoted) fs.rmSync(file, { force: true });
2467
- }
2468
- const all = [...new Set([...carried, ...removed])].sort();
2469
- if (!opts.dryRun) {
2470
- if (all.length > 0) {
2471
- fs.writeFileSync(sidecarPath, `${JSON.stringify({ budgetTokens, demoted: all }, null, 2)}\n`, 'utf8');
2472
- } else if (fs.existsSync(sidecarPath)) {
2473
- fs.rmSync(sidecarPath, { force: true });
2474
- }
2475
- }
2476
- return { demoted: all, removed };
2465
+ // Compatibility exports: budgets never change selection or retain demotions.
2466
+ export function reconcileInlineRuleBudget(destDir, budgetTokens, opts = {}) {
2467
+ if (!opts.dryRun) fs.rmSync(path.join(destDir, RULE_BUDGET_SIDECAR), { force: true });
2468
+ return { demoted: [], removed: [] };
2477
2469
  }
2478
-
2479
- /**
2480
- * Fit an always-on rule set under the inline budget (#2562).
2481
- *
2482
- * CRITICAL rules and the generated indexes are never demoted. When the
2483
- * remaining set still exceeds the budget, HIGH rules are demoted largest-first
2484
- * (fewest rules leave the always-on set), then rules with no declared level.
2485
- * Demoted rules stay reachable via `aiwg show rule <name>` and are listed in
2486
- * RULES-ONDEMAND.md under their own heading. Returns `{ inline, demoted }`
2487
- * with the original order preserved for `inline`.
2488
- */
2489
- export function applyInlineRuleBudget(ruleFiles, budgetTokens = resolveRulesInlineBudgetTokens()) {
2490
- const files = [...(ruleFiles || [])];
2491
- if (!budgetTokens || budgetTokens <= 0) return { inline: files, demoted: [] };
2492
- const sized = files.map((file) => {
2493
- const base = path.basename(file);
2494
- let content = '';
2495
- try { content = fs.readFileSync(file, 'utf8'); } catch { /* unreadable → keep inline */ }
2496
- const level = base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md' ? 'critical' : ruleEnforcementLevel(content);
2497
- return { file, tokens: Math.ceil(Buffer.byteLength(content, 'utf8') / RULE_CHARS_PER_TOKEN), level };
2498
- });
2499
- let total = sized.reduce((sum, item) => sum + item.tokens, 0);
2500
- if (total <= budgetTokens) return { inline: files, demoted: [] };
2501
- const demotable = sized
2502
- .filter((item) => item.level !== 'critical')
2503
- .sort((a, b) => {
2504
- const rank = (level) => (level === 'high' ? 0 : level === null ? 1 : 2);
2505
- return rank(a.level) - rank(b.level) || b.tokens - a.tokens;
2506
- });
2507
- const demoted = new Set();
2508
- for (const item of demotable) {
2509
- if (total <= budgetTokens) break;
2510
- demoted.add(item.file);
2511
- total -= item.tokens;
2512
- }
2513
- return {
2514
- inline: files.filter((file) => !demoted.has(file)),
2515
- demoted: files.filter((file) => demoted.has(file)),
2516
- };
2470
+ export function applyInlineRuleBudget(ruleFiles) {
2471
+ return { inline: [...(ruleFiles || [])], demoted: [] };
2517
2472
  }
2518
2473
 
2519
2474
  /**
@@ -2527,10 +2482,14 @@ export function isAlwaysOnRule(filePath) {
2527
2482
  const base = path.basename(filePath);
2528
2483
  if (base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md') return true;
2529
2484
  try {
2530
- const lvl = ruleEnforcementLevel(fs.readFileSync(filePath, 'utf8'));
2531
- return lvl !== 'medium' && lvl !== 'low';
2532
- } catch {
2533
- return true; // unreadable → keep (safe default)
2485
+ const content = fs.readFileSync(filePath, 'utf8');
2486
+ const parsed = parseRuleFrontmatter(content);
2487
+ if (!parsed.valid) throw new Error(`${filePath}: ${parsed.diagnostics.map(d => d.message).join('; ')}`);
2488
+ const activation = parsed.rule.tagged ? parsed.rule.activation : inferRuleActivation(ruleEnforcementLevel(content));
2489
+ return activation === 'kernel' || activation === 'paths';
2490
+ } catch (error) {
2491
+ if (error.code === 'ENOENT') return true;
2492
+ throw error;
2534
2493
  }
2535
2494
  }
2536
2495
 
@@ -2548,7 +2507,9 @@ export function listOnDemandRuleFiles(srcRoot, excludeAddons = []) {
2548
2507
  for (const f of listMdFiles(dir)) {
2549
2508
  const b = path.basename(f);
2550
2509
  if (b === 'RULES-INDEX.md' || b === 'RULES-ONDEMAND.md') continue;
2551
- if (!isAlwaysOnRule(f)) out.push(f);
2510
+ const parsed = parseRuleFrontmatter(fs.readFileSync(f, 'utf8'));
2511
+ if (!parsed.valid) throw new Error(`${f}: invalid rule frontmatter`);
2512
+ if (!parsed.rule.tagged && !isAlwaysOnRule(f)) out.push(f);
2552
2513
  }
2553
2514
  };
2554
2515
  for (const addon of discoverAddons(aiwgRoot)) {
@@ -2661,19 +2622,23 @@ export function getAddonRuleFiles(srcRoot, excludeAddons = [], options = {}) {
2661
2622
  for (const addon of addons) {
2662
2623
  if (excludeAddons.includes(addon.name)) continue;
2663
2624
 
2664
- // Skip addons with consolidated rule indexes — their rules are
2665
- // included via assembleRulesIndex() instead of individual files
2666
- if (addon.manifest?.consolidation?.deployIndexOnly) continue;
2667
-
2668
2625
  const rulesDir = path.join(addon.path, 'rules');
2669
2626
  if (fs.existsSync(rulesDir)) {
2670
- files.push(...listMdFiles(rulesDir));
2627
+ for (const file of listMdFiles(rulesDir)) {
2628
+ // Consolidation remains a legacy inventory policy. Explicit activation
2629
+ // owns delivery, including the desired set used by the outer prune.
2630
+ if (addon.manifest?.consolidation?.deployIndexOnly) {
2631
+ const parsed = parseRuleFrontmatter(fs.readFileSync(file, 'utf8'));
2632
+ if (!parsed.valid) throw new Error(`${file}: invalid rule frontmatter`);
2633
+ if (!parsed.rule.tagged) continue;
2634
+ }
2635
+ files.push(file);
2636
+ }
2671
2637
  }
2672
2638
  }
2673
2639
 
2674
- // Tier gate (#1673): inline only always-on (CRITICAL/HIGH) rules. MEDIUM/LOW
2675
- // stay on-demand. Applied here so both the deploy enumeration and the prune
2676
- // desired-set (computeAllArtifactBasenames) exclude them for every provider.
2640
+ // Activation selects bodies; only untagged rules use the old severity tier.
2641
+ // The deploy enumeration and prune desired set must select the same bodies.
2677
2642
  return files.filter(isAlwaysOnRule);
2678
2643
  }
2679
2644
 
@@ -3365,3 +3330,25 @@ export const ProviderInterface = {
3365
3330
  // ── File Extension ────────────────────────────────────────────────
3366
3331
  getFileExtension(type) { return '.md'; }
3367
3332
  };
3333
+
3334
+ /** Replace only the generated binding block; keep authored bytes intact. */
3335
+ export function replaceBoundRulesBlock(content, rules = []) {
3336
+ const clean = content.replace(/\n?<!-- AIWG:bound-rules:start -->[\s\S]*?<!-- AIWG:bound-rules:end -->\n?/g, '');
3337
+ if (!rules.length) return clean;
3338
+ const unique = [...new Map(rules.map(rule => [rule.name, rule])).values()]
3339
+ .sort((a, b) => a.name.localeCompare(b.name));
3340
+ return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\n## Bound rules\n\nRequired reading on every invocation: fetch and follow every bound rule below before starting governed work. A summary or discovery pointer does not discharge this obligation.\n\n${unique.map(rule =>
3341
+ `- ${rule.name} — ${rule.summary}\n Fetch and follow before governed work: \`aiwg show rule ${rule.name}\`.`
3342
+ ).join('\n')}\n<!-- AIWG:bound-rules:end -->\n`;
3343
+ }
3344
+
3345
+ export function injectBoundRules(content, source, opts = {}) {
3346
+ if (!opts.ruleDelivery) return content;
3347
+ return replaceBoundRulesBlock(content, opts.ruleDelivery.bindings.get(path.resolve(source)) || []);
3348
+ }
3349
+
3350
+ /** All provider output is staged; the complete startup guard runs before promotion. */
3351
+ export async function withRuleDeployment(opts, provider, deploy) {
3352
+ const { deployWithRuleDelivery } = await import('./rule-delivery.mjs');
3353
+ return deployWithRuleDelivery(opts, provider, deploy);
3354
+ }
@@ -29,6 +29,7 @@ import path from 'path';
29
29
  import { tmpdir } from 'os';
30
30
  import { classifyModelRole, modelForRole } from './model-role.mjs';
31
31
  import {
32
+ withRuleDeployment,
32
33
  ensureDir,
33
34
  listMdFiles,
34
35
  listMdFilesRecursive,
@@ -49,9 +50,7 @@ import {
49
50
  getAddonRuleFiles,
50
51
  listOnDemandRuleFiles,
51
52
  writeOnDemandRuleIndex,
52
- readRuleBudgetSidecar,
53
53
  reconcileInlineRuleBudget,
54
- resolveRulesInlineBudgetTokens,
55
54
  assembleRulesIndex,
56
55
  normalizeDeploymentMode,
57
56
  collectFrameworkArtifacts,
@@ -521,6 +520,10 @@ export function getFileExtension(type) {
521
520
  * Orchestrates deployment of agents, commands, skills, and rules
522
521
  */
523
522
  export async function deploy(opts) {
523
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
524
+ }
525
+
526
+ async function deploySelected(opts) {
524
527
  const {
525
528
  srcRoot,
526
529
  target,
@@ -619,7 +622,7 @@ export async function deploy(opts) {
619
622
  // Deploy based on flags — track counts for summary
620
623
  const counts = { agents: 0, commands: 0, skills: 0, rules: 0, souls: 0 };
621
624
 
622
- if (!commandsOnly && !skillsOnly) {
625
+ if (!commandsOnly && !skillsOnly && !rulesOnly) {
623
626
  // Apply filters if specified
624
627
  const filteredAgents = filterAgentFiles(agentFiles, opts);
625
628
  if (verbose && (opts.filter || opts.filterRole)) {
@@ -664,17 +667,7 @@ export async function deploy(opts) {
664
667
  pruneStaleAiwgSkills(kernelDestDir, computeAllKernelNames(srcRoot), opts);
665
668
  }
666
669
 
667
- const inlineBudgetTokens = resolveRulesInlineBudgetTokens();
668
- let demotedRuleNames = [];
669
670
  if (shouldDeployRules || rulesOnly) {
670
- // Inline budget (#2562): rules a previous pass already moved on demand are
671
- // not re-inlined; the directory is reconciled against the budget after
672
- // this pass deploys (see below).
673
- const sidecar = readRuleBudgetSidecar(path.join(target, paths.rules));
674
- if (sidecar && sidecar.budgetTokens === inlineBudgetTokens && sidecar.demoted.length > 0) {
675
- const skip = new Set(sidecar.demoted);
676
- ruleFiles = ruleFiles.filter((f) => !skip.has(path.basename(f).replace(/\.md$/, '')));
677
- }
678
671
  // Try assembled rules index (combines all component indexes)
679
672
  const assembled = assembleRulesIndex(srcRoot);
680
673
  if (assembled) {
@@ -707,17 +700,8 @@ export async function deploy(opts) {
707
700
  // On-demand index (#1673): list the MEDIUM/LOW rules that were tier-gated
708
701
  // out of the always-on set so agents can fetch them via `aiwg show rule`.
709
702
  const rulesDestDir = path.join(target, paths.rules);
710
- // Keep the always-on directory small enough that a subagent dispatch
711
- // still fits (#2562). Demoted HIGH rules remain binding and are listed
712
- // in RULES-ONDEMAND.md with their fetch hint.
713
- const budget = reconcileInlineRuleBudget(rulesDestDir, inlineBudgetTokens, opts);
714
- demotedRuleNames = budget.demoted;
715
- if (budget.removed.length > 0 && !opts.quiet) {
716
- console.log(` Inline rule budget: ${budget.removed.length} HIGH rule(s) moved on demand to fit ${inlineBudgetTokens.toLocaleString()} tokens: ${budget.removed.join(', ')} (see RULES-ONDEMAND.md)`);
717
- }
718
- const onDemandCount = writeOnDemandRuleIndex(rulesDestDir, listOnDemandRuleFiles(srcRoot), {
719
- ...opts, demotedNames: demotedRuleNames, inlineBudgetTokens,
720
- });
703
+ reconcileInlineRuleBudget(rulesDestDir, undefined, opts);
704
+ const onDemandCount = writeOnDemandRuleIndex(rulesDestDir, listOnDemandRuleFiles(srcRoot), opts);
721
705
  if (verbose && onDemandCount > 0) {
722
706
  console.log(` On-demand rules (not inlined): ${onDemandCount} → RULES-ONDEMAND.md`);
723
707
  }
@@ -38,6 +38,7 @@ import { spawn } from 'child_process';
38
38
  import { load as loadYaml } from 'js-yaml';
39
39
  import { classifyModelRole, modelForRole } from './model-role.mjs';
40
40
  import {
41
+ withRuleDeployment,
41
42
  ensureDir,
42
43
  listMdFiles,
43
44
  listMdFilesRecursive,
@@ -269,7 +270,8 @@ export async function deployCommands(targetDir, srcRoot, opts) {
269
270
 
270
271
  const child = spawn('node', [scriptPath, ...args], {
271
272
  stdio: 'inherit',
272
- cwd: helperRoot
273
+ cwd: helperRoot,
274
+ env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
273
275
  });
274
276
 
275
277
  child.on('close', (code) => {
@@ -318,7 +320,8 @@ export async function deploySkills(targetDir, srcRoot, opts) {
318
320
 
319
321
  const child = spawn('node', [scriptPath, ...args], {
320
322
  stdio: 'inherit',
321
- cwd: helperRoot
323
+ cwd: helperRoot,
324
+ env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
322
325
  });
323
326
 
324
327
  child.on('close', (code) => {
@@ -332,7 +335,7 @@ export async function deploySkills(targetDir, srcRoot, opts) {
332
335
  // Self-heal: prune AIWG-managed skill dirs left behind in the legacy
333
336
  // ~/.codex/skills/ home location by pre-fix versions, so codex-rs stops
334
337
  // listing each skill twice.
335
- pruneLegacyCodexSkills(opts);
338
+ pruneLegacyCodexSkills(opts, opts.legacyCodexSkills || legacyHomeSkillsDir);
336
339
  }
337
340
 
338
341
  /**
@@ -398,17 +401,16 @@ export function deployRules(ruleFiles, targetDir, opts) {
398
401
  * Aggregate agents to single AGENTS.md file
399
402
  */
400
403
  export function aggregateToAgentsMd(agentFiles, destPath, opts) {
401
- const blocks = [];
402
- for (const f of agentFiles) {
403
- let content = fs.readFileSync(f, 'utf8');
404
- content = transformAgent(f, content, opts);
405
- if (!content.endsWith('\n')) content += '\n';
406
- blocks.push(content);
404
+ const lines = ['# AIWG agent definitions', '', 'Read the selected definition before invoking an agent.', ''];
405
+ for (const file of agentFiles) {
406
+ const { metadata } = parseFrontmatter(fs.readFileSync(file, 'utf8'));
407
+ const name = metadata.name || path.basename(file, '.md');
408
+ lines.push(`- ${name}: .codex/agents/${path.basename(file, '.md')}.toml`);
407
409
  }
408
- const out = blocks.join('\n');
410
+ const out = `${lines.join('\n')}\n`;
409
411
  if (opts.dryRun) console.log(`[dry-run] write ${destPath}`);
410
412
  else fs.writeFileSync(destPath, out, 'utf8');
411
- console.log(`wrote ${path.relative(process.cwd(), destPath)} with ${agentFiles.length} agents`);
413
+
412
414
  }
413
415
 
414
416
  // ============================================================================
@@ -536,6 +538,10 @@ export function getFileExtension(type) {
536
538
  * Main deployment function for Codex provider
537
539
  */
538
540
  export async function deploy(opts) {
541
+ return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
542
+ }
543
+
544
+ async function deploySelected(opts) {
539
545
  const {
540
546
  srcRoot,
541
547
  target,
@@ -628,6 +634,7 @@ export async function deploy(opts) {
628
634
  // Aggregate to single AGENTS.md
629
635
  const destPath = path.join(target, 'AGENTS.md');
630
636
  console.log(`\nAggregating ${agentFiles.length} agents to AGENTS.md...`);
637
+ deployAgents(agentFiles, target, opts);
631
638
  aggregateToAgentsMd(agentFiles, destPath, opts);
632
639
  } else {
633
640
  console.log(`\nDeploying ${agentFiles.length} agents...`);