audit

SkillAI & models

Audit Codex configuration, workflow, and prompt-efficiency (instruction cost, value-per-token) drift; emit ranked gaps and measurable gates.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the audit skill

What this skill tells your AI

The instructions your AI receives, as published by borda/ai-rig in plugins/codex-rig/skills/audit/SKILL.md and read by ahel’s review.

Full-sweep audit of .claude/ config + all plugins/*/ files: agents, skills, rules, settings.json, hooks. Spawns foundry:curator per-file, aggregates system-wide for cross-file issues — infinite loops, inventory drift, missing permissions, interop breaks. Reports findings; fix level chosen from follow-up gate.

  • $ARGUMENTS: optional — parse --flags first, then resolve remaining tokens as scope

    Flags (order independent, any combination with scope):

    • --local — audit source tree (plugins/*/) not user setup (.claude/ + installed cache); plugin-dev workflows where local edits not yet installed; sets LOCAL_MODE=true
    • --upgrade — fetch latest Claude Code docs, filter new features by genuine value, apply: config changes (apply + correctness check), capability changes (calibrate before → apply → calibrate after → accept if Δrecall ≥ 0 and ΔF1 ≥ 0). Skip to Mode: upgrade. Mutually exclusive with --adversarial and --efficiency — error if combined with either.
    • --adversarial (alias: --challenge) — adversarial review of all agents + skills in scope using foundry:challenger (Phase A) + Codex adversarial pass (Phase B); surfaces issues beyond standard per-file audit; see Mode: adversarial. Mutually exclusive with --upgrade only; combinable with --efficiency.
    • --efficiency — cost and efficiency sweep: model tier validation, token bloat detection, unbounded spawn patterns, cross-file boilerplate duplication, missing model declarations, bin/ extraction candidates (Check 33). Generates prioritized cost-reduction plan with estimated savings. Detection only — run /distill executables to act on extraction candidates. Skip to Mode: efficiency. Mutually exclusive with --upgrade only; combinable with --adversarial.
    • --skip-gate — suppress follow-up gate (for automation pipelines)
    • --fast — widen fan-out from MAX_BATCHES to MAX_BATCHES_FAST, trading tokens for wall-clock. Not free: each extra agent costs ~120,851 tok of fixed overhead regardless of how little work it does (see <constants>). Use when latency matters more than cost; omit by default. Combinable with every other flag.

    Legacy positional tokens (fix, upgrade, adversarial, challenge, ab, apply, fast, full) — hard error: print migration hint and stop. Example: "fix medium removed — run /audit and pick fix level from gate, or pass --upgrade / --adversarial as flags."

    Scope tokens (positional, space-separated — resolve each token before Step 2):

    • No scope: full sweep — sources per --local: without --local covers .claude/agents/, .claude/skills/, .claude/rules/, hooks, settings, ~/.claude/plugins/cache/ installed; with --local covers plugins/*/agents/, plugins/*/skills/ + .claude/ secondary
    • agents — restrict sweep to agent files only
    • skills — restrict sweep to skill files only
    • rules — restrict sweep to rule files only
    • communication — restrict sweep to communication governance files: rules/communication.md, rules/quality-gates.md, TEAM_PROTOCOL.md, skills/_shared/file-handoff-protocol.md
    • setup — restrict to system-config files: settings.json, permissions-guide.md, hooks, MEMORY.md, README.md, plugin integration, post-install user state (Checks 1–11, 30, I1, I2, I3); Step 3: setup SKILL.md only (one foundry:curator spawn); Checks I1–I3 read ~/.claude/ not .claude/
    • plugin — plugin integration only: codex plugin (Check 7), foundry plugin + init validation (Check 8, including 8g); Step 3: setup SKILL.md only (one foundry:curator spawn)
    • plugins — full audit of all plugins: per-file audit of every plugins/*/agents/*.md and plugins/*/skills/*/SKILL.md + integration checks (7, 8) per plugin
    • plugins <name> — same as plugins scoped to one plugin: plugins/<name>/agents/*.md + plugins/<name>/skills/*/SKILL.md + integration checks; <name> must match dir under plugins/ (e.g. plugins foundry, plugins oss, plugins research)
    • <plugin-name>tier 2 shorthand: bare plugin dir name (e.g. oss, foundry, research, develop, codemap) auto-resolved when token matches dir under plugins/; equivalent to plugins <name>; no plugins prefix needed
    • <agent-name>tier 3: name matches plugins/*/agents/<name>.md or .claude/agents/<name>.md; runs agent checks only (Checks 14a, 14b, 15, 19, 20, 17, 12, 13, 25, 22, 26, 29); one file in Step 3
    • <skill-name>tier 3: name matches plugins/*/skills/<name>/SKILL.md or .claude/skills/<name>/SKILL.md; runs skill checks only (Checks 14a, 14b, 15, 17, 12, 23, 22, 13, 24, 25, 26, 27, 28, 29); one file in Step 3
    • Multiple scope tokens — space-separated, any combo; scope = union of resolved file sets: agents skills, oss research, shepherd curator, review resolve; check list = union (de-duplicated)

    Scope token resolution (each remaining token after flag-strip, resolved before Step 2): (1) reserved keywords (agents, skills, rules, communication, setup, plugin, plugins) → use as-is; (2) matches dir under plugins/<token>/ → tier 2; (3) matches agent file in plugins/*/agents/<token>.md or .claude/agents/<token>.md → tier 3 agent; (4) matches skill dir plugins/*/skills/<token>/ or .claude/skills/<token>/ → tier 3 skill; (5) no match → error and stop

    Valid combinations: scope tokens + flags mix freely: foundry --local, foundry --adversarial, agents skills --local, oss research --adversarial, foundry --efficiency, plugins --efficiency, foundry --adversarial --efficiency, plugins --local --adversarial --efficiency. --upgrade mutually exclusive with --adversarial and --efficiency — error if combined with either. --local compatible with all. When --adversarial and --efficiency both present: run adversarial Phases A–C then efficiency Phases A–C sequentially; merge findings; single follow-up gate.

BATCH_SIZE_MIN=5       # minimum files per batch; ensures curator gets sufficient context per spawn
MAX_BATCHES=4          # total batch cap; EFFECTIVE_BATCH = max(BATCH_SIZE_MIN, ceil(total / MAX_BATCHES))
MAX_BATCHES_FAST=10    # only when --fast: trades ~120K tok/agent for wall-clock (see Fan-out cost model)
ADVERSARIAL_BATCH_SIZE=2  # adversarial phases (A, A-prime) use smaller batches for deeper per-file attention
AGENT_CALL_BUDGET=55   # target tool-calls per spawned agent; above ~60 agents stall mid-task without returning an envelope
  • Key boundary 1: after Steps 3+4 fan-out (curator spawns + system-wide checks complete), before Step 5 aggregate.
  • Key boundary 2: after Step 5 aggregate (aggregate.md + summary.jsonl written), before Step 7 report.
  • Preserve at boundary 1: RUN_DIR, per-batch finding file paths, static-findings.jsonl path.
  • Preserve at boundary 2: RUN_DIR, aggregate.md path, summary.jsonl path, finding counts.

Task hygiene: load and follow the protocol below.

# loads: compaction-contract.md
cat "$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_foundry}/bin/resolve_shared_path.py" foundry skills/_shared 2>/dev/null || echo "plugins/cc_foundry/skills/_shared")/task-hygiene.md"

Orchestration contract: orchestrator is thin coordinator — issues Glob/Grep for inventory, spawns agents, reads JSON envelopes, aggregates findings. Must NOT read agent/skill/rule file bodies directly. Inline read of non-template file = protocol violation; causes context overflow at scale.

Task tracking: TaskCreate for each major phase; mark status live:

  • Phase 1: setup + collect (Pre-flight + Steps 1–2) → in_progress on start, completed when file list ready
  • Phase 2: per-file audit (Step 3) → in_progress on agent launch, completed when all reports received
  • Phase 3: system-wide checks (Step 4) → in_progress on start, completed when all checks done
  • Phases 2 and 3 launch simultaneously — mark both in_progress same update; independent, must not serialize
  • Phase 4: aggregate + fix (Steps 5–10) → in_progress, completed when fixes land; do NOT mark completed until EITHER: (a) follow-up gate fires (Step 7) AND fixes applied or user chose skip; OR (b) --skip-gate active — gate suppressed, complete after Step 5 aggregation; Step 5 aggregation alone does NOT complete Phase 4 in normal mode
  • Phase 5: write final report (Step 11a) → in_progress, completed only once $RUN_DIR/report.md exists on disk — assembling the report in context does not complete this phase
  • Phase 6: print report header (Step 11b) → blockedBy Phase 5; mark completed immediately BEFORE emitting the header block (task-lifecycle.md §TaskUpdate before long output), then print
  • On loop retry or scope change → new task; do not reuse completed task

Surface progress at milestones: after system-wide checks ("✓ Checks 1-20 complete, N findings so far — spawning per-file audits"), after agent reports ("Agent reports received — N medium, N low findings"), before each fix batch ("Fixing N medium findings in parallel").

Pre-flight checks

Context budget: full audit (12+ agents, 14+ skills, 12 system checks) runs close to context limits. File-based handoff mandatory — every sub-agent writes full output to file, returns only compact JSON envelope. Sub-agent echoing findings to context = compaction before audit completes.

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
KEEP_ITEMS=""
# grep+sed, not `[[ =~ ]]` — zsh leaves BASH_REMATCH empty on match, silently dropping the user's --keep list
KEEP_ITEMS=$(printf '%s' "$ARGUMENTS" | grep -o -- '--keep[[:space:]]"[^"]*"' | head -1 | sed 's/^--keep[[:space:]]"//; s/"$//')
ARGUMENTS=$(echo "$ARGUMENTS" | sed 's/--keep "[^"]*"//g')
rm -f .temp/state/skill-contract.md  # clear stale contract (compaction-contract.md §Lifecycle)  # timeout: 5000
LOCAL_MODE=false;       [[ " $ARGUMENTS " == *" --local "* ]]       && LOCAL_MODE=true
ADVERSARIAL_MODE=false; [[ " $ARGUMENTS " == *" --adversarial "* ]] && ADVERSARIAL_MODE=true; [[ " $ARGUMENTS " == *" --challenge "* ]] && ADVERSARIAL_MODE=true
EFFICIENCY_MODE=false;  [[ " $ARGUMENTS " == *" --efficiency "* ]]  && EFFICIENCY_MODE=true
UPGRADE_MODE=false;     [[ " $ARGUMENTS " == *" --upgrade "* ]]     && UPGRADE_MODE=true
SKIP_GATE=false;        [[ " $ARGUMENTS " == *" --skip-gate "* ]]   && SKIP_GATE=true
FAST_MODE=false;        [[ " $ARGUMENTS " == *" --fast "* ]]         && FAST_MODE=true
ARGUMENTS=" $ARGUMENTS "
ARGUMENTS="${ARGUMENTS// --local / }"; ARGUMENTS="${ARGUMENTS// --adversarial / }"
ARGUMENTS="${ARGUMENTS// --efficiency / }"; ARGUMENTS="${ARGUMENTS// --upgrade / }"
ARGUMENTS="${ARGUMENTS// --skip-gate / }"; ARGUMENTS="${ARGUMENTS// --challenge / }"
ARGUMENTS="${ARGUMENTS// --fast / }"
ARGUMENTS=$(echo "$ARGUMENTS" | tr -s ' '); ARGUMENTS="${ARGUMENTS# }"; ARGUMENTS="${ARGUMENTS% }"

if [ "$UPGRADE_MODE" = "true" ] && { [ "$ADVERSARIAL_MODE" = "true" ] || [ "$EFFICIENCY_MODE" = "true" ]; }; then
    printf "! --upgrade is mutually exclusive with --adversarial and --efficiency\n"
    exit 1
fi

# preflight helpers defined inline — fresh shell per Bash() call; sourced functions unavailable
preflight_ok()   { local f=".temp/state/preflight/$1.ok"; [ -f "$f" ] && [ $(($(date +%s) - $(cat "$f"))) -lt 14400 ]; }
preflight_pass() { mkdir -p .temp/state/preflight; date +%s >".temp/state/preflight/$1.ok"; }

if [ ! -d ".claude" ]; then
    printf "! BREAKING: .claude/ directory not found — nothing to audit\n"
    exit 1
fi

# jq — Check 4 depends on it
if preflight_ok jq; then
    JQ_AVAILABLE=true
elif command -v jq &>/dev/null; then # timeout: 5000
    preflight_pass jq
    JQ_AVAILABLE=true
else
    printf "⚠ MISSING: jq not found — Check 4 (permissions-guide drift) will be skipped\n"
    JQ_AVAILABLE=false
fi

if ! preflight_ok git && ! command -v git &>/dev/null; then # timeout: 5000
    printf "⚠ MISSING: git not found — path portability check may miss repo-root references\n"
else
    preflight_ok git || preflight_pass git
fi

# node — Check 10 (RTK prefix parsing) + upgrade hook syntax check depend on it
if preflight_ok node; then
    NODE_AVAILABLE=true
elif command -v node &>/dev/null; then # timeout: 5000
    preflight_pass node
    NODE_AVAILABLE=true
else
    printf "⚠ MISSING: node not found — Check 10 (RTK hook parsing) and upgrade hook syntax check will be skipped\n"
    NODE_AVAILABLE=false
fi

AUDIT_TPL=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_foundry}/bin/resolve_skill_subdir.py" audit templates $( [ "$LOCAL_MODE" = true ] && echo "--local" )) || { printf "! BREAKING: audit/templates not found — run /foundry:setup first\n"; exit 1; }  # timeout: 5000

# Persist LOCAL_MODE + AUDIT_TPL — fresh-shell state loss
# Re-derive at each Step start — see ADV-M1
mkdir -p "${TMPDIR:-/tmp}/audit-state-${CSID}"
echo "$LOCAL_MODE" > "${TMPDIR:-/tmp}/audit-state-${CSID}/local-mode"
echo "$AUDIT_TPL"  > "${TMPDIR:-/tmp}/audit-state-${CSID}/audit-tpl"
echo "$KEEP_ITEMS" > "${TMPDIR:-/tmp}/audit-state-${CSID}/keep-items"

If .claude/ missing, abort immediately. Missing jq is warning — audit continues with Check 4 skipped.

State re-derivation across Bash blocks — Claude Code spawns a fresh shell per Bash() call; variables set in pre-flight are LOST in Steps 2–11. Every Bash block in subsequent steps that uses LOCAL_MODE or AUDIT_TPL must re-read them from the persisted state files:

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r LOCAL_MODE < "${TMPDIR:-/tmp}/audit-state-${CSID}/local-mode" 2>/dev/null || LOCAL_MODE="false"
AUDIT_TPL=$(cat "${TMPDIR:-/tmp}/audit-state-${CSID}/audit-tpl" 2>/dev/null || python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_foundry}/bin/resolve_skill_subdir.py" audit templates $( [ "$LOCAL_MODE" = true ] && echo "--local" ))

Place these three lines at the top of every Bash block in Steps 2–11 that references either variable.

Unsupported flag check — after extracting supported flags (--local, --upgrade, --adversarial, --efficiency, --skip-gate, --keep), scan $ARGUMENTS for remaining --<token> tokens. If found: print ! Unknown flag(s): `--<token>`. Supported: `--local`, `--upgrade`, `--adversarial`, `--efficiency`, `--skip-gate`, `--keep`. then invoke AskUserQuestion — (a) Abort (stop, re-invoke with correct flags) · (b) Continue ignoring (skip unknown flags, proceed). On Abort: stop.

Step 1: Run pre-commit (if configured)

preflight_ok()   { local f=".temp/state/preflight/$1.ok"; [ -f "$f" ] && [ $(($(date +%s) - $(cat "$f"))) -lt 14400 ]; }
preflight_pass() { mkdir -p .temp/state/preflight; date +%s >".temp/state/preflight/$1.ok"; }

if (preflight_ok pre-commit || { command -v pre-commit &>/dev/null && preflight_pass pre-commit; }) &&
[ -f .pre-commit-config.yaml ]; then
    timeout 600 pre-commit run --all-files # timeout: 600000
fi

Files auto-corrected by pre-commit hooks are clean before structural audit. Note modified files — include in audit scope even if not originally targeted.

If pre-commit not configured, skip silently.

Step 1b: Layer-1 deterministic static pass

Run the zero-LLM checker driver — the same deterministic checkers pre-commit enforces, aggregated into one reproducible findings file. These results are authoritative for their check classes: Steps 3–4 (LLM curator + judgment checks) must NOT re-derive them in prose — treat them as already-verified and spend model tokens only on judgment.

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r LOCAL_MODE < "${TMPDIR:-/tmp}/audit-state-${CSID}/local-mode" 2>/dev/null || LOCAL_MODE="false"
STATIC_SCOPE=$( [ "$LOCAL_MODE" = true ] && echo plugins || echo .claude )
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_foundry}/bin/audit_static.py" --scan-dir "$STATIC_SCOPE" \
    --jsonl "${TMPDIR:-/tmp}/audit-state-${CSID}/static-findings.jsonl"  # timeout: 120000

Covered deterministically by the driver (map to legacy check IDs — do NOT re-run these as prose): 14a tag symmetry · 14b fence symmetry · 14c README drift · 14d mode-dispatch integrity · 14e cross-plugin shared-file drift · 43 bash-variable persistence · 42 spawn-prompt $VAR (checks-skills.md) · 32d orphaned bin/ scripts · cli-flag-drift SKILL.md flags vs argparse (check_cli_flag_drift.py, checks-index 42) · R3 bin/computed-path reference integrity. The whole-repo checks (orphaned-bin, routing-links, shared-drift) always scope to plugins/ regardless of STATIC_SCOPE; the driver is most complete in --local mode. Step 5 merges static-findings.jsonl into the aggregate.

Layer-1 recall is benchmarkedtests/test_audit_static.py plants a known defect per scope-aware class and asserts the driver catches every one (100% mechanical recall), so this pass is trusted, not assumed.

Step 1c: Layer-3 recurrence signal (attention weighting)

Read a compact git-churn signal so audit attention follows measured churn — the files and change-classes that keep being re-fixed are where the next defect most likely hides. Report the dominant recurring-fix class, not only point findings.

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_foundry}/bin/audit_churn.py" --limit 300 --path plugins \
    > "${TMPDIR:-/tmp}/audit-state-${CSID}/churn-signal.json" 2>/dev/null  # timeout: 20000

Use churn-signal.json (commit_types, top_churn, recurring_hint) to: (1) prioritize per-file audits toward the most-churned files first in Step 3 batching; (2) add a Recurring fix class line to the Step 7 report naming the dominant theme (e.g. "version-bump churn — candidate for automation"). Zero-LLM, best-effort; skip silently if git is unavailable.

Step 2: Collect all config files

Enumerate everything in scope with built-in tools. Run all Glob calls in parallel.

Plugin layout resolution (--local/plugins scope — multi-plugin source tree, mandatory before the fixed-pattern Globs below): don't assume every plugin uses skills/+agents/ — a plugin can override the convention in its own manifest (e.g. plugins/codemap-py/.claude-plugin/plugin.json declares "skills": "./claude-skills/", not ./skills/; a fixed */skills/*/SKILL.md glob silently never matches it). Resolve each plugin's real dir names first:

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r LOCAL_MODE < "${TMPDIR:-/tmp}/audit-state-${CSID}/local-mode" 2>/dev/null || LOCAL_MODE="false"
if [ "$LOCAL_MODE" = "true" ]; then
  for pj in plugins/*/.claude-plugin/plugin.json; do
    [ -f "$pj" ] || continue
    plugin_name=$(basename "$(dirname "$(dirname "$pj")")")
    python3 -c "
import json
d = json.load(open('$pj'))
skills = (d.get('skills') or './skills/').strip('./')
agents = (d.get('agents') or './agents/').strip('./')
print(f'PLUGIN_LAYOUT: $plugin_name | skills={skills} | agents={agents}')
" 2>/dev/null || echo "PLUGIN_LAYOUT: $plugin_name | skills=skills | agents=agents"
  done
fi

For every PLUGIN_LAYOUT: line, use its skills=/agents= value (not the hardcoded skills/agents names) when Globbing that plugin below — e.g. codemap-py prints skills=claude-skills, so Glob claude-skills/*/SKILL.md under plugins/codemap-py/, not skills/*/SKILL.md. Plugins with no manifest override resolve to the same skills/agents names the fixed patterns below already assume — no behavior change for them. codex-skills/-style dirs for non-Claude-Code runtimes are intentionally excluded — only the manifest-declared path is authoritative, since a heuristic *skill* name match would also sweep in skill dirs meant for a different agent runtime and produce false-positive findings against Claude Code's frontmatter schema.

Source selection by LOCAL_MODE:

  • LOCAL_MODE=false (default — user setup): .claude/ primary; plugins/ skipped. Installed/active config only.
  • LOCAL_MODE=true (--local — project source): plugins/ primary; .claude/ secondary for rules/hooks/settings only.

Without --local (LOCAL_MODE=false):

  • Agents: Glob tool, pattern agents/*.md, path .claude/
  • Skills: Glob tool, pattern skills/*/SKILL.md, path .claude/
  • Rules: Glob tool, pattern rules/*.md, path .claude/
  • Communication: Read tool on rules/communication.md, rules/quality-gates.md, TEAM_PROTOCOL.md, skills/_shared/file-handoff-protocol.md
  • Settings: Read tool on .claude/settings.json
  • Hooks: Glob tool, pattern hooks/*, path .claude/

With --local (LOCAL_MODE=true):

  • Agents (source — primary): for each PLUGIN_LAYOUT: line above, Glob tool pattern <agents-dir>/*.md, path plugins/<plugin_name>/
  • Skills (source — primary): for each PLUGIN_LAYOUT: line above, Glob tool pattern <skills-dir>/*/SKILL.md, path plugins/<plugin_name>/
  • Agents (project-local — secondary): Glob tool, pattern agents/*.md, path .claude/
  • Skills (project-local — secondary): Glob tool, pattern skills/*/SKILL.md, path .claude/
  • Rules / Settings / Hooks: same as without --local (.claude/)

Merge into single flat inventory. When LOCAL_MODE=true and same logical name in both plugins/ and .claude/, prefer plugin source — skip .claude/ duplicate. Record full paths — Step 3 cross-reference checks depend on current inventory. If MEMORY.md not updated since last agent/skill added/removed, run live disk scan, not cached roster. Stale inventory = primary cause of false-negative cross-reference findings.

Coverage reconciliation (--local/plugins scope only — mandatory, not optional): a plugin contributing zero files to the inventory is invisible to every downstream check and must never pass as a silent clean sweep — same failure shape as a check that always no-ops: absence of findings misread as absence of problems. The base set for this comparison is every directory under plugins/, not just the ones that produced a PLUGIN_LAYOUT: line above — a plugin with a missing or malformed plugin.json contributes no PLUGIN_LAYOUT: line either, and comparing against that subset would make it invisible all over again:

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
mkdir -p "${TMPDIR:-/tmp}/audit-state-${CSID}"
find plugins -mindepth 1 -maxdepth 1 -type d ! -name ".*" 2>/dev/null | sed 's|plugins/||' | sort > "${TMPDIR:-/tmp}/audit-state-${CSID}/all-plugins"

After inventory is built (model-context comparison — inventory already in context, not re-read from disk): compare every name in all-plugins against plugin names actually present in the inventory. For each plugin with zero inventory entries, print and persist in the same bash call (re-export CSID here too — a later call starts a fresh shell and an unexported $CSID silently writes to the wrong sentinel path):

export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
: > "${TMPDIR:-/tmp}/audit-state-${CSID}/unscanned-plugins"
# for each zero-contribution plugin found above:
printf "⚠ UNSCANNED: %s — no files matched scope globs\n" "<plugin>" | tee -a "${TMPDIR:-/tmp}/audit-state-${CSID}/unscanned-plugins"

Step 11 reads this sentinel and surfaces it as a top-level report section — a zero-coverage plugin must never produce a green summary.

Scope filtering for Step 2 (applies on top of LOCAL_MODE):

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
27
Forks
4
Last commit
Sep 2026
Hacker News mentions
20
Advanced
Catalog kind
skill
Gateway key
audit-borda
Source
github.com/borda/ai-rig