@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
@@ -0,0 +1,357 @@
1
+ /**
2
+ * MCP profile tool filters.
3
+ *
4
+ * A profile's providerOverrides hold toolDeny / toolAllow patterns of the form
5
+ * `<server>__<tool>`, where <tool> may contain `*`. The `*` provider key applies
6
+ * to every harness; a harness's own key adds to it.
7
+ *
8
+ * Each harness enforces tool filters differently, and some not at all:
9
+ *
10
+ * claude-code permissions.deny rules `mcp__<server>__<tool>` in
11
+ * .claude/settings.local.json (project) or ~/.claude/settings.json (user).
12
+ * toolAllow is refused: Claude has no restrict-only allowlist.
13
+ * https://code.claude.com/docs/en/permissions
14
+ * codex per-server disabled_tools / enabled_tools, exact names; enabled = false
15
+ * for a whole-server deny. https://learn.chatgpt.com/docs/extend/mcp
16
+ * opencode top-level tools map of `<server>_<tool>` globs; the last matching entry
17
+ * wins. https://opencode.ai/docs/mcp-servers
18
+ * factory per-server disabledTools; disabled = true for a whole-server deny.
19
+ * https://docs.factory.com/cli/configuration/mcp
20
+ * windsurf per-server disabledTools. https://docs.devin.ai/desktop/cascade/mcp
21
+ * antigravity per-server disabledTools; disabled = true for a whole-server deny.
22
+ * https://antigravity.google/docs/mcp
23
+ *
24
+ * Cursor, Warp, OMP and Grok Build document no file-level tool filter.
25
+ * Anything a harness cannot express is returned as a warning, never dropped silently.
26
+ */
27
+
28
+ import { readFile } from 'fs/promises';
29
+ import { createHash } from 'node:crypto';
30
+ import { assertConfigDestination, canonicalConfigPath, prepareConfigWrite, writeConfigTransaction } from './config-file.mjs';
31
+ import { resolve } from 'path';
32
+ import { homedir } from 'os';
33
+ import { normalizeRuntimeProviderId } from '../providers/provider-definitions.mjs';
34
+
35
+ const PER_SERVER_DISABLED_TOOLS = new Set(['factory', 'windsurf', 'antigravity']);
36
+ const SERVER_DISABLE_KEY = { factory: 'disabled', antigravity: 'disabled' };
37
+
38
+ function unique(values) {
39
+ return [...new Set(values)];
40
+ }
41
+
42
+ /** Merge the `*` overrides with the harness's own. Keys are normalized, so `claude` and `claude-code` both apply. */
43
+ export function resolveToolFilters(profile, provider) {
44
+ const target = normalizeRuntimeProviderId(provider) || provider;
45
+ const deny = [];
46
+ const allow = [];
47
+ for (const [key, override] of Object.entries(profile?.providerOverrides || {})) {
48
+ if (key !== '*' && (normalizeRuntimeProviderId(key) || key) !== target) continue;
49
+ deny.push(...(override?.toolDeny || []));
50
+ allow.push(...(override?.toolAllow || []));
51
+ }
52
+ return { deny: unique(deny), allow: unique(allow) };
53
+ }
54
+
55
+ export function hasToolFilters(filters) {
56
+ return Boolean(filters && (filters.deny.length > 0 || filters.allow.length > 0));
57
+ }
58
+
59
+ /** Split `<server>__<tool>`; returns null when there is no server part. */
60
+ export function parseToolPattern(pattern) {
61
+ const index = pattern.indexOf('__');
62
+ if (index <= 0 || index + 2 >= pattern.length) return null;
63
+ return { server: pattern.slice(0, index), tool: pattern.slice(index + 2) };
64
+ }
65
+
66
+ const isGlob = value => value.includes('*');
67
+
68
+ /**
69
+ * Group patterns by injected server. Patterns for servers outside the injected
70
+ * set do not apply; malformed patterns and glob server names are warnings.
71
+ */
72
+ function groupByServer(patterns, serverNames, kind, warnings) {
73
+ const groups = new Map();
74
+ for (const pattern of patterns) {
75
+ const parsed = parseToolPattern(pattern);
76
+ if (!parsed || isGlob(parsed.server)) {
77
+ warnings.push(`${kind} pattern "${pattern}" is not <server>__<tool> with a literal server name; not rendered.`);
78
+ continue;
79
+ }
80
+ if (!serverNames.includes(parsed.server)) continue;
81
+ if (!groups.has(parsed.server)) groups.set(parsed.server, []);
82
+ groups.get(parsed.server).push(parsed.tool);
83
+ }
84
+ return groups;
85
+ }
86
+
87
+ /** Claude Code and opencode name a server's tools after the server with these characters replaced. */
88
+ const sanitize = value => value.replace(/[^a-zA-Z0-9_-]/g, '_');
89
+
90
+ /**
91
+ * Compute what a harness should receive.
92
+ *
93
+ * Returns:
94
+ * serverFields per-server fields to merge into each entry (JSON harnesses)
95
+ * tomlLines per-server TOML lines to append to each [mcp_servers.<name>] table
96
+ * topLevel fields to merge into the harness config root (opencode `tools`)
97
+ * claudePermissions permission rules for Claude Code's settings file
98
+ * warnings filters the harness cannot express
99
+ */
100
+ export function planToolFilters(provider, serverNames, filters) {
101
+ const target = normalizeRuntimeProviderId(provider) || provider;
102
+ const plan = { serverFields: {}, tomlLines: {}, topLevel: {}, claudePermissions: null, warnings: [] };
103
+ if (target === 'claude-code' && filters?.allow.length) {
104
+ throw new Error(`claude-code: refusing toolAllow patterns ${filters.allow.map(pattern => JSON.stringify(pattern)).join(', ')}: `
105
+ + 'Claude Code has no restrict-only allowlist. permissions.allow pre-approves tools; deny rules cannot express all except, '
106
+ + 'and allow rules cannot carve exceptions out of deny rules. Use toolDeny instead.');
107
+ }
108
+ if (!hasToolFilters(filters)) return plan;
109
+ const deny = groupByServer(filters.deny, serverNames, 'toolDeny', plan.warnings);
110
+ const allow = groupByServer(filters.allow, serverNames, 'toolAllow', plan.warnings);
111
+ if (deny.size === 0 && allow.size === 0) return plan;
112
+
113
+ if (target === 'claude-code') {
114
+ const rule = (server, tool) => (tool === '*' ? `mcp__${sanitize(server)}` : `mcp__${sanitize(server)}__${tool}`);
115
+ const denyRules = [...deny].flatMap(([server, tools]) => tools.map(tool => rule(server, tool)));
116
+ plan.claudePermissions = { deny: unique(denyRules), allow: [] };
117
+ return plan;
118
+ }
119
+
120
+ if (target === 'opencode') {
121
+ const tools = {};
122
+ for (const server of allow.keys()) tools[`${sanitize(server)}_*`] = false;
123
+ for (const [server, list] of deny) {
124
+ for (const tool of list) {
125
+ const key = `${sanitize(server)}_${tool}`;
126
+ delete tools[key];
127
+ tools[key] = false;
128
+ }
129
+ }
130
+ // Specific allows follow denies under last-match-wins. An identical deny key wins.
131
+ for (const [server, list] of allow) {
132
+ for (const tool of list) {
133
+ if (deny.get(server)?.includes(tool)) continue;
134
+ const key = `${sanitize(server)}_${tool}`;
135
+ delete tools[key];
136
+ tools[key] = true;
137
+ }
138
+ }
139
+ plan.topLevel.tools = tools;
140
+ return plan;
141
+ }
142
+
143
+ if (target === 'codex') {
144
+ if (allow.size) {
145
+ const unrestricted = serverNames.filter(name => !allow.has(name) && !deny.get(name)?.includes('*'));
146
+ if (unrestricted.length) {
147
+ plan.warnings.push(`codex: toolAllow is per-server; servers without an allowlist remain unrestricted by toolAllow: ${unrestricted.join(', ')}. Add toolAllow patterns or toolDeny <server>__* for them.`);
148
+ }
149
+ }
150
+ for (const name of serverNames) {
151
+ const lines = [];
152
+ const denied = deny.get(name) || [];
153
+ const allowed = allow.get(name) || [];
154
+ if (denied.includes('*')) {
155
+ plan.tomlLines[name] = ['enabled = false'];
156
+ continue;
157
+ }
158
+ const exact = (list, kind) => list.filter(tool => {
159
+ if (!isGlob(tool)) return true;
160
+ plan.warnings.push(`codex: ${kind} "${name}__${tool}" is a glob; Codex matches tool names exactly, so it is not rendered.`);
161
+ return false;
162
+ });
163
+ const enabled = allowed.includes('*') ? [] : exact(allowed, 'toolAllow');
164
+ const disabled = exact(denied, 'toolDeny');
165
+ if (enabled.length > 0) lines.push(`enabled_tools = [${enabled.map(tool => JSON.stringify(tool)).join(', ')}]`);
166
+ if (disabled.length > 0) lines.push(`disabled_tools = [${disabled.map(tool => JSON.stringify(tool)).join(', ')}]`);
167
+ if (lines.length > 0) plan.tomlLines[name] = lines;
168
+ }
169
+ return plan;
170
+ }
171
+
172
+ if (PER_SERVER_DISABLED_TOOLS.has(target)) {
173
+ for (const [server, list] of deny) {
174
+ if (list.includes('*')) {
175
+ const key = SERVER_DISABLE_KEY[target];
176
+ if (key) plan.serverFields[server] = { [key]: true };
177
+ else plan.warnings.push(`${target}: toolDeny "${server}__*" disables a whole server, which ${target} has no documented file key for; not rendered.`);
178
+ continue;
179
+ }
180
+ const exact = list.filter(tool => {
181
+ if (!isGlob(tool)) return true;
182
+ plan.warnings.push(`${target}: toolDeny "${server}__${tool}" is a glob; disabledTools takes exact names, so it is not rendered.`);
183
+ return false;
184
+ });
185
+ if (exact.length > 0) plan.serverFields[server] = { disabledTools: exact };
186
+ }
187
+ for (const server of allow.keys()) {
188
+ plan.warnings.push(`${target}: toolAllow for "${server}" is not rendered; ${target} has no tool allowlist.`);
189
+ }
190
+ return plan;
191
+ }
192
+
193
+ plan.warnings.push(`${target}: tool filters are not rendered; ${target} documents no per-tool filter in its MCP config.`);
194
+ return plan;
195
+ }
196
+
197
+ /** Merge the per-server fields and top-level fields of a plan into a JSON harness config. */
198
+ export function applyJsonToolFilterPlan(config, serversKey, plan) {
199
+ const servers = config[serversKey] || {};
200
+ for (const [name, fields] of Object.entries(plan.serverFields)) {
201
+ if (servers[name]) {
202
+ const previous = servers[name];
203
+ if (Object.hasOwn(previous, 'disabledTools') && !Array.isArray(previous.disabledTools)) {
204
+ throw new Error(`Refusing tool filters for server "${name}": existing disabledTools must be an array`);
205
+ }
206
+ servers[name] = { ...previous, ...fields };
207
+ if (fields.disabledTools && Array.isArray(previous.disabledTools)) {
208
+ servers[name].disabledTools = unique([...previous.disabledTools, ...fields.disabledTools]);
209
+ }
210
+ }
211
+ }
212
+ if (plan.topLevel.tools) {
213
+ const tools = { ...(config.tools || {}) };
214
+ // opencode uses last-match-wins, including when an existing wildcard conflicts.
215
+ for (const key of Object.keys(plan.topLevel.tools)) delete tools[key];
216
+ config.tools = { ...tools, ...plan.topLevel.tools };
217
+ }
218
+ return config;
219
+ }
220
+
221
+ export function claudeSettingsPath(projectDir = '.', scope = 'project') {
222
+ if (scope === 'user') return resolve(process.env.HOME || process.env.USERPROFILE || homedir(), '.claude/settings.json');
223
+ return resolve(projectDir, '.claude/settings.local.json');
224
+ }
225
+
226
+ const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
227
+ const digest = value => createHash('sha256').update(value).digest('hex');
228
+
229
+ function validateSettings(settings, settingsPath) {
230
+ if (!isObject(settings)) throw new Error(`Claude Code settings ${settingsPath} must contain an object`);
231
+ if (Object.hasOwn(settings, 'permissions')) {
232
+ if (!isObject(settings.permissions)) {
233
+ throw new Error(`Claude Code settings ${settingsPath}: permissions must be an object`);
234
+ }
235
+ for (const key of ['allow', 'deny', 'ask', 'additionalDirectories']) {
236
+ if (Object.hasOwn(settings.permissions, key)
237
+ && (!Array.isArray(settings.permissions[key]) || settings.permissions[key].some(rule => typeof rule !== 'string'))) {
238
+ throw new Error(`Claude Code settings ${settingsPath}: permissions.${key} must be an array of strings`);
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ /** Validate destinations and settings before any MCP writes; keep ownership outside Claude's schema. */
245
+ export async function prepareClaudePermissions(settingsPath, permissions, {
246
+ userScope = false, projectRoot, managedDir, sidecar = false, mcpPath,
247
+ } = {}) {
248
+ if (permissions.allow?.length) {
249
+ throw new Error(`Claude Code: refusing permissions.allow ${permissions.allow.map(rule => JSON.stringify(rule)).join(', ')}: no restrict-only allowlist; use permissions.deny instead.`);
250
+ }
251
+ settingsPath = resolve(settingsPath);
252
+ if (mcpPath && settingsPath === resolve(mcpPath)) {
253
+ throw new Error(`Refusing Claude Code settings sidecar ${settingsPath}: collides with MCP config`);
254
+ }
255
+ const info = await assertConfigDestination(settingsPath, projectRoot);
256
+ if (mcpPath && info) {
257
+ const mcpInfo = await assertConfigDestination(mcpPath, projectRoot);
258
+ if (mcpInfo && info.dev === mcpInfo.dev && info.ino === mcpInfo.ino) {
259
+ throw new Error(`Refusing Claude Code settings sidecar ${settingsPath}: collides with MCP config`);
260
+ }
261
+ }
262
+ let content;
263
+ let existing = {};
264
+ if (info) {
265
+ content = await readFile(settingsPath, 'utf-8');
266
+ try {
267
+ existing = JSON.parse(content);
268
+ } catch (error) {
269
+ if (error instanceof SyntaxError) {
270
+ throw new Error(`Refusing to overwrite malformed Claude Code settings ${settingsPath}: invalid JSON`);
271
+ }
272
+ throw error;
273
+ }
274
+ }
275
+ validateSettings(existing, settingsPath);
276
+
277
+ const canonicalPath = await canonicalConfigPath(settingsPath);
278
+ const recordPath = managedDir ? resolve(managedDir, 'claude-tool-permissions', `${digest(canonicalPath)}.json`) : null;
279
+ let record = null;
280
+ if (recordPath && await assertConfigDestination(recordPath, managedDir)) {
281
+ try {
282
+ record = JSON.parse(await readFile(recordPath, 'utf-8'));
283
+ if (!isObject(record) || record.settingsPath !== canonicalPath || !Array.isArray(record.denyAdded)
284
+ || record.denyAdded.some(rule => typeof rule !== 'string')
285
+ || (record.denyDigest !== undefined && (typeof record.denyDigest !== 'string' || !/^[a-f0-9]{64}$/.test(record.denyDigest)))
286
+ || (record.denyPreserved !== undefined && (!Array.isArray(record.denyPreserved)
287
+ || record.denyPreserved.some(rule => typeof rule !== 'string' || !record.denyAdded.includes(rule))))) {
288
+ throw new Error('unsupported ownership record shape');
289
+ }
290
+ } catch (error) {
291
+ throw new Error(`Invalid Claude permission ownership record ${recordPath}: ${error.message}. Delete ${recordPath} to reset ownership; existing deny rules will be treated as user-owned.`);
292
+ }
293
+ }
294
+ if (sidecar && info && (!record?.sidecar || record.contentDigest !== digest(content))) {
295
+ throw new Error(`Refusing implicit Claude Code settings sidecar ${settingsPath}: existing file was not created by AIWG or was changed. Choose another --out path.`);
296
+ }
297
+ if (sidecar && !recordPath) throw new Error('Claude settings sidecars require an ownership record directory');
298
+
299
+ const desired = unique(permissions.deny);
300
+ const current = existing.permissions || {};
301
+ const deny = [...(current.deny || [])];
302
+ const owned = [];
303
+ const preserved = [];
304
+ const warnings = [];
305
+ const unchangedDeny = record?.denyDigest === digest(JSON.stringify(deny));
306
+ for (const rule of record?.denyAdded || []) {
307
+ const index = deny.indexOf(rule);
308
+ if (index < 0) continue;
309
+ if (!unchangedDeny || record.denyPreserved?.includes(rule)) {
310
+ owned.push(rule);
311
+ preserved.push(rule);
312
+ } else if (desired.includes(rule)) owned.push(rule);
313
+ else deny.splice(index, 1); // Remove only our occurrence; preserve user-added duplicates.
314
+ }
315
+ for (const rule of desired) {
316
+ if (deny.includes(rule)) continue; // A pre-existing user rule never becomes AIWG-owned.
317
+ deny.push(rule);
318
+ owned.push(rule);
319
+ }
320
+ const retained = preserved.filter(rule => !desired.includes(rule));
321
+ if (retained.length) {
322
+ warnings.push(`Claude Code settings ${settingsPath}: preserving previously tracked deny rules because permissions.deny changed or ownership is uncertain: ${unique(retained).join(', ')}. Remove these rules manually from permissions.deny in ${settingsPath} if no longer wanted.`);
323
+ }
324
+ const active = Boolean(record || desired.length || sidecar);
325
+ const merged = active ? { ...existing, permissions: { ...current, deny } } : existing;
326
+ const nextContent = JSON.stringify(merged, null, 2) + '\n';
327
+ const writes = active ? [{
328
+ file: settingsPath, content: nextContent,
329
+ options: { userScope: userScope || sidecar, projectRoot, newFileMode: 0o600 },
330
+ }] : [];
331
+ if (active && recordPath) {
332
+ writes.push({
333
+ file: recordPath,
334
+ content: JSON.stringify({
335
+ settingsPath: canonicalPath, denyAdded: owned, denyPreserved: unique(preserved),
336
+ denyDigest: digest(JSON.stringify(deny)), sidecar,
337
+ ...(sidecar ? { contentDigest: digest(nextContent) } : {}),
338
+ }, null, 2) + '\n',
339
+ options: { userScope: true, projectRoot: managedDir },
340
+ });
341
+ }
342
+ for (const { file, options } of writes) await prepareConfigWrite(file, options);
343
+ return {
344
+ merged, active, warnings, writes,
345
+ async commit() { await writeConfigTransaction(writes); },
346
+ };
347
+ }
348
+
349
+ /** Merge deny rules after validation, optionally tracking ownership for profile switching. */
350
+ export async function mergeClaudePermissions(settingsPath, permissions, { dryRun = false, ...options } = {}) {
351
+ const prepared = await prepareClaudePermissions(settingsPath, permissions, options);
352
+ if (!dryRun) {
353
+ await prepared.commit();
354
+ for (const warning of prepared.warnings) console.warn(`WARNING ${warning}`);
355
+ }
356
+ return prepared.merged;
357
+ }
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { readFile, stat } from 'node:fs/promises';
12
12
  import { z } from 'zod';
13
- import { mcpError, mcpJson } from '../helpers.mjs';
13
+ import { assertNoWorkerBinding, mcpError, mcpJson } from '../helpers.mjs';
14
14
 
15
15
  const FLEET_VERSION = 'agentic-orchestration/v1';
16
16
  const ACTIVITY_VERSION = 'activity.event/v1';
@@ -143,6 +143,7 @@ export class AgenticSandboxMcpClient {
143
143
  }
144
144
 
145
145
  async request(path, { method = 'GET', body, headers = {}, validate = (value) => safeResponse(value) } = {}) {
146
+ assertNoWorkerBinding(body, 'mcp');
146
147
  const baseUrl = configuredBaseUrl(this.env);
147
148
  const token = await managementBearer(this.env);
148
149
  const response = await this.fetch(`${baseUrl}${path}`, {
@@ -158,6 +159,7 @@ export class AgenticSandboxMcpClient {
158
159
  else return mcpError(`sandbox returned non-JSON HTTP ${response.status}`);
159
160
  }
160
161
  try {
162
+ assertNoWorkerBinding(parsed, 'mcp');
161
163
  return typedResult(parsed, response.status, validate);
162
164
  } catch (error) {
163
165
  return mcpError(`${error.code ?? 'sandbox_malformed_response'}: ${error.message}`);
@@ -202,18 +202,37 @@ export function registerDecisionToolset(server) {
202
202
  context: z.string().optional(),
203
203
  threshold: z.number().min(0).max(1).default(0.8),
204
204
  timeout_ms: z.number().int().positive().default(15000),
205
+ passes: z.number().int().positive().optional(),
206
+ escalation_passes: z.number().int().positive().optional(),
207
+ caller: z.string().optional().describe('Optional decision-point attribution tag, recorded in the decision ledger (#2946)'),
205
208
  },
206
209
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
207
- }, async ({ question, yes_no, choices, scale, context, threshold, timeout_ms }) => {
210
+ }, async ({ question, yes_no, choices, scale, context, threshold, timeout_ms, passes, escalation_passes, caller }) => {
208
211
  const modeCount = (yes_no ? 1 : 0) + (choices ? 1 : 0) + (scale ? 1 : 0);
209
212
  if (modeCount !== 1) throw new Error('Select exactly one of yes_no, choices, or scale');
210
- return (await driver()).askDecision({
213
+ const driverModule = await driver();
214
+ const opts = options();
215
+ const input = {
211
216
  question,
212
217
  mode: yes_no ? { kind: 'yes-no' } : choices ? { kind: 'choices', choices } : { kind: 'scale', low: scale.low, high: scale.high },
213
218
  ...(context === undefined ? {} : { context }),
214
219
  threshold,
215
220
  timeoutMs: timeout_ms,
216
- }, options());
221
+ ...(passes === undefined ? {} : { passes }),
222
+ ...(escalation_passes === undefined ? {} : { escalationPasses: escalation_passes }),
223
+ };
224
+ const sanitizedCaller = typeof caller === 'string' && driverModule.ATTRIBUTION_FIELD_PATTERN.test(caller) ? caller : null;
225
+ const invocation = await driverModule.runDecisionOffloadCheckpoint(input, {
226
+ transport: 'mcp',
227
+ projectRoot: opts.cwd,
228
+ env: opts.env,
229
+ caller: sanitizedCaller ?? 'decision-mcp:ask',
230
+ ledgerCaller: sanitizedCaller,
231
+ category: sanitizedCaller ? undefined : 'unattributed',
232
+ askRuntime: opts,
233
+ });
234
+ if (!invocation.result) throw invocation.error ?? new Error('Decision ask was skipped');
235
+ return invocation.result;
217
236
  }, ASK);
218
237
 
219
238
  registerJsonTool(server, 'decision-setup-synthetic-classification', {
@@ -23,21 +23,22 @@
23
23
 
24
24
  import { z } from 'zod';
25
25
  import { runAiwgCli, mcpError, mcpJson, AIWG_ROOT } from '../helpers.mjs';
26
- import fs from 'node:fs/promises';
27
26
  import path from 'node:path';
27
+ import { pinContainedRoot, readContainedFile, listContainedFiles, validateContainedPath } from '../../utils/contained-filesystem.mjs';
28
28
 
29
29
  /**
30
30
  * Register discovery tools on the MCP server.
31
31
  *
32
32
  * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
33
33
  */
34
- export function registerDiscoveryTools(server) {
34
+ export function registerDiscoveryTools(server, { corpusStore } = {}) {
35
+ const store = corpusStore ? Promise.resolve(corpusStore) : pinContainedRoot(path.resolve(AIWG_ROOT));
35
36
  // ============================================================
36
37
  // discover — cross-type semantic search
37
38
  // ============================================================
38
39
  server.registerTool('discover', {
39
40
  title: 'Discover AIWG Capabilities',
40
- description: 'Semantic search across AIWG operational assets: skills, agents, commands, rules, flows, runbooks, templates, and behaviors. Returns ranked candidates with capability summaries. Mirrors `aiwg discover --json`.',
41
+ description: 'Semantic search across AIWG operational assets: skills, agents, commands, rules, flows, runbooks, templates, and behaviors. Returns ranked candidates with capability summaries, governing rules, rule stats, and required fetch-and-follow checks before governed work. Mirrors `aiwg discover --json`.',
41
42
  inputSchema: {
42
43
  phrase: z.string().describe('Natural-language capability description (e.g. "create intake form")'),
43
44
  type: z.enum(['skill', 'agent', 'command', 'rule', 'flow', 'runbook', 'template', 'behavior']).optional().describe('Restrict to one artifact type'),
@@ -84,12 +85,13 @@ export function registerDiscoveryTools(server) {
84
85
  title: 'Show AIWG Skill',
85
86
  description: 'Fetch the full body of an AIWG SKILL.md by name. Returns path + content envelope.',
86
87
  inputSchema: {
88
+ bound_rule_bodies: z.boolean().optional().describe('Include full bound rule bodies'),
87
89
  name: z.string().describe('Skill name (slug; e.g. "intake-wizard")'),
88
90
  },
89
91
  annotations: { readOnlyHint: true },
90
- }, async ({ name }) => {
92
+ }, async ({ name, bound_rule_bodies }) => {
91
93
  try {
92
- const { stdout, code, stderr } = await runAiwgCli(['show', 'skill', name, '--json']);
94
+ const { stdout, code, stderr } = await runAiwgCli(['show', 'skill', name, '--json', ...(bound_rule_bodies ? ['--bound-rule-bodies'] : [])]);
93
95
  if (code !== 0) return mcpError(`skill-show failed: ${stderr}`);
94
96
  return mcpJson(stdout);
95
97
  } catch (err) {
@@ -138,6 +140,27 @@ export function registerDiscoveryTools(server) {
138
140
  // ============================================================
139
141
  // rule-list / rule-show (#1320)
140
142
  // ============================================================
143
+ server.registerTool('rule-inventory', {
144
+ title: 'Inventory configured AIWG rules',
145
+ description: 'Inspect scoped rule configuration and activation without asserting native session loading. Mirrors aiwg rules inventory --json.',
146
+ inputSchema: {
147
+ scope: z.enum(['workspace', 'user', 'installation', 'catalog']).default('workspace'),
148
+ capability: z.string().optional(), activation: z.string().optional(),
149
+ tier: z.string().optional(), provider: z.string().optional(), bodies: z.boolean().default(false),
150
+ },
151
+ annotations: { readOnlyHint: true },
152
+ }, async ({ scope, capability, activation, tier, provider, bodies }) => {
153
+ try {
154
+ const args = ['rules', 'inventory', '--scope', scope ?? 'workspace', '--json'];
155
+ for (const [flag, value] of [['--capability', capability], ['--activation', activation], ['--tier', tier], ['--provider', provider]]) {
156
+ if (value) args.push(flag, value);
157
+ }
158
+ if (bodies) args.push('--bodies');
159
+ const { stdout, code, stderr } = await runAiwgCli(args);
160
+ if (code !== 0) return mcpError(`rule-inventory failed: ${stderr || stdout}`);
161
+ return mcpJson(stdout);
162
+ } catch (err) { return mcpError(`rule-inventory: ${err.message}`); }
163
+ });
141
164
  server.registerTool('rule-list', {
142
165
  title: 'List AIWG Rules',
143
166
  description: 'Enumerate AIWG enforcement rules (e.g. skill-discovery, no-attribution, anti-laziness, citation-policy). Top-7 CRITICAL/HIGH rules are inlined into Hermes AGENTS.md priming (skill-discovery added #1347); this tool reaches the rest.',
@@ -181,12 +204,13 @@ export function registerDiscoveryTools(server) {
181
204
  title: 'Show AIWG Agent Definition',
182
205
  description: 'Fetch the full agent definition (system prompt + frontmatter) by name.',
183
206
  inputSchema: {
207
+ bound_rule_bodies: z.boolean().optional().describe('Include full bound rule bodies'),
184
208
  name: z.string().describe('Agent name (e.g. "test-engineer", "security-auditor")'),
185
209
  },
186
210
  annotations: { readOnlyHint: true },
187
- }, async ({ name }) => {
211
+ }, async ({ name, bound_rule_bodies }) => {
188
212
  try {
189
- const { stdout, code, stderr } = await runAiwgCli(['show', 'agent', name, '--json']);
213
+ const { stdout, code, stderr } = await runAiwgCli(['show', 'agent', name, '--json', ...(bound_rule_bodies ? ['--bound-rule-bodies'] : [])]);
190
214
  if (code !== 0) return mcpError(`agent-show failed: ${stderr}`);
191
215
  return mcpJson(stdout);
192
216
  } catch (err) {
@@ -207,7 +231,7 @@ export function registerDiscoveryTools(server) {
207
231
  annotations: { readOnlyHint: true },
208
232
  }, async ({ framework, filter }) => {
209
233
  try {
210
- const results = await scanTemplates(AIWG_ROOT, { framework, filter });
234
+ const results = await scanTemplates(await store, { framework, filter });
211
235
  return mcpJson({ count: results.length, templates: results });
212
236
  } catch (err) {
213
237
  return mcpError(`template-list: ${err.message}`);
@@ -223,21 +247,27 @@ export function registerDiscoveryTools(server) {
223
247
  annotations: { readOnlyHint: true },
224
248
  }, async ({ template }) => {
225
249
  try {
226
- // Allow caller to pass either a corpus-relative path or just the template filename
227
- const candidates = [
228
- path.join(AIWG_ROOT, 'agentic/code/frameworks', template),
229
- path.join(AIWG_ROOT, 'agentic/code/frameworks/sdlc-complete/templates', template),
230
- path.resolve(template),
231
- ];
232
- for (const c of candidates) {
250
+ validateContainedPath(template);
251
+ // Preserve corpus-relative and template-relative selection, but never
252
+ // try cwd/absolute candidates. Each candidate is read through one pin.
253
+ const root = await store;
254
+ const frameworks = 'agentic/code/frameworks';
255
+ const [framework, ...parts] = template.split('/');
256
+ const candidates = template.startsWith(`${frameworks}/`)
257
+ ? [template]
258
+ : [
259
+ `${frameworks}/${template}`,
260
+ `${frameworks}/${framework}/templates/${parts.join('/')}`,
261
+ `${frameworks}/sdlc-complete/templates/${template}`,
262
+ ];
263
+ for (const candidate of candidates) {
264
+ // Only template subtrees, including basename selection, are eligible.
265
+ if (!/^agentic\/code\/frameworks\/[^/]+\/templates\/.+/.test(candidate)) continue;
233
266
  try {
234
- // Anti-traversal: ensure resolved path stays inside AIWG_ROOT
235
- const resolved = path.resolve(c);
236
- if (!resolved.startsWith(path.resolve(AIWG_ROOT))) continue;
237
- const content = await fs.readFile(resolved, 'utf-8');
238
- return mcpJson({ path: resolved, content });
267
+ const content = await readContainedFile(root, candidate);
268
+ if (content !== null) return mcpJson({ path: path.join(root.root, candidate), content });
239
269
  } catch {
240
- // try next
270
+ // Try the next contained candidate; no alternate root is adopted.
241
271
  }
242
272
  }
243
273
  return mcpError(`template not found: ${template}`);
@@ -251,41 +281,18 @@ export function registerDiscoveryTools(server) {
251
281
  * Enumerate templates by walking the canonical corpus.
252
282
  * Restricted to the AIWG_ROOT subtree (no .aiwg/ project artifacts).
253
283
  */
254
- async function scanTemplates(aiwgRoot, { framework, filter } = {}) {
284
+ async function scanTemplates(store, { framework, filter } = {}) {
285
+ if (framework) validateContainedPath(framework);
286
+ const prefix = framework ? `agentic/code/frameworks/${framework}/templates/` : 'agentic/code/frameworks/';
287
+ const entries = await listContainedFiles(store, prefix);
255
288
  const results = [];
256
- const root = path.join(aiwgRoot, 'agentic/code/frameworks');
257
- let frameworks = [];
258
- try {
259
- frameworks = (await fs.readdir(root, { withFileTypes: true }))
260
- .filter(d => d.isDirectory())
261
- .map(d => d.name);
262
- } catch {
263
- return results;
264
- }
265
- for (const fw of frameworks) {
266
- if (framework && fw !== framework) continue;
267
- const tplRoot = path.join(root, fw, 'templates');
268
- await walkTemplates(tplRoot, tplRoot, fw, results, filter);
269
- }
270
- return results;
271
- }
272
-
273
- async function walkTemplates(base, dir, framework, out, filter) {
274
- let entries = [];
275
- try {
276
- entries = await fs.readdir(dir, { withFileTypes: true });
277
- } catch {
278
- return;
279
- }
280
289
  for (const entry of entries) {
281
- const full = path.join(dir, entry.name);
282
- if (entry.isDirectory()) {
283
- await walkTemplates(base, full, framework, out, filter);
284
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
285
- const rel = path.relative(base, full);
286
- const name = entry.name.replace(/\.md$/, '');
287
- if (filter && !name.includes(filter) && !rel.includes(filter)) continue;
288
- out.push({ framework, name, relativePath: rel, path: full });
289
- }
290
+ const match = /^agentic\/code\/frameworks\/([^/]+)\/templates\/(.+\.md)$/.exec(entry.path);
291
+ if (!match) continue;
292
+ const [, fw, relativePath] = match;
293
+ const name = path.basename(relativePath, '.md');
294
+ if (filter && !name.includes(filter) && !relativePath.includes(filter)) continue;
295
+ results.push({ framework: fw, name, relativePath, path: path.join(store.root, entry.path) });
290
296
  }
297
+ return results;
291
298
  }