@event4u/agent-config 8.12.0 → 9.0.0

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 (284) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +164 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/agent-handoff.md +35 -0
  5. package/dist/agent-src/commands/agents/init.md +1 -0
  6. package/dist/agent-src/commands/agents/optimize.md +1 -0
  7. package/dist/agent-src/commands/agents/user/accept.md +3 -2
  8. package/dist/agent-src/commands/agents/user/init.md +5 -4
  9. package/dist/agent-src/commands/agents/user/update.md +3 -2
  10. package/dist/agent-src/commands/agents/user.md +1 -0
  11. package/dist/agent-src/commands/agents.md +1 -0
  12. package/dist/agent-src/commands/analytics/show.md +1 -0
  13. package/dist/agent-src/commands/analytics.md +1 -0
  14. package/dist/agent-src/commands/analyze/decision.md +1 -0
  15. package/dist/agent-src/commands/analyze/incident.md +1 -0
  16. package/dist/agent-src/commands/analyze/near-miss.md +1 -0
  17. package/dist/agent-src/commands/analyze/postmortem.md +1 -0
  18. package/dist/agent-src/commands/analyze/premortem.md +1 -0
  19. package/dist/agent-src/commands/analyze/reference-repo.md +1 -0
  20. package/dist/agent-src/commands/analyze.md +3 -2
  21. package/dist/agent-src/commands/brand/identity.md +6 -5
  22. package/dist/agent-src/commands/brand/review.md +6 -5
  23. package/dist/agent-src/commands/brand/strategy.md +6 -4
  24. package/dist/agent-src/commands/brand/tokens.md +7 -6
  25. package/dist/agent-src/commands/brand/voice.md +5 -4
  26. package/dist/agent-src/commands/brand.md +9 -7
  27. package/dist/agent-src/commands/bug/fix.md +1 -0
  28. package/dist/agent-src/commands/bug/investigate.md +1 -0
  29. package/dist/agent-src/commands/bug.md +2 -1
  30. package/dist/agent-src/commands/challenge-me/vision.md +1 -0
  31. package/dist/agent-src/commands/challenge-me/with-docs.md +1 -0
  32. package/dist/agent-src/commands/challenge-me.md +28 -6
  33. package/dist/agent-src/commands/chat-history.md +1 -0
  34. package/dist/agent-src/commands/check-current-md.md +1 -0
  35. package/dist/agent-src/commands/commit.md +1 -0
  36. package/dist/agent-src/commands/context/create.md +3 -2
  37. package/dist/agent-src/commands/context.md +1 -0
  38. package/dist/agent-src/commands/cost/profile.md +1 -0
  39. package/dist/agent-src/commands/cost.md +2 -1
  40. package/dist/agent-src/commands/council/analysis.md +1 -0
  41. package/dist/agent-src/commands/council/debate.md +1 -0
  42. package/dist/agent-src/commands/council/default.md +21 -20
  43. package/dist/agent-src/commands/council/design.md +1 -0
  44. package/dist/agent-src/commands/council/optimize.md +1 -0
  45. package/dist/agent-src/commands/council/pr.md +1 -0
  46. package/dist/agent-src/commands/council.md +1 -0
  47. package/dist/agent-src/commands/estimate-ticket.md +1 -0
  48. package/dist/agent-src/commands/feature/dev.md +1 -0
  49. package/dist/agent-src/commands/feature/explore.md +1 -0
  50. package/dist/agent-src/commands/feature/plan.md +1 -0
  51. package/dist/agent-src/commands/feature/refactor.md +1 -0
  52. package/dist/agent-src/commands/feature/roadmap.md +1 -0
  53. package/dist/agent-src/commands/feature.md +2 -1
  54. package/dist/agent-src/commands/fix/pr-comments.md +19 -0
  55. package/dist/agent-src/commands/fix.md +2 -1
  56. package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
  57. package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
  58. package/dist/agent-src/commands/ghostwriter/show.md +1 -0
  59. package/dist/agent-src/commands/ghostwriter/write.md +1 -0
  60. package/dist/agent-src/commands/ghostwriter.md +1 -0
  61. package/dist/agent-src/commands/grill-me.md +17 -12
  62. package/dist/agent-src/commands/humanize.md +1 -0
  63. package/dist/agent-src/commands/image/analyse.md +2 -1
  64. package/dist/agent-src/commands/image/create.md +1 -0
  65. package/dist/agent-src/commands/image/verify.md +1 -0
  66. package/dist/agent-src/commands/image.md +16 -12
  67. package/dist/agent-src/commands/implement-ticket.md +1 -0
  68. package/dist/agent-src/commands/judge/on-diff.md +1 -0
  69. package/dist/agent-src/commands/judge/solo.md +1 -0
  70. package/dist/agent-src/commands/judge/steps.md +1 -0
  71. package/dist/agent-src/commands/judge.md +1 -0
  72. package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
  73. package/dist/agent-src/commands/knowledge/forget.md +1 -0
  74. package/dist/agent-src/commands/knowledge/ingest.md +1 -0
  75. package/dist/agent-src/commands/knowledge/list.md +10 -0
  76. package/dist/agent-src/commands/knowledge.md +1 -0
  77. package/dist/agent-src/commands/memory/learn-low-impact.md +1 -0
  78. package/dist/agent-src/commands/memory/mine-session.md +1 -0
  79. package/dist/agent-src/commands/memory/promote.md +3 -2
  80. package/dist/agent-src/commands/memory.md +1 -0
  81. package/dist/agent-src/commands/mission/upgrade.md +45 -24
  82. package/dist/agent-src/commands/mode.md +1 -0
  83. package/dist/agent-src/commands/module/create.md +1 -0
  84. package/dist/agent-src/commands/module/explore.md +1 -0
  85. package/dist/agent-src/commands/module.md +1 -0
  86. package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
  87. package/dist/agent-src/commands/optimize/project.md +1 -0
  88. package/dist/agent-src/commands/optimize/prompt.md +1 -0
  89. package/dist/agent-src/commands/optimize.md +2 -1
  90. package/dist/agent-src/commands/orchestrate.md +1 -0
  91. package/dist/agent-src/commands/override.md +1 -0
  92. package/dist/agent-src/commands/package.md +2 -1
  93. package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
  94. package/dist/agent-src/commands/post-as/me.md +1 -0
  95. package/dist/agent-src/commands/post-as.md +1 -0
  96. package/dist/agent-src/commands/pr/create/description-only.md +1 -0
  97. package/dist/agent-src/commands/pr/create.md +28 -0
  98. package/dist/agent-src/commands/prediction-pool.md +7 -6
  99. package/dist/agent-src/commands/prepare-for-review.md +1 -0
  100. package/dist/agent-src/commands/profile/activate.md +1 -0
  101. package/dist/agent-src/commands/profile/deactivate.md +1 -0
  102. package/dist/agent-src/commands/profile.md +1 -0
  103. package/dist/agent-src/commands/project.md +2 -1
  104. package/dist/agent-src/commands/refine-ticket.md +7 -0
  105. package/dist/agent-src/commands/research/deep.md +1 -0
  106. package/dist/agent-src/commands/research.md +1 -0
  107. package/dist/agent-src/commands/review/routing.md +1 -0
  108. package/dist/agent-src/commands/review.md +2 -1
  109. package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
  110. package/dist/agent-src/commands/roadmap/materialize.md +1 -0
  111. package/dist/agent-src/commands/roadmap/process-full.md +1 -0
  112. package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
  113. package/dist/agent-src/commands/roadmap/process-step.md +1 -0
  114. package/dist/agent-src/commands/roadmap.md +1 -0
  115. package/dist/agent-src/commands/security-audit-config.md +2 -1
  116. package/dist/agent-src/commands/skill/preview.md +1 -0
  117. package/dist/agent-src/commands/skill.md +1 -0
  118. package/dist/agent-src/commands/skills/discover.md +1 -0
  119. package/dist/agent-src/commands/skills.md +1 -0
  120. package/dist/agent-src/commands/sync/agent-settings.md +1 -0
  121. package/dist/agent-src/commands/sync.md +2 -1
  122. package/dist/agent-src/commands/team/adversarial.md +97 -0
  123. package/dist/agent-src/commands/team/delegate.md +110 -0
  124. package/dist/agent-src/commands/team/review.md +85 -0
  125. package/dist/agent-src/commands/team/status.md +123 -0
  126. package/dist/agent-src/commands/team-knowledge.md +8 -6
  127. package/dist/agent-src/commands/team.md +152 -0
  128. package/dist/agent-src/commands/tests/e2e-heal.md +1 -0
  129. package/dist/agent-src/commands/tests/e2e-plan.md +1 -0
  130. package/dist/agent-src/commands/tests/execute.md +1 -0
  131. package/dist/agent-src/commands/tests.md +2 -1
  132. package/dist/agent-src/commands/update-form-request-messages.md +1 -0
  133. package/dist/agent-src/commands/upstream-contribute.md +1 -0
  134. package/dist/agent-src/commands/video/from-script.md +3 -2
  135. package/dist/agent-src/commands/video/from-song.md +22 -21
  136. package/dist/agent-src/commands/video/scene.md +1 -0
  137. package/dist/agent-src/commands/video/stitch.md +2 -1
  138. package/dist/agent-src/commands/video/storyboard.md +1 -0
  139. package/dist/agent-src/commands/video.md +1 -0
  140. package/dist/agent-src/commands/work.md +1 -0
  141. package/dist/agent-src/commands/worktree/cleanup.md +8 -7
  142. package/dist/agent-src/commands/worktree/create.md +1 -0
  143. package/dist/agent-src/commands/worktree/verify.md +1 -0
  144. package/dist/agent-src/commands/worktree.md +1 -0
  145. package/dist/agent-src/rules/active-remediation.md +12 -36
  146. package/dist/agent-src/rules/artifact-drafting-protocol.md +7 -64
  147. package/dist/agent-src/rules/context-hygiene.md +19 -113
  148. package/dist/agent-src/rules/design-fidelity.md +4 -60
  149. package/dist/agent-src/rules/domain-adoption-policy.md +10 -106
  150. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +12 -60
  151. package/dist/agent-src/rules/improve-before-implement.md +6 -0
  152. package/dist/agent-src/rules/minimal-safe-diff.md +6 -86
  153. package/dist/agent-src/rules/size-enforcement.md +10 -0
  154. package/dist/agent-src/rules/tool-safety.md +17 -3
  155. package/dist/agent-src/skills/agent-security-review/SKILL.md +12 -1
  156. package/dist/agent-src/skills/ai-council/SKILL.md +11 -0
  157. package/dist/agent-src/skills/bug-analyzer/SKILL.md +24 -2
  158. package/dist/agent-src/skills/code-review/SKILL.md +105 -93
  159. package/dist/agent-src/skills/code-review/checklists/backend.md +50 -0
  160. package/dist/agent-src/skills/code-review/checklists/database.md +19 -0
  161. package/dist/agent-src/skills/code-review/checklists/dependency.md +18 -0
  162. package/dist/agent-src/skills/code-review/checklists/docs.md +14 -0
  163. package/dist/agent-src/skills/code-review/checklists/frontend.md +19 -0
  164. package/dist/agent-src/skills/code-review/checklists/infra.md +17 -0
  165. package/dist/agent-src/skills/code-review/checklists/producing-the-review.md +62 -0
  166. package/dist/agent-src/skills/code-review/checklists/test-only.md +13 -0
  167. package/dist/agent-src/skills/code-review/evals/evals.json +53 -15
  168. package/dist/agent-src/skills/condense-memory/SKILL.md +3 -2
  169. package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +15 -0
  170. package/dist/agent-src/skills/docker/SKILL.md +10 -0
  171. package/dist/agent-src/skills/docx-authoring/SKILL.md +102 -0
  172. package/dist/agent-src/skills/github-ci/evals/evals.json +25 -0
  173. package/dist/agent-src/skills/humanizer/SKILL.md +79 -3
  174. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +112 -0
  175. package/dist/agent-src/skills/humanizer/references/fixtures.md +70 -0
  176. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +7 -1
  177. package/dist/agent-src/skills/laravel/SKILL.md +10 -0
  178. package/dist/agent-src/skills/launch-readiness/SKILL.md +35 -0
  179. package/dist/agent-src/skills/merge-conflicts/SKILL.md +30 -0
  180. package/dist/agent-src/skills/pdf-tools/SKILL.md +101 -0
  181. package/dist/agent-src/skills/quality-tools/evals/evals.json +15 -0
  182. package/dist/agent-src/skills/security-audit/SKILL.md +29 -0
  183. package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +90 -0
  184. package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +52 -0
  185. package/dist/agent-src/skills/skill-writing/SKILL.md +79 -0
  186. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +11 -3
  187. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +103 -0
  188. package/dist/agent-src/skills/terraform/SKILL.md +10 -0
  189. package/dist/agent-src/skills/test-driven-development/SKILL.md +31 -0
  190. package/dist/agent-src/skills/threat-modeling/SKILL.md +15 -0
  191. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  192. package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  193. package/dist/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  194. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  195. package/dist/agent-src/templates/scripts/telemetry_record.ts +5 -0
  196. package/dist/discovery/deprecation-report.md +1 -1
  197. package/dist/discovery/discovery-manifest.json +378 -174
  198. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  199. package/dist/discovery/discovery-manifest.summary.md +4 -4
  200. package/dist/discovery/orphan-report.md +1 -1
  201. package/dist/discovery/packs.json +16 -8
  202. package/dist/discovery/trust-report.md +3 -3
  203. package/dist/discovery/workspaces.json +14 -6
  204. package/dist/install/install.mjs +46 -0
  205. package/dist/mcp/registry-manifest.json +3 -3
  206. package/dist/router.json +1 -1
  207. package/dist/scripts/condense.js +5 -0
  208. package/dist/scripts/condense.js.map +1 -1
  209. package/dist/server/schemas/settings.js +11 -0
  210. package/dist/server/schemas/settings.js.map +1 -1
  211. package/docs/CLAIMS.md +9 -2
  212. package/docs/SKILL_CENSUS.md +8 -0
  213. package/docs/architecture.md +3 -3
  214. package/docs/benchmark.md +67 -2
  215. package/docs/catalog.md +10 -4
  216. package/docs/command-flows.md +6 -1
  217. package/docs/contracts/ai-council-config.md +9 -2
  218. package/docs/contracts/ai-team-config.md +224 -0
  219. package/docs/contracts/command-clusters.md +1 -0
  220. package/docs/contracts/prelaunch-diagnostics.md +151 -0
  221. package/docs/contracts/release-pr-gating.md +1 -0
  222. package/docs/decisions/ADR-049-configuration-trust-boundary.md +9 -1
  223. package/docs/featured-skills.md +2 -2
  224. package/docs/getting-started-by-role.md +3 -1
  225. package/docs/getting-started.md +42 -1
  226. package/docs/governance-advantage.md +1 -1
  227. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +45 -0
  228. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +28 -0
  229. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +76 -0
  230. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +90 -0
  231. package/docs/guidelines/agent-infra/domain-adoption-gates.md +112 -0
  232. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
  233. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +99 -0
  234. package/docs/guidelines/agent-infra/skill-quality-checklist.md +13 -0
  235. package/docs/guidelines/design-fidelity-mechanics.md +76 -0
  236. package/docs/maintainers/branch-protection.md +18 -0
  237. package/docs/parity/command-frontmatter.md +60 -0
  238. package/docs/proof.md +7 -6
  239. package/package.json +1 -1
  240. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  241. package/src/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  242. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  243. package/src/agent-src/templates/scripts/telemetry_record.ts +5 -0
  244. package/src/config/agent-settings.template.yml +71 -7
  245. package/src/config/prelaunch-areas.yml +36 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +288 -1
  247. package/src/scripts/_lib/claude_plugin.ts +114 -4
  248. package/src/scripts/_lib/orchestration_record.ts +5 -0
  249. package/src/scripts/_lib/tool_adapter_registry.ts +120 -0
  250. package/src/scripts/ai_team/config.ts +234 -0
  251. package/src/scripts/ai_team/review_gate.ts +636 -0
  252. package/src/scripts/ai_team/team_dispatch.ts +612 -0
  253. package/src/scripts/bench_ab_task_runner.ts +11 -1
  254. package/src/scripts/bench_parity_count.ts +279 -0
  255. package/src/scripts/build_linear_digest.ts +3 -1
  256. package/src/scripts/check_bridge_derivation.ts +172 -0
  257. package/src/scripts/check_consumer_scope_flip.ts +179 -0
  258. package/src/scripts/check_council_config_location.ts +115 -15
  259. package/src/scripts/check_host_loadability.ts +81 -0
  260. package/src/scripts/condense.ts +5 -0
  261. package/src/scripts/hook_manifest.yaml +12 -1
  262. package/src/scripts/install.ts +39 -0
  263. package/src/scripts/lint_handoffs.ts +42 -0
  264. package/src/scripts/lint_skill_descriptions.ts +228 -0
  265. package/src/scripts/lint_skill_descriptions_allowlist.json +1 -0
  266. package/src/scripts/lint_skill_scripts_readonly.ts +145 -0
  267. package/src/scripts/lint_skill_scripts_readonly_allowlist.json +8 -0
  268. package/src/scripts/lint_supported_tools_matrix.ts +35 -0
  269. package/src/scripts/optimize_skill_description.ts +144 -0
  270. package/src/scripts/orchestration_record.ts +2 -1
  271. package/src/scripts/prelaunch_diagnostics.ts +248 -0
  272. package/src/scripts/rule_backlinks.ts +0 -0
  273. package/src/scripts/run_skill_evals.ts +70 -4
  274. package/src/scripts/schemas/command.schema.json +7 -0
  275. package/src/scripts/schemas/evals.schema.json +105 -14
  276. package/src/scripts/schemas/prelaunch-report.schema.json +107 -0
  277. package/src/scripts/schemas/skill.schema.json +18 -1
  278. package/src/scripts/schemas/subagent.schema.json +6 -0
  279. package/src/scripts/skill_linter.ts +43 -3
  280. package/src/scripts/smoke_host_loadability.sh +73 -0
  281. package/src/scripts/team_review_gate_hook.ts +115 -0
  282. package/src/scripts/trigger_eval_grandfather.json +1 -2
  283. package/src/scripts/utilization_report.ts +225 -0
  284. package/src/server/schemas/settings.ts +25 -0
@@ -0,0 +1,612 @@
1
+ /**
2
+ * Team-review multi-host fallback dispatch — road-to-team-mode Phase 3.
3
+ *
4
+ * On hosts WITHOUT the official Claude-Code plugin (Cursor, Augment, Copilot,
5
+ * plain terminals), `/team:review` falls back to this script: build a
6
+ * READ-ONLY repo-context bundle (`git status --porcelain`, a size-capped
7
+ * staged+unstaged diff, the tracked-file list), send it through the existing
8
+ * subscription-authed `OpenAICliClient` transport with a review system
9
+ * prompt, and emit the result in the team-review status envelope
10
+ * (`src/skills/subagent-orchestration/schemas/team-review-status.json`).
11
+ *
12
+ * READ-ONLY BY CONSTRUCTION: the only subprocesses this module spawns are
13
+ * read-only `git` queries (`status` / `diff` / `ls-files` / `rev-parse`) and
14
+ * the codex CLI call itself. No code path writes to the repository.
15
+ *
16
+ * Honesty contract (Phase 3 Step 2): every fallback run renders the
17
+ * capability-delta header at the TOP — one synchronous call, diff-bundle
18
+ * instead of live repo access, no background jobs, explicitly worse than the
19
+ * plugin — pointing Claude-Code users back to the Phase 2 plugin path.
20
+ *
21
+ * Governance reuse (Phase 3 Step 3): same `ai_team` config (fail-closed when
22
+ * `enabled: false`), same `cli_call_budget` openai quota bucket (the gate and
23
+ * the recording both live in `CliClient.ask` — no new counter), same
24
+ * `_AUTH_FAILURE_PATTERNS` classification. `--manual` renders the bundle
25
+ * between `═` rules for paste-into-web usage — no call, no quota.
26
+ *
27
+ * The review system prompt is derived from the adversarial-review findings
28
+ * shape of an Apache-2.0 upstream — attribution in the repo-root NOTICE file.
29
+ */
30
+ import { spawnSync } from 'node:child_process';
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+ import { fileURLToPath, pathToFileURL } from 'node:url';
34
+
35
+ import {
36
+ type CouncilResponse,
37
+ load_cli_call_counts,
38
+ OpenAICliClient,
39
+ } from '../ai_council/clients.js';
40
+ import { AI_TEAM_MODEL_AUTO, type AiTeamConfig, load_ai_team_config } from './config.js';
41
+
42
+ // ── errors ──────────────────────────────────────────────────────────────
43
+
44
+ /** Raised for any team-dispatch failure that is the caller's to surface. */
45
+ export class TeamDispatchError extends Error {
46
+ constructor(message: string) {
47
+ super(message);
48
+ this.name = 'TeamDispatchError';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Raised when `ai_team.enabled` is false — the fail-closed gate.
54
+ * The message carries the enable pointer; callers print it verbatim.
55
+ */
56
+ export class TeamDisabledError extends TeamDispatchError {
57
+ constructor() {
58
+ super(
59
+ 'team mode is disabled (`ai_team.enabled: false`, the shipped default). ' +
60
+ 'Set `ai_team.enabled: true` in `.agent-settings.yml` to opt in — ' +
61
+ 'see docs/contracts/ai-team-config.md.',
62
+ );
63
+ this.name = 'TeamDisabledError';
64
+ }
65
+ }
66
+
67
+ // ── repo-context bundle (READ-ONLY) ─────────────────────────────────────
68
+
69
+ /**
70
+ * Combined staged+unstaged diff cap, in characters. Chars (not bytes/lines)
71
+ * because the cap protects the model-context budget and chars/4 is the
72
+ * established token estimate in this tree (`pricing.estimate_input_tokens`).
73
+ */
74
+ export const DIFF_BUNDLE_MAX_CHARS = 120_000;
75
+
76
+ /** Truncation marker appended when the diff is cut — names how much was cut. */
77
+ export function truncation_marker(cut_chars: number, cap: number): string {
78
+ return (
79
+ `\n[... TRUNCATED: ${cut_chars} characters of diff cut at the ` +
80
+ `${cap}-char bundle cap — the reviewer saw a partial diff ...]`
81
+ );
82
+ }
83
+
84
+ /** Injectable git runner: argv (without the leading `git`) → stdout. */
85
+ export type GitRunner = (args: readonly string[]) => string;
86
+
87
+ /** Allowlisted read-only git subcommands — the read-only-by-construction proof. */
88
+ const _READ_ONLY_GIT_SUBCOMMANDS: ReadonlySet<string> = new Set([
89
+ 'status',
90
+ 'diff',
91
+ 'ls-files',
92
+ 'rev-parse',
93
+ ]);
94
+
95
+ function _default_run_git(cwd: string): GitRunner {
96
+ return (args: readonly string[]): string => {
97
+ const r = spawnSync('git', [...args], { cwd, encoding: 'utf-8', maxBuffer: 64 * 1024 * 1024 });
98
+ if (r.error) {
99
+ throw new TeamDispatchError(`git ${args[0] ?? ''} failed: ${r.error.message}`);
100
+ }
101
+ if (r.status !== 0) {
102
+ throw new TeamDispatchError(
103
+ `git ${args[0] ?? ''} exited ${r.status}: ${(r.stderr ?? '').slice(0, 500)}`,
104
+ );
105
+ }
106
+ return r.stdout ?? '';
107
+ };
108
+ }
109
+
110
+ /** Wrap a runner so any non-allowlisted subcommand is a hard error. */
111
+ function _guard_read_only(run: GitRunner): GitRunner {
112
+ return (args: readonly string[]): string => {
113
+ const sub = args[0] ?? '';
114
+ if (!_READ_ONLY_GIT_SUBCOMMANDS.has(sub)) {
115
+ throw new TeamDispatchError(
116
+ `refusing non-read-only git subcommand \`${sub}\` — ` +
117
+ 'team_dispatch is read-only by construction.',
118
+ );
119
+ }
120
+ return run(args);
121
+ };
122
+ }
123
+
124
+ /** The repo-context bundle sent to the reviewer. */
125
+ export interface RepoContextBundle {
126
+ /** `git rev-parse HEAD` at bundle time — becomes `reviewed_ref`. */
127
+ readonly head_ref: string;
128
+ /** `git status --porcelain` output. */
129
+ readonly status: string;
130
+ /** Combined staged+unstaged diff, possibly truncated (marker appended). */
131
+ readonly diff: string;
132
+ readonly diff_truncated: boolean;
133
+ /** Characters cut from the combined diff (0 when not truncated). */
134
+ readonly diff_cut_chars: number;
135
+ /** Tracked-file list (`git ls-files`). */
136
+ readonly files: readonly string[];
137
+ }
138
+
139
+ export interface BuildBundleOptions {
140
+ cwd?: string;
141
+ run_git?: GitRunner;
142
+ /** Override the diff cap (tests exercise the boundary). */
143
+ max_diff_chars?: number;
144
+ }
145
+
146
+ /**
147
+ * Build the READ-ONLY repo-context bundle.
148
+ *
149
+ * The combined diff is `staged` (`git diff --cached`) then `unstaged`
150
+ * (`git diff`), section-labelled. When the combined text exceeds the cap it
151
+ * is cut at exactly `max_diff_chars` characters and the truncation marker
152
+ * (naming the cut size) is appended — the bundle never silently loses data.
153
+ */
154
+ export function build_repo_context_bundle(opts: BuildBundleOptions = {}): RepoContextBundle {
155
+ const cap = opts.max_diff_chars ?? DIFF_BUNDLE_MAX_CHARS;
156
+ const run = _guard_read_only(opts.run_git ?? _default_run_git(opts.cwd ?? process.cwd()));
157
+
158
+ const head_ref = run(['rev-parse', 'HEAD']).trim();
159
+ const status = run(['status', '--porcelain']).trimEnd();
160
+ const staged = run(['diff', '--cached']).trimEnd();
161
+ const unstaged = run(['diff']).trimEnd();
162
+ const files = run(['ls-files'])
163
+ .split('\n')
164
+ .map((l) => l.trim())
165
+ .filter((l) => l.length > 0);
166
+
167
+ const combined =
168
+ `### staged diff (git diff --cached)\n${staged || '(empty)'}\n\n` +
169
+ `### unstaged diff (git diff)\n${unstaged || '(empty)'}`;
170
+
171
+ let diff = combined;
172
+ let diff_truncated = false;
173
+ let diff_cut_chars = 0;
174
+ if (combined.length > cap) {
175
+ diff_cut_chars = combined.length - cap;
176
+ diff = combined.slice(0, cap) + truncation_marker(diff_cut_chars, cap);
177
+ diff_truncated = true;
178
+ }
179
+
180
+ return { head_ref, status, diff, diff_truncated, diff_cut_chars, files };
181
+ }
182
+
183
+ // ── capability-delta header (Phase 3 Step 2 — honesty contract) ─────────
184
+
185
+ /**
186
+ * Rendered at the TOP of every fallback run (call AND manual modes).
187
+ * Never soften this: the fallback is deliberately described as worse than
188
+ * the plugin so nobody mistakes bundle-review for live-repo review.
189
+ */
190
+ export function render_capability_delta_header(): string {
191
+ return [
192
+ '⚠️ team:review — multi-host FALLBACK (reduced capability)',
193
+ 'This is the diff-bundle fallback, not the official Claude Code plugin:',
194
+ ' • ONE synchronous model call — no background jobs, no broker, no build→review→fix loop',
195
+ ' • the reviewer sees a size-capped diff bundle, NOT the live repository',
196
+ ' • findings are advisory; nothing here writes to the repo',
197
+ 'On Claude Code, use the plugin path instead — it is strictly more capable:',
198
+ ' agent-config doctor --check team',
199
+ ].join('\n');
200
+ }
201
+
202
+ // ── review system prompt ────────────────────────────────────────────────
203
+
204
+ /**
205
+ * Review system prompt — findings shape derived from an Apache-2.0 upstream
206
+ * adversarial-review skill (attribution: repo-root NOTICE). The JSON contract
207
+ * mirrors `team-review-status.json` so parsing is a projection, not a guess.
208
+ */
209
+ export const TEAM_REVIEW_SYSTEM_PROMPT = [
210
+ 'You are an adversarial senior code reviewer on a two-model team: another',
211
+ 'model wrote the changes; your ONLY job is to find what it got wrong.',
212
+ 'Assume the diff is flawed. Hunt for: logic errors, race conditions,',
213
+ 'missing edge/empty/error cases, security smells (injection, authz gaps,',
214
+ 'secrets), silent behavior changes, broken callers the diff forgot, and',
215
+ 'over-engineering where a simpler change would do.',
216
+ '',
217
+ 'You receive a READ-ONLY repo-context bundle: `git status --porcelain`,',
218
+ 'a size-capped combined staged+unstaged diff (a truncation marker names',
219
+ 'anything cut), and the tracked-file list. You cannot run commands or',
220
+ 'read files beyond the bundle — when a judgment needs unseen context,',
221
+ 'say so via status NEEDS_CONTEXT instead of guessing.',
222
+ '',
223
+ 'Respond with a SINGLE JSON object and nothing else (no prose, no code',
224
+ 'fence) in exactly this shape:',
225
+ '{',
226
+ ' "status": "DONE" | "DONE_WITH_CONCERNS" | "NEEDS_CONTEXT",',
227
+ ' "summary": "<one or two sentences>",',
228
+ ' "findings": [',
229
+ ' {',
230
+ ' "severity": "critical" | "major" | "minor" | "info",',
231
+ ' "evidence": "<what is wrong, citing the exact hunk/line from the diff>",',
232
+ ' "suggested_fix": "<concrete, minimal fix>",',
233
+ ' "location": "<file[:line] when known>"',
234
+ ' }',
235
+ ' ]',
236
+ '}',
237
+ '',
238
+ 'Rules: every finding cites evidence from the bundle — never invent code',
239
+ 'that is not in the diff. No findings above "info" → status DONE with an',
240
+ 'empty findings array. Any "critical"/"major"/"minor" finding → status',
241
+ 'DONE_WITH_CONCERNS. Missing context blocks a judgment → NEEDS_CONTEXT',
242
+ 'and put the single blocking question in "summary".',
243
+ ].join('\n');
244
+
245
+ /** Render the user prompt from the bundle. */
246
+ export function render_review_user_prompt(bundle: RepoContextBundle): string {
247
+ return [
248
+ `## Repo-context bundle (read-only snapshot at HEAD ${bundle.head_ref})`,
249
+ '',
250
+ '### git status --porcelain',
251
+ bundle.status || '(clean)',
252
+ '',
253
+ bundle.diff,
254
+ '',
255
+ `### tracked files (${bundle.files.length})`,
256
+ bundle.files.join('\n'),
257
+ ].join('\n');
258
+ }
259
+
260
+ // ── manual mode (council precedent: paste block between ═ rules) ────────
261
+
262
+ /**
263
+ * Render the bundle + system prompt between `═` rules for paste-into-web
264
+ * usage — mirrors the council's `ManualClient._render_block`. No model call,
265
+ * no quota touch.
266
+ */
267
+ export function render_manual_block(bundle: RepoContextBundle): string {
268
+ const bar = '═'.repeat(67);
269
+ const head =
270
+ `${bar}\n` +
271
+ 'Team review — manual mode\n' +
272
+ 'Paste this block into your LLM web UI · the reply is the review.\n' +
273
+ `${bar}`;
274
+ const body = `${TEAM_REVIEW_SYSTEM_PROMPT}\n\n---\n\n${render_review_user_prompt(bundle)}`;
275
+ const tail = `${bar}\nEnd of paste block — no call was made, no quota was spent.\n${bar}`;
276
+ return `${head}\n\n${body}\n\n${tail}`;
277
+ }
278
+
279
+ // ── team-review envelope ────────────────────────────────────────────────
280
+ // Wire format: src/skills/subagent-orchestration/schemas/team-review-status.json
281
+ // (extends the subagent-status.json frame).
282
+
283
+ export type TeamReviewStatus = 'DONE' | 'DONE_WITH_CONCERNS' | 'NEEDS_CONTEXT' | 'BLOCKED';
284
+
285
+ export type FindingSeverity = 'critical' | 'major' | 'minor' | 'info';
286
+
287
+ export interface TeamReviewFinding {
288
+ readonly severity: FindingSeverity;
289
+ readonly evidence: string;
290
+ readonly suggested_fix: string;
291
+ readonly location?: string;
292
+ }
293
+
294
+ export interface TeamReviewQuota {
295
+ /** Today's openai-bucket call count AFTER this run. */
296
+ readonly used: number;
297
+ /** `ai_team.max_calls_per_day` — the team-side ceiling on the shared bucket. */
298
+ readonly ceiling: number;
299
+ }
300
+
301
+ export interface TeamReviewEnvelope {
302
+ status: TeamReviewStatus;
303
+ findings: TeamReviewFinding[];
304
+ /** HEAD sha the bundle was built from. */
305
+ reviewed_ref: string;
306
+ model: string;
307
+ quota: TeamReviewQuota;
308
+ summary?: string;
309
+ /** Verbatim model output when findings could not be parsed — NEVER dropped. */
310
+ raw?: string;
311
+ /** Required when status = NEEDS_CONTEXT. */
312
+ blocking_question?: string;
313
+ /** Required when status = BLOCKED. */
314
+ blocking_reason?: string;
315
+ }
316
+
317
+ const _VALID_SEVERITIES: ReadonlySet<string> = new Set(['critical', 'major', 'minor', 'info']);
318
+ const _MODEL_STATUSES: ReadonlySet<string> = new Set(['DONE', 'DONE_WITH_CONCERNS', 'NEEDS_CONTEXT']);
319
+
320
+ /** Best-effort parse result: parsed findings, or the raw-preserving fallback. */
321
+ export interface ParsedReview {
322
+ readonly parsed: boolean;
323
+ readonly status: TeamReviewStatus;
324
+ readonly findings: TeamReviewFinding[];
325
+ readonly summary: string;
326
+ /** Populated (verbatim model text) only when `parsed` is false. */
327
+ readonly raw?: string;
328
+ }
329
+
330
+ function _strip_code_fence(text: string): string {
331
+ const t = text.trim();
332
+ const m = /^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/.exec(t);
333
+ return m !== null && m[1] !== undefined ? m[1] : t;
334
+ }
335
+
336
+ /**
337
+ * Parse the model output into findings — best-effort, never throwing.
338
+ *
339
+ * Unparseable output (not JSON, wrong shape, no salvageable findings) →
340
+ * `parsed: false` with the verbatim text in `raw`; the caller maps that to
341
+ * status DONE_WITH_CONCERNS so a malformed review is loud, never dropped.
342
+ * Findings with an unknown severity are kept, clamped to `info`.
343
+ */
344
+ export function parse_review_findings(text: string): ParsedReview {
345
+ const fallback: ParsedReview = {
346
+ parsed: false,
347
+ status: 'DONE_WITH_CONCERNS',
348
+ findings: [],
349
+ summary: 'model output was not parseable as the team-review JSON contract; raw text preserved.',
350
+ raw: text,
351
+ };
352
+ let data: unknown;
353
+ try {
354
+ data = JSON.parse(_strip_code_fence(text));
355
+ } catch {
356
+ return fallback;
357
+ }
358
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
359
+ return fallback;
360
+ }
361
+ const obj = data as Record<string, unknown>;
362
+ const raw_findings = obj['findings'];
363
+ if (!Array.isArray(raw_findings)) {
364
+ return fallback;
365
+ }
366
+ const findings: TeamReviewFinding[] = [];
367
+ for (const entry of raw_findings) {
368
+ if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
369
+ return fallback; // a non-object finding poisons trust in the whole parse
370
+ }
371
+ const f = entry as Record<string, unknown>;
372
+ const evidence = f['evidence'];
373
+ const suggested_fix = f['suggested_fix'];
374
+ if (typeof evidence !== 'string' || evidence.trim() === '') {
375
+ return fallback;
376
+ }
377
+ if (typeof suggested_fix !== 'string' || suggested_fix.trim() === '') {
378
+ return fallback;
379
+ }
380
+ const raw_sev = typeof f['severity'] === 'string' ? (f['severity'] as string) : 'info';
381
+ const severity = (_VALID_SEVERITIES.has(raw_sev) ? raw_sev : 'info') as FindingSeverity;
382
+ const finding: TeamReviewFinding = {
383
+ severity,
384
+ evidence,
385
+ suggested_fix,
386
+ ...(typeof f['location'] === 'string' && f['location'].trim() !== ''
387
+ ? { location: f['location'] as string }
388
+ : {}),
389
+ };
390
+ findings.push(finding);
391
+ }
392
+ const model_status = typeof obj['status'] === 'string' ? (obj['status'] as string) : '';
393
+ let status: TeamReviewStatus;
394
+ if (_MODEL_STATUSES.has(model_status)) {
395
+ status = model_status as TeamReviewStatus;
396
+ } else {
397
+ // Derive from findings when the model skipped/garbled the status.
398
+ status = findings.length > 0 ? 'DONE_WITH_CONCERNS' : 'DONE';
399
+ }
400
+ // Consistency floor: findings present can never report a bare DONE.
401
+ if (status === 'DONE' && findings.length > 0) {
402
+ status = 'DONE_WITH_CONCERNS';
403
+ }
404
+ const summary =
405
+ typeof obj['summary'] === 'string' && obj['summary'].trim() !== ''
406
+ ? (obj['summary'] as string)
407
+ : findings.length > 0
408
+ ? `${findings.length} finding(s) from cross-model review.`
409
+ : 'no findings above info from cross-model review.';
410
+ return { parsed: true, status, findings, summary };
411
+ }
412
+
413
+ // ── transport (reuses OpenAICliClient — quota + auth machinery intact) ──
414
+
415
+ /**
416
+ * Thin OpenAICliClient veneer honoring the `ai_team.model: 'auto'` sentinel:
417
+ * `auto` passes NO `--model` flag so the codex CLI's own default applies
418
+ * (tracks the subscription's strongest model). Everything else — quota gate,
419
+ * call recording into the shared `counts.openai` bucket, stderr
420
+ * classification via `_AUTH_FAILURE_PATTERNS` — is inherited unchanged.
421
+ */
422
+ export class TeamReviewCliClient extends OpenAICliClient {
423
+ protected override _build_command(
424
+ system_prompt: string,
425
+ user_prompt: string,
426
+ max_tokens: number,
427
+ ): string[] {
428
+ const cmd = super._build_command(system_prompt, user_prompt, max_tokens);
429
+ if (this.model === AI_TEAM_MODEL_AUTO) {
430
+ const i = cmd.indexOf('--model');
431
+ if (i >= 0) {
432
+ cmd.splice(i, 2);
433
+ }
434
+ }
435
+ return cmd;
436
+ }
437
+ }
438
+
439
+ // ── dispatch ────────────────────────────────────────────────────────────
440
+
441
+ /** Result of a fallback run — manual render or a call with its envelope. */
442
+ export interface TeamReviewRunResult {
443
+ readonly mode: 'manual' | 'call';
444
+ /** The capability-delta header that was rendered at the top. */
445
+ readonly header: string;
446
+ readonly bundle: RepoContextBundle;
447
+ /** Present in manual mode: the paste block between `═` rules. */
448
+ readonly manual_block?: string;
449
+ /** Present in call mode: the team-review status envelope. */
450
+ readonly envelope?: TeamReviewEnvelope;
451
+ }
452
+
453
+ export interface RunTeamReviewOptions {
454
+ /** Pre-built config (test seam); default: `load_ai_team_config({ cwd })`. */
455
+ config?: AiTeamConfig;
456
+ cwd?: string;
457
+ /** `--manual`: render the paste block, make no call, spend no quota. */
458
+ manual?: boolean;
459
+ /** Client factory (test seam); default builds a `TeamReviewCliClient`. */
460
+ make_client?: (config: AiTeamConfig) => TeamReviewCliClient;
461
+ run_git?: GitRunner;
462
+ max_diff_chars?: number;
463
+ /** Quota state path override (test seam) — default shared cli-calls.json. */
464
+ cli_calls_path?: string | null;
465
+ /** Sink for user-facing output; default `process.stdout`. */
466
+ out?: (line: string) => void;
467
+ }
468
+
469
+ function _quota_snapshot(config: AiTeamConfig, cli_calls_path: string | null): TeamReviewQuota {
470
+ const counts = load_cli_call_counts(cli_calls_path);
471
+ return { used: counts['openai'] ?? 0, ceiling: config.max_calls_per_day };
472
+ }
473
+
474
+ function _envelope_from_response(
475
+ response: CouncilResponse,
476
+ bundle: RepoContextBundle,
477
+ quota: TeamReviewQuota,
478
+ ): TeamReviewEnvelope {
479
+ const base = {
480
+ reviewed_ref: bundle.head_ref,
481
+ model: response.model,
482
+ quota,
483
+ };
484
+ if (response.error !== null && response.error !== undefined && response.error !== '') {
485
+ let blocking_reason: string;
486
+ if (response.error === 'auth_expired') {
487
+ blocking_reason =
488
+ 'codex CLI auth failed (auth_expired) — run `codex login`, then retry. ' +
489
+ 'No review was produced.';
490
+ } else if (response.error === 'cli_quota_exhausted') {
491
+ blocking_reason =
492
+ `daily cli-call quota exhausted for the shared openai bucket ` +
493
+ `(${quota.used}/${quota.ceiling}) — resets at UTC midnight. No review was produced.`;
494
+ } else {
495
+ blocking_reason = `codex CLI transport failed: ${response.error}. No review was produced.`;
496
+ }
497
+ return { ...base, status: 'BLOCKED', findings: [], blocking_reason };
498
+ }
499
+ const parsed = parse_review_findings(response.text);
500
+ const envelope: TeamReviewEnvelope = {
501
+ ...base,
502
+ status: parsed.status,
503
+ findings: parsed.findings,
504
+ summary: parsed.summary,
505
+ };
506
+ if (!parsed.parsed) {
507
+ envelope.raw = parsed.raw ?? response.text;
508
+ }
509
+ if (parsed.status === 'NEEDS_CONTEXT') {
510
+ // Contract: the prompt tells the model to put its single blocking
511
+ // question in `summary`; the envelope schema requires it here.
512
+ envelope.blocking_question = parsed.summary;
513
+ }
514
+ return envelope;
515
+ }
516
+
517
+ /**
518
+ * Run the multi-host team-review fallback.
519
+ *
520
+ * Order of operations is the contract: (1) fail-closed config gate,
521
+ * (2) capability-delta header at the top, (3) read-only bundle,
522
+ * (4) manual render OR one synchronous `codex exec` call through the shared
523
+ * quota/auth machinery, (5) envelope emission. Throws `TeamDisabledError`
524
+ * when `ai_team.enabled` is false — never a silent no-op.
525
+ */
526
+ export function run_team_review(opts: RunTeamReviewOptions = {}): TeamReviewRunResult {
527
+ const config = opts.config ?? load_ai_team_config({ cwd: opts.cwd ?? null });
528
+ if (!config.enabled) {
529
+ throw new TeamDisabledError();
530
+ }
531
+ const out = opts.out ?? ((line: string): void => void process.stdout.write(`${line}\n`));
532
+
533
+ // Honesty contract: header FIRST, before any other output, in every mode.
534
+ const header = render_capability_delta_header();
535
+ out(header);
536
+
537
+ const bundle_opts: BuildBundleOptions = {};
538
+ if (opts.cwd !== undefined) {
539
+ bundle_opts.cwd = opts.cwd;
540
+ }
541
+ if (opts.run_git !== undefined) {
542
+ bundle_opts.run_git = opts.run_git;
543
+ }
544
+ if (opts.max_diff_chars !== undefined) {
545
+ bundle_opts.max_diff_chars = opts.max_diff_chars;
546
+ }
547
+ const bundle = build_repo_context_bundle(bundle_opts);
548
+
549
+ if (opts.manual === true) {
550
+ const manual_block = render_manual_block(bundle);
551
+ out(manual_block);
552
+ return { mode: 'manual', header, bundle, manual_block };
553
+ }
554
+
555
+ const cli_calls_path = opts.cli_calls_path ?? null;
556
+ const client =
557
+ opts.make_client !== undefined
558
+ ? opts.make_client(config)
559
+ : new TeamReviewCliClient({
560
+ model: config.model,
561
+ max_calls_per_day: config.max_calls_per_day,
562
+ cli_calls_path,
563
+ });
564
+
565
+ const response = client.ask(TEAM_REVIEW_SYSTEM_PROMPT, render_review_user_prompt(bundle));
566
+ const quota = _quota_snapshot(config, cli_calls_path);
567
+ const envelope = _envelope_from_response(response, bundle, quota);
568
+ out(JSON.stringify(envelope, null, 2));
569
+ return { mode: 'call', header, bundle, envelope };
570
+ }
571
+
572
+ // ── CLI entry ───────────────────────────────────────────────────────────
573
+
574
+ function _main(argv: string[]): number {
575
+ const manual = argv.includes('--manual');
576
+ try {
577
+ const result = run_team_review({ manual });
578
+ if (result.mode === 'call' && result.envelope?.status === 'BLOCKED') {
579
+ return 1;
580
+ }
581
+ return 0;
582
+ } catch (exc) {
583
+ if (exc instanceof TeamDispatchError) {
584
+ process.stderr.write(`${exc.message}\n`);
585
+ return 2;
586
+ }
587
+ throw exc;
588
+ }
589
+ }
590
+
591
+ function _isCliEntry(): boolean {
592
+ if (process.argv[1] === undefined) {
593
+ return false;
594
+ }
595
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
596
+ if (import.meta.url === argvUrl) {
597
+ return true;
598
+ }
599
+ // Symlinked invocations (installed projections, macOS /var → /private/var)
600
+ // make the raw URLs differ — compare realpaths (same guard as airgap.ts).
601
+ try {
602
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
603
+ const argv = fs.realpathSync(path.resolve(process.argv[1]));
604
+ return here === argv;
605
+ } catch {
606
+ return false;
607
+ }
608
+ }
609
+
610
+ if (_isCliEntry()) {
611
+ process.exit(_main(process.argv.slice(2)));
612
+ }
@@ -204,6 +204,7 @@ interface RunLiveOpts {
204
204
  setting_sources?: string | null;
205
205
  max_budget?: number | null;
206
206
  model?: string | null;
207
+ effort?: string | null;
207
208
  }
208
209
 
209
210
  /**
@@ -219,7 +220,7 @@ export function run_live(
219
220
  timeoutS: number,
220
221
  opts: RunLiveOpts = {},
221
222
  ): RunResult {
222
- const { sysprompt_file = null, setting_sources = null, max_budget = null, model = null } = opts;
223
+ const { sysprompt_file = null, setting_sources = null, max_budget = null, model = null, effort = null } = opts;
223
224
  const binary = claude_executable();
224
225
  if (binary === null) {
225
226
  return {
@@ -253,6 +254,12 @@ export function run_live(
253
254
  // account quota (the failure mode that starved later arms on the first run).
254
255
  cmd.push('--max-budget-usd', _pyStrNum(max_budget));
255
256
  }
257
+ if (effort) {
258
+ // Pin the reasoning effort for the run (skill-quality-gates Phase 4):
259
+ // a task or run that declares `effort` gets a reproducible knob setting
260
+ // instead of the host's session default drifting between runs.
261
+ cmd.push('--effort', effort);
262
+ }
256
263
  if (setting_sources) {
257
264
  cmd.push('--setting-sources', setting_sources);
258
265
  }
@@ -805,6 +812,9 @@ export function run_variant(
805
812
  setting_sources: setting_sources_for(variant),
806
813
  max_budget,
807
814
  model,
815
+ // Per-task effort pin (skill-quality-gates Phase 4): a corpus task
816
+ // may declare `effort` to hold the reasoning knob constant.
817
+ effort: typeof task['effort'] === 'string' ? (task['effort'] as string) : null,
808
818
  });
809
819
  } else {
810
820
  runResult = run_dry(task as Record<string, unknown>, cloneRoot, variant);