Codebase audit

Checks a whole repository: CI gates, dependencies, setup, and conventions. Use it for this input: repo. Report each check as PASS, FAIL, or N/A with file:line evidence. A FAIL carries the card's severity. Any block FAIL means request changes. A should-fix or discuss FAIL goes in the review but does not block.

FAIL [block] chg-expand-contract - src/a.py:42 - renames the column and drops the old one in one migration
FAIL [should-fix] chg-one-concern - src/b.py:7 - a logging rewrite rides along with the bug fix
PASS chg-breaking-change-taxonomy - src/c.py:15 - no signature or return shape changes
N/A chg-perf-claim-evidence - the description makes no performance claim

Checklist

What the badges mean

Filters

Concerns
Topic tags
Topics

Cards

code-adr

discussstablemaintainabilityprocess

Applies when: Auditing whether the reasons behind significant design choices are recorded.

Rule. Keep a short written record for each significant design choice (datastore, framework, deviation from a pattern) that states the context, the decision, and its consequences.

Why. Without a record, the next reader reverses a decision blindly or cargo-cults it. Also list the options you ruled out and why: a record that weighs only the chosen design, its strengths or its flaws, is not a trade-off analysis.

FAIL
Repo uses Kafka for one 10-msg/day job. No record why; nobody dares remove it.
PASS
docs/adr/0007-queue.md: context, decision, consequences, status, and the 2 options ruled out.

Counterpoint. ADRs rot and become paperwork. Keep each to a page, write them only for hard-to-reverse choices, and mark superseded ones.

Sources: docs.aws.amazon.com, learn.microsoft.com, cloud.google.com, www.gov.uk, engineering.atspotify.com, www.cognitect.com, github.com

code-build-hermetic

should-fixstableprocess

Applies when: The repository has a build, a packaging, or a release step.

Rule. Build from declared inputs only, so that the same source revision gives the same artifact.

Why. A build that reads whatever is installed on the machine gives different output on different machines, so a bug cannot be reproduced and a release cannot be checked against its source. Tools are inputs too: pin the compiler and toolchain versions, and do not call host binaries. Undeclared shared files also break builds at scale, for example two tasks that clean and use the same /tmp directory race each other.

FAIL
steps:
  - run: make               # uses whatever gcc, node and protoc the runner has
  - run: date > BUILD_ID    # the clock is an undeclared input
PASS
container:
  image: ghcr.io/acme/builder@sha256:4b1e...   # toolchain pinned by digest
steps:
  - run: SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) make   # timestamps come from the commit

Counterpoint. "Bit-identical builds are overkill." Full reproducibility is the ceiling. Declared inputs, pinned tools, and a recorded revision are the floor, and they cost little. code-dependency-pinning covers dependency pins.

Tool gate: Build twice in clean containers and compare checksums; diffoscope shows where two artifacts differ.

Sources: bazel.build, reproducible-builds.org, airbnb.tech, slack.engineering, dropbox.tech

code-ci-gates

should-fixstableprocess

Applies when: Auditing which checks must pass before a change merges.

Rule. Require review and passing build, tests, lint, and type checks on the default branch before merge, and leave nothing mechanical to reviewer vigilance.

Why. Any rule that depends on someone remembering will be skipped. Components where changes merge unreviewed or without discussion tend to ship more post-release defects.

FAIL
Default branch: no protection. Force-push allowed. CI optional.
PASS
Protected main: 1 approval + required checks (build, test, lint, types).

Counterpoint. Gates slow small teams, and flaky tests train people to override. Keep gates few and reliable, fix flakes instead of overriding them, and let the gates own the mechanical checks so review time goes to design.

Tool gate: Branch protection with required status checks.

Sources: github.com, blog.palantir.com, www.kubernetes.dev, rebels.cs.uwaterloo.ca

code-consistency-drift

discussstablecorrectnessmaintainability

Applies when: Auditing whether one concern is solved in several incompatible ways across the repo.

Rule. For each recurring concern (errors, config, HTTP clients, logging), make one pattern the standard instead of leaving several as equal peers.

Why. Each extra variant is another thing to learn, test, and secure, and drift accumulates through changes that each looked locally fine. Two limiters built two ways enforce the same quota differently. List the older variants and count them, so the migration shows progress and a new one shows up in review.

FAIL
Three HTTP clients (requests, urllib3, httpx) with three timeout conventions;
no document says which is current.
PASS
docs: "use httpx via net/client.py". Lint bans direct requests imports;
legacy sites listed in MIGRATION.md.

Counterpoint. "Forcing one way can block a better tool." Allow change through a proposal that updates the standard and the migration list, not through a quiet second pattern. Consistency breaks ties. It does not outrank a measured reason to switch.

Tool gate: Lint rules that ban the deprecated pattern once a standard is chosen.

Sources: blog.pragmaticengineer.com, google.github.io, dev.to, engineering.atspotify.com, www.uber.com, tech.cabify.com, stripe.dev

code-dead-code

should-fixstablemaintainability

Applies when: Auditing for code, flags and files that nothing uses.

Rule. Remove unreferenced functions, commented-out code, expired flags, and orphaned files; history keeps them.

Why. Dead code costs reading time, misleads searches, and gets "fixed" and tested for no benefit.

FAIL
# def old_export(): ...   (commented out since 2022)
if FLAGS["legacy_ui"]:     # flag permanently false since release 4
PASS
Code deleted; git log -S shows where it went. Detector reports 0 unused symbols
or each is allowlisted with a reason.

Counterpoint. Code may be called by reflection, plugins, or external users. Check with the detector's config and a grep of consumers. For public API, deprecate first (see chg-expand-contract).

Tool gate: Dead-code detectors (vulture, knip, deadcode) plus unused-export lint in CI.

Sources: henrikwarne.com, programmingisterrible.com, blog.palantir.com

code-dependency-licence-sbom

should-fixstablesecurity

Applies when: Auditing what third-party code the repo ships and under which terms.

Rule. Generate an SBOM, and scan dependencies for known vulnerabilities on every change or on a schedule.

Why. Without an inventory, a team cannot answer a disclosed vulnerability quickly. CI is the usual place for both steps: the SBOM then matches what was built, and the scan runs without anyone remembering it. Team policy: also fail the build on a dependency whose licence is not on the approved list.

FAIL
No SBOM anywhere, no scanner in CI, and dependency alerts turned off.
PASS
CI: generate SBOM, run vulnerability scanner, fail on high severity;
release artifacts attach the SBOM.

Counterpoint. SBOMs and scanners create noise that nobody triages. Tune severity thresholds and assign an owner, since an ignored report is worse than none. The licence list is team policy, not a universal rule. Drop it if your team has no licence constraints.

Tool gate: A vulnerability scanner such as OSV-Scanner or Dependabot alerts; an SBOM generator such as Syft.

Sources: owasp.org, github.com, www.cisa.gov, www.nist.gov, docs.github.com, tech.cabify.com

code-dependency-pinning

blockstablesecurity

Applies when: Auditing how the repo resolves third-party packages.

Rule. Commit a lockfile with hashes and install from it in CI.

Why. Unpinned versions make builds non-reproducible and let a compromised release flow straight into production. Updates should arrive as reviewed, staged changes, not silently. code-workflow-supply-chain covers CI action pins.

FAIL
requirements.txt:  requests>=2   (no lockfile; CI runs pip install -U)
PASS
requirements.lock with hashes; CI: pip install --require-hashes -r requirements.lock
Update bot opens PRs; changes are rolled out in stages.

Counterpoint. Pinning delays security patches. A pin plus an update bot with a defined cadence beats floating versions, because the lock makes the update visible and revertable.

Tool gate: Lockfile-required install in CI; dependency-update bot config present.

Sources: owasp.org, github.com, sre.google, pip.pypa.io, docs.npmjs.com

code-perf-budget-in-ci

should-fixstableperformanceprocess

Applies when: The repo has a performance target (bundle size, page metrics, latency under load, or a benchmark) that matters to users.

Rule. Write the performance budget down as a CI check that fails the build when it is exceeded.

Why. Performance regresses a few percent at a time, and no reviewer sees a 3% slowdown in a diff. A budget that only warns becomes a log line nobody reads.

FAIL
lighthouserc.json: "assertions": { "categories:performance": ["warn", { "minScore": 0.9 }] }
k6 script with no thresholds block
PASS
size-limit: [{ "path": "dist/app.js", "limit": "180 kB" }]   # npm run size in CI
k6: thresholds: { http_req_duration: ["p(95)<300"] }        # non-zero exit fails the job

Counterpoint. "Benchmarks in CI are noisy and flake." They are when a gate trusts one run. Gate on a stable budget, compare repeated runs (benchstat wants 10 to 20), and keep the threshold above the noise. On a site with much room to improve, a tight budget only turns every build red, so set it at today's level and lower it later.

Tool gate: Lighthouse CI assert, size-limit, k6 thresholds, Gatling assertions, or a benchstat comparison in CI.

Sources: github.com, github.com, grafana.com, docs.gatling.io, learn.microsoft.com, docs.aws.amazon.com, firefox-source-docs.mozilla.org, pkg.go.dev

code-rule-has-enforcer

should-fixstablemaintainability

Applies when: Auditing a repo's instruction files, review history or comments that state a rule.

Rule. Back each rule that a type, lint or CI check could enforce with that check, not with a line in an instruction file or a repeated review comment.

Why. A rule that relies on memory gets broken, and reviewers then repeat the same correction. An instruction file is advisory: a person or an agent can skip it. A lint message that names the fix teaches it at the moment of the mistake. A "do not remove" code comment has the same weakness as an instruction file line.

FAIL
CONTRIBUTING.md: "never call requests.get without a timeout"   (no lint rule)
Review history: the same comment on 6 PRs
PASS
# pyproject.toml, checked by ruff in CI
[tool.ruff.lint]
extend-select = ["S113"]   # request-without-timeout

Counterpoint. "Not every rule is checkable." Right, and judgement rules stay as prose. The FAIL is a checkable rule with no check. See code-ci-gates for where checks run, and chg-review-signal for what reviewers stop commenting on once a tool owns it.

Tool gate: The enforcer itself: a type, a lint rule or a CI check.

Sources: github.com, github.com, github.com, github.com, engineering.atspotify.com, engineering.atspotify.com, stripe.dev, stripe.dev, tech.cabify.com

code-secret-scan

blockstablesecurityprocess

Applies when: Any repository, including its history, issues and pull requests.

Rule. Scan for committed credentials, and treat any hit as leaked: revoke and replace it first.

Why. A secret in history is readable by everyone with clone access, and forks and caches keep it after a rewrite. Rewriting history is slow and often unnecessary once the secret is dead.

FAIL
git log -p | rg 'AKIA'      # a live cloud key in an old commit; "removed it in the next commit"
PASS
scanner runs on every push and covers history
key revoked and reissued the same day; new value comes from the secret store

Counterpoint. "Scanners give false positives." Tune with an allowlist of reviewed, non-secret strings. A missed live key costs far more than a triaged alert. sys-secrets-and-errors covers where secrets live at runtime.

Tool gate: Secret scanning on push (provider or pre-commit hook) with push protection on.

Sources: docs.github.com, cheatsheetseries.owasp.org, www.cisa.gov, docs.gitlab.com

code-workflow-supply-chain

blockstablesecurityprocess

Applies when: Auditing CI workflow files and release pipelines.

Rule. Pin every CI action to a full commit SHA.

Why. The pipeline holds secrets and publish rights, so a compromised action or injected input owns the artifact. Also give workflows least-privilege tokens, keep untrusted event data out of run: and out of pull_request_target checkouts, and attach build provenance to releases. code-dependency-pinning covers dependency pins, and code-dependency-licence-sbom covers the inventory.

FAIL
steps:
  - uses: actions/checkout@v4        # tag, can be moved to other code
  - uses: some/action@main           # branch, changes on every push
PASS
permissions:
  contents: read
steps:
  - uses: actions/checkout@<40-char-sha>   # v4.2.2
  - uses: some/action@<40-char-sha>        # v3.1.0

Counterpoint. "SHA pinning blocks automatic fixes and adds update churn." Let an update bot propose SHA bumps, so fixes still arrive as reviewed changes. Where the platform offers a lockfile for workflow dependencies, that lockfile is stronger than SHA pins and meets this check.

Tool gate: OpenSSF Scorecard Pinned-Dependencies check; zizmor unpinned-uses audit.

Sources: github.com, slsa.dev, owasp.org, docs.github.com, www.stepsecurity.io

Draft cards

6 draft cards

A draft card is not vetted. Read it for ideas, but do not use it for a verdict.

code-a11y-check-in-ci

should-fixdraftaccessibilitytesting

Applies when: The repo builds a web UI and has an automated test suite.

Rule. Run an automated accessibility check, such as axe on rendered pages, in the automated tests, and fail the test on a violation.

Why. A violation that nobody sees at review ships. A failing test catches the missing label or alt on the pull request that adds it, and run in CI on every change it blocks the merge. A JSX lint such as jsx-a11y catches some of the same problems earlier, in the editor. Either way it is a floor: tools find only part of the problems, so a person still tests with a keyboard.

FAIL
// e2e: axe runs, but nothing fails on what it finds
const results = await new AxeBuilder({ page }).analyze();
console.log(results.violations.length);
PASS
// e2e: fail on any axe violation
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);

Counterpoint. "Tools only catch a minority, and we have a backlog, so a failing check would turn every build red." The check catches the cheap failures at no review cost, which frees the manual pass for what only a person can judge. For the backlog, exclude the known violations by name (disableRules, exclude, or a fingerprint of each known issue) and fail on anything new. Remove each exclusion once it is fixed.

Tool gate: This card is the gate: axe (through @axe-core/playwright or similar) against rendered pages in the test step; eslint-plugin-jsx-a11y in the lint step for JSX code.

Sources: github.com, www.deque.com, alphagov.github.io, www.gov.uk, designsystem.digital.gov, www.gov.uk, playwright.dev, github.com

code-ai-pr-disclosure

should-fixdraftprocess

Applies when: A pull request was written in whole or part by an AI tool.

Rule. Disclose AI assistance in the PR, and have a human author who can explain and defend every changed line without the tool.

Why. Reviewers calibrate by who wrote the code. An undisclosed generated change gets the wrong scrutiny, and an author who cannot explain it leaves the reviewer to do the author's job.

FAIL
PR description: "Add retry logic."
(500 generated lines; asked why a 3rd retry, the author says "the model chose it")
PASS
PR description: "Add retry logic. Drafted with <tool>; I read and tested every line.
 Tests: test_retry.py. Not covered: jitter."
Trailer: Assisted-by: <tool>

Counterpoint. "Disclosure stigmatises tool use." The rule is about scrutiny, not blame: disclosed work is reviewed the same way and merged on the same terms. The agent never signs off or certifies origin; the human does.

Tool gate: PR template checkbox for AI assistance; commit trailer check.

Sources: docs.kernel.org, docs.kernel.org, github.com

code-documented-setup-runs

should-fixdraftmaintainabilityprocess

Applies when: Auditing a repo's README or contribution guide.

Rule. Make every setup, build and run command in the README or contribution guide succeed on a clean checkout.

Why. A stale step costs each new contributor and each agent the same time, and they cannot tell a doc error from their own mistake.

FAIL
README: `make dev`    ->  make: *** No rule to make target 'dev'
PASS
CI job: fresh clone, run the README's commands in order, fail on any non-zero exit

Counterpoint. "Setup depends on the machine." Document the prerequisites, and run the commands in a clean container to prove them. Judge a miss by the damage it causes, not by its existence.

Tool gate: CI job that runs the documented setup on a clean checkout.

Sources: github.com

code-file-lines-ceiling

discussdraftmaintainabilityprocess

Applies when: Auditing a repo for files that have grown past comprehension.

Rule. Keep every hand-written source file under about 1,000 lines unless it has a listed exception, and split larger files by knowledge owned, not by execution order.

Why. Huge files hide unrelated decisions, attract merge conflicts, and cannot be understood in one read.

FAIL
$ wc -l src/*.py | sort -n | tail -2
  4,812 src/utils.py
  3,105 src/app.py
PASS
No hand-written file over 1,000 lines; generated files listed in an allowlist.

Counterpoint. Ousterhout favours deep modules and a long file can be coherent. Splitting for its own sake adds shallow pieces. 1,000 lines is a trigger for a look, not a verdict. The fix is to separate decisions (his information-leakage and temporal-decomposition flags).

Tool gate: CI check on lines per file (cloc, wc) with an allowlist.

Sources: dev.to, programmingisterrible.com

code-flaky-test-quarantine

should-fixdrafttestingprocess

Applies when: Auditing how the codebase handles tests that pass and fail on the same code.

Rule. Report every test that passes only on retry, move it to a quarantine with a named owner and a ticket, and return it to the merge gate only after it passes many reruns.

Why. A test that fails at random on unchanged code teaches people to rerun until green, and then a real regression looks like noise. Dropbox removes tests that fail too often from the main pool and files a bug against the owner. Its fix workflow then runs the test 100 or more times in CI before the fix lands. Shopify retries a failed test but tells developers about every pass after a retry. The usual causes are a fixed pause where the test should wait for an element (Shopify) and state left behind by an earlier test (Cabify, Dropbox).

FAIL
# .github/workflows/test.yml
- run: pytest --reruns 3          # a pass on retry counts as a pass, and nobody hears about it
PASS
# .github/workflows/test.yml
- run: pytest --reruns 3 -m "not quarantine" --junitxml=report.xml
- run: python tools/flaky_report.py report.xml   # opens a ticket per pass after retry, owner from CODEOWNERS
# nightly.yml: a quarantined test rejoins the gate after 100 clean runs
- run: pytest -m quarantine --count 100

Counterpoint. "Retries keep the build green, and quarantine hides coverage." Shopify's data backs retries: a slightly slower build beats an engineer rerunning it by hand. Retries are fine when each pass after a retry reaches someone. A quarantined test still runs outside the gate, and its owner and ticket stop it from staying there for good. Skipping or deleting a flaky test in a diff is a separate FAIL. See chg-no-silenced-diagnostics. For the merge gate itself, see code-ci-gates.

Sources: dropbox.tech, dropbox.tech, shopify.engineering, shopify.engineering, tech.cabify.com

code-scoped-validation-loop

discussdrafttestingprocess

Applies when: Auditing how a contributor checks a small change.

Rule. Document a command that checks one package or path, with readable output, so a small change does not need the full-repo run.

Why. If the only check takes twenty minutes or prints thousands of lines, people skip it and push to CI, and agents cannot tell which failure is theirs.

FAIL
CONTRIBUTING.md: "run `make test` (all 14,000 tests, ~25 min)"
PASS
pytest tests/billing -q        # about 20 s
ruff check src/billing

Counterpoint. "CI runs everything anyway." CI is the last check, not the loop. A scoped command lets the author find a failure in seconds instead of after a push.

Tool gate: A documented command that runs tests and lint for one path or package.

Sources: github.com