Skip to main content

autocto

CI PyPI License: MIT Python 3.12+ Tests

Automated CTO: repository health analyzers that read a codebase and its git history and report where the real engineering risk lives.

autocto pipeline: git history and the source tree feed four analyzers (hotspots, duplicates, maintenance cost, architectural debt); a fifth, the migration planner, orders human-proposed changes with a topological sort. Sample output against the recall repo: frontend/src/App.jsx scores 5289, churn 41 times complexity 129.

Status: all five planned analyzers are shipped (hotspots, duplicates, maintenance-cost, architectural-debt, migration-plan), plus a CLI (autocto) and a v0.2.0 release on PyPI. See Roadmap for what's next.

Quickstart

pipx install repo-autocto   # or: pip install repo-autocto
autocto report /path/to/some/repo
$ autocto hotspots ../recall --limit 3
path                  churn  complexity  score
--------------------  -----  ----------  -----
frontend/src/App.jsx  41     129         5289
backend/main.py       41     89          3649
backend/memory.py     11     49          539

(Captured 2026-09-27 against a local checkout of recall. Run against autocto's own tree it is less interesting: every file there has a churn of 1.)

Every subcommand (hotspots, duplicates, maintenance, architecture, report) takes a repo path (default: current directory) and --json for machine-readable output. autocto plan proposal.json is the one command that takes a file instead of a repo: it orders a JSON redesign proposal (title, rationale, changes with id, description, files, depends_on, risk) into the markdown migration plan. autocto --help and autocto <command> --help show every flag.

To gate a pull request, hotspots and maintenance take --fail-over N: if any file's score (or cost) is above N, the offending files are printed to stderr and the command exits 1 (exit 2 stays for usage errors). It checks every file, not only the --limit rows shown.

autocto hotspots . --fail-over 5000   # CI step: fails the build when a file crosses the line

Source walks skip .git, node_modules, dist, build, caches, site-packages, and any virtual environment whose name starts with venv or .venv (so .venv312 and venv-3.14 too).

Prefer calling it from Python directly? Each analyzer is still a pure analyze_repo(repo_dir) function - the CLI is a thin wrapper over the same functions used below.

pip install -e ".[dev]"
python -c "
from pathlib import Path
from autocto.hotspots import analyze_repo

for hotspot in analyze_repo(Path('.'))[:3]:
    print(hotspot)
"

This runs the bug-hotspot analyzer against whatever repo you point it at - here, autocto's own working directory. See Demo below for real output, and Architecture for what each module returns.

Analyzers

  1. Bug-hotspot analyzer (src/autocto/hotspots.py) - churn x complexity over git log; the files that change often AND are complex are where bugs cluster. analyze_repo(repo_dir) ranks a real repo's tracked files.
  2. Duplicated-logic detector (src/autocto/duplicates.py) - token-shingle (Jaccard) similarity across files; analyze_repo(repo_dir) ranks a real repo's file pairs by duplication risk.
  3. Maintenance-cost estimator (src/autocto/maintenance_cost.py) - size x churn x dependency fan-in; analyze_repo(repo_dir) ranks a real repo's tracked files by size x churn x (1 + fan_in).
  4. Architectural-debt report (src/autocto/architectural_debt.py) - three independent signals over one same-repo import graph: import cycles (Tarjan strongly-connected components), god files (large AND highly-connected), and layering violations (only checked when the caller supplies an explicit layer order - see Architecture). analyze_repo(repo_dir) returns all three for a real repo.
  5. Migration-plan generator (src/autocto/migration_plan.py) - turns a human-authored redesign proposal (a set of ProposedChanges, each with what it touches and what it depends on) into an ordered, verifiable markdown plan. generate_migration_plan(title, changes) returns the rendered plan; unlike the other four, it has no "read a real repo" half - its only input is the proposal itself.

All analyzers are pure functions over repo data (or, for the migration-plan generator, over a proposal you supply): offline, deterministic, testable without API keys.

Demo

Real, captured output - not invented numbers. Three runs below, all against repos checked out locally at the time this README was written.

Bug-hotspot analyzer against recall (a separate, actively developed repo

with 94 commits of real history - more churn variance than autocto's own single-commit-per-file history shows)

Command:

python -c "
from pathlib import Path
from autocto.hotspots import analyze_repo

for hotspot in analyze_repo(Path('/path/to/recall'))[:5]:
    print(hotspot)
"

Output:

Hotspot(path='frontend/src/App.jsx', churn=29, complexity=129, score=3741)
Hotspot(path='backend/main.py', churn=14, complexity=89, score=1246)
Hotspot(path='backend/memory.py', churn=4, complexity=46, score=184)
Hotspot(path='eval/benchmark.py', churn=3, complexity=40, score=120)
Hotspot(path='backend/live.py', churn=4, complexity=19, score=76)

App.jsx is both the most-changed file in that repo's history (29 commits) and its most branch-heavy - exactly the churn x complexity signal this analyzer is built to surface.

Duplicated-logic detector against autocto's own tree

Command:

python -c "
from pathlib import Path
from autocto.duplicates import analyze_repo

pairs = analyze_repo(Path('.'))
print(f'{len(pairs)} pair(s) at or above the default 0.6 similarity threshold')
for pair in pairs:
    print(pair)
"

Output:

0 pair(s) at or above the default 0.6 similarity threshold

autocto's own two analyzer modules and their tests share no duplicated 5-token shingles above the default threshold - a correct negative result on a small, deliberately non-duplicated codebase, not a bug.

Maintenance-cost estimator against recall (same repo as the hotspot demo,

for a like-for-like comparison)

Command:

python -c "
from pathlib import Path
from autocto.maintenance_cost import analyze_repo

for cost in analyze_repo(Path('/path/to/recall'))[:5]:
    print(cost)
"

Output:

MaintenanceCost(path='frontend/src/App.jsx', size=772, churn=29, fan_in=1, cost=44776)
MaintenanceCost(path='backend/main.py', size=461, churn=14, fan_in=1, cost=12908)
MaintenanceCost(path='backend/memory.py', size=262, churn=4, fan_in=0, cost=1048)
MaintenanceCost(path='backend/live.py', size=125, churn=4, fan_in=1, cost=1000)
MaintenanceCost(path='eval/benchmark.py', size=309, churn=3, fan_in=0, cost=927)

App.jsx tops both this ranking and the hotspot one - it is large, the most-changed file in the repo, AND imported elsewhere, which is exactly what "maintenance cost" should mean: not just churn x complexity, but the size of the file and the blast radius of touching it.

Architectural-debt report against personal-llm (a bigger, more layered

codebase than recall or autocto itself - more likely to actually show something)

Command:

python -c "
from pathlib import Path
from autocto.architectural_debt import analyze_repo

report = analyze_repo(Path('/path/to/personal-llm'))
print(f'{len(report.cycles)} import cycle(s)')
print(f'{len(report.god_files)} god file(s)')
for god_file in report.god_files:
    print(' ', god_file)

layered = analyze_repo(
    Path('/path/to/personal-llm'),
    layer_order=['router', 'memory', 'rag', 'interfaces'],
)
print(f'{len(layered.layering_violations)} layering violation(s) under router -> memory -> rag -> interfaces')
"

Output:

0 import cycle(s)
3 god file(s)
  GodFile(path='src/personal_llm/memory/store.py', size=287, fan_in=14, fan_out=1)
  GodFile(path='src/personal_llm/interfaces/cli.py', size=302, fan_in=1, fan_out=11)
  GodFile(path='src/personal_llm/interfaces/api.py', size=257, fan_in=0, fan_out=9)
0 layering violation(s) under router -> memory -> rag -> interfaces

Real, captured, honest results, including the zeroes - same "a clean result is a correct result, not a suppressed one" rule duplicates.py's demo already follows. personal-llm has no import cycles and no layering violations under that ordering (its router package genuinely never reaches up into interfaces, by inspection of the real source), which is a legitimate, useful thing to report about a codebase: this project's dependency discipline is holding. memory/store.py is flagged as a god file because fourteen other files reference it by name (the SQLite-backed MemoryStore class is the one shared persistence layer) on top of nearly 300 lines - exactly the "big and everyone depends on it" combination this signal exists to surface.

Migration-plan generator over a real duplication in this repo

Unlike the other four analyzers, this one has no "scan a real repo" mode - its input is a proposal you write, not something analyze_repo discovers. So instead of pointing it at a repo, this worked example describes a real, verifiable duplication in autocto's own tree: duplicates.py, maintenance_cost.py, and architectural_debt.py each define an identical _iter_source_files/_SKIP_DIR_NAMES pair today (grep -n "_iter_source_files\|_SKIP_DIR_NAMES" src/autocto/*.py shows all three) - exactly the kind of duplication duplicates.py itself is built to flag, not yet acted on.

Command:

python -c "
from autocto.migration_plan import ProposedChange, generate_migration_plan

changes = [
    ProposedChange(
        id='extract-shared-file-walker',
        description='Extract the duplicated _iter_source_files/_SKIP_DIR_NAMES pair out of duplicates.py, maintenance_cost.py, and architectural_debt.py into one shared module',
        files=('src/autocto/_repo_walk.py',),
        risk='medium',
    ),
    ProposedChange(
        id='migrate-duplicates',
        description='Point duplicates.py at the shared walker instead of its own copy',
        files=('src/autocto/duplicates.py',),
        depends_on=('extract-shared-file-walker',),
        risk='low',
    ),
    ProposedChange(
        id='migrate-maintenance-cost',
        description='Point maintenance_cost.py at the shared walker',
        files=('src/autocto/maintenance_cost.py',),
        depends_on=('extract-shared-file-walker',),
        risk='low',
    ),
    ProposedChange(
        id='migrate-architectural-debt',
        description='Point architectural_debt.py at the shared walker',
        files=('src/autocto/architectural_debt.py',),
        depends_on=('extract-shared-file-walker', 'migrate-maintenance-cost'),
        risk='medium',
    ),
    ProposedChange(
        id='delete-dead-walkers',
        description='Delete the now-unused per-module _iter_source_files/_SKIP_DIR_NAMES copies',
        depends_on=('migrate-duplicates', 'migrate-maintenance-cost', 'migrate-architectural-debt'),
        risk='high',
    ),
]

print(generate_migration_plan('Consolidate duplicated file-walking logic', changes,
    rationale='duplicates.py, maintenance_cost.py, and architectural_debt.py each define an '
              'identical _iter_source_files/_SKIP_DIR_NAMES pair today (verified by grep) - this '
              'plan removes the duplication for real.'))
"

Output (real, generated by the command above, not hand-written):

# Migration Plan: Consolidate duplicated file-walking logic

## Rationale

duplicates.py, maintenance_cost.py, and architectural_debt.py each define an identical _iter_source_files/_SKIP_DIR_NAMES pair today (verified by grep) - this plan removes the duplication for real.

## Steps

Ordered so every change's dependencies land first (`autocto.migration_plan.order_changes`, topological sort). Execute top to bottom; do not skip ahead.

### 1. extract-shared-file-walker - Extract the duplicated _iter_source_files/_SKIP_DIR_NAMES pair out of duplicates.py, maintenance_cost.py, and architectural_debt.py into one shared module

**Risk:** medium
**Files:** src/autocto/_repo_walk.py
**Verify:** Run the full test suite; smoke-test the affected feature manually.

### 2. migrate-duplicates - Point duplicates.py at the shared walker instead of its own copy

**Risk:** low
**Files:** src/autocto/duplicates.py
**Depends on:** extract-shared-file-walker
**Verify:** Run the affected tests; confirm no other callers reference the old shape.

### 3. migrate-maintenance-cost - Point maintenance_cost.py at the shared walker

**Risk:** low
**Files:** src/autocto/maintenance_cost.py
**Depends on:** extract-shared-file-walker
**Verify:** Run the affected tests; confirm no other callers reference the old shape.

### 4. migrate-architectural-debt - Point architectural_debt.py at the shared walker

**Risk:** medium
**Files:** src/autocto/architectural_debt.py
**Depends on:** extract-shared-file-walker, migrate-maintenance-cost
**Verify:** Run the full test suite; smoke-test the affected feature manually.

### 5. delete-dead-walkers - Delete the now-unused per-module _iter_source_files/_SKIP_DIR_NAMES copies

**Risk:** high
**Depends on:** migrate-duplicates, migrate-maintenance-cost, migrate-architectural-debt
**Verify:** Run the full test suite; stage the change behind a flag or on a branch and get a second reviewer before merging.

## Rollback

Each step above should be its own commit (or PR) so any single step can be reverted independently without undoing steps that landed after it, as long as later steps did not themselves depend on it - check the "Depends on" list above before reverting a step other changes rely on.

## Reviewer instructions

This is a PLAN ONLY - nothing has been changed. A human executes each step, runs its verification, and only then moves to the next.

The topological sort put extract-shared-file-walker first (nothing depends on it) and delete-dead-walkers last (it depends on all three migrations), exactly as declared - not hand-ordered. This plan itself is not applied by this repo; it is only a worked demonstration of the generator.

Architecture

One line per planned analyzer, numbered to match Analyzers above:

  1. src/autocto/hotspots.py - churn (git log --numstat commit-frequency count) x complexity (branch-keyword/operator density proxy). Pure functions parse_numstat_log, complexity_score, compute_hotspots wired together by the one effectful entry point, analyze_repo(repo_dir, *, extensions=..., git_log_fn=...), which returns list[Hotspot] (path, churn, complexity, score).
  2. src/autocto/duplicates.py - token-shingle Jaccard similarity across files. Pure functions tokenize, shingle, jaccard_similarity, find_duplicates wired together by analyze_repo(repo_dir, *, extensions=..., shingle_size=..., threshold=...), which returns list[DuplicatePair] (path_a, path_b, similarity).
  3. src/autocto/maintenance_cost.py - size (line count) x churn (reuses hotspots.parse_numstat_log) x dependency fan-in (how many other files import it, from the real import graph built by src/autocto/imports.py, Python and JS/TS). Pure functions count_lines, extract_referenced_names, compute_fan_in (the older stem name-matching helpers, kept for callers with only bare names), estimate_costs (formula: size x churn x (1 + fan_in) - see that function's docstring for why 1 + fan_in, not bare fan_in) wired together by analyze_repo(repo_dir, *, extensions=..., git_log_fn=...), which returns list[MaintenanceCost] (path, size, churn, fan_in, cost).
  4. src/autocto/architectural_debt.py - three independent signals over one same-repo import graph, built by autocto.imports.resolve_imports, which resolves each import statement to the one file it names (Python via ast, packages, relative imports and src/ layouts; JS/TS relative specifiers, index.* and .js -> .ts). Imports of anything outside the repo give no edge; tsconfig path aliases and dynamic imports are not followed. build_import_graph is the older stem name-matching variant. Import cycles: find_cycles runs an iterative Tarjan strongly-connected-components pass over the graph and reports each component of size > 1 (a documented simplification versus enumerating every elementary cycle - see the function's docstring for why). God files: find_god_files flags files that clear BOTH a size threshold (line count, via maintenance_cost.count_lines) AND a connections threshold (fan_in + fan_out, fan_in and fan_out both from the same import graph), returning list[GodFile] (path, size, fan_in, fan_out) ranked by size x (fan_in + fan_out). Layering violations: find_layering_violations only runs when the caller passes an explicit layer_order (an ordered list of top-level package names, most-foundational first) - there is no universal default across arbitrary repos, so None (the default) means "skip the check", never a fabricated ordering; a violation is an import edge pointing from a more-foundational layer up into a less-foundational one, returned as list[LayeringViolation] (importer, importee, importer_layer, importee_layer). All three are wired together by the one effectful entry point, analyze_repo(repo_dir, *, extensions=..., layer_order=None, size_threshold=..., connections_threshold=...), which returns one ArchitecturalDebtReport (cycles, god_files, layering_violations). No git, no subprocess anywhere in this module - none of its three signals need churn.
  5. src/autocto/migration_plan.py - takes a human-authored Sequence[ProposedChange] (each with id, description, files, depends_on, risk) and orders it with order_changes (Kahn's-algorithm topological sort, ties broken by id ascending for determinism, raising CycleError on an unresolvable depends_on cycle or an unknown id). build_migration_plan(title, changes) attaches a risk-derived verification instruction to each step (MigrationStep); the one effectful-looking but actually still-pure entry point, generate_migration_plan(title, changes), builds then renders to markdown in one call. No filesystem or subprocess access anywhere in this module - it never reads a repo, only the proposal passed in.

All shipped modules follow the same shape: filesystem/subprocess access is isolated to analyze_repo, everything else is a pure function over strings and dicts. Where a module has a genuinely effectful operation (git log in hotspots/maintenance_cost) it is an injectable seam (git_log_fn) so tests never shell out to real git; architectural_debt.py needs no such seam at all, since none of its three signals depend on git history; migration_plan.py has no analyze_repo at all, since its whole job is over caller-supplied data, not a repo scan. See CONTRIBUTING.md for the pattern in detail.

Roadmap

All five Tier 4 analyzers in the ai-ecosystem Night Shift queue (PROJECT-GENESIS.md section 9) are shipped, plus the CLI and the v0.2.0 PyPI release. Nothing further is queued for this repo yet - next candidates would come from a fresh Night Shift pass over PROJECT-GENESIS.md section 9, or from real usage against other repos in the fleet.

Metadata

Release files for repo-autocto 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for repo-autocto 0.2.0
File Size Uploaded
repo_autocto-0.2.0.tar.gz 42.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for repo-autocto 0.2.0
File Interpreter ABI Platform
repo_autocto-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 74.9 kB

Release files / repo_autocto-0.2.0.tar.gz

Download URL repo_autocto-0.2.0.tar.gz
Size 42.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a6424d2bc70bff9cbe6fa83b0c0c035aced54a739c481ac2043e45d7e2a56607
BLAKE2b-256 checksum
How to use checksums
9387b0ed53c60aa27af154619dce4c3a8184df57cf48d6039b7037d0b2224b87
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release files / repo_autocto-0.2.0-py3-none-any.whl

Download URL repo_autocto-0.2.0-py3-none-any.whl
Size 32.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
99df8960282e0c95134ad2ec9fc2be26fa65c78182bd66347b692393d454f615
BLAKE2b-256 checksum
How to use checksums
d3a6a635f1e86296cfe3c4e421ff3ae6071667cedece3652748c186a3099ae3c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page