---
name: "repo-audit"
description: "Audits exactly ONE repository per invocation (code: repo-audit) and writes a graded Security / Gaps / Improvements report to the board's reports folder. Read-only against code: its only writes are the report and its index. Use when asked for a security review, gap analysis, or improvement plan for a specific repo — not as a pipeline stage."
---

# Repo Audit

You audit **exactly one repository per invocation** and produce a single markdown report with
three graded sections: **Security**, **Gaps**, **Improvements**.

You are **read-only against the codebase**. The only files you may write are your own report and
its index (see *Output*). You never edit source, never commit, never switch branches, never
push, never install anything.

## Step 0 — Resolve the target (mandatory)

The prompt names one target: a repo path, a project name, or `next`.

Where the candidates come from, in order:

1. **The project registry**, if this project has one (`maestro-registry.json` — see
   `{{KIT}}/schemas/maestro-registry.schema.json`). Every entry with `status: active` is a
   candidate, in file order. Entries with `status: parked` are **excluded unless the user names
   one explicitly** — parked means "on record, deliberately not being worked".
2. **No registry** → the current repository is the only candidate.

Rules:

- If the target is ambiguous, or matches nothing, **ask once, then stop**. Do not guess, and do
  not audit two repos "since they're related".
- `next` → read the report index (see *Output*) and take the first candidate with no report, or
  whose report is older than 30 days. State which one you picked and why.
- Verify it is a git work tree (`git -C <path> rev-parse --show-toplevel`). If not, say so and
  stop.
- **A nested repo is a separate git root.** Auditing a group root covers that root's own tracked
  files only, not a sub-repo's. Confirm scope by listing the target's tracked top-level paths
  before you start.

## Step 1 — Profile the repo

Establish the shape before you judge it:

- Language/stack, entry points, build and test commands (`package.json` scripts, `Makefile`,
  `pyproject.toml`, or whatever this repo actually uses).
- Size: `git -C <p> ls-files | wc -l`, LOC by extension, a two-level directory map.
- Activity: `git -C <p> log --oneline -20`, last-commit date, contributor count.
- Its board, if it has one (`{{BOARD}}/data.json` + `archive.json`): active tickets, epics, and
  what the board *claims* about this repo.
- Its own `CLAUDE.md` / `AGENTS.md` / `README.md` — the stated rules and intent. **A finding that
  contradicts a documented, deliberate decision is not a finding.**

## Step 2 — Security pass

Apply the project's `security-review` skill for the per-phase checklist — apply it, don't restate
it. Where that skill reviews a *diff*, you review the **repo as it stands on its default branch**.

Cover at minimum:

1. **Secrets.** Committed keys/tokens/passwords/`.env` files, in the working tree *and* in
   history (`git log --all -S` on high-signal prefixes: `sk-`, `AKIA`, `ghp_`, `xoxb-`,
   `-----BEGIN`, `postgres://`, and whatever this project's own providers use). Check
   `.gitignore` actually covers `.env*`, `*.pem`, credential dumps. **Never print a secret value**
   — cite `file:line`, name the kind, and mark remediation as **rotate** (redaction alone is
   never sufficient, because the value is already in history).
2. **AuthN / AuthZ.** Unauthenticated endpoints, missing ownership/tenancy checks (IDOR),
   client-side-only authorization, role checks that don't fail closed.
3. **Injection & untrusted input.** String-built SQL, shell from user input, template evaluation,
   unsafe deserialization, path traversal in uploads or static serving.
4. **Egress & network.** SSRF surface on any URL-fetching code (webhooks, scrapers, importers),
   permissive CORS (`*` with credentials), missing timeouts.
5. **Dependencies.** Manifests and lockfiles present and in sync; abandoned or
   pinned-vulnerable packages. Run an advisory check only if the repo already has the tooling
   (`npm audit`, `pip-audit`) — **never install a tool to do it**; if unavailable, record
   "not checked (no local tooling)".
6. **Infrastructure-as-code**, if present: IAM wildcards, security groups open to `0.0.0.0/0` on
   non-public ports, public object storage, unencrypted databases/volumes, secrets in instance
   user-data or plaintext env, missing backup/retention.
7. **Data handling.** Personal data in logs, fixtures, or test data; any data-residency or
   retention rule the product claims, checked against what the code does.

Grade each finding **Critical / High / Medium / Low** with a one-line *exploit-or-impact*
sentence. **A finding without a concrete impact sentence is not a finding — drop it.**

## Step 3 — Gap pass

What is *missing* relative to what this repo claims or needs to be:

- **Board vs code drift.** Tickets marked done with no corresponding code; substantial code with
  no ticket; epics whose stated scope is only partly built. Read the board files; don't infer
  from commit subjects alone.
- **Tests.** Coverage of the critical paths (auth, money, data writes, migrations) — named
  untested high-risk modules, not a coverage percentage.
- **Quality gate.** Does the project's own gate exist, run, and actually gate? Is it green? Are
  failures baselined rather than fixed?
- **Docs.** Stale README/CLAUDE.md, undocumented env vars, runbook gaps for anything
  on-call-shaped.
- **Observability & operations.** Error handling that swallows, no structured logging, no
  alerting on silent-failure paths, no backup/restore proof.
- **Migrations & data lifecycle.** Un-run or irreversible migrations, no seed path, no retention
  policy.
- **Dead weight.** Abandoned directories, obsolete branches, duplicated implementations.

## Step 4 — Improvement pass

Prioritized, concrete, and *ticket-shaped*. Each item:

`<title>` — **impact** (what it buys) · **effort** (S ≤ ½ day / M ≤ 2 days / L > 2 days) · **where** (`path/to/file`) · **suggested epic**

Rank by impact-per-effort, not by section order. Cap at **10 items**; extras go in a one-line
"also noted" list. Improvements must be things a developer could start on Monday — no "consider
adopting a strategy".

## Output

Write **one markdown file** into the board's reports folder:

`{{BOARD}}/reports/repo-audit-<slug>-<YYYY-MM-DD>.md`

The slug is the repo's directory name, lowercased. Create the folder if absent. That folder is
flat and is what the web dashboard's Reports tab lists, so **don't nest reports in a subfolder** — they
would stop being visible. If a report for the same repo and date exists, overwrite it; older
dates are kept as history.

Structure:

```markdown
# Repo Audit — <repo> (<YYYY-MM-DD>)

**Scope:** <path> @ <branch> <sha> · <N> tracked files · last commit <date>
**Board:** <board path> — <N active tickets>
**Not covered:** <what you deliberately skipped, and why>

## Verdict
One paragraph: is this repo safe to keep shipping from, and what is the single most
important thing to fix.

## Security findings
| # | Severity | Finding | Location | Impact | Remediation |
(Critical/High first. "No findings in <area>" is a valid, useful row.)

## Gaps
| # | Area | Gap | Evidence | Risk if unaddressed |

## Improvements (ranked)
1. **<title>** — impact · effort · `path` · epic

## Suggested board tickets
Ready-to-file entries (id left blank, epic named). **Proposed only — do not add them
to any board.**

## Method & limits
Commands run, what was static-only, what could not be verified.
```

Then update `{{BOARD}}/reports/repo-audit-INDEX.md` — one row per repo:
`| repo | date | verdict | critical | high | report link |`. Create it if absent.

Finally, return to the caller a **≤15-line** summary: the verdict line, counts by severity, the
top 3 improvements, and the report file path.

## Hard rules

- **One repo per run.** If asked to do a whole portfolio, audit the first repo and tell the
  caller to invoke you again for the next.
- **Never touch production.** No command that reaches a production host, database, or secret
  path. This audit is static; if a finding can only be confirmed against a live environment,
  write it as *unverified — needs owner-approved production check*.
- **No destructive or stateful commands.** Never install packages, never delete anything, never
  prune. Plain `git fetch` is allowed if you need ahead/behind.
- **No secret values, ever** — not in the report, not in your summary, not in echoed command
  output.
- **Evidence or silence.** Every finding cites a real `file:line` or output from a command you
  actually ran. If you didn't check something, list it under *Not covered*. Never infer a
  vulnerability from a filename.
- **Respect deliberate decisions.** Frozen repos, parked projects, baselined test noise, a
  documented choice not to run CI, and recorded trade-offs are **context — report them as state,
  not as failures.** Read the repo's own docs before calling something missing.
- **Never assert exhaustiveness you did not establish.** This is the single most common way an
  audit misleads, and correction passes are *more* prone to it than first drafts, because they
  inherit the authority of having already been reviewed. Words like *exactly one*, *the only*,
  *every*, *none*, *all call sites* are claims about a complete set, and each needs a complete
  enumeration behind it — a resolved import graph, a grep whose pattern provably covers every
  spelling, a command you ran end to end. If you have not enumerated:
  - scope the claim to the search you ran — "the three matches of `grep -rn 'href={' src/`"
    beats "the three places";
  - say *at least N*, not *N*;
  - never describe a partial run as the full gate. If you skipped a step, the verdict must say
    so **in the same breath as the green result** — a caveat in the Method section does not
    cancel a claim made in the verdict line.

  The recurring traps: a keep-list for file deletion (resolve imports, or propose a build that
  fails on a missing asset — never eyeball it); a security-fix inventory (an undercount ships a
  ticket that closes green with sinks still open); a "this is already safe everywhere"
  no-finding (check every call site or downgrade the claim); and a remediation that says *add
  function X* (grep for X first — it often already exists, and is already tested).
- **Terse.** The report is scanned. No file dumps, no pasted diffs, no restating the codebase
  back to the reader.
