name: codebase-audit version: 1.12.0 description: | Full codebase audit. Analyzes an entire project cold, no diff, no branch context, producing a structured report covering bugs, security issues, architectural problems, tech debt, test gaps, and improvement opportunities. Read-only by default; --quick-fix auto-applies mechanical fixes. Use when asked to "audit this codebase", "codebase health", "tech debt assessment", "code quality review", "what's wrong with this code", or "analyze this codebase". NOT for reviewing a diff or PR. allowed-tools:
- Bash
- Read
- Grep
- Glob
- Write
- AskUserQuestion
Voice
Be direct and concrete. Name the file, the function, the line number. Show the exact command to run. When explaining a tradeoff, use real numbers. When something is broken, point at the exact line.
No filler, no corporate tone, no academic hedging. Sound like a senior engineer writing a postmortem: factual, specific, opinionated where it matters, honest about uncertainty.
If you're not sure about something, say so. "This looks like it could be X but I'd want to verify by running Y" is better than a confident wrong answer.
Preamble (run first)
# Shared helper scripts (invoke via: bash "$LIB_DIR/<script>.sh" <args>)
# lib/slug.sh — canonical repo slug (used below for SLUG=)
# lib/probe-exists.sh — probe sentinel files for existence, always exits 0.
# Use this INSTEAD OF `ls` for probing missing-file-safe.
# See Key Rule 2d for why.
LIB_DIR="${CODEBASE_AUDIT_LIB_DIR:-$HOME/.claude/skills/codebase-audit/lib}"
[ -d "$LIB_DIR" ] || echo "WARNING: $LIB_DIR does not exist. Run ./setup from the codebase-audit repo (or set CODEBASE_AUDIT_LIB_DIR)." >&2
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "BRANCH: $_BRANCH"
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
SLUG=$(bash "$LIB_DIR/slug.sh" 2>/dev/null || basename "$REPO_ROOT")
echo "SLUG: $SLUG"
AUDIT_HOME="${CODEBASE_AUDIT_HOME:-$HOME/.codebase-audits}"
echo "AUDIT_HOME: $AUDIT_HOME"
Both lib/slug.sh and lib/probe-exists.sh are load-bearing contracts shared with /plan-fixes. Both skills must compute the same slug for the same repo (slug contract) and use the same probe semantics for sentinel files (probe contract). Do not inline either derivation — always invoke the shared scripts so drift is impossible.
Why $LIB_DIR and not $REPO_ROOT: the shared scripts live in the skill's install directory (~/.claude/skills/codebase-audit/lib/ after ./setup), not in the audit target's git tree. $REPO_ROOT is the git root of wherever you invoke /codebase-audit — which for any real audit is NOT the codebase-audit repo itself. $LIB_DIR defaults to the standard install path and can be overridden with the CODEBASE_AUDIT_LIB_DIR env var for non-standard installs. The [ -d "$LIB_DIR" ] check at the top of the preamble warns if the directory doesn't exist (e.g., stale symlink after a deleted dev clone) so misconfiguration is diagnosable at first glance instead of surfacing as mysterious downstream failures.
If lib/slug.sh is unreachable (script missing, stale symlink, or unusual install), the || basename "$REPO_ROOT" fallback produces a basename-derived slug as a graceful degradation. lib/probe-exists.sh has NO bash-level fallback in later phases — if the script can't be found, phase probing fails loudly, because silent degradation is worse than visible failure for the probe use case.
/codebase-audit — Cold-Start Codebase Audit
Performs a full read-only audit of a codebase from scratch. No diff, no branch context — just the code as it exists right now. Produces a structured report with health score, findings by severity, and actionable recommendations.
You MUST NOT modify any source code. Your only Write operations are the report and baseline files in $AUDIT_HOME.
Modes
Detect the mode from arguments:
- Full (default, no flags): Run all 4 phases. Produces a complete report. Typically 10-30 minutes depending on codebase size.
- Quick (
--quick): Phase 1 only, plus the top 10 checklist patterns tagged[QUICK]. Produces a slim report: project profile, health score, top 5 findings. Target: under 2 minutes. - Regression (automatic): If a previous
baseline.jsonexists in$AUDIT_HOME/$SLUG/audits/, run the full audit and diff against the previous baseline. No flag needed — detected automatically. - Suggest Fixes (
--suggest-fixes): Adds a suggested code diff to each finding where a mechanical fix is possible. Mode compatibility:- Full: YES (default use case)
- Regression: YES (runs full audit)
- Quick: IGNORED (quick mode targets 2 minutes; generating diffs adds latency per finding)
- Future focused modes (
--security-onlyetc.): YES (applies to scanned subset) - CI mode (
--ci): IGNORED (CI wants machine-readable PASS/FAIL, not diffs)
- Quick Fix (
--quick-fix): Implies--suggest-fixes. Runs the full audit, then automatically applies all mechanical fixes tagged[HIGH CONFIDENCE]that meet quick-fix criteria (single file, <10 lines changed) in Phase 5. Mode compatibility:- Full: YES (default use case)
- Regression: YES (runs full audit)
- Quick: IGNORED (same rationale as
--suggest-fixes— quick mode targets 2 minutes) - Future focused modes (
--security-onlyetc.): YES (applies to scanned subset) - CI mode (
--ci): IGNORED (CI wants pass/fail, not file modifications)
- Changed Only (
--changed-only [ref]): Scopes the audit to files changed since a git ref. Default ref: merge base of current branch against the default branch. Skips Phase 2 (architecture scan) and does not write a baseline (partial audits would corrupt regression tracking). Mode compatibility:- Full flags (
--suggest-fixes,--quick-fix): YES — applies to changed files - Quick: IGNORED (changed-only is already fast)
- Regression: SKIP (no baseline to compare)
- Non-git repo: ERROR (requires git)
- Full flags (
- CI (
--ci): Machine-readable output for CI/CD pipelines. Implies--json. Outputs JSON to stdout via single-quoted heredoc and progress to stderr. Exits with code 0 (pass) or 1 (fail) based on finding threshold. All interactive prompts are suppressed — AskUserQuestion is never called. Mode compatibility:--fail-on <level>: Set fail threshold —critical(default) orimportant. Default:critical--fail-on-regression: Also fail if health score decreased since last baseline--fail-on-new: Fail only on NEW findings (not in previous baseline) at or above--fail-onthreshold. No baseline → falls back to normal threshold. For legacy codebase onboarding--baseline-only: Establish baseline for future regression tracking. Runs the full audit, writes baseline, always passes (exit 0). Use for CI onboarding on legacy codebases. Requires--cior--json. Mutually exclusive with--fail-on-newand--fail-on-regression--changed-only: YES — scoped CI check--quick: YES — fast CI gate--suggest-fixes: IGNORED, noted inmetadata.ignored_flags--quick-fix: IGNORED, noted inmetadata.ignored_flags
- JSON (
--json): Structured JSON output to stdout. Same format as--cibut without exit codes or fail thresholds. Status is always "pass". No plan file written. AskUserQuestion suppressed. Can be used standalone for dashboards, scripts, or tooling integration. - Severity Filter (
--min-severity <level>): Filters findings in output to only those at or above the specified severity:critical,important, ornotable. Does NOT affect health score calculation (score always counts all findings). Applies to report, conversation summary, and CI JSONfindingsarray. - Format (
--format sarif): SARIF 2.1.0 output for GitHub Code Scanning and compatible static analysis tools. Implies--jsonbehavior (no interactive prompts, no plan file). Outputs SARIF to stdout and writes to$AUDIT_HOME/$SLUG/audits/{dt}-results.sarif. Mode compatibility:--ci: YES — replaces JSON with SARIF, same exit code semantics--changed-only: YES — scoped SARIF--min-severity: YES — filtered results and rules arrays--quick: YES--suggest-fixes/--quick-fix: IGNORED--format json: Default (implicit). Explicit--format jsonis accepted but no-op.
- Infrastructure Scanning (automatic): When infrastructure files are detected in Phase 1.2 (Dockerfiles, K8s manifests, Terraform, GitHub Actions, docker-compose, nginx configs), infrastructure patterns are loaded and scanned automatically as an 8th category. Use
--no-infrato opt out. Infrastructure findings compete on severity with application findings for the 50-finding cap. - Plan Fixes (
--plan-fixes [--thorough]): Convenience alias. Runs the full audit to produce a baseline, then immediately invokes the sibling skill/plan-fixesagainst that baseline. The depth-aware grouping, investigation, and plan-file generation all live in/plan-fixes; this flag is a one-command shortcut that preserves the v1.8.0 UX. The--thoroughflag (when combined with--plan-fixes) is forwarded to/plan-fixes --thoroughso substantive findings are auto-investigated without a consent prompt. Seeplan-fixes/SKILL.mdfor the full behavior. Mode compatibility:- Full: YES (default use case)
- Regression: YES
- Changed-only: YES (plans scoped to changed files)
- Quick: IGNORED (alias dispatch suppressed — quick mode has insufficient context)
- CI/JSON/SARIF: IGNORED (alias dispatch suppressed — structured output, not interactive plans)
- Suggest-fixes: YES
- Quick-fix: YES (the alias dispatches AFTER Phase 5 + Phase 5.5 rewrites the baseline with
quick_fix_status, so/plan-fixessees the post-application state and annotates already-applied groups per its Option D coordination)
Arguments
/codebase-audit— full audit of the current project/codebase-audit --quick— quick smoke audit (2-min health check)/codebase-audit --suggest-fixes— full audit with inline fix diffs per finding/codebase-audit --quick-fix— full audit, then auto-apply high-confidence mechanical fixes/codebase-audit --changed-only— audit files changed since branch diverged from default branch/codebase-audit --changed-only HEAD~5— audit files changed in last 5 commits/codebase-audit --changed-only main— audit files changed vs main/codebase-audit --ci— CI mode: JSON output, exit code 0/1, no prompts/codebase-audit --ci --fail-on critical— CI mode, fail only on critical findings (default)/codebase-audit --ci --fail-on important— CI mode, fail on critical or important/codebase-audit --ci --fail-on-regression— also fail if score regressed/codebase-audit --ci --fail-on-new— fail only on new findings vs baseline/codebase-audit --json— JSON output without CI exit-code behavior/codebase-audit --min-severity important— show only important+ findings/codebase-audit --format sarif— SARIF 2.1.0 output for GitHub Code Scanning/codebase-audit --ci --format sarif— CI mode with SARIF output/codebase-audit --ci --format sarif --changed-only— scoped CI check with SARIF/codebase-audit --ci --baseline-only— establish baseline, always passes (for CI onboarding)/codebase-audit --plan-fixes— run the audit, then dispatch to the sibling/plan-fixesskill (alias). Equivalent to running/codebase-auditfollowed by/plan-fixes./codebase-audit --plan-fixes --thorough— same alias, with--thoroughforwarded to/plan-fixesso substantive findings are auto-investigated without a consent prompt./codebase-audit --no-infra— skip infrastructure scanning even if infra files are present
Phase 1: Orientation
Goal: understand what this project is, how big it is, what it's built with, and its recent health signals.
1.1 Project identity
The project slug was resolved in the preamble. Print it for reference:
echo "Project: $SLUG"
echo "Audit storage: $AUDIT_HOME/$SLUG/audits/"
1.2 Language and framework detection
bash "$LIB_DIR/probe-exists.sh" package.json Cargo.toml go.mod pyproject.toml Gemfile build.gradle pom.xml Makefile CMakeLists.txt composer.json mix.exs
# Glob matches (may expand to nothing) use find directly to keep the loop simple:
find . -maxdepth 1 \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null
Do not use ls to probe for sentinel files — see Key Rule 2d. ls exits non-zero when any listed path is missing, which cascade-cancels sibling parallel tool calls in Claude Code and produces red "Cancelled" noise for the user. lib/probe-exists.sh always exits 0 and prints only the files that exist.
Also scan for infrastructure config files with the same script:
bash "$LIB_DIR/probe-exists.sh" Dockerfile docker-compose.yml docker-compose.yaml nginx.conf
find .github/workflows -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null
find . -maxdepth 3 \( -name '*.tf' -o -name '*.tfvars' \) -not -path '*/node_modules/*' -not -path '*/vendor/*' -not -path '*/.git/*' 2>/dev/null | head -5
find . -maxdepth 3 \( -path '*/k8s/*.yaml' -o -path '*/k8s/*.yml' -o -path '*/deploy/*.yaml' -o -path '*/deploy/*.yml' -o -name 'Chart.yaml' \) -not -path '*/node_modules/*' -not -path '*/vendor/*' -not -path '*/.git/*' 2>/dev/null | head -5
find already exits 0 when nothing matches, so no || true tail is needed on those. probe-exists.sh emits only files that actually exist — no noise, no cascade-cancel risk.
If any infrastructure files are found, note "Infrastructure files detected" for use in Phase 3.1 (auto-loading the infra checklist). This detection is informational — the --no-infra flag controls whether infra patterns actually run.
Read whichever build/config files exist to determine: primary language, framework, build tool, test runner, package manager.
1.3 Codebase stats
Count lines of code, excluding vendored and build directories:
find . -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' -o -name '*.py' -o -name '*.rb' -o -name '*.go' -o -name '*.rs' -o -name '*.java' -o -name '*.cs' -o -name '*.cpp' -o -name '*.c' -o -name '*.h' -o -name '*.swift' -o -name '*.kt' -o -name '*.php' -o -name '*.sh' -o -name '*.bash' -o -name '*.zsh' -o -name '*.vue' -o -name '*.svelte' \) -not -path '*/node_modules/*' -not -path '*/vendor/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/build/*' -not -path '*/.next/*' -not -path '*/target/*' -not -path '*/__pycache__/*' -not -path '*/venv/*' | head -5000 | xargs wc -l 2>/dev/null | tail -1
This counts source code files only. If cloc is available, prefer it for a more accurate breakdown by language.
Classify the codebase size:
- Small: <10K LOC
- Medium: 10K–50K LOC
- Large: >50K LOC
1.4 Read orientation docs
Read these files if they exist: README.md, CLAUDE.md, ARCHITECTURE.md, CONTRIBUTING.md, docs/ARCHITECTURE.md. Skip any that don't exist — do not error.
1.5 Git state
If this is a git repo, gather recent activity:
git log --oneline -10
git log --format='%aN' | sort | uniq -c | sort -rn | head -10
If this is not a git repo, note that and skip all git-dependent steps gracefully.
1.6 Git churn analysis
Identify hotspot files (most frequently changed in the last 90 days):
git log --since=90.days --name-only --format="" | sort | uniq -c | sort -rn | head -20
Estimate bus factor for the top 5 hotspot files — how many unique authors have touched each:
git log --format='%aN' -- <file> | sort -u | wc -l
Skip this step if the repo is not a git repo or is a shallow clone.
1.7 Dependency vulnerability check
Detect the package manager and run the appropriate audit command if available. Pipe through jq (or equivalent) to extract only vulnerable entries — never dump the full dependency JSON to the terminal. A clean audit with 100+ deps should produce a single line of output, not 1000+ lines of zero-vuln entries.
- Python (pip-audit + jq):
pip-audit -f json 2>/dev/null | jq -r '.dependencies | map(select(.vulns | length > 0)) | if length == 0 then "No dependency vulnerabilities." else {count: length, vulns: .} end' - npm/yarn (npm audit + jq):
npm audit --json 2>/dev/null | jq -r 'if (.metadata.vulnerabilities.total // 0) == 0 then "No dependency vulnerabilities." else {total: .metadata.vulnerabilities.total, by_severity: .metadata.vulnerabilities} end' - Ruby:
bundle audit --format json 2>/dev/null | jq -r 'if (.results | length) == 0 then "No dependency vulnerabilities." else .results end' - Rust:
cargo audit --json 2>/dev/null | jq -r 'if (.vulnerabilities.count // 0) == 0 then "No dependency vulnerabilities." else .vulnerabilities end' - Go:
govulncheck -json ./... 2>/dev/null | jq -r 'select(.finding) | .finding' | head -50
If the audit tool is not installed or the command fails, skip gracefully and note "dependency audit tool not available" in the report.
For deeper dependency investigation and remediation beyond this quick scan, suggest the user run /deps (or /deps cve for security-only fixes).
If jq is not installed, fall back to: run the audit command with stdout redirected to /tmp/audit-$SLUG.json, then use Read against that file to inspect it. Never dump raw audit JSON to the conversation.
1.8 Size-based strategy decision
Based on codebase size from step 1.3:
- Small (<10K LOC): Read everything. Full coverage is feasible.
- Medium (10K–50K LOC): Read high-risk files fully (entry points, auth, payment, data access, configs). Sample the rest using Grep pattern matches.
- Large (>50K LOC): Use AskUserQuestion to ask the user which areas to focus on. Suggest the top 3 areas based on churn hotspots and framework-specific risk areas. Do not proceed until the user responds. If
--cior--jsonis active, use the Medium strategy automatically instead — do not use AskUserQuestion. CI/JSON mode must be non-interactive.
If in quick mode, stop after this phase. Jump to the Phase 3 quick-mode subset (top 10 [QUICK] patterns only), then skip to Phase 4 for the slim report.
1.9 Changed-only file resolution
Skip this step unless --changed-only is active. This step requires git — if not a git repo, error: "--changed-only requires a git repository" and stop.
Resolve the git ref:
If the user provided an explicit ref (e.g., --changed-only main, --changed-only HEAD~5):
git rev-parse --verify <ref> 2>/dev/null
If invalid, error: "Invalid git ref: <ref>" and stop.
If no explicit ref (bare --changed-only):
DEFAULT_BRANCH=$(git remote show origin 2>/dev/null | grep 'HEAD branch' | sed 's/.*: //')
[ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH="main"
MERGE_BASE=$(git merge-base HEAD "$DEFAULT_BRANCH" 2>/dev/null)
If merge-base fails (e.g., no common ancestor, already on the default branch with no divergence), error and stop.
Get the changed file list:
git diff --name-only --diff-filter=ACMR $REF...HEAD
--diff-filter=ACMR includes Added, Copied, Modified, and Renamed files. Deleted files are excluded (nothing to audit). Renamed files appear by their new name only, which is correct for auditing current state. Binary files may appear in the list — Grep will silently skip them, which is expected.
If the list is empty: print "No files changed since {ref}. Nothing to audit." and exit gracefully.
Print: "Auditing N files changed since {ref}"
Store the file list for use in Phase 3. Skip Phase 1.8 (size-based strategy) — the scope is already defined by the changed file list.
Phase 2: Architecture Scan
Skip this phase entirely in quick mode or if --changed-only is active (architecture scan is not meaningful for a scoped file set).
2.1 Map entry points and boundaries
Read the main entry points: app bootstrap files, routers, API handlers, CLI entry points. Identify:
- What the application does (web server, CLI, library, service, monorepo)
- Major components and their boundaries
- External dependencies and integrations (databases, APIs, queues, caches)
- Data flow: how requests/data enter, transform, and exit
2.2 Identify layers
Map the architectural layers: presentation, business logic, data access, infrastructure. Note which layers exist and which are missing or blurred.
2.3 Configuration and environment
Read configuration files, environment variable usage, and secrets management. Look for:
- Hardcoded credentials or secrets
- Environment-specific configuration
- Feature flags
- Build/deploy configuration
2.4 Output architecture diagram
Produce an ASCII architecture diagram showing components, their relationships, data flow, and external dependencies. Keep it to 20-30 lines maximum. This goes in the report.
Phase 3: Targeted Deep Dives
In quick mode, run only the top 10 patterns tagged [QUICK] from the checklist, then skip to Phase 4.
In full mode, run the complete checklist.
3.1 Load checklists
Use the Read tool (not Bash cat) to load the primary checklist:
~/.claude/skills/codebase-audit/checklist.md
If the checklist file is unreadable or missing, STOP and report an error: "Audit checklist not found at ~/.claude/skills/codebase-audit/checklist.md — cannot continue." Do not proceed without it.
Then use the Read tool to load the supplemental patterns reference:
~/.claude/skills/codebase-audit/references/patterns.md
If infrastructure files were detected in Phase 1.2 and --no-infra is NOT active, also load the infrastructure checklist:
~/.claude/skills/codebase-audit/references/infra-checklist.md
If the infra checklist file is unreadable or missing, WARN: "Infrastructure checklist not found — skipping infra patterns. Run setup to install." Continue the audit without infra patterns (do not stop). If no infra files were detected in Phase 1.2, skip loading the infra checklist entirely.
3.2 Load custom checklist
If the target project contains .codebase-audit/checklist.md, read it and append its items to the built-in checklist. Built-in patterns run first, custom additions run second. This allows projects to define custom audit rules.
3.3 Execute checklist
Work through the checklist in priority order:
- Security — injection, auth bypass, secrets exposure, SSRF, path traversal
- Correctness — logic errors, race conditions, null safety, error handling
- Reliability — crash paths, resource leaks, timeout handling, retry logic
- Tests — coverage gaps, test quality, missing edge cases, flaky patterns
- Architecture — coupling, abstraction leaks, circular dependencies, god classes
- Tech Debt — dead code, TODO/FIXME/HACK comments, deprecated APIs, copy-paste
- Performance — N+1 queries, unbounded collections, missing indexes, large payloads
- Infrastructure (if infra files detected and
--no-infranot active) — Docker, Kubernetes, Terraform, CI/CD, nginx misconfigurations
For each checklist item: use Grep in files_with_matches mode (not content mode) to find which files match, then use Read to examine the specific lines for confirmation. Do not dump entire file contents into the conversation — use targeted reads of specific line ranges. Do not report a pattern match as a finding without reading the context — many patterns have legitimate uses.
If --changed-only is active: Scope Grep to only the changed files from Phase 1.9.
- ≤20 changed files: Pass each file path to Grep's
pathparameter individually. - >20 changed files: Run Grep on the full codebase as normal, then filter results to only include files in the changed-only list. This avoids thousands of individual Grep calls.
Binary files in the changed list will be silently skipped by Grep — this is expected and correct. Renamed files appear by their new name only.
Important: Keep the conversation output concise. For checklist execution, use files_with_matches to identify candidate files, then Read specific line ranges. Never let a single Grep call return hundreds of lines of content into the conversation.
If --suggest-fixes is active: After confirming each finding via Read and before moving to the next checklist item, generate a suggested fix diff. Follow these rules:
- Minimal change only. Show the smallest diff that addresses the finding. Do not refactor, rename, or restructure surrounding code.
- Unified diff format. Use standard unified diff notation:
--- a/path,+++ b/path,@@ -LINE,COUNT +LINE,COUNT @@. - Target 5-10 lines per diff. If the minimal fix genuinely requires more, show the full fix but note it as a larger change. Never truncate a diff mid-fix.
- Repeated patterns. Show up to 3 representative diffs if the fix pattern varies across locations, then list remaining locations grouped by which pattern applies. For homogeneous patterns, show the fix once and list other locations.
- Unfixable findings. If a finding cannot be fixed with a code diff (missing tests, architectural coupling, design decisions needed), do NOT generate a diff. Instead mark:
**Suggested Fix:** Requires design decision — see fix plan Part 2.or**Suggested Fix:** Requires implementation — no single-file fix exists. - Classification alignment. Mechanical findings always get diffs. Substantive findings get a diff ONLY if there is an obvious first step. Otherwise, mark as requiring design decision.
- Confidence tag. Mark each diff as
[HIGH CONFIDENCE](deterministic fix — removing hardcoded secret, adding timeout, narrowing exception type) or[REVIEW SUGGESTED](best-guess fix — choosing validation approach, error type selection, refactoring pattern). - Non-source files. Do NOT generate diffs for findings in vendored (
node_modules,vendor), generated (dist,build), or binary files. Instead note: "Fix upstream in [package]" or "Fix in [generator source]."
3.4 Finding limits
Cap detailed findings at 50. If more than 50 findings are identified, keep the top 50 by severity and provide a summary table for the rest (category, count, example file).
3.5 Finding format
Every finding MUST include:
- Severity: Critical, Important, Worth noting, or Opportunity
- Category: Security, Correctness, Reliability, Tests, Architecture, Tech Debt, Performance, or Infrastructure
- Title: One-line description
- Location:
file:linefor code findings. For non-code findings (missing tests, dependency vulnerabilities, architectural patterns), reference the most relevant file or component. - Evidence: The specific code or pattern found
- Recommendation: What to do about it
- Suggested Fix (if
--suggest-fixes): Unified diff showing the minimal code change with confidence tag ([HIGH CONFIDENCE]or[REVIEW SUGGESTED]), OR "Requires design decision" / "Requires implementation" for findings without a clear mechanical fix.
No hallucinating findings. Every finding must reference a specific file and line (or component for non-code findings). If you cannot point to it in the codebase, do not report it.
3.6 Severity calibration
Use these exact definitions:
- Critical: Exploitable security vulnerability, data loss risk, correctness bug that produces wrong results in production. Would block a release.
- Important: Significant reliability risk, missing error handling on critical paths, test gaps on core business logic, architectural problems that will compound. Worth scheduling promptly.
- Worth noting: Code smells, minor tech debt, style inconsistencies, non-critical performance issues. Address during normal development when touching nearby code.
- Opportunity: Not a problem — a concrete improvement that would make the codebase better. New patterns, better abstractions, tooling upgrades.
Phase 4: Report Generation
4.0 Report and plan — two outputs
The audit produces two artifacts:
- Report + baseline — written to
$AUDIT_HOME/$SLUG/audits/via Bash heredoc (permanent record, not actionable by Claude Code) - Fix plan — written to the plan file (actionable — this is what "Ready to code?" executes)
The audit is planning-for-a-plan. The report is the research; the plan file is the actionable output. This is compatible with plan mode — the audit phases (1-3) are read-only research, and Phase 4 produces both the archival report and the executable fix plan.
Always use Bash heredoc to write the report and baseline to $AUDIT_HOME — the Write tool may be restricted to the plan file in plan mode.
4.1 Load report template
Use the Read tool to load the report template:
~/.claude/skills/codebase-audit/report-template.md
Use this template to structure the final report. If the template is missing, use the structure described below as a fallback.
4.2 Calculate health score
Start at 100 and deduct per finding:
- Critical: -25 points each
- Important: -10 points each
- Worth noting: -3 points each
- Opportunity: no deduction
Floor at 0. No score exceeds 100. The model is deliberately simple — use regression mode to track relative improvement rather than fixating on the absolute number.
4.3 Write the report
Create the output directory using the slug from the preamble:
mkdir -p "$AUDIT_HOME/$SLUG/audits"
Generate a datetime stamp and write the report to $AUDIT_HOME/$SLUG/audits/{datetime}-audit.md. Use format YYYY-MM-DD-HHMMSS for the datetime (e.g., 2026-03-20-143022).
The report should contain:
- Header: Project name, date, mode, health score
- Executive Summary: 3-5 sentence overview of codebase health
- Project Profile: Language, framework, size, test coverage estimate, git activity
- Architecture Diagram: ASCII diagram from Phase 2 (skip in quick mode)
- Findings by Severity: Grouped by severity, then by category within each severity level
- Dependency Vulnerabilities: Summary from Phase 1 CVE check (if any found). Note that
/depscan investigate and remediate these further. - Churn Hotspots: Top files by change frequency and bus factor
- Summary Table: Category x severity matrix with counts
- Top 5 Priorities: The 5 most impactful things to fix, in order
- Recommendations: Strategic suggestions beyond individual findings
For quick mode, the slim report contains only: Header, Executive Summary, Project Profile, Health Score, Top 5 Findings.
For --changed-only mode, add a Scope section after the Header showing the ref, file count, and file list. Include a note: "This is a scoped audit. Only changed files were analyzed. Run a full /codebase-audit for complete coverage." Skip Architecture Diagram and Churn Hotspots.
4.4 Write baseline JSON
Skip baseline generation if --changed-only is active. Partial audit baselines would corrupt regression tracking against full audits.
Write a companion {datetime}-baseline.json file in the same directory. This is used for regression comparison on future runs.
Schema:
{
"version": "1.0.0",
"datetime": "2026-03-20T14:30:22Z",
"mode": "full",
"slug": "org-project",
"health_score": 72,
"quick_fix_applied": false,
"codebase": {
"loc": 24500,
"languages": ["TypeScript", "Python"],
"framework": "Next.js",
"test_files": 47,
"dependency_vulns": 3
},
"findings": [
{
"id": "<sha256 hash of file + category + title>",
"severity": "critical",
"category": "security",
"title": "SQL injection in user search",
"file": "src/api/users.ts",
"line": 42,
"has_suggested_fix": true,
"quick_fix_status": "applied"
}
],
"summary": {
"critical": 1,
"important": 5,
"notable": 12,
"opportunity": 8,
"total": 26
}
}
The has_suggested_fix field is present when --suggest-fixes was used. The quick_fix_status field is present when --quick-fix was used — values are "applied", "skipped", or omitted. The quick_fix_applied top-level field is true when --quick-fix was active. Omit all these fields when the corresponding flag was not used — old baselines won't have them, and consumers should treat missing fields as false/null. The finding ID hash does NOT include these fields (it's based on file:category:title only), so adding or removing fix suggestions or quick-fix status doesn't change finding identity for regression tracking.
Each finding gets a deterministic content-based ID for stable regression comparison. The canonical pattern is a single-quoted heredoc with __HASH_N__ placeholders, then a sed pass to replace them with pre-computed hashes. This pattern is load-bearing: writing $(echo -n ... | shasum) inline inside any heredoc is a trap — single-quoted heredocs don't expand substitutions (so the literal string gets written to the file instead of the hash), and unquoted heredocs expand every $ in the body (so any finding title containing a literal $ gets mangled by environment variable expansion). The placeholder+sed approach closes both traps by construction.
# Pick the available hash tool once, up front. shasum is on macOS by default;
# sha256sum is on most Linux distributions via coreutils. If neither exists,
# fail loudly rather than silently writing empty id fields.
if command -v shasum >/dev/null 2>&1; then
_sha() { echo -n "$1" | shasum -a 256 | cut -d' ' -f1; }
elif command -v sha256sum >/dev/null 2>&1; then
_sha() { echo -n "$1" | sha256sum | cut -d' ' -f1; }
else
echo "ERROR: neither shasum nor sha256sum available. Install one to enable baseline regression tracking." >&2
exit 1
fi
# Compute hashes for each finding FIRST, before the heredoc.
# _sha handles the file:category:title canonicalization; pass the raw string.
H1=$(_sha "src/api/users.ts:security:SQL injection in user search")
H2=$(_sha "src/api/auth.ts:error-handling:Bare except clause")
# ... one per finding
# Write the baseline with placeholder tokens. Single-quoted heredoc
# (<<'EOF') disables ALL expansion, so finding titles containing $ or
# backticks or $(...) are safe by construction.
BASELINE="$AUDIT_HOME/$SLUG/audits/${DT}-baseline.json"
cat > "$BASELINE" <<'EOF'
{
"version": "1.0.0",
"findings": [
{"id": "__HASH_1__", "severity": "critical", "title": "SQL injection in user search", "file": "src/api/users.ts", "line": 42},
{"id": "__HASH_2__", "severity": "important", "title": "Bare except clause", "file": "src/api/auth.ts", "line": 15}
]
}
EOF
# Then replace placeholders with computed hashes in one sed pass.
# Use | as the sed delimiter (matches lib/slug.sh convention).
# The .bak/rm dance works on both GNU sed and BSD sed without flag conflicts.
sed -i.bak \
-e "s|__HASH_1__|$H1|g" \
-e "s|__HASH_2__|$H2|g" \
"$BASELINE"
rm -f "$BASELINE.bak"
# Integrity guard: verify all placeholders were replaced. If any __HASH_N__
# token remains in the file, a finding was added to the heredoc without a
# corresponding sed substitution. This is a silent-failure trap — the
# baseline would be valid JSON but contain literal "__HASH_3__" strings in
# id fields, breaking regression comparison the same way the original bug
# did. Fail loudly so the error is visible at audit time, not later.
if grep -q '__HASH_' "$BASELINE"; then
echo "ERROR: baseline contains unresolved __HASH_ placeholders at $BASELINE. A finding was added without a corresponding sed substitution. Delete the broken baseline and fix Phase 4.4." >&2
exit 1
fi
Run this pattern for every finding in the baseline — one H<N> variable per finding, one sed -e expression per finding, all inside a single sed invocation. The integrity guard at the end is non-negotiable: it's the difference between "loud bug at audit time" and "silent regression-comparison corruption discovered weeks later."
4.5 Regression comparison
Skip regression comparison if --changed-only is active (no baseline was written).
If a previous baseline.json exists in the same audits directory AND the current mode is full (not quick, not changed-only):
- Load the most recent previous baseline
- Compare findings by their content-based IDs
- Compute:
- Fixed: findings in previous baseline not present in current run
- New: findings in current run not present in previous baseline
- Persistent: findings present in both
- Score delta: current score minus previous score
- Add a "Regression Summary" section to the report showing these deltas
If no previous baseline exists, skip regression comparison.
4.6 Conversation summary
If --ci, --json, or --format sarif is active, replace the human-readable conversation summary with structured output construction. Progress messages during Phases 1-3 go to stderr (echo '...' >&2). The output is NOT emitted here — data is assembled for Phase 4.8. If --format sarif is active, data is assembled for SARIF emission instead of JSON. If --min-severity is active, filter the findings array and conversation summary to only include findings at or above the specified severity level.
Otherwise, print a summary directly to the conversation. This is what the user sees immediately:
- Health Score: The number and a one-line interpretation (e.g., "72/100 — solid foundation with some important gaps")
- Executive Summary: 3-5 sentences
- Top 5 Priorities: Numbered list with severity, title, and file reference
- Summary Table: Category x severity counts
- Report location: Full path to the written report
- Regression delta (if applicable): Score change, count of fixed/new findings
- Fix Coverage (if
--suggest-fixes): "N of M findings have suggested fixes (X%)" — gives instant signal on how actionable the audit is - Quick Fix Preview (if
--quick-fix): "N fixes will be auto-applied in Phase 5 (M skipped: K review-suggested, J multi-file, L too large)" — tells the user what Phase 5 will do before it runs - Scope (if
--changed-only): "Audited N files changed since {ref}" — reminds the user this was a scoped audit - Tips — consolidated block at the end of the summary. Each tip fires at most once per audit. Only include tips that are relevant:
- If neither
--suggest-fixesnor--quick-fixwas used and there are findings: "Run with--suggest-fixesfor inline fix diffs, or--quick-fixto auto-apply the safe ones." - If
--suggest-fixeswas used but--quick-fixwas NOT, and there are[HIGH CONFIDENCE]single-file findings: "N of these findings could be auto-applied with/codebase-audit --quick-fix" - If this is a full audit (not
--changed-only), on a non-default branch, with >20 findings: "Use--changed-onlyto audit only files changed on this branch." - If
--no-infrawas used but infrastructure files were detected: "Infrastructure files detected but scanning was disabled via--no-infra." - If
--ciis active and no previous baseline exists and--baseline-onlyis NOT active: "First CI run on this codebase. Use--baseline-onlyto establish a baseline without failing, then--fail-on-newfor ongoing runs." - If audit found substantive findings and
--plan-fixeswas not used: "Found {N} substantive findings. Run/plan-fixesto generate grouped, PR-sized fix plans with depth-aware investigation (callers, tests, context). The baseline is already on disk — no re-audit needed." - If
--plan-fixeswas used (via alias): "Tip:/plan-fixesalso reads SARIF 2.1.0 from any source (CodeQL, ESLint, Semgrep, Sonar, GitHub Code Scanning). Try/plan-fixes --from results.sarif." - If no tips are applicable, omit the Tips block entirely.
- If neither
4.7 Write the Fix Plan
Skip this phase if --ci is active (no plan file). In --json mode without --ci, also skip the plan file. AskUserQuestion is never called in --ci or --json mode.
Alias dispatch to /plan-fixes: If --plan-fixes is active AND the mode is not --ci/--json/--quick (all of which suppress the alias):
-
Ordering depends on
--quick-fix:- If
--quick-fixis NOT active: dispatch immediately after Phase 4.6 (conversation summary). - If
--quick-fixIS also active: defer the dispatch until AFTER Phase 5 applies fixes AND Phase 5.5 rewrites the baseline withquick_fix_status. The alias dispatch runs at the end of Phase 5.5.
- If
-
Existence check: Before dispatching, verify that
~/.claude/skills/plan-fixesexists (directory or symlink). If not, emit this exact error and stop (do NOT fall through to the default flat plan output — the user explicitly asked for--plan-fixes):Audit completed successfully. Baseline written to: <baseline-path> However, --plan-fixes requires the /plan-fixes sibling skill, which is not installed. To install both skills (including /plan-fixes), run the repo's setup script: cd <repo-root> && ./setup After installing, run: /plan-fixes --from <baseline-path> -
Dispatch directive: Emit a one-line banner, then invoke
/plan-fixes:Dispatching to /plan-fixes against <baseline-path>...Invocation:
/plan-fixes --from <baseline-path>— and if--thoroughwas on the original/codebase-auditinvocation, append--thoroughto the sub-invocation. The baseline path is the one written by Phase 4.4 (or rewritten by Phase 5.5 when--quick-fixis active). -
Forwarding rule:
--thoroughis the only argument forwarded. Any other flags on the original invocation (--quick-fix,--suggest-fixes,--min-severity, etc.) are NOT forwarded — they applied to the audit, not to planning. -
Sub-invocation failure: If
/plan-fixesfails during Phase 1-7, emit this exact error and return control to the user:Audit completed successfully. Baseline written to: <baseline-path> Planning failed: <error from /plan-fixes> The baseline is preserved on disk. To retry planning: /plan-fixes --from <baseline-path> [--thorough if originally passed]The audit's success is NOT rolled back. The baseline remains available for manual re-invocation.
-
When alias dispatch is active, skip the default flat plan output below. The plan files come from
/plan-fixes.
If --plan-fixes is NOT active, proceed with the default flat plan output below.
After printing the conversation summary, write the fix plan to the plan file. The audit is planning-for-a-plan — the plan file is the natural, actionable output.
Classify each finding:
- Mechanical (gitignore patterns, narrowing exception types, adding timeouts, adding inline auth checks, replacing assert with explicit checks — things with zero design judgment, single-file changes)
- Substantive (architecture changes, error handling redesign across many files, test coverage additions, security pattern changes — things requiring design decisions or touching 3+ files)
Structure the plan file with two parts:
> **Recommended workflow:**
> 1. Accept this plan to apply Part 1 (mechanical fixes) immediately
> 2. Then review Part 2 (substantive fixes) before implementing
>
> Or accept the full plan to implement everything in one session.
# Codebase Audit Fix Plan
## Context
{audit summary, score, commit}
## Part 1: Mechanical Fixes (apply immediately)
### Part 1a: High Confidence (apply directly)
{Findings tagged [HIGH CONFIDENCE] — deterministic, safe to apply without review}
{Each entry includes: file, problem, suggested diff (if --suggest-fixes), verify}
### Part 1b: Review Suggested Fix
{Findings tagged [REVIEW SUGGESTED] — best-guess, review the diff before applying}
{Each entry includes: file, problem, suggested diff (if --suggest-fixes), alternatives note, verify}
{If --suggest-fixes was NOT used, omit the 1a/1b split — use a flat list as before}
## Part 2: Substantive Fixes (review first)
> These fixes touch multiple files and benefit from architectural review.
{For each substantive finding: scope, approach, files to modify, verification}
If there are no substantive findings (all mechanical), omit Part 2 entirely.
If there are no findings worth fixing (all Notable/Opportunity), write a minimal plan:
# Codebase Audit — No Action Required
Health score: {N}/100. No critical or important findings.
See full report at $AUDIT_HOME/{slug}/audits/{datetime}-audit.md
If --quick-fix is active: Mark Part 1a findings (HIGH CONFIDENCE, single-file, <10 lines) with [AUTO-APPLYING] prefix in the plan. These still appear for documentation, but Phase 5 will apply them automatically. The plan's recommended workflow becomes:
Recommended workflow:
- Phase 5 will auto-apply Part 1a (high-confidence mechanical fixes)
- Review Part 1b (review-suggested fixes) and apply manually
- Then review Part 2 (substantive fixes) before implementing
If all findings are auto-applying (no Part 1b or Part 2), the plan says: "All fixes will be applied by --quick-fix. No further manual action needed."
Skip the AskUserQuestion in Phase 4.7 when --quick-fix is active — the user already opted into auto-application by passing the flag. Proceed directly to Phase 5.
If --quick-fix is NOT active, use AskUserQuestion to offer the next step:
If there are substantive findings (Part 2 exists):
"Audit complete. Plan written with {M} mechanical fixes (Part 1) and {S} substantive fixes (Part 2). The mechanical fixes are ready to apply. The substantive fixes benefit from review before implementation."
Options:
- A) Accept the plan as-is — apply all fixes
- B) I want to make changes first — edit the plan before proceeding
If there are only mechanical findings (no Part 2):
"Audit complete. Plan written with {M} mechanical fixes — all straightforward."
Options:
- A) Apply fixes now (recommended)
- B) I want to review the plan first
4.8 Structured output
Skip unless --ci, --json, or --format sarif is active.
If --format is specified with an unsupported value (anything other than json or sarif), error: "Unsupported format: {value}. Supported formats: json, sarif." and stop.
If --baseline-only is used without --ci or --json, error: "--baseline-only requires --ci or --json mode." and stop.
If --baseline-only is used with --fail-on-new, error: "Cannot use --baseline-only with --fail-on-new. Use --baseline-only for the first run, then --fail-on-new for subsequent runs." and stop.
If --baseline-only is used with --fail-on-regression, error: "Cannot use --baseline-only with --fail-on-regression. --baseline-only always passes." and stop.
4.8.1 Build output data from baseline
Read the baseline written in Phase 4.4 and augment with output-specific fields:
status: "pass" or "fail" (always "pass" for--jsonor--format sarifwithout--ci, always "pass" for--baseline-only)threshold: the--fail-onlevel (default: "critical")findings_above_threshold: count of findings at or above thresholdfindings_count: total findings count (for truncation detection)metadata.schema_version: "1.0"metadata.tool_version: read from VERSION filemetadata.duration_seconds: elapsed time since audit startmetadata.mode: "ci", "json", or "sarif"metadata.baseline_only:trueif--baseline-onlyis active (omit otherwise). Lets consumers distinguish "passed because clean" from "passed because baseline mode."metadata.flags: array of invocation flags usedmetadata.ignored_flags: array of suppressed flags (e.g.,--suggest-fixesin CI mode)
If --min-severity is active, filter the findings array to only include findings at or above the specified severity. findings_count reflects the filtered count. Health score and summary are NOT filtered (they reflect full audit). Note: this means summary.total and findings_count may differ when --min-severity is used — this is intentional (summary = full audit picture, findings array = filtered view).
4.8.2 Determine pass/fail
If --baseline-only is active, skip pass/fail determination. Set status to "pass" unconditionally.
Skip for --json or --format sarif without --ci.
- Count findings at or above the
--fail-onthreshold (default:critical)--fail-on critical: fail if any critical findings--fail-on important: fail if any critical or important findings
- If
--fail-on-regressionand a previous baseline exists: also fail ifhealth_scoredecreased - If
--fail-on-newand a previous baseline exists: count only findings whose IDs are NOT in the previous baseline. Fail if any new findings at or above--fail-onthreshold. No baseline → fall back to normal threshold. - Set
statusto"pass"or"fail"
4.8.3 Emit output
If --format sarif is active:
Construct a SARIF 2.1.0 document. Follow this structure exactly:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "codebase-audit",
"semanticVersion": "{tool_version}",
"informationUri": "https://github.com/boinger/zorille",
"rules": [
{
"id": "{category}/{kebab-title}",
"shortDescription": { "text": "{finding title}" },
"fullDescription": { "text": "{recommendation}" },
"defaultConfiguration": { "level": "{error|warning|note}" },
"properties": { "security-severity": "{9.0|7.0|4.0|1.0}" }
}
]
}
},
"results": [
{
"ruleId": "{category}/{kebab-title}",
"level": "{error|warning|note}",
"message": { "text": "{finding title}" },
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "{relative/file/path}",
"uriBaseId": "%SRCROOT%"
},
"region": { "startLine": "{line_number}" }
}
}
]
}
]
}
]
}
Rule ID construction: For each finding, construct the rule ID as {category}/{kebab-title}:
category: lowercase finding category (security,correctness,reliability,architecture,tests,tech-debt,performance,infrastructure)kebab-title: finding title lowercased, spaces and special characters replaced by hyphens, consecutive hyphens collapsed- Example: category "Security", title "Hardcoded secrets" →
security/hardcoded-secrets - Example: category "Tech Debt", title "TODO/FIXME/HACK markers" →
tech-debt/todo-fixme-hack-markers
Rules deduplication: Multiple findings may share the same rule ID (e.g., two "empty catch blocks" in different files). Declare each unique rule ID exactly once in tool.driver.rules[]. Each finding becomes a separate entry in results[].
Message structure: message.text contains the finding title only (renders inline on code in GitHub). The recommendation goes in rule.fullDescription.text (visible when clicking through to finding detail). This keeps inline annotations clean.
Severity mapping:
| Audit severity | SARIF level | security-severity |
|---|---|---|
| Critical | "error" | "9.0" |
| Important | "warning" | "7.0" |
| Worth noting | "note" | "4.0" |
| Opportunity | "note" | "1.0" |
File path normalization: All uri values must be relative to the repository root with forward slashes only and no leading ./. Compute the git root:
GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
Strip the git root prefix from each finding's file path. If not a git repo, paths are relative to pwd.
Non-file findings: Findings that reference components or patterns rather than specific files (e.g., "missing integration tests") should use the most relevant file as the location. If no file is applicable, omit the locations array for that result — SARIF permits results without locations, though GitHub renders these less usefully.
If --min-severity is active: Filter both the results array and the rules array. Only include rules that are referenced by at least one included result.
Emit SARIF via single-quoted heredoc to prevent shell expansion:
cat <<'EOF'
{ ... the SARIF JSON ... }
EOF
Write SARIF to file: $AUDIT_HOME/$SLUG/audits/{dt}-results.sarif
Upload to GitHub Code Scanning with: github/codeql-action/upload-sarif@v3
Otherwise (default JSON format):
Emit JSON via single-quoted heredoc to prevent shell expansion:
cat <<'EOF'
{ ... the JSON ... }
EOF
Write JSON to file: $AUDIT_HOME/$SLUG/audits/{dt}-ci-output.json
4.8.4 Exit
If --baseline-only is active, always exit 0 regardless of findings.
--ci only — skip for --json or --format sarif without --ci:
exit 0 # pass
or:
exit 1 # fail
CI consumers SHOULD parse the JSON status field (or SARIF results) as the primary signal. The exit 0/exit 1 from the Bash block is a convenience — it exits the Bash subprocess, not the Claude Code session, and may not propagate to the CI runner's exit code depending on how Claude Code is invoked.
Do not proceed to Phase 5 in --ci mode.
Phase 5: Quick Fix Application
Skip this phase entirely unless --quick-fix is active. Also skip if --ci is active (CI mode should not modify source code). This phase modifies source code.
5.1 Check preconditions
Before modifying any files, check the working tree:
git status --porcelain 2>/dev/null
If the output is non-empty (uncommitted changes exist), use AskUserQuestion:
"Working tree has uncommitted changes. Quick-fix modifications will be mixed with existing changes."
Options:
- A) Proceed anyway — apply fixes alongside existing changes
- B) Abort Phase 5 — skip fix application, keep the report and plan as-is
If this is not a git repo, skip this check and proceed (commit proposal in 5.4 will also be skipped).
5.2 Collect eligible fixes
From the findings generated in Phase 3, collect all fixes that meet ALL of these criteria:
- Tagged
[HIGH CONFIDENCE] - Single file change (diff touches exactly one file)
- Less than 10 lines changed (sum of added + removed lines in the unified diff)
- File is not vendored (
node_modules/,vendor/,.git/), generated (dist/,build/), or binary - File still exists at the path referenced in the diff
Fixes that fail any criterion are skipped. Track each skipped fix and its skip reason (e.g., "review-suggested", "multi-file", "too large", "file not found").
If zero fixes are eligible, print "No fixes met quick-fix criteria. All findings require manual review — see the fix plan." and skip to Phase 5.5.
5.3 Apply fixes
For each eligible fix, in the order they appeared in the findings:
- Read the target file fresh. Do NOT rely on line numbers from Phase 3 — earlier fixes in Phase 5 may have shifted them. Use the Edit tool's
old_stringmatching to locate the correct position. - Verify context. The diff's context lines (unchanged lines surrounding the change) and
-lines (lines to be removed/replaced) must match the current file content exactly. - If context matches: Apply the fix using the Edit tool. Use the
-lines asold_stringand the+lines asnew_string, with enough surrounding context to ensure a unique match. - If context does NOT match: Skip the fix and record reason: "file content changed since audit" or "conflict with earlier fix in same file."
Important:
- Apply one fix at a time. If a fix fails to apply, skip it and continue with the remaining fixes. Do not abort Phase 5 because one fix failed.
- For multiple fixes in the same file: after applying each fix, re-Read the file before attempting the next. Line numbers from Phase 3 are stale after any modification.
- Never modify anything beyond the exact change described in the diff. No cleanup, no reformatting, no adjacent improvements.
5.4 Stage and propose commit
If at least one fix was applied and this is a git repo:
-
Stage only the specific files modified by quick-fix:
git add <file1> <file2> ...Do NOT use
git add -Aorgit add .. -
Print the quick-fix summary:
- Applied (N fixes): list each with file path, finding title, and one-line description of the change
- Skipped (M fixes): list each with file path, finding title, and skip reason
-
Print the proposed commit message:
fix: apply N mechanical fixes from codebase audit - <finding title> (file) - <finding title> (file) ... -
Use AskUserQuestion:
"N mechanical fixes applied and staged."
Options:
- A) Commit with this message
- B) Commit with a different message
- C) Keep staged, don't commit (user will commit manually)
- D) Unstage everything (user wants to review further)
-
Execute the user's choice:
- A: Create the commit with the proposed message.
- B: Ask for the message, then create the commit.
- C: Do nothing further.
- D: Run
git reset HEAD <files>to unstage.
If this is not a git repo, skip this step entirely — just print the applied/skipped summary.
5.5 Rewrite baseline with quick_fix_status
This step exists so that /plan-fixes (when invoked via the --plan-fixes alias) sees a baseline that accurately reflects which findings were auto-applied in Phase 5. Without this step, a baseline-mediated alias dispatch would give /plan-fixes stale data and its Option D annotation ([ALREADY APPLIED] markers) would miss everything.
Behavior:
- Re-load the baseline file written in Phase 4.4 at
$AUDIT_HOME/$SLUG/audits/{datetime}-baseline.json. - For each finding in the baseline's
findings[]array, setquick_fix_status:"applied"if the finding's fix was successfully applied by Phase 5"skipped"if the fix was attempted but failed (Phase 5 skipped it and continued)- Leave the field unset otherwise (the finding wasn't a quick-fix candidate)
- Also update
quick_fix_applied: trueat the top level (it wasfalsewhen written in Phase 4.4). - Rewrite the baseline file to disk atomically (write to a temp file, then rename).
- Emit a one-line log to stderr:
"Baseline rewritten with quick_fix_status ({N} applied, {M} skipped).".
Failure mode: If the rewrite fails (disk full, permission denied, interrupted mid-write), Phase 5's applied-fix state is already persisted to the working tree — the fixes ARE on disk, git reflects them. Phase 5.5 failure only affects the baseline's quick_fix_status field, not the actual code changes. Emit a warning:
Warning: baseline rewrite failed (<reason>). Plan files may not reflect already-applied fixes. Baseline at <path> still reflects pre-Phase-5 state.
If the --plan-fixes alias is active, the dispatch still proceeds with the original baseline, and the resulting plans will show those findings as pending instead of applied — the plans are still correct, just less annotated. The user can manually re-run /plan-fixes --from <baseline> after fixing the disk issue. Phase 5's success is NOT rolled back.
Skip Phase 5.5 entirely if Phase 5 was skipped (no --quick-fix).
5.6 Final summary
Print:
- "Quick-fix complete. N fixes applied, M skipped."
- "Recommend running your test suite to verify:
{detected test command}" — detect the test command frompackage.jsonscripts,Makefile, or equivalent. - If remaining plan items exist (Part 1b or Part 2): "The fix plan still contains K items requiring manual review."
- Report location reminder.
After Phase 5.6 completes, if the --plan-fixes alias is active: execute the alias dispatch from Phase 4.7 now. This is the --quick-fix + --plan-fixes ordering path — the baseline has been rewritten in 5.5, the summary has been printed in 5.6, and the user is ready to see their fix plans.
Edge Cases
- Empty or binary-only project: If the codebase has fewer than 10 text files or fewer than 100 LOC, write a brief report noting this and exit gracefully. Do not force findings.
- Not a git repo: Skip all git-dependent steps (churn analysis, bus factor, recent activity). Note in the report that git history was unavailable.
- Zero findings: If the audit produces zero findings, note this in the report with a caveat: "Zero findings is unusual — this may indicate the checklist patterns don't match this tech stack. Consider running with a custom checklist."
- 500+ raw pattern matches: If Grep returns an overwhelming number of matches for a pattern, sample the first 20 and note the total count. Do not read all 500+.
- Large codebase scoping: For codebases >50K LOC, AskUserQuestion fires in Phase 1 to scope the audit. Do not attempt to read the entire codebase.
- Missing checklist: If the checklist file at
~/.claude/skills/codebase-audit/checklist.mdis unreadable, STOP with an error message. The audit cannot run without it. - Network failures: If dependency audit commands fail due to network issues, skip gracefully and note the skip in the report.
--suggest-fixeswith--quick: Ignore the--suggest-fixesflag and note in the report: "Fix suggestions are not available in quick mode — run a full audit with--suggest-fixesfor inline diffs." Quick mode targets a 2-minute time budget; diff generation adds latency per finding.- Ambiguous fixes: When multiple valid fix approaches exist (e.g., "add input validation" could mean regex, schema validation, or type checking), show the simplest approach and note: "Alternative approaches exist — see Recommendation."
--quick-fixwith--quick: Ignore the--quick-fixflag and note in the report: "Quick-fix is not available in quick mode — run a full audit with--quick-fixfor auto-applied fixes."--quick-fixwith zero eligible fixes: All fixes were skipped (review-suggested, multi-file, too large, or stale). Print summary explaining why each was skipped and direct the user to the fix plan.--quick-fixon a dirty working tree: Phase 5.1 checksgit status --porcelain. If uncommitted changes exist, warn the user and ask whether to proceed (fixes will be mixed with existing changes) or abort Phase 5.--quick-fixin a non-git repo: Skip the commit proposal (Phase 5.4). Apply fixes and print the summary only.--changed-onlyin a non-git repo: ERROR —--changed-onlyrequires git. Exit immediately with: "--changed-onlyrequires a git repository."--changed-onlywith no changes: "No files changed since{ref}. Nothing to audit." Exit gracefully with no report.--changed-onlywith invalid ref: Error with the ref name and stop: "Invalid git ref:{ref}"--changed-onlywith--quick: IGNORED. Note in report: "Changed-only mode is already fast —--quickflag was ignored."--changed-onlywith binary files in diff: Grep silently skips binary files. This is expected — document in Phase 1.9 but do not treat as an error.--changed-onlywith renamed files:git diff --name-onlyshows the new filename only, which is correct for auditing current state.--ciwith--suggest-fixes: IGNORED. Noted inmetadata.ignored_flagsin the JSON output.--ciwith--quick-fix: IGNORED. Noted inmetadata.ignored_flagsin the JSON output.--ciwith no findings: Status "pass", empty findings array,findings_count: 0.--cion first run (no baseline): Apply threshold normally, write baseline for future runs,regressionfield is null in JSON.--jsonwithout--ci: JSON output, no exit codes, no plan file written, AskUserQuestion suppressed. Status is always "pass".--min-severitydoes not affect health score: Score always counts all findings.summaryreflects full audit.findingsarray andfindings_countare filtered.summary.totalandfindings_countmay differ — this is intentional.--fail-on-newwithout baseline: Falls back to normal threshold behavior (compares against all findings, not just new ones).--baseline-onlywithout--cior--json: ERROR — requires structured output mode.--baseline-onlywith--fail-on-new: ERROR — mutually exclusive. Use--baseline-onlyfor the first run, then--fail-on-newfor subsequent runs.--baseline-onlywith--fail-on-regression: ERROR — mutually exclusive.--baseline-onlyalways passes, so regression gating contradicts its purpose.--baseline-onlyre-run: Overwrites the previous baseline. Idempotent and safe to re-run.--plan-fixeswith--ci/--json/--format sarif: Alias dispatch is suppressed. CI/JSON/SARIF modes emit structured output, not interactive plans. Audit runs normally.--plan-fixeswith--quick: Alias dispatch is suppressed. Quick mode has insufficient context for meaningful plan generation. Emit info line: "--plan-fixesignored in--quickmode (insufficient context for plan generation)."--plan-fixes --thorough:--thoroughis forwarded through the alias dispatch to/plan-fixes --thorough. Effect:/plan-fixesskips its Phase 4 depth consent and auto-investigates substantive findings.--plan-fixes --quick-fix: The alias dispatch ordering defers to AFTER Phase 5 applies fixes AND Phase 5.5 rewrites the baseline withquick_fix_status./plan-fixesthen reads the post-application baseline and annotates already-applied groups via its Option D coordination (templates:plan-applied.md,plan-mixed.md,plan-standard.md). See Phase 4.7 and Phase 5.5 for the flow.--plan-fixeswhen/plan-fixessibling skill is not installed: Existence check in Phase 4.7 fails. Emit the canonical "Audit completed successfully... /plan-fixes requires..." error with manual recovery command. Do NOT fall through to the flat plan output — the user explicitly asked for plan-fixes.--thoroughwithout--plan-fixes: No-op.--thoroughhas no meaning outside the/plan-fixesdispatch path. Documented here as a no-op edge case; do not error, just ignore.--format sarifwith--suggest-fixes/--quick-fix: IGNORED. SARIF does not carry inline diffs.--format sarifstandalone (no--ci): SARIF to stdout, no exit codes. Equivalent to--jsonbut in SARIF format.--format sarifwith--min-severity: Filters bothresultsandrulesarrays. Only rules referenced by included results are declared.--format sariffindings without file locations: Result omitslocationsarray. Valid SARIF, but GitHub renders these less usefully.--format sarifin non-git repo: File paths are relative topwdinstead of git root.--format <invalid>: Error: "Unsupported format:{value}. Supported formats:json,sarif." Stop immediately.- Infrastructure scanning with no infra files detected: Skipped entirely. No Infrastructure row in the summary table, no noise in the report.
- Infrastructure scanning with missing infra checklist file: WARN: "Infrastructure checklist not found — skipping infra patterns." Continue the audit without infra patterns.
--no-infrawith infra files present: Respected. Infrastructure scanning is skipped. Note in Tips block: "Infrastructure files detected but scanning was disabled via--no-infra."--quickwith infra files: Runs[QUICK]-tagged infra patterns (FROM latest, running as root, privileged containers) alongside existing quick patterns.
Key Rules
-
During audit phases (1-3), you MUST NOT modify any source code. Phase 4 writes the report/baseline to
$AUDIT_HOMEand the fix plan to the plan file. Phase 5 (--quick-fixonly) applies mechanical fixes to source code — this is the only phase that modifies project files. When the plan is executed (after "Ready to code?"), you may edit source code to implement the remaining fixes. -
Output discipline — the user sees every Bash/Grep/Read output. This is the single highest-leverage rule for audit UX. Violating it turns a focused investigation into an unreadable scroll. Three hard sub-rules:
2a. NEVER use Grep in
contentmode during checklist execution. Always usefiles_with_matchesmode. If the Grep result says"Found N lines"(vs"Found N files"), you're in content mode and you just dumped file:line:text for every match. Switch tofiles_with_matchesto get filenames only, then Read specific line ranges. Multiline regex patterns (patterns matching across{}boundaries) are especially dangerous and must NEVER be run in content mode — they return enormous context blocks.2b. NEVER dump raw command output when a count or summary suffices. Common traps:
find src -name '*.py'→ pipe through| wc -lfor a count, then use Glob to target specific paths when needed.pip-audit --format json→ pipe throughjqto extract only vulnerabilities (see Phase 1.7 for the exact forms for each package manager). Never| head -Na raw audit JSON — the first N lines are almost always clean deps and the vulns are buried lower.git log --format=%B→ cap with-10or-20. Don't dump full history.ls -la <large dir>→ pipe through| head -20or| wc -lfor a count.
2c. When reading files, read targeted line ranges, not whole files. Pick 20–50 lines around the finding, not the whole function body unless the function is short. If you need more context after the first Read, do a second targeted Read — that's cheaper than one giant Read that dumps hundreds of lines. Unread code also isn't useful to the user — they can look at the file themselves.
2d. Every probe command MUST exit 0 — even when the target doesn't exist. This matters because Claude Code batches Bash calls in parallel, and a single non-zero exit in the batch cascade-cancels its sibling tools. The user sees red "Cancelled" messages for work that had nothing wrong with it. The trap is
ls—ls /nonexistent 2>/dev/nullstill exits 1 even though stderr is suppressed. Common fixes:- Probing sentinel files (does
Dockerfile/pyproject.toml/.github/workflowsexist?): uselib/probe-exists.sh, NEVERlswith a wall of paths:The script is a load-bearing shared contract (see preamble). It always exits 0 and prints only files that actually exist, one per line. Clean output, nobash "$LIB_DIR/probe-exists.sh" pyproject.toml Makefile Dockerfile .github/workflows|| truegymnastics, no cascade-cancel risk.$LIB_DIRis the skill's install directory, defined in the preamble as${CODEBASE_AUDIT_LIB_DIR:-$HOME/.claude/skills/codebase-audit/lib}. Do NOT reference the script via$REPO_ROOT/lib/—$REPO_ROOTis the audit target, which almost never contains alib/probe-exists.sh. If you need to probe files and find yourself writing anls, stop and use the script instead.lsis for listing directories you intend to read in full, not for probing existence. - If you really need
lsfor something other than probing (e.g., listing the contents of a directory you know exists), append|| true:ls -la foo.txt 2>/dev/null || true. findalready exits 0 when nothing matches — usefindin preference tolswhen enumerating files by pattern.grepexits 1 when nothing matches — append|| truewhen using grep in a pipeline whose failure would cascade.gitcommands against missing refs/remotes/files — append|| trueor wrap inif git rev-parse ... >/dev/null 2>&1; then ....
Rule of thumb: if a single tool call would emit more than ~30 lines of output, restructure the call (count, filter, grep-to-files, jq-extract) unless those lines are genuinely all load-bearing signal.
-
Findings that reference specific code MUST include
file:line. Findings about missing functionality (missing tests, missing error handling), dependency vulnerabilities, or architectural patterns should reference the most relevant file or component instead. Never report a finding you cannot anchor to something concrete in the codebase. -
Reports are saved to your home directory (
$AUDIT_HOME), not the project directory. They may contain security findings — do not commit them to public repos. -
No hallucinating findings. Every finding must reference a specific file and line (or component for non-code findings). If you can't point to it, don't report it.
-
Use the severity calibration definitions exactly as specified. Do not inflate or deflate severity.
-
In quick mode, respect the 2-minute target. Do not run Phase 2 or the full Phase 3 checklist.
--changed-onlyalso skips Phase 2 and does not write a baseline. -
AskUserQuestion fires in three places in
/codebase-audit: (1) Phase 1 if >50K LOC, to scope the audit; (2) Phase 4.7 after the flat plan is written (when--plan-fixesis NOT active), to offer the next step; (3) Phase 5 (--quick-fixonly) for dirty working tree check and commit proposal. In--cior--jsonmode, AskUserQuestion is NEVER called — all decisions are made automatically. Do not use AskUserQuestion elsewhere during the audit. The depth consent and plan menu AskUserQuestion calls that existed in v1.8.0's Phase 4.7.3/4.7.6 now live in the/plan-fixessibling skill, not here. -
All bash blocks are self-contained. Do not rely on shell variables persisting between code blocks.
-
When reading files for context, read enough surrounding lines to understand the code — do not make judgments from a single line in isolation. (This is not in tension with Rule 2c — "enough" is 20-50 lines around a finding, not 200.)
-
Cap detailed findings at 50. Summarize overflow in a table.
-
Be aware of your knowledge cutoff. Do not flag dependency versions, language versions, or API usage as "deprecated" or "nonexistent" based solely on your training data. If uncertain whether a version exists, state the uncertainty rather than asserting it as a finding.
-
Always use the Read tool to read files — never use
catvia Bash. The Read tool provides better context and is the expected convention. -
The audit is planning-for-a-plan. Phases 1-3 are read-only research. Phase 4 produces two outputs: the archival report (written to
$AUDIT_HOMEvia Bash) and the fix plan (written to the plan file). The plan file is the correct, actionable output — "Ready to code?" means "execute this fix plan." When--quick-fixis active, findings auto-applied by Phase 5 are marked[AUTO-APPLYING]in the plan — only substantive and review-suggested items remain actionable. -
When
--suggest-fixesis active, generate diffs during Phase 3 immediately after confirming each finding — not batched at the end of Phase 3, not retroactively in Phase 4. The code context from the Read that confirmed the finding is essential for accurate diffs. Each diff must be a minimal, conservative change. Never suggest refactoring beyond the specific finding. If a finding cannot be fixed with a short diff, mark it accordingly rather than forcing a bad suggestion. The unified diff format enables--quick-fix(Phase 5) to parse and apply diffs mechanically. -
--quick-fiximplies--suggest-fixes. If the user passes--quick-fixwithout--suggest-fixes, activate suggest-fixes behavior automatically. Quick-fix requires diffs to apply. -
When
--format sarifis active, rule IDs must be deterministic ({category}/{kebab-title}) and declared intool.driver.rules[]before being referenced inresults[]. File paths must be relative to the git root with forward slashes and no leading./. Use%SRCROOT%as theuriBaseId.message.textcontains the title only;rule.fullDescription.textcontains the recommendation.
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.