@aiwg/cli 2026.10.3 → 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 (279) hide show
  1. package/README.md +2 -2
  2. package/agentic/code/providers/model-capabilities.v1.json +96 -4
  3. package/bin/aiwg.mjs +50 -27
  4. package/dist/src/agents/grounding/knowledge-base.js +54 -6
  5. package/dist/src/artifacts/browser-export.js +6 -0
  6. package/dist/src/artifacts/checksum-manifest.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +173 -92
  8. package/dist/src/artifacts/citation-table-schema.js +59 -0
  9. package/dist/src/artifacts/cli.js +57 -18
  10. package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
  11. package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
  12. package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
  13. package/dist/src/artifacts/corpus-views/renderers.js +3 -3
  14. package/dist/src/artifacts/discovery-eval.js +6 -4
  15. package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
  16. package/dist/src/artifacts/fortemi-core-sync.js +166 -40
  17. package/dist/src/artifacts/framework-capabilities.js +171 -0
  18. package/dist/src/artifacts/framework-index-paths.js +73 -0
  19. package/dist/src/artifacts/framework-source-digest.js +75 -0
  20. package/dist/src/artifacts/index-builder.js +285 -131
  21. package/dist/src/artifacts/index-files.js +132 -18
  22. package/dist/src/artifacts/index-preparation.js +65 -0
  23. package/dist/src/artifacts/index-refresh.js +576 -0
  24. package/dist/src/artifacts/index-worker.mjs +9 -0
  25. package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
  26. package/dist/src/artifacts/prebuilt-deployment.js +46 -0
  27. package/dist/src/artifacts/prebuilt-relations.js +80 -0
  28. package/dist/src/artifacts/query-engine.js +410 -88
  29. package/dist/src/artifacts/relations-builder.js +168 -0
  30. package/dist/src/artifacts/relations.js +148 -0
  31. package/dist/src/artifacts/stats.js +5 -2
  32. package/dist/src/artifacts/types.js +30 -10
  33. package/dist/src/artifacts/watcher.js +1 -1
  34. package/dist/src/auth/credential-store.js +2 -1
  35. package/dist/src/catalog/cli.d.mts +5 -0
  36. package/dist/src/channel/manager.d.mts +20 -0
  37. package/dist/src/channel/manager.mjs +19 -2
  38. package/dist/src/cli/command-log.js +2 -3
  39. package/dist/src/cli/handlers/artifacts.js +3 -0
  40. package/dist/src/cli/handlers/auth.js +3 -0
  41. package/dist/src/cli/handlers/decision.js +19 -12
  42. package/dist/src/cli/handlers/diagnose.js +4 -3
  43. package/dist/src/cli/handlers/effect.js +4 -2
  44. package/dist/src/cli/handlers/evidence.js +3 -0
  45. package/dist/src/cli/handlers/help.js +1 -0
  46. package/dist/src/cli/handlers/index.js +3 -1
  47. package/dist/src/cli/handlers/init.js +4 -1
  48. package/dist/src/cli/handlers/lint.js +5 -0
  49. package/dist/src/cli/handlers/models.js +104 -8
  50. package/dist/src/cli/handlers/ralph-launcher.js +2 -0
  51. package/dist/src/cli/handlers/refresh.js +12 -0
  52. package/dist/src/cli/handlers/resource-versions.js +3 -0
  53. package/dist/src/cli/handlers/rules.js +195 -0
  54. package/dist/src/cli/handlers/run.js +4 -0
  55. package/dist/src/cli/handlers/runtime-info.js +1 -2
  56. package/dist/src/cli/handlers/script-runner.js +3 -2
  57. package/dist/src/cli/handlers/session.js +19 -9
  58. package/dist/src/cli/handlers/sessions.js +98 -37
  59. package/dist/src/cli/handlers/setup-manifest.js +39 -44
  60. package/dist/src/cli/handlers/setup.js +41 -0
  61. package/dist/src/cli/handlers/steward.js +11 -0
  62. package/dist/src/cli/handlers/subcommands.js +57 -2
  63. package/dist/src/cli/handlers/uhp.js +3 -0
  64. package/dist/src/cli/handlers/use.js +294 -55
  65. package/dist/src/cli/handlers/utilities.js +26 -10
  66. package/dist/src/cli/handlers/version.js +8 -0
  67. package/dist/src/cli/router.js +15 -1
  68. package/dist/src/cli/services/deployment-verification.js +19 -9
  69. package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
  70. package/dist/src/cli/skill-usage.js +2 -3
  71. package/dist/src/config/aiwg-config.js +87 -37
  72. package/dist/src/config/cli.js +111 -5
  73. package/dist/src/config/decision-config.js +106 -0
  74. package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
  75. package/dist/src/config/gitignore.js +50 -0
  76. package/dist/src/config/index-graph-overrides.js +36 -0
  77. package/dist/src/config/project-artifacts-health.d.mts +37 -0
  78. package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
  79. package/dist/src/config/user-config-dir.d.mts +9 -0
  80. package/dist/src/config/user-config.js +2 -1
  81. package/dist/src/config/user-registry.js +23 -15
  82. package/dist/src/config/user-state.js +7 -0
  83. package/dist/src/decision/ask.js +203 -43
  84. package/dist/src/decision/context-live-qualification.js +2 -2
  85. package/dist/src/decision/driver.js +13 -8
  86. package/dist/src/decision/ensemble/aggregate.js +23 -10
  87. package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
  88. package/dist/src/decision/entry.js +2 -15
  89. package/dist/src/decision/graph-live-qualification.js +1 -1
  90. package/dist/src/decision/graph-run.js +9 -0
  91. package/dist/src/decision/heldout/collector.js +5 -13
  92. package/dist/src/decision/heldout/contract.js +30 -7
  93. package/dist/src/decision/jev-credentials.js +15 -1
  94. package/dist/src/decision/ledger.js +131 -28
  95. package/dist/src/decision/offload-activation.js +137 -0
  96. package/dist/src/decision/offload.js +550 -0
  97. package/dist/src/decision/qualification/load.js +4 -6
  98. package/dist/src/decision/savings.js +28 -0
  99. package/dist/src/effects/types.js +5 -1
  100. package/dist/src/effects/verifiers/git.js +2 -1
  101. package/dist/src/effects/verifiers/tracker.js +26 -14
  102. package/dist/src/extensions/commands/definitions.js +19 -0
  103. package/dist/src/extensions/project-local-remove.js +36 -1
  104. package/dist/src/gates/providers/loader.js +61 -17
  105. package/dist/src/installation/manager-command.d.mts +18 -0
  106. package/dist/src/installation/manager-command.mjs +14 -2
  107. package/dist/src/installation/manager.d.mts +49 -0
  108. package/dist/src/installation/manager.mjs +78 -3
  109. package/dist/src/issues/cli.js +4 -4
  110. package/dist/src/issues/local.js +24 -5
  111. package/dist/src/jobs/runner.js +1 -1
  112. package/dist/src/lint/cli.js +27 -1
  113. package/dist/src/lint/loader.js +2 -1
  114. package/dist/src/lint/reporters.js +25 -1
  115. package/dist/src/lint/rule-activation.js +43 -0
  116. package/dist/src/lint/runner.js +137 -62
  117. package/dist/src/mcp/adapters/codex-runtime.js +7 -1
  118. package/dist/src/mcp/claude-install.d.mts +4 -0
  119. package/dist/src/mcp/claude-install.mjs +41 -0
  120. package/dist/src/mcp/cli.d.mts +1 -0
  121. package/dist/src/mcp/cli.mjs +36 -11
  122. package/dist/src/mcp/config-file.d.mts +28 -0
  123. package/dist/src/mcp/config-layers.d.mts +36 -0
  124. package/dist/src/mcp/credentials.d.mts +51 -0
  125. package/dist/src/mcp/grok-build-config.d.mts +6 -0
  126. package/dist/src/mcp/grok-build-config.mjs +1 -1
  127. package/dist/src/mcp/helpers.mjs +3 -2
  128. package/dist/src/mcp/muse-native-config.d.mts +13 -0
  129. package/dist/src/mcp/muse-native-config.mjs +1 -0
  130. package/dist/src/mcp/omp-config.d.mts +4 -0
  131. package/dist/src/mcp/registry.js +3 -93
  132. package/dist/src/mcp/registry.mjs +3 -87
  133. package/dist/src/mcp/server-config.d.mts +3 -0
  134. package/dist/src/mcp/server-config.mjs +89 -0
  135. package/dist/src/mcp/server.mjs +37 -375
  136. package/dist/src/mcp/test-simple.mjs +2 -1
  137. package/dist/src/mcp/toml-editor.d.mts +4 -0
  138. package/dist/src/mcp/toml-string.d.mts +1 -0
  139. package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
  140. package/dist/src/mcp/tool-filters.d.mts +46 -0
  141. package/dist/src/mcp/tools/decision.mjs +12 -11
  142. package/dist/src/mcp/tools/discovery.mjs +62 -55
  143. package/dist/src/mcp/tools/orchestration.mjs +12 -2
  144. package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
  145. package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
  146. package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
  147. package/dist/src/mission-protocol/codecs.js +8 -0
  148. package/dist/src/models/config-loader.js +7 -2
  149. package/dist/src/models/model-capabilities.v1.json +96 -4
  150. package/dist/src/models/model-discovery.js +76 -11
  151. package/dist/src/models/provider-policy.js +101 -7
  152. package/dist/src/models/qualification-digest.d.mts +2 -0
  153. package/dist/src/models/resolver.js +12 -10
  154. package/dist/src/models/worker-direct-readiness.js +65 -44
  155. package/dist/src/models/worker-dispatch-offer.js +3 -3
  156. package/dist/src/models/worker-health-readiness.js +16 -3
  157. package/dist/src/models/worker-installation.d.mts +39 -0
  158. package/dist/src/models/worker-installation.mjs +135 -28
  159. package/dist/src/process/bounded-child-process.d.mts +84 -0
  160. package/dist/src/process/bounded-child-process.mjs +43 -0
  161. package/dist/src/process/cli-command.d.mts +16 -0
  162. package/dist/src/process/signal-child.d.mts +6 -0
  163. package/dist/src/process/signal-child.mjs +7 -0
  164. package/dist/src/providers/deployed-skill-roots.js +20 -0
  165. package/dist/src/providers/grok-build-acp.js +3 -2
  166. package/dist/src/providers/grok-build-headless.js +12 -4
  167. package/dist/src/providers/grok-build-inspect.js +1 -0
  168. package/dist/src/providers/omp-agent.d.mts +8 -0
  169. package/dist/src/providers/omp-diagnostics.d.mts +1 -0
  170. package/dist/src/providers/omp-paths.d.mts +13 -0
  171. package/dist/src/providers/skill-ownership.d.mts +9 -0
  172. package/dist/src/providers/skill-ownership.mjs +88 -0
  173. package/dist/src/rules/activation.d.mts +21 -0
  174. package/dist/src/rules/activation.mjs +62 -0
  175. package/dist/src/rules/inline-budget.d.mts +26 -0
  176. package/dist/src/rules/inline-budget.mjs +66 -0
  177. package/dist/src/rules/inventory.js +129 -0
  178. package/dist/src/rules/pin-state.js +73 -0
  179. package/dist/src/rules/pins.d.mts +14 -0
  180. package/dist/src/rules/pins.mjs +112 -0
  181. package/dist/src/rules/rule-frontmatter.schema.json +197 -0
  182. package/dist/src/serve/mission-conductor.js +38 -12
  183. package/dist/src/serve/worker-invocation.js +30 -10
  184. package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
  185. package/dist/src/sessions/adapter-registry.js +23 -19
  186. package/dist/src/sessions/adapters/claude.js +39 -54
  187. package/dist/src/sessions/adapters/codex.js +82 -67
  188. package/dist/src/sessions/adapters/copilot.js +4 -4
  189. package/dist/src/sessions/adapters/cursor.js +40 -57
  190. package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
  191. package/dist/src/sessions/adapters/factory.js +31 -27
  192. package/dist/src/sessions/adapters/generic.js +10 -10
  193. package/dist/src/sessions/adapters/grok-build.js +11 -11
  194. package/dist/src/sessions/adapters/grokbot.js +24 -13
  195. package/dist/src/sessions/adapters/hermes.js +27 -29
  196. package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
  197. package/dist/src/sessions/adapters/muse.js +150 -93
  198. package/dist/src/sessions/adapters/omp.js +28 -14
  199. package/dist/src/sessions/adapters/openclaw.js +89 -79
  200. package/dist/src/sessions/adapters/opencode.js +27 -24
  201. package/dist/src/sessions/adapters/openhuman.js +18 -60
  202. package/dist/src/sessions/adapters/pi.js +29 -14
  203. package/dist/src/sessions/adapters/warp.js +3 -3
  204. package/dist/src/sessions/adapters/windsurf.js +19 -48
  205. package/dist/src/sessions/batch-contracts.js +16 -8
  206. package/dist/src/sessions/batch-import.js +51 -11
  207. package/dist/src/sessions/contracts.js +40 -2
  208. package/dist/src/sessions/coverage-preflight.js +1 -1
  209. package/dist/src/sessions/export-plan.js +4 -3
  210. package/dist/src/sessions/import-exclusions.js +72 -0
  211. package/dist/src/sessions/import-lease.js +34 -18
  212. package/dist/src/sessions/import-limits.js +37 -0
  213. package/dist/src/sessions/importer.js +60 -38
  214. package/dist/src/sessions/lifecycle.js +45 -0
  215. package/dist/src/sessions/policy.js +133 -25
  216. package/dist/src/sessions/readers.js +106 -53
  217. package/dist/src/sessions/refresh.js +106 -0
  218. package/dist/src/sessions/repository.js +147 -52
  219. package/dist/src/sessions/workspace-discovery.js +221 -56
  220. package/dist/src/sessions/workspace-matching.js +107 -0
  221. package/dist/src/skills/importer.js +1 -0
  222. package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
  223. package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
  224. package/dist/src/smiths/context-pipeline/finalization.js +6 -5
  225. package/dist/src/smiths/context-pipeline/generator.js +21 -4
  226. package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
  227. package/dist/src/smiths/mcpsmith/generator.js +1 -1
  228. package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
  229. package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
  230. package/dist/src/storage/backends/fs.js +18 -76
  231. package/dist/src/storage/backends/obsidian.js +1 -0
  232. package/dist/src/tracker/forge-http.js +2 -1
  233. package/dist/src/update/checker.d.mts +2 -0
  234. package/dist/src/update/service.d.mts +22 -0
  235. package/dist/src/utils/contained-filesystem.d.mts +37 -0
  236. package/dist/src/utils/contained-filesystem.mjs +683 -0
  237. package/dist/src/utils/temporary-directory.js +36 -0
  238. package/dist/src/writing/example-generator.js +1 -1
  239. package/package.json +15 -2
  240. package/schemas/models/model-catalog.v1.schema.json +1 -1
  241. package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
  242. package/tools/_resolve-impl.mjs +13 -15
  243. package/tools/agents/deploy-agents.mjs +49 -21
  244. package/tools/agents/providers/base.mjs +104 -134
  245. package/tools/agents/providers/claude.mjs +8 -24
  246. package/tools/agents/providers/codex.mjs +18 -11
  247. package/tools/agents/providers/cursor.mjs +19 -18
  248. package/tools/agents/providers/grok-build.mjs +3 -1
  249. package/tools/agents/providers/hermes.mjs +57 -47
  250. package/tools/agents/providers/openclaw.mjs +9 -4
  251. package/tools/agents/providers/pi.mjs +5 -0
  252. package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
  253. package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
  254. package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
  255. package/tools/agents/providers/rule-delivery.mjs +1270 -0
  256. package/tools/agents/providers/warp.mjs +20 -26
  257. package/tools/cli/decision-offload-report.mjs +40 -0
  258. package/tools/cli/workspace-status.mjs +862 -0
  259. package/tools/plugin/package-plugins.mjs +186 -8
  260. package/tools/plugin/plugin-links.mjs +114 -0
  261. package/tools/providers/antigravity-transport.mjs +3 -1
  262. package/tools/providers/deepseek-harness-transport.mjs +6 -4
  263. package/tools/providers/omp-transport.mjs +4 -2
  264. package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
  265. package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
  266. package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
  267. package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
  268. package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
  269. package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
  270. package/tools/ralph-external/lib/session-environment.mjs +28 -0
  271. package/tools/ralph-external/lib/systemd-client.mjs +49 -0
  272. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  273. package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
  274. package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
  275. package/tools/ralph-external/process-monitor.mjs +1017 -0
  276. package/tools/ralph-external/session-launcher.mjs +5057 -0
  277. package/tools/scripts/resource-preflight.mjs +541 -0
  278. package/tools/skills/deploy-skills-codex.mjs +2 -1
  279. package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
@@ -1,11 +1,11 @@
1
- import { ATTRIBUTION_FIELD_PATTERN, askDecision, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, recordDecisionAsk, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
1
+ import { ATTRIBUTION_FIELD_PATTERN, DecisionAskUsageError, decisionCapabilities, DecisionLedgerSinceError, decisionLedgerHistory, decisionLedgerStats, evaluateRequestPath, listPatterns, livePlan, MAX_ATTRIBUTION_FIELD_LENGTH, materializeSyntheticClassificationSetup, parseDecisionLedgerSince, readDecisionContextFile, runDecisionOffloadCheckpoint, runOfflinePattern, setupJev, showPattern, syntheticClassificationSetup, validateDecisionInput, } from '../../decision/driver.js';
2
2
  function jsonResult(value, exitCode = 0) {
3
3
  return { exitCode, message: JSON.stringify(value, null, 2), rawOutput: true };
4
4
  }
5
5
  function usage() {
6
6
  return [
7
7
  'Usage: aiwg decision <capabilities|status|patterns|validate|evaluate|setup|history|stats> [options]',
8
- ' aiwg decision ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--context <text> | --context-file <path> | --context-stdin] [--threshold 0.8] [--timeout-ms 15000] [--provider <id>] [--session <id>] [--caller <tag>] [--record-text] [--json]',
8
+ ' aiwg decision ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--context <text> | --context-file <path> | --context-stdin] [--threshold 0.8] [--timeout-ms 15000] [--passes N] [--escalation-passes N] [--provider <id>] [--session <id>] [--caller <tag>] [--record-text] [--json]',
9
9
  '',
10
10
  'Commands:',
11
11
  ' capabilities|status Show offline readiness, primitives, config and feature availability',
@@ -19,7 +19,7 @@ function usage() {
19
19
  ' setup jev [--token-stdin] [--region <r>] [--endpoint <url>] [--verify] [--remove]',
20
20
  ' ask --question "<q>" (--yes-no | --choices a,b,c | --scale 1-5) [--provider <id>] [--session <id>] [--caller <tag>] [--record-text]',
21
21
  ' history [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--json]',
22
- ' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller] [--json]',
22
+ ' stats [--since <date>] [--provider <id>] [--session <id>] [--kind yes-no|choices|scale] [--status answered|fallback] [--caller <tag>|<prefix>* | --no-caller] [--group-by provider|session|kind|caller|passes|escalation] [--json]',
23
23
  '',
24
24
  'Notes:',
25
25
  ' Every COMPLETED `ask` call (CLI and MCP) appends one record to the decision',
@@ -139,22 +139,25 @@ async function decisionAsk(ctx) {
139
139
  : choices !== undefined ? { kind: 'choices', choices: choices.split(',').map(value => value.trim()).filter(Boolean) }
140
140
  : { kind: 'scale', ...scale },
141
141
  ...(context === undefined ? {} : { context }),
142
+ ...(args.includes('--passes') ? { passes: parseNumber(takeRequiredOption(args, '--passes'), '--passes') } : {}),
143
+ ...(args.includes('--escalation-passes') ? { escalationPasses: parseNumber(takeRequiredOption(args, '--escalation-passes'), '--escalation-passes') } : {}),
142
144
  ...(args.includes('--threshold') ? { threshold: parseNumber(takeOption(args, '--threshold'), '--threshold') } : {}),
143
145
  ...(args.includes('--timeout-ms') ? { timeoutMs: parseNumber(takeOption(args, '--timeout-ms'), '--timeout-ms') } : {}),
144
146
  };
145
- const result = await askDecision(input, { env: process.env });
146
- // Best-effort and bounded (recordDecisionAsk never throws and never hangs past its own
147
- // deadline): a ledger failure, timeout, or attribution-detection error can never change
148
- // `result` or the exit code decided below (review r1 findings 3 and 4).
149
- await recordDecisionAsk(input, result, {
147
+ const invocation = await runDecisionOffloadCheckpoint(input, {
150
148
  transport: 'cli',
151
149
  projectRoot: ctx.cwd,
152
150
  env: process.env,
153
151
  explicitProvider: takeOption(args, '--provider'),
154
152
  explicitSessionId,
155
- caller: caller ?? null,
153
+ caller: caller ?? 'decision-cli:ask',
154
+ ledgerCaller: caller ?? null,
155
+ category: caller ? undefined : 'unattributed',
156
156
  recordText: args.includes('--record-text'),
157
157
  });
158
+ if (!invocation.result)
159
+ throw invocation.outcome === 'unknown' ? invocation.error : new Error('Decision ask was skipped');
160
+ const result = invocation.result;
158
161
  if (args.includes('--json'))
159
162
  return jsonResult(result);
160
163
  if (result.status === 'fallback')
@@ -175,7 +178,7 @@ function parseSince(value) {
175
178
  }
176
179
  const DECISION_LEDGER_KINDS = ['yes-no', 'choices', 'scale'];
177
180
  const DECISION_LEDGER_STATUSES = ['answered', 'fallback'];
178
- const DECISION_LEDGER_GROUP_BYS = ['provider', 'session', 'kind', 'caller'];
181
+ const DECISION_LEDGER_GROUP_BYS = ['provider', 'session', 'kind', 'caller', 'passes', 'escalation'];
179
182
  function parseKindFilter(value) {
180
183
  if (value === undefined)
181
184
  return undefined;
@@ -305,10 +308,14 @@ async function decisionStats(ctx) {
305
308
  if (result.groups.length === 0)
306
309
  return { exitCode: 0, message: [...header, 'No decision ledger records match the filters.'].join('\n'), rawOutput: true };
307
310
  const lines = result.groups.flatMap(group => [
308
- `${result.groupBy ?? 'all'}=${group.key ?? 'null'}: ${group.totalCalls} calls (${group.answered} answered, ${group.fallback} fallback, rate=${group.fallbackRate.toFixed(2)})`,
311
+ `${result.groupBy ?? 'all'}=${group.key ?? 'null'}: ${group.totalCalls} calls (${group.answered} answered, ${group.fallback} fallback, hit rate=${group.hitRate.toFixed(2)}, fallback rate=${group.fallbackRate.toFixed(2)})`,
309
312
  ` confidence: mean=${group.confidence.mean?.toFixed(2) ?? '-'} min=${group.confidence.min?.toFixed(2) ?? '-'} max=${group.confidence.max?.toFixed(2) ?? '-'} (n=${group.confidence.count})`,
313
+ ` agreement: mean=${group.agreement.mean?.toFixed(2) ?? '-'} escalated=${group.escalatedCalls} passes run=${group.passesRun}`,
310
314
  ` jev usage: input=${group.jevUsage.inputTokens} output=${group.jevUsage.outputTokens}`,
311
- ` estimated LLM tokens avoided=${group.estimatedLlmTokensAvoidedTotal} net savings=${group.netSavingsTokens}`,
315
+ ` tokens per answered decision=${group.tokensPerAnsweredDecision?.toFixed(2) ?? '-'}`,
316
+ ` llm_tokens_avoided=${group.llm_tokens_avoided} jev_tokens=${group.jev_tokens} cost_weighted_net_usd=${group.cost_weighted_net_usd.toFixed(6)}`,
317
+ ` v1 comparison only: estimated LLM tokens avoided=${group.estimatedLlmTokensAvoidedTotal} net savings=${group.netSavingsTokens}`,
318
+ ...(group.legacyReestimatedCalls ? [` v1 records re-estimated=${group.legacyReestimatedCalls} weights=${JSON.stringify(result.legacyEstimationWeights)}`] : []),
312
319
  ]);
313
320
  return { exitCode: 0, message: [...header, ...lines].join('\n'), rawOutput: true };
314
321
  }
@@ -207,9 +207,9 @@ function redactSecretsInObject(obj) {
207
207
  }
208
208
  function collectGitInfo(cwd) {
209
209
  try {
210
- const branch = spawnSync('git', ['-C', cwd, 'rev-parse', '--abbrev-ref', 'HEAD'], { stdio: 'pipe', timeout: 5000 });
211
- const log = spawnSync('git', ['-C', cwd, 'log', '-10', '--oneline'], { stdio: 'pipe', timeout: 5000 });
212
- const remote = spawnSync('git', ['-C', cwd, 'remote', '-v'], { stdio: 'pipe', timeout: 5000 });
210
+ const branch = spawnSync('git', ['-C', cwd, 'rev-parse', '--abbrev-ref', 'HEAD'], { stdio: 'pipe', timeout: 5000, killSignal: 'SIGKILL' });
211
+ const log = spawnSync('git', ['-C', cwd, 'log', '-10', '--oneline'], { stdio: 'pipe', timeout: 5000, killSignal: 'SIGKILL' });
212
+ const remote = spawnSync('git', ['-C', cwd, 'remote', '-v'], { stdio: 'pipe', timeout: 5000, killSignal: 'SIGKILL' });
213
213
  if (branch.status !== 0)
214
214
  return null;
215
215
  return [
@@ -232,6 +232,7 @@ function collectDoctorOutput() {
232
232
  stdio: 'pipe',
233
233
  timeout: 30_000,
234
234
  env: { ...process.env, AIWG_LOG_DISABLE: '1', NO_COLOR: '1' },
235
+ killSignal: 'SIGKILL',
235
236
  });
236
237
  const out = r.stdout?.toString() ?? '';
237
238
  const err = r.stderr?.toString() ?? '';
@@ -243,7 +243,7 @@ function openLedger(session, registry) {
243
243
  * `kinds`, `record`, `reconcile` and `probe` all use it, so `kinds` lists
244
244
  * exactly what the registry contains. Tracker authority is resolved lazily at
245
245
  * verification time, so a project without tracker configuration still lists
246
- * the tracker kinds and answers `unknown` / `tracker-blocked`.
246
+ * the tracker kinds and answers `unknown` with a tracker precondition reason.
247
247
  */
248
248
  export function buildCliVerifierRegistry(projectRoot, extensions = [], tracker = { config: null, remoteUrls: {} }) {
249
249
  const injected = new Set(extensions.map(verifier => verifier.kind));
@@ -425,7 +425,9 @@ function expectationsFrom(parsed) {
425
425
  return { expected, ...(timeoutMs !== undefined ? { timeoutMs } : {}), ...(verifierVersion ? { verifierVersion } : {}) };
426
426
  }
427
427
  function emit(rendered, format) {
428
- const message = format === 'text' ? rendered.text : effectOutputJson(rendered.output);
428
+ const evidence = rendered.output.evidence;
429
+ const hint = typeof evidence?.hint === 'string' ? `\n${evidence.hint}` : '';
430
+ const message = format === 'text' ? rendered.text + hint : effectOutputJson(rendered.output);
429
431
  return { exitCode: rendered.output.exitCode, message, rawOutput: true };
430
432
  }
431
433
  function errorOutput(error) {
@@ -28,6 +28,9 @@ function usage() {
28
28
  }
29
29
  export const evidenceHandler = {
30
30
  id: 'evidence', name: 'Evidence', description: 'Export and verify portable evaluation evidence bundles', category: 'utility', aliases: [],
31
+ async help() {
32
+ return { exitCode: 0, message: usage(), rawOutput: true };
33
+ },
31
34
  async execute(ctx) {
32
35
  const [subcommand] = ctx.args;
33
36
  if (!subcommand || subcommand === '--help' || subcommand === '-h')
@@ -92,6 +92,7 @@ function displayHelp() {
92
92
  ['show <type> <name>', 'Stream the body of an indexed artifact'],
93
93
  ['versions <list|resolve|show>', 'Browse and resolve signed AIWG web resource releases'],
94
94
  ['auth <login|status|logout>', 'Authenticate for paid AIWG web resources'],
95
+ ['corpus <subcommand>', 'Research corpus tools (snapshot, radar, profiles, curator)'],
95
96
  ['index <subcommand>', 'Manage the artifact index (build/query/discover/deps/stats)'],
96
97
  ['artifacts move --to <path>', 'Move/rename the project AIWG artifact root and reindex'],
97
98
  ]);
@@ -73,6 +73,7 @@ import { installationHandler } from './installation.js';
73
73
  import { uhpHandler } from './uhp.js';
74
74
  import { schemaHandler } from './schema.js';
75
75
  import { datasetHandler } from './dataset.js';
76
+ import { rulesHandler } from './rules.js';
76
77
  import { decisionHandler } from './decision.js';
77
78
  import { gatesHandler } from './gates.js';
78
79
  // Re-export individual handlers
@@ -80,7 +81,7 @@ export {
80
81
  // Maintenance
81
82
  helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
82
83
  // Framework management
83
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, writerProfileHandler, botHandoffHandler, buildVerifyHandler, writingHandler, schemaHandler, datasetHandler, decisionHandler, gatesHandler, installationHandler,
84
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, writerProfileHandler, botHandoffHandler, buildVerifyHandler, writingHandler, schemaHandler, datasetHandler, decisionHandler, rulesHandler, gatesHandler, installationHandler,
84
85
  // Project
85
86
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
86
87
  // Workspace
@@ -172,6 +173,7 @@ export const allHandlers = [
172
173
  schemaHandler,
173
174
  datasetHandler,
174
175
  decisionHandler,
176
+ rulesHandler,
175
177
  gatesHandler,
176
178
  // Workspace management
177
179
  ...workspaceHandlers,
@@ -245,7 +245,7 @@ export const initHandler = {
245
245
  }
246
246
  else if (workerDispatchArg === 'enable') {
247
247
  await writeAiwgConfig(projectDir, { ...existing, parallelism: { ...existing.parallelism, worker_dispatch: true } });
248
- ui.success(' Enabled parallelism.worker_dispatch.');
248
+ ui.success(' Enabled parallelism.worker_dispatch. Next: aiwg doctor --probe-workers --qualify.');
249
249
  }
250
250
  else {
251
251
  await writeAiwgConfig(projectDir, { ...existing, setup: { ...existing.setup, worker_dispatch_offer: 'declined' } });
@@ -261,6 +261,7 @@ export const initHandler = {
261
261
  const decision = await askWorkerDispatchOffer(rl, existing.parallelism, existing.setup, ctx.signal);
262
262
  if (decision?.kind === 'enable') {
263
263
  await writeAiwgConfig(projectDir, { ...existing, parallelism: { ...existing.parallelism, worker_dispatch: true } });
264
+ ui.info('Next: aiwg doctor --probe-workers --qualify.');
264
265
  }
265
266
  else if (decision?.kind === 'decline') {
266
267
  await writeAiwgConfig(projectDir, { ...existing, setup: { ...existing.setup, worker_dispatch_offer: 'declined' } });
@@ -379,6 +380,8 @@ export const initHandler = {
379
380
  config = await populateDeployedTo(config, projectDir);
380
381
  // Write
381
382
  await writeAiwgConfig(projectDir, config);
383
+ if (workerDispatchDecision?.kind === 'enable')
384
+ ui.info('Next: aiwg doctor --probe-workers --qualify.');
382
385
  const normalizedAiwgMdPath = await writeNormalizedAiwgMd(projectDir);
383
386
  const workspace = await ensureWorkspaceContext(projectDir);
384
387
  ui.blank();
@@ -25,6 +25,11 @@ export const lintHandler = {
25
25
  description: 'Lint AIWG artifacts against declarative rule sets',
26
26
  category: 'utility',
27
27
  aliases: ['-lint', '--lint'],
28
+ async help() {
29
+ const { main } = await import('../../lint/cli.js');
30
+ await main(['--help']);
31
+ return { exitCode: 0 };
32
+ },
28
33
  async execute(ctx) {
29
34
  try {
30
35
  const { main } = await import('../../lint/cli.js');
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { promises as fs } from 'node:fs';
6
6
  import os from 'node:os';
7
+ import { isDeepStrictEqual } from 'node:util';
7
8
  import path from 'node:path';
8
9
  import { glob } from 'glob';
9
10
  import { load as loadYaml } from 'js-yaml';
@@ -11,8 +12,16 @@ import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectMo
11
12
  import { resolveGrokBuildRoleModel } from '../../models/grok-build-models.js';
12
13
  import { readAiwgConfig, emptyConfig } from '../../config/aiwg-config.js';
13
14
  import { ConfigLoader } from '../../models/config-loader.js';
14
- import { loadRecordedWorkerInstallations } from '../../models/worker-installation.mjs';
15
+ import { loadRecordedWorkerInstallations, normalizeWorkerVersionBanner } from '../../models/worker-installation.mjs';
15
16
  import { parseWorkerCommandArgs, resolveWorkerInvocation, workerInvocationDiagnostic } from '../../serve/worker-invocation.js';
17
+ /** Compare selection facts without observation times or approval/provenance metadata. */
18
+ function approvalFacts(row) {
19
+ if (!row.worker)
20
+ return null;
21
+ const { observedAt, verifiedAt, firstObservedAt, fetchedAt, retiredAt, evidenceDigest, provenance, qualification, ...facts } = row.worker;
22
+ return { id: row.id, apiModelId: row.apiModelId, llmProvider: row.llmProvider,
23
+ hidden: row.hidden, reasoningEfforts: row.reasoningEfforts, facts };
24
+ }
16
25
  /**
17
26
  * #2892: exported so the MCP worker-invoke bridge can resolve a direct worker
18
27
  * plan in-process (same snapshot/driver construction as `aiwg models resolve`),
@@ -30,9 +39,48 @@ export async function loadWorkerInputs(ctx) {
30
39
  return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
31
40
  registry, installations: {} }, drivers: {} };
32
41
  }
33
- const { registry: qualifiedRegistry, installations, drivers } = await loadRecordedWorkerInstallations({
34
- registry, cwd: ctx.cwd, policy: models.defaults.workerSelection,
42
+ const { registry: qualifiedRegistry, installations, drivers, observations } = await loadRecordedWorkerInstallations({
43
+ registry, cwd: ctx.cwd, policy: models.defaults.workerSelection, parallelism: config.parallelism,
35
44
  });
45
+ catalog.discovery ??= { source: 'static', fetchedAt: new Date().toISOString(), providers: {} };
46
+ catalog.discovery.instances = { ...catalog.discovery.instances };
47
+ for (const [cli, approved] of Object.entries(observations)) {
48
+ const cached = catalog.discovery.instances[cli] ?? catalog.discovery.providers[approved.provider];
49
+ // Discovery stores the CLI banner; qualification stores its parsed version.
50
+ const invocation = drivers[cli]?.qualification.adapter.invocation;
51
+ const versionPattern = invocation?.kind === 'process' ? invocation.versionPattern : undefined;
52
+ const version = (value) => {
53
+ if (value === undefined)
54
+ return value;
55
+ try {
56
+ return normalizeWorkerVersionBanner(value, versionPattern);
57
+ }
58
+ catch {
59
+ return value;
60
+ } // A changed or invalid banner must still fail closed.
61
+ };
62
+ const current = cached ? { ...cached, runtimeVersion: version(cached.runtimeVersion),
63
+ accountBinding: cached.accountBinding ?? cached.accountScope,
64
+ models: cached.models.map(row => row.worker ? { ...row,
65
+ worker: { ...row.worker, runtimeVersion: version(row.worker.runtimeVersion) } } : row) } : undefined;
66
+ // Stale discovery must not replace a newer approved observation.
67
+ if (!current || Date.parse(approved.observedAt) > Date.parse(current.observedAt)) {
68
+ catalog.discovery.instances[cli] = approved;
69
+ continue;
70
+ }
71
+ catalog.discovery.instances[cli] = { ...current, models: current.models.map(row => {
72
+ const prior = approved.models.find(model => model.id === row.id);
73
+ if (!row.worker || !prior?.worker)
74
+ return row;
75
+ if (Date.parse(prior.worker.observedAt) > Date.parse(row.worker.observedAt))
76
+ return prior;
77
+ // New retirement/conflict/visibility evidence always requires requalification.
78
+ const safe = !row.hidden && row.worker.visibility !== 'hidden'
79
+ && row.worker.lifecycle !== 'retired' && !row.worker.conflicts?.length;
80
+ return safe && isDeepStrictEqual(approvalFacts(row), approvalFacts(prior))
81
+ ? { ...row, worker: { ...row.worker, qualification: prior.worker.qualification } } : row;
82
+ }) };
83
+ }
36
84
  return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
37
85
  registry: qualifiedRegistry, installations }, drivers };
38
86
  }
@@ -272,9 +320,60 @@ function replaceFrontmatter(raw, item, tier) {
272
320
  }
273
321
  return `---\n${frontmatter}\n---\n${match[2]}`;
274
322
  }
323
+ const MODELS_HELP = `Usage: aiwg models <subcommand> [options]
324
+
325
+ Subcommands:
326
+ list List discovered agent and skill model policies
327
+ audit Compile selected policies and report diagnostics
328
+ resolve Resolve artifact policies, or a direct worker invocation
329
+ validate Validate selected artifact policies (not worker configuration)
330
+ sources Show the effective model catalog without network refresh
331
+ refresh Refresh model discovery (may access the network)
332
+ set-default <TIER> Write defaults.tier to models.json
333
+ set --tier <TIER> Update selected agent/skill frontmatter; requires a selector or --all
334
+ migrate Add canonical role/tier hints to legacy skills
335
+
336
+ Artifact selectors and options:
337
+ --agent <name> --skill <name> --glob <pattern> --role <role>
338
+ --tier <tier> --framework <name> --all
339
+ --provider <provider> Provider for artifact compilation (default: claude)
340
+ --model <id> Explicit Grok Build model for artifact resolve
341
+ --target <directory> Artifact/project root (default: current directory)
342
+ --scope project|user set-default destination (default: project)
343
+ --dry-run Preview mutations without writing
344
+ --json Emit JSON
345
+ --drift Include catalog drift for sources/refresh
346
+ --url <url> Remote catalog URL for refresh
347
+ --help, -h Show this reference
348
+ TIER: economy | standard | premium | max-quality
349
+
350
+ Direct worker resolution (plans an invocation; does not launch it):
351
+ aiwg models resolve --role <role> --task <class> --group <group>
352
+ --brief <file> --workdir <tree> --final-output <file>
353
+ [--transport shell|mcp] [--format json|shell] [--authorize-premium]
354
+ --role efficiency | coding | reasoning
355
+ --task Task class from defaults.workerSelection.taskClasses
356
+ Built-ins: inventory, implementation, architecture, review
357
+ --group Group from parallelism.groups and the role's guidance
358
+ --brief Absolute path to the task brief
359
+ --workdir Absolute path to the worker tree
360
+ --final-output Absolute path for the final result
361
+ --transport Must match group execution transport (default: shell)
362
+ --format JSON plan or POSIX shell command (default: json)
363
+ --json Alias for --format json
364
+ --authorize-premium Authorize premium for this resolution only
365
+ Agents require explicit human confirmation of escalation
366
+
367
+ Worker groups live in .aiwg/aiwg.config; role guidance lives in project-root models.json.
368
+ Configure both, then run aiwg doctor --probe-workers --qualify.
369
+ See docs/getting-started/worker-dispatch.md for a complete example.`;
275
370
  async function execute(ctx, services = { load: loadWorkerInputs }) {
371
+ if (ctx.args.length === 0 || ctx.args[0] === 'help' || ctx.args.includes('--help') || ctx.args.includes('-h')) {
372
+ print(MODELS_HELP, false);
373
+ return { exitCode: 0 };
374
+ }
276
375
  // Preserve artifact/native resolve when direct-only flags are absent.
277
- if (ctx.args[0] === 'resolve' && ctx.args.slice(1).some(arg => /^--(?:task|group|brief|workdir|final-output|transport|format)(?:=|$)/.test(arg))) {
376
+ if (ctx.args[0] === 'resolve' && ctx.args.slice(1).some(arg => /^--(?:task|group|brief|workdir|final-output|transport|format|authorize-premium)(?:=|$)/.test(arg))) {
278
377
  return resolveDirect(ctx, services);
279
378
  }
280
379
  const parsed = parseArgs(ctx.args);
@@ -290,10 +389,6 @@ async function execute(ctx, services = { load: loadWorkerInputs }) {
290
389
  const provider = flagString(parsed, 'provider') ?? 'claude';
291
390
  if (!PROVIDERS.has(provider))
292
391
  return { exitCode: 2, message: `Unknown provider: ${provider}` };
293
- if (parsed.subcommand === 'help' || parsed.flags.has('help')) {
294
- print('Usage: aiwg models <audit|list|resolve|sources|refresh|set-default|set|validate|migrate> [selectors] [--provider P] [--dry-run] [--json]', json);
295
- return { exitCode: 0 };
296
- }
297
392
  if (parsed.subcommand === 'sources' || parsed.subcommand === 'refresh') {
298
393
  const { diffModelCatalog } = await import('../../models/model-discovery.js');
299
394
  const catalog = await effectiveCatalog(ctx, target, parsed.subcommand === 'refresh', parsed, provider);
@@ -438,6 +533,7 @@ export const modelsHandler = {
438
533
  description: 'Audit, resolve, validate, and safely update model policy',
439
534
  category: 'catalog',
440
535
  aliases: [],
536
+ help: async () => ({ exitCode: 0, message: MODELS_HELP, rawOutput: true }),
441
537
  execute,
442
538
  };
443
539
  export function createModelsHandler(services) {
@@ -296,6 +296,8 @@ export function saveLauncherRegistry(projectRoot, registry) {
296
296
  * Check if a process is still running
297
297
  */
298
298
  export function isProcessAlive(pid) {
299
+ if (!Number.isSafeInteger(pid) || pid <= 0)
300
+ return false;
299
301
  try {
300
302
  process.kill(pid, 0);
301
303
  return true;
@@ -572,6 +572,7 @@ export const refreshHandler = {
572
572
  // #2507: give operators a reclaim path for artifacts an older
573
573
  // AIWG left behind without a managed marker.
574
574
  ...(forceDeploy ? ['--force'] : []),
575
+ ...(hasFlag(ctx.args, '--allow-over-budget') ? ['--allow-over-budget'] : []),
575
576
  ...modelDeployArgs,
576
577
  ],
577
578
  rawArgs: ['use', fw],
@@ -596,6 +597,17 @@ export const refreshHandler = {
596
597
  }
597
598
  }
598
599
  }
600
+ // Runtime updates can change the corpus even when there are no recorded
601
+ // deployments. Refresh both discovery representations, independently of use.
602
+ if (!dryRun) {
603
+ try {
604
+ const { refreshFrameworkCapabilities } = await import('../../artifacts/framework-capabilities.js');
605
+ await refreshFrameworkCapabilities(frameworkRoot, true, { force: true });
606
+ }
607
+ catch (error) {
608
+ ui.warn(`Capability index refresh failed: ${error instanceof Error ? error.message : String(error)}`);
609
+ }
610
+ }
599
611
  // Step 4.25: Report planned project-local deploys (#1035).
600
612
  // The active use handler performs the actual project-local deploy during
601
613
  // framework refresh; this block surfaces dry-run and completion details.
@@ -159,6 +159,9 @@ export const versionsHandler = {
159
159
  description: "Browse and resolve signed AIWG web resource releases",
160
160
  category: "index",
161
161
  aliases: [],
162
+ async help() {
163
+ return { exitCode: 0, message: usage(), rawOutput: true };
164
+ },
162
165
  async execute(ctx) {
163
166
  if (ctx.args[0] === "help" || ctx.args[0] === "--help" || ctx.args[0] === "-h") {
164
167
  console.log(usage());
@@ -0,0 +1,195 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { loadRulePinConfigs, selectorKey, validateRulePinCapabilities } from '../../rules/pins.mjs';
4
+ const usage = `Usage: aiwg rules pin|unpin [rule...] [--tier critical,high] [--capability id,... | --group name] [--all-tiers] [--all]
5
+ aiwg rules pinned [--capability id] [--json]
6
+ aiwg rules inventory|list [--scope workspace|user|installation|catalog] [--capability id,...] [--activation mode,...] [--tier level,...] [--provider name] [--bodies] [--json]
7
+ aiwg rules group set <name> <capability>... | group list
8
+ Flags: --project (default), --user, --dry-run, --no-deploy, --json, --allow-over-budget
9
+ Pins and group changes validate every input without prompting. --dry-run prints effective rules and per-provider startup bytes.`;
10
+ const valueFlags = new Set(['--tier', '--capability', '--group', '--scope', '--activation', '--provider']);
11
+ const boolFlags = new Set(['--all-tiers', '--all', '--dry-run', '--no-deploy', '--json', '--allow-over-budget', '--user', '--project', '--help', '-h', '--bodies']);
12
+ function parse(args) {
13
+ const values = new Map();
14
+ const flags = new Set();
15
+ const positional = [];
16
+ for (let i = 0; i < args.length; i++) {
17
+ const arg = args[i];
18
+ if (valueFlags.has(arg)) {
19
+ if (!args[i + 1] || args[i + 1].startsWith('-') || values.has(arg))
20
+ throw new Error(`${arg} requires one value`);
21
+ values.set(arg, args[++i]);
22
+ }
23
+ else if (boolFlags.has(arg))
24
+ flags.add(arg);
25
+ else if (arg.startsWith('-'))
26
+ throw new Error(`Unknown flag: ${arg}`);
27
+ else
28
+ positional.push(arg);
29
+ }
30
+ if (flags.has('--user') && flags.has('--project'))
31
+ throw new Error('Choose --project or --user');
32
+ const list = (flag) => values.get(flag)?.split(',').map(id => id.trim());
33
+ return { values, flags, positional, list };
34
+ }
35
+ // Provider helpers log progress while assembling staged output. Keep the command's
36
+ // stdout a single JSON document (and keep temporary staging paths out of text output).
37
+ async function quietDeployment(deploy, options) {
38
+ const log = console.log;
39
+ const warn = console.warn;
40
+ try {
41
+ console.log = () => { };
42
+ console.warn = () => { };
43
+ return await deploy(options);
44
+ }
45
+ finally {
46
+ console.log = log;
47
+ console.warn = warn;
48
+ }
49
+ }
50
+ export const rulesHandler = {
51
+ id: 'rules', name: 'Rule pins', category: 'config', aliases: [],
52
+ description: 'Pin rules to every request, inspect pins, and manage capability groups',
53
+ async help() { return { exitCode: 0, message: usage, rawOutput: true }; },
54
+ async execute(ctx) {
55
+ const json = ctx.args.includes('--json');
56
+ try {
57
+ const { values, flags, positional, list } = parse(ctx.args);
58
+ if (flags.has('--help') || flags.has('-h') || !positional.length)
59
+ return { exitCode: 0, message: usage, rawOutput: true };
60
+ const { rulePinState } = await import('../../rules/pin-state.js');
61
+ const { setRuleConfigKey } = await import('../../config/cli.js');
62
+ const [command, ...ids] = positional;
63
+ if (command === 'inventory' || command === 'list') {
64
+ if (ids.length || values.has('--group') || [...flags].some(flag => !['--json', '--bodies'].includes(flag)))
65
+ throw new Error('inventory accepts only inventory filters and output flags');
66
+ const scope = values.get('--scope') ?? 'workspace';
67
+ if (!['workspace', 'user', 'installation', 'catalog'].includes(scope))
68
+ throw new Error('Invalid inventory scope');
69
+ const activation = list('--activation');
70
+ const tier = list('--tier');
71
+ if (activation?.some(value => !['kernel', 'summary', 'bound', 'paths', 'on-demand'].includes(value)))
72
+ throw new Error('Invalid activation filter');
73
+ if (tier?.some(value => !['critical', 'high', 'medium', 'low'].includes(value)))
74
+ throw new Error('Invalid tier filter');
75
+ if (list('--capability')?.some(value => !value))
76
+ throw new Error('Invalid capability filter');
77
+ const { ruleInventory } = await import('../../rules/inventory.js');
78
+ const report = await ruleInventory(ctx.cwd, ctx.frameworkRoot, { scope: scope, capability: list('--capability'), activation, enforcement: tier, provider: values.get('--provider'), bodies: flags.has('--bodies') });
79
+ return { exitCode: 0, rawOutput: true, message: json ? JSON.stringify(report, null, 2) :
80
+ [`Scope: ${report.scope} (${report.scope_description})`, 'Per-rule delivery and session loading: unknown.', ...report.rules.map(rule => `${rule.capability}/${rule.name} [${rule.effective_activation ?? 'unconfigured'}, ${rule.enforcement ?? 'unknown'}]: ${rule.summary ?? '(no summary)'}; ${rule.fetch_command ?? 'fetch ID unknown'}${rule.content ? `\n${rule.content}` : ''}`), ...report.warnings, ...(!report.rules.length ? ['No matching rules.'] : [])].join('\n') };
81
+ }
82
+ if (values.has('--scope') || values.has('--activation') || values.has('--provider') || flags.has('--bodies'))
83
+ throw new Error('Inventory filters are only valid for rules inventory');
84
+ const source = flags.has('--user') ? 'user' : 'project';
85
+ const configs = loadRulePinConfigs(ctx.cwd);
86
+ const before = rulePinState(ctx.cwd, ctx.frameworkRoot, configs, true);
87
+ if (command === 'pinned') {
88
+ if (ids.length || [...values.keys()].some(key => key !== '--capability'))
89
+ throw new Error('pinned accepts only --capability and output/scope flags');
90
+ const filter = list('--capability');
91
+ if (filter?.some(id => !before.installed.includes(id)))
92
+ throw new Error(`Unknown capability. Installed ids: ${before.installed.join(', ')}`);
93
+ const rules = before.rules.filter(rule => !filter || filter.includes(rule.capability?.replace(/^(framework|addon|extension|plugin):/, '') || ''));
94
+ return { exitCode: 0, message: json ? JSON.stringify({ rules, warnings: before.warnings }, null, 2)
95
+ : [...rules.map(rule => `${rule.name}: pinned kernel (canonical ${rule.canonical_activation}) ${rule.matches.map(match => `${match.source}: ${JSON.stringify(match.selector)}`).join('; ')}`), ...before.warnings, ...(!rules.length ? ['No effective rule pins.'] : [])].join('\n'), rawOutput: true };
96
+ }
97
+ if (command === 'group' && ids[0] === 'list') {
98
+ if (ids.length !== 1 || values.size)
99
+ throw new Error('Usage: aiwg rules group list');
100
+ const groups = { user: configs.user.rules?.pin_groups || {}, project: configs.project.rules?.pin_groups || {} };
101
+ return { exitCode: 0, message: JSON.stringify(groups, null, 2), rawOutput: true };
102
+ }
103
+ const selected = configs[source];
104
+ selected.rules = { ...selected.rules };
105
+ if (command === 'group') {
106
+ const [action, name, ...capabilities] = ids;
107
+ if (action !== 'set' || !name || !capabilities.length || values.size)
108
+ throw new Error('Usage: aiwg rules group set <name> <capability>...');
109
+ selected.rules.pin_groups = { ...selected.rules.pin_groups, [name]: [...new Set(capabilities.flatMap(id => id.split(',')))].sort() };
110
+ }
111
+ else if (command === 'pin' || command === 'unpin') {
112
+ if (flags.has('--all-tiers') && values.has('--tier'))
113
+ throw new Error('--all-tiers and --tier are mutually exclusive');
114
+ const selector = {};
115
+ if (ids.length)
116
+ selector.rules = [...new Set(ids)].sort();
117
+ if (values.has('--tier'))
118
+ selector.enforcement = list('--tier');
119
+ if (values.has('--capability'))
120
+ selector.capabilities = list('--capability');
121
+ if (values.has('--group'))
122
+ selector.group = values.get('--group');
123
+ if (flags.has('--all'))
124
+ selector.all = true;
125
+ if (!Object.keys(selector).length)
126
+ throw new Error('Supply rule ids, --tier, --capability, --group, or --all');
127
+ if (flags.has('--all-tiers') && !selector.capabilities && !selector.group && !selector.all)
128
+ throw new Error('--all-tiers requires --capability, --group or --all');
129
+ const existing = selected.rules.always_load || [];
130
+ const key = selectorKey(selector);
131
+ if (command === 'pin') {
132
+ const unknown = ids.filter(id => !before.inventory.some(rule => [rule.name, rule.id, rule.path].includes(id)));
133
+ if (unknown.length)
134
+ throw new Error(`Unknown installed rule ids: ${unknown.join(', ')}`);
135
+ selected.rules.always_load = existing.some(item => selectorKey(item) === key) ? existing : [...existing, selector];
136
+ }
137
+ else
138
+ selected.rules.always_load = existing.filter(item => selectorKey(item) !== key);
139
+ }
140
+ else
141
+ throw new Error(usage);
142
+ // Only the edited scope is strict: stale pins in another scope remain visible warnings.
143
+ const errors = validateRulePinCapabilities(selected.rules, before.installed, { ...configs.user.rules?.pin_groups, ...configs.project.rules?.pin_groups });
144
+ if (errors.length)
145
+ throw new Error(errors.join('\n'));
146
+ const after = rulePinState(ctx.cwd, ctx.frameworkRoot, configs, true);
147
+ const providers = [...new Set([...(configs.project.providers || []), ...Object.values(configs.project.installed || {}).flatMap(entry => Object.keys(entry.deployedTo || {}))])];
148
+ const reports = [];
149
+ const failures = [];
150
+ const plans = [];
151
+ // Use precisely the installed rule inventory, including local bundles; do not select new capabilities.
152
+ for (const provider of providers) {
153
+ if (!['claude', 'codex', 'cursor', 'hermes', 'warp', 'openclaw'].includes(provider)) {
154
+ failures.push(`Rule pin projection is unavailable for provider '${provider}'`);
155
+ continue;
156
+ }
157
+ const module = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools/agents/providers', `${provider}.mjs`)).href);
158
+ const options = { srcRoot: path.join(ctx.frameworkRoot, 'agentic/code'), target: ctx.cwd, mode: 'all',
159
+ ruleSources: after.inventory.map(rule => rule.path), config: configs.project, pinConfigs: configs,
160
+ rulesOnly: true, deployRules: true, deploySkills: false, deployCommands: false,
161
+ force: true, quiet: true, allowOverBudget: flags.has('--allow-over-budget') };
162
+ try {
163
+ const report = await quietDeployment(module.deploy, { ...options, dryRun: true, previewReport: true });
164
+ reports.push(report);
165
+ plans.push({ deploy: module.deploy, options });
166
+ }
167
+ catch (error) {
168
+ failures.push(`${provider}: ${error instanceof Error ? error.message : String(error)}`);
169
+ }
170
+ }
171
+ if (failures.length)
172
+ throw new Error(failures.join('\n'));
173
+ const dryRun = flags.has('--dry-run') || ctx.dryRun;
174
+ if (!dryRun) {
175
+ await setRuleConfigKey(ctx.cwd, source, 'rules', JSON.stringify(selected.rules));
176
+ if (!flags.has('--no-deploy'))
177
+ for (const plan of plans)
178
+ await quietDeployment(plan.deploy, plan.options);
179
+ }
180
+ const redeploy = 'aiwg refresh --skip-update --force' + (flags.has('--allow-over-budget') ? ' --allow-over-budget' : '');
181
+ const needsRedeploy = flags.has('--no-deploy') || plans.length === 0;
182
+ const output = { dry_run: !!dryRun, source, rules: after.rules, warnings: after.warnings, providers: reports,
183
+ deployed: !dryRun && !needsRedeploy, ...(needsRedeploy ? { redeploy } : {}) };
184
+ return { exitCode: 0, message: json ? JSON.stringify(output, null, 2)
185
+ : [dryRun ? 'Rule pin preview (config unchanged)' : 'Rule pins updated', ...after.rules.map(rule => `${rule.name}: kernel (canonical ${rule.canonical_activation})`),
186
+ ...reports.map(report => { const r = report; return `${r.provider}: ${r.selected_bytes} startup bytes${report.exceeded ? ' (over budget; explicitly allowed)' : ''}`; }),
187
+ ...after.warnings, ...(needsRedeploy ? [`Redeploy: ${redeploy}`] : [])].join('\n'), rawOutput: true };
188
+ }
189
+ catch (error) {
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ return { exitCode: 1, message: json ? JSON.stringify({ error: message }) : message, rawOutput: json };
192
+ }
193
+ },
194
+ };
195
+ //# sourceMappingURL=rules.js.map
@@ -62,6 +62,10 @@ export const runHandler = {
62
62
  description: 'Run a user-defined script from aiwg.config',
63
63
  category: 'utility',
64
64
  aliases: [],
65
+ async help() {
66
+ printRunUsage();
67
+ return { exitCode: 0 };
68
+ },
65
69
  async execute(ctx) {
66
70
  let parsed;
67
71
  try {