@event4u/agent-config 8.12.0 → 8.13.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 (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/pr/create.md +26 -0
  5. package/dist/agent-src/commands/team/adversarial.md +96 -0
  6. package/dist/agent-src/commands/team/delegate.md +109 -0
  7. package/dist/agent-src/commands/team/review.md +84 -0
  8. package/dist/agent-src/commands/team/status.md +92 -0
  9. package/dist/agent-src/commands/team.md +147 -0
  10. package/dist/agent-src/rules/improve-before-implement.md +2 -0
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/discovery/deprecation-report.md +1 -1
  13. package/dist/discovery/discovery-manifest.json +148 -10
  14. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  15. package/dist/discovery/discovery-manifest.summary.md +3 -3
  16. package/dist/discovery/orphan-report.md +1 -1
  17. package/dist/discovery/packs.json +11 -6
  18. package/dist/discovery/trust-report.md +2 -2
  19. package/dist/discovery/workspaces.json +10 -5
  20. package/dist/install/install.mjs +38 -0
  21. package/dist/mcp/registry-manifest.json +2 -2
  22. package/dist/server/schemas/settings.js +7 -0
  23. package/dist/server/schemas/settings.js.map +1 -1
  24. package/docs/CLAIMS.md +1 -1
  25. package/docs/architecture.md +1 -1
  26. package/docs/catalog.md +10 -4
  27. package/docs/command-flows.md +6 -1
  28. package/docs/contracts/ai-team-config.md +191 -0
  29. package/docs/contracts/command-clusters.md +1 -0
  30. package/docs/featured-skills.md +1 -1
  31. package/docs/getting-started-by-role.md +3 -1
  32. package/docs/getting-started.md +31 -1
  33. package/docs/maintainers/branch-protection.md +18 -0
  34. package/docs/proof.md +1 -1
  35. package/package.json +1 -1
  36. package/src/config/agent-settings.template.yml +40 -0
  37. package/src/scripts/_cli/cmd_doctor.ts +196 -1
  38. package/src/scripts/_lib/claude_plugin.ts +39 -4
  39. package/src/scripts/ai_team/config.ts +162 -0
  40. package/src/scripts/install.ts +39 -0
  41. package/src/scripts/rule_backlinks.ts +0 -0
  42. package/src/server/schemas/settings.ts +17 -0
@@ -0,0 +1,191 @@
1
+ ---
2
+ stability: beta
3
+ keep-beta-until: 2026-10-10
4
+ ---
5
+
6
+ # AI-Team Config (`ai_team` block)
7
+
8
+ **Purpose.** Lock the schema, validation, and quota rules for the `/team`
9
+ cross-model review family — one strong external model (via the codex CLI,
10
+ subscription-authed) reviewing inside the repo. Team mode is the **depth**
11
+ complement to the council's **breadth**; this contract is the boundary that
12
+ prevents drift across the settings template, the loader, the `/team`
13
+ wrappers, and the quota counter.
14
+
15
+ **Audience.** Authors of `src/scripts/ai_team/config.ts`, the `/team`
16
+ command family, the doctor `team` section, and the setup wizard. Also
17
+ reviewers checking that new team features keep the schema intact.
18
+
19
+ **Status.** Internal-locked. Changes require a contract version bump and a
20
+ revision entry in the consuming roadmap.
21
+
22
+ ## File location
23
+
24
+ Unlike the council (ALWAYS user-global `.ai-council.yml` — see
25
+ [ai-council-config.md](ai-council-config.md)), the `ai_team` block lives in
26
+ the **project settings cascade**:
27
+
28
+ ```
29
+ <project>/.agent-settings.yml → ai_team: { … }
30
+ ```
31
+
32
+ read via `load_agent_settings` (`src/scripts/_lib/agent_settings.ts`) and
33
+ validated by `src/scripts/ai_team/config.ts:load_ai_team_config`. Team mode
34
+ is a per-project posture (default-off), not a per-developer credential
35
+ store — the codex CLI carries the subscription auth itself, so there is
36
+ nothing secret to centralize. The shipped defaults live in
37
+ `src/config/agent-settings.template.yml` and are mirrored by the wizard
38
+ schema (`src/server/schemas/settings.ts`, enforced by the schema↔template
39
+ parity test).
40
+
41
+ ## Schema
42
+
43
+ ```yaml
44
+ ai_team:
45
+ enabled: <bool> # master switch, default false
46
+ model: <string> # default 'auto'
47
+ allow_delegate: <bool> # second opt-in for the write path, default false
48
+ max_calls_per_day: <int >= 0> # default 50; counts into the shared openai bucket
49
+ suppress_setup_hint: <bool> # default false; cosmetic wizard-hint suppression
50
+ ```
51
+
52
+ | Key | Type | Default | Semantics |
53
+ |---|---|---|---|
54
+ | `enabled` | bool | `false` | Master switch. `false` = `/team` commands are never suggested and every invocation refuses with an enable pointer — byte-identical to pre-feature behavior. `true` = the family is live; per-command gates below still apply. |
55
+ | `model` | string | `'auto'` | Model handed to the codex CLI. `'auto'` = pass **no** `--model` flag; the codex CLI's own default applies. Any other value passes through **verbatim** as `--model <value>`. |
56
+ | `allow_delegate` | bool | `false` | Second opt-in for the **only** wrapper that delegates write access (`/team:delegate`). Refuses with an enable pointer until BOTH `enabled` and `allow_delegate` are `true`. |
57
+ | `max_calls_per_day` | int ≥ 0 | `50` | Per-day ceiling on team calls, read against the **shared** `cli_call_budget` openai bucket (see § Quota). `0` blocks all team calls. |
58
+ | `suppress_setup_hint` | bool | `false` | Suppress the one-line wizard/init recommendation to install the codex plugin on Claude-Code hosts. Cosmetic only. |
59
+
60
+ ### Why `model: 'auto'` instead of a pinned ID
61
+
62
+ A hardcoded model ID rots silently (the council's own CLI default is a
63
+ deliberate, documented *pin* — `DEFAULT_OPENAI_CLI_MODEL` in
64
+ `src/scripts/ai_council/clients.ts`, see
65
+ [ai-council-config.md](ai-council-config.md)). Team mode inverts that:
66
+ under a subscription the *strongest* available model is the rational
67
+ default, and only the codex CLI knows what that currently is. `'auto'`
68
+ therefore delegates the choice to the CLI; a set value is a user pin and
69
+ passes through verbatim — the loader never rewrites or "corrects" it.
70
+
71
+ **Future keys:** `review_gate` (managed, loop-bound governance of the
72
+ plugin's Stop-hook Review Gate) is reserved for a later team-mode phase
73
+ (Phase 4). Do not add it — or any other key — ahead of its phase; the
74
+ loader rejects unknown keys today by design.
75
+
76
+ ## Role semantics — the council-verdict design constraint
77
+
78
+ Verbatim from the consuming roadmap's council verdict, binding on every
79
+ team-mode phase:
80
+
81
+ > **Council verdict on team-mode frontmatter (claude-sonnet-4-5 + gpt-4o,
82
+ > 2026-07-12, unanimous REJECT):** skills/commands get NO `team_mode` /
83
+ > role frontmatter. Role semantics belong in the `ai_team` config block +
84
+ > prompt library (reusing subagent-orchestration's implementer/judge frame
85
+ > and status envelope), not smeared across 271 skill files while the
86
+ > feature's role model does not operationally exist. Revisit-if: Phase 2
87
+ > execution turns out to be blocked SPECIFICALLY on artefact-level role
88
+ > metadata — then re-open via decision-revisit-gate, not by silently
89
+ > adding the key.
90
+
91
+ Operationally: ALL role semantics (builder/reviewer/verifier selection,
92
+ eligibility) live HERE — in this `ai_team` block — plus the prompt
93
+ library, reusing `subagent-orchestration`'s implementer/judge frame and
94
+ its `subagent-status.json` envelope. **No `team_mode`/role frontmatter key
95
+ is added to skill/command/rule schemas.**
96
+
97
+ ## Quota — one subscription, one counter
98
+
99
+ ```
100
+ TEAM CALLS COUNT INTO THE EXISTING cli_call_budget OPENAI BUCKET.
101
+ ONE SUBSCRIPTION, ONE COUNTER. NEVER A PARALLEL COUNTER.
102
+ ```
103
+
104
+ The daily CLI-call counter is already **generic per-provider** — no
105
+ team-specific counting system exists or may be built:
106
+
107
+ - **State file:** `~/.event4u/agent-config/cli-calls.json`
108
+ (`CLI_CALLS_FILENAME`, `src/scripts/ai_council/clients.ts:716`), shape
109
+ `{ date: "YYYY-MM-DD", counts: { <provider>: <int> } }`, resets on UTC
110
+ date rollover.
111
+ - **Read:** `load_cli_call_counts(path?)` —
112
+ `src/scripts/ai_council/clients.ts:738`. Returns today's per-provider
113
+ counts; empty on rollover.
114
+ - **Increment:** `record_cli_call(provider, path?)` —
115
+ `src/scripts/ai_council/clients.ts:774`. Returns the new total.
116
+ - **Gate + increment on the transport itself:** `CliClient.ask()` checks
117
+ the counter *before* spawning (`clients.ts` § quota gate, returns
118
+ `error: 'cli_quota_exhausted'` + a `block_quota` events-log line when
119
+ exhausted) and records the call *after* — even failed calls count, so a
120
+ broken CLI cannot burn the budget in a tight loop.
121
+ - **The openai bucket:** `OpenAICliClient` (`clients.ts:1275`) has
122
+ `name = 'openai'`; council CLI calls and team calls land on the **same**
123
+ `counts.openai` entry.
124
+
125
+ A `/team` invocation therefore reuses the existing seams, in preference
126
+ order:
127
+
128
+ 1. **Through the transport** — construct `OpenAICliClient` with
129
+ `max_calls_per_day: <ai_team.max_calls_per_day>`; the gate, the
130
+ exhausted-error, the events-log line, and the increment all come for
131
+ free.
132
+ 2. **Direct** (for `/team:status` rendering or a pre-flight check) —
133
+ `load_cli_call_counts()['openai'] ?? 0` to read,
134
+ `record_cli_call('openai')` to count. No new helper exists because
135
+ none is needed; adding a team-side duplicate of this counter is a
136
+ contract violation.
137
+
138
+ `ai_team.max_calls_per_day` and the council's
139
+ `cli_call_budget.max_calls_per_day.openai` are two *ceilings* over the
140
+ same *counter*: whichever path a call takes, it increments the one shared
141
+ bucket, and each surface enforces its own configured cap against that
142
+ shared count.
143
+
144
+ ## Validation rules (fail-closed)
145
+
146
+ `load_ai_team_config` / `build_ai_team_config`
147
+ (`src/scripts/ai_team/config.ts`) raise `TeamConfigError` when:
148
+
149
+ 1. `ai_team` is present but not a mapping,
150
+ 2. any **unknown key** appears under `ai_team` (a typo must never
151
+ silently disable a gate),
152
+ 3. `enabled` / `allow_delegate` / `suppress_setup_hint` is not a boolean,
153
+ 4. `model` is not a non-empty string,
154
+ 5. `max_calls_per_day` is not a non-negative integer (booleans rejected).
155
+
156
+ An **absent** `ai_team` block is not an error — it yields the defaults
157
+ (`AI_TEAM_DEFAULTS`) and the feature stays off.
158
+
159
+ ## Default-off posture
160
+
161
+ Every team-mode capability is default-off. With the block absent or
162
+ `enabled: false`, all paths are byte-identical to pre-feature behavior:
163
+ no command suggestions, no wrapper execution, no quota consumption, no
164
+ wizard nagging beyond the single suppressible setup hint.
165
+ `/team:delegate` is doubly gated (`enabled` AND `allow_delegate`).
166
+
167
+ ## No-claims constraint
168
+
169
+ No public claim about cross-model review quality ("a second model finds
170
+ errors the first never sees", "two strongest models catch each other's
171
+ errors", or any review-lift assertion) may exist anywhere in the tree
172
+ until the pre-registered three-arm defect-finding benchmark has produced
173
+ a verdict bound in CLAIMS.md. Workflow-value prose is allowed; lift
174
+ claims are not. An honest null keeps the feature documented as workflow
175
+ value only.
176
+
177
+ ## Boundary vs. the council
178
+
179
+ Team mode and the council are complements, not variants — the council's
180
+ neutrality contract is untouched by team mode:
181
+
182
+ | Axis | Council ([ai-council-config.md](ai-council-config.md)) | Team (`ai_team`) |
183
+ |---|---|---|
184
+ | Shape | N members, breadth | One strong reviewer, depth |
185
+ | Repo access | NEVER (artefact text + neutral preamble only) | Full (reviewer runs inside the working tree) |
186
+ | Config location | User-global `.ai-council.yml` (ADR-104) | Project `.agent-settings.yml` cascade |
187
+ | Default models | Mid-tier, cost-conscious | Subscription's strongest (`model: 'auto'`) |
188
+ | Quota | `cli_call_budget` per provider | Same counter — shared openai bucket |
189
+
190
+ This link is deliberately one-directional (team → council); the council
191
+ contract does not reference team mode.
@@ -45,6 +45,7 @@ column 1 of this table.
45
45
  | `council` | 3 | `default` · `pr` · `design` · `optimize` · `analysis` | `council` (legacy default lens) · `council-pr` · `council-design` · `council-optimize`; `analysis` added 2026-05-14 — wrapper for local analysis outputs with a Top-N consensus tail block consumed by `/roadmap create` |
46
46
  | `challenge-me` | — | `vision` · `with-docs` | new — Pocock-inspired one-question-at-a-time interview; `vision` is the standard 95%-confidence variant, `with-docs` adds doc/glossary awareness with a session-scoped glossary and load-bearing claim-vs-code verification |
47
47
  | `research` | 2 | `deep` · `report` | preliminary-research scaffolder ported from `Weizhena/Deep-Research-skills` (cluster head emits `outline.yaml` + `fields.yaml` against the `research-schema` contract). `:deep` populates per-item JSON in batches with native web-search + JSON-Schema self-validation (no Python runtime); `:report` renders `report.md` directly + optionally emits a `jq` template for deterministic regeneration. `add-items` / `add-fields` intentionally **not** ported — re-run `/research <topic>` to extend the field framework. |
48
+ | `team` | 2 | `review` · `adversarial` · `delegate` · `status` | new 2026-07-12 (road-to-team-mode Phase 2) — cross-model depth review: thin fail-closed delegations to the official codex plugin on Claude-Code hosts under `ai_team` governance ([`ai-team-config`](ai-team-config.md)); default-off (`ai_team.enabled: false`), `delegate` behind the second opt-in `allow_delegate`; internal visibility until the ADR promotion gate |
48
49
  | `orchestrate` | — | _(none yet — cluster head only)_ | new — runtime executor for YAML pipelines under `.agent-config/orchestrations/` per the [`orchestration-dsl-v1`](orchestration-dsl-v1.md) contract; chains personas / skills / commands / sub-agents deterministically. Single cluster head; sub-commands deferred until a second verb is needed. |
49
50
  | `sync` | 4 | `agent-settings` · `gitignore` · `gitignore-fix` | new cluster 2026-07-08 — absorbs the former single-sub `sync-gitignore` cluster (slug-neutral nests: `sync-agent-settings`, `sync-gitignore`, `sync-gitignore-fix` keep their slugs; the gitignore node keeps its own `fix` sub-dir, mirroring the `agents/user` deep-nest precedent). Bare `/sync` shows the menu. |
50
51
  | `ghostwriter` | — | `fetch` · `write` · `list` · `show` · `delete` | new cluster 2026-05-15 — third voice primitive for AI-assisted writing in the public voice of a public figure. Hybrid storage: real-person profiles live consumer-side under `agents/reference/ghostwriter/<slug>.md` (gitignored by default); package source ships only `fictional: true` fixtures. Zero network code in the package — `:fetch` delegates web-fetch / web-search to the host agent. Mandatory disclosure footer on every `:write` output (no opt-out). Schema: [`ghostwriter-schema`](ghostwriter-schema.md). |
@@ -92,4 +92,4 @@ task lint-featured-skills
92
92
 
93
93
  CI runs this in `taskfiles/ci-fast.yml`. Stale entries (renamed / removed skill or command) fail the build. See [`scripts/lint_featured_skills.py`](../src/scripts/lint_featured_skills.py) for the matcher.
94
94
 
95
- → Browse all 271 skills: [`dist/agent-src/skills/`](../dist/agent-src/skills/) · all 178 commands: [`dist/agent-src/commands/`](../dist/agent-src/commands/)
95
+ → Browse all 271 skills: [`dist/agent-src/skills/`](../dist/agent-src/skills/) · all 183 commands: [`dist/agent-src/commands/`](../dist/agent-src/commands/)
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Pick the entry that matches what you do day-to-day. Each section names the three skills you will reach for first and shows whether MCP (no terminal) or CLI (terminal) is the simpler install path for that role.
4
4
 
5
- `agent-config` ships 271 skills, 104 governed rules, and 178 commands. You do not need all of them. Each role below filters to the slice that pays back in week one; the rest stays available and shows up on demand when a task references it.
5
+ `agent-config` ships 271 skills, 104 governed rules, and 183 commands. You do not need all of them. Each role below filters to the slice that pays back in week one; the rest stays available and shows up on demand when a task references it.
6
6
 
7
7
  > **Taglines at a glance →** [`docs/role-experiences.md`](role-experiences.md) — the one-line pitch per role experience, in one table.
8
8
 
@@ -73,6 +73,8 @@ Cinematic-blueprint approach: the agent expands your script into a 12-block scen
73
73
  - [`nextjs-patterns`](../dist/agent-src/skills/nextjs-patterns/SKILL.md) — App Router, Server Components, Server Actions, caching.
74
74
  - [`quality-tools`](../dist/agent-src/skills/quality-tools/SKILL.md) — PHPStan, Rector, ECS error triage and fix loop.
75
75
 
76
+ Cross-model review of the real diff (**team mode**, default off) is documented in [Getting Started § Team mode](getting-started.md#team-mode--cross-model-review-default-off).
77
+
76
78
  **Install path:** **CLI.** Run `npx @event4u/agent-config init --tools=claude-code,cursor` in the project root. MCP works too but loses git / file-system tooling that the IDE-integrated path gives you.
77
79
 
78
80
  ---
@@ -174,7 +174,37 @@ Your agent now understands slash commands:
174
174
  | `/fix quality` | Run and fix all quality checks |
175
175
  | `/chat-history import` | Pull a prior session into the current chat (resume) |
176
176
 
177
- → [Browse all 178 active commands](../dist/agent-src/commands/)
177
+ → [Browse all 183 active commands](../dist/agent-src/commands/)
178
+
179
+ ---
180
+
181
+ ## Team mode — cross-model review (default off)
182
+
183
+ Team mode pairs two strong models on one change: one model builds, a
184
+ second reviews the **real diff** in your working tree and hands the
185
+ findings back for a fix round. It is the depth complement to the
186
+ [AI council](../dist/agent-src/skills/ai-council/SKILL.md): the council
187
+ collects many cheap, neutral opinions on an artefact's *text*; team mode
188
+ puts one strong, opinionated reviewer *inside the repo*.
189
+
190
+ | Axis | Council | Team mode |
191
+ |---|---|---|
192
+ | Repo access | none — members see only the artefact text | full — the reviewer reads the real diff and git state |
193
+ | Shape | breadth — N neutral opinions, one round | depth — one strong reviewer, build→review→fix iteration |
194
+ | Cost model | API-billed members (per-token) | subscription-authed `codex` CLI (runs under your existing plan) |
195
+
196
+ **Setup.** Run `agent-config doctor --check team` — it verifies the
197
+ `codex` binary + auth and, on Claude Code hosts, the Codex plugin, and
198
+ prints the exact remediation command for anything missing.
199
+
200
+ **Default off.** Team mode ships disabled (`ai_team.enabled: false` in
201
+ `.agent-settings.yml`); no team command runs and no cross-model call is
202
+ made until you opt in.
203
+
204
+ **No lift claim.** Whether cross-model team review catches more real
205
+ defects than single-model adversarial self-review is **unproven** — the
206
+ package makes no review-quality claim until the pre-registered benchmark
207
+ has run and its verdict is recorded.
178
208
 
179
209
  ---
180
210
 
@@ -71,6 +71,24 @@ second-writer race to protect against; flip to `true` when there are
71
71
  concurrent mergers. `required_pull_request_reviews: null` per the
72
72
  bus-factor reality above.
73
73
 
74
+ ## Operating conditions under `strict: false`
75
+
76
+ `strict: false` drops the branches-up-to-date requirement, so three
77
+ conditions become merge-time habits the config cannot enforce:
78
+
79
+ - **The checked commit must not change after green CI.** No post-CI
80
+ amend or force-push on the PR branch — the config already blocks
81
+ force-pushes to `main`; the same discipline applies to the head that
82
+ CI checked: what went green is what merges.
83
+ - **Critical checks must have run on the final head before merge.** A
84
+ human glance at the checks tab on the PR's latest commit — with
85
+ `strict: false` GitHub does not re-run required checks when the base
86
+ moves, so the green you see is only as fresh as the head it ran on.
87
+ - **Auto-merge must not be enabled in a way that merges with pending or
88
+ failed required checks.** Enable auto-merge only once the required
89
+ list is complete and green on the final head — never as a
90
+ fire-and-forget while checks are still queued.
91
+
74
92
  ## Rollback
75
93
 
76
94
  ```bash
package/docs/proof.md CHANGED
@@ -27,7 +27,7 @@ evidence pointer, or `task check-claims` fails the build.
27
27
  | Claim | Kind | Evidence | Resolves |
28
28
  |---|---|---|---|
29
29
  | The release process is documented as an inheritable runbook + succession doc, and the project's bus-factor (trailing-90-day distinct human reviewers) is tracked and reported truthfully — currently 1, not implied to be more. | qual | `docs/succession.md#trailing 90 days` | ✅ |
30
- | 178 commands. | quant | `src/scripts/check_artefact_count_messaging.ts#Artefact-count messaging gate` | ✅ |
30
+ | 183 commands. | quant | `src/scripts/check_artefact_count_messaging.ts#Artefact-count messaging gate` | ✅ |
31
31
  | A MEASURED-BUT-NOT-SHIPPED experiment — the thin rule projection reduced eager rule load 78,513 → 13,881 GPT-tokens (whole always-loaded projection 98,529 → 33,897, ~65.6%), but FAILED the quality gate (thin win-rate 36.2% vs required 48%) and does not ship; it un-defers only behind `discipline_profile: essential`. Shipped behavior does NOT include this reduction. Method: `agent-config benchmark` over the pinned token baseline; the baseline is the honest "what the user pays if everything loads eagerly", NOT a synthetic full-corpus strawman (council Q4); quality gate per the Phase-0 paired judge run. | quant | `internal/bench/reports/token-baseline.json#eager_rule_load` | ✅ |
32
32
  | On a weak host (claude-haiku-4-5) the package produces a significant, placebo-controlled discipline lift on scope/downstream traps; on a strong host the same measurement is a published null — the package transplants discipline a weak model lacks, not model intelligence. | quant | `docs/benchmark.md#weak-host-specific` | ✅ |
33
33
  | The non-coding domain skills (finance/founder/ops/content) are forged on TS/PHP and labeled unvalidated until they pass a sourced domain-truth fixture; no public prose implies proven domain correctness, and the validated count is CI-ratcheted. | qual | `src/scripts/domain_soundness_status.ts#checkRatchet` | ✅ |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "8.12.0",
3
+ "version": "8.13.0",
4
4
  "description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -629,6 +629,46 @@ worktrees:
629
629
  #
630
630
  # See `docs/contracts/ai-council-config.md` for the schema.
631
631
 
632
+ # --- AI Team (cross-model strong-pair review — /team family) ---
633
+ #
634
+ # Governs the default-off `/team` capability family: one strong external
635
+ # model (via the codex CLI, subscription-authed) reviews inside the repo —
636
+ # the depth complement to the council's breadth. Schema + validation
637
+ # contract: `docs/contracts/ai-team-config.md`. The loader
638
+ # (`src/scripts/ai_team/config.ts`) rejects unknown keys fail-closed.
639
+ ai_team:
640
+ # Master switch. false (default) = /team commands are never suggested
641
+ # and every invocation refuses with an enable pointer; true = the
642
+ # family is live (individual gates below still apply).
643
+ enabled: false
644
+
645
+ # Model handed to the codex CLI. 'auto' (default) = pass NO --model
646
+ # flag, the codex CLI's own default applies — this tracks the
647
+ # subscription's current strongest model instead of pinning a stale
648
+ # ID. Any other value passes through verbatim as `--model <value>`.
649
+ model: auto
650
+
651
+ # Second opt-in for the ONLY wrapper that delegates write access
652
+ # (/team:delegate → the plugin's native-Codex worker). false (default)
653
+ # = delegate refuses with an enable pointer even when ai_team.enabled
654
+ # is true. Both keys must be true before delegation is reachable.
655
+ allow_delegate: false
656
+
657
+ # Per-day cap on team calls. Team calls count into the EXISTING
658
+ # cli_call_budget openai bucket (~/.event4u/agent-config/cli-calls.json,
659
+ # daily UTC reset) — one subscription, one counter, never a parallel
660
+ # count. This key is the team-side ceiling read against that shared
661
+ # bucket. 0 = block all team calls; raise if you review heavily.
662
+ max_calls_per_day: 50
663
+
664
+ # Suppress the one-line wizard/init recommendation to set up the codex
665
+ # plugin on Claude-Code hosts. Cosmetic only — never changes behavior.
666
+ suppress_setup_hint: false
667
+
668
+ # NOTE for future keys: `review_gate` (managed loop-bound governance of
669
+ # the plugin's Stop-hook Review Gate) lands in a later team-mode phase
670
+ # (Phase 4) — do not add it here ahead of that phase.
671
+
632
672
  # --- Onboarding ---
633
673
  #
634
674
  # Tracks whether the initial setup flow (/onboard) has been completed
@@ -29,7 +29,7 @@
29
29
  * scope · global-binary · claude-plugin · stale-orphans · manifest-integrity ·
30
30
  * lockfile-freshness · bridge-drift · mcp-mode · mcp-beta-readiness ·
31
31
  * offline-readiness · python-runtime · humanizer-runtime · tier-usage-readiness ·
32
- * council-cli ·
32
+ * council-cli · team ·
33
33
  * unsupported-combos · wizard-state.
34
34
  * Each emits a structured `{id, status, message, remedy}` record with
35
35
  * `status` ∈ `ok` / `warn` / `fail` / `skipped` (rendered
@@ -747,6 +747,7 @@ const CHECK_IDS = [
747
747
  'humanizer-runtime',
748
748
  'tier-usage-readiness',
749
749
  'council-cli',
750
+ 'team',
750
751
  'unsupported-combos',
751
752
  'wizard-state',
752
753
  'settings-review-pending',
@@ -769,6 +770,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
769
770
  'humanizer-runtime',
770
771
  'tier-usage-readiness',
771
772
  'council-cli',
773
+ 'team',
772
774
  'wizard-state',
773
775
  'settings-review-pending',
774
776
  ]);
@@ -1975,6 +1977,196 @@ function pyFloat(value: number): string {
1975
1977
  return String(value);
1976
1978
  }
1977
1979
 
1980
+ // ---------------------------------------------------------------------------
1981
+ // team check (road-to-team-mode Phase 1) — codex binary + auth, official
1982
+ // codex plugin on Claude-Code hosts, Review-Gate consistency.
1983
+ // ---------------------------------------------------------------------------
1984
+
1985
+ /**
1986
+ * Codex CLI home — `CODEX_HOME` env override (the codex CLI's own
1987
+ * convention), else `~/.codex`.
1988
+ */
1989
+ function _codex_home(): string {
1990
+ const env = (process.env['CODEX_HOME'] ?? '').trim();
1991
+ return env !== '' ? env : path.join(os.homedir(), '.codex');
1992
+ }
1993
+
1994
+ /**
1995
+ * Codex binary name for the team check — REUSES the council probe's binary
1996
+ * discovery: when a council config exists and declares an enabled cli-mode
1997
+ * `openai` member with a `binary` override, honour it; else the provider
1998
+ * default from `_CLI_PROVIDER_META` (`codex`). Never throws — an unreadable
1999
+ * or invalid council config falls back to the default binary (the council-cli
2000
+ * check owns reporting that problem).
2001
+ */
2002
+ function _team_codex_binary_name(project_root: string): string {
2003
+ const default_bin = (_CLI_PROVIDER_META['openai'] as [string, boolean])[0];
2004
+ const { load_council_config, resolve_config_path } = ai_council_config;
2005
+ const council_path = resolve_config_path(project_root);
2006
+ if (!pathExists(String(council_path))) {
2007
+ return default_bin;
2008
+ }
2009
+ let cfg: ai_council_config.CouncilConfig;
2010
+ try {
2011
+ cfg = load_council_config(council_path);
2012
+ } catch {
2013
+ return default_bin;
2014
+ }
2015
+ const member = cfg.members.get('openai');
2016
+ if (member && member.enabled && member.mode === 'cli' && member.binary) {
2017
+ return member.binary;
2018
+ }
2019
+ return default_bin;
2020
+ }
2021
+
2022
+ /** Settings-flag coercion shared with the tier-usage check (bool or string). */
2023
+ function _settings_flag(val: unknown): boolean {
2024
+ if (typeof val === 'boolean') return val;
2025
+ if (typeof val === 'string') {
2026
+ return ['true', 'yes', 'on', '1'].includes(val.trim().toLowerCase());
2027
+ }
2028
+ return false;
2029
+ }
2030
+
2031
+ /** The `ai_team` block from `.agent-settings.yml` — lenient, `{}` when absent. */
2032
+ function _read_ai_team_block(project_root: string): Dict {
2033
+ const settings_file = project_settings_path(project_root);
2034
+ if (!isFile(settings_file)) return {};
2035
+ let content: string;
2036
+ try {
2037
+ content = readText0(settings_file);
2038
+ } catch {
2039
+ return {};
2040
+ }
2041
+ const loaded = yamlSafeLoad(content);
2042
+ if (typeof loaded !== 'object' || loaded === null || Array.isArray(loaded)) return {};
2043
+ const raw = (loaded as Dict)['ai_team'];
2044
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return {};
2045
+ return raw as Dict;
2046
+ }
2047
+
2048
+ /**
2049
+ * `team` health check — three sub-signals folded into one result:
2050
+ *
2051
+ * (a) codex binary present (council-probe binary discovery reused) AND
2052
+ * auth present. Auth is a READ-ONLY presence probe of
2053
+ * `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`) — token
2054
+ * expiry cannot be validated without a billable call; runtime expiry
2055
+ * is caught by `OpenAICliClient._AUTH_FAILURE_PATTERNS`.
2056
+ * (b) on Claude-Code hosts (claude binary on PATH or the Claude config dir
2057
+ * present): the official codex plugin recorded in
2058
+ * `plugins/installed_plugins.json`. Detection is read-only — doctor
2059
+ * never writes under `~/.claude/`.
2060
+ * (c) Review-Gate consistency: `ai_team.review_gate.managed` on while the
2061
+ * loop bound (`max_consecutive_blocks`, Phase 4 — unbuilt) is absent
2062
+ * → WARN.
2063
+ *
2064
+ * Absent feature ≠ broken feature: with `ai_team` absent or disabled the
2065
+ * check reports ok ("team mode not configured (default-off)"); WARN fires
2066
+ * only on failing sub-signals or inconsistent half-configured states.
2067
+ */
2068
+ function _check_team(project_root: string): Dict {
2069
+ const ai_team = _read_ai_team_block(project_root);
2070
+ const enabled = _settings_flag(ai_team['enabled']);
2071
+ const gateRaw = ai_team['review_gate'];
2072
+ const gate =
2073
+ typeof gateRaw === 'object' && gateRaw !== null && !Array.isArray(gateRaw)
2074
+ ? (gateRaw as Dict)
2075
+ : {};
2076
+ const gate_managed = _settings_flag(gate['managed']);
2077
+ const bound = gate['max_consecutive_blocks'];
2078
+ const bound_present = typeof bound === 'number' && Number.isInteger(bound) && bound > 0;
2079
+
2080
+ if (!enabled) {
2081
+ if (gate_managed) {
2082
+ return {
2083
+ id: 'team',
2084
+ status: 'warn',
2085
+ message:
2086
+ 'half-configured: ai_team.review_gate.managed is on but ' +
2087
+ 'ai_team.enabled is false — the gate never runs',
2088
+ remedy:
2089
+ 'set `ai_team.enabled: true` in .agent-settings.yml, or ' +
2090
+ 'remove `ai_team.review_gate.managed`',
2091
+ };
2092
+ }
2093
+ return {
2094
+ id: 'team',
2095
+ status: 'ok',
2096
+ message: 'team mode not configured (default-off)',
2097
+ remedy:
2098
+ 'opt in via `ai_team.enabled: true` in .agent-settings.yml, ' +
2099
+ 'then re-run `agent-config doctor --check team`',
2100
+ };
2101
+ }
2102
+
2103
+ const remedies: string[] = [];
2104
+
2105
+ // (a) codex binary — council-probe discovery reused.
2106
+ const binary_name = _team_codex_binary_name(project_root);
2107
+ const binary_resolved = shutilWhich(binary_name);
2108
+ const binary_glyph = binary_resolved !== null ? '✅' : '❌';
2109
+ if (binary_resolved === null) {
2110
+ remedies.push('install the codex CLI: `npm install -g @openai/codex`');
2111
+ }
2112
+
2113
+ // (a, cont.) codex auth — presence probe of $CODEX_HOME/auth.json.
2114
+ const auth_path = path.join(_codex_home(), 'auth.json');
2115
+ const authed = pathExists(auth_path);
2116
+ const auth_glyph = authed ? '✅' : '❌';
2117
+ if (!authed) {
2118
+ remedies.push('run `codex login`');
2119
+ }
2120
+
2121
+ // (b) official codex plugin — Claude-Code hosts only.
2122
+ const is_claude_host =
2123
+ shutilWhich('claude') !== null || pathExists(claude_plugin.claude_config_dir());
2124
+ let plugin_str: string;
2125
+ if (is_claude_host) {
2126
+ const plugin_present = claude_plugin.codex_plugin_installed();
2127
+ plugin_str = `codex plugin ${plugin_present ? '✅' : '❌'}`;
2128
+ if (!plugin_present) {
2129
+ remedies.push(
2130
+ 'install the official codex plugin: ' +
2131
+ claude_plugin.CODEX_PLUGIN_INSTALL_HINT.map((c) => `\`${c}\``).join(' then '),
2132
+ );
2133
+ }
2134
+ } else {
2135
+ plugin_str = 'codex plugin — (not a Claude Code host; fallback path applies)';
2136
+ }
2137
+
2138
+ // (c) Review-Gate consistency.
2139
+ let gate_str: string;
2140
+ if (gate_managed && !bound_present) {
2141
+ gate_str = 'review-gate ⚠️ on, loop bound absent';
2142
+ remedies.push(
2143
+ 'set `ai_team.review_gate.max_consecutive_blocks` (loop bound; ' +
2144
+ 'ships with the Review-Gate governance phase) or set ' +
2145
+ '`ai_team.review_gate.managed: false`',
2146
+ );
2147
+ } else {
2148
+ gate_str = `review-gate ${gate_managed ? 'on' : 'off'}`;
2149
+ }
2150
+
2151
+ const detail =
2152
+ `codex binary ${binary_glyph} (${binary_name}) · ` +
2153
+ `codex auth ${auth_glyph} (${auth_path}) · ${plugin_str} · ${gate_str}`;
2154
+ if (remedies.length > 0) {
2155
+ return {
2156
+ id: 'team',
2157
+ status: 'warn',
2158
+ message: `${remedies.length} team sub-signal(s) failing · ${detail}`,
2159
+ remedy: remedies.join('; '),
2160
+ };
2161
+ }
2162
+ return {
2163
+ id: 'team',
2164
+ status: 'ok',
2165
+ message: `team mode enabled · ${detail}`,
2166
+ remedy: '',
2167
+ };
2168
+ }
2169
+
1978
2170
  function _check_unsupported_combos(manifest: Dict): Dict {
1979
2171
  const global_only = new Set(['droid', 'qoder']);
1980
2172
  const bad: string[] = [];
@@ -2257,6 +2449,7 @@ function _run_checks(
2257
2449
  'humanizer-runtime': () => _check_humanizer_runtime(),
2258
2450
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2259
2451
  'council-cli': () => _check_council_cli(project_root),
2452
+ team: () => _check_team(project_root),
2260
2453
  'unsupported-combos': () => _check_unsupported_combos(manifest),
2261
2454
  'wizard-state': _check_wizard_state,
2262
2455
  'settings-review-pending': _check_settings_review_pending,
@@ -2329,6 +2522,7 @@ function _run_checks_no_manifest(
2329
2522
  'humanizer-runtime': () => _check_humanizer_runtime(),
2330
2523
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2331
2524
  'council-cli': () => _check_council_cli(project_root),
2525
+ team: () => _check_team(project_root),
2332
2526
  'unsupported-combos': () => _skipped_manifest_check('unsupported-combos'),
2333
2527
  'wizard-state': _check_wizard_state,
2334
2528
  'settings-review-pending': _check_settings_review_pending,
@@ -2818,6 +3012,7 @@ export {
2818
3012
  _check_mcp_beta_readiness,
2819
3013
  _check_tier_usage_readiness,
2820
3014
  _check_council_cli,
3015
+ _check_team,
2821
3016
  _check_unsupported_combos,
2822
3017
  _check_wizard_state,
2823
3018
  _check_settings_review_pending,