@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,252 @@
1
+ /**
2
+ * Worker-dispatch effect verification (#2891, S1 direct/shared checkpoint).
3
+ *
4
+ * Modelled on the ledger lock recovery (`src/effects/lock-recovery.ts`): an
5
+ * extension kind whose verifier is registered only when the evidence it
6
+ * checks actually exists. Here the evidence is Tier 0 supervisor evidence
7
+ * held by the conductor — the dispatch record digest for one invocation,
8
+ * plus optional `file.digest` artifacts. Without supervisor evidence the
9
+ * factory returns null and the kind stays unregistered, so reconciliation
10
+ * through the existing ledger answers `unknown` / `verifier-missing` instead
11
+ * of manufacturing proof.
12
+ *
13
+ * Artifact checks reuse the built-in `file.digest` verifier by delegation;
14
+ * this module owns no second ledger and no file hashing of its own.
15
+ *
16
+ * Managed (S2+) durable dispatch proof and Tier 1 registration are out of
17
+ * scope; this verifier covers direct/shared supervisor evidence only.
18
+ *
19
+ * @see docs/contracts/effect-ledger.v1.md "Verifier interface"
20
+ */
21
+ import { createHash } from 'node:crypto';
22
+ import { constants } from 'node:fs';
23
+ import { open } from 'node:fs/promises';
24
+ import { resolve } from 'node:path';
25
+ import { EffectLedgerError } from './errors.js';
26
+ import { fileDigestVerifier } from './verifiers/file.js';
27
+ import { EffectVerifierError } from './verifiers/types.js';
28
+ export const DISPATCH_KIND = 'x.aiwg.worker-dispatch';
29
+ export const DISPATCH_VERIFIER_VERSION = '1.0.0';
30
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
31
+ /**
32
+ * Canonical invocation-ID shape for dispatch evidence (#2891). This is the
33
+ * SINGLE definition: `tools/ralph-external/lib/dispatch-evidence.mjs`
34
+ * imports this exact binding (through `tools/_resolve-impl.mjs`'s
35
+ * `importImpl`, since `tools/` cannot import `src/*.js` directly) rather
36
+ * than duplicating the literal, so the two can never drift.
37
+ */
38
+ export const INVOCATION_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
39
+ /** Conductor-owned evidence directories and files are owner-only. */
40
+ export const DISPATCH_EVIDENCE_DIR_MODE = 0o700;
41
+ export const DISPATCH_EVIDENCE_FILE_MODE = 0o600;
42
+ export function dispatchEffectTarget(invocationId) {
43
+ return `x-aiwg:dispatch/${invocationId}`;
44
+ }
45
+ /**
46
+ * Test-only race-injection seam (#2891 R1/R7), mirroring the equivalent hook
47
+ * in `tools/ralph-external/lib/dispatch-evidence.mjs`. No-op in production
48
+ * (`afterOpen` defaults to `null`); this package's own conformance suite
49
+ * sets it to a one-shot callback that performs a deterministic TOCTOU swap
50
+ * between `loadSupervisorEvidence`'s two `open()` calls, then clears
51
+ * itself. Never read outside `openTraced` below.
52
+ */
53
+ export const __raceTestHooks = { afterOpen: null };
54
+ async function openTraced(path, flags) {
55
+ const handle = await open(path, flags);
56
+ if (__raceTestHooks.afterOpen)
57
+ await __raceTestHooks.afterOpen(path);
58
+ return handle;
59
+ }
60
+ /**
61
+ * Read supervisor evidence from the conductor-owned evidence location. The
62
+ * evidence directory is opened `O_NOFOLLOW` ONCE and kept open for the
63
+ * whole read: the file `<invocationId>.json` is then opened through that
64
+ * SAME still-open directory descriptor, via `/proc/self/fd/<dirfd>/<name>`,
65
+ * never by re-resolving `<evidenceDir>/<invocationId>.json` as a fresh path
66
+ * string. This means a swap of `evidenceDir`'s path between validating the
67
+ * directory and reading the file cannot redirect the read onto an attacker-
68
+ * controlled location — the descriptor still refers to the original,
69
+ * validated directory regardless of what the path now names.
70
+ *
71
+ * Every check runs against an open fd: the evidence directory must be a
72
+ * real directory owned by us with mode exactly 0700, and the evidence file
73
+ * must be a regular file owned by us with mode exactly 0600 and exactly one
74
+ * link (no surprise hardlink). Supervisor evidence is never read from a
75
+ * worker-writable path: any symlink, wrong mode, wrong owner, extra link or
76
+ * unbound invocationId throws instead of verifying.
77
+ *
78
+ * When `expectedRecordDigest` is supplied (the sha256 of the evidence
79
+ * file's raw bytes, recorded by the conductor at capture time — for
80
+ * example from the effect ledger intent this evidence backs), the freshly
81
+ * read bytes must hash to it or the load throws. Shape and permission
82
+ * checks alone cannot detect in-place mutation of an already-captured
83
+ * evidence file; this closes that gap for any caller that has an earlier
84
+ * recorded digest to check against.
85
+ */
86
+ export async function loadSupervisorEvidence(options) {
87
+ const invocationId = options?.invocationId;
88
+ if (typeof invocationId !== 'string' || !INVOCATION_PATTERN.test(invocationId)) {
89
+ throw new EffectVerifierError('malformed-response', 'Supervisor evidence needs a valid invocation ID');
90
+ }
91
+ if (!options || typeof options.evidenceDir !== 'string' || !options.evidenceDir) {
92
+ throw new EffectVerifierError('container-unreadable', 'Supervisor evidence needs a conductor-owned evidence directory');
93
+ }
94
+ const dirResolved = resolve(options.evidenceDir);
95
+ let dirHandle;
96
+ try {
97
+ dirHandle = await openTraced(dirResolved, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | constants.O_NOFOLLOW);
98
+ }
99
+ catch {
100
+ throw new EffectVerifierError('container-unreadable', `Supervisor evidence directory is not a real directory: ${dirResolved}`);
101
+ }
102
+ try {
103
+ const dirStat = await dirHandle.stat();
104
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
105
+ if (!dirStat.isDirectory() || (dirStat.mode & 0o777) !== DISPATCH_EVIDENCE_DIR_MODE
106
+ || (uid !== null && dirStat.uid !== uid)) {
107
+ throw new EffectVerifierError('container-unreadable', `Supervisor evidence directory is not owner-only 0700: ${dirResolved}`);
108
+ }
109
+ const name = `${invocationId}.json`;
110
+ const ref = `/proc/self/fd/${dirHandle.fd}/${name}`;
111
+ let handle;
112
+ try {
113
+ handle = await openTraced(ref, constants.O_RDONLY | constants.O_NOFOLLOW);
114
+ }
115
+ catch {
116
+ throw new EffectVerifierError('container-unreadable', `Supervisor evidence is unreadable: ${dirResolved}/${name}`);
117
+ }
118
+ try {
119
+ const stat = await handle.stat();
120
+ const fileUid = typeof process.getuid === 'function' ? process.getuid() : null;
121
+ if (!stat.isFile() || (stat.mode & 0o777) !== DISPATCH_EVIDENCE_FILE_MODE
122
+ || (fileUid !== null && stat.uid !== fileUid) || stat.nlink !== 1) {
123
+ throw new EffectVerifierError('container-unreadable', `Supervisor evidence is not an owner-only 0600 regular file with a single link: ${dirResolved}/${name}`);
124
+ }
125
+ const raw = await handle.readFile('utf8');
126
+ if (typeof options.expectedRecordDigest === 'string' && options.expectedRecordDigest) {
127
+ const actual = `sha256:${createHash('sha256').update(raw, 'utf8').digest('hex')}`;
128
+ if (actual !== options.expectedRecordDigest) {
129
+ throw new EffectVerifierError('malformed-response', 'Supervisor evidence does not match its recorded digest; mutation after capture is not trusted');
130
+ }
131
+ }
132
+ let parsed;
133
+ try {
134
+ parsed = JSON.parse(raw);
135
+ }
136
+ catch {
137
+ throw new EffectVerifierError('malformed-response', 'Supervisor evidence is not valid JSON');
138
+ }
139
+ if (!hasSupervisorEvidence(parsed) || parsed.invocationId !== invocationId) {
140
+ throw new EffectVerifierError('malformed-response', 'Supervisor evidence is not bound to this invocation');
141
+ }
142
+ return parsed;
143
+ }
144
+ finally {
145
+ await handle.close();
146
+ }
147
+ }
148
+ finally {
149
+ await dirHandle.close();
150
+ }
151
+ }
152
+ /** True only when actual supervisor evidence exists: a bound invocation and digest. */
153
+ export function hasSupervisorEvidence(evidence) {
154
+ if (!evidence || typeof evidence !== 'object')
155
+ return false;
156
+ const record = evidence;
157
+ if (typeof record.invocationId !== 'string' || !INVOCATION_PATTERN.test(record.invocationId))
158
+ return false;
159
+ if (typeof record.evidenceDigest !== 'string' || !DIGEST_PATTERN.test(record.evidenceDigest))
160
+ return false;
161
+ if (record.artifactPath !== undefined || record.artifactDigest !== undefined) {
162
+ if (typeof record.artifactPath !== 'string' || !record.artifactPath)
163
+ return false;
164
+ if (typeof record.artifactDigest !== 'string' || !DIGEST_PATTERN.test(record.artifactDigest))
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+ function assertContextShape(context, target) {
170
+ const invocationId = context.invocationId;
171
+ const evidenceDigest = context.evidenceDigest;
172
+ if (typeof invocationId !== 'string' || !INVOCATION_PATTERN.test(invocationId)
173
+ || target !== dispatchEffectTarget(invocationId)) {
174
+ throw new EffectVerifierError('malformed-response', 'Dispatch effect has an unexpected target or invocation');
175
+ }
176
+ if (typeof evidenceDigest !== 'string' || !DIGEST_PATTERN.test(evidenceDigest)) {
177
+ throw new EffectVerifierError('malformed-response', 'Dispatch effect has no evidence digest');
178
+ }
179
+ return {
180
+ invocationId,
181
+ evidenceDigest,
182
+ ...(typeof context.artifactPath === 'string' ? { artifactPath: context.artifactPath } : {}),
183
+ ...(typeof context.artifactDigest === 'string' ? { artifactDigest: context.artifactDigest } : {}),
184
+ };
185
+ }
186
+ /**
187
+ * Build the dispatch verifier bound to one project's supervisor evidence, or
188
+ * null when no actual supervisor evidence exists. Callers register the
189
+ * returned verifier (for example through the `extensions` of
190
+ * `buildCliVerifierRegistry`) only when it is non-null; a null result means
191
+ * the kind stays unregistered and reconciliation reports unknown.
192
+ */
193
+ export function createDispatchVerifierIfSupervised(options) {
194
+ if (!options || typeof options.projectDir !== 'string' || !options.projectDir) {
195
+ throw new EffectLedgerError('usage', 'Dispatch verification needs a project directory', 'invalid-project');
196
+ }
197
+ if (!hasSupervisorEvidence(options.evidence))
198
+ return null;
199
+ const supervised = options.evidence;
200
+ // The record never picks its own strictness: artifacts root ONLY at the
201
+ // conductor-owned evidence directory, and the branch below is driven by
202
+ // what the SUPERVISOR evidence names, not by what the ledger context
203
+ // offers. No evidenceDir means no delegation target: `verify()` below
204
+ // reports `evidence-conflict` for an artifact check rather than silently
205
+ // widening the trusted root to `projectDir`.
206
+ const files = options.evidenceDir ? fileDigestVerifier({ root: options.evidenceDir }) : null;
207
+ return {
208
+ kind: DISPATCH_KIND,
209
+ version: DISPATCH_VERIFIER_VERSION,
210
+ canReportAbsent: true,
211
+ async verify(request) {
212
+ const context = assertContextShape((request.context ?? {}), request.target);
213
+ if (context.invocationId !== supervised.invocationId) {
214
+ throw new EffectVerifierError('malformed-response', 'Dispatch evidence is bound to a different invocation');
215
+ }
216
+ const expected = request.expected.digest ?? request.payloadDigest;
217
+ if (expected !== supervised.evidenceDigest || context.evidenceDigest !== supervised.evidenceDigest) {
218
+ return { result: 'unknown', reason: 'evidence-conflict', complete: false };
219
+ }
220
+ // Supervisor-driven: when supervised evidence names an artifact the
221
+ // verifier ALWAYS delegates to file.digest. A context that omits the
222
+ // artifact (or mismatches it), or a missing evidenceDir to delegate
223
+ // into, is evidence-conflict, never a present/complete downgrade and
224
+ // never a projectDir fallback.
225
+ if (supervised.artifactPath !== undefined) {
226
+ if (!files) {
227
+ return { result: 'unknown', reason: 'evidence-conflict', complete: false };
228
+ }
229
+ if (context.artifactPath !== supervised.artifactPath || context.artifactDigest !== supervised.artifactDigest) {
230
+ return { result: 'unknown', reason: 'evidence-conflict', complete: false };
231
+ }
232
+ const observation = await files.verify({
233
+ ...request,
234
+ kind: 'file.digest',
235
+ target: `file:${supervised.artifactPath}@${supervised.artifactDigest}`,
236
+ expected: { digest: supervised.artifactDigest },
237
+ });
238
+ if (observation.result !== 'present')
239
+ return observation;
240
+ return {
241
+ result: 'present', reason: 'state-match', complete: true,
242
+ evidence: { invocationId: context.invocationId, evidenceDigest: supervised.evidenceDigest, artifact: supervised.artifactPath },
243
+ };
244
+ }
245
+ return {
246
+ result: 'present', reason: 'state-match', complete: true,
247
+ evidence: { invocationId: context.invocationId, evidenceDigest: supervised.evidenceDigest },
248
+ };
249
+ },
250
+ };
251
+ }
252
+ //# sourceMappingURL=dispatch-verifier.js.map
@@ -19,6 +19,7 @@ export { writeCheckpoint } from './checkpoint.js';
19
19
  export { checkpointDigest, verifyLedger, } from './verify.js';
20
20
  export { EFFECT_RETENTION_POLICY, purgeEffect } from './lifecycle.js';
21
21
  export { LEDGER_LOCK_NAME_PATTERN, LOCK_RECOVERY_KIND, LOCK_RECOVERY_VERIFIER_VERSION, inspectLedgerLock, inspectLedgerLocks, ledgerLockRecoveryVerifier, lockRecoveryTarget, recoverStaleLedgerLock, } from './lock-recovery.js';
22
+ export { DISPATCH_KIND, DISPATCH_VERIFIER_VERSION, INVOCATION_PATTERN, createDispatchVerifierIfSupervised, dispatchEffectTarget, hasSupervisorEvidence, loadSupervisorEvidence, } from './dispatch-verifier.js';
22
23
  export { assertDigestOnly, containsRestrictedMaterial } from './redaction.js';
23
24
  export { EFFECT_SCHEMA_IDS, effectSchemaErrors, isEffectSchemaValid } from './schema.js';
24
25
  export { decisionLinkOf, verifyDecisionLinks, } from './decision-links.js';
@@ -41,7 +41,11 @@ export const UNKNOWN_REASONS = [
41
41
  'container-unreadable',
42
42
  'paging-incomplete',
43
43
  'consistency-lag',
44
- 'tracker-blocked',
44
+ 'tracker-blocked', // Retained for existing ledger records.
45
+ 'tracker-provider-unresolved',
46
+ 'tracker-repository-mismatch',
47
+ 'tracker-credential-missing',
48
+ 'tracker-no-access-route',
45
49
  'malformed-response',
46
50
  'evidence-conflict',
47
51
  ];
@@ -13,6 +13,7 @@
13
13
  *
14
14
  * @see docs/contracts/effect-ledger.v1.md "Built-in verifiers"
15
15
  */
16
+ import { signalChild } from '../../process/signal-child.mjs';
16
17
  import { spawn } from 'node:child_process';
17
18
  import { EffectVerifierError } from './types.js';
18
19
  export const GIT_VERIFIER_VERSION = '1.0.0';
@@ -31,7 +32,7 @@ function spawnRunner(repoDir, binary) {
31
32
  const collect = (sink) => (chunk) => {
32
33
  size += chunk.length;
33
34
  if (size > MAX_OUTPUT)
34
- child.kill();
35
+ signalChild(child);
35
36
  else
36
37
  sink.push(chunk);
37
38
  };
@@ -5,12 +5,12 @@
5
5
  * - **Authority.** The tracker comes from `resolveTrackerAuthority` over the
6
6
  * project config and git remote URLs. A target whose forge is neither the
7
7
  * internal nor the customer tracker, or whose repository differs from that
8
- * tracker remote, is `unknown` / `tracker-blocked`. The API base is derived
9
- * from the selected remote URL; no host is assumed.
8
+ * tracker remote, is `unknown` with a specific precondition reason. The API
9
+ * base is derived from the selected remote URL; no host is assumed.
10
10
  * - **Access order.** `chooseTrackerAccess` over the probes a CLI process can
11
11
  * offer: the tracker HTTP API (with credentials from the existing tracker
12
12
  * token path, `resolveToken`), then the forge CLI (`gh`, GitHub only). MCP
13
- * is never offered. A blocker is `unknown` / `tracker-blocked`.
13
+ * is never offered. Blockers include a safe configuration or credential hint.
14
14
  * - **Matching.** A comment matches exactly when it carries the
15
15
  * `<!-- aiwg-effect: <id> -->` marker and was written by the pinned tracker
16
16
  * actor; a merged PR matches its merge commit's `Effect-Id:` trailer.
@@ -20,8 +20,8 @@
20
20
  * older than `minAbsentAgeMs`; a younger intent is `unknown` /
21
21
  * `consistency-lag`.
22
22
  *
23
- * Access material stays inside the transport: evidence carries IDs, SHAs and
24
- * digests only, and errors carry fixed messages.
23
+ * Access material stays inside the transport: evidence carries IDs, SHAs,
24
+ * digests and safe repair hints only, and errors carry fixed messages.
25
25
  *
26
26
  * @see docs/contracts/effect-ledger.v1.md "Tracker verifiers"
27
27
  */
@@ -92,7 +92,7 @@ function onPath(binary) {
92
92
  }
93
93
  }));
94
94
  }
95
- /** Resolve the tracker lane for a target: authority, remote, access path and reader. Throws `tracker-blocked`. */
95
+ /** Resolve the tracker lane, or return a classified precondition failure with a safe repair hint. */
96
96
  function resolveLane(options, target) {
97
97
  const parsed = TARGET.exec(target);
98
98
  if (!parsed)
@@ -103,16 +103,16 @@ function resolveLane(options, target) {
103
103
  const remotes = options.config?.remotes;
104
104
  let lane = null;
105
105
  if (authority.provider === provider) {
106
- lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor };
106
+ lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor, actorConfigKey: 'remotes.tracker_actor.login' };
107
107
  }
108
108
  else if (authority.customerIssueTrackerRemote && authority.customerProvider === provider) {
109
- lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor };
109
+ lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor, actorConfigKey: 'remotes.customer_tracker_actor.login' };
110
110
  }
111
111
  if (!lane)
112
- throw new EffectVerifierError('tracker-blocked');
112
+ return unknown('tracker-provider-unresolved', { hint: 'Set remotes.issue_provider (or remotes.customer_issue_provider for the customer tracker) to the target forge.' });
113
113
  const endpoint = forgeEndpointFromRemote(provider, lane.url);
114
114
  if (!endpoint || endpoint.repository.toLowerCase() !== parsed[2].toLowerCase())
115
- throw new EffectVerifierError('tracker-blocked');
115
+ return unknown('tracker-repository-mismatch', { hint: 'Match the target repository to the remote selected by remotes.issue_tracker (or remotes.customer_issue_tracker).' });
116
116
  const laneAuthority = { ...authority, provider, issueTrackerRemote: lane.remote, issueTrackerUrl: lane.url };
117
117
  const token = resolveToken(provider, options.env ?? process.env);
118
118
  const cli = options.cli === false ? null : options.cli ?? {};
@@ -125,13 +125,16 @@ function resolveLane(options, target) {
125
125
  const readerOptions = options.pageSize ? { pageSize: options.pageSize } : {};
126
126
  if (decision.kind === 'http-api' && token) {
127
127
  const transport = httpForgeTransport({ provider, apiBaseUrl: endpoint.apiBaseUrl, token, fetchImpl: options.fetchImpl, maxResponseBytes: options.maxResponseBytes });
128
- return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
128
+ return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
129
129
  }
130
130
  if (decision.kind === 'cli' && cli) {
131
131
  const transport = cliForgeTransport({ provider, hostname: endpoint.host, runner: cli.runner, binary: cli.binary, maxResponseBytes: options.maxResponseBytes });
132
- return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
132
+ return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
133
133
  }
134
- throw new EffectVerifierError('tracker-blocked');
134
+ // Gitea has no raw-read CLI fallback. GitHub may use gh when no token is set.
135
+ return provider === 'gitea'
136
+ ? unknown('tracker-credential-missing', { hint: 'Set AIWG_GITEA_TOKEN or GITEA_TOKEN for the tracker HTTP API.' })
137
+ : unknown('tracker-no-access-route', { hint: 'Set AIWG_GITHUB_TOKEN or GITHUB_TOKEN, or install and authenticate gh for the tracker host.' });
135
138
  }
136
139
  const unknown = (reason, evidence) => ({ result: 'unknown', reason, complete: false, ...(evidence ? { evidence } : {}) });
137
140
  /** `absent` once the intent is old enough, else `unknown` / `consistency-lag`. */
@@ -160,6 +163,8 @@ export function trackerCommentVerifier(options) {
160
163
  canReportAbsent: true,
161
164
  async verify(request) {
162
165
  const lane = resolveLane(options, request.target);
166
+ if ('result' in lane)
167
+ return lane;
163
168
  const expectedDigest = expectedString(request, 'digest');
164
169
  if (expectedDigest !== undefined && !DIGEST.test(expectedDigest))
165
170
  return unknown('malformed-response');
@@ -220,7 +225,10 @@ export function trackerCommentVerifier(options) {
220
225
  const summary = { ...base, pages, scanned };
221
226
  // A marker for this effect under another author is never ours; escalate instead of replaying.
222
227
  if (foreignMarker)
223
- return unknown('evidence-conflict', { ...summary, foreignMarker: true });
228
+ return unknown('evidence-conflict', {
229
+ ...summary, foreignMarker: true,
230
+ ...(!pinned ? { actorUnpinned: true, hint: `Set ${lane.actorConfigKey} to the trusted tracker author.` } : {}),
231
+ });
224
232
  if (digestMatch) {
225
233
  return { result: 'present', reason: 'digest-match', complete, evidence: { ...summary, commentId: digestMatch.id, bodyDigest: digestMatch.bodyDigest, confidence: 'exact' } };
226
234
  }
@@ -241,6 +249,8 @@ export function trackerIssueClosedVerifier(options) {
241
249
  canReportAbsent: true,
242
250
  async verify(request) {
243
251
  const lane = resolveLane(options, request.target);
252
+ if ('result' in lane)
253
+ return lane;
244
254
  const issue = await read(() => lane.reader.getIssue(lane.repository, lane.number, request.signal));
245
255
  const evidence = { ...laneEvidence(lane), state: issue.state, closedAt: issue.closedAt };
246
256
  if (issue.state === 'closed')
@@ -263,6 +273,8 @@ export function trackerPrMergedVerifier(options) {
263
273
  if (expectedSha !== undefined && !OBJECT_ID.test(expectedSha))
264
274
  return unknown('malformed-response');
265
275
  const lane = resolveLane(options, request.target);
276
+ if ('result' in lane)
277
+ return lane;
266
278
  const pr = await read(() => lane.reader.getPullState(lane.repository, lane.number, request.signal));
267
279
  const evidence = {
268
280
  ...laneEvidence(lane), state: pr.state, merged: pr.merged, mergedAt: pr.mergedAt,
@@ -200,6 +200,24 @@ export const decisionCommand = {
200
200
  allowedTools: ['Read', 'Write', 'Bash'],
201
201
  },
202
202
  };
203
+ export const rulesCommand = {
204
+ id: 'rules',
205
+ type: 'command',
206
+ name: 'Rule Pins',
207
+ description: 'Pin installed rules to every request and manage reusable capability groups',
208
+ version: '1.0.0',
209
+ capabilities: ['cli', 'rules', 'configuration', 'activation'],
210
+ keywords: ['rules', 'pin', 'unpin', 'pinned', 'tier', 'capability', 'group'],
211
+ category: 'config',
212
+ platforms: { claude: 'full', generic: 'full' },
213
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
214
+ metadata: {
215
+ type: 'command',
216
+ template: 'utility',
217
+ argumentHint: '<pin|unpin|pinned|group> [rules...] [--tier tiers] [--capability ids] [--group name] [--all] [--dry-run] [--json]',
218
+ allowedTools: ['Read', 'Write', 'Bash'],
219
+ },
220
+ };
203
221
  export const gatesCommand = {
204
222
  id: 'gates',
205
223
  type: 'command',
@@ -3752,6 +3770,7 @@ export const commandDefinitions = [
3752
3770
  updateCommand,
3753
3771
  installationCommand,
3754
3772
  decisionCommand,
3773
+ rulesCommand,
3755
3774
  gatesCommand,
3756
3775
  refreshCommand,
3757
3776
  regenerateCommand,
@@ -12,8 +12,10 @@
12
12
  * @design @.aiwg/architecture/design-aiwg-remove-revert.md
13
13
  * @implements #1037
14
14
  */
15
- import { lstat, readdir, stat, unlink, rmdir } from 'fs/promises';
15
+ import { lstat, readdir, readFile, stat, unlink, rmdir } from 'fs/promises';
16
16
  import { resolve, join, relative, dirname, isAbsolute } from 'path';
17
+ import { deployedSkillRoots } from '../providers/deployed-skill-roots.js';
18
+ import { releaseSkillOwnership } from '../providers/skill-ownership.mjs';
17
19
  import { appendProjectLocalActivity } from './project-local-activity.js';
18
20
  import { sha256OfFileNormalized } from './managed-marker.js';
19
21
  import { getProviderArtifactPathStrings } from '../providers/provider-definitions.js';
@@ -257,7 +259,40 @@ export async function removeProjectLocalBundle(config, projectDir, bundleId, opt
257
259
  for (const provider of providers) {
258
260
  const artifactHashes = artifactHashesForProvider(installedEntry, provider);
259
261
  let providerHadSkip = false;
262
+ // Receipts cover the complete transformed skill and support tree, including
263
+ // retired skills no longer present in the source inventory.
264
+ const receiptSkills = new Set();
265
+ const sourceRoot = installedEntry.localPath ? resolve(projectDir, installedEntry.localPath) : undefined;
266
+ for (const root of deployedSkillRoots(projectDir, provider)) {
267
+ for (const name of await readdir(root).catch(() => [])) {
268
+ const skillDir = join(root, name);
269
+ try {
270
+ await readFile(join(skillDir, '.aiwg-manifest.json'), 'utf8');
271
+ }
272
+ catch {
273
+ continue;
274
+ }
275
+ receiptSkills.add(name);
276
+ const removed = releaseSkillOwnership(skillDir, {
277
+ frameworkSlug: bundleId,
278
+ skillSource: sourceRoot ? join(sourceRoot, 'skills', name) : undefined,
279
+ sourceRoot,
280
+ }, { dryRun });
281
+ if (removed === null) {
282
+ // Unrelated receipts are outside this bundle's removal inventory.
283
+ if (!Object.keys(artifactHashes).some(sourceRel => sourceRel.startsWith(`skills/${name}/`)))
284
+ continue;
285
+ providerHadSkip = true;
286
+ }
287
+ outcomes.push({ provider, artifactPath: `skills/${name}/SKILL.md`, deployedAbsPath: skillDir,
288
+ case: removed === null ? 'replaced' : 'pristine', reverted: removed !== null && !dryRun,
289
+ message: removed === null ? 'owned by another source — preserved' : dryRun
290
+ ? '[dry-run] would release skill ownership' : 'released skill ownership; operator files preserved' });
291
+ }
292
+ }
260
293
  for (const sourceRel of Object.keys(artifactHashes)) {
294
+ if (sourceRel.startsWith('skills/') && receiptSkills.has(sourceRel.split('/')[1]))
295
+ continue;
261
296
  const owner = resolveOwnership(config, bundleId, provider, sourceRel);
262
297
  if (owner) {
263
298
  providerHadSkip = true;
@@ -1,4 +1,4 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { FEATURE_CATALOG, getFeature } from './catalog.js';
@@ -46,7 +46,7 @@ export async function installFeature(name, options = {}) {
46
46
  const root = options.root ?? getFeaturesRoot();
47
47
  await prepareFeatureManifest(feature, root);
48
48
  const npmCommand = options.npmCommand ?? (process.platform === 'win32' ? 'npm.cmd' : 'npm');
49
- const result = spawnSync(npmCommand, ['install', '--prefix', root, '--no-audit', '--no-fund'], { stdio: 'inherit', env: process.env });
49
+ const result = boundedSpawnSync(npmCommand, ['install', '--prefix', root, '--no-audit', '--no-fund'], 'long', { stdio: 'inherit', env: process.env });
50
50
  if (result.error)
51
51
  throw result.error;
52
52
  if (result.status !== 0) {
@@ -1,3 +1,4 @@
1
+ import { assertMissionWorkerHandoff } from '../mission-protocol/codecs.js';
1
2
  /** A2A/telemetry metadata key reserved for the optional Flow graph profile. */
2
3
  export const AIWG_GRAPH_METADATA_KEY = 'aiwg.flow.graph';
3
4
  function nonEmpty(value) {
@@ -15,9 +16,12 @@ export function isGraphExecutionMetadata(value) {
15
16
  && nonEmpty(item.nodeRunId);
16
17
  }
17
18
  export function graphMetadataRecord(value) {
19
+ assertMissionWorkerHandoff(value, 'graph');
18
20
  return { [AIWG_GRAPH_METADATA_KEY]: value };
19
21
  }
20
22
  export function extractGraphMetadata(metadata) {
23
+ if (metadata)
24
+ assertMissionWorkerHandoff({ metadata }, 'graph');
21
25
  const value = metadata?.[AIWG_GRAPH_METADATA_KEY];
22
26
  return isGraphExecutionMetadata(value) ? value : undefined;
23
27
  }
@@ -28,6 +32,7 @@ export function extractGraphMetadata(metadata) {
28
32
  * member of this contract and is therefore dropped by construction.
29
33
  */
30
34
  export function projectGraphMetadata(value, audience) {
35
+ assertMissionWorkerHandoff(value, 'graph');
31
36
  if (!isGraphExecutionMetadata(value))
32
37
  throw new Error('Invalid graph execution metadata.');
33
38
  if (audience === 'internal')