diff --git a/.codex/plugins/brewcode/skills/superreview-setup/references/intent-guard.toml.template b/.codex/plugins/brewcode/skills/superreview-setup/references/intent-guard.toml.template new file mode 100644 index 0000000..47c62aa --- /dev/null +++ b/.codex/plugins/brewcode/skills/superreview-setup/references/intent-guard.toml.template @@ -0,0 +1,3 @@ +name = "intent-guard" +description = "Review-only anti-drift check comparing requested and delivered scope." +developer_instructions = "Review only. Compare what was requested with what was delivered, report concrete drift with file:line evidence, and never implement or mutate project files." diff --git a/.codex/plugins/brewcode/skills/superreview-setup/scripts/emit-intent-guard.sh b/.codex/plugins/brewcode/skills/superreview-setup/scripts/emit-intent-guard.sh new file mode 100755 index 0000000..2e89942 --- /dev/null +++ b/.codex/plugins/brewcode/skills/superreview-setup/scripts/emit-intent-guard.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail +root="${1:-}" +test -n "$root" || { echo "usage: emit-intent-guard.sh " >&2; exit 2; } +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +template="$SCRIPT_DIR/../references/intent-guard.toml.template" +agents="$root/.codex/agents" +target="$agents/intent-guard.toml" + +validate() { + python3 - "$1" <<'PY' +import pathlib, sys, tomllib +path = pathlib.Path(sys.argv[1]) +try: + data = tomllib.loads(path.read_text(encoding="utf-8")) +except (OSError, UnicodeError, tomllib.TOMLDecodeError) as exc: + print(f"invalid TOML: {exc}", file=sys.stderr) + raise SystemExit(1) +required = {"name", "description", "developer_instructions"} +if set(data) != required: + print("TOML keys must be exactly name, description, developer_instructions", file=sys.stderr) + raise SystemExit(1) +if any(type(data[key]) is not str for key in required) or data["name"] != "intent-guard": + print("intent-guard native fields must be strings and name must be fixed", file=sys.stderr) + raise SystemExit(1) +PY +} + +validate "$template" +if [ -f "$target" ]; then + validate "$target" + echo "INTENT_GUARD: REUSE .codex/agents/intent-guard.toml" + exit 0 +fi +mkdir -p "$agents" +tmp="$(mktemp "$agents/.intent-guard.XXXXXX")" +trap 'rm -f "$tmp"' EXIT HUP INT TERM +cp "$template" "$tmp" +validate "$tmp" +mv "$tmp" "$target" +trap - EXIT HUP INT TERM +echo "INTENT_GUARD: CREATED .codex/agents/intent-guard.toml" diff --git a/.codex/plugins/brewcode/skills/teams-setup/README.md b/.codex/plugins/brewcode/skills/teams-setup/README.md index 80040d8..dc22f4d 100644 --- a/.codex/plugins/brewcode/skills/teams-setup/README.md +++ b/.codex/plugins/brewcode/skills/teams-setup/README.md @@ -1,6 +1,6 @@ # Teams -Create and manage dynamic teams of domain-specific agents with a tracking framework. Analyzes your project, proposes 5-20 specialized agents, creates them, and sets up performance tracking. Every team also gets one fixed review-only member, `intent-guard`. +Create and manage dynamic teams of domain-specific agents with a tracking framework. The skill analyzes your project, proposes 5-20 specialized agents, creates them, and sets up performance tracking. A new team also gets exactly one review-only `intent-guard`; an upgraded legacy team with none stays that way. ## Quick Start @@ -28,7 +28,7 @@ The verb always comes first and the optional `` after it. That parser guar `disable` is a rename, not a deletion — the roster rows stay in `team.md` with `Status: disabled`, and `verify-team.sh` reports `DISABLED` per parked member and still exits PASS. `enable` puts it all back. Both take effect for the NEXT session: agent discovery is read at session start. -`purge` keeps exactly one thing: `.codex/agents/intent-guard.toml`, shared with `$brewcode:superreview-setup`. It removes both `.toml` and `.toml.disabled`, so purging a disabled team leaves nothing behind. +`purge` removes both `.toml` and `.toml.disabled`, so purging a disabled team leaves no owned domain profile behind. When `Intent guard` is `required`, it keeps `.codex/agents/intent-guard.toml` because that file is shared with `$brewcode:superreview-setup`; `legacy-absent` has no guard to keep. ## Examples @@ -83,7 +83,7 @@ After `$brewcode:teams-setup install my-team`: agents/ agent-one.md # Domain agents (5-20 depending on variant) agent-two.md - intent-guard.toml # Fixed review-only member, every team, not counted + intent-guard.toml # One review-only member for policy `required`; not counted teams/ my-team/ team.md # Roster: agent list, domains, missions, status @@ -91,11 +91,20 @@ After `$brewcode:teams-setup install my-team`: trace-ops.sh # Tracer, copied from the plugin at install -- agents call THIS path ``` +`team.md` stores logical `intent_guard_policy=required|legacy-absent` in its `Intent guard` field. +`required` means the roster contains exactly one `intent-guard` row. `legacy-absent` means it contains +zero such rows; upgrade preserves that state and never creates the role. New installs default to +`required`. + +Codex domain profiles are Markdown files under `.codex/agents/`. The Codex projection uses native +TOML files under `.codex/agents/`; it carries no YAML-in-TOML guidance. + ## How Agents Work -Created agents follow the **sub-agent task Acceptance Protocol** -- they self-select tasks based on domain fit, record acceptance/refusal in `trace.jsonl`, and log issues and insights as they work. - -Every generated domain agent is also born with a **Return Contract**: verdict first, <=30 lines, `path:line`, no file bodies, no command output, no logs, no preamble -- bulk material goes to `.codex/reports/` and only the path comes back. It holds whether or not `$brewtools:agent-return-setup` is installed; the guard only adds mechanical thresholds. `verify-team.sh` warns on an older agent that lacks the section, and `upgrade` re-adds it. +Created agents load `team.md`, whose single **Shared Agent Contract** owns acceptance, routing, tracing, +return, scope-fit and colleague rules. Domain profiles do not repeat those contracts. Their bodies have +exactly six ordered headings: `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique +invariants`, and `Unique verification`. They write through the project-local copy of the tracer: @@ -125,7 +134,7 @@ $brewcode:teams-setup install my-project | v [C2] Team Proposal ------ 3 variants + user confirmation - (+ intent-guard, fixed, not counted) + (+ one intent-guard for new teams, not counted) | v [C2.5] Model Selection -- high-reasoning model / balanced model / fast model / mixed (domain agents only) @@ -134,7 +143,7 @@ $brewcode:teams-setup install my-project [C3] Agent Creation ----- agent-creator x N (batches of 3-4) | v -[C3-IG] intent-guard ---- generate.sh emit-agent (create or reuse), then adapt if created +[C3-IG] intent-guard ---- required: emit/reuse; legacy-absent: do nothing | v [C4] Framework Setup ---- team.md + trace.jsonl + trace-ops.sh + verification @@ -171,22 +180,32 @@ After agent creation, a quality pipeline validates the team: > Skip with `--skip-review`. Run separately: `$brewcode:teams-setup upgrade --review` -> `intent-guard` is never used as a reviewer in this pipeline, and it is judged by different criteria than domain agents: placeholders resolved, template header stripped, frontmatter untouched (short review-only description, read-only tools). "Missing domain sections" is a false positive for it. +> When policy is `required`, `intent-guard` is not a reviewer in this pipeline and is judged by its own +> review-only contract. The six-heading domain-profile gate does not apply to it. Under `legacy-absent`, +> there is no guard to review. -## intent-guard (always in the team) +## intent-guard policy -Every team gets `intent-guard` in addition to its domain agents. It is an **anti-drift check**: it compares what was **ASKED** (the original request, ticket, spec, plan, project policy) against what was **DELIVERED**, and reports the delta. +`team.md` has exactly one `Intent guard` policy field: + +| Policy | Roster contract | Upgrade behavior | +|--------|-----------------|------------------| +| `required` | Exactly one review-only `intent-guard` row, outside the domain-agent count | Preserve the row; never duplicate it | +| `legacy-absent` | Zero `intent-guard` rows | Preserve absence; never create the role | + +New installs use `required`. The role is an **anti-drift check**: it compares what was **ASKED** (the +original request, ticket, spec, plan, project policy) against what was **DELIVERED**, and reports the delta. | Property | Value | |----------|-------| -| Counted in the 5 / 10-12 / 15-20 roster? | No -- it is outside the domain-agent count and cannot be dropped | +| Counted in the 5 / 10-12 / 15-20 roster? | No -- when required, it is outside the domain-agent count | | Tools | Read-only (`Read`, `Glob`, `Grep`, `Bash`). Never edits, builds, or runs tests | | Model | `balanced model`, fixed by its template -- not affected by the C2.5 model choice | | Invocation | Explicit, by name, during review only -- never during development, never an implementation owner | | Source | Emitted by `skills/superreview-setup/scripts/generate.sh emit-agent` from the shared template -- the single writer of this file, used by both skills | | Output | Verdict `ALIGNED` / `MINOR DRIFT` / `MAJOR DRIFT` plus <=10 findings, each with ASKED / SOURCE+tier / DELIVERED evidence / severity / minimal correction | -**Single writer (idempotent):** `teams` never authors this file. It runs +**Single writer (idempotent, `required` only):** `teams` never authors this file. It runs `superreview-setup/scripts/generate.sh emit-agent`, which creates it from the shared template or reuses an existing one and prints `INTENT_GUARD: CREATED|REUSE|MIGRATED `. On `REUSE` -- typically because `$brewcode:superreview-setup` ran first -- the file is left exactly as is and only the `team.md` roster row is @@ -196,14 +215,16 @@ invariants, drift examples, evidence commands) and touches nothing else -- front as emitted. Both skills therefore converge on one shared file produced by one pipeline, never two variants. -`intent-guard` is also excluded from `upgrade` and `uninstall` agent pruning (enforced in the cleanup flow -itself, Step 3, including a refusal if it is named explicitly): it does not write trace entries, so zero -activity is its normal state, not a reason to delete it. Teams created before `intent-guard` existed are -not broken by this -- `verify-team.sh` only WARNs, with the command to add it. +When policy is `required`, `intent-guard` is excluded from `upgrade` and `uninstall` pruning. It does not +write trace entries, so zero activity is normal. Teams created before the role existed can be upgraded +without it: the migration writes `legacy-absent`, retains zero rows, and does not emit a profile. + +Project Dusk is the compatibility baseline for this path: its roster stays at 13 members; +`task-tracker` remains a non-member owner, and `Intent guard: legacy-absent` keeps `intent-guard` absent. ## sub-agent task Acceptance Protocol -Each agent follows a 3-step self-selection before accepting a task: +The shared contract in `team.md` gives every domain agent the same 3-step self-selection before accepting a task: | # | Check | Question | If No | |---|-------|----------|-------| @@ -228,7 +249,9 @@ When other skills (convention, superreview, e2e) spawn agents, they check for te > If a team agent refuses a task (sub-agent task Acceptance Protocol), the skill re-delegates to the next priority level. Max 2 retries before falling back to system agents. -> `intent-guard` is outside this resolution chain -- it is never selected as an implementation or review owner by domain fit. It runs only when a review flow invokes it explicitly by name. +> Under `required`, `intent-guard` is outside this resolution chain -- it is never selected as an +> implementation or review owner by domain fit and runs only when a review flow invokes it explicitly. +> Under `legacy-absent`, there is no role to resolve. ## Related Skills diff --git a/.codex/plugins/brewcode/skills/teams-setup/SKILL.md b/.codex/plugins/brewcode/skills/teams-setup/SKILL.md index ae2263d..d95fe23 100644 --- a/.codex/plugins/brewcode/skills/teams-setup/SKILL.md +++ b/.codex/plugins/brewcode/skills/teams-setup/SKILL.md @@ -7,916 +7,49 @@ description: "Creates and manages dynamic teams of domain agents. Triggers: crea Use collaboration agents only when the user or project instructions explicitly request a team. Split work into bounded independent tasks, keep one owner per file or surface, exchange evidence through collaboration messages, and synthesize results in the parent session. Do not invent unsupported agent parameters or create persistent team configuration unless requested. -## Complete native workflow - -Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - - +## Native authority -# Teams +Manage persistent project teams under `.codex/teams/{TEAM_NAME}/` and domain agents under `.codex/agents/`. Agent files are real TOML parsed with Python `tomllib`; never rename Markdown/YAML agents to `.toml`. Each team agent has exactly three top-level string keys: `name`, `description`, `developer_instructions`. -Manage dynamic teams of domain-specific agents with tracking framework. +Resolve one mode: `status`, `install`, `upgrade`, `enable`, `disable`, `uninstall`, or `purge`. Read applicable `AGENTS.md`, inspect existing teams and agents, preserve unrelated files, and use only scripts/references shipped beside this skill. Never edit installed caches. -**Arguments:** `` +### C2.6: Shared Contract Bootstrap ---- +Bootstrap happens before any team-owned `.codex/agents/{name}.toml` is written: instantiate the fenced template from `references/framework-files.md` and write `team.md` at `.codex/teams/{TEAM_NAME}/team.md` with metadata, `## Shared Agent Contract`, explicit `Intent guard` policy, and zero domain rows. Do not add domain-agent rows yet. Copy the project-local tracer, initialize trace storage, substitute every placeholder, then run `scripts/verify-team.sh`. **STOP on any failure. Do not spawn or write an agent.** -## Prompt contract +### C3: Agent Creation -Position 1 of `` is a **free-form prompt** (RU/EN) — the mode and the `[name]` positional are -optional and may follow in any order. Nobody types keys: resolve mode + team name FROM the prompt. +Create each approved domain `.codex/agents/{name}.toml` from `references/agent-template.md`. Parse it structurally with `tomllib` and require only `name`, `description`, and `developer_instructions`. The `developer_instructions` value uses exactly these ordered headings and no others: `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique invariants`, `Unique verification`. Its first must-load item is exactly `.codex/teams/{TEAM_NAME}/team.md`, occurring once. Enforce <=3200 UTF-8 bytes and `ceil(chars/4) <=800` over `developer_instructions` itself. -| Mode | EN keywords | RU keywords | Mutates? | -|------|-------------|-------------|----------| -| `status` | *(empty)*, `status`, `show`, `list`, `check` | `статус`, `покажи`, `что`, `проверь` | no | -| `install` | `install`, `create`, `setup`, `new team`, `build` | `установи`, `создай`, `настрой`, `новая команда` | yes | -| `upgrade` | `upgrade`, `update`, `tune`, `improve`, `retune` | `обнови`, `улучши`, `настрой лучше` | yes | -| `enable` | `enable`, `on`, `turn on`, `activate`, `restore` | `включи`, `активируй`, `верни`, `восстанови` | yes | -| `disable` | `disable`, `off`, `turn off`, `pause`, `park` | `выключи`, `отключи`, `пауза`, `приостанови` | yes | -| `uninstall` | `uninstall`, `remove`, `delete`, `clean up`, `tear down` | `удали`, `убери`, `сними`, `очисти` | yes, destructive | -| `purge` | `purge`, `wipe`, `nuke`, `delete everything`, `remove all` | `снеси`, `удали всё`, `вычисти`, `полностью удали` | yes, destructive | +`intent-guard` is exempt from the six-heading domain profile. Under `required`, run `/skills/superreview-setup/scripts/emit-intent-guard.sh `; that sole shared writer create-only copies its native authority, structurally validates it, and never overwrites an existing file. Never ask agent-creator to write it. Under `legacy-absent`, create no row and no role. -1. Strip flags (`--skip-review`, `--review`). An explicit mode token anywhere wins outright, no scoring. -2. Else score modes by distinct whole-word keyword hits (table above). Highest unique score wins. - Tie with a destructive mode -> `request_user_input`; tie with `status` -> `status`; tie of two mutating - modes -> the keyword appearing first; all zero -> **the documented default: `status` if the named - team already exists, else `install`** (`detect-mode.sh` already applies this default when the input - is empty or the first word is not a canonical mode). -3. Empty arguments -> the same default. `status` asks nothing; `install` and the other mutating - defaults ask ONE scoping question only when the answer changes what gets written. -4. Outcome-changing ambiguity -> ONE `request_user_input` (max 4 questions) BEFORE any work. -5. A prompt that is not a bare `mode [name]` pair is still input, never an error: extract the team - NAME (and, for `install`, the team description) from the prose. **Never treat the first word of a - sentence as the positional `[name]`** — `"disable the payments team"` names team `payments`, not - `disable`; `detect-mode.sh`'s literal first-word parse is only correct for a bare `mode [name]` - shape, see Error Handling below for the prose case. +### C4: Roster Finalization -Then print this block ONCE, before the first action (`## Universal Prelude` Step 0.4): - -``` -PLAN — brewcode:teams-setup -INPUT: -MODE: -SCOPE: -DO: <2-5 imperative bullets> -RESULT: -``` - -Labels are literal; values follow the conversation language. `status` still prints it — asks nothing. - ---- - -## Phase 1: Parse Arguments - -**EXECUTE** using shell: -```bash -bash "/scripts/detect-mode.sh" "" && echo "OK" || echo "FAILED" -``` - -Output: `MODE:`, `TEAM_NAME:`, `PROMPT:` (optional), plus the artifact-metadata scalars -`PLUGIN_VERSION:`, `CONTENT_VERSION:`, `GENERATED_BY:`, `LAST_UPDATED:`. Store all of them. - -> **Artifact metadata — every file this skill writes.** `team.md` and every generated domain agent carry -> `version` = `PLUGIN_VERSION:`, `generated_by` = `GENERATED_BY:` (`brewcode:teams-setup`), -> `last_updated` = `LAST_UPDATED:`, and `doc_type: llm` on the agents. `team.md`'s header table ALSO -> carries `content_version` = `CONTENT_VERSION:`, right after `Version` — self-located by -> `detect-mode.sh` from THIS skill's own `SKILL.md` line-1 `brewcode-meta:` marker (the one -> `bump-version.sh` stamps at release), never copied from `PLUGIN_VERSION:` and never invented. -> Take the values from the output above — never hardcode a version, never call `date` a second time -> with a different format, and never stamp a "template version": the plugin version replaces it. -> `.codex/agents/intent-guard.toml` is the ONE exception: `generate.sh emit-agent` stamps it with -> `generated_by: brewcode:superreview-setup`, and teams never touches those keys. - -`MODE` is one of the canonical seven, in this order: `status | install | upgrade | enable | disable | -uninstall | purge`. On any `ERROR:` line: report it verbatim and **STOP**. Never guess a mode, and -never treat a canonical verb as a team name — `install enable` creates a team NAMED `enable`, so the -verb always comes first and the optional `[name]` positional after it. - -> **How a team is enabled or disabled.** Codex discovers a project agent only through -> `.codex/agents/.toml`. `disable` renames each member to `.toml.disabled`; `enable` renames -> it back. The file body, `team.md`, `trace.jsonl`, `trace-archive.jsonl` and the cursor are untouched -> either way, so the toggle is fully reversible and loses no configuration and no history. It is NOT -> an uninstall: nothing is deleted. `intent-guard` is never parked — it is shared with -> `$brewcode:superreview-setup`, exactly as in UNINSTALL and PURGE. - ---- - -## Universal Prelude (every mode) - -### Step 0: Init + Validate + Confirm - -1. Output: `Mode: {MODE}, Team: {TEAM_NAME}` - -2. Load environment: - -| Action | Command / Path | -|--------|----------------| -| Read agent template | `/references/agent-template.md` | -| Read framework templates | `/references/framework-files.md` | -| Check team dir | `.codex/teams/{TEAM_NAME}/` -- exists? | -| Check existing agents | `.codex/agents/` -- list all | -| If team.md exists | Read, show current roster | -| If trace.jsonl exists | Show entry counts via `trace-ops.sh read` | - -3. If team exists, verify: - ```bash - bash "/scripts/verify-team.sh" "TEAM_NAME_HERE" && echo "PASS" || echo "FAIL" - ``` - -4. Print the **PLAN** block (`## Prompt contract` above) — once, before step 5's confirmation and - before any mutation. `status` prints it too, then skips straight to its report — no request_user_input. - -5. Mutating modes only — **ASK** using request_user_input: "PLAN above. Continue?" - Options: "Yes, continue" | "No, I want changes" | "Cancel" - - "changes" -> request_user_input for details, revise the PLAN and reprint it - - "Cancel" -> **STOP** - ---- - -## Delegation (applies to EVERY sub-agent task spawn in this skill) - -A big task handed to one agent = an agent gone for an hour: you cannot observe it, cannot correct -it, and it usually drifts off-target. One subagent = ONE bounded unit — one deliverable -(here: ONE agent file), ~<=5 files, ~<=10 steps. Bigger MUST be split into N tasks, all spawned -in ONE message. That is why agents are created one-per-spawn and reviews are fanned out. - -Every spawn prompt MUST carry: - -| Field | Content | -|-------|---------| -| GOAL | the overall task and why it exists — the point beyond the file edit | -| ROLE | what this agent owns; what it must NOT touch | -| SCOPE | exact paths/commands in bounds + explicit out-of-bounds | -| CONTEXT | what is already done, by whom, what runs in parallel — trimmed to what THIS agent needs | -| CONSUMER | who or what uses the result next, and the shape it must fit | -| DONE | acceptance criteria + the exact report shape you want back | - -A bare one-line task is never enough. See C8 for the canonical spawn shape. -Every code/test brief MUST make the agent find the closest well-built counterpart in the repo and follow its principles - IN ADDITION to conventions/rules/docs, never instead. - ---- - -## Mode: INSTALL (9 phases) - -### C1: Project Analysis - -Spawn 3-5 Explore agents in ONE message via sub-agent collaboration tools: - -| # | Focus | -|---|-------| -| 1 | Code structure: modules, packages, domains, architectural layers | -| 2 | Existing agents (`.codex/agents/`, `brewcode/agents/`, `~/.codex/agents/`) + Codex infrastructure | -| 3 | Tech stack: build files, frameworks, dependencies, languages | -| 4 | CI/CD, testing, deploy, infrastructure | -| 5 (optional) | Domain boundaries: business logic, API, data layer, UI | - -All via `Codex delegation brief (task_role="Explore")`. Consolidate into single analysis document. - -**Also harvest the intent-guard facts** (agent #1 and #4 cover most of these; add explicit asks to their prompts). -These fill the placeholders of the shared `intent-guard.md.template` in C3 — an unharvested fact must be recorded -as `none` / `not present in this project`, never invented: - -| Fact | Fills | Where to look | -|------|-------|---------------| -| Project name | `{PROJECT_NAME}` | repo dir, root `AGENTS.md`, `package.json`/build file | -| Where original requirements live (tracker, issues, Slack, "chat only") | `{TRACKER_LABEL}` | `AGENTS.md`, `.github/`, issue templates, CI links | -| Spec / design-doc paths or globs | `{SPEC_LOCATION}` | `.codex/specs/**`, `docs/`, `adr/` | -| Plan / task-board / task-graph paths | `{PLAN_LOCATION}` | `.codex/features/**`, `TASKS.md`, board files | -| Policy paths: root + nested `AGENTS.md`, rules, conventions | `{POLICY_LOCATION}` | `AGENTS.md`, `.codex/rules/**` | -| Planned scale / user count, testing policy, dependency policy, file-layout policy, architecture stance | `{PROJECT_INVARIANTS_TABLE}` | `AGENTS.md`, rules, test dirs, manifests, module layout | -| 3-6 plausible drift instances in this repo's vocabulary | `{DRIFT_EXAMPLES_TABLE}` | derived from the invariants above | -| Cheap evidence commands (diffstat, manifest diff, test-file count, new-file list) for this stack | `{EVIDENCE_COMMANDS_BASH}` | build/test tooling found by agent #3 and #4 | - -### C2: Team Proposal (interactive) - -Based on analysis + PROMPT (if provided), propose 3 variants via request_user_input. - -**`intent-guard` is in EVERY team, always, and is NOT one of the counted slots.** It is a review-only -anti-drift check (asked-vs-delivered), not a domain agent, so the 5 / 10-12 / 15-20 counts describe -DOMAIN agents only. Show it as a fixed row in every variant table, never as an option the user picks -and never as something the user can drop: - -``` -Fixed member (every variant, not counted): -| Agent | Domain | Mission | -| intent-guard | -- (review-only) | Compares what was ASKED vs what was DELIVERED; explicit invocation only | - -Minimal (5 domain agents + intent-guard): -| Agent | Domain | Mission | - -Balanced (10-12 domain agents + intent-guard) -- Recommended: -| Agent | Domain | Mission | - -Maximum (15-20 domain agents + intent-guard): -| Agent | Domain | Mission | -``` - -Options: "Minimal (5)" | "Balanced (recommended)" | "Maximum (15-20)" | "Custom -- I'll specify" - -If "Custom" -- second request_user_input for free input; intent-guard stays regardless of what the user -specifies. Final confirmation of agent list before proceeding. - -> If `.codex/agents/intent-guard.toml` already exists (e.g. `$brewcode:superreview-setup` created it), -> label the fixed row `reuse (already present)` — C3-IG's `emit-agent` call will report `REUSE` and -> leave the file untouched. - -**Agent names are globally unique across teams — REJECT a name another team already owns.** Two teams -listing the same agent share one file: one team's `upgrade` rewrites the other's member, and the other's -`uninstall`/`purge` is then blocked by the ownership check (`cleanup-flow.md` Step 3 step 0c), leaving -an undeletable roster row. Before showing the variants, run from the project root for every proposed name -(`intent-guard` excluded — it is shared BY DESIGN and exempt from this check): - -```bash -for a in {PROPOSED_NAMES}; do - o=$(bash "/scripts/agent-owners.sh" "$a") && echo "TAKEN $a <- $o" -done; echo "OK" -``` - -- exit 0 (any owner printed) -> the name is **TAKEN**. Drop it from the variant and propose a distinct - one — a domain-qualified rename such as `{name}-{TEAM_NAME}` or a different domain word. Never reuse, - never "join" the other team's agent. -- exit 2 (no owner) -> free, use it. -- exit 1 (refusal, reason on stderr) -> treat the name as TAKEN until the reason is fixed; report the - stderr line, do not guess. - -Say which names were renamed and why in the confirmation before C3. - -### C2.5: Model Selection (request_user_input) - -"Default model for domain agents: high-reasoning model (most reliable)." - -| Model | Best for | Cost | -|-------|----------|------| -| high-reasoning model | Complex domains, architecture, critical logic | High | -| balanced model | Standard domains, CRUD, testing, utilities | Medium | -| fast model | Simple utility agents, formatting, validation | Low | - -Options: "high-reasoning model (recommended)" | "balanced model" | "fast model" | "Mixed -- I'll choose per agent" - -If "Mixed" -- ask model per agent in C3. Store as `DEFAULT_MODEL` (default: high-reasoning model). - -> `DEFAULT_MODEL` applies to DOMAIN agents only. `intent-guard` keeps the `model: balanced model` its shared -> template ships — do not ask about it, do not override it. - -### C2.6: Shared Contract Bootstrap (before agent discovery) - -This gate MUST finish before any team-owned `.codex/agents/{name}.toml` is written. An interrupted install -may leave a partial roster, but no discoverable compact profile may ever point at a missing shared contract. - -1. Create `.codex/teams/{TEAM_NAME}/`. -2. Read `/references/framework-files.md`; write `team.md` with substituted metadata, - the byte-faithful `## Shared Agent Contract`, the `## Agents` header, and only the fixed - `intent-guard` row. Do not add domain-agent rows yet; C4 finalizes the successfully created roster. -3. Create empty `trace.jsonl`; copy the project-local `trace-ops.sh` and make it executable. -4. Gate before C3: `team.md` exists, contains `## Shared Agent Contract`, the project-local tracer path, - and `A task traced \`took\` ends with exactly one terminal track: \`completed\` or \`failed\`.` - -**STOP on any failure. Do not spawn or write an agent.** Resume by repairing this bootstrap first; never -strip shared rules from a profile until its target `team.md` passes the gate. - -### C3: Agent Creation (agent-creator x N) - -1. Read `/references/agent-template.md` -1a. Confirm C2.6 completed. `.codex/teams/{TEAM_NAME}/team.md` is already written and gated; if missing - or incomplete, **STOP before the first spawn** and repair the bootstrap. -1b. **Re-run the C2 uniqueness check on the FINAL confirmed roster, immediately before the first spawn** — - the user may have typed names in the "Custom" branch that never passed it. Same script, same exit-code - reading. Any `TAKEN` name -> **do not spawn**; go back and rename it with the user first. Also refuse a - name whose `.codex/agents/{name}.toml.disabled` exists with no live file: that is another install's parked - agent, and writing the live path recreates the dual-copy state both `enable` and `disable` refuse. -2. For each agent, spawn `Codex delegation brief (task_role="brewcode:agent-creator")` — ONE agent file per spawn, never a whole team. Prompt carries GOAL (build this one `{TEAM_NAME}` roster member; siblings own other domains), ROLE (owns `.codex/agents/{name}.toml` only), SCOPE (that file; other agents, `team.md`, project source out), CONTEXT (settled mission/domain/project analysis, selected model, 3-4 sibling names; no trigger/domain overlap; the gated shared contract already exists), CONSUMER (C4 adds the final roster row; C5 reviews; the roster routes work), DONE: - - `description` <=100 chars (optimal ~80), single-line role + 2-3 triggers, no ``; - - body <=3200 bytes (~800 est-tokens), with exactly these ordered headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`; - - `## Must-load references` names `.codex/teams/{TEAM_NAME}/team.md` first; - - profile contains only domain-unique facts. `sub-agent task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, `Scope Fit`, shared routing, and shared output rules stay only in `team.md`; - - placeholders substituted; return file path + description line. - - Every spawn prompt MUST also carry the template path and the four metadata lines, resolved — the - subagent cannot see Phase 1's output, so **replace `{PLUGIN_VERSION}` and `{LAST_UPDATED}` below with - the literal values from the Phase 1 `PLUGIN_VERSION:` / `LAST_UPDATED:` lines before you send the - prompt.** A token that reaches the subagent ships verbatim into the agent file, and `setup-status` - then reports that agent `partial` forever. Those two spellings are the only sanctioned ones — never an - angle form, never a double brace: - - ``` - CONTEXT (cont.): structure from /references/agent-template.md — read it first. - DONE (cont.): the frontmatter ends with exactly these four keys, in this order, AFTER the agent's - own keys (name, description, model, tools — leave those byte-untouched, `tools` above all): - doc_type: llm - version: "{PLUGIN_VERSION}" - generated_by: "brewcode:teams-setup" - last_updated: "{LAST_UPDATED}" - ``` - - `verify-team.sh` re-reads every generated agent's frontmatter and FAILS on a wrong order, a missing - key or wrong quoting, so a prompt that shipped a token does not pass C4. -3. Batch 3-4 agents in parallel per message -4. After each batch, optimize without changing the six-heading contract: - ``` - Codex delegation brief (task_role="brewtools:text-optimizer", message="Light-optimize .codex/agents/{agent-name}.toml; preserve its exact six ordered headings, team.md reference, names/numbers/negations/scope. Output metrics.") - ``` - > `brewtools` not installed (`text-optimizer` unavailable) — skip the pass, agents stay as written. - > **Never run the optimizer on `.codex/agents/intent-guard.toml`.** Its frontmatter `description` - > is deliberately short and review-only; an optimizer pass may reword, lengthen or reflow it into - > a normal domain-agent description, which would make it compete for auto-activation. Excluded. - -#### C3-IG: intent-guard (always, exactly once) - -`.codex/agents/intent-guard.toml` has exactly ONE writer: `generate.sh emit-agent`, shared with -`$brewcode:superreview-setup`. Never author this file from the template yourself, and never spawn an agent -to author it — that would fork the file into two divergent pipelines. `agent-creator` appears in this -phase only as a post-processor that replaces three seeded BLOCKs. - -**Step 1 — emit.** Run from the project root, exporting the C1 facts. Unharvested fact -> `none` / -`not present in this project`; never invent a tracker, a path or a ticket id. - -**EXECUTE** using shell (substitute the C1 values first): -```bash -PROJECT_NAME="PROJECT_NAME_HERE" \ -TRACKER_LABEL="TRACKER_LABEL_HERE" \ -SPEC_LOCATION="SPEC_LOCATION_HERE" \ -PLAN_LOCATION="PLAN_LOCATION_HERE" \ -POLICY_LOCATION="POLICY_LOCATION_HERE" \ -bash "/../superreview-setup/scripts/generate.sh" emit-agent && echo "OK" || echo "FAILED" -``` - -It creates-or-reuses ONLY `.codex/agents/intent-guard.toml` (superreview does not need to have run) and -prints exactly one `INTENT_GUARD:` line on STDOUT: `INTENT_GUARD: CREATED `, -`INTENT_GUARD: REUSE ` or `INTENT_GUARD: MIGRATED ` (a pre-standard file of ours, restamped -in place — metadata only, tailored body preserved). Diagnostics (e.g. "recreating from template") go to -stderr and never add a second status line. -> **STOP if FAILED** -- report the script output; do not fall back to hand-authoring the file. - -**Step 2 — sanity-check the emitted file** (a pre-existing file may be empty, truncated or -placeholder-laden; `-f` alone proves nothing). This runs on the REUSE path too, where `$f` is somebody's -already-adapted agent whose evidence block legitimately holds shell expansions — so strip `${VAR}` FIRST -and match bare tokens on what is left. Without the strip a `${BASE}` scores as an unresolved placeholder, -and this step's remedy is `rm -f`: it would delete a tailored file. -```bash -f=.codex/agents/intent-guard.toml -[ -s "$f" ] && grep -q '^name: intent-guard' "$f" \ - && ! sed 's/\${[A-Z_][A-Z_]*}//g' "$f" | grep -q '{[A-Z_]\{2,\}}' && echo "SANE" || echo "CORRUPT" -grep -qF '` line. Key each Edit on that marker: `old_string` = the - seeded block PLUS its marker line, `new_string` = your project-specific replacement - WITHOUT any marker. A surviving marker is what makes a skipped adaptation detectable — - `generate.sh validate` reports any file that still carries one as UNTAILORED. - HARD out of bounds — a single byte changed here is a failed task: - - the frontmatter (name, description, model: balanced model, tools, color, maxTurns). The - description is <= 100 chars, review-only, explicitly-invoked BY DESIGN; do NOT rewrite, - lengthen or 'improve' it. This overrides any default description-authoring habit. - - the file header, every heading, and every other section of the file - - the shared template, other agent files, team.md, trace.jsonl, project source - CONTEXT: C1 project analysis is settled — use these facts, invent nothing: - PROJECT_INVARIANTS_TABLE = from C1: planned scale/user count, testing policy, dependency - policy, file-layout policy, architecture stance - DRIFT_EXAMPLES_TABLE = 3-6 drift instances in THIS repo's vocabulary - EVIDENCE_COMMANDS_BASH = cheap evidence commands for THIS stack (diffstat, manifest diff, - test-file count, new-file list) - Unknown fact -> write 'none' / 'not present in this project'. Never fabricate a tracker, - a path or a ticket id. Do not add a Scope Fit block, sub-agent task Acceptance Protocol, trace - instructions or a Domain Instructions section — this agent has no code domain. - CONSUMER: $brewcode:superreview-setup spawns this same file by name during review, and C4 adds its row to - .codex/teams/{TEAM_NAME}/team.md — the file name and agent name stay exactly 'intent-guard'. - DONE: three BLOCKs project-specific, all three SEEDED-DEFAULT markers gone (consumed by the - replacements), everything else byte-identical to what emit-agent wrote. - Report: path + the three BLOCK contents + confirmation that frontmatter and header are untouched. -") -``` - -**Step 4 — verify.** FOUR counts, one grep per line, in this order. Each pattern matches the ARTIFACT, -never prose ABOUT it: the emitted agent legitimately keeps a tail comment that NAMES the stripped -`TEMPLATE HEADER`, so an unanchored `grep -c 'TEMPLATE HEADER'` reports `1` on every healthy file and -turns this gate into an unpassable loop. Match the header's opening line, not the phrase. Same reason the -placeholder count strips `${VAR}` first: `{PROJECT_NAME}` is a token, `` in an adapted -evidence command is not, and only a strip-then-match tells them apart — a `$`-guard inside the pattern -mis-handles adjacent tokens. `|| true` on every line: zero matches is the happy path for three of the four -counts (repo rule avoid#7), and a count must still PRINT under `set -o pipefail`, especially when it is the -one going red. - -```bash -f=.codex/agents/intent-guard.toml -sed 's/\${[A-Z_][A-Z_]*}//g' "$f" | grep -c '{[A-Z_]\{2,\}}' || true # 0 — unresolved placeholder -grep -c '^'; -const LEGACY_STAMP = ''; - +const EMIT = join(HERE, '..', '..', 'superreview-setup', 'scripts', 'emit-intent-guard.sh'); +const TEMPLATE = join(HERE, '..', '..', 'superreview-setup', 'references', 'intent-guard.toml.template'); let passed = 0; let failed = 0; const results = []; -function check(name, actual, expected, message) { +function check(name, actual, expected, description) { if (actual === expected) { - passed++; - results.push(` PASS ${name} (${message})`); + passed += 1; + results.push(' PASS ' + name + ' (' + description + ')'); } else { - failed++; - results.push( - ` FAIL ${name} (${message} | actual=${JSON.stringify(actual)} expected=${JSON.stringify(expected)})`, - ); + failed += 1; + results.push(' FAIL ' + name + ' (' + description + ' | actual=' + JSON.stringify(actual) + ' expected=' + JSON.stringify(expected) + ')'); } } -/** A project root with an optional pre-existing intent-guard.toml. */ -function makeProject(label, body) { - const root = join(BASE, label); - mkdirSync(join(root, '.codex', 'agents'), { recursive: true }); - if (body !== null) writeFileSync(join(root, IG_REL), body); - return root; +function run(root) { + const result = spawnSync('bash', [EMIT, root], { encoding: 'utf8', timeout: 30000 }); + return { status: result.status, output: (result.stdout || '') + (result.stderr || '') }; } -function emitAgent(root) { - const r = spawnSync('bash', [GENERATE, 'emit-agent'], { cwd: root, encoding: 'utf8', timeout: 30000 }); - return { stdout: (r.stdout || '').trim(), stderr: r.stderr || '', status: r.status }; +function parse(path) { + const result = spawnSync('python3', ['-c', 'import json,pathlib,sys,tomllib; print(json.dumps(tomllib.loads(pathlib.Path(sys.argv[1]).read_text()), sort_keys=True))', path], { encoding: 'utf8' }); + return { status: result.status, data: result.status === 0 ? JSON.parse(result.stdout) : null }; } -const agentNames = (root) => readdirSync(join(root, '.codex', 'agents')).sort(); -const backups = (root) => - agentNames(root).filter((n) => /^intent-guard\.toml\.bak-[0-9]{8}-[0-9]{6}$/.test(n)); -const read = (root, rel) => readFileSync(join(root, rel), 'utf8'); +const template = parse(TEMPLATE); +check('template.parse', template.status, 0, 'shared native template is valid TOML'); +check('template.keys', Object.keys(template.data).sort().join(','), 'description,developer_instructions,name', 'template has exactly three keys'); +check('template.name', template.data.name, 'intent-guard', 'template name is fixed'); -const FOREIGN_WITH_TOKEN = [ - '---', - 'name: intent-guard', - 'description: our own hand-written drift check', - '---', - '', - '# intent-guard', - '', - 'Use {REQUEST_ID} to correlate the review with the ticket.', - '', -].join('\n'); - -// ──────────────────────────────────────────────────────────────────────────── -// B1 — BCOP09: an UNSTAMPED file carrying a {TOKEN} is the project's own agent. -// REUSE, byte-identical, no backup, tokens reported as a conflict. -// ──────────────────────────────────────────────────────────────────────────── { - const root = makeProject('b1', FOREIGN_WITH_TOKEN); - const r = emitAgent(root); - - check('b1.status', r.status, 0, 'emit-agent succeeds on a foreign agent'); - check('b1.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'exactly one status line, and it is REUSE'); - check('b1.bytes', read(root, IG_REL), FOREIGN_WITH_TOKEN, 'the hand-written file is byte-identical'); - check('b1.backupCount', backups(root).length, 0, 'nothing was backed up because nothing was rewritten'); - check('b1.tree', agentNames(root).join(','), 'intent-guard.toml', 'no extra file was created'); - check( - 'b1.conflictReported', - r.stderr.includes('{REQUEST_ID}'), - true, - 'the token is reported on stderr as a conflict', - ); + const root = mkdtempSync(join(tmpdir(), 'native-intent-create-')); + const result = run(root); + const target = join(root, '.codex', 'agents', 'intent-guard.toml'); + check('create.exit', result.status, 0, 'first run succeeds'); + check('create.verdict', result.output.trim(), 'INTENT_GUARD: CREATED .codex/agents/intent-guard.toml', 'first run reports creation'); + check('create.bytes', readFileSync(target, 'utf8'), readFileSync(TEMPLATE, 'utf8'), 'created file equals shared authority byte-for-byte'); + check('create.parse', parse(target).status, 0, 'created file remains structurally valid TOML'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B2 — an unstamped file WITHOUT `name: intent-guard` frontmatter is still -// foreign: unrunnable by our rules, but not ours to overwrite. -// ──────────────────────────────────────────────────────────────────────────── { - const body = '# somebody else\n\nnotes about {TICKET_ID}\n'; - const root = makeProject('b2', body); - const r = emitAgent(root); - - check('b2.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'REUSE, not RECREATE'); - check('b2.bytes', read(root, IG_REL), body, 'a foreign file without our frontmatter survives byte-identical'); - check('b2.backupCount', backups(root).length, 0, 'no backup, because no write'); + const root = mkdtempSync(join(tmpdir(), 'native-intent-reuse-')); + const agents = join(root, '.codex', 'agents'); + mkdirSync(agents, { recursive: true }); + const target = join(agents, 'intent-guard.toml'); + const foreign = 'name = "intent-guard"\ndescription = "Foreign review-only agent."\ndeveloper_instructions = "Review only; preserve these bytes."\n'; + writeFileSync(target, foreign); + const result = run(root); + check('reuse.exit', result.status, 0, 'valid existing native agent is reused'); + check('reuse.verdict', result.output.trim(), 'INTENT_GUARD: REUSE .codex/agents/intent-guard.toml', 'reuse is explicit'); + check('reuse.bytes', readFileSync(target, 'utf8'), foreign, 'reuse preserves foreign bytes'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B3 — control: the same foreign file with no token at all also REUSEs. This is -// the pre-fix behaviour and must not change. -// ──────────────────────────────────────────────────────────────────────────── -{ - const body = FOREIGN_WITH_TOKEN.replace('{REQUEST_ID}', 'the ticket id'); - const root = makeProject('b3', body); - const r = emitAgent(root); - - check('b3.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'token-free foreign file reuses too'); - check('b3.bytes', read(root, IG_REL), body, 'byte-identical'); - check('b3.stderrEmpty', r.stderr, '', 'no conflict to report, so stderr stays silent'); +for (const [name, body, reason] of [ + ['renamedMarkdown', '---\nname: intent-guard\n---\n', 'invalid TOML'], + ['extraKey', 'name = "intent-guard"\ndescription = "Review."\ndeveloper_instructions = "Review."\nmodel = "legacy"\n', 'keys must be exactly'], +]) { + const root = mkdtempSync(join(tmpdir(), 'native-intent-' + name + '-')); + const agents = join(root, '.codex', 'agents'); + mkdirSync(agents, { recursive: true }); + const target = join(agents, 'intent-guard.toml'); + writeFileSync(target, body); + const result = run(root); + check(name + '.exit', result.status, 1, 'invalid existing artifact fails closed'); + check(name + '.reason', result.output.includes(reason), true, 'failure names the structural defect'); + check(name + '.bytes', readFileSync(target, 'utf8'), body, 'failure never overwrites existing bytes'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B4 — OUR OWN stamped file with an unresolved token is BROKEN: recreated, but -// only after its bytes are copied to .bak-. -// ──────────────────────────────────────────────────────────────────────────── -{ - const body = ['---', 'name: intent-guard', '---', '', 'half-substituted {PROJECT_NAME}', '', STAMP, ''].join('\n'); - const root = makeProject('b4', body); - const r = emitAgent(root); - - const bak = backups(root); - check('b4.stdout', r.stdout, `INTENT_GUARD: CREATED ${IG_REL}`, 'our own broken file is recreated'); - check('b4.backupCount', bak.length, 1, 'exactly one backup was written'); - check( - 'b4.backupBytes', - // concatenation, not indexing: zero backups yields '' and fails the check instead of throwing - bak.map((n) => read(root, join('.codex', 'agents', n))).join(''), - body, - 'the backup holds the original bytes verbatim', - ); - check( - 'b4.recreated', - read(root, IG_REL).includes('/)?.[0]; + if (!marker) throw new Error('teams-setup source is missing brewcode-meta marker'); + return [ + marker, + '', + '## Native authority', + '', + 'Manage persistent project teams under `.codex/teams/{TEAM_NAME}/` and domain agents under `.codex/agents/`. Agent files are real TOML parsed with Python `tomllib`; never rename Markdown/YAML agents to `.toml`. Each team agent has exactly three top-level string keys: `name`, `description`, `developer_instructions`.', + '', + 'Resolve one mode: `status`, `install`, `upgrade`, `enable`, `disable`, `uninstall`, or `purge`. Read applicable `AGENTS.md`, inspect existing teams and agents, preserve unrelated files, and use only scripts/references shipped beside this skill. Never edit installed caches.', + '', + '### C2.6: Shared Contract Bootstrap', + '', + 'Bootstrap happens before any team-owned `.codex/agents/{name}.toml` is written: instantiate the fenced template from `references/framework-files.md` and write `team.md` at `.codex/teams/{TEAM_NAME}/team.md` with metadata, `## Shared Agent Contract`, explicit `Intent guard` policy, and zero domain rows. Do not add domain-agent rows yet. Copy the project-local tracer, initialize trace storage, substitute every placeholder, then run `scripts/verify-team.sh`. **STOP on any failure. Do not spawn or write an agent.**', + '', + '### C3: Agent Creation', + '', + 'Create each approved domain `.codex/agents/{name}.toml` from `references/agent-template.md`. Parse it structurally with `tomllib` and require only `name`, `description`, and `developer_instructions`. The `developer_instructions` value uses exactly these ordered headings and no others: `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique invariants`, `Unique verification`. Its first must-load item is exactly `.codex/teams/{TEAM_NAME}/team.md`, occurring once. Enforce <=3200 UTF-8 bytes and `ceil(chars/4) <=800` over `developer_instructions` itself.', + '', + '`intent-guard` is exempt from the six-heading domain profile. Under `required`, run `/skills/superreview-setup/scripts/emit-intent-guard.sh `; that sole shared writer create-only copies its native authority, structurally validates it, and never overwrites an existing file. Never ask agent-creator to write it. Under `legacy-absent`, create no row and no role.', + '', + '### C4: Roster Finalization', + '', + 'After all intended agents validate, write the final roster. Declared `Agents` equals the number of unique domain rows; duplicate names fail. a new team defaults to `required`. Policy `required` has exactly one `intent-guard` row with fixed cells `--`, `Anti-drift check: what was ASKED vs what was DELIVERED`, `active`, team `Last update`, `review-only`, team `Version`. Policy `legacy-absent` has zero rows. the complete written `team.md` (metadata + shared contract + every row) MUST be <=2800 characters; `ceil(chars/4) <=700` estimated tokens. Measure the full substituted file, not the empty template.', + '', + '### C8: Fix', + '', + 'Repair only failed owned artifacts. Domain agents come from `/references/agent-template.md`; repair the shared contract before an agent rewrite. Preserve foreign agents and unrelated work. Re-run structural TOML, roster, policy, size, and shared-contract checks.', + '', + '### C9: Re-verify', + '', + 'Run `scripts/verify-team.sh {TEAM_NAME}`, both test runners, and compatibility validation. Hard-gate `developer_instructions` only: <=3200 bytes and `ceil(chars/4) <=800`; `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique invariants`, `Unique verification` in order with no other headings. `intent-guard` is exempt from this six-heading gate, not structural TOML validation.', + '', + '> To skip review pipeline is not an acceptance path; unresolved checks remain failures.', + '', + '### U1b: Shared Contract Migration Gate', + '', + 'On upgrade, insert the canonical block before `## Agents` and validate it before touching agents. Record an existing intent-guard roster row -> `required`; no row -> `legacy-absent`. Never synthesize the row on the latter path. Legacy agent bodies remain byte-identical during this gate. **No agent may be tuned, regenerated, stripped, or reformatted until the shared contract passes.** absence migrates', + 'to `legacy-absent`; `legacy-absent` forbids that row and MUST NOT add the role during upgrade.', + '', + '### U2: Analyze Performance', + '', + 'Use trace evidence only to decide whether a domain profile needs role-specific adjustment. Never duplicate the shared contract.', + '', + '### U4: Apply Changes', + '', + 'Convert every touched Codex domain agent to the exact three-key TOML contract and six-heading `developer_instructions` shape. Parse before replacement, write atomically, parse again, and preserve untouched agents byte-identical.', + '', + 'For `enable` and `disable`, use `scripts/toggle-team.sh`; it parks/restores domain `.toml` files byte-identically and never parks `intent-guard`. For `uninstall` and `purge`, follow the shipped cleanup flow and explicit confirmation gates. Every mode ends with `verify-team.sh` and reports exact paths, counts, and failures.', + ].join('\n'); +} + // Pip pins parsed out of a skill's check_deps.sh `pip_spec` case arms. // The Codex variant of that script cannot be a verbatim copy (the source uses floating // `brew install` and an expanded `pip install "${specs[@]}"`, both rejected by @@ -633,6 +934,13 @@ function sourcePipPins(sourceDir, required) { } function generateSpecialResources(plugin, skill, sourceDir, targetDir) { + if (plugin === 'brewcode' && skill === 'superreview-setup') { + writeFile(path.join(targetDir, 'references', 'intent-guard.toml.template'), `name = "intent-guard" +description = "Review-only anti-drift check comparing requested and delivered scope." +developer_instructions = "Review only. Compare what was requested with what was delivered, report concrete drift with file:line evidence, and never implement or mutate project files." +`); + } + if (plugin === 'brewcode' && skill === 'rules') { writeFile(path.join(targetDir, 'README.md'), `# Rules for Codex @@ -1003,12 +1311,99 @@ This template never applies to \`intent-guard\`. Its sole writer remains the sha (whole, stem, escape) => (KEEP_MD.test(stem) ? whole : `${stem}${escape}.toml`) )); } + writeFile(path.join(targetDir, '..', 'superreview-setup', 'scripts', 'emit-intent-guard.sh'), [ + '#!/usr/bin/env bash', + 'set -euo pipefail', + 'root="${1:-}"', + 'test -n "$root" || { echo "usage: emit-intent-guard.sh " >&2; exit 2; }', + 'SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"', + 'template="$SCRIPT_DIR/../references/intent-guard.toml.template"', + 'agents="$root/.codex/agents"', + 'target="$agents/intent-guard.toml"', + '', + 'validate() {', + ' python3 - "$1" <<\'PY\'', + 'import pathlib, sys, tomllib', + 'path = pathlib.Path(sys.argv[1])', + 'try:', + ' data = tomllib.loads(path.read_text(encoding="utf-8"))', + 'except (OSError, UnicodeError, tomllib.TOMLDecodeError) as exc:', + ' print(f"invalid TOML: {exc}", file=sys.stderr)', + ' raise SystemExit(1)', + 'required = {"name", "description", "developer_instructions"}', + 'if set(data) != required:', + ' print("TOML keys must be exactly name, description, developer_instructions", file=sys.stderr)', + ' raise SystemExit(1)', + 'if any(type(data[key]) is not str for key in required) or data["name"] != "intent-guard":', + ' print("intent-guard native fields must be strings and name must be fixed", file=sys.stderr)', + ' raise SystemExit(1)', + 'PY', + '}', + '', + 'validate "$template"', + 'if [ -f "$target" ]; then', + ' validate "$target"', + ' echo "INTENT_GUARD: REUSE .codex/agents/intent-guard.toml"', + ' exit 0', + 'fi', + 'mkdir -p "$agents"', + 'tmp="$(mktemp "$agents/.intent-guard.XXXXXX")"', + 'trap \'rm -f "$tmp"\' EXIT HUP INT TERM', + 'cp "$template" "$tmp"', + 'validate "$tmp"', + 'mv "$tmp" "$target"', + 'trap - EXIT HUP INT TERM', + 'echo "INTENT_GUARD: CREATED .codex/agents/intent-guard.toml"', + ].join('\n'), 0o755); + writeFile(path.join(targetDir, 'tests', 'suite-intent-guard.mjs'), nativeIntentGuardSuite(), 0o755); + const nativeFramework = fs.readFileSync(path.join(targetDir, 'references', 'framework-files.md'), 'utf8'); + const nativeTeamMatch = nativeFramework.match(/## team\.md\n\n```markdown\n([\s\S]*?)\n```/); + if (!nativeTeamMatch) throw new Error('native teams framework is missing fenced team.md template'); + for (const name of ['suite-lifecycle.mjs', 'suite-parked-conflict.mjs']) { + const file = path.join(targetDir, 'tests', name); + let value = rewriteNativeLifecycleSuite(fs.readFileSync(file, 'utf8'), nativeTeamMatch[1]); + if (name === 'suite-lifecycle.mjs') value = value.replace('nativeTeam(root, rows)', 'nativeTeam(root, rows, separator)'); + writeFile(file, value, 0o755); + } // The canonical Codex tree keeps its manifest at `.codex/package/plugin.json`, while the // installed distribution exposes `.codex-plugin/plugin.json`. Make the verifier runnable in // both layouts and keep its isolated regression suite anchored to the canonical manifest. const verifier = path.join(targetDir, 'scripts', 'verify-team.sh'); - writeFile(verifier, fs.readFileSync(verifier, 'utf8').replace( + let verifierText = fs.readFileSync(verifier, 'utf8'); + verifierText = replaceMarked( + verifierText, + '# BEGIN CLIENT AGENT VALIDATION', + '# END CLIENT AGENT VALIDATION', + nativeTeamAgentValidation() + ); + verifierText = replaceMarked( + verifierText, + '# BEGIN LIVE CLIENT AGENT CHECK', + '# END LIVE CLIENT AGENT CHECK', + `# BEGIN LIVE CLIENT AGENT CHECK + native_kind=domain + [ "$agent" = "intent-guard" ] && native_kind=review-only + set +e + native_out=$(check_native_agent ".codex/agents/\${agent}.toml" "$agent" "$native_kind") + native_rc=$? + set -e + if [ "$native_rc" -eq 0 ]; then + echo "OK" + if [ "$native_kind" = "domain" ] && [ "$shared_contract_present" -ne 1 ]; then + echo " CHECK: compact six-heading profile ... FAIL (shared team contract missing; interrupted install/unsafe migration)" + FAIL=1 + elif [ "$native_kind" = "domain" ]; then + echo " CHECK: structurally parsed six-heading developer_instructions ... OK" + fi + else + echo "FAIL" + printf '%s\n' "$native_out" + FAIL=1 + fi + # END LIVE CLIENT AGENT CHECK` + ); + verifierText = verifierText.replace( 'PLUGIN_JSON="$SCRIPT_DIR/../../../.codex-plugin/plugin.json"', 'PLUGIN_JSON="$SCRIPT_DIR/../../../.codex-plugin/plugin.json"\n[ -f "$PLUGIN_JSON" ] || PLUGIN_JSON="$SCRIPT_DIR/../../../package/plugin.json"' ).replace( @@ -1020,7 +1415,8 @@ case "$PV" in *) printf 'ERROR:cannot resolve source plugin version (X.Y.Z) from %s\\n' "$SCRIPT_DIR/../SKILL.md"; exit 1 ;; esac TODAY=$(date +%F)` - ), 0o755); + ).replaceAll('frontmatter', 'TOML agent schema'); + writeFile(verifier, verifierText, 0o755); const detector = path.join(targetDir, 'scripts', 'detect-mode.sh'); writeFile(detector, fs.readFileSync(detector, 'utf8').replace( 'esac\n\n# content_version self-location:', @@ -1034,7 +1430,7 @@ esac # content_version self-location:` ), 0o755); const profileSuite = path.join(targetDir, 'tests', 'suite-agent-profile-contract.mjs'); - writeFile(profileSuite, fs.readFileSync(profileSuite, 'utf8') + let profileText = fs.readFileSync(profileSuite, 'utf8') .replace( "const canonicalSkillPath = join(repo, 'brewcode', 'skills', 'teams-setup', 'SKILL.md');", "const canonicalSkillPath = join(repo, 'brewcode', '.codex', 'skills', 'teams-setup', 'SKILL.md');" @@ -1058,11 +1454,54 @@ esac "const pluginVersion = (/brewcode-meta: version=([0-9]+\\.[0-9]+\\.[0-9]+)/.exec(canonicalSkill) || [])[1];" ) .replace('return `${canonicalTeam', 'return `${projectedTeam') - .replace('function agentFile({ body = representativeBody,', 'function agentFile({ body = runtimeRepresentativeBody,') + .replace('return instantiateTeamTemplate(canonicalTeam', 'return instantiateTeamTemplate(projectedTeam') .replace('const oversized = `${representativeBody}', 'const oversized = `${runtimeRepresentativeBody}') .replace('mutate(representativeBody)', 'mutate(runtimeRepresentativeBody)') .replaceAll('join(world, SOURCE_CLIENT_DIR', "join(world, '.codex'") - .replaceAll("'build-eng.md'", "'build-eng.toml'"), 0o755); + .replaceAll('`${name}.md`', '`${name}.toml`') + .replaceAll("'build-eng.md'", "'build-eng.toml'") + .replace( + "result.output.includes('ceiling is 3200 (~800 est-tokens), frontmatter excluded')", + "result.output.includes('ceilings are 3200 bytes and 800 ceil(chars/4) tokens')" + ) + .replace('an oversized body fails even with small frontmatter', + 'an oversized developer_instructions value fails') + .replace('body only (frontmatter excluded): <=3200 bytes', + '`developer_instructions` only: <=3200 bytes') + .replace('a fully legacy team remains runnable while upgrade is required', + 'a structurally parsed native agent without six headings fails') + .replace("check('verifier.legacyMigrationSafe.exit', result.status, 0,", + "check('verifier.legacyMigrationSafe.exit', result.status, 1,") + .replace( + "result.output.includes('has no Shared Agent Contract (legacy team)')\n && result.output.includes('legacy repeated/unknown profile shape')", + "result.output.includes('body headings must be exactly the six ordered teams-setup headings in developer_instructions')" + ); + profileText = replaceMarked( + profileText, + '// BEGIN RUNTIME AGENT FIXTURES', + '// END RUNTIME AGENT FIXTURES', + nativeTeamFixtureBlock() + ); + profileText = replaceMarked( + profileText, + '// BEGIN SOURCE FRONTMATTER BUDGET FIXTURE', + '// END SOURCE FRONTMATTER BUDGET FIXTURE', + nativeTeamSchemaFixtures() + ); + profileText = profileText.replace( + "console.log('suite-agent-profile-contract.mjs');", + `const nativeVerifier = readFileSync(verifierPath, 'utf8'); +check('codex.verifier.tomllib', nativeVerifier.includes('import tomllib'), true, + 'native verifier parses TOML structurally'); +check('codex.verifier.noYamlParser', nativeVerifier.includes('NR == 1 && $0 == "---"'), false, + 'native verifier has no YAML fence parser'); +check('codex.verifier.exactKeys', + nativeVerifier.includes('required = {"name", "description", "developer_instructions"}'), true, + 'native verifier pins the exact supported top-level schema'); + +console.log('suite-agent-profile-contract.mjs');` + ); + writeFile(profileSuite, profileText, 0o755); } if (plugin === 'brewtools' && (skill === 'deploy' || skill === 'ssh')) { @@ -1118,7 +1557,9 @@ function generateSkill(plugin, skill) { fs.mkdirSync(targetDir, { recursive: true }); if (special) { copyTransformedTree(sourceDir, targetDir); - const workflow = MANUAL_NATIVE_SKILLS.has(`${plugin}/${skill}`) ? '' : ` + const workflow = plugin === 'brewcode' && skill === 'teams-setup' + ? `\n${nativeTeamsWorkflow(body)}\n` + : MANUAL_NATIVE_SKILLS.has(`${plugin}/${skill}`) ? '' : ` ## Complete native workflow Follow every phase below. When a phase delegates work, use Codex collaboration with only \`task_name\` and \`message\`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use \`request_user_input\` for the documented user gates. Resolve \`\`, \`\`, \`\`, and \`\` before running commands. diff --git a/.codex/scripts/validate-compat.mjs b/.codex/scripts/validate-compat.mjs index 3080d3c..a89f9c4 100644 --- a/.codex/scripts/validate-compat.mjs +++ b/.codex/scripts/validate-compat.mjs @@ -17,7 +17,7 @@ const EXPECTED_SKILLS = { // supports in its source `argument-hint`; the Codex variant must document each one. const CANONICAL_MODES = ['status', 'install', 'upgrade', 'enable', 'disable', 'uninstall', 'purge']; const MANUAL_NATIVE_SKILLS = new Set([ - 'brewcode/convention', 'brewcode/rules', 'brewtools/manager-setup', 'brewtools/task-board-setup', + 'brewcode/convention', 'brewcode/rules', 'brewcode/teams-setup', 'brewtools/manager-setup', 'brewtools/task-board-setup', 'brewtools/think-short-setup' ]); const errors = []; diff --git a/brewcode/.codex/skills/superreview-setup/references/intent-guard.toml.template b/brewcode/.codex/skills/superreview-setup/references/intent-guard.toml.template new file mode 100644 index 0000000..47c62aa --- /dev/null +++ b/brewcode/.codex/skills/superreview-setup/references/intent-guard.toml.template @@ -0,0 +1,3 @@ +name = "intent-guard" +description = "Review-only anti-drift check comparing requested and delivered scope." +developer_instructions = "Review only. Compare what was requested with what was delivered, report concrete drift with file:line evidence, and never implement or mutate project files." diff --git a/brewcode/.codex/skills/superreview-setup/scripts/emit-intent-guard.sh b/brewcode/.codex/skills/superreview-setup/scripts/emit-intent-guard.sh new file mode 100755 index 0000000..2e89942 --- /dev/null +++ b/brewcode/.codex/skills/superreview-setup/scripts/emit-intent-guard.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail +root="${1:-}" +test -n "$root" || { echo "usage: emit-intent-guard.sh " >&2; exit 2; } +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +template="$SCRIPT_DIR/../references/intent-guard.toml.template" +agents="$root/.codex/agents" +target="$agents/intent-guard.toml" + +validate() { + python3 - "$1" <<'PY' +import pathlib, sys, tomllib +path = pathlib.Path(sys.argv[1]) +try: + data = tomllib.loads(path.read_text(encoding="utf-8")) +except (OSError, UnicodeError, tomllib.TOMLDecodeError) as exc: + print(f"invalid TOML: {exc}", file=sys.stderr) + raise SystemExit(1) +required = {"name", "description", "developer_instructions"} +if set(data) != required: + print("TOML keys must be exactly name, description, developer_instructions", file=sys.stderr) + raise SystemExit(1) +if any(type(data[key]) is not str for key in required) or data["name"] != "intent-guard": + print("intent-guard native fields must be strings and name must be fixed", file=sys.stderr) + raise SystemExit(1) +PY +} + +validate "$template" +if [ -f "$target" ]; then + validate "$target" + echo "INTENT_GUARD: REUSE .codex/agents/intent-guard.toml" + exit 0 +fi +mkdir -p "$agents" +tmp="$(mktemp "$agents/.intent-guard.XXXXXX")" +trap 'rm -f "$tmp"' EXIT HUP INT TERM +cp "$template" "$tmp" +validate "$tmp" +mv "$tmp" "$target" +trap - EXIT HUP INT TERM +echo "INTENT_GUARD: CREATED .codex/agents/intent-guard.toml" diff --git a/brewcode/.codex/skills/teams-setup/README.md b/brewcode/.codex/skills/teams-setup/README.md index 80040d8..dc22f4d 100644 --- a/brewcode/.codex/skills/teams-setup/README.md +++ b/brewcode/.codex/skills/teams-setup/README.md @@ -1,6 +1,6 @@ # Teams -Create and manage dynamic teams of domain-specific agents with a tracking framework. Analyzes your project, proposes 5-20 specialized agents, creates them, and sets up performance tracking. Every team also gets one fixed review-only member, `intent-guard`. +Create and manage dynamic teams of domain-specific agents with a tracking framework. The skill analyzes your project, proposes 5-20 specialized agents, creates them, and sets up performance tracking. A new team also gets exactly one review-only `intent-guard`; an upgraded legacy team with none stays that way. ## Quick Start @@ -28,7 +28,7 @@ The verb always comes first and the optional `` after it. That parser guar `disable` is a rename, not a deletion — the roster rows stay in `team.md` with `Status: disabled`, and `verify-team.sh` reports `DISABLED` per parked member and still exits PASS. `enable` puts it all back. Both take effect for the NEXT session: agent discovery is read at session start. -`purge` keeps exactly one thing: `.codex/agents/intent-guard.toml`, shared with `$brewcode:superreview-setup`. It removes both `.toml` and `.toml.disabled`, so purging a disabled team leaves nothing behind. +`purge` removes both `.toml` and `.toml.disabled`, so purging a disabled team leaves no owned domain profile behind. When `Intent guard` is `required`, it keeps `.codex/agents/intent-guard.toml` because that file is shared with `$brewcode:superreview-setup`; `legacy-absent` has no guard to keep. ## Examples @@ -83,7 +83,7 @@ After `$brewcode:teams-setup install my-team`: agents/ agent-one.md # Domain agents (5-20 depending on variant) agent-two.md - intent-guard.toml # Fixed review-only member, every team, not counted + intent-guard.toml # One review-only member for policy `required`; not counted teams/ my-team/ team.md # Roster: agent list, domains, missions, status @@ -91,11 +91,20 @@ After `$brewcode:teams-setup install my-team`: trace-ops.sh # Tracer, copied from the plugin at install -- agents call THIS path ``` +`team.md` stores logical `intent_guard_policy=required|legacy-absent` in its `Intent guard` field. +`required` means the roster contains exactly one `intent-guard` row. `legacy-absent` means it contains +zero such rows; upgrade preserves that state and never creates the role. New installs default to +`required`. + +Codex domain profiles are Markdown files under `.codex/agents/`. The Codex projection uses native +TOML files under `.codex/agents/`; it carries no YAML-in-TOML guidance. + ## How Agents Work -Created agents follow the **sub-agent task Acceptance Protocol** -- they self-select tasks based on domain fit, record acceptance/refusal in `trace.jsonl`, and log issues and insights as they work. - -Every generated domain agent is also born with a **Return Contract**: verdict first, <=30 lines, `path:line`, no file bodies, no command output, no logs, no preamble -- bulk material goes to `.codex/reports/` and only the path comes back. It holds whether or not `$brewtools:agent-return-setup` is installed; the guard only adds mechanical thresholds. `verify-team.sh` warns on an older agent that lacks the section, and `upgrade` re-adds it. +Created agents load `team.md`, whose single **Shared Agent Contract** owns acceptance, routing, tracing, +return, scope-fit and colleague rules. Domain profiles do not repeat those contracts. Their bodies have +exactly six ordered headings: `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique +invariants`, and `Unique verification`. They write through the project-local copy of the tracer: @@ -125,7 +134,7 @@ $brewcode:teams-setup install my-project | v [C2] Team Proposal ------ 3 variants + user confirmation - (+ intent-guard, fixed, not counted) + (+ one intent-guard for new teams, not counted) | v [C2.5] Model Selection -- high-reasoning model / balanced model / fast model / mixed (domain agents only) @@ -134,7 +143,7 @@ $brewcode:teams-setup install my-project [C3] Agent Creation ----- agent-creator x N (batches of 3-4) | v -[C3-IG] intent-guard ---- generate.sh emit-agent (create or reuse), then adapt if created +[C3-IG] intent-guard ---- required: emit/reuse; legacy-absent: do nothing | v [C4] Framework Setup ---- team.md + trace.jsonl + trace-ops.sh + verification @@ -171,22 +180,32 @@ After agent creation, a quality pipeline validates the team: > Skip with `--skip-review`. Run separately: `$brewcode:teams-setup upgrade --review` -> `intent-guard` is never used as a reviewer in this pipeline, and it is judged by different criteria than domain agents: placeholders resolved, template header stripped, frontmatter untouched (short review-only description, read-only tools). "Missing domain sections" is a false positive for it. +> When policy is `required`, `intent-guard` is not a reviewer in this pipeline and is judged by its own +> review-only contract. The six-heading domain-profile gate does not apply to it. Under `legacy-absent`, +> there is no guard to review. -## intent-guard (always in the team) +## intent-guard policy -Every team gets `intent-guard` in addition to its domain agents. It is an **anti-drift check**: it compares what was **ASKED** (the original request, ticket, spec, plan, project policy) against what was **DELIVERED**, and reports the delta. +`team.md` has exactly one `Intent guard` policy field: + +| Policy | Roster contract | Upgrade behavior | +|--------|-----------------|------------------| +| `required` | Exactly one review-only `intent-guard` row, outside the domain-agent count | Preserve the row; never duplicate it | +| `legacy-absent` | Zero `intent-guard` rows | Preserve absence; never create the role | + +New installs use `required`. The role is an **anti-drift check**: it compares what was **ASKED** (the +original request, ticket, spec, plan, project policy) against what was **DELIVERED**, and reports the delta. | Property | Value | |----------|-------| -| Counted in the 5 / 10-12 / 15-20 roster? | No -- it is outside the domain-agent count and cannot be dropped | +| Counted in the 5 / 10-12 / 15-20 roster? | No -- when required, it is outside the domain-agent count | | Tools | Read-only (`Read`, `Glob`, `Grep`, `Bash`). Never edits, builds, or runs tests | | Model | `balanced model`, fixed by its template -- not affected by the C2.5 model choice | | Invocation | Explicit, by name, during review only -- never during development, never an implementation owner | | Source | Emitted by `skills/superreview-setup/scripts/generate.sh emit-agent` from the shared template -- the single writer of this file, used by both skills | | Output | Verdict `ALIGNED` / `MINOR DRIFT` / `MAJOR DRIFT` plus <=10 findings, each with ASKED / SOURCE+tier / DELIVERED evidence / severity / minimal correction | -**Single writer (idempotent):** `teams` never authors this file. It runs +**Single writer (idempotent, `required` only):** `teams` never authors this file. It runs `superreview-setup/scripts/generate.sh emit-agent`, which creates it from the shared template or reuses an existing one and prints `INTENT_GUARD: CREATED|REUSE|MIGRATED `. On `REUSE` -- typically because `$brewcode:superreview-setup` ran first -- the file is left exactly as is and only the `team.md` roster row is @@ -196,14 +215,16 @@ invariants, drift examples, evidence commands) and touches nothing else -- front as emitted. Both skills therefore converge on one shared file produced by one pipeline, never two variants. -`intent-guard` is also excluded from `upgrade` and `uninstall` agent pruning (enforced in the cleanup flow -itself, Step 3, including a refusal if it is named explicitly): it does not write trace entries, so zero -activity is its normal state, not a reason to delete it. Teams created before `intent-guard` existed are -not broken by this -- `verify-team.sh` only WARNs, with the command to add it. +When policy is `required`, `intent-guard` is excluded from `upgrade` and `uninstall` pruning. It does not +write trace entries, so zero activity is normal. Teams created before the role existed can be upgraded +without it: the migration writes `legacy-absent`, retains zero rows, and does not emit a profile. + +Project Dusk is the compatibility baseline for this path: its roster stays at 13 members; +`task-tracker` remains a non-member owner, and `Intent guard: legacy-absent` keeps `intent-guard` absent. ## sub-agent task Acceptance Protocol -Each agent follows a 3-step self-selection before accepting a task: +The shared contract in `team.md` gives every domain agent the same 3-step self-selection before accepting a task: | # | Check | Question | If No | |---|-------|----------|-------| @@ -228,7 +249,9 @@ When other skills (convention, superreview, e2e) spawn agents, they check for te > If a team agent refuses a task (sub-agent task Acceptance Protocol), the skill re-delegates to the next priority level. Max 2 retries before falling back to system agents. -> `intent-guard` is outside this resolution chain -- it is never selected as an implementation or review owner by domain fit. It runs only when a review flow invokes it explicitly by name. +> Under `required`, `intent-guard` is outside this resolution chain -- it is never selected as an +> implementation or review owner by domain fit and runs only when a review flow invokes it explicitly. +> Under `legacy-absent`, there is no role to resolve. ## Related Skills diff --git a/brewcode/.codex/skills/teams-setup/SKILL.md b/brewcode/.codex/skills/teams-setup/SKILL.md index ae2263d..d95fe23 100644 --- a/brewcode/.codex/skills/teams-setup/SKILL.md +++ b/brewcode/.codex/skills/teams-setup/SKILL.md @@ -7,916 +7,49 @@ description: "Creates and manages dynamic teams of domain agents. Triggers: crea Use collaboration agents only when the user or project instructions explicitly request a team. Split work into bounded independent tasks, keep one owner per file or surface, exchange evidence through collaboration messages, and synthesize results in the parent session. Do not invent unsupported agent parameters or create persistent team configuration unless requested. -## Complete native workflow - -Follow every phase below. When a phase delegates work, use Codex collaboration with only `task_name` and `message`; treat each "Codex delegation brief" block as role and message content, not executable syntax. Use `request_user_input` for the documented user gates. Resolve ``, ``, ``, and `` before running commands. - - +## Native authority -# Teams +Manage persistent project teams under `.codex/teams/{TEAM_NAME}/` and domain agents under `.codex/agents/`. Agent files are real TOML parsed with Python `tomllib`; never rename Markdown/YAML agents to `.toml`. Each team agent has exactly three top-level string keys: `name`, `description`, `developer_instructions`. -Manage dynamic teams of domain-specific agents with tracking framework. +Resolve one mode: `status`, `install`, `upgrade`, `enable`, `disable`, `uninstall`, or `purge`. Read applicable `AGENTS.md`, inspect existing teams and agents, preserve unrelated files, and use only scripts/references shipped beside this skill. Never edit installed caches. -**Arguments:** `` +### C2.6: Shared Contract Bootstrap ---- +Bootstrap happens before any team-owned `.codex/agents/{name}.toml` is written: instantiate the fenced template from `references/framework-files.md` and write `team.md` at `.codex/teams/{TEAM_NAME}/team.md` with metadata, `## Shared Agent Contract`, explicit `Intent guard` policy, and zero domain rows. Do not add domain-agent rows yet. Copy the project-local tracer, initialize trace storage, substitute every placeholder, then run `scripts/verify-team.sh`. **STOP on any failure. Do not spawn or write an agent.** -## Prompt contract +### C3: Agent Creation -Position 1 of `` is a **free-form prompt** (RU/EN) — the mode and the `[name]` positional are -optional and may follow in any order. Nobody types keys: resolve mode + team name FROM the prompt. +Create each approved domain `.codex/agents/{name}.toml` from `references/agent-template.md`. Parse it structurally with `tomllib` and require only `name`, `description`, and `developer_instructions`. The `developer_instructions` value uses exactly these ordered headings and no others: `Mission`, `Owned surfaces`, `Exclusions`, `Must-load references`, `Unique invariants`, `Unique verification`. Its first must-load item is exactly `.codex/teams/{TEAM_NAME}/team.md`, occurring once. Enforce <=3200 UTF-8 bytes and `ceil(chars/4) <=800` over `developer_instructions` itself. -| Mode | EN keywords | RU keywords | Mutates? | -|------|-------------|-------------|----------| -| `status` | *(empty)*, `status`, `show`, `list`, `check` | `статус`, `покажи`, `что`, `проверь` | no | -| `install` | `install`, `create`, `setup`, `new team`, `build` | `установи`, `создай`, `настрой`, `новая команда` | yes | -| `upgrade` | `upgrade`, `update`, `tune`, `improve`, `retune` | `обнови`, `улучши`, `настрой лучше` | yes | -| `enable` | `enable`, `on`, `turn on`, `activate`, `restore` | `включи`, `активируй`, `верни`, `восстанови` | yes | -| `disable` | `disable`, `off`, `turn off`, `pause`, `park` | `выключи`, `отключи`, `пауза`, `приостанови` | yes | -| `uninstall` | `uninstall`, `remove`, `delete`, `clean up`, `tear down` | `удали`, `убери`, `сними`, `очисти` | yes, destructive | -| `purge` | `purge`, `wipe`, `nuke`, `delete everything`, `remove all` | `снеси`, `удали всё`, `вычисти`, `полностью удали` | yes, destructive | +`intent-guard` is exempt from the six-heading domain profile. Under `required`, run `/skills/superreview-setup/scripts/emit-intent-guard.sh `; that sole shared writer create-only copies its native authority, structurally validates it, and never overwrites an existing file. Never ask agent-creator to write it. Under `legacy-absent`, create no row and no role. -1. Strip flags (`--skip-review`, `--review`). An explicit mode token anywhere wins outright, no scoring. -2. Else score modes by distinct whole-word keyword hits (table above). Highest unique score wins. - Tie with a destructive mode -> `request_user_input`; tie with `status` -> `status`; tie of two mutating - modes -> the keyword appearing first; all zero -> **the documented default: `status` if the named - team already exists, else `install`** (`detect-mode.sh` already applies this default when the input - is empty or the first word is not a canonical mode). -3. Empty arguments -> the same default. `status` asks nothing; `install` and the other mutating - defaults ask ONE scoping question only when the answer changes what gets written. -4. Outcome-changing ambiguity -> ONE `request_user_input` (max 4 questions) BEFORE any work. -5. A prompt that is not a bare `mode [name]` pair is still input, never an error: extract the team - NAME (and, for `install`, the team description) from the prose. **Never treat the first word of a - sentence as the positional `[name]`** — `"disable the payments team"` names team `payments`, not - `disable`; `detect-mode.sh`'s literal first-word parse is only correct for a bare `mode [name]` - shape, see Error Handling below for the prose case. +### C4: Roster Finalization -Then print this block ONCE, before the first action (`## Universal Prelude` Step 0.4): - -``` -PLAN — brewcode:teams-setup -INPUT: -MODE: -SCOPE: -DO: <2-5 imperative bullets> -RESULT: -``` - -Labels are literal; values follow the conversation language. `status` still prints it — asks nothing. - ---- - -## Phase 1: Parse Arguments - -**EXECUTE** using shell: -```bash -bash "/scripts/detect-mode.sh" "" && echo "OK" || echo "FAILED" -``` - -Output: `MODE:`, `TEAM_NAME:`, `PROMPT:` (optional), plus the artifact-metadata scalars -`PLUGIN_VERSION:`, `CONTENT_VERSION:`, `GENERATED_BY:`, `LAST_UPDATED:`. Store all of them. - -> **Artifact metadata — every file this skill writes.** `team.md` and every generated domain agent carry -> `version` = `PLUGIN_VERSION:`, `generated_by` = `GENERATED_BY:` (`brewcode:teams-setup`), -> `last_updated` = `LAST_UPDATED:`, and `doc_type: llm` on the agents. `team.md`'s header table ALSO -> carries `content_version` = `CONTENT_VERSION:`, right after `Version` — self-located by -> `detect-mode.sh` from THIS skill's own `SKILL.md` line-1 `brewcode-meta:` marker (the one -> `bump-version.sh` stamps at release), never copied from `PLUGIN_VERSION:` and never invented. -> Take the values from the output above — never hardcode a version, never call `date` a second time -> with a different format, and never stamp a "template version": the plugin version replaces it. -> `.codex/agents/intent-guard.toml` is the ONE exception: `generate.sh emit-agent` stamps it with -> `generated_by: brewcode:superreview-setup`, and teams never touches those keys. - -`MODE` is one of the canonical seven, in this order: `status | install | upgrade | enable | disable | -uninstall | purge`. On any `ERROR:` line: report it verbatim and **STOP**. Never guess a mode, and -never treat a canonical verb as a team name — `install enable` creates a team NAMED `enable`, so the -verb always comes first and the optional `[name]` positional after it. - -> **How a team is enabled or disabled.** Codex discovers a project agent only through -> `.codex/agents/.toml`. `disable` renames each member to `.toml.disabled`; `enable` renames -> it back. The file body, `team.md`, `trace.jsonl`, `trace-archive.jsonl` and the cursor are untouched -> either way, so the toggle is fully reversible and loses no configuration and no history. It is NOT -> an uninstall: nothing is deleted. `intent-guard` is never parked — it is shared with -> `$brewcode:superreview-setup`, exactly as in UNINSTALL and PURGE. - ---- - -## Universal Prelude (every mode) - -### Step 0: Init + Validate + Confirm - -1. Output: `Mode: {MODE}, Team: {TEAM_NAME}` - -2. Load environment: - -| Action | Command / Path | -|--------|----------------| -| Read agent template | `/references/agent-template.md` | -| Read framework templates | `/references/framework-files.md` | -| Check team dir | `.codex/teams/{TEAM_NAME}/` -- exists? | -| Check existing agents | `.codex/agents/` -- list all | -| If team.md exists | Read, show current roster | -| If trace.jsonl exists | Show entry counts via `trace-ops.sh read` | - -3. If team exists, verify: - ```bash - bash "/scripts/verify-team.sh" "TEAM_NAME_HERE" && echo "PASS" || echo "FAIL" - ``` - -4. Print the **PLAN** block (`## Prompt contract` above) — once, before step 5's confirmation and - before any mutation. `status` prints it too, then skips straight to its report — no request_user_input. - -5. Mutating modes only — **ASK** using request_user_input: "PLAN above. Continue?" - Options: "Yes, continue" | "No, I want changes" | "Cancel" - - "changes" -> request_user_input for details, revise the PLAN and reprint it - - "Cancel" -> **STOP** - ---- - -## Delegation (applies to EVERY sub-agent task spawn in this skill) - -A big task handed to one agent = an agent gone for an hour: you cannot observe it, cannot correct -it, and it usually drifts off-target. One subagent = ONE bounded unit — one deliverable -(here: ONE agent file), ~<=5 files, ~<=10 steps. Bigger MUST be split into N tasks, all spawned -in ONE message. That is why agents are created one-per-spawn and reviews are fanned out. - -Every spawn prompt MUST carry: - -| Field | Content | -|-------|---------| -| GOAL | the overall task and why it exists — the point beyond the file edit | -| ROLE | what this agent owns; what it must NOT touch | -| SCOPE | exact paths/commands in bounds + explicit out-of-bounds | -| CONTEXT | what is already done, by whom, what runs in parallel — trimmed to what THIS agent needs | -| CONSUMER | who or what uses the result next, and the shape it must fit | -| DONE | acceptance criteria + the exact report shape you want back | - -A bare one-line task is never enough. See C8 for the canonical spawn shape. -Every code/test brief MUST make the agent find the closest well-built counterpart in the repo and follow its principles - IN ADDITION to conventions/rules/docs, never instead. - ---- - -## Mode: INSTALL (9 phases) - -### C1: Project Analysis - -Spawn 3-5 Explore agents in ONE message via sub-agent collaboration tools: - -| # | Focus | -|---|-------| -| 1 | Code structure: modules, packages, domains, architectural layers | -| 2 | Existing agents (`.codex/agents/`, `brewcode/agents/`, `~/.codex/agents/`) + Codex infrastructure | -| 3 | Tech stack: build files, frameworks, dependencies, languages | -| 4 | CI/CD, testing, deploy, infrastructure | -| 5 (optional) | Domain boundaries: business logic, API, data layer, UI | - -All via `Codex delegation brief (task_role="Explore")`. Consolidate into single analysis document. - -**Also harvest the intent-guard facts** (agent #1 and #4 cover most of these; add explicit asks to their prompts). -These fill the placeholders of the shared `intent-guard.md.template` in C3 — an unharvested fact must be recorded -as `none` / `not present in this project`, never invented: - -| Fact | Fills | Where to look | -|------|-------|---------------| -| Project name | `{PROJECT_NAME}` | repo dir, root `AGENTS.md`, `package.json`/build file | -| Where original requirements live (tracker, issues, Slack, "chat only") | `{TRACKER_LABEL}` | `AGENTS.md`, `.github/`, issue templates, CI links | -| Spec / design-doc paths or globs | `{SPEC_LOCATION}` | `.codex/specs/**`, `docs/`, `adr/` | -| Plan / task-board / task-graph paths | `{PLAN_LOCATION}` | `.codex/features/**`, `TASKS.md`, board files | -| Policy paths: root + nested `AGENTS.md`, rules, conventions | `{POLICY_LOCATION}` | `AGENTS.md`, `.codex/rules/**` | -| Planned scale / user count, testing policy, dependency policy, file-layout policy, architecture stance | `{PROJECT_INVARIANTS_TABLE}` | `AGENTS.md`, rules, test dirs, manifests, module layout | -| 3-6 plausible drift instances in this repo's vocabulary | `{DRIFT_EXAMPLES_TABLE}` | derived from the invariants above | -| Cheap evidence commands (diffstat, manifest diff, test-file count, new-file list) for this stack | `{EVIDENCE_COMMANDS_BASH}` | build/test tooling found by agent #3 and #4 | - -### C2: Team Proposal (interactive) - -Based on analysis + PROMPT (if provided), propose 3 variants via request_user_input. - -**`intent-guard` is in EVERY team, always, and is NOT one of the counted slots.** It is a review-only -anti-drift check (asked-vs-delivered), not a domain agent, so the 5 / 10-12 / 15-20 counts describe -DOMAIN agents only. Show it as a fixed row in every variant table, never as an option the user picks -and never as something the user can drop: - -``` -Fixed member (every variant, not counted): -| Agent | Domain | Mission | -| intent-guard | -- (review-only) | Compares what was ASKED vs what was DELIVERED; explicit invocation only | - -Minimal (5 domain agents + intent-guard): -| Agent | Domain | Mission | - -Balanced (10-12 domain agents + intent-guard) -- Recommended: -| Agent | Domain | Mission | - -Maximum (15-20 domain agents + intent-guard): -| Agent | Domain | Mission | -``` - -Options: "Minimal (5)" | "Balanced (recommended)" | "Maximum (15-20)" | "Custom -- I'll specify" - -If "Custom" -- second request_user_input for free input; intent-guard stays regardless of what the user -specifies. Final confirmation of agent list before proceeding. - -> If `.codex/agents/intent-guard.toml` already exists (e.g. `$brewcode:superreview-setup` created it), -> label the fixed row `reuse (already present)` — C3-IG's `emit-agent` call will report `REUSE` and -> leave the file untouched. - -**Agent names are globally unique across teams — REJECT a name another team already owns.** Two teams -listing the same agent share one file: one team's `upgrade` rewrites the other's member, and the other's -`uninstall`/`purge` is then blocked by the ownership check (`cleanup-flow.md` Step 3 step 0c), leaving -an undeletable roster row. Before showing the variants, run from the project root for every proposed name -(`intent-guard` excluded — it is shared BY DESIGN and exempt from this check): - -```bash -for a in {PROPOSED_NAMES}; do - o=$(bash "/scripts/agent-owners.sh" "$a") && echo "TAKEN $a <- $o" -done; echo "OK" -``` - -- exit 0 (any owner printed) -> the name is **TAKEN**. Drop it from the variant and propose a distinct - one — a domain-qualified rename such as `{name}-{TEAM_NAME}` or a different domain word. Never reuse, - never "join" the other team's agent. -- exit 2 (no owner) -> free, use it. -- exit 1 (refusal, reason on stderr) -> treat the name as TAKEN until the reason is fixed; report the - stderr line, do not guess. - -Say which names were renamed and why in the confirmation before C3. - -### C2.5: Model Selection (request_user_input) - -"Default model for domain agents: high-reasoning model (most reliable)." - -| Model | Best for | Cost | -|-------|----------|------| -| high-reasoning model | Complex domains, architecture, critical logic | High | -| balanced model | Standard domains, CRUD, testing, utilities | Medium | -| fast model | Simple utility agents, formatting, validation | Low | - -Options: "high-reasoning model (recommended)" | "balanced model" | "fast model" | "Mixed -- I'll choose per agent" - -If "Mixed" -- ask model per agent in C3. Store as `DEFAULT_MODEL` (default: high-reasoning model). - -> `DEFAULT_MODEL` applies to DOMAIN agents only. `intent-guard` keeps the `model: balanced model` its shared -> template ships — do not ask about it, do not override it. - -### C2.6: Shared Contract Bootstrap (before agent discovery) - -This gate MUST finish before any team-owned `.codex/agents/{name}.toml` is written. An interrupted install -may leave a partial roster, but no discoverable compact profile may ever point at a missing shared contract. - -1. Create `.codex/teams/{TEAM_NAME}/`. -2. Read `/references/framework-files.md`; write `team.md` with substituted metadata, - the byte-faithful `## Shared Agent Contract`, the `## Agents` header, and only the fixed - `intent-guard` row. Do not add domain-agent rows yet; C4 finalizes the successfully created roster. -3. Create empty `trace.jsonl`; copy the project-local `trace-ops.sh` and make it executable. -4. Gate before C3: `team.md` exists, contains `## Shared Agent Contract`, the project-local tracer path, - and `A task traced \`took\` ends with exactly one terminal track: \`completed\` or \`failed\`.` - -**STOP on any failure. Do not spawn or write an agent.** Resume by repairing this bootstrap first; never -strip shared rules from a profile until its target `team.md` passes the gate. - -### C3: Agent Creation (agent-creator x N) - -1. Read `/references/agent-template.md` -1a. Confirm C2.6 completed. `.codex/teams/{TEAM_NAME}/team.md` is already written and gated; if missing - or incomplete, **STOP before the first spawn** and repair the bootstrap. -1b. **Re-run the C2 uniqueness check on the FINAL confirmed roster, immediately before the first spawn** — - the user may have typed names in the "Custom" branch that never passed it. Same script, same exit-code - reading. Any `TAKEN` name -> **do not spawn**; go back and rename it with the user first. Also refuse a - name whose `.codex/agents/{name}.toml.disabled` exists with no live file: that is another install's parked - agent, and writing the live path recreates the dual-copy state both `enable` and `disable` refuse. -2. For each agent, spawn `Codex delegation brief (task_role="brewcode:agent-creator")` — ONE agent file per spawn, never a whole team. Prompt carries GOAL (build this one `{TEAM_NAME}` roster member; siblings own other domains), ROLE (owns `.codex/agents/{name}.toml` only), SCOPE (that file; other agents, `team.md`, project source out), CONTEXT (settled mission/domain/project analysis, selected model, 3-4 sibling names; no trigger/domain overlap; the gated shared contract already exists), CONSUMER (C4 adds the final roster row; C5 reviews; the roster routes work), DONE: - - `description` <=100 chars (optimal ~80), single-line role + 2-3 triggers, no ``; - - body <=3200 bytes (~800 est-tokens), with exactly these ordered headings and no others: `## Mission`, `## Owned surfaces`, `## Exclusions`, `## Must-load references`, `## Unique invariants`, `## Unique verification`; - - `## Must-load references` names `.codex/teams/{TEAM_NAME}/team.md` first; - - profile contains only domain-unique facts. `sub-agent task Acceptance Protocol`, `Return Contract`, `Trace Instructions`, `Colleagues`, `Scope Fit`, shared routing, and shared output rules stay only in `team.md`; - - placeholders substituted; return file path + description line. - - Every spawn prompt MUST also carry the template path and the four metadata lines, resolved — the - subagent cannot see Phase 1's output, so **replace `{PLUGIN_VERSION}` and `{LAST_UPDATED}` below with - the literal values from the Phase 1 `PLUGIN_VERSION:` / `LAST_UPDATED:` lines before you send the - prompt.** A token that reaches the subagent ships verbatim into the agent file, and `setup-status` - then reports that agent `partial` forever. Those two spellings are the only sanctioned ones — never an - angle form, never a double brace: - - ``` - CONTEXT (cont.): structure from /references/agent-template.md — read it first. - DONE (cont.): the frontmatter ends with exactly these four keys, in this order, AFTER the agent's - own keys (name, description, model, tools — leave those byte-untouched, `tools` above all): - doc_type: llm - version: "{PLUGIN_VERSION}" - generated_by: "brewcode:teams-setup" - last_updated: "{LAST_UPDATED}" - ``` - - `verify-team.sh` re-reads every generated agent's frontmatter and FAILS on a wrong order, a missing - key or wrong quoting, so a prompt that shipped a token does not pass C4. -3. Batch 3-4 agents in parallel per message -4. After each batch, optimize without changing the six-heading contract: - ``` - Codex delegation brief (task_role="brewtools:text-optimizer", message="Light-optimize .codex/agents/{agent-name}.toml; preserve its exact six ordered headings, team.md reference, names/numbers/negations/scope. Output metrics.") - ``` - > `brewtools` not installed (`text-optimizer` unavailable) — skip the pass, agents stay as written. - > **Never run the optimizer on `.codex/agents/intent-guard.toml`.** Its frontmatter `description` - > is deliberately short and review-only; an optimizer pass may reword, lengthen or reflow it into - > a normal domain-agent description, which would make it compete for auto-activation. Excluded. - -#### C3-IG: intent-guard (always, exactly once) - -`.codex/agents/intent-guard.toml` has exactly ONE writer: `generate.sh emit-agent`, shared with -`$brewcode:superreview-setup`. Never author this file from the template yourself, and never spawn an agent -to author it — that would fork the file into two divergent pipelines. `agent-creator` appears in this -phase only as a post-processor that replaces three seeded BLOCKs. - -**Step 1 — emit.** Run from the project root, exporting the C1 facts. Unharvested fact -> `none` / -`not present in this project`; never invent a tracker, a path or a ticket id. - -**EXECUTE** using shell (substitute the C1 values first): -```bash -PROJECT_NAME="PROJECT_NAME_HERE" \ -TRACKER_LABEL="TRACKER_LABEL_HERE" \ -SPEC_LOCATION="SPEC_LOCATION_HERE" \ -PLAN_LOCATION="PLAN_LOCATION_HERE" \ -POLICY_LOCATION="POLICY_LOCATION_HERE" \ -bash "/../superreview-setup/scripts/generate.sh" emit-agent && echo "OK" || echo "FAILED" -``` - -It creates-or-reuses ONLY `.codex/agents/intent-guard.toml` (superreview does not need to have run) and -prints exactly one `INTENT_GUARD:` line on STDOUT: `INTENT_GUARD: CREATED `, -`INTENT_GUARD: REUSE ` or `INTENT_GUARD: MIGRATED ` (a pre-standard file of ours, restamped -in place — metadata only, tailored body preserved). Diagnostics (e.g. "recreating from template") go to -stderr and never add a second status line. -> **STOP if FAILED** -- report the script output; do not fall back to hand-authoring the file. - -**Step 2 — sanity-check the emitted file** (a pre-existing file may be empty, truncated or -placeholder-laden; `-f` alone proves nothing). This runs on the REUSE path too, where `$f` is somebody's -already-adapted agent whose evidence block legitimately holds shell expansions — so strip `${VAR}` FIRST -and match bare tokens on what is left. Without the strip a `${BASE}` scores as an unresolved placeholder, -and this step's remedy is `rm -f`: it would delete a tailored file. -```bash -f=.codex/agents/intent-guard.toml -[ -s "$f" ] && grep -q '^name: intent-guard' "$f" \ - && ! sed 's/\${[A-Z_][A-Z_]*}//g' "$f" | grep -q '{[A-Z_]\{2,\}}' && echo "SANE" || echo "CORRUPT" -grep -qF '` line. Key each Edit on that marker: `old_string` = the - seeded block PLUS its marker line, `new_string` = your project-specific replacement - WITHOUT any marker. A surviving marker is what makes a skipped adaptation detectable — - `generate.sh validate` reports any file that still carries one as UNTAILORED. - HARD out of bounds — a single byte changed here is a failed task: - - the frontmatter (name, description, model: balanced model, tools, color, maxTurns). The - description is <= 100 chars, review-only, explicitly-invoked BY DESIGN; do NOT rewrite, - lengthen or 'improve' it. This overrides any default description-authoring habit. - - the file header, every heading, and every other section of the file - - the shared template, other agent files, team.md, trace.jsonl, project source - CONTEXT: C1 project analysis is settled — use these facts, invent nothing: - PROJECT_INVARIANTS_TABLE = from C1: planned scale/user count, testing policy, dependency - policy, file-layout policy, architecture stance - DRIFT_EXAMPLES_TABLE = 3-6 drift instances in THIS repo's vocabulary - EVIDENCE_COMMANDS_BASH = cheap evidence commands for THIS stack (diffstat, manifest diff, - test-file count, new-file list) - Unknown fact -> write 'none' / 'not present in this project'. Never fabricate a tracker, - a path or a ticket id. Do not add a Scope Fit block, sub-agent task Acceptance Protocol, trace - instructions or a Domain Instructions section — this agent has no code domain. - CONSUMER: $brewcode:superreview-setup spawns this same file by name during review, and C4 adds its row to - .codex/teams/{TEAM_NAME}/team.md — the file name and agent name stay exactly 'intent-guard'. - DONE: three BLOCKs project-specific, all three SEEDED-DEFAULT markers gone (consumed by the - replacements), everything else byte-identical to what emit-agent wrote. - Report: path + the three BLOCK contents + confirmation that frontmatter and header are untouched. -") -``` - -**Step 4 — verify.** FOUR counts, one grep per line, in this order. Each pattern matches the ARTIFACT, -never prose ABOUT it: the emitted agent legitimately keeps a tail comment that NAMES the stripped -`TEMPLATE HEADER`, so an unanchored `grep -c 'TEMPLATE HEADER'` reports `1` on every healthy file and -turns this gate into an unpassable loop. Match the header's opening line, not the phrase. Same reason the -placeholder count strips `${VAR}` first: `{PROJECT_NAME}` is a token, `` in an adapted -evidence command is not, and only a strip-then-match tells them apart — a `$`-guard inside the pattern -mis-handles adjacent tokens. `|| true` on every line: zero matches is the happy path for three of the four -counts (repo rule avoid#7), and a count must still PRINT under `set -o pipefail`, especially when it is the -one going red. - -```bash -f=.codex/agents/intent-guard.toml -sed 's/\${[A-Z_][A-Z_]*}//g' "$f" | grep -c '{[A-Z_]\{2,\}}' || true # 0 — unresolved placeholder -grep -c '^'; -const LEGACY_STAMP = ''; - +const EMIT = join(HERE, '..', '..', 'superreview-setup', 'scripts', 'emit-intent-guard.sh'); +const TEMPLATE = join(HERE, '..', '..', 'superreview-setup', 'references', 'intent-guard.toml.template'); let passed = 0; let failed = 0; const results = []; -function check(name, actual, expected, message) { +function check(name, actual, expected, description) { if (actual === expected) { - passed++; - results.push(` PASS ${name} (${message})`); + passed += 1; + results.push(' PASS ' + name + ' (' + description + ')'); } else { - failed++; - results.push( - ` FAIL ${name} (${message} | actual=${JSON.stringify(actual)} expected=${JSON.stringify(expected)})`, - ); + failed += 1; + results.push(' FAIL ' + name + ' (' + description + ' | actual=' + JSON.stringify(actual) + ' expected=' + JSON.stringify(expected) + ')'); } } -/** A project root with an optional pre-existing intent-guard.toml. */ -function makeProject(label, body) { - const root = join(BASE, label); - mkdirSync(join(root, '.codex', 'agents'), { recursive: true }); - if (body !== null) writeFileSync(join(root, IG_REL), body); - return root; +function run(root) { + const result = spawnSync('bash', [EMIT, root], { encoding: 'utf8', timeout: 30000 }); + return { status: result.status, output: (result.stdout || '') + (result.stderr || '') }; } -function emitAgent(root) { - const r = spawnSync('bash', [GENERATE, 'emit-agent'], { cwd: root, encoding: 'utf8', timeout: 30000 }); - return { stdout: (r.stdout || '').trim(), stderr: r.stderr || '', status: r.status }; +function parse(path) { + const result = spawnSync('python3', ['-c', 'import json,pathlib,sys,tomllib; print(json.dumps(tomllib.loads(pathlib.Path(sys.argv[1]).read_text()), sort_keys=True))', path], { encoding: 'utf8' }); + return { status: result.status, data: result.status === 0 ? JSON.parse(result.stdout) : null }; } -const agentNames = (root) => readdirSync(join(root, '.codex', 'agents')).sort(); -const backups = (root) => - agentNames(root).filter((n) => /^intent-guard\.toml\.bak-[0-9]{8}-[0-9]{6}$/.test(n)); -const read = (root, rel) => readFileSync(join(root, rel), 'utf8'); +const template = parse(TEMPLATE); +check('template.parse', template.status, 0, 'shared native template is valid TOML'); +check('template.keys', Object.keys(template.data).sort().join(','), 'description,developer_instructions,name', 'template has exactly three keys'); +check('template.name', template.data.name, 'intent-guard', 'template name is fixed'); -const FOREIGN_WITH_TOKEN = [ - '---', - 'name: intent-guard', - 'description: our own hand-written drift check', - '---', - '', - '# intent-guard', - '', - 'Use {REQUEST_ID} to correlate the review with the ticket.', - '', -].join('\n'); - -// ──────────────────────────────────────────────────────────────────────────── -// B1 — BCOP09: an UNSTAMPED file carrying a {TOKEN} is the project's own agent. -// REUSE, byte-identical, no backup, tokens reported as a conflict. -// ──────────────────────────────────────────────────────────────────────────── { - const root = makeProject('b1', FOREIGN_WITH_TOKEN); - const r = emitAgent(root); - - check('b1.status', r.status, 0, 'emit-agent succeeds on a foreign agent'); - check('b1.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'exactly one status line, and it is REUSE'); - check('b1.bytes', read(root, IG_REL), FOREIGN_WITH_TOKEN, 'the hand-written file is byte-identical'); - check('b1.backupCount', backups(root).length, 0, 'nothing was backed up because nothing was rewritten'); - check('b1.tree', agentNames(root).join(','), 'intent-guard.toml', 'no extra file was created'); - check( - 'b1.conflictReported', - r.stderr.includes('{REQUEST_ID}'), - true, - 'the token is reported on stderr as a conflict', - ); + const root = mkdtempSync(join(tmpdir(), 'native-intent-create-')); + const result = run(root); + const target = join(root, '.codex', 'agents', 'intent-guard.toml'); + check('create.exit', result.status, 0, 'first run succeeds'); + check('create.verdict', result.output.trim(), 'INTENT_GUARD: CREATED .codex/agents/intent-guard.toml', 'first run reports creation'); + check('create.bytes', readFileSync(target, 'utf8'), readFileSync(TEMPLATE, 'utf8'), 'created file equals shared authority byte-for-byte'); + check('create.parse', parse(target).status, 0, 'created file remains structurally valid TOML'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B2 — an unstamped file WITHOUT `name: intent-guard` frontmatter is still -// foreign: unrunnable by our rules, but not ours to overwrite. -// ──────────────────────────────────────────────────────────────────────────── { - const body = '# somebody else\n\nnotes about {TICKET_ID}\n'; - const root = makeProject('b2', body); - const r = emitAgent(root); - - check('b2.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'REUSE, not RECREATE'); - check('b2.bytes', read(root, IG_REL), body, 'a foreign file without our frontmatter survives byte-identical'); - check('b2.backupCount', backups(root).length, 0, 'no backup, because no write'); + const root = mkdtempSync(join(tmpdir(), 'native-intent-reuse-')); + const agents = join(root, '.codex', 'agents'); + mkdirSync(agents, { recursive: true }); + const target = join(agents, 'intent-guard.toml'); + const foreign = 'name = "intent-guard"\ndescription = "Foreign review-only agent."\ndeveloper_instructions = "Review only; preserve these bytes."\n'; + writeFileSync(target, foreign); + const result = run(root); + check('reuse.exit', result.status, 0, 'valid existing native agent is reused'); + check('reuse.verdict', result.output.trim(), 'INTENT_GUARD: REUSE .codex/agents/intent-guard.toml', 'reuse is explicit'); + check('reuse.bytes', readFileSync(target, 'utf8'), foreign, 'reuse preserves foreign bytes'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B3 — control: the same foreign file with no token at all also REUSEs. This is -// the pre-fix behaviour and must not change. -// ──────────────────────────────────────────────────────────────────────────── -{ - const body = FOREIGN_WITH_TOKEN.replace('{REQUEST_ID}', 'the ticket id'); - const root = makeProject('b3', body); - const r = emitAgent(root); - - check('b3.stdout', r.stdout, `INTENT_GUARD: REUSE ${IG_REL}`, 'token-free foreign file reuses too'); - check('b3.bytes', read(root, IG_REL), body, 'byte-identical'); - check('b3.stderrEmpty', r.stderr, '', 'no conflict to report, so stderr stays silent'); +for (const [name, body, reason] of [ + ['renamedMarkdown', '---\nname: intent-guard\n---\n', 'invalid TOML'], + ['extraKey', 'name = "intent-guard"\ndescription = "Review."\ndeveloper_instructions = "Review."\nmodel = "legacy"\n', 'keys must be exactly'], +]) { + const root = mkdtempSync(join(tmpdir(), 'native-intent-' + name + '-')); + const agents = join(root, '.codex', 'agents'); + mkdirSync(agents, { recursive: true }); + const target = join(agents, 'intent-guard.toml'); + writeFileSync(target, body); + const result = run(root); + check(name + '.exit', result.status, 1, 'invalid existing artifact fails closed'); + check(name + '.reason', result.output.includes(reason), true, 'failure names the structural defect'); + check(name + '.bytes', readFileSync(target, 'utf8'), body, 'failure never overwrites existing bytes'); + rmSync(root, { recursive: true, force: true }); } -// ──────────────────────────────────────────────────────────────────────────── -// B4 — OUR OWN stamped file with an unresolved token is BROKEN: recreated, but -// only after its bytes are copied to .bak-. -// ──────────────────────────────────────────────────────────────────────────── -{ - const body = ['---', 'name: intent-guard', '---', '', 'half-substituted {PROJECT_NAME}', '', STAMP, ''].join('\n'); - const root = makeProject('b4', body); - const r = emitAgent(root); - - const bak = backups(root); - check('b4.stdout', r.stdout, `INTENT_GUARD: CREATED ${IG_REL}`, 'our own broken file is recreated'); - check('b4.backupCount', bak.length, 1, 'exactly one backup was written'); - check( - 'b4.backupBytes', - // concatenation, not indexing: zero backups yields '' and fails the check instead of throwing - bak.map((n) => read(root, join('.codex', 'agents', n))).join(''), - body, - 'the backup holds the original bytes verbatim', - ); - check( - 'b4.recreated', - read(root, IG_REL).includes('