---
name: cursor-orchestrator
description: >
  Use when executing implementation plan with cursor-agent as coding
  engine instead of Claude subagents. Invoke on /cursor-orchestrator, when user
  says "use cursor-agent to implement", or when sdd applies but user
  wants cursor for heavy coding work.
---

# Cursor Orchestrator

SDD with cursor-agent as implementer. Claude plans, instructs, reviews. cursor-agent
codes and commits. Never reverse.

**REQUIRED:** Understand `sdd` — wave sequencing and worktree setup carry over.

---

## Multi-wave gate — check FIRST before any inline work

Handed a PLAN_SLUG or JSONL? Classify shape before executing anything:
- **Multi-wave/multi-phase** (>1 wave/phase OR any wave has parallel tasks) → **STOP. Invoke `Skill("run-plan")` with the slug.** Do NOT proceed inline.
- **Single-wave single-phase** → proceed below.

Multi-wave inline = context saturation across sessions → **$5.13/task** (measured). run-plan Workflow = **$1.985/task** + resumable + parallel. Break-even = first task. Inline orchestrators are single-wave engines only.

---

## IRON LAW — active every turn, survives compaction

**Claude not write, edit, or touch implementation code. Not one line.**

Rule permanent. No reset after compaction, long session, advisor call, BLOCKED status, or any event.

Forbidden — Claude must NEVER:
- Write or edit source files (`.ts`, `.tsx`, `.astro`, `.sql`, configs, etc.)
- Run build or test commands to verify implementation
- Apply "quick fix" inline to unblock cursor-agent
- Commit implementation work
- Reason "faster if I just do this myself"

All violations. Dispatch cursor-agent instead — always.

### Sole exception — trivial-edit inline (LOC rule)

**When code LOC to change ≤ LOC of the cursor prompt it would take to dispatch the change → Claude edits inline.** Dispatching cursor for an edit smaller than its own prompt is pure overhead (a 48-line prompt to flip 1 comment line = waste). Bounds:
- Applies ONLY to mechanical, self-contained edits: comment fix, single-line rename, typo, one-token tweak — where a correct cursor prompt costs more lines than the edit itself.
- Does NOT apply to logic, multi-line, multi-file, or any change needing judgment — those go to cursor regardless of size. Never use this to "unblock myself" on real work.
- Inline edits still pass the code gate (Tier 1 + Tier 2) like any other commit.

This is the ONLY carve-out. Everything above the threshold stays forbidden.

**Plans are contract-level (see `plan`).** A task tagged `apply inline (LOC≤LOP), no dispatch` carries literal code shorter than its dispatch prompt → Claude applies it directly (this carve-out). Every other task gives a Contract (seam + behavior + acceptance), NOT a body → dispatch cursor-agent, which writes the body from the repo. A plan that hands a full implementation body for a dispatched task is a planning defect — do not transcribe it; dispatch the contract.

**After compaction:** Lost context on wave state — re-read skill, check `git log` in worktree before anything. No reconstruct by writing code.

**If cursor-agent blocked:** Provide context, rewrite prompt, escalate to user — never pick up work yourself.

---

## Your role (Claude = controller only)

- Set up worktree (via `using-git-worktrees`)
- Write cursor-agent prompts with full context + worktree path
- Read output, judge correctness
- **Code gate (two-tier):**
  - **Tier 1 — cursor-agent reviewer:** dispatch cursor-agent in AUDIT mode over the wave's
    commits. cursor-agent reads the diff + constraint sources and reports findings. Dispatch
    cursor-agent fixer for every finding. Re-run cursor-agent reviewer. Loop until cursor-agent
    reports CLEAN — **no iteration cap**.
  - **Tier 2 — Codex final gate (only after Tier 1 clean):** invoke `Skill("ask-codex")`
    (`gpt-5.5/low`) over the same commit range. Dispatch cursor-agent fixer for every Codex
    finding. Re-invoke Codex to re-scan. Loop until Codex reports CLEAN — **no iteration cap**.
- **Process gate:** call `advisor()` between waves and before announcing completion — reviews
  direction, decision soundness, big picture (not line-level code). Fixer loop if it flags
- **Never write implementation code yourself — see IRON LAW above**

## Two gates — do not conflate

| Gate | Who | Sees | Catches | When |
|------|-----|------|---------|------|
| **Code gate Tier 1** | cursor-agent in AUDIT mode | The actual wave diff + project constraint files (reads them itself) | Code quality, security, a11y, perf, componentization, business logic, deploy limits, cost, SEO/GEO | After every wave's commits land — must reach CLEAN before Tier 2 |
| **Code gate Tier 2** | Codex (`Skill("ask-codex")`, `gpt-5.5/low`) | Same wave diff + constraint files | Same dimensions — final independent-model pass | Only after Tier 1 is clean |
| **Process gate** | `advisor()` | Only what the orchestrator surfaces in conversation | Wrong direction, bad decisions, lost big picture | Between waves + before completion |

The code gate replaces advisor's old line-level role — it sees real files, advisor cannot.
Both gates feed the **same fixer loop** (cursor-agent only). Neither gate edits code.

---

## cursor-agent invocation

**NEVER** background the dispatch — not Bash `run_in_background: true`, not `ctx_execute(background:true)`, not `&`. **A Claude Code bug makes the task-completion notification UNRELIABLE: you may never be re-invoked when cursor finishes, so you wait forever or act on half-built state. A backgrounded cursor task = FAILURE.** (Reaffirmed by the user 2026-06-14 after a bg dispatch slipped through — "bg task = failure, always run in front, follow strictly.") Always run FOREGROUND via `ctx_execute` so the call BLOCKS until cursor exits and you read the result inline.

```
mcp__plugin_quietcontext_quietcontext__ctx_execute(
  language: "shell",
  code: `~/.claude/skills/cursor-orchestrator/ca.sh --workspace <WORKTREE_PATH> --trust "PROMPT" --task-slug "<slug>" >/dev/null 2>&1; echo "done exit=$? log=$(ls -t ~/Projects/multideal/tmp/logs/*-<slug>.log | head -1)"`
)
```
ca.sh tees raw stream-json to its log; redirect the dispatch's stdout to `/dev/null` so the foreground call returns ~nothing, then parse that log in a SECOND `ctx_execute` (final assistant report + `git -C <wt> log`) → tiny summary. Foreground keeps return-size sane AND removes the bg-notification dependency.

- `--workspace` must point to worktree, not main repo root
- `--task-slug` is the task identifier (e.g., "wave-1-auth", "fix-bug-123") — appended to log filename
- cursor-agent has full tool access: reads/writes files, runs shell, runs tests, commits

---

## Dispatch constraints (timeouts, cache, quota)

- **Timeout per dispatch:** coding/fix = **58 min** (`timeout: 3480000`). Never use 60 min —
  a full hour invalidates the Claude session prompt cache (expensive re-read). Read-only
  AUDIT dispatches = 30 min (`timeout: 1800000`). On timeout/hang, re-dispatch a **smaller**
  scope (split the task), never the same prompt verbatim.
- **Separate quota:** cursor-agent runs on Cursor's quota, Tier 2 runs on Codex's (OpenAI)
  quota — neither burns the Anthropic Opus/Sonnet/Haiku pool. Push heavy code reading
  (large-codebase scans, spec-vs-impl audits) to cursor-agent so the orchestrator never burns
  its own quota scanning. Haiku is too weak for spec-divergence judgment — do not use it for
  auditing.
- **Code-gate boundary (does NOT contradict the above):** Tier 2 Codex reads the **bounded
  wave diff + targeted constraint files** — small, high-value. **Full-codebase and
  spec-divergence audits still go to cursor-agent.** Code gate = review a diff; audit = scan
  a tree. Never let Tier 2 scan the whole codebase.
- **Return-size discipline:** cursor-agent raw output can blow the tool-result token limit.
  Make it **WRITE artifacts to files** (audit JSON, reports) and **return only tiny summaries**
  (counts + one-line-per-item). Aggregate the files off-context (e.g. ctx_execute), never by
  pasting cursor's full output back.

## Efficient auditing (measured)

- **1 spec per prompt, run many in PARALLEL** (separate concurrent cursor-agent sessions —
  multiple ctx_execute calls in one message). Measured A/B: solo single-spec audit found
  **30 findings** vs **9** for the same spec inside a 4-spec batch (3.3×, and caught a P0 the
  batch missed). Batching dilutes attention — never batch >1 spec for quality-critical audits.
  Same wall-clock as batching, strictly better depth.

## Spec is the source of truth

Every audit finding is triaged before fixing: **is the spec correct, or is the spec wrong?**
- Spec correct, code diverges → fix code to match spec.
- Spec wrong / outdated / doesn't match the real system need → fix the **spec** (and matching
  `docs/plans/tasks/<slug>.md`), with a one-line rationale; don't bend correct code to a bad spec.
- Code and spec must never be left in silent disagreement. (Also in the project root `CLAUDE.md`.)

**Who judges (critical):** a cursor-agent-reported mismatch is a CANDIDATE, not a proven bug.
Judging code-wrong vs spec-wrong needs high reasoning + full project/feature intent — cursor-agent
(composer model) does NOT have this. So:
- cursor-agent NEVER decides if a spec is right and NEVER edits a spec on its own initiative — it
  reports the mismatch and executes the DECIDED fix.
- **The code-review subagent likewise REPORTS, never decides spec correctness.** It tags each
  finding with `fix_direction` as a recommendation; spec-wrong-vs-code-wrong judgment stays with
  the orchestrator + advisor + user escalation. The reviewer reads code and constraints and
  surfaces evidence — it does not own the call.
- The orchestrator + advisor own the judgment; genuine product-intent calls escalate to the user.
- Per-finding `fix_direction` flag in audit/review JSON: `code_to_spec` (clear bug → straight to
  cursor fixer) | `spec_review` (intent-question → orchestrator/advisor/user decide before any fix).
  cursor and the reviewer flag conservatively; the decision is never theirs.
- A spec edit is dispatched to a cursor fixer only on the orchestrator's explicit instruction.

---

## Worktree setup

Before first wave, invoke `using-git-worktrees` to create `.worktrees/<branch>/`.
Record `WORKTREE_PATH`. Pass in every cursor-agent prompt.

```
WORKTREE_PATH: /path/to/.worktrees/<branch>/
All work must happen inside this path.
```

---

## Execution loop

```
Setup worktree → read plan → extract waves → TodoWrite tasks
For each wave (in order):
  → dispatch cursor-agent implementer(s) with WORKTREE_PATH
  → cursor-agent commits its own work inside the worktree
  → verify commits exist: git log in worktree
  → CODE GATE TIER 1: dispatch cursor-agent reviewer over the wave's commit range
  → if findings → cursor-agent fixer → cursor-agent re-review → repeat until cursor-agent CLEAN
  → CODE GATE TIER 2: invoke Skill("ask-codex") gpt-5.5/low over the same commit range
  → if findings → cursor-agent fixer → Codex re-review → repeat until Codex CLEAN
  → PROCESS GATE: advisor() — direction / decisions / big picture
  → if advisor flags → fixer loop → advisor() again → repeat until clean
  → next wave
After all waves → FINAL PROCESS GATE: advisor() before announcing completion
  → if advisor flags → fixer loop → advisor() again → repeat until clean
  → push / deploy / announce done
  → invoke `Skill("learn-from-mistakes")` inline (not as subagent)
```

---

## Single-task wave

1. Dispatch cursor-agent implementer (template below) — workspace = WORKTREE_PATH
2. cursor-agent commits work
3. Verify: `git log --oneline -5` in worktree shows expected commit
4. **Code gate Tier 1:** dispatch cursor-agent reviewer over commit range → cursor-agent fixer loop until cursor-agent CLEAN
5. **Code gate Tier 2:** invoke Skill("ask-codex") gpt-5.5/low over same commit range → cursor-agent fixer + Codex re-scan loop until Codex CLEAN
5. **Process gate:** `advisor()` — direction / big picture → fixer loop until clean
6. Clean → next wave

## Multi-task wave (sequential, no file overlap)

Before dispatching: confirm zero file overlap across tasks.

Run cursor-agent per task sequentially — each **commits its own work**.

After all tasks committed:
- Verify all commits in worktree git log
- **Code gate Tier 1:** cursor-agent reviewer over all wave commits → cursor-agent fixer loop until cursor-agent CLEAN
- **Code gate Tier 2:** Skill("ask-codex") gpt-5.5/low over all wave commits → cursor-agent fixer + Codex re-scan loop until Codex CLEAN
- **Process gate:** `advisor()` over the wave → fixer loop until clean

---

## Fixer loop (shared by all gates)

Re-run the **same gate that flagged** to confirm the fix. Code gate Tier 1 and Tier 2 use separate
loops — Tier 2 does NOT start until Tier 1 is fully clean.

```
Gate has findings?
  ── no ──→ gate clean
              code gate Tier 1 clean → advance to Tier 2
              code gate Tier 2 clean → advance to process gate
              process gate clean → advance to next wave
  ── yes ─→ triage fix_direction per finding:
              code_to_spec → straight to cursor-agent fixer
              spec_review  → orchestrator + advisor (+ user) decide before any fix
            dispatch cursor-agent fixer with WORKTREE_PATH + findings verbatim
            fixer commits fix
            RE-RUN THE SAME GATE:
              code gate Tier 1  → re-dispatch cursor-agent reviewer over commits + fix commits
              code gate Tier 2  → re-invoke Skill("ask-codex") gpt-5.5/low over commits + fix commits
              process gate      → advisor() again
            repeat until that gate returns clean
            no iteration cap — loop until resolved
            if truly stuck (same finding resurfaces 3x) → STOP, escalate to user
```

---

## Implementer prompt template

**Carry the CONTRACT, never the body.** The dispatch prompt pins the seam + points to exemplars; the implementer writes every function/file body by reading the repo — that is its job, on its context, not yours. A body authored here pays opus/sonnet to write code the implementer re-derives and the gate re-reviews — triple cost, and it poisons orchestrator context.

- **Paste the plan task AS-IS.** Plan is contract-level → prompt stays contract-level. NEVER expand a contract bullet into source. Plan line "`isFoo(e): e is Foo` — structural guard, check tag not `instanceof`" → pass that line verbatim; DO NOT write the function body.
- **Reference exemplars by PATH, never paste contents.** "mirror `packages/fields/src/errors.ts`" — NOT the pasted file. Repo file is ground truth + self-updating; a paste rots and freezes an untypechecked guess.
- **Literal code allowed ONLY for:** (a) a cross-task SEAM a parallel sibling imports and cannot derive — a shared signature/type — pin it exactly; (b) a task tagged `apply inline (LOC≤LOP)`. Everything else → behavior + acceptance test; body written downstream.
- **Pre-dispatch self-check:** does the prompt contain a function/file body the implementer is meant to write? → strip to contract + path pointer.

```
You are implementing [Task N: task name].

## Worktree
WORKTREE_PATH: <path>
All file edits and git operations must happen inside this path.
cd to WORKTREE_PATH before any work.

## Task
[FULL task text from plan — paste it verbatim]

## Context
[Architecture context, dependencies, exemplar file paths to mirror — by path, never pasted contents]

## Codebase conventions
[Key patterns: naming, imports, error handling — grep the worktree if unsure]

## Files to create / modify
[Explicit list from plan]

## Constraints
- Follow existing patterns exactly — read files before writing
- Do not modify files outside the task scope
- Commit your work when done: git commit -m "feat(task-N): <description>"
- If blocked or uncertain: write BLOCKED or NEEDS_CONTEXT at end, explain why

## Report back
STATUS: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
COMMIT: [SHA of your commit]
FILES_MODIFIED: [list]
SUMMARY: [what you did]
CONCERNS: [if any]
```

---

## Code-review subagent (the code gate)

Both tiers use the **same prompt**: `~/.claude/skills/cursor-orchestrator/code-review-prompt.md`.
Substitute `<WORKTREE_PATH>`, `<BASE_SHA>..<HEAD_SHA>`, `SPEC PATHS`, `<slug>` before dispatching.

### Tier 1 — cursor-agent reviewer

Dispatch cursor-agent in AUDIT mode with the prompt. Run via `ctx_execute` (not background),
timeout 30 min (`timeout: 1800000`). Read-only — report only, no edits, no commits.

### Tier 2 — Codex (only after Tier 1 clean)

Invoke `Skill("ask-codex")` with args `"gpt-5.5/low <review prompt>"`.
Read-only — report only, no edits. Value: independent-model final pass catches subtle logic errors,
spec intent divergence, security design flaws that look correct line-by-line.

Prompt: `~/.claude/skills/cursor-orchestrator/code-review-prompt.md`
Data script: `~/.claude/skills/cursor-orchestrator/gather-diff.sh`

Fill substitutions (`<WORKTREE_PATH>`, `<BASE_SHA>`, `<HEAD_SHA>`, spec paths) before dispatching.

Triage: P0/P1 → fixer loop. P2 → orchestrator's discretion (fix now or log). Any `spec_review`
finding → decide direction before dispatching a fixer (see "Spec is the source of truth").

---

## Fixer prompt template

```
You implemented [task/wave description] and review found issues.

## Worktree
WORKTREE_PATH: <path>
All edits and git operations inside this path.
cd to WORKTREE_PATH before any work.

## Issues to fix
[Paste gate findings verbatim — from the code-review subagent or advisor(),
 every finding, no paraphrasing. Include file:line and dimension per finding.]

## Fix rules
- Fix exactly what is listed — do not refactor unrelated code
- Re-run any tests covering modified files
- Commit the fix: git commit -m "fix: <finding summary>"

## Report back
STATUS: DONE | BLOCKED
COMMIT: [SHA]
FIXED: [what you changed per finding]
```

---

## Handling cursor-agent output

| Output contains | Action |
|-----------------|--------|
| STATUS: DONE | Verify commit SHA in git log, enter code gate Tier 1 (cursor-agent reviewer) |
| STATUS: DONE_WITH_CONCERNS | Read concerns — correctness issues block the code gate; observations proceed |
| STATUS: BLOCKED | Provide missing context, re-dispatch |
| STATUS: NEEDS_CONTEXT | Answer question, re-dispatch |
| No STATUS line | Treat as DONE_WITH_CONCERNS — scan for blockers |

---

## Red flags — stop immediately

- **Writing, editing, or touching implementation code yourself — IRON LAW violation**
- **"Just this once / quick fix / unblock myself" — still violation**
- Skipping code gate Tier 1, Tier 2, or `advisor()` because "looks fine"
- Advancing to Tier 2 before Tier 1 is clean, or to process gate before both tiers clean
- Letting either reviewer edit code, or scan the whole tree instead of the wave diff
- Advancing to next wave before BOTH code gate tiers AND process gate clean on current wave
- Announcing completion before the final advisor() process gate is clean
- Dispatching fixer without quoting specific gate findings verbatim
- Letting the review subagent (or cursor) decide spec-correctness — that's orchestrator + advisor + user
- Running cursor-agent against main repo instead of worktree
- Ignoring BLOCKED — never retry without changing something

---

## Learned Rules

### scan-output-schema-drift | fired:1 | 2026-06-11
Parallel scan agents (8/11) ignored the specified JSON schema and used their own field names (no `status` field), making programmatic tally impossible and forcing REPORT.md to rely on prose summaries → wrong.
Prevent: in scan prompts, include a COMPLETE concrete example finding as JSON (all required fields named explicitly), not just a prose schema description. State "your output MUST match this exact structure".

### hono-cenv-undefined-in-tests | fired:1 | 2026-06-11
cursor-agent wrote `(c.env as { ENVIRONMENT?: string }).ENVIRONMENT` without optional chaining. Hono test apps created with `new Hono()` have `c.env === undefined` → TypeError crash, 4 tests broken after merge → wrong.
Prevent: code gate must flag any `c.env.X` or `(c.env as T).X` that lacks `?.` optional chaining. Hono tests never pass env bindings; every c.env access must be `(c.env as T | undefined)?.X`.

### for-update-on-aggregate-query | fired:1 | 2026-06-11
cursor-agent added `FOR UPDATE` to an aggregate `SUM(...)` query → Postgres rejects at runtime ("FOR UPDATE is not allowed with aggregate functions"); not caught by tsc or vitest → P1 regression found in next rescan.
Prevent: code gate must explicitly verify SQL `FOR UPDATE` additions — confirm the locked query is a plain row-select, not an aggregate. Only `SELECT … WHERE id=? FOR UPDATE` is valid; `SELECT SUM(…) FOR UPDATE` is always wrong.

### cursor-agent-incidental-edits-in-gitignored-docs | fired:1 | 2026-06-11
cursor-agent editing a spec file introduced a typo (`/g`-flag → `/g`/flag) beyond its stated task scope. Orchestrator copied 2 of 4 edited specs sight-unseen; the typo was only caught because the other 2 were diffed against originals.
Prevent: after any cursor-agent wave that touches docs/specs (gitignored), diff EVERY edited file against the pre-edit original before accepting. Use `diff <original> <worktree-copy>` per file; do not skip any file because it "only had the expected edits."

### grep-field-rename-sweep-no-head-truncation | fired:1 | 2026-06-11
After spec field-rename (ruleId→id, source→engine), verification grep used `| head -40` on a 380-line spec. Lines 89-144 (5 of 7 stale refs) were cut; only seen after advisor forced unbounded re-run.
Prevent: field-rename consistency sweeps (`grep -n "oldField" spec.md`) must NEVER pipe to `| head -N`. Run unbounded; classify every hit (live-violation field vs stored/row field) before declaring clean.

### fixer-git-add-A-sweeps-untracked | fired:1 | 2026-06-11
Fixer prompt said `git add -A && git commit` for a one-line fix → swept untracked `docs/` (spec+plan) into the commit, violating the repo's "git = runtime only, no docs" policy; needed `git rm --cached` + `--amend` to undo → wrong.
Prevent: in implementer/fixer prompts, NEVER instruct `git add -A` or `git add .`. Name the exact file(s) the task touches (`git add src/foo.mjs`). Untracked specs/plans/docs stay untracked unless user explicitly opted in.

### big-picture-wave-ahead-warning-is-p1 | fired:1 | 2026-06-11
Code-reviewer BIG_PICTURE noted "Wave-3 heads-up: 3 strings will break e2e" after Wave 2 review. Orchestrator accepted the deferral and advanced. Advisor blocked Wave 3 dispatch and forced a fixer loop → wrong.
Prevent: scan BIG_PICTURE for any "will break in Wave N / heads-up" language. Treat as implicit P1 — dispatch a fixer loop before advancing, same as an explicit P1 finding. BIG_PICTURE warnings are not advisory notes; they are deferred blockers.

### ctx-search-prompt-echo-means-log-file | fired:3 | 2026-06-11
After cursor-agent dispatch, ctx_search / ctx_execute returned only the echoed prompt or an "indexed N sections" notice — not the agent's report. Orchestrator risked advancing past a gate without confirming results; also wasted a step grepping the workspace's own `tmp/logs` (empty). → wrong.
Prevent: when a cursor dispatch returns only prompt echo or "Indexed … sections", the report is in the raw log, NOT the index. `ca.sh` ALWAYS tees to `$HOME/Projects/multideal/tmp/logs/<datetime>-<slug>.log` regardless of `--workspace` — never look under the workspace repo. Read it: `LOG=$(ls -t ~/Projects/multideal/tmp/logs/*<slug>* | head -1)`; for JSON-event logs parse assistant `text` blocks (python) to pull the VERDICT/STATUS block, plain `grep -aE` for simple logs.

### inline-prompt-bad-substitution | fired:1 | 2026-06-11
Multi-KB prompt passed inline as `--trust "PROMPT"` inside ctx_execute shell code contained `${{ secrets.X }}` (GitHub Actions syntax) → bash "bad substitution" killed the dispatch before cursor-agent started → wrong.
Prevent: ALWAYS Write the prompt to a tmp file first, then dispatch with `PROMPT=$(cat file)` + `--trust "$PROMPT"`. Never inline prompts containing `${`, backticks, or `$((` into shell code.

### parallel-migration-number-collision | fired:1 | 2026-06-11
Two parallel cursor-agents (checkout + printful tasks) each created `drizzle/migrations/0038_*.sql` off the same base → duplicate number + journal conflict at merge; needed a fixer to renumber and repair `_journal.json` → wrong.
Prevent: when 2+ parallel tasks may add DB migrations, pre-assign each task a distinct migration number in its dispatch prompt ("your migration is 0039"). Verify journal/filename consistency at merge time.

### cf-env-access-gate-grep | fired:1 | 2026-06-11
Despite explicit G3 instruction in prompts, one implementer invented an `await import("cloudflare:workers")` env fallback and another left module-scope `import.meta.env` secret reads (undefined at CF Pages runtime → silent 401s); both reached merge, caught only by external gate → wrong.
Prevent: on CF Pages projects, gate/orchestrator must run `grep -n "import.meta.env\|cloudflare:workers" <changed files>` over every wave diff — any hit in an API route or lib is an automatic P1; env reads belong inside handlers via `locals.runtime.env`.

### gate-validated-single-unit-not-composed-topology | fired:1 | 2026-06-12
i18n pilot proved React context reaches a consumer in ONE self-wrapped island; both code gates passed it, but neither exercised the realistic topology (switcher + content as TWO sibling islands = separate React roots). Advisor caught that context doesn't cross island boundaries — the actual integration constraint went untested. Same class: a pattern (per-locale routing) was promoted to DEFAULT in the canonical doc with NO executed test of that pattern, and its only bug (buildLocalePath double-prefix) hid there → wrong.
Prevent: when a wave's thesis is "X works / crosses boundary B", the gate must require an executed test of the REALISTIC COMPOSED topology (multiple units, separate roots, production layout) — not the single-unit happy path. Never label a pattern "proven/default" in a durable doc unless a test runs THAT pattern end-to-end.

### spec-finding-overstates-vs-harness-runtime | fired:2 | 2026-06-12
Wrote spec success-criterion + Finding claiming the consumer "server re-renders in the new locale" and routing was "demonstrated" — but the harness is `output:'static'` (pages prerendered at BUILD time; request-time `resolveLocale` negotiation never runs). Advisor flagged the durable spec text overstated what the static harness proved (twice: criterion #2 enshrined the niche mechanism, then Finding #3 the SSR claim) → wrong, same spec-must-match-reality rule that gates code.
Prevent: before writing "proven/demonstrated/re-renders" in a spec or findings doc, check the harness's actual runtime mode (static prerender vs request-time SSR) and scope each claim to what the executed test exercised; cite the test + note what is unit-tested-only vs integration-exercised.

### worktree-fresh-checkout-full-suite-env-red | fired:1 | 2026-06-11
Plan's per-wave verify step said `npm test && npm run self-test` (full suite). In a FRESH `git worktree`, the full suite was already RED — 6 pre-existing failures from gitignored fixtures not checked out (`rules/baseline/fixtures/`) + absent external checker bins (tsc/knip/jscpd/depcruise/type-coverage/ast-grep). Trusting that step would mask real regressions and could trigger phantom fixer loops on env failures → wrong.
Prevent: before trusting any "run full test suite" verify step in a fresh worktree, run it ONCE yourself to baseline. If red for env reasons (gitignored fixtures absent, external bins missing), OVERRIDE the plan: narrow the implementer/fixer/reviewer verify to the feature's own `*.test` files, and state the env-red baseline in the dispatch prompt so cursor never chases pre-existing failures. (Advisor's "baseline before trusting Step 5" catch.)
### acceptance-must-recheck-all-gate-metrics | fired:1 | 2026-06-12
cursor moved width correction post-limiter; its acceptance verified width+TP only — LUFS crushed −3.5LU, PLR blown, caught one full eval run later → wrong. Fixing one gate metric silently broke two others.
Prevent: any fix touching gain staging or stage ordering in a multi-metric-gated pipeline must re-verify EVERY gate metric in the acceptance, not just the failing one. Orchestrator: reject DONE reports whose verification list is shorter than the gate's check list.

### out-of-band-tasks-missing-from-gate-prompt | fired:1 | 2026-06-12
Wave included DB fix (Neon MCP UPDATE) and gitignored fixture edit — both invisible to the worktree. Dispatched Tier 1 code gate without noting these facts. Reviewer flagged both as P1 blockers; orchestrator had to triage them as false positives and manually pre-contextualize Tier 2 → wasted triage round.
Prevent: before writing any code gate prompt, scan the wave's task list for tasks done out-of-band (Neon MCP mutations, gitignored file edits, manual infra changes). Add a "CRITICAL CONTEXT — out-of-band fixes already applied" section at the top with task name, what was done, and verification evidence. Both Tier 1 and Tier 2 prompts need this section; do NOT skip Tier 1 expecting Tier 2 to be cleaner.

### db-update-merge-aggregate-string-uniformity | fired:1 | 2026-06-12
After `UPDATE vendor_addresses SET city_code='tel-aviv' WHERE city_code='TLV'`, only verified `city_code` was uniform (0 TLV rows). Didn't check `city` name string uniformity. Query uses `MIN(va.city)` — if merged rows had mixed He/En names, MIN returns English (Latin sorts before Hebrew in PG collation) → wrong-language label ships silently. Advisor caught before deploy; query confirmed 1 row (all Hebrew).
Prevent: after any UPDATE that coalesces rows under a new key where a string column will be aggregated by MIN/MAX in a downstream query, run `SELECT <agg_col>, <key_col>, COUNT(*) FROM <table> WHERE <key_col>='<new_val>' GROUP BY <agg_col>, <key_col>` — expect exactly 1 row. Add this verification as a process gate step before deploy.

### ctx-execute-language-shell-not-bash | fired:1 | 2026-06-12
ctx_execute called with `language: "bash"` → `invalid_enum_value` error at runtime. The skill's own invocation example had the wrong value ("bash") — now fixed to "shell".
Prevent: always use `language: "shell"` (NOT "bash") for shell commands in ctx_execute. Valid enum values: javascript, typescript, python, shell, ruby, go, rust, php, perl, r, elixir, csharp.

### gate-misses-orphan-committed-files | fired:1 | 2026-06-13
cursor force-committed two orphans the two-tier code gate (diff-only) structurally missed: a unit test under a then-gitignored path (the harness was uncommitted at the time) and a drizzle journal half-entry (idx added, no matching `meta/NNNN_snapshot.json`). Both = a committed file depending on uncommitted state → breaks on fresh checkout / next `db:generate`. Advisor caught both → wrong.
Prevent: code gate reviews the DIFF and cannot see tree-vs-policy. Before declaring a branch merge-ready, run two tree checks yourself: (1) `git ls-tree -r --name-only HEAD | git check-ignore --stdin` must be EMPTY (no committed file is gitignored); (2) no committed file imports/depends on an uncommitted one (test→harness, migration journal entry→snapshot). Any hit → cursor `git rm --cached` (keep local) before merge.

### plan-verify-gates-dropped-in-prompt-translation | fired:1 | 2026-06-15
Plan Task 3 listed 3 verify gates (typecheck / parity / **host-suite**); the cursor implementer prompt's Step 3 carried only 2 — host-suite silently dropped in the plan→prompt handoff. The unevaluated gate read green; only the final advisor process gate caught it → wrong. (Distinct from acceptance-must-recheck-all-gate-metrics: that fires when ACCEPTING a DONE report; this fires earlier, at prompt authoring.)
Prevent: when authoring a cursor implementer/verify prompt, enumerate the PLAN's verify gates 1:1 and confirm none dropped; reject any DONE report whose verify list is shorter than the PLAN's gate list (compare against the plan, not the prompt you wrote). A gate present in the plan but absent from the executing prompt is unevaluated, not passed.

### typecheck-not-enough-before-merge | fired:1 | 2026-06-16
Both code gate tiers passed on typecheck-only; orchestrator moved toward merge without running build. Advisor blocked: "build hasn't run" is a BLOCK — typecheck ≠ bundle → wrong.
Prevent: after all code gates clean, before merge, run `pnpm --filter <app> build` (or equivalent) and confirm exit 0 + `dist/server/entry.mjs` exists. The standing gate = typecheck+test+build+verify; typecheck is rung 1 of 4, not the whole gate.

### spec-count-claims-must-self-check | fired:1 | 2026-06-16
Wave 2 spec intro said "12 hardcoded" while spec body said "15 occurrences" — internal inconsistency not caught before dispatch; Opus Tier 2 flagged it as P2 spec drift → wrong.
Prevent: before dispatching any wave, grep the spec for all numeric literals and verify each count against a real `grep -c` on the target files. Never assume counts written in different spec sections agree — cross-check them explicitly.

### review-range-from-rev-parse-head | fired:1 | 2026-06-16
Defined the code-gate range from a dispatch's self-reported `COMMIT:` sha (`…65d6ed1da`); a later coding dispatch had already committed `4c5ec0223` on top + a mid-process `git reset` discarded uncommitted work → gate range excluded the real tip, money-path commit went un-gated until an Opus-vs-Tier1 conflict exposed it → wrong.
Prevent: define EVERY review range from a fresh `git -C <wt> rev-parse HEAD` taken at gate time, never from a dispatch's self-reported `COMMIT:` line (a dispatch can reset + emit multiple commits, so a reported sha may be an intermediate). After parsing any DONE report, `rev-parse HEAD` before building the range.

### no-unverified-provenance-in-reports | fired:1 | 2026-06-16
Wrote a causal provenance story ("a fixer committed beyond scope") into the embed report from memory/inference, not evidence; advisor flagged it, and the "corrected" version still over-claimed per-commit attribution the logs didn't support. Also skipped a `git reset` reflog line I'd already pulled → wrong.
Prevent: a report states only what `git reflog`/dispatch-logs PROVE (who committed = whichever log shows the `git commit` call; audits proven read-only = 0 commit calls). Never enshrine a causal "dispatch X did Y" claim without log evidence; if one bounded check doesn't resolve attribution, soften to proven facts — don't spiral into archaeology. Read every line of any git/reflog output you pulled (no skipped signals).

### gitignore-pattern-inert-mid-slash-anchor | fired:1 | 2026-06-17
multideal's live-suite ignores (`tests/admin/`, `tests/vendor/`, `tests/customer/`) were written assuming repo-root = app, but a gitignore pattern with a MID-STRING slash anchors to the gitignore's directory — so in the monorepo they never matched `apps/web/tests/...`, silently leaving the whole live-credential suite (`.auth-state.json` w/ live cookies, `helpers/auth.ts`) UN-ignored. Pattern present but inert → wrong (broken safety net; no leak only because files were untracked + `git add -u`/no-`-A` discipline held).
Prevent: a gitignore rule for a SENSITIVE/secret path is not "done" when written — PROVE it with `git check-ignore <path>` (output = the file → matched; empty → inert). Mid-slash patterns are dir-anchored; in a monorepo anchor to the real subtree (`apps/web/tests/<dir>/`), and re-verify both directions (secret→IGNORED, committed suite→still tracked).

### rm-d-test-is-rot-candidate-run-before-recommit | fired:1 | 2026-06-17
Planned to re-commit `VendorLanding.test.tsx` as a KEEP because git history showed it was once tracked (`git rm`'d by a blanket-ignore commit). Advisor blocked: a `git rm`'d test is the textbook ROT candidate, not a keeper — it ran RED (imported a component deleted in a later refactor) → wrong to commit sight-unrun, doubly so since it sat OUTSIDE the gate's `tests/unit` glob (unguarded).
Prevent: never treat "was previously tracked" as KEEP evidence. Before re-committing any resurfaced/untracked test, RUN it against current source (`pnpm exec vitest run <file>`); green→commit, red→Sonnet-fix or drop. Also confirm it falls inside the gate's run glob, else you commit a test nothing guards.

### probe-io-contract-before-trusting-results | fired:1 | 2026-06-19
Verifying the price-slider wave via live API probes, I (a) POSTed `/api/feed` a nested body `{center,radiusKm,filter:{maxPrice}}` → all keys unknown to the flat `feedFilterSchema` → zod SILENTLY stripped them → every probe returned total=20 (looked like the filter was dead), and (b) read `b.priceFloor` top-level when the value was nested under `b.otherFilters.priceFloor` → false "server broken" alarm. Both were MY probe-contract errors, not code defects → wrong.
Prevent: before trusting an orchestrator verification probe, pin its I/O contract to the source — read the endpoint's zod schema for the EXACT request shape (flat vs nested; unknown keys are stripped, not rejected) and log the full response object ONCE to learn real field nesting before asserting any field path. A uniform/unchanged result across varied inputs = suspect your body shape first.

### haiku-too-weak-for-render-observation-use-cursor | fired:1 | 2026-06-19
For the final "does the slider actually RENDER ₪10/₪250+" gate I dispatched a Haiku agent; user corrected "haiku is useless for this, use cursor-orchestrator". The existing "Playwright runs = Haiku" rule covers run-an-existing-spec-and-report-pass/fail, NOT live DOM OBSERVATION that requires finding robust selectors, walking the facet DOM, and judging rendered values → wrong tool.
Prevent: split by cognitive load — Haiku only for "run this committed spec, report N passed/M failed"; any browser task needing selector discovery, DOM traversal, or judgment of rendered values goes to cursor-agent (foreground via ca.sh), never Haiku.

### tailwind-v4-container-namespace-is-shared-with-max-w | fired:1 | 2026-06-19
cursor's Phase-0 ui-tokens build overrode `@theme` `--container-{sm..2xl}` to the viewport scale (40..96rem). `--container-*` is a SHARED Tailwind v4 namespace backing BOTH the `@md:`/`@container` query variants AND the `max-w-*`/`min-w-*`/`w-*` width utilities — so `max-w-md` silently became 48rem (stock 28rem), off the ecosystem standard, plus a non-monotonic scale (`@2xl` 96 > stock `@3xl` 48). tsc + vitest both passed; only seam-reviewer caught it → wrong, would have frozen into an L0 contract.
Prevent: code gate must FLAG any `@theme` declaration/override of `--container-*` in ui-tokens — declare ONLY `--breakpoint-*` (viewport variants) and leave Tailwind's stock `--container-*` for `@md:`/`@container`. The oracle test = compile through Tailwind v4 + assert `max-w-{sm,md,2xl}` resolve to stock 24/28/42rem. `md:`→`@md:` is NOT a threshold-preserving rename (48 vs 28rem).

### contract-fix-must-sweep-all-doctrine-docs-tree-wide | fired:1 | 2026-06-19
After fixing the `--container-*` defect in the spec + code, I left CLAUDE.md / coding-standard §6 / the runsheet still teaching the OLD "ship both `--breakpoint-*`/`--container-*` + override" contract — advisor flagged it a BLOCKER (next agent re-learns the just-fixed defect). Then my verification grep was scoped to a guessed 3-file subset and missed root `CLAUDE.md`/`AGENTS.md` entirely → wrong (under-scoped sweep ≈ no sweep).
Prevent: when a gate fix changes a CONTRACT/rule, treat doctrine-doc propagation as part of the fix — `grep -rn` the changed token/term across the WHOLE repo (docs/ + packages/ + root *.md incl. CLAUDE.md/AGENTS.md/registry.json), update every teaching site in the same pass, then re-grep tree-wide to prove zero residual. Never trust a hand-picked file list as the sweep scope.

### visual-fix-orchestrator-must-view-screenshot-before-merge | fired:1 | 2026-06-20
Both code gates (Tier-1 build-CSS proof + Tier-2 by-construction reasoning) verified the footer/nav clearance CODE, and I was about to FF-merge on cursor's prose "AppShell PASS · Vendor PASS · Desktop PASS" alone. For an occlusion/layout/visual fix the screenshot IS the verification — gates verify code, not pixels — and I'd read Wave-2's screenshots but skipped the merge-commit's. Advisor blocked the merge → wrong.
Prevent: for any visual/occlusion/layout/RTL fix, the ORCHESTRATOR itself READs the verification screenshot of THE COMMIT BEING MERGED before merge — implementer/gate "PASS" prose is never sufficient (verify_artifact_not_agent_report). A missing screenshot = implementer claimed PASS without the artifact → re-dispatch, don't merge.

### cursor-ignores-nuanced-fmt-rule-use-absolute-prohibition | fired:1 | 2026-06-20
Across waves, prompt said "no bare cargo fmt; use `cargo fmt -- <file>` only" — cursor ran bare `cargo fmt` on EVERY dispatch anyway, reformatting 6 out-of-scope pre-existing fmt-debt files into the working tree, forcing `git checkout` cleanup each wave → wrong. Changing to an ABSOLUTE ban ("do NOT run cargo fmt at ALL; edit by hand matching indentation") + a pre-commit `git diff --cached` self-check left the tree clean, zero pollution.
Prevent: when a target file carries out-of-scope fmt/lint debt, give cursor an ABSOLUTE "never run the formatter" prohibition (not a conditional/scoped one — the composer model ignores nuance) AND a "verify `git diff --cached` is ONLY the intended lines before commit" gate. Conditional fmt instructions are reliably ignored.

### baseline-assert-by-fingerprint-not-file-count | fired:1 | 2026-06-21
Cursor baseline prompt asserted "final entry count == 32 / exactly 26 drizzle entries" derived from the 26-FILE list. The slopgate ratchet baselines per-violation FINGERPRINT, and 8 files imported `@zync/db` twice → 34 drizzle fingerprints. Cursor correctly flagged the mismatch in CONCERNS rather than break to hit 32 → my assert was wrong (violation-count ≠ file-count).
Prevent: when authoring a baseline/grandfather prompt, NEVER hard-assert an entry count equal to the file count — a file can carry N>1 fingerprints (multiple matching lines/imports). Assert by membership instead ("every baselined entry's `file` is one of the listed set; no entries outside it") and let the engine-computed count stand. Pre-count actual matches (e.g. `grep -c` the pattern per file) before writing any equality assert.

### jsdoc-star-slash-closes-block-comment | fired:1 | 2026-06-21
Inline carve-out edit of a `reserveStock.ts` JSDoc used a `*/30` literal inside a `/** */` block → the `*/` CLOSED the comment → eslint `Parsing error: ';' expected`; lint-staged reverted it (no broken commit landed) → wrong.
Prevent: NEVER put a literal `*/` (cron `*/N`, a glob, integer math) inside a `/* */` or `/** */` block comment — it terminates the comment. Rephrase ("every-30-minute"), or move it out of the comment; scan any block-comment edit for a stray `*/` before saving. Statically detectable (bucket A/B) but language-universal → propose-only, never self-apply a global slopgate rule on one session.

### worktree-skips-gitignored-tooling-files | fired:2 | 2026-06-21
Dispatched an authoritative `pnpm gate` into a bare `git worktree` that lacked `apps/web/scripts/cpu-limit.sh` + `vitest.config.ts` → run died `./scripts/cpu-limit.sh: not found`. A `git worktree` checks out only TRACKED files; gitignored/untracked tooling the gate depends on is never materialized → wrong (spurious gate-red, NOT fix-caused). Distinct from pnpm-install-after-worktree-merge (node_modules) — this is untracked source tooling. SECOND firing (silent, NO error): worktree lacked the gitignored `.prettierrc.json` (printWidth 100 / singleQuote) that main carries → lint-staged's prettier fell back to its DEFAULTS (printWidth 80, double quotes) and reformatted EVERY touched file single→double + rewrapped calls, churning ~70 lines per commit. No error surfaced — the gate passed; the damage was a whole-file style flip needing a follow-up reformat commit.
Prevent: before running any gate/build/COMMIT (lint-staged runs on commit) in a fresh worktree, copy ALL gitignored-but-required tooling from the main checkout first — not just gate scripts but FORMATTER/LINT configs (`.prettierrc*`, `.prettierignore`, eslint config, `vitest.config.ts`, `scripts/*.sh`) — via `git -C <main> ls-files --others --ignored --exclude-standard` ∩ deps. A missing config fails SILENTLY (tool uses defaults), not loudly. Never read a worktree gate-red as fix-caused, and never trust a worktree commit's formatting, until tooling presence is confirmed.

### ca-sh-grok-pin-403-when-credits-out-switch-cursor-model | fired:1 | 2026-06-21
A Wave-3 cursor dispatch via `ca.sh` died exit=1 / HTTP 403 "personal-team-blocked:spending-limit … need a Grok subscription". `ca.sh` PINS `--model composer-2.5`, which routes through grok.com (separate billing from Cursor); its `run_grok` fallback (grok-composer-2.5-fast) is ALSO grok → both dead when the grok account hits its limit. Looked like a code/prompt failure but was pure billing-channel exhaustion → wrong to retry the same prompt.
Prevent: on a `ca.sh` dispatch that 403s with a Grok/spending-limit/subscription message, do NOT re-dispatch verbatim — the grok channel is out of credit. Switch to a direct `cursor-agent --model gpt-5.2-codex` (or a `claude-opus-4-8-*` cursor model) which bills on Cursor's quota, bypassing the `composer-2.5`/grok pin. Distinguish billing-channel 403 (switch model) from a real BLOCKED (rewrite prompt) by reading the error body before retrying.

### captured-dom-ref-regression-blind-under-react-keying | fired:1 | 2026-06-22
cursor wrote a focus-follows-item a11y test that captured the focused button ONCE (`const btn = getByRole(...)`) then asserted `document.activeElement === btn` after each reorder → wrong: it passes even under index-keying, because React reuses the slot DOM node in place (rewrites its label, keeps the node), so the captured ref stays `=== activeElement` — the test is blind to the exact regression it claims to guard. Looked correct line-by-line; the gate caught it on read.
Prevent: a test asserting an element TRACKS a logical item across DOM churn (reorder, sort, insert) MUST re-query by stable accessible identity (role+name) on EACH iteration, never compare against a reference captured before the churn. Before accepting any such test, ask "does this still pass if the render is keyed by index?" — if yes, the assertion proves nothing. Verify the negative empirically (probe the broken keying, confirm the test FAILS, revert the probe).

### gate-must-run-affected-package-suite-on-interaction-swap | fired:1 | 2026-06-24
W3 swapped a component's interaction model (`<Select>` combobox → `<PalettePicker>` RadioGroup) in SettingsScreen. Both code-gate tiers diff-reviewed the SWAP correctly, but neither RAN `@app/mod-cms`'s existing vitest suite — so stale test helpers (`getByRole('combobox'|'option')`, `ArrowDown` locators) targeting the OLD role set survived into the merge and failed only at the post-merge cold gate (fixed after the fact in `2f02192`) → wrong. A diff review reads the changed file; it does not exercise the OTHER files (tests) whose assumptions the change just invalidated.
Prevent: when a wave changes a component's interaction contract (role set, ARIA roles, query surface, public props), the gate MUST run that package's FULL existing test suite (`pnpm --filter <exact-pkg> test`) — not only tests for files in the diff — before declaring CLEAN. The defect lives in unchanged test helpers the diff never touched; reading the diff cannot catch it. Bucket C (needs run-time + the suite's own locators) → text rule only, no slopgate.

### cold-gate-failure-not-refuted-by-warm-cache-rerun | fired:1 | 2026-06-24
A post-merge COLD `pnpm gate` failed on a consumer i18n Playwright test; I "refuted" it by re-running the gate, which passed — but those reruns were WARM (turbo cache) and re-served the prior PASS, proving nothing. Advisor flagged it: a cold failure cannot be cleared by a warm rerun, and W4 had just added `@tailwindcss/vite` to apps/consumer — a credible regression mechanism left unexamined → wrong to call it flake on warm-green alone.
Prevent: to discriminate a transient concurrency flake from a real regression, reproduce COLD under load — `rm -rf .turbo && pnpm run test` (assert `0 cached / N total`), repeat ≥2×, and read the previously-flaking test's actual timing (passed at 397ms vs 5000ms timeout = benign). A warm-cache green NEVER refutes a cold-cache red; only a cold-cache green does.

### confirmation-framed-scan-biases-to-rubber-stamp | fired:1 | 2026-06-26
Final Tier-2 Opus scan was framed "confirm these N fixes hold — print CLEAN if all pass" → biased to rubber-stamp the named fixes without re-testing completeness; missed that ~12 of 24 client strings were still hardcoded English outside the bridge pattern → advisor caught it post-CLEAN → wrong.
Prevent: NEVER frame the final Tier-2 re-scan as "confirm these fixes hold". Frame it adversarially on the DIMENSION: "Is every client-rendered string sourced uniformly from the bridge? List any exception." Confirmation framing (confirm X) biases to pass; dimension framing (is X uniform across all instances?) forces a completeness check.

### pattern-fix-must-address-all-instances-not-named-subset | fired:1 | 2026-06-26
Four Tier-2 review rounds each named a subset of un-bridged strings; each fixer addressed only the named subset; the next scan found the remaining ones → 4 rounds to close what should have been 1 → wrong. Root cause: fixer prompt listed specific strings instead of the PATTERN to apply uniformly.
Prevent: when a finding says "N of M instances follow pattern P, the rest don't" — the fixer prompt MUST say "apply pattern P to ALL instances, not just those named; grep for every occurrence of [anti-pattern] and fix each one." Never let a finding name the subset and leave the rest implicit.

### split-fix-batch-by-priority-tier-not-by-count | fired:1 | 2026-06-26
Eight P2 findings sent as one 217-line cursor fix prompt → 58-min timeout; had to split into two separate dispatches (fix3a = P1s, fix3b = P2s) → wrong to batch all findings together.
Prevent: when a review produces >4 findings, proactively split into ≤2 dispatches by priority tier: one for P0/P1 (blocking), one for P2 (advisory). Each batch stays ≤~100 prompt lines. Don't wait for a timeout to learn the batch was too big. Bucket C → text rule only.

### cold-gate-dep-order-must-chain-not-separate-steps | fired:1 | 2026-06-26
Cold gate prompt listed ui-primitives build + mod-cms typecheck as separate numbered steps → cursor-agent batched all 5 commands in parallel → mod-cms tsc ran before build finished → false GATE_FAIL on "no exported member 'Skeleton'" → wrong.
Prevent: when gate commands have a dependency order (e.g. build package A before typechecking consumer B), chain them as ONE shell command with `&&` inside a single step — `pnpm --filter A build && pnpm --filter B typecheck` — never as separate numbered steps that cursor can parallelize. Bucket C → text rule only.

### negative-assert-needs-positive-companion | fired:1 | 2026-06-30
Wrote `expect(html).not.toContain('javascript:')` with no positive companion → passes vacuously if the code path stops rendering entirely → advisor blocked merge → wrong.
Prevent: every negative-only assertion ("bad thing stripped") must pair with a positive companion proving the code path fired (e.g. assert the field label, surrounding markup, or safe replacement also renders). Code gate: flag any standalone `not.toContain`/`not.toMatch` on a sanitized sink with no companion positive assert on the same render path. Bucket C → text rule only.
