---
name: issues
description: "Audit every issue in a GitHub repository, open and closed — verify each status against the code rather than the thread, hunt fixed-but-open and never-landed-fix candidates, tier the open set into the repo's existing plan, and deliver a report bundle with a cross-link map. Read-only: it recommends tracker changes but never makes them. Use for a whole-tracker review, not a single issue."
---

# audit:issues

Whole-tracker review of one repository. It works from a frozen snapshot and checkpoints as it goes. One measured run, on a 230-item tracker with 56 open issues: **22 minutes end to end**, of which phase 2 — verifying those 56 — was about 9, or roughly **10 seconds per open issue**. The two figures answer different questions: budget the total from the whole run, and scale it by the *open* count, since that is what phase 2 walks. Phase 2 also carries nearly all of the judgement, which is what makes a run expensive to re-derive rather than long to sit through.

> **Status: first runbook of the `audit` family.** The procedure below is the QuantEcon-adapted form of a runbook that has been executed once end to end; the gap that execution found (closed threads unread) is now doctrine rule 3 and is closed by the snapshot. Plan and open questions: [issue #12](https://github.com/QuantEcon/skills/issues/12).

## Invocation

```
/audit:issues [OWNER/REPO] [--out DIR]
```

Both arguments are optional. With no repo, audit the current checkout's `origin`. Everything else is discovered:

| Input | Discovery | On failure |
|---|---|---|
| Project-notes system | The search order in [quantecon-context.md](../../references/quantecon-context.md#finding-the-plan-to-slot-into) | Say none was found; tier against milestones |
| Label policy | [QEP-2](https://github.com/QuantEcon/qeps/pull/2), status read at run time | Treat as draft — recommend canonical labels only |
| Work-plan anchor | The live plan or tracking issue in the tracker | Tier against the notes system alone |
| Prior audits | Earlier audit bundles in the notes system; closed "priority order" issues | Note that this is the first audit |

Ask only when discovery is ambiguous — two plausible plan anchors, say — not when it simply comes up empty. Report every resolved input in the report's method section.

## Read this first

- [doctrine.md](../../references/doctrine.md) — trust rules, evidence classes, the read-only boundary, phases, coverage self-audit
- [quantecon-context.md](../../references/quantecon-context.md) — repo types, label ownership, the cross-repo graph, the closing-keyword hazard
- [deliverables.md](../../references/deliverables.md) — what a report owes its reader, where it may land, and this skill's four-document shape

Then, in the audited repo: the notes system, `CHANGELOG.md`, the latest release notes, and `AGENTS.md`/`CLAUDE.md`.

## Working directory

Runs happen in a checkout of the audited repo, because phase 2 verifies claims against its default branch. **Put the working directory somewhere the repo already ignores**, so a run leaves `git status` clean and no `.gitignore` edit is needed ([doctrine §3](../../references/doctrine.md#3-read-only-boundary)) — in that order:

1. An ignored scratch location in the notes system — `.dev/scratch/audit-<YYYY-MM-DD>/` in QuantEcon repos that have one, where `.dev/scratch/*` is already gitignored.
2. `.audit/<repo>-<YYYY-MM-DD>/` at the checkout root, left untracked and uncommitted.
3. Outside the checkout entirely, if the repo would show either as dirty.

State which was used in the report's method section. Everything the run produces goes under it:

| Path | Written by | Holds |
|---|---|---|
| `snapshot/` | phase 1 | `meta.json`, `issues.json`, `prs.json`, `coverage.json` |
| `findings.md` | phase 2 | one entry per item — **both** the open and the closed pass, under `## Open` and `## Closed`, appended as each is verified |
| `links.md` | phase 3 | the cross-link graph |
| `01-…` `02-…` `03-…` `README.md` | phase 4 | the delivered bundle |

**Append to the checkpoint as you go, not when the phase ends** ([doctrine §4](../../references/doctrine.md#4-checkpointing)). Phase 2 carries nearly all the judgement, so it is both the phase an interruption lands inside rather than between, and the phase whose log the catalog is later assembled from.

**Both passes are checkpointed, and the resume rule reads `issues.json`.** Phase 2 walks two sets — the open issues and the closed ones — and each has its own section in `findings.md`. On restart, partition `issues.json` by state and, for each partition independently, resume at the lowest number with no entry under the matching heading; re-verify the last entry in each rather than trusting a possibly truncated write. Do not infer progress from the file's length or from a single block: run 1 wrote only the open set to `findings.md` and sent the 62 closed issues straight to the catalog, so a resume would have re-verified all 62 from scratch while reporting itself complete ([defect 2](https://github.com/QuantEcon/skills/issues/21)).

## Phase 1 — snapshot

```bash
python ${CLAUDE_PLUGIN_ROOT}/scripts/fetch_tracker.py OWNER/REPO --out <dir>/snapshot
```

Preflight fails in the first minute on missing `gh`, missing auth, or an invisible repo — read the error rather than working around it. The snapshot captures issues and PRs in any state with full comment threads, so closed threads cost nothing extra to read and there is no excuse for skipping them.

Two things REST does not return, so the snapshot cannot hold them: **native sub-issue links** and **Projects membership**. Fetch those with `gh api graphql` where a parent/tracker issue depends on them, and list them in the coverage statement either way.

Read `coverage.json` before proceeding. A stream at the fetch limit, or unaccounted numbers, is a phase 1 problem — fix it here, not in the writeup.

## Phase 2 — verify

Per [doctrine §1](../../references/doctrine.md#1-what-makes-a-bulk-audit-trustworthy), the thread is a hypothesis and the default branch is the evidence.

**Each open issue.** Does the complaint still reproduce on the default branch — check the named file or function, not the claim about it? Did a merged PR or a campaign claim it, and does the diff actually contain the change (this is where wave escapes hide)? Is it a duplicate of another open issue — same fix, no mutual link? Is it superseded, where a newer issue is the better carrier — then port the lessons across and close the old one as superseded, never silently. Is it blocked on an external decision — annotate the hold and split live from gated scope, so the issue does not read as wholly frozen when part of it is actionable. For parents and trackers: are the children attached natively, and is it still the plan of record?

**Each closed issue.** Is `stateReason` sensible? Does it map to a shipped PR or a recorded decision? Were the thread's remainders re-homed somewhere still open, or did they die with the thread?

**Hunt these categories actively** — they are what the audit exists to find: fixed-but-open · scheduled-but-escaped · unlinked duplicates · delivered-elsewhere (close when the dependency lands) · validated-purpose-served · agreed-but-never-filed, where a thread proposal got a nod and no issue · stale notes-system lines, which are reported as nits rather than tracker actions.

Sibling-repo checks belong here too: for QuantEcon, "resolved in a sibling" and "one step of a rollout" are the two most common wrong conclusions a single-repo audit reaches.

**Check every citation against the baseline ref before tagging it `[verified]`** ([doctrine §2](../../references/doctrine.md#2-evidence-classes)):

- **a commit** — run `git merge-base --is-ancestor <sha> <ref>`; exit 0 means citable
- **a `file:line`** — confirm that line says what the finding claims *on the ref*, not in the working tree
- **a pull request** — confirm it is merged into that ref

Evidence that lives only on an unmerged branch is still worth citing — as the open PR it is, tagged `[stated]` or `[inferred]`.

Write each item's finding to `findings.md` as it is verified — the closed pass too, under its own heading, not straight into the catalog — in the catalog entry format from [deliverables.md](../../references/deliverables.md#the-auditissues-bundle), so phase 4 assembles the catalog rather than re-deriving it and an interrupted run loses one item rather than the phase.

## Phase 3 — relate

Parse every `#N` in all issue **and** PR bodies, both directions. One parsing caveat: a range reference (`#169–#176`) matches its endpoints only, so check milestone membership before declaring the middle numbers orphaned.

Produce, into `links.md`: the cluster map, the table of missing links worth adding (duplicate pairs, origin↔carrier, complementary checks, family orphans), true orphans and over-dense hubs, and the external cross-link registry.

## Phase 4 — tier and write

Slot into the repo's existing plan; never invent a parallel one. Tier by repo type — a build break in a `lecture-*` repo and a consumer-visible change in an `action-*` repo outrank their thread activity.

| Tier | Contents |
|---|---|
| **GC** | Verified done, superseded, or duplicate. Each gets a one-line closing comment, drafted and unsent. |
| **T0** | This week: live breakage, security exposure, release-gate reds, scheduled plan steps, small verified fixes. |
| **T1** | Aligned with the current phase or milestone of the repo's plan. |
| **T2** | Production quality, grouped by family — issues sharing a fix-shape are one unit of work. |
| **T3** | Decision-gated, external-dependency, deliberately deferred, parking lots. |

Priority labels only for genuine outliers, a handful either way, and only if the policy provides them. Then write the bundle per [deliverables.md](../../references/deliverables.md), as `01-issue-triage-report.md`, `02-issue-catalog.md`, `03-issue-links.md`, `README.md`.

**Scale the bundle to the tracker.** Four documents suit a tracker big enough that the argument, the enumeration and the graph get in each other's way. Below roughly 30 open issues they do not: fold the catalog and the link graph into the report, keep the `README.md` index, and say in the coverage statement which shape was used. Padding a small audit into four files makes it *less* checkable, which is the one thing the shape exists to protect.

## Phase 5 — self-audit

Run [doctrine §5](../../references/doctrine.md#5-coverage-self-audit) against `coverage.json`: reconcile the counts, explain every unaccounted number, confirm threads were read on both the open and closed sides, and state the residue — inline review comments, GraphQL-only data, anything after the snapshot timestamp. Fold any change back into the documents rather than appending a correction.

## What this skill does not do

It does not close, label, comment, or edit — see [doctrine §3](../../references/doctrine.md#3-read-only-boundary). The tracker pass is a separate, human-driven step working from the bundle, and label application belongs to `qe gh labels`. Judging one PR's technical quality is likewise out of scope; that is a per-item review, not a portfolio audit.
