Catalog PyPI
PACKAGE EXPLORERView in registry

ai-code-guardrails

PyPI

Deterministic guardrails for AI-written code: diff budget, scope discipline, test integrity, dependency policy — proven against an executable red-team corpus.

aicicode-reviewdiffguardrailsllmpull-requestsupply-chain
Versions collected1
Direct dependencies1From release 0.2.0
Last observed21 Oct, 10:13 UTC

README

Version 0.2.0 documentation

ai-code-guardrails

Deterministic guardrails for AI-written code. Catches 36 of 36 known failure modes of AI-generated changes, at 0% false positives on the control corpus — and ships the corpus so you can verify that claim yourself.

The problem

Code written by AI agents fails in patterned, adversarial-looking ways that human review is bad at catching: the failing test quietly deleted or skipped, the diff that balloons past reviewability, the "drive-by" edit outside the task, the hallucinated or squatted dependency, and — once you add automated checks — the agent editing the checks themselves, or granting itself the waiver.

Linters and CVE scanners don't look for any of that. These guards do, mechanically, on every pull request.

What it checks

Every guard is a pure function of the git diff (base...head), runnable in any CI and locally. No runtime dependencies — stdlib Python only.

GuardCatches
diff budgetPRs past reviewable size (default: warn at 400 changed lines, fail at 800), including evasion via fake "generated"/"vendored" file names — claimed exemptions are counted and capped
scope disciplinefiles the declared scope never authorised — works from a SpecForge task spec or a plain .guardrails/scope.yml allow-list; either declaration is read from the base ref, so a PR can't widen its own permission slip
test integritydeleted test files, added skip/focus markers, net assertion loss — and tests that survive but prove nothing: a snapshot re-recorded with no code change, tautological assertions (assert True, asserting the mock's own return value), a test mocking the very unit it is named after
suppressionsilencing the type checker or linter instead of fixing the code — # type: ignore, @ts-ignore, eslint-disable, # noqa above a threshold; switching a rule off in the linter's config fails on the first occurrence
dependency policydependencies absent from the project's declaration file (with machine-recognisable matching — a package named is can't hide in prose), plus the routes that skip the manifest entirely: a lockfile authorising an undeclared direct dependency, a new git submodule, an added install-time script; optional registry-existence check against slopsquatting
self-modificationany PR touching the guards' own code or config without a human-applied label

Waivers are structurally human-only. A guard is overridden by adding a PR label — which requires triage permission on the repository. Waiver tokens written into commit messages or the PR body (both of which an agent authors) never change a verdict; they are reported as attempted self-waivers.

Verify the claim

The red-team corpus is executable: 36 attacks that must be caught and 23 legitimate controls that must stay quiet, each a throwaway git repository whose diff is the attack. It runs as this package's test suite:

git clone https://github.com/Pandaeb/ai-code-guardrails
cd ai-code-guardrails
pip install -e ".[dev]"
pytest

Controls are not optional decoration: a detection rate is only meaningful next to a false-positive rate. A guard that flags everything scores 100% on attacks and gets uninstalled within a week.

Install

pipx install ai-code-guardrails

or, without pipx:

pip install ai-code-guardrails

or, for hacking on it:

git clone https://github.com/Pandaeb/ai-code-guardrails
cd ai-code-guardrails
pip install -e ".[dev]"

Python 3.9+, no runtime dependencies, works anywhere git does.

Run it on your repository

From inside any git repository with an open change:

ai-code-guardrails check --base origin/main

That compares origin/main...HEAD and prints one verdict per guard plus a summary; exit code 1 means at least one guard failed. On a repository with no configuration at all you get sensible behaviour: diff budget (400/800) and test integrity are live, dependency policy warns with setup instructions until a declaration file exists (then undeclared additions fail), the scope guard skips until you declare a scope (see below), and self-modification watches .guardrails.json.

Wire it into CI

The published action is the whole job. It installs the package from the action's own pinned ref — never from the repository under review — and wires the waiver surfaces itself:

permissions:
  contents: read

on:
  pull_request:
    # labeled/unlabeled matter: adding a waiver label must re-run the guards
    types: [opened, synchronize, reopened, labeled, unlabeled]

jobs:
  guards:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0 # the guards diff against the base branch
      - uses: Pandaeb/ai-code-guardrails@v0.2.0

Inputs, all optional: base (default: the PR's target branch), head, feature, config, registry-check, format. Pin a tag — the action is the root of trust for the whole setup.

Without the action, the same job by hand:

      - uses: actions/setup-python@v6
        with:
          python-version: "3.13"
      - run: pip install ai-code-guardrails==0.2.0
      - run: ai-code-guardrails check --base "origin/${GITHUB_BASE_REF}" --head HEAD
        env:
          GUARDRAILS_ACKS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
          GUARDRAILS_PR_BODY: ${{ github.event.pull_request.body }}

Then create the waiver labels (oversize, scope, test-removal, test-weakening, suppression, new-dependency, guardrail-change) and make the check required in branch protection. The guards install from outside the repository under review — pin the action to a released tag — so a PR cannot rewrite the checks that judge it.

Machine-readable output

--format json prints a stable, versioned report (schema_version: 1): package version, one entry per guard (status, detail, structured waiver records — including ignored self-waiver attempts), summary counts, and the exit code.

--format sarif prints SARIF 2.1.0 with one rule per guard and, where the guard knows the offending file, a location per finding. Uploaded to GitHub code scanning it turns a red check into an annotation on the exact file — the undeclared dependency's manifest, the deleted test, the file that blew the budget:

jobs:
  guards:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      # Job-level exception to the contents:read default: the SARIF
      # upload writes to the repository's code-scanning API.
      security-events: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: Pandaeb/ai-code-guardrails@v0.2.0
        with:
          format: sarif # also written to guardrails.sarif
      - uses: github/codeql-action/upload-sarif@v3
        # The check step exits 1 when a guard fails - upload the
        # annotations exactly then, and keep the job red.
        if: always()
        with:
          sarif_file: guardrails.sarif

Configure

Optional .guardrails.json at the repository root — read from the base ref, so a PR editing it is judged by the old rules (and trips the self-modification guard):

{
  "diff_size": { "soft_limit": 400, "hard_limit": 800 },
  "selfmod": {
    "protected_paths": [".guardrails.json", ".github/workflows/**"]
  },
  "deps": { "declaration_files": ["docs/dependencies.md"] },
  "test_integrity": { "assert_loss_fail_threshold": 3 }
}

Every key overrides the built-in defaults per section; this repository's own .guardrails.json is a working example.

Scope without specs

Teams that will never write a task spec declare scope in .guardrails/scope.yml — one allow: key, a list of globs (a deliberately minimal YAML subset; the package stays stdlib-only):

# What the current change stream is allowed to touch.
allow:
  - src/payments/**
  - tests/**

Land it on the main branch first: like every declaration it is read from the base ref, so editing it inside a PR never widens that same PR's permissions — the edit is reported and takes effect only after it is reviewed and merged. A PR that introduces the file for the first time is judged by it but flagged as carrying an unreviewed declaration. The PR description is never a scope provider: the agent writes it.

Dependency declarations

New dependencies must be named — backticked, quoted, or as a list entry — in one of the declaration files: DEPENDENCIES.md, docs/dependencies.md, a SpecForge tech.md/architecture.md, or whatever deps.declaration_files points at. No declaration file at all: the guard warns with instructions instead of failing. An explicitly configured policy under which nothing could ever be declared (empty list, missing files) fails — a promised policy that cannot pass is a broken promise, not a free pass.

License

FSL-1.1-MIT (Functional Source License): the source is open, you can use it freely — including commercially, in your company's CI — but not to build a competing product. Each version automatically becomes plain MIT two years after its release. Deliberately source-available, not OSI open source.

Vulnerabilities — including guard bypasses — are security reports: see SECURITY.md. Contributions: see CONTRIBUTING.md.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.2.06 Aug 20261

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
pandaeb/ai-code-guardrails

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 10:13 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:ad84f36eebae6e4376936af7f9f787402101c6195db05c4b5ede307613dc4c43