---
name: ayf:review-pack
description: |
  Build a self-contained HTML review pack for task N: task spec, git diff,
  self-test findings, guided review questions, and a Pass/Needs-Work verdict.
  Opens in any browser -- no server required.
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash
  - Glob
  - Grep
---

# /review-pack N -- Human Review Pack

Produce a single self-contained HTML file at `.ay/retros/review-pack-{N}.html` that
gives a human everything needed to review task N in one page: the task spec, the
full diff, the agent's self-test findings, guided questions, and a Pass /
Needs-Work verdict they can copy back. No dev server, no external assets -- it
opens straight from disk in any browser.

## Phase 1: GATHER INPUTS

1. **Task spec** -- read `.ay/tasks/{N}.md` (try `.ay/tasks/{N}-*.md` if the bare
   name is missing). If none exists, use the task's BOARD.md row as the spec and
   note that no task file was found.
2. **Scope dirs** -- determine the directories this task was allowed to touch,
   from the task spec's "work in" / files section. If none are declared, leave
   scope empty (the diff will cover everything).
3. **Diff** -- capture the changes for this task:
   ```bash
   git diff main...HEAD -- <scope-dirs>     # scope-limited when scope is known
   git diff main...HEAD                       # fallback: whole branch
   ```
   Use `main...HEAD` (three dots) so the diff is the branch's own work, not
   unrelated changes on main. If `main` is not present, fall back to the repo's
   default branch or `HEAD~N`.
4. **Self-test findings** -- read `.ay/retros/agent-findings.json` if it exists
   (produced by `/self-test`). If absent, the pack renders a "run /self-test
   first" note in that section.

## Phase 2: BUILD THE HTML

Write `.ay/retros/review-pack-{N}.html` (create `.ay/retros/` if needed) from the
template below. Replace every `{{...}}` placeholder:

- `{{N}}` -- task number.
- `{{TASK_TITLE}}` -- task title (from the spec heading or BOARD row).
- `{{GENERATED_AT}}` -- ISO-8601 UTC timestamp.
- `{{TASK_SPEC_HTML}}` -- the task spec text, **HTML-escaped** (`&`→`&amp;`,
  `<`→`&lt;`, `>`→`&gt;`), newlines preserved (it sits inside a `<pre>`).
- `{{GIT_DIFF_HTML}}` -- the raw diff, **HTML-escaped** the same way. If empty,
  use the literal text `No changes on this branch.`
- `{{FINDINGS_ROWS}}` -- one `<tr>` per finding from agent-findings.json:
  `<tr><td>T-01</td><td><span class="badge pass">pass</span></td><td>desc</td><td>evidence</td></tr>`
  (escape `description` and `evidence`; `status` drives the badge class:
  `pass`|`fail`|`skip`). If findings are absent, replace the whole `<tbody>` with
  a single row: `<tr><td colspan="4" class="muted">No agent-findings.json — run /self-test first.</td></tr>`.

Never inject un-escaped spec or diff text -- a stray `<` in the diff would break
the page.

Template (self-contained; CSS vars mirror `Human/assets/base.css`):

```html
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Review Pack · Task {{N}}</title>
<style>
  :root{
    --bg:#06060d; --bg1:#0b0a1a; --bg2:#111025;
    --glass:rgba(255,255,255,.034); --brd:rgba(255,255,255,.075); --brdH:rgba(255,255,255,.15);
    --p:#8b5cf6; --pd:#6d28d9; --pl:#c4b5fd; --g:#22c55e; --gd:#15803d; --a:#f59e0b; --r:#ef4444;
    --t1:#f0eeff; --t2:#9896c4; --t3:#4e4c70;
    --rad:10px; --radL:16px;
    --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
    --mono:'JetBrains Mono','Fira Code',ui-monospace,monospace;
  }
  [data-theme="light"]{
    --bg:#f5f4fe; --bg1:#edeaff; --bg2:#e5e1ff;
    --glass:rgba(109,40,217,.045); --brd:rgba(109,40,217,.12); --brdH:rgba(109,40,217,.28);
    --t1:#0d0c1f; --t2:#3e3b6b; --t3:#7672a8;
  }
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
  body{font-family:var(--font);background:var(--bg);color:var(--t1);line-height:1.65;
       font-size:15px;padding:32px 20px;max-width:1000px;margin:0 auto}
  h1{font-size:26px;font-weight:800;letter-spacing:-.02em}
  h2{font-size:15px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
     color:var(--t2);margin:0 0 12px}
  .sub{color:var(--t2);font-size:13px;margin-top:4px}
  .card{background:var(--glass);border:1px solid var(--brd);border-radius:var(--radL);
        padding:22px;margin:18px 0}
  pre{font-family:var(--mono);font-size:12.5px;background:var(--bg2);border:1px solid var(--brd);
      border-radius:var(--rad);padding:16px;overflow:auto;white-space:pre;color:var(--t1)}
  .diff .ln-add{color:var(--g)} .diff .ln-del{color:var(--r)} .diff .ln-hdr{color:var(--p);font-weight:600}
  table{width:100%;border-collapse:collapse;font-size:13.5px}
  th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--brd);vertical-align:top}
  th{color:var(--t2);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.05em}
  .badge{display:inline-block;padding:2px 9px;border-radius:20px;font-size:11px;font-weight:700;
         text-transform:uppercase;letter-spacing:.04em}
  .badge.pass{background:rgba(34,197,94,.16);color:var(--g)}
  .badge.fail{background:rgba(239,68,68,.16);color:var(--r)}
  .badge.skip{background:rgba(245,158,11,.16);color:var(--a)}
  .muted{color:var(--t3)}
  label.q{display:block;font-weight:600;margin:16px 0 6px}
  textarea{width:100%;min-height:70px;font-family:var(--font);font-size:14px;color:var(--t1);
           background:var(--bg2);border:1px solid var(--brd);border-radius:var(--rad);padding:10px;resize:vertical}
  textarea:focus{outline:none;border-color:var(--brdH)}
  .verdict{display:flex;gap:10px;margin:8px 0 4px}
  .toggle{flex:1;padding:14px;border:1px solid var(--brd);border-radius:var(--rad);background:var(--bg2);
          color:var(--t2);font-weight:700;font-size:15px;cursor:pointer;transition:.15s}
  .toggle[aria-pressed="true"].pass{background:var(--gd);color:#fff;border-color:var(--g)}
  .toggle[aria-pressed="true"].needs{background:#7f1d1d;color:#fff;border-color:var(--r)}
  .btn{display:inline-block;margin-top:14px;padding:11px 18px;border:none;border-radius:var(--rad);
       background:var(--p);color:#fff;font-weight:700;font-size:14px;cursor:pointer}
  .btn:hover{background:var(--pd)}
  .topbar{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}
  .themebtn{background:var(--glass);border:1px solid var(--brd);color:var(--t2);border-radius:var(--rad);
            padding:8px 12px;cursor:pointer;font-size:13px}
  #out{margin-top:12px;display:none}
</style>
</head>
<body>
  <div class="topbar">
    <div>
      <h1>Review Pack · Task {{N}}</h1>
      <div class="sub">{{TASK_TITLE}} · generated {{GENERATED_AT}}</div>
    </div>
    <button class="themebtn" onclick="document.documentElement.dataset.theme = document.documentElement.dataset.theme==='dark'?'light':'dark'">◐ theme</button>
  </div>

  <div class="card"><h2>1 · Task Spec</h2><pre>{{TASK_SPEC_HTML}}</pre></div>

  <div class="card"><h2>2 · Changes (git diff main...HEAD)</h2><pre class="diff" id="diff">{{GIT_DIFF_HTML}}</pre></div>

  <div class="card"><h2>3 · Self-Test Findings</h2>
    <table><thead><tr><th>Test</th><th>Status</th><th>Description</th><th>Evidence</th></tr></thead>
    <tbody>{{FINDINGS_ROWS}}</tbody></table>
  </div>

  <div class="card"><h2>4 · Guided Review</h2>
    <label class="q">Does this solve the stated problem?</label>
    <textarea data-q="solves_problem"></textarea>
    <label class="q">Are there edge cases not covered?</label>
    <textarea data-q="edge_cases"></textarea>
    <label class="q">Is the code scope-clean (only touches what it should)?</label>
    <textarea data-q="scope_clean"></textarea>
  </div>

  <div class="card"><h2>5 · Verdict</h2>
    <div class="verdict">
      <button class="toggle pass"  id="v-pass"  aria-pressed="false" onclick="setVerdict('pass')">✓ Pass</button>
      <button class="toggle needs" id="v-needs" aria-pressed="false" onclick="setVerdict('needs-work')">✗ Needs Work</button>
    </div>
    <button class="btn" onclick="copyReview()">Copy review as JSON</button>
    <pre id="out"></pre>
  </div>

<script>
  // Colorize the diff lines.
  (function(){
    var el=document.getElementById('diff'); if(!el) return;
    el.innerHTML = el.textContent.split('\n').map(function(l){
      var c = l.startsWith('+')&&!l.startsWith('+++') ? 'ln-add'
            : l.startsWith('-')&&!l.startsWith('---') ? 'ln-del'
            : (l.startsWith('@@')||l.startsWith('diff ')||l.startsWith('+++')||l.startsWith('---')) ? 'ln-hdr' : '';
      var esc=l.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
      return c ? '<span class="'+c+'">'+esc+'</span>' : esc;
    }).join('\n');
  })();

  var verdict=null;
  function setVerdict(v){
    verdict=v;
    document.getElementById('v-pass').setAttribute('aria-pressed', v==='pass');
    document.getElementById('v-needs').setAttribute('aria-pressed', v==='needs-work');
  }
  function copyReview(){
    var answers={};
    document.querySelectorAll('textarea[data-q]').forEach(function(t){ answers[t.dataset.q]=t.value.trim(); });
    var review={ task:{{N}}, verdict:verdict, answers:answers, ts:new Date().toISOString() };
    var json=JSON.stringify(review,null,2);
    var out=document.getElementById('out'); out.style.display='block'; out.textContent=json;
    if(navigator.clipboard){ navigator.clipboard.writeText(json); }
  }
</script>
</body>
</html>
```

## Phase 3: OPEN

Open the pack in the default browser (best-effort, non-fatal):

```bash
open ".ay/retros/review-pack-{N}.html" 2>/dev/null \
  || xdg-open ".ay/retros/review-pack-{N}.html" 2>/dev/null \
  || echo "Open .ay/retros/review-pack-{N}.html in your browser"
```

## Phase 4: REPORT

Print: `Review pack ready: .ay/retros/review-pack-{N}.html`

The human reviews in the browser, picks **Pass** or **Needs Work**, answers the
three questions, and clicks **Copy review as JSON**. They paste that JSON back
into the session. On **Pass**, proceed. On **Needs Work**, act on the answers
(fix, re-run `/self-test {N}`, re-run `/review-pack {N}`).
