# Linear

> Linear feedback resolution pipeline. Auto-detects intent (fix feedbacks by default), loads the 10-step protocol with DYNAMIC audit chain (audit-selector.py picks 4-12 relevant audits per ticket, scoped to the ticket only), dispatches sequential work sessions, Oracle quality gate. Use when user says "/linear", "linear", "fix linear", "resolve feedback", "regler les feedbacks".

- Skill: `agentik-os/linear-2` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add agentik-os/linear-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentik-os/linear-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: agentik-os (https://skillmd.com/u/agentik-os)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/agentik-os/linear-2

---


# /linear — Linear Feedback Resolution Pipeline (v4.1)

<linear-skill>

> **📦 PORTABLE INSTALL NOTE (Claude desktop app).** This skill was authored for the Agentik OS
> VPS. All VPS dependencies are bundled inside this skill's `references/` folder:
> `43-linear-ticket-pipeline.md` (the full protocol), `audit-selector.py`, `dispatch-to-session.sh`,
> `linear-mission.sh`, `ram-guard.sh`, `linear-ticket-gate.sh`. Whenever this file says
> `Read ~/.claude/docs/rules-archive/43-linear-ticket-pipeline.md`, read
> `references/43-linear-ticket-pipeline.md` instead. The dispatch / tmux / oracle steps assume
> the Omega multi-session infra; on a plain desktop install, run the protocol **single-threaded in
> the current session** (do each ticket sequentially yourself) instead of dispatching workers.
> Requires the Linear MCP connector to be enabled in the Claude app.

## MANDATORY FIRST STEP — READ THE FULL RULES DOC

**Before doing ANYTHING else**, Read the complete canonical protocol (bundled with this skill):

```
Read references/43-linear-ticket-pipeline.md
```

That file is the **single source of truth** for the Linear workflow. This skill file is
a launcher — the rules file contains the full 10-step protocol, strict comment template,
banned patterns, BEFORE/AFTER Playwright captures, auth bypass setup, quality gate
criteria, audit scoping, 100/100 threshold, and Move-to-In-Review logic (Gareth alone marks Done). If anything in this
launcher contradicts the rules file, **the rules file wins**.

Never execute `/linear` without having Read that file in the current session.

## AUTO INTENT DETECTION (three modes)

When `/linear` is invoked, detect intent by scanning the user's full prompt for keywords
AND by inspecting each fetched ticket's last comment author. Read the prompt as a whole —
never blindly pattern-match one word out of context.

### Mode 1 — FIX (default)
**Triggers:** no keyword, or any of: `fix`, `resolve`, `feedback`, `regler`, `corriger`, `tickets ouverts`, `open tickets`
**Action:** fetch OPEN tickets (`state.type NOT IN ["completed", "canceled"]`), run full 10-step protocol.

### Mode 2 — VERIFY-DONE
**Triggers (any of):** `verify`, `verifie`, `vérifie`, `reverifie`, `revérifie`, `re-verify`, `recheck`,
`done`, `revérifier les tasks`, `check done`, `audit done`, `validate done`, `nouveau protocole`, `new protocol`
**Action:** fetch DONE tickets (`state.type IN ["completed"]`), run **Step 8 only** (targeted DYNAMIC audit chain,
audit-selector.py picks 4-12 audits per ticket, 100/100 threshold each + adversarial confirmed +
intent Q1-Q5 PASS). Re-open any ticket that fails.

### Mode 3 — RE-REVIEW (auto-detected per-ticket, no user keyword needed)
**Trigger (per ticket):** ticket state is NOT Done/Completed (Backlog, Todo, In Progress, …)
AND `lastComment.user.name` (case-insensitive) contains `"gareth"`.
**Meaning:** Gareth has rejected the previous fix and re-opened the ticket with a new comment
(typically text + screenshot explaining what's still wrong).
**Action:** treat as a fresh fix iteration where Gareth's latest comment is the **new authoritative spec**:
1. Read the FULL ticket: original description + EVERY comment in chronological order + EVERY screenshot.
2. Download Gareth's latest screenshot (if any) via `curl -sL -H "Authorization: $LINEAR_API_KEY" URL -o file.jpg` and Read it.
3. Re-run the full 10-step protocol, but the BEFORE state must reflect the CURRENT state of the page
   (which is the supposedly-fixed version), and the fix must address Gareth's specific complaint.
4. The Linear comment for the new fix MUST quote Gareth's latest message verbatim under
   "What was requested" (instead of the original ticket description).

### Mode 4 — REGRESSION (auto-detected per-ticket, NEW 2026-05-08)
**Trigger (per ticket):** ticket state is NOT Done/Completed (Backlog, Todo, In Progress)
AND `comments.nodes` contains ≥1 prior `## Fix Verification Report` (or `Status: RESOLVED`, `Status: RÉSOLU`) by the bot/oracle
AND (state transition history shows it was previously Done/Completed OR last bot comment is followed by a state revert).
**Meaning:** Gareth (or a teammate) moved a previously-"fixed" ticket back to Backlog because **the fix did NOT actually work** OR the feature is not yet live. The prior verification report is a LIE — runtime did not match the claim. First Law violation.
**Action:** This is the most serious mode. The previous fix attempts must be treated as confirmed failures, not as starting points. Workflow:
1. **Read EVERY prior Fix Verification Report comment** chronologically. Extract: commit hash, files changed, claimed root cause, claimed fix.
2. **List the failed attempts** in the new prompt (see WORKER PROMPT TEMPLATE — `PRIOR FAILED ATTEMPTS` block, mandatory in Mode 4).
3. **Forbid repeating the same approach.** If the prior fix touched `src/auth/foo.ts:42` with approach X, the new attempt MUST either:
   - touch a different layer (root cause is upstream/downstream of the prior fix), OR
   - apply a fundamentally different technique on the same line, OR
   - explicitly explain why the prior fix was correct in code but the issue is environmental (deployment, caching, feature flag, env var, build artifact).
4. **Run a deeper diagnosis FIRST** (before proposing any fix): live runtime evidence per First Law — actual logs from prod, actual screenshots NOW (not "should look like"), actual network requests, actual console state. Hypothesis-driven, not pattern-match.
5. **The new comment MUST include a "Why prior attempts failed" section** quoting each prior verification report and explaining the false-positive cause. Without this section the gate fails.

### Resolution rules
1. If both Fix and Verify keywords present → ask user which mode (don't guess).
2. If zero tickets in the chosen mode → report empty, do not fallback silently.
3. If `/linear` is typed alone with no keywords → default = Fix mode.
4. **Mode priority (when multiple match for a single ticket):** Mode 4 (REGRESSION) > Mode 3 (RE-REVIEW) > Mode 1 (FIX). Mode 4 wins because the prior-failure data is more important than the latest comment author check.
5. Always echo the detected mode to the user before starting:
   - Mode 3: `Mode detected: RE-REVIEW for {TICKET-ID} — Gareth posted feedback at {timestamp}, treating as fresh fix with new spec.`
   - Mode 4: `Mode detected: REGRESSION for {TICKET-ID} — {N} prior Fix Verification Report(s) by bot, ticket back in {state}. Treating prior fixes as confirmed failures, deep root-cause analysis required.`

## LINEAR API KEY LOOKUP

```bash
LINEAR_API_KEY=$(cat .mcp.json | python3 -c "import json,sys; print(json.load(sys.stdin)['mcpServers']['linear']['env']['LINEAR_API_KEY'])" 2>/dev/null)
LINEAR_API_KEY=${LINEAR_API_KEY:-$(grep '^LINEAR_API_KEY=' .env.local 2>/dev/null | cut -d= -f2)}
```

---

## THE 10-STEP PROTOCOL (10/10 standard, non-negotiable)

```
1. DEEP ANALYSIS            Download screenshots from comments, Read each, parse description
2. BEFORE CAPTURE           tunnel-browser.sh → auth cascade → screenshot + console BEFORE any code
3. IMPLEMENT FIX            Based on complete analysis, npm run build must pass
4. AFTER CAPTURE            tunnel-browser.sh → same auth → screenshot + console AFTER fix
4.1 MULTI-STEP AFTER        If ticket describes a workflow/conversation/N-phase flow, capture
                            after-step-1.png … after-step-N.png — one screenshot per stage.
                            Triggers: workflow, agent, chat, étape, phase, wizard, onboarding,
                            "then/ensuite". Single static state → keep one after.jpg.
5. SELF VERIFY              5 mandatory questions, all YES (covers ALL stages if multi-step)
6. STRICT COMMENT           Exact template with Before/After (all stages) + verify URL + console diff
7. QUALITY GATE COMMENT     Oracle verifies comment has all required sections
8.  DYNAMIC AUDIT CHAIN     audit-selector.py picks 4-12 audits per ticket (mission-aware), parallel, 100/100 each + adversarial dual-pass + intent verification
8b. FIX-AND-REAUDIT LOOP    If any audit < 100, fix every finding, re-run failing audits (max 5 iter)
8c. INTENT VERIFICATION     /featureaudit-style — does AFTER state match user's stated need? Live runtime evidence + user quote vs measured behavior. 100/100 strict.
9.  MOVE TO "In Review"     Oracle moves to "In Review: Gareth" — NEVER Done (Gareth alone marks Done)
10. POST-MISSION AUDIT      BLOCKS done_clean. oracle-mark-done.sh invokes oracle-post-mission-audit.sh
                            which unions every done ticket's files_touched, picks 4-12 audits via
                            audit-selector on the cumulative footprint, dispatches them in parallel,
                            gates 100/100 on each, iterates max 5x. Catches regressions only visible
                            in the combined diff that per-ticket Step 8 cannot see.
                            Escape hatch: SKIP_POST_MISSION_AUDIT=1 (tests only — logs WARN).
```

**Auth method (Steps 2 + 4)**: Always call `~/.claude/lib/tunnel-browser.sh`. By default it uses the **JWT method** (dev@agentik-os.com via Clerk Backend API) — reliable, VPS-autonomous, works when user's Mac is off. Set `USE_TUNNEL=1` only when you know the Mac is on AND want to reuse the user's real session (for example when investigating an issue that only reproduces with the user's actual data). **Default = JWT, not tunnel.**

Full protocol reference: `~/.claude/docs/rules-archive/43-linear-ticket-pipeline.md`

---

## MODE 2 — VERIFY-DONE PROTOCOL (re-verification of already-closed tickets)

When Verify-Done mode is detected, the pipeline is SIMPLIFIED. No new fixes, no new
screenshots unless DYNAMIC audit chain fails. Goal: retroactively certify that Done tickets
meet the current quality bar (v4.1, 100/100 threshold).

### Verify-Done step sequence

```
V1. FETCH DONE       graphql: state.type IN ["completed"]
V2. DISPLAY          list tickets with original fix commit hash (parse existing comments)
V2.5. COMMENT CHECK  For each Done ticket: search comment bodies for "Fix Verification" or
                     "Rapport de Vérification". If MISSING:
                     a. Find original fix commit: `git log --oneline --all --grep={TICKET_ID}`
                     b. Extract: commit hash, date, files changed, commit message
                     c. Post retroactive verification comment using template below
                     d. Log: "Posted retroactive comment on {TICKET_ID} — commit {hash}"
                     This step is NON-NEGOTIABLE. A Done ticket without a verification
                     comment is incomplete.
V3. SELECT           user picks: all / last N / by identifier
V4. LOOP (sequential, one ticket at a time):
    a. Read ticket description + original Linear comment (extract files/URL/selector).
       If no verification comment found, Step V2.5 should have already posted one —
       verify it exists before proceeding.
    b. Run Step 8 (Targeted Quadruple Audit) scoped STRICTLY to original fix:
         files_modified  = files from original commit
         page_url        = URL from ticket
         selector        = from ticket
    c. Evaluate: all 4 audits = 100/100 ?
    d. If PASS:
         - Post re-verification comment (template below)
         - Leave ticket Done
    e. If FAIL:
         - Post failure comment with findings
         - REOPEN ticket (set state to "In Progress" or equivalent)
         - Flag for next Fix-mode run
V5. REPORT           X passed, Y failed-and-reopened, Z errors
```

### Re-verification comment template (PASS case)

```markdown
## Re-Verification Report (v4.1 protocol)

**Ticket:** {ID} - {Title}
**Originally resolved:** {original commit} on {original date}
**Re-verified:** {today} under the new 10-step protocol with 100/100 DYNAMIC audit chain

### Targeted Audit Results
| Audit | Score | Status |
|-------|-------|--------|
| codeaudit  | {X}/100 | PASS |
| uiuxaudit  | {X}/100 | PASS |
| flowaudit  | {X}/100 | PASS |
| debugaudit | {X}/100 | PASS |

Scope: original fix files + original ticket URL only.

### Status: RE-VERIFIED (still DONE)
```

### Re-verification comment template (FAIL case → reopen)

```markdown
## Re-Verification Report (v4.1 protocol) — REOPENED

**Ticket:** {ID} - {Title}
**Originally resolved:** {original commit}
**Re-verified:** {today} — FAILED new 100/100 threshold

### Targeted Audit Results
| Audit | Score | Status |
|-------|-------|--------|
| codeaudit  | {X}/100 | {PASS/FAIL} |
| uiuxaudit  | {X}/100 | {PASS/FAIL} |
| flowaudit  | {X}/100 | {PASS/FAIL} |
| debugaudit | {X}/100 | {PASS/FAIL} |

### Findings requiring fix
{bulleted findings from failing audits, only the ones in ticket scope}

### Status: REOPENED — needs new fix under v4.1 protocol
```

### Retroactive verification comment template (for VERIFY-DONE mode, when no original comment exists)

When Step V2.5 detects a Done ticket with NO verification comment, post this template
using data extracted from `git log --all --grep={TICKET_ID}` and `git show --stat {hash}`:

```markdown
## Rapport de Vérification Rétrospective

**Ticket:** {ID} — {title}
**Commit:** {hash from git log}
**Date du fix:** {date from git log}
**Vérifié par:** Claude AI (protocole v4.1 rétrospectif)

### Ce qui a été demandé
> {quote from ticket description}

### Ce qui a été fait
- **Fichiers modifiés:** {files from git show --stat}
- **Changement:** {commit message summary}

### Pourquoi ça fonctionne
{Technical explanation derived from commit message + files changed}

### Statut: RÉSOLU ✅ (commentaire rétrospectif)
```

This is NON-NEGOTIABLE. A Done ticket without any verification comment is incomplete,
even if it was fixed correctly. The retroactive comment provides traceability.

### Reopen mutation

```graphql
mutation {
  issueUpdate(id: "UUID", input: { stateId: "IN_PROGRESS_STATE_UUID" }) { success }
}
```
Query workflowStates first to get the correct `In Progress` state UUID for the team.

### Dispatch rule for Verify-Done

Still SEQUENTIAL (one ticket at a time) but MUCH faster than Fix mode — no BEFORE/AFTER
captures, no code edits. Each ticket = 3 parallel audits then decision. Work session name:
`{Project}-linear-verify-{TICKET_ID}`.

---

## DISPATCH RULE: TRUE SLIDING WINDOW (refill-on-completion, supersedes wait-for-batch) (2026-05-09, supersedes 2026-04-18 wait-for-batch which superseded SEQUENTIAL)

```
WRONG (old):  dispatch 5 -> wait for ALL 5 .done.json -> dispatch next 5 (idle slots while fast workers wait for slow ones)
RIGHT:        maintain pool of POOL_SIZE=5 active workers. As soon as ANY worker writes
              .done.json, gate it AND immediately call next-batch N=1 to refill that slot.
```

**Per oracle wake protocol:**

1. **RAM guard first.** `~/.aisb/lib/ram-guard.sh check 70` — exit 1 (>70% used) → `ScheduleWakeup(180s)` and retry. Exit 0 → proceed. Rationale: each worker can peak at 4–13 GiB.
2. **Count active workers.** `ACTIVE=$(ls ~/.aisb/state/worker-{Project}-worker-*.progress.json 2>/dev/null | xargs -I{} sh -c "test ! -f {%.progress.json}.done.json && echo 1" | wc -l)` — number of in-flight workers (progress file exists, done.json does not).
3. **Compute slots to fill.** `SLOTS=$((POOL_SIZE - ACTIVE))` where `POOL_SIZE=5`.
4. **Drain done workers FIRST.** For every `worker-{Project}-worker-*.done.json` that exists:
   a. DYNAMIC audit chain (audit-selector.py picks 4-12 audits)
   b. `close-gate.sh ack-worker`
   c. `linear-ticket-gate.sh` validates v2 chain
   d. `linear-mission.sh done <ticket-id>`
   e. `linear-move-to-review.sh` (or equivalent state transition)
5. **Refill open slots.** While `SLOTS > 0`:
   - `~/.aisb/lib/linear-mission.sh next-batch <mid> 1 {Project}-worker` — N=1 because each call refills one slot atomically.
   - exit 0 + 1 ticket → dispatch via `dispatch-to-session.sh`, decrement `SLOTS`
   - exit 2 (queue empty) → break refill loop
   - exit 3 (all pending conflict with in-progress) → break refill loop, `ScheduleWakeup(90s)` to wait for slots to free
6. **ScheduleWakeup.** If `ACTIVE > 0` OR pending tickets remain: `ScheduleWakeup(90s)`. Else: mission complete, write `done.json`.

**Why N=1 per call:** keeps the file-conflict packer simple (1 ticket vs in-progress reservation), and each refill is atomic. The lib `next-batch` already handles in_progress file reservation (FIX-SLIDING-WINDOW-01, 2026-04-18).

**POOL_SIZE rationale:** 5 = empirical max for 16GB RAM VPS. Configurable per project via `.orchestrator/linear-config.json` `pool_size` field (default 5).

**Isolation discipline:** unchanged — if `POOL_SIZE > 1` AND workers run on the same project path, each worker MUST operate in its own `git worktree add` copy. Two parallel workers sharing the same `.next/` or git index = corruption. Oracle provisions worktrees before dispatch.

Fix order: not guaranteed — workers run concurrently. The ANALYZER guarantees file-disjointness against in-progress reservations, which is the invariant that matters.

Oracle NEVER edits project code directly. Always dispatch.

---

## STEP 6: STRICT COMMENT TEMPLATE (inline — do NOT improvise)

Post this EXACT template on the Linear ticket. **No free-form. No shortcuts. No lazy paraphrase.**

### BANNED PATTERNS (automatic quality gate FAIL)
- **"Retroactive verification confirms"** — lazy batch comment with zero data
- **"Code changes committed and deployed"** — says nothing useful
- **"No regressions reported"** — unverified claim without evidence
- **Any comment under 800 characters** — too short to contain real before/after data
- **Batch-posting the same template** for multiple tickets — each must be individually crafted
- **Paraphrasing the commit message** instead of quoting the user's original words
- **Mode 4: same files touched as prior failed attempt with no different layer addressed** — gate compares `git diff --name-only` against `prior_attempts.json[*].files_changed` and rejects if identical.
- **Mode 4: missing "## Why prior attempts failed" section** when prior_attempts.json has ≥1 entry.

### Template (FIX mode — after Step 5 self-verify passes)

```markdown
## Fix Verification Report

**Ticket:** {TICKET_ID} - {Title}
**Commit:** {hash}
**Date:** {YYYY-MM-DD}
**Verified by:** Claude AI (10-step protocol v4.1)

### What was requested
> {Exact quote from ticket description — user's own words}

### Before State
- **Page:** {URL}
- **Element:** {CSS selector or description from ticket}
- **Visual state:** {What the element looked like BEFORE — from Playwright screenshot}
- **Console errors:** {count} errors

### After State
- **Visual state:** {What the element looks like AFTER — from Playwright screenshot}
- **Console errors:** {count} errors
- **Change applied:** {file:lines — what was modified}

### Before vs After Comparison
| Aspect | Before | After | Matches request? |
|--------|--------|-------|------------------|
| {element/text/layout} | {old state} | {new state} | YES/NO |

### Console Log Comparison
| Metric | Before | After | Status |
|--------|--------|-------|--------|
| Errors | {N} | {M} | {IMPROVED/SAME/REGRESSED} |
| Warnings | {N} | {M} | {IMPROVED/SAME/REGRESSED} |
| Uncaught exceptions | {N} | {M} | {IMPROVED/SAME/REGRESSED} |

**Console regression rule:** if ANY row = REGRESSED → fix the regression first, do NOT proceed.

### Verify Manually
**URL:** {PROD_URL}/path
**What to check:** {Exact thing Gareth should look at}
**Device:** {viewport from ticket user agent}

### Self-Verification Checklist
- [x] BEFORE screenshot captured via Playwright
- [x] AFTER screenshot captured via Playwright
- [x] Visual change matches ticket request
- [x] Zero NEW console errors (regression check passed)
- [x] No visual regressions on same page
- [x] Build passes (npm run build = 0 errors)

### Status: RESOLVED (awaiting audit)
```

### Mode 4 (REGRESSION) — additional MANDATORY section

When `prior_attempts.json` contains ≥1 entry, the comment MUST also include this section
**before "Status: RESOLVED"**. The gate refuses to pass without it (since 2026-05-08).

```markdown
### Why prior attempts failed

| # | Commit | Date | Approach taken | Why it didn't actually work |
|---|--------|------|----------------|-----------------------------|
| 1 | abc1234 | 2026-04-30 | Patched src/foo.ts:42 with X | Audit passed but feature flag `bar` was off in prod, so the fix never ran. |
| 2 | def5678 | 2026-05-03 | Re-patched same file with Y  | Same approach, same blind spot — flag still off. |

**This attempt is different because:** {one paragraph quoting actual runtime evidence
from before-state — logs, screenshots, network — that pinpoints the new root cause and
explains why the prior commits passed audits but failed in user reality.}
```

If you cannot articulate this honestly, the fix isn't ready. Stop and gather more
runtime evidence (First Law).

> Audit results (`### Targeted Audit Results`) are appended **after** Step 8 by Oracle — not by the worker.

### GOOD vs BAD reference example

**BAD (2/10 — NEVER do this):**
```
## Fix Verification Report (Retroactive)
**Ticket:** CAU-32
**Commit:** 8322cf4
### Verification
Retroactive verification confirms:
- Code changes committed and deployed
- Build passes successfully
- No regressions reported
### Status: RESOLVED
```
Why it's bad: no user quote, no before/after, no URL, no details. Useless for Gareth.

**GOOD (10/10 — the standard):**
```
## Fix Verification Report
**Ticket:** CAU-32 - [BUG] ici ces trois bouton font la meme chose
**Commit:** 8322cf4 · **Date:** 2026-04-03

### What was requested
> "ici ces trois bouton font la meme chose, et pas ce qu'ils disent faire."
User clicked the 3 upload buttons (Any file, Take photo, From phone).
All 3 opened the same generic file picker instead of their labeled action.

### Before State
- Page: https://www.causio.ai/start/.../documents
- Visual: 3 buttons all open same file picker
- Console errors: 0

### After State
- Visual: "Any file" → file picker, "Take photo" → camera capture, "From phone" → QR transfer
- Console errors: 0
- Change: src/app/(client)/start/[token]/documents/page.tsx:145-180 — distinct onClick per button

### Before vs After Comparison
| Aspect | Before | After | Matches? |
|--------|--------|-------|----------|
| "Take photo" | generic picker | camera capture | YES |
| "From phone" | generic picker | QR transfer | YES |

### Verify Manually
URL: https://www.causio.ai/start/NNFr5cAr8.../documents
Check: click each of the 3 buttons, verify distinct behaviors
Device: Desktop Chrome

### Self-Verification Checklist
- [x] BEFORE / AFTER screenshots via Playwright
- [x] Each button now distinct behavior
- [x] Zero new console errors

### Status: RESOLVED
```

Why it's good: quotes the user, precise before/after, clickable URL, specific file:line. Verifiable in 30 seconds.



---

## STEP 8: DYNAMIC AUDIT SET (upgraded 2026-05-08, was QUINTUPLE)

After the Linear comment quality gate passes, oracle runs `audit-selector.py` to pick the AUDITS RELEVANT FOR THIS TICKET, then dispatches them all in parallel, STRICTLY scoped to this one ticket. No project-wide scans. No scope creep.

> **Why dynamic:** running the same 5 audits regardless of mission wastes time on irrelevant checks (e.g. uiuxaudit on a SQL migration) AND misses critical audits not in the fixed set (e.g. secaudit on an auth change, dataaudit on a schema migration). The selector chooses 4–12 audits depending on the mission text + modified files + project type, always including codeaudit + logicaudit baseline.

### How to invoke the selector

```bash
ARTIFACTS="$PROJECT_DIR/.linear-fix/$TICKET_ID"
FILES=$(cd $PROJECT_DIR && git diff --name-only HEAD~1 HEAD | tr '\n' ' ')
~/.aisb/lib/audit-selector.py \
  --mission="$TICKET_SUMMARY" \
  --files="$FILES" \
  --project-path="$PROJECT_DIR" \
  --ticket="$TICKET_ID" \
  > "$ARTIFACTS/audits-selected.json"
```

The selector returns JSON like:

```json
{
  "audits": ["codeaudit","debugaudit","logicaudit","secaudit","apiaudit","dataaudit","perfaudit"],
  "count": 7,
  "project_type": "webapp",
  "rationale": {
    "secaudit": ["mission keyword match: \\bauth\\b","DB layer touched → injection/RBAC check"],
    "dataaudit": ["file pattern match: convex/.*\\.(ts|js)$"],
    "...": "..."
  }
}
```

### Dispatch every selected audit in parallel

For each `audit` in `audits-selected.json[*]`:

```bash
~/.aisb/lib/dispatch-to-session.sh \
  "$PROJECT-worker-{x}-audit-${audit%audit}" \
  "/${audit} --files=$FILES --ticket=$TICKET_ID --url=$PAGE_URL --user-need='$USER_QUOTE'" \
  "$PROJECT_DIR"
```

Each audit persists `$ARTIFACTS/${audit}.json` with schema `{"score":100,"skill_used":"<audit>"}`. The `--user-need='<quote>'` arg passes the verbatim user quote so each audit can verify the fix matches user intent (not just generic standards).

### Scoping rules (HARD LIMITS)

```
files_modified   = git diff --name-only HEAD~1 HEAD
page_url         = URL from ticket description (Context -> URL)
element_selector = CSS selector from ticket (Targeted Element -> Selector)
viewport         = derived from ticket user agent
ticket_scope     = 1-line ticket summary

FORBIDDEN during Step 8:
  - Scanning files NOT in files_modified
  - Testing pages NOT in page_url
  - Proposing fixes OUTSIDE the ticket scope
  - "While we are here" expansions
  - Touching working code unrelated to the fix
```

### The 16 candidate audits (selector picks the relevant ones)

| Audit | Domain | When relevant |
|---|---|---|
| **codeaudit** | Code architecture | ALWAYS (baseline) |
| **logicaudit** | Systems logic, optimization | ALWAYS (baseline) |
| **debugaudit** | Runtime bugs, console, network | code touches runtime paths |
| **flowaudit** | User flows, state machines | UI flow / journey changed |
| **uiuxaudit** | Visual design / Gestalt | UI components touched |
| **a11yaudit** | WCAG, keyboard, ARIA | UI components touched |
| **motionaudit** | Animations, scroll, transitions | animation libs touched |
| **copyaudit** | Wording, claims, i18n | text/labels/i18n changed |
| **seoaudit** | Crawl, schema, meta | public pages touched |
| **perfaudit** | Core Web Vitals, bundle, N+1 | hot path / SSR / queries |
| **secaudit** | OWASP, injection, auth | auth, perms, env, SQL touched |
| **apiaudit** | REST/GraphQL contracts | API/route handlers touched |
| **dataaudit** | Schema, migrations, integrity | DB / Convex / Prisma touched |
| **dxaudit** | DX, README, setup | docs / build config touched |
| **automationaudit** | Cron, scripts, daemons | scheduling / cron touched |
| **featureaudit** | Feature completeness vs PRD | mission mentions a new feature |

Each returns a normalized score /100. The selector outputs the relevant subset (4–12 typically) with rationale per audit.

### Pass threshold — 100/100 each (strict, NO exceptions)

| Result | Action |
|--------|--------|
| All selected audits = 100/100 | Proceed to Step 8c (Intent Verification) |
| Any audit < 100/100 | Return to Step 3 with audit findings inlined, new fix attempt, re-run Step 8 (fix-and-reaudit loop) |
| 5 iterations still fail | Escalate to user, do NOT move to In Review |

> **No partial credit.** A 99/100 score = FAIL. The audits are calibrated so that 100 is achievable when the fix is correct AND well-integrated. Anything less means a real issue is hiding. The dynamic selector typically picks 4–12 audits per ticket — every single one must hit 100.

---

## STEP 8c: INTENT VERIFICATION (NEW 2026-05-08)

After the QUINTUPLE audit passes, the oracle runs ONE final check that the other 5 cannot do — **does the result actually match what the user asked for?** Code can be 100% solid, UI 100% clean, flow 100% correct, runtime 100% bug-free, logic 100% optimal — and still NOT solve the user's actual problem (e.g. wrong layer fixed, feature flag off in prod, env mismatch, edge case the user described not covered).

### The Intent Verification gate

Read THREE inputs:

1. **`user_quote`** — verbatim text from ticket description (or Gareth's latest comment in RE-REVIEW/REGRESSION mode). The user's own words.
2. **`measured_behavior`** — captured live from the AFTER screenshot + console log + network trace + (if applicable) DB state.
3. **`change_summary`** — what code changed (`git diff --stat`).

Then answer THESE FIVE QUESTIONS in writing in the Linear comment:

```markdown
### Intent Verification

**User asked for:**
> {user_quote verbatim — quoted from description or latest Gareth comment}

**Q1. Does the AFTER screenshot show the user's described element/state correctly?**
→ {YES with evidence | NO with what's missing}

**Q2. Does the user's described workflow now work end-to-end on the live URL?**
→ {YES — confirmed by Playwright trace at {URL} | NO — fails at step X}

**Q3. Are the values/labels/copy in the AFTER state EXACTLY what the user described
(not paraphrased)?**
→ {YES — quote AFTER state exact text | NO — diff what's wrong}

**Q4. If the user described a specific edge case, was it tested?**
→ {YES — test case "X" passed | N/A — no edge case described | NO — not tested}

**Q5. Is the fix LIVE on prod (not just merged to main)?**
→ {YES — prod URL returns 200 AND deployment timestamp > commit timestamp |
   NO — explain blocker}
```

**Pass threshold:** all 5 questions answered YES (or N/A for Q4 if no edge case).

**If any answer is NO:** the fix is NOT done, regardless of audit scores. Return to Step 3 with the missing piece as the new mission. Do NOT move to In Review.

This gate exists because the 5 audits answer "is the code correct?" but not "did we solve the user's problem?". A senior engineer asks both. So does the oracle.

### Append audit results to Linear comment

After quintuple audit passes, append to the existing Linear comment:

```markdown
### Targeted Audit Results (ticket-scoped)
| Audit | Score | Status |
|-------|-------|--------|
| codeaudit  (files: {files_modified}) | {X}/100 | PASS |
| uiuxaudit  (element: {selector})      | {X}/100 | PASS |
| flowaudit  (page: {page_url})         | {X}/100 | PASS |
| debugaudit (page: {page_url})         | {X}/100 | PASS |
| logicaudit (files + page logic)       | {X}/100 | PASS |

Scope: this ticket only. No project-wide changes.
```

---

## ORACLE QUALITY GATE (reinforced, 2 criteria)

Before moving a ticket to In Review, Oracle verifies BOTH:

### Criterion 1 — Comment completeness (Step 7)
| Required | Check |
|----------|-------|
| `### Before State` | Section exists with visual state |
| `### After State` | Section exists with visual state |
| `### Before vs After Comparison` | Table with rows |
| `### Verify Manually` | Clickable URL present |
| Commit hash | Matches a real git commit |
| `### Self-Verification Checklist` | Has `[x]` items |
| `### Targeted Audit Results` | 4 audits with scores |

### Criterion 2 — Quintuple audit (Step 8) + Intent Verification (Step 8c)
All 5 audits = 100/100, each scoped strictly to the ticket. PLUS the Intent
Verification 5-question gate must answer YES (or N/A for Q4) to every question.

**If EITHER fails → reject, worker redoes. Do NOT move to In Review.**

A 100/100 quintuple WITHOUT a passing Intent Verification = FAIL. Code-correct
but user-need-mismatched is the most insidious failure mode and the gate exists
specifically to catch it.

---

## PHASE FLOW (orchestrator logic)

```
1. FETCH     -> graphql fetch open tickets + comments
2. DISPLAY   -> dashboard with screenshot count + priority
3. SELECT    -> user picks (bugs / all / specific IDs) or auto
4. LOOP (sequential, one ticket at a time):
   a. Dispatch work session {Project}-linear-{TICKET_ID}
   b. Worker runs Steps 1-7 (analysis -> fix -> build -> capture -> comment)
   c. Oracle Quality Gate on comment (Criterion 1)
   d. Step 8: dispatch 4 audits in parallel, scoped to ticket
   e. Oracle Quality Gate on audits (Criterion 2, threshold 100/100)
   f. If both pass -> Move ticket to "In Review: Gareth" (fallback "In Review") via Linear API — NEVER Done
   g. If either fails -> return to step b (max 5 fix-and-reaudit iterations)
5. REPORT    -> X resolved, Y rejected, Z escalated, links to Linear
```

---

## LINEAR API (fetch + comment + move to In Review)

### Fetch tickets with FULL context (description + comments + state history)

> **2026-05-08 update:** the fetch query now includes `history` (state transitions) and orders comments chronologically. Workers MUST consume the entire returned object — description AND every comment AND every state transition. The history field is what powers Mode 4 (REGRESSION) detection.

```graphql
{
  issues(filter: { state: { type: { nin: ["completed", "canceled"] } } }) {
    nodes {
      id identifier title description priority createdAt updatedAt
      state { id name type }
      labels { nodes { name } }
      comments(orderBy: createdAt) {
        nodes {
          id body createdAt
          user { name displayName email }
        }
      }
      history(orderBy: createdAt) {
        nodes {
          createdAt
          fromState { id name type }
          toState   { id name type }
          actor { name displayName email }
        }
      }
    }
  }
}
```

### Per-ticket pre-processing (BEFORE dispatching a worker)

For each ticket returned, the oracle/orchestrator MUST compute these derived fields and inject them into the worker prompt:

```
prior_fix_attempts        = comments.nodes filtered by:
                              - body contains "## Fix Verification Report" OR "Status: RESOLVED" OR "Status: RÉSOLU"
                              - user is the bot/oracle (NOT a human)
                            → list of {commit, date, files_changed, claimed_root_cause}
prior_done_transitions    = history.nodes filtered by:
                              - toState.type == "completed" OR toState.name matches /done/i
                            → list of {at, by}
moved_back_after_done     = TRUE if (prior_done_transitions length >= 1) AND
                                    (current state.type != "completed")
                          → triggers Mode 4 (REGRESSION)
last_comment              = comments.nodes[-1] (most recent)
gareth_last_comment       = TRUE if last_comment.user.name (case-insensitive) contains "gareth"
                          → triggers Mode 3 (RE-REVIEW)
```

### Artifact files the orchestrator MUST write per ticket (before dispatch)

```bash
# Path: $PROJECT_DIR/.linear-fix/{TICKET_ID}/

# 1. mode.txt — single line: FIX | RE-REVIEW | REGRESSION | VERIFY-DONE
echo "$MODE" > "$PROJECT_DIR/.linear-fix/$TICKET/mode.txt"

# 2. prior_attempts.json — JSON array, one object per prior Fix Verification Report
#    Schema: [{ "commit": "abc1234", "date": "2026-04-30", "files_changed": ["src/foo.ts"],
#               "claimed_root_cause": "..." }]
#    Empty array [] if zero prior attempts.
jq -n --argjson list "$PRIOR_LIST_JSON" '$list' \
  > "$PROJECT_DIR/.linear-fix/$TICKET/prior_attempts.json"

# 3. all_comments.md — full chronological dump for the worker to Read
#    (description on top, then every comment with author + timestamp).

# 4. state_history.md — chronological state transitions, one per line.
```

The quality gate (`linear-ticket-gate.sh`) reads `prior_attempts.json` and enforces the "Why prior attempts failed" comment section when the array is non-empty. Without these artifacts the worker cannot pass Mode 4.

**Mode resolution (per ticket):**

```python
if moved_back_after_done OR (prior_fix_attempts and current_state != "completed"):
    mode = "REGRESSION"   # Mode 4 — prior attempts failed
elif gareth_last_comment and current_state != "completed":
    mode = "RE-REVIEW"    # Mode 3 — Gareth's new spec
elif current_state == "completed":
    mode = "VERIFY-DONE"  # Mode 2 (only if user invoked verify)
else:
    mode = "FIX"          # Mode 1 — default

### Download screenshots from comment bodies

```bash
# Comments contain: ![Screenshot](https://uploads.linear.app/...)
# Downloads go into the gate-compliant artifact dir under the project repo
mkdir -p $PROJECT_DIR/.linear-fix/{TICKET_ID}/comments
curl -sL -H "Authorization: ${LINEAR_API_KEY}" \
  "https://uploads.linear.app/..." \
  -o $PROJECT_DIR/.linear-fix/{TICKET_ID}/comments/global.jpg
```
Header: `Authorization: {KEY}` — NOT `Bearer`.

### Post comment

```graphql
mutation {
  commentCreate(input: { issueId: "UUID", body: "BODY" }) { success }
}
```

### Move to In Review (NEVER Done — Gareth alone marks Done)

**Step 1 — Discover the review state** via `workflowStates` (never hardcode stateIds, never use a Done stateId):

```graphql
{
  team(id: "TEAM_ID") {
    states { nodes { id name } }
  }
}
```

Resolution order (case-insensitive):
1. exact match `In Review: Gareth`
2. fallback exact match `In Review`
3. else ABORT — tell user: "no review state found, ticket left in current state"

**Step 2 — Move ticket** using the resolved `REVIEW_STATE_UUID`:

```graphql
mutation {
  issueUpdate(id: "UUID", input: { stateId: "REVIEW_STATE_UUID" }) { success }
}
```

**FORBIDDEN** (per rule 43):
- ❌ Hardcoding any project's Done stateId (e.g. `543275fe-9661-4dca-999c-133157fbc61a`)
- ❌ Calling `issueUpdate` with a Done stateId
- ❌ "I'll mark it Done since the gate passed" — Gareth marks Done, always

Call via:
```bash
curl -s -X POST https://api.linear.app/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: ${LINEAR_API_KEY}" \
  -d '{"query": "..."}'
```

---

## WORKER PROMPT TEMPLATE (dispatched sequentially)

```
MISSION: Fix {TICKET_ID} - {Title}
MODE:    {FIX | RE-REVIEW | REGRESSION}   ← oracle injects this from mode resolution

FULL TICKET DESCRIPTION:
{description}

ALL COMMENTS (chronological, oldest → newest — read every one):
{for each comment in comments.nodes:
  ─── {comment.user.name} · {comment.createdAt}
  {comment.body}
  (screenshots in this comment downloaded to $PROJECT_DIR/.linear-fix/{TICKET_ID}/comments/{comment.id}/)
}

STATE TRANSITION HISTORY (chronological):
{for each h in history.nodes:
  {h.createdAt}: {h.fromState.name} → {h.toState.name} (by {h.actor.name})
}

────────────────────────────────────────────────────────────
PRIOR FAILED ATTEMPTS (MANDATORY in REGRESSION mode — empty otherwise):
────────────────────────────────────────────────────────────
{for each attempt in prior_fix_attempts (oldest → newest):
  ATTEMPT #{n} · {attempt.date} · commit {attempt.commit}
    Claimed root cause: {attempt.claimed_root_cause}
    Files changed:      {attempt.files_changed}
    Outcome:            FAILED — ticket was moved back to {current_state.name}
                        on {moved_back_at} by {moved_back_by}
}

REGRESSION MODE RULES (apply when MODE = REGRESSION):
- Each attempt above is a CONFIRMED FAILURE. Do NOT repeat their approach.
- Before ANY edit: capture LIVE runtime evidence (prod logs, current screenshot,
  network trace, console state). First Law — code lies, runtime tells truth.
- Form a NEW hypothesis that explains why prior attempts compiled & passed audits
  but did not solve the user's problem. Common causes: feature flag off, env var
  missing in prod, deploy didn't ship, cache, role-gated path, race condition,
  env mismatch staging vs prod.
- If the prior fixes look correct in code, the issue is OUTSIDE the code (env,
  deploy, config, data). Investigate THAT first. Don't write more code if the
  problem isn't code.
- New Linear comment MUST contain a "## Why prior attempts failed" section
  citing each attempt by commit hash + the specific reason it didn't work.
  Without this section the quality gate FAILS.

SCREENSHOT ANALYSIS (already downloaded at $PROJECT_DIR/.linear-fix/{TICKET_ID}/comments/):
{what each screenshot shows}

ROOT CAUSE:
{file:line -> current behavior -> required change}

PAGE URL:        {URL}
CSS SELECTOR:    {selector}
DEVICE/VIEWPORT: {from user agent}
CONSOLE ERRORS:  {from ticket}

MANDATOR

…(truncated)
