Agent Code Guard

Usage

Installation

Use Python 3.10+ and an installer with patched certificate trust: pip >=26.2.2 or uv >=0.13.4. Keep the operating system’s CA store updated as well. The Python security advisory explains why updating only system certificates does not repair upstream pip’s vendored certifi bundle.

Virtual environment

Create and activate a virtual environment. On Linux/macOS:

python3 -m venv .venv
source .venv/bin/activate

On Windows PowerShell:

py -m venv .venv
.venv\Scripts\Activate.ps1

Each new environment can seed an older pip through ensurepip. Before any dependency downloads, run this command with the activated environment’s Python:

python -m pip --isolated --disable-pip-version-check install --no-index --no-deps --no-cache-dir --require-hashes "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
python -m pip --version
python -m pip install agent-code-guard

The advisory supplies this exact wheel and SHA-256. Even when the seed pip’s TLS roots are stale, the locally specified hash authenticates the bootstrap bytes before installation. The command disables index lookup, dependency resolution, cache reuse, and pip’s update check. Do not replace it with an unpinned pip install --upgrade pip or disable TLS verification. Subsequent dependency downloads use the patched pip. For Python.org macOS installations, also update the separate certifi package used by that installation, as the advisory directs.

pipx and uv

Install Agent Code Guard as an isolated CLI tool with pipx only after repairing the installer pipx actually uses:

pipx install agent-code-guard

For pipx’s pip backend, locate its shared environment using pipx environment and run the pinned bootstrap above with that environment’s Python before pipx install, pipx upgrade-shared, or any other download. On Linux/macOS its interpreter is normally <PIPX_SHARED_LIBS>/bin/python; on Windows it is <PIPX_SHARED_LIBS>\Scripts\python.exe. If the shared environment does not yet exist, use the virtual-environment route above rather than bootstrapping pipx through an older downloader. Updating a project’s or system’s pip does not update pipx’s shared pip. See how pipx works.

For pipx’s uv backend or a direct uv tool install, first verify uv --version reports >=0.13.4. Obtain a patched uv through an updated OS package manager or the pinned-pip environment above; an older uv’s bundled roots must not be used to fetch its own replacement. Then:

uv tool install agent-code-guard

Source installs

For source or repository development installs, bootstrap pip in the active virtual environment as above, then run one of the following from a checkout:

python -m pip install .
python -m pip install -e .

The checkout runner at skills/code-guard/scripts/code_guard.py exists for repository and skill compatibility; it is not the primary installed command.

Command and scope

The public command is code-guard. With no paths, it defaults to ., but normal development should select current work explicitly.

Installed version

Report the installed distribution identity without configuration, scope, Git, provider, skill, or guard work:

$ code-guard --version
agent-code-guard <version>

For machine-readable output:

$ code-guard --version --json
{
  "distribution": "agent-code-guard",
  "version": "<version>"
}

The value is read from installed metadata for the canonical agent-code-guard distribution. --version may be combined only with --json. Both successful version forms exit 0; incompatible arguments or unavailable metadata exit 3. Human errors use standard error and JSON errors use an error object on standard output. If distribution metadata is unavailable, the error is installed distribution metadata is unavailable for agent-code-guard.

Installation diagnostics

Inspect the active installation and its immediate local capabilities without running project analysis:

code-guard doctor
code-guard doctor --json

The human and JSON reports cover the active distribution, Python process, invoked entry point, bundled skill, current-directory configuration and Git context, and all supported parser providers. A healthy report exits 0; a completed unhealthy report exits 1; invocation or internal failures that prevent a report exit 3. Completed reports use standard output only.

doctor is reserved only as the exact first token. Analyze a file or directory with that name through a qualified spelling such as ./doctor, .\doctor, or an absolute path. Doctor is read-only: it does not analyze source, repair or install dependencies, export skills, modify configuration or Git state, access the network, or persist diagnostics.

Diagnostic output includes resolved launcher, interpreter, skill, Git, and configuration paths. Treat those paths and other environment details as potentially sensitive before sharing a report.

Git changed-only

code-guard . --changed-only

This selects staged, unstaged, and untracked files known to the current Git worktree, then intersects them with positional file or directory bounds.

Staged changes

code-guard . --staged

This selects only index changes as source candidates. Analysis still uses working-tree configuration and baselines, including unstaged policy edits.

Base comparison

code-guard . --base-ref origin/main --ci

This selects added, copied, modified, or renamed files from the merge-base comparison <ref>...HEAD. The ref must exist and resolve in the environment; fetch the intended base before running in shallow or isolated CI checkouts.

Only one of --changed-only, --staged, and --base-ref may be used at a time. Positional paths bound the Git-selected candidates; they do not add files outside that selection.

Policy relaxation in Git modes

Every Git comparison also runs the non-configurable policyRelaxation guard. It compares persistent effective policy after production defaults and normalization, using the same retained Git authority as source selection:

Mode Trusted policy Current policy
--changed-only Resolved HEAD Actual working tree
--staged Resolved HEAD Actual working tree, including unstaged policy
--base-ref REF Runner-selected merge-base object Actual working tree

Changed/staged repositories with no HEAD use built-in defaults and empty baselines as the trusted state. Base-ref still requires valid commit authority. Missing owned artifacts mean defaults/empty allowances. Invalid authority, malformed current/historical policy, unsupported baseline versions, and unsafe historical symlink/object/ancestor shapes are tool errors, never policy PASS.

The four compared artifacts are the actual active configuration and:

Automatic config discovery stays relative to the invocation directory; baselines stay at the Git root. Repository-owned explicit config aliases map to their canonical repository path. External configs and repository policy symlink traversal cannot establish trusted history and fail closed in Git modes. Plain explicit/audit analysis omits this inapplicable guard.

The guard reviews disabling any configured guard, increasing active REVIEW or LOC thresholds, changing LOC ratchetAt from review to fail, reducing counted blank/comment evidence, removing normalized LOC extensions, adding normalized exclusion/exemption declarations, and adding/increasing any of the three baseline allowances. Lowering/removing allowances, reason-only edits, and neutral default serialization/reordering stay quiet. Transient CLI choices are excluded from persistent policy comparison.

Exclusions use declaration sets, without symbolic glob reasoning: even a redundant added declaration may REVIEW. Ordered LOC override match sets retain last-match-wins semantics. Unchanged normalized topology permits threshold comparison; a changed topology produces one conservative overrideTopologyChanged reason. These findings describe what changed and do not claim that arbitrary effective path coverage was proven weaker.

REVIEW is one aggregate with totalReasons, sorted reasonCounts and artifactCounts, at most 20 reasons, and omittedReasons. Reasons sort by artifact, logical field, path/selector, and code. Each retains artifact, field, reasonCode, before, after, and explanatory message, with a path or exact callable selector where appropriate. Topology before/after summaries retain entry counts and SHA-256 fingerprints instead of unbounded match lists. Human, full/debug JSON, and compact JSON share this cap. REVIEW requires policyRelaxation guidance; PASS has no required policy. --ci retains visible REVIEW and exits 0; independent FAIL/INCOMPLETE dominates.

Policy comparison does not alter source counts. An empty selected-source scope can REVIEW a policy change, even when positional bounds, scope.exclude, or LOC exclusions hide every source or policy file from ordinary analysis. Legitimate authorized policy relaxation can be retained; do not reverse it solely to clear REVIEW or weaken another control to silence the finding.

Residual trust boundaries remain: transient CLI thresholds/counting/exclusions; choosing a base that omits prior weakening; changing/removing Code Guard or its CI invocation; and switching invocation/config outside the active compared artifact. This guard does not enforce CI integrity or infer authorization.

Explicit files and no VCS

Without Git, pass the exact files owned by the current change:

code-guard src/Foo.py src/Bar.ts docs/guide.md

Git selectors require a Git repository and fail rather than falling back to a recursive audit. Do not create a manifest merely to provide scope.

Deliberate recursive audit

code-guard .

A directory without a Git selector is a recursive audit. In a Git repository, directory discovery respects Git tracking and ignore rules. Outside Git, it walks the directory while pruning .git, node_modules, bin, and obj. Changed work and a full audit are intentionally different operations.

Recursive discovery does not follow symlinks. An explicitly supplied file symlink expresses caller intent and is inspected at its resolved target. An explicit directory symlink is rejected rather than traversed recursively.

Missing explicit paths, incompatible selectors, unavailable base refs, invalid scope, and Git selectors outside a repository are tool errors. Git-derived paths that no longer exist are ignored.

Legacy LOC adoption ratchet

Established repositories may adopt LOC policy without accepting further growth. The default guards.loc.ratchetAt: "fail" records files above effective failAt. Use "review" when existing files above effective warnAt may remain but must not grow. Create the canonical source-controlled ratchet over an intentionally bounded scope:

code-guard src/legacy --create-loc-baseline
git add .agent-tools/code-guard.loc-baseline.json
git diff --cached

The file is always <analysis-root>/.agent-tools/code-guard.loc-baseline.json, where the root is the enclosing Git top-level or, outside Git, the resolved invocation directory. Normal analysis reads it automatically and never writes it. Under fail, a file at its allowance, or reduced while still above failAt, becomes grandfathered REVIEW. Under review, a file within its allowance keeps ordinary REVIEW between warnAt and failAt, and is grandfathered only above failAt. Growth above either allowance is FAIL. At or below the selected policy threshold, the entry is reported as no longer needed.

After reducing or deleting legacy code, explicitly lower and prune entries:

code-guard src/legacy --update-loc-baseline

Update bounds come only from the positional paths. Update lowers existing allowances, removes entries that are missing, excluded, inapplicable, or no longer above the selected policy threshold, and leaves entries outside the bounds unchanged. It never adds a newly oversized path or raises an allowance; any attempted growth aborts the entire update. A rename is an old-path deletion plus a new, ungrandfathered destination—Git history is not consulted. Manual JSON edits are ordinary source-control changes and require review.

This ratchet differs from LOC exclusions, threshold overrides, and allowedLargeFiles: exclusions skip LOC evaluation, overrides change ordinary thresholds, and allowedLargeFiles is a reasoned static exemption without a size-regression check. A ratchet entry may not overlap allowedLargeFiles.

This workflow is only for adopting established legacy repositories. New projects, including Agent Code Guard itself, should meet policy directly and must not create a LOC ratchet baseline.

Do not switch between fail and review, raise thresholds, add exclusions or exemptions, or edit allowances merely to silence a growth failure. Those are source-controlled policy changes and require their own substantive justification.

Reviewed Markdown document ratchet

After reviewing an oversized, cohesive Markdown document, explicitly record its accepted physical-line count over a deliberately bounded scope:

code-guard docs/architecture.md --create-markdown-baseline
git add .agent-tools/code-guard.markdown-baseline.json
git diff --cached

This creates <analysis-root>/.agent-tools/code-guard.markdown-baseline.json. The analysis root is the enclosing Git top-level or, outside Git, the resolved invocation directory. Creation requires an enabled markdownDocumentSize guard and records only selected .md files strictly above its effective reviewAt. An existing baseline is never overwritten by create. Review and commit the baseline as the explicit acceptance decision; it is not part of ordinary setup.

Normal analysis reads the baseline automatically without writing it. With the default 800-line threshold and an accepted allowance of 845 document lines:

Current document Document-size result
Same path, 845 lines PASS, within allowance
Same path, 840 lines PASS, within allowance
Same path, 846 lines REVIEW, allowance exceeded
New or renamed path, 845 lines REVIEW, no allowance
Any document at or below 800 lines PASS, no allowance needed

This ratchet applies only to markdownDocumentSize. It does not change the threshold, exclude Markdown from analysis, or suppress markdownSectionSize. An accepted document can still have section REVIEW findings. Growth remains REVIEW (exit 1, or 0 with --ci), not FAIL.

After shortening or deleting documents, explicitly lower or prune entries:

code-guard docs --update-markdown-baseline

Update requires an existing baseline and an enabled document guard. It lowers existing allowances within positional bounds, removes deleted or scope-excluded paths and entries at or below the effective threshold, and preserves entries outside those bounds. It never adds an entry or increases an allowance. Growth in a retained entry aborts the entire update without changing the baseline. A rename is a deletion plus an unaccepted new path. Manual baseline changes require source-control review; do not rebaseline merely to hide growth.

Both write modes accept paths, --config, and repeated --scope-exclude. They reject other baseline modes, LOC counting/threshold options, Git selectors, JSON/CI analysis options, diagnostics, version reporting, and skill management. Writes do not run normal analysis. Successful writes exit 0; invalid inputs or unsafe baselines produce tool errors (exit 3, stderr, no completed report). Normal analysis and creation do not grant allowances through an explicitly supplied file symlink; updates reject symlink bounds. Baseline storage and entries must not traverse symlinks, and baseline-enabled scopes must stay inside the analysis root.

Human output shows the accepted allowance and within, exceeded, or no longer needed. Full/debug JSON retains document measurements and the unchanged thresholds.reviewAt; entries matched by path add baselineLines and ratchetStatus (within, exceeded, or notNeeded). Unmatched findings retain their original shape. Compact JSON omits accepted PASS findings like other passes. Accepted documents do not require the document-size policy; independent section findings still require their own policy.

See the persisted schema.

Accepted callable REVIEW ratchet

After a human authorizes accepting a current callable REVIEW, record exactly one finding in exactly one explicit physical file:

code-guard offenders_patterns.py \
  --accept-callable-review cyclomaticComplexity python offenders_patterns._auth \
  --reason "Reviewed cohesive authentication-format catalog; re-review on growth."
git add .agent-tools/code-guard.callable-review-baseline.json
git diff --cached

The selector is guard + embedded language + exact lexical identity, as shown in full JSON. Canonical guards are callableSize, nesting, and cyclomaticComplexity; the existing complexity result/policy ID is still complexity, which is rejected as a baseline/CLI alias. Each invocation targets one current REVIEW, requires a non-empty reason, and records its exact current measurement. Directories, multiple files, PASS/nonexistent targets, duplicate lexical identities, and existing allowances are rejected. A second explicitly reviewed callable can be added with another invocation; there is no bulk mode. Acceptance cannot replace or raise an existing ceiling, including after growth.

Normal analysis automatically reads the baseline without writing it. The threshold remains the general policy for unreviewed code; the accepted value is the reviewed ceiling for one callable/guard. For complexity threshold 15 and accepted value 17:

Current complexity Result
17 or 16 PASS, withinAllowance
18 REVIEW, grown
15 or less PASS, notNeeded; removable by maintenance
Unaccepted callable at 16 REVIEW under the unchanged threshold

Only a unique current physical path + language + lexical identity receives the allowance. Inserting unrelated lines above a named callable preserves the match. Renames/moves become unaccepted identities. Anonymous callbacks whose existing lexical names contain coordinates can become stale when moved and require re-review. Different embedded languages in Vue remain separate; two identical names in the same language fail closed. Remove an ambiguous entry through an explicitly reviewed source-control edit before accepting a unique current identity. No fuzzy matching or new parser behavior rescues old entries.

Explicit maintenance operates within positional file/directory bounds:

code-guard src --update-callable-review-baseline
code-guard src --update-callable-review-baseline --prune-stale-callable-reviews

Maintenance requires an existing baseline. It lowers a ceiling to the current measurement while still above the ordinary threshold, removes allowances no longer needed, and never adds/increases one. Growth aborts the entire proposal without partial writes. Stale entries remain until pruning is explicitly requested; directory bounds allow pruning entries for deleted files. Disabled guards and existing excluded/unselected/inapplicable files are preserved. Unchanged maintenance preserves file bytes and timestamps. Reasons survive lowering. Normal analysis reports stale identities only in completed analyzed files; maintenance additionally reports missing paths within its bounds.

Both commands require explicit positional scope and current filesystem state. They accept --config and repeated --scope-exclude, reject other write modes, LOC counting/threshold options, --changed-only, --staged, --base-ref, --ci, JSON modes, diagnostics, version reporting, and skill management. Known unavailable syntax/provider evidence blocks writes. Success exits 0; invalid/unsafe/ambiguous targets exit 3 on stderr without a completed report. Baseline storage and entries may not traverse symlinks or escape the analysis root. Write bounds reject symlinks; ordinary explicit-file symlink analysis cannot gain an allowance unless the physical file is also reached directly.

Full/debug matched findings add allowedMeasured, ratchetStatus (withinAllowance, grown, notNeeded), and reason, retaining ordinary thresholds.reviewAt, measurement, range, language, and guard-native details. Compact JSON omits accepted PASS findings. Human output stays quiet for those passes and shows current versus accepted measurement on growth. Stale entries add concise human diagnostics and callableReviewBaseline.diagnostics in all JSON modes. Growth routes the existing guard policy; accepted passes do not. Independent REVIEW/FAIL and INCOMPLETE evidence and exit codes remain intact.

An allowance records a human-reviewed outcome, not proof of ideal structure. Agents must not create or raise one merely to silence a finding. Do not split coherent callables mechanically, obscure control flow to reduce measured complexity/nesting, or change thresholds/exclusions to avoid re-review. Genuine reductions remain visible without automatic baseline mutation. Review and commit acceptance decisions. See the schema.

Results and exit codes

--ci changes a REVIEW-only result to exit 0. It does not suppress FAIL, INCOMPLETE, or tool errors. REVIEW is not automatic refactoring, and metrics must never be gamed.

Human and JSON output

Human output is the default and emphasizes actionable findings. Every completed analysis starts with the aggregate state and exact scope counts:

PASS: 3 selected; 2 analyzed; 1 inapplicable; 0 excluded.

The labels do not pluralize. An empty valid selection with no policy relaxation is PASS: 0 selected; 0 analyzed; 0 inapplicable; 0 excluded. and exits 0. Existing finding and required-policy lines follow this summary unchanged.

The counts have these meanings:

Therefore analyzed + inapplicable == selected, and an excluded file belongs to none of the other sets. All values are non-negative integers. Git-ignored files never discovered, paths outside positional bounds, and absent Git-derived files are not counted as exclusions.

Add --json for the stable, compatible full machine-readable output, including all passing and actionable findings. Completed PASS, REVIEW, and FAIL results include exactly one top-level summary alongside the existing overall, requiredPolicies, and guards values:

"scope": {
  "selected": 3,
  "analyzed": 2,
  "inapplicable": 1,
  "excluded": 0
}

Counts do not change aggregate state, findings, required policies, or exit codes. Tool errors retain their existing human or JSON error form and do not include a successful scope object.

When a known per-file syntax or provider failure occurs, the headline is INCOMPLETE, followed by ordered unavailable context and the incomplete syntax guard identifiers before ordinary findings. JSON uses overall: "incomplete", adds the authoritative completed aggregate as completedOverall, and includes ordered top-level unavailable records containing path, embedded language, kind, and the exact provider message. scope.unavailable overlaps analyzed, so analyzed + inapplicable == selected remains true. Every guard adds complete only on incomplete runs; incomplete syntax guards also add ordered unavailablePaths. Guard states, findings, and requiredPolicies continue to describe only completed evidence.

Choose a completed-analysis serialization mode explicitly when needed:

code-guard . --json --json-mode compact
code-guard . --json --json-mode debug

compact is intended for routine agent checks. It preserves overall, the complete scope, requiredPolicies, every guard and guard state, and existing guard and retained-finding ordering. It omits each finding whose normalized state is pass and retains unchanged findings whose state is review or fail. This includes omitting LOC exemptions: their native status is exempt, but their normalized state is pass. debug is an explicit name for the full output and is byte-for-byte identical to bare --json for the same completed analysis.

For incomplete output, full and debug retain identical unavailable records; compact filters only ordinary passing findings and also retains those records unchanged. All three JSON modes, human output, normal invocation, and --ci exit 3.

Both named modes require --json and apply only to completed analysis output. They do not change analysis, scope, policies, ordering, aggregate or guard states, exit codes, or error shapes and channels. Version JSON supports only bare --version --json; skill-management modes are also incompatible with JSON analysis options. Values are exact and case-sensitive. There is no detail mode.

requiredPolicies lists the policy identifiers needed for actionable findings. An agent should load only those referenced policies, preserve project intent, and decide whether a REVIEW warrants meaningful improvement. A passing result has no required policy work.

Agent integration

After a meaningful editing turn in Git, the normal structured agent command is:

code-guard . --changed-only --json --json-mode compact

Outside Git, pass the exact edited paths instead. An optional, user-authorized platform hook may add --ci; this changes REVIEW’s process exit from 1 to 0 without hiding its findings or changing FAIL and tool-error exits. Agent Code Guard does not install hooks.

See the human and agent workflow for the repeated manual and hook-assisted loop. Installed distributions also carry a version-matched skill payload; skill distribution documents discovery, export, and platform activation.

Performance benchmarking

Performance changes can be measured against the fixed Wayfarer workload with tools/benchmark_wayfarer.py. The caller supplies a disposable checkout at commit 679ddae9717bf78681a2cfbf794f687127b23b5d, its exact project config, and an output directory outside that checkout:

python tools/benchmark_wayfarer.py --wayfarer-path C:\bench\Wayfarer --config-path C:\bench\wayfarer-code-guard.config.json --output-directory C:\bench\results\after --installation-mode "editable wheel from issue 122 branch"

The script validates the source commit, records Python and Code Guard versions, the configuration hash, exact commands, three fresh sequential warm-process samples and medians for LOC-only, syntax-only, and normal six-guard scans, plus a normal-run cProfile file. It requires successful complete Git-status checks before and after the run, and fails if verification fails or analysis creates repository metadata. It rejects output paths equal to or beneath the target, never clones or writes to the target checkout, and is intentionally not a CI test. Run the same script and installation mode against the before and after revisions, retaining both result directories for comparison.

Back to top