This release is a pre-release and may not be stable for production use.
AI Repo Gardener
Find files the AI forgot to delete.
AI Repo Gardener is a deterministic garbage collector for AI-edited Python
repositories. The package, CLI, and portable Skill keep the concise
repo-gardener identifier. Version 0.1.0a11 covers three evidence systems:
repository garbage collection, folder-architecture pressure, and
baseline-relative Python house-style drift. Weak guesses never become
automatic deletions or automatic moves.
$ repo-gardener diff .
[HIGH] stale-file parser_v2.py -> parser.py
confidence 100% risk 0% action safe_delete_candidate
- call_site_migration: app.py
- git_chronology: replacement_added_later
- inbound_imports: 0
$ repo-gardener fix . --dry-run
DELETE parser_v2.py (replacement: parser.py, confidence: 100%)
Both commands default to --base HEAD, so their Git evidence connects without
repeating an option. Use an explicit ref on both commands when auditing a
committed range.
What v0.1 supports
stale-file: a whole Python file has a reachable, structurally similar replacement plus evidence such as call-site migration or Git chronology.orphan-file: a file changed in the current iteration is unreachable, has zero inbound imports, and has no plausible replacement. This is always review-only.orphan-helper: an unreferenced top-level helper, with private/exported/API boundaries and exact string-name references kept explicit. This is always review-only.duplicate-implementation: substantial top-level functions with an exact normalized-AST match across files. It proposes consolidation review, never deletion.dependency-leftover: a runtime dependency declared bypyproject.toml,requirements.txt,requirements/*.txt,setup.cfg, or literalsetup.py, with no matching static/runtime import. Distribution/import-name uncertainty remains visible and the result is review-only. Common distribution/import aliases are recognized, while command-only tools such as pytest, ruff, and gunicorn are excluded from this import-based rule.- Safe deletion: isolated-copy validation before mutation, snapshot, SHA-256 verification, symlink and path-escape refusal, stale-plan rejection, and manual restore. Apply remains experimental.
- Both conventional
src/packageimports and literalsrc.packageimports. - Worktree, staged, committed-range, and untracked changes in
diffmode. - Built-in entrypoint recognition for FastAPI, Flask, Django, Click, and Typer.
- Import- and constructor-alias-aware framework discovery, including aliases in
module-level
try/conditional blocks and factory-local imports. Ambiguous import-root spellings keep every possible local module reachable instead of dropping the edge. - Alias-propagating runtime reference detection for
importlib,runpy,builtins,pkgutil,pkg_resources, file-path loaders, module-shaped strings, and escaped dynamic-loader callables. - Python packaging roots from
pyproject.toml,setup.cfg, and literalsetup.pyentry points,package-dir, and package discovery roots. PyPAimport-names/import-namespacesand setuptoolspy_modulesdeclarations are protected as public distribution APIs; dynamic or unresolved metadata disables auto-delete. - Deployment and automation roots from Dockerfiles, Compose, Procfiles,
systemd units, GitHub Actions, Render, tox, and common Python application
commands such as
python -m, Uvicorn/Gunicornmodule:app, Celery-A, Flask--app, and pytest--pyargs. Templated runtime module commands are reported as uncertainty and disable automatic deletion repository-wide. - Stable versioned JSON for agents and CI.
- Python runtime/tool entrypoints such as
sitecustomize.py,usercustomize.py,noxfile.py,fabfile.py,locustfile.py, anddocs/conf.pyare treated as roots even without inbound repository imports. - Replacement review compares callable signatures, public class members, re-exports, and public constants; assignment-only data/config modules never become automatic deletion candidates.
- UTF-8 BOM and ordinary UTF-8/CRLF Python sources are parsed consistently; any remaining parse failure is shown in both pretty and JSON output.
- Python standard library only at runtime; no model call, account, telemetry, API key, or network access.
The symbol and dependency rules are deliberately conservative static evidence, not proof that a public API, plugin dependency, CLI tool, or reflective caller is unused.
The published real-world benchmark pins requests, Flask, pandas, Django, FastAPI,
pytest, and Pydantic and records cold/warm/diff/structure/style runs. No scan in
that table produced an automatic-deletion candidate. Findings shown at
--confidence all are broken down by rule so review-only tutorial and
compatibility duplicates are not confused with deletion proposals. Exact
commits, timings, and the machine-readable result are in
benchmarks/real-world-smoke.md; this is a
smoke result, not a population-precision claim. A separate machine-readable
20-repository-state labeled corpus reports safe-delete TP/FP/FN and is
documented in benchmarks/labeled-corpus.md.
Try the alpha
Python 3.11+ is required.
The hosted CI matrix is configured for Ubuntu, Windows, and macOS across Python 3.11 through 3.14. Platform support is claimed from hosted runs, not inferred from a single local machine.
python -m pip install "repo-gardener==0.1.0a11"
# Or run once without installing:
uvx --from "repo-gardener==0.1.0a11" repo-gardener --version
repo-gardener --version
repo-gardener diff /path/to/project --base HEAD~1
repo-gardener fix /path/to/project --base HEAD~1 --dry-run
To install from source instead, clone the repository and run
python -m pip install . from its root.
Applying a deletion requires the exact JSON plan that was reviewed:
repo-gardener fix /path/to/project --dry-run --format json > reviewed-plan.json
repo-gardener fix /path/to/project --apply --plan reviewed-plan.json --validate "python -m pytest" --validation-timeout 300
The plan pins the base ref and SHA, HEAD SHA, effective configuration hash, operation set, candidate/replacement hashes, and call-site evidence hashes. Apply re-analyzes the repository and exits with an error if the current plan ID differs from the reviewed plan.
The release wheel contains the complete portable Agent Skill. Locate it after installation, then copy the printed directory into a compatible agent's skill directory:
repo-gardener skill-path
The repository copy is also self-contained and can run without installation:
python skills/repo-gardener/scripts/run_repo_gardener.py diff /path/to/project --base HEAD~1
Copy skills/repo-gardener/ when installing from a clone. Both copies follow
the open Agent Skills specification.
Commands
| Command | Purpose | Mutation |
|---|---|---|
skill-path |
Print the complete portable Skill bundled with the installation | Never |
scan |
Run the supported repo-GC rules | Never |
stale |
Run file-, symbol-, duplicate-, and dependency-level repo GC | Never |
diff [--base <ref>] |
Audit committed, worktree, staged, and untracked iteration changes; base defaults to HEAD |
Never |
fix [--base <ref>] --dry-run |
Preview matching high-confidence deletions; base defaults to HEAD |
Never |
fix [--base <ref>] --dry-run --format json |
Emit the reviewed plan contract | Never |
fix --apply --plan <json> --validate <cmd> [--validation-timeout <seconds>] |
Experimental: validate the reviewed deletion in an isolated copy, reverify the original, then apply it | Yes |
fix --restore |
Restore the latest operation | Yes |
structure |
Run experimental directory analysis explicitly | Never |
style --baseline <ref-or-date> |
Run experimental repo-relative Python style analysis | Never |
scan --experimental |
Add structure and style to a full scan | Never |
Review-only architecture and style analyzers
Structure and style remain opt-in and non-mutating. They do not run in scan
or diff unless --experimental is supplied.
Structure reports a deterministic 0–100 pressure score with flatness,
directory load, cohesion, generic-module pressure, and domain-fragmentation
factors. Domain affinity combines imports, recent Git co-change history, and
symbol vocabulary. Credible partitions include a target folder, exact file
moves and module-name rewrites, target collisions, package-initialization
semantics, relative-import and __file__ resource risks, string-reference
evidence, and risk. Plans set
apply_supported: false; one giant connected component is still reported only
as factual directory load rather than a fabricated partition.
Style findings are deviations from a baseline, never proof of AI authorship. For repositories already dominated by generated code, provide a pre-AI commit or date:
repo-gardener style . --baseline HEAD~20 --confidence all
repo-gardener style . --baseline 2026-01-15 --confidence all
The extractor includes Python-specific conventions: builtin vs typing
generics, X | None vs Optional[X], Path vs os.path, comprehension vs
manual-loop preference, dataclass/TypedDict vs bare dictionaries, and logging
vs print. It also measures branch/cyclomatic complexity, private-helper
ratio, snake-case function naming, function-name word count, multi-clause
defensive guards, single-use tiny helpers, thin wrappers,
log-then-reraise handlers, redundant temporary-return pairs, repeated mapping
.get() calls, and narration-style logging. Ratio features
carry their observation support; low-support conventions such as one Path
call are excluded before scoring. Fewer than five supported baseline files
produces no finding; fewer than 20 cannot produce high confidence.
Safety and configuration
Every mutating run requires validation. Protected paths, generated code, migrations, plugin paths, package APIs, dynamic references, partial replacements, orphan findings, structure findings, and style findings are never automatic deletion candidates.
Any Python parse error disables automatic deletion across the repository while
still allowing review findings. The same veto applies to opaque runtime loading,
including eval/exec, reflected import callables, non-literal imports, and
pkgutil/pkg_resources module discovery. The veto also covers loaders stored
in containers or passed as values, plus runpy.run_path,
spec_from_file_location, and importlib file loaders. Literal module-shaped
strings raise risk only when they resolve to a module that exists in the
repository.
Setuptools modules declared through py-modules/py_modules remain visible as
review findings but can never pass the automatic-deletion gate. Configuration
types are strict: quoted booleans such as allow_delete_src = "false" are an
error, not a truthy safety override.
What the default package protection means
By default, only files at the repository root can pass the automatic-deletion
risk gate. Every Python file inside a subdirectory is review-only, including an
implicit namespace package without __init__.py.
| Layout | Default result for an otherwise strong stale-file finding |
|---|---|
parser_old.py at repository root |
Eligible for safe_delete_candidate |
app/parser_old.py without app/__init__.py |
Review: possible implicit namespace API |
app/parser_old.py with app/__init__.py |
Review: possible package API |
src/pkg/parser_old.py |
Review: package and src/ protection |
To make reviewed application-internal package files eligible, the repository owner must explicitly opt out of both protections:
[safety]
allow_delete_src = true
allow_delete_package_modules = true
Copy repo-gardener.toml.example to
repo-gardener.toml to declare entrypoints, protected paths, exclusions,
validation commands, and analysis thresholds. Only enable these safety
overrides after confirming that the package is application-internal rather than
a public or plugin-facing API.
Validation commands read from the target repository are untrusted and ignored
by default. Prefer explicit --validate arguments. --trust-repo-config is an
opt-in to execute commands supplied by that repository and should only be used
after review. Validation runs against an isolated copy where the planned
candidate files have already been removed. Only after validation succeeds does
AI Repo Gardener reverify the original hashes and perform the real deletion.
Regular and linked Git worktrees use a disposable Git worktree. The original
staged index is applied separately from the working-tree overlay, so validation
can distinguish git diff --cached from unstaged changes. Repository symlinks
that are absolute or escape the repository are rejected before validation
because they could route a relative-path side effect outside the disposable
workspace. Other relative-path validation side effects stay in the disposable
copy. A failing command reports its exit code and bounded stderr/stdout. This
is not a command sandbox: commands that address absolute paths or external
services can still affect them. Each command has a 300-second default timeout;
set a different positive limit with --validation-timeout.
Applied fixes store recoverable snapshots under .repo-gardener/. Add this
line to the target repository's .gitignore:
.repo-gardener/
Rollback snapshots and state files are confined to .repo-gardener/; symlinks
at the state root or any rollback path are refused. Manual rollback restores the
deleted candidate files. Validation does not run in the original working tree,
so its ordinary relative-path cache, coverage, generated-file, or test-fixture
side effects are discarded with the isolated copy.
Unchanged Python files use an external extraction-metadata cache outside the
target repository (%LOCALAPPDATA%/repo-gardener/cache on Windows or the XDG
cache directory on Unix). The key combines repository-relative parsing context,
source content, Python version, and an automatically derived analysis ABI, so
identical ephemeral clones/worktrees can reuse records while analyzer changes
invalidate old entries. A hit still reparses the source with Python's AST for
safety; it reuses the more expensive derived imports, symbols, framework roots,
and runtime-reference metadata. metrics.parse_cache_hits reports reuse. Set
REPO_GARDENER_DISABLE_CACHE=1 to disable it or
REPO_GARDENER_CACHE_DIR to choose a different parent directory. Source text
is not stored in the cache, and changing file content invalidates the entry.
The same build-once release candidate runs on every pull request, main, and manual
dispatch without publishing. Tags reuse that pipeline: one wheel is built,
installed and tested outside the source tree on all 12 OS/Python combinations,
validated as a portable Skill, dogfooded, provenance-attested, attached to a
draft prerelease, published to PyPI through trusted publishing, and released
only after every gate passes. A stable aggregate release-candidate status is
required by the protected main ruleset. Third-party
Actions are pinned to full commit SHAs. Weekly pinned-repository benchmarks
publish their JSON result as a workflow artifact.
Security reports follow SECURITY.md. Safety-critical files
have code-owner review rules, dependency updates are monitored, and protected
branch/tag policies are configured in GitHub rather than implied by this file.
The JSON contract is documented in
skills/repo-gardener/references/finding-schema.md.
Alpha limits
The fixture suite covers stale replacements, partial replacements, dynamic
string paths, plugins, literal src.* imports, file and helper orphans,
duplicate implementations, dependency leftovers, rename chronology, style
support/baselines, co-change affinity, entropy, and move-plan examples. Public-repository
smoke results and exact commit hashes are recorded in
benchmarks/real-world-smoke.md. That run is
not a labeled precision benchmark. The curated labeled corpus and its explicit
DELETE/KEEP/REVIEW ground truth are documented in
benchmarks/labeled-corpus.md. The separate adversarial safety gate is
recorded in benchmarks/safety-benchmark.md;
it reports eligible-deletion false positives rather than claiming population
precision.
Repository safety overrides are honored during analysis without
--trust-repo-config (that
flag controls validation-command execution only), so review the repository
config before authorizing apply. The plan pins its effective configuration hash.
For CI, --fail-on high, --fail-on medium, and --fail-on any use exit codes
1 for a reached finding threshold and 2 for tool/configuration errors.
Development
python -m pip install -e ".[dev]"
python -m pytest
ruff check .
ruff format --check .
python skills/repo-gardener/scripts/run_repo_gardener.py scan . --confidence all
The demo asset is reproducible with Pillow installed:
python scripts/render_demo.py
Contributions must add or update a reusable fixture for every false positive or new evidence rule.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file repo_gardener-0.1.0a11-py3-none-any.whl.
File metadata
- Download URL: repo_gardener-0.1.0a11-py3-none-any.whl
- Upload date:
- Size: 148.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d21e5c1669cd6b63e718c1b22853a1a8f56ab3429a80501d7489daa4254c2732
|
|
| MD5 |
951dbc76f067ee87eec365029032b4b1
|
|
| BLAKE2b-256 |
d6b5389c9f74b4df7af4d026b2c647c64f3ba0e8898702533d141e8c9a0541c9
|
Provenance
The following attestation bundles were made for repo_gardener-0.1.0a11-py3-none-any.whl:
Publisher:
release.yml on niansia/ai-repo-gardener
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
repo_gardener-0.1.0a11-py3-none-any.whl -
Subject digest:
d21e5c1669cd6b63e718c1b22853a1a8f56ab3429a80501d7489daa4254c2732 - Sigstore transparency entry: 2660434373
- Sigstore integration time:
-
Permalink:
niansia/ai-repo-gardener@88e2d7240a72af37e1c2f1c8b9910773b7da343b -
Branch / Tag:
refs/tags/v0.1.0-alpha.11 - Owner: https://github.com/niansia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@88e2d7240a72af37e1c2f1c8b9910773b7da343b -
Trigger Event:
push
-
Statement type: