archfit

module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2026 License: Apache-2.0

README

archfit

CI Release Version GHCR Image Size Go Reference Go Report Card License

Architecture-fitness checks for AI agents and CI.

archfit checks whether the code still follows the architecture you intended. It reads code-structure facts from language analyzers, then evaluates them against .archfit.yaml: boundaries, layers, public APIs, cycles, and Balanced Coupling.

Balanced Coupling is a way to judge whether a dependency is healthy or risky. It looks at three things: how strong the dependency is, how far apart the coupled parts are, and how often they change.

The output is built for automation: deterministic gate violations, structured repair tasks for agents, and a banded architecture scorecard. Architecture drift becomes something your agent and CI can act on before it turns into review pain.

Why

AI agents are good at local edits. They are not good at carrying the whole system design in context.

That gap matters. An agent can make a change that passes tests while it quietly:

  • imports across a forbidden boundary;
  • bypasses a module's public API;
  • adds a shortcut between layers;
  • grows a dependency cycle.

Each single change can look harmless. Over time, those shortcuts become the real architecture. Coupling grows, bugs cascade across distant modules, and every future fix needs more human context, more agent tokens, and more retries.

archfit puts an architecture-fitness signal in that loop:

flowchart LR
    A[Agent edits files] --> C[archfit check]
    C -->|clean| P([PR opened])
    C -->|violation + agent_tasks| R[Agent applies the repair order]
    R --> C
    classDef ok fill:#d3f9d8,stroke:#2f9e44,color:#000;
    classDef gate fill:#ffe3e3,stroke:#e03131,color:#000;
    class P ok;
    class C gate;

When a gate fails, archfit emits an agent_tasks entry. It is a repair order, not a vague log line:

{
  "rule_id": "no_internal_access",
  "goal": "Replace the internal-API access from pkg/a/a.go to pkg/b/internal/impl.go with b's public API.",
  "constraints": [
    "Use only the public API of module b",
    "public surface of \"b\": [pkg/b/api/**]"
  ],
  "files": ["pkg/a/a.go", "pkg/b/internal/impl.go"],
  "validation": ["archfit check -c .archfit.yaml --full"]
}

The task includes the goal, the constraints, the files to inspect, and the command that proves the fix.

How it works

archfit separates facts, gates, and narration.

Language adapters collect code-structure facts. A deterministic core evaluates those facts against your config. Optional LLM features can explain and prioritize the evidence, but they never decide whether the gate passes.

flowchart TB
    subgraph tools[External extractors]
        direction LR
        T1[go list]
        T2[dependency-cruiser]
        T3[ast-grep]
        T4[grimp]
    end
    CFG[".archfit.yaml"]
    tools -->|dependency facts| core
    CFG --> core
    subgraph core["archfit core — deterministic, no LLM"]
        direction LR
        CL[classify] --> RU[gates]
        CL --> ME[metrics] --> SC[score]
    end
    core --> OUT["SARIF · JSON · Markdown · text<br/>agent_tasks · scorecard"]
    OUT -. off-gate · advisory only .-> LLM["review · enrich · autopilot<br/>narrate &amp; prioritize evidence"]
    classDef side fill:#f3f0ff,stroke:#7048e8,color:#000;
    class LLM side;
    style core fill:#e7f5ff,stroke:#1971c2,color:#000;

For unchanged input, gate output is byte-identical. That makes it safe for CI and for comparing commits. If an optional analyzer is missing, the dependent metric reports n/a with the enable step instead of pretending everything is fine.

What you get

  • Deterministic gates for forbidden dependencies, public-API boundaries, layer direction, import cycles, and configured thresholds.
  • agent_tasks repair blocks so an AI agent gets the fix, not just the error.
  • A banded 7-dimension scorecard (archfit score): boundary integrity, coupling balance, dependency-graph health, cohesion, change locality, architecture fitness, and analysis confidence.
  • Balanced-Coupling advisories — strength × distance × volatility, grouped into rollups instead of one line per edge.
  • Honest coverage — missing tools degrade to n/a, not a false green; gaps are reported in every output format.
  • Baselines so accepted current debt does not hide new findings.
  • Off-gate LLM narration (review, enrich, autopilot) that may only narrate and prioritize collected evidence — never invent gate violations.

Methodology — Balanced Coupling

Coupling is not automatically bad. Some parts should move together. The problem is coupling that is too strong, too distant, or too volatile for the design.

archfit operationalizes the checkable parts of Vlad Khononov's Balanced Coupling model. It scores cross-boundary relationships on three axes and explains whether the relationship looks cohesive or risky:

flowchart LR
    S["Integration strength<br/>contract → intrusive"] --> J{coupling<br/>verdict}
    D["Distance<br/>same module → cross deploy-unit"] --> J
    V["Volatility<br/>low → high change-rate"] --> J
    J -->|high strength · low distance| G["Cohesion<br/>balanced — leave it"]
    J -->|high strength · high distance · high volatility| B["Cascading change<br/>distributed-monolith risk"]
    classDef good fill:#d3f9d8,stroke:#2f9e44,color:#000;
    classDef risk fill:#ffe3e3,stroke:#e03131,color:#000;
    class G good;
    class B risk;

It does not replace architecture review — it makes repeatable evidence cheap to collect and safe to run in CI. For the mapping from theory to signals, see Concepts and Metrics.

How it compares

Language-specific boundary linters are still useful. archfit sits one level above them.

Those tools supply facts. archfit turns the facts into a measured verdict about intended architecture, Balanced-Coupling risk, score movement, and repair tasks.

Tool Languages Output for agents/CI Coupling model LLM narration
archfit See languages SARIF + JSON + agent_tasks + score Balanced Coupling (S/D/V) off-gate
dependency-cruiser TS/JS JSON/HTML, rule violations rule-based no
import-linter Python text, contract pass/fail layered contracts no
ArchUnit Java/JVM (.NET/TS) test assertions in the build rule-based no

Use a single-language linter when you want fine-grained in-build assertions for one ecosystem. Use archfit when you want one architecture config, one machine-readable verdict, and feedback shaped for an AI-agent repair loop.

Quick start

Install the CLI (use a release tag, not @latest, in repeatable docs):

go install github.com/alexei-led/archfit/cmd/archfit@v0.5.0

Or run the Docker image with the bundled toolchain:

docker run --rm -v "$(pwd):/repo" ghcr.io/alexei-led/archfit:v0.5.0 \
  check --config /repo/.archfit.yaml --full

Run the first check in a repository:

archfit doctor                                   # check available analyzers
archfit init --root . --output .archfit.yaml     # generate a starter config
$EDITOR .archfit.yaml
archfit check --config .archfit.yaml --full      # run the gates
archfit score --config .archfit.yaml --full      # banded scorecard

Accept current findings as a baseline when they represent known debt:

archfit baseline --full --config .archfit.yaml

Starter configs for common project shapes live in examples/. Need Docker, CI, optional analyzers, or language-specific setup? Start with the guide.

Core commands

Command Use
archfit doctor Check local analyzer/tool availability.
archfit init Generate a starter .archfit.yaml.
archfit update Sync .archfit.yaml with current structure.
archfit check Run architecture gates and metrics.
archfit score Emit the banded 7-dimension scorecard.
archfit scan Produce a full Markdown audit report.
archfit baseline Save accepted current findings.
archfit review Off-gate LLM narrative review of the evidence.
archfit enrich Draft off-gate LLM coupling-label refinements.
archfit autopilot Draft a full .archfit.yaml via LLM (review-only).
archfit explain <id> Explain one finding by fingerprint prefix.
archfit install Check or install optional language tools.

See the commands guide for flags, formats, and exit codes. Point --root at the repo and --config elsewhere to run archfit from outside the analyzed tree (external CI).

Documentation

License

Apache-2.0. See LICENSE.

Directories

Path Synopsis
cmd
archfit command
autopilot: one-shot LLM drafter for a full .archfit.yaml.
autopilot: one-shot LLM drafter for a full .archfit.yaml.
internal
agenttask
Package agenttask assembles the structured repair-task block (spec §13) from gate findings.
Package agenttask assembles the structured repair-task block (spec §13) from gate findings.
baseline
Package baseline handles loading and saving the archfit baseline file, which tracks accepted findings and metric snapshots across runs.
Package baseline handles loading and saving the archfit baseline file, which tracks accepted findings and metric snapshots across runs.
calibrate
Package calibrate compares two coupling scorers over a coupling.Index and reports per-edge agreement between them.
Package calibrate compares two coupling scorers over a coupling.Index and reports per-edge agreement between them.
classify
Package classify assigns Balanced Coupling classifications to graph edges: strength, distance, volatility, and explicitness.
Package classify assigns Balanced Coupling classifications to graph edges: strength, distance, volatility, and explicitness.
config
Package config defines the Config struct, its view projections, and the Load function that parses and validates an archfit.yaml configuration file.
Package config defines the Config struct, its view projections, and the Load function that parses and validates an archfit.yaml configuration file.
engine
Package engine implements the pipeline orchestrator that runs extractors, classify, rules, status, metrics, and renderers in sequence.
Package engine implements the pipeline orchestrator that runs extractors, classify, rules, status, metrics, and renderers in sequence.
extract/astgrep
Package astgrep implements the ports.PatternProvider port for ast-grep.
Package astgrep implements the ports.PatternProvider port for ast-grep.
extract/clones
Package clones provides a clone-detection runner that identifies duplicated code blocks across files.
Package clones provides a clone-detection runner that identifies duplicated code blocks across files.
extract/complexity
Package complexity provides a cyclomatic-complexity runner that collects per-function CCN values via the external lizard tool.
Package complexity provides a cyclomatic-complexity runner that collects per-function CCN values via the external lizard tool.
extract/deployunit
Package deployunit detects deploy units in a repository by scanning for Go main packages, TypeScript package.json workspaces, Python pyproject.toml, Dockerfiles, and Kubernetes Deployment/StatefulSet manifests.
Package deployunit detects deploy units in a repository by scanning for Go main packages, TypeScript package.json workspaces, Python pyproject.toml, Dockerfiles, and Kubernetes Deployment/StatefulSet manifests.
extract/dynimports
Package dynimports detects dynamic/lazy imports that are invisible to the static dependency graph: Python non-top-level (in-function) `import`/`from`, `importlib.import_module` / `__import__`, and TypeScript `require()` / dynamic `import()`.
Package dynimports detects dynamic/lazy imports that are invisible to the static dependency graph: Python non-top-level (in-function) `import`/`from`, `importlib.import_module` / `__import__`, and TypeScript `require()` / dynamic `import()`.
extract/gitnexus
Package gitnexus provides an optional dependant-count provider backed by the gitnexus CLI's knowledge graph.
Package gitnexus provides an optional dependant-count provider backed by the gitnexus CLI's knowledge graph.
extract/golang
Package golang implements the Go import extractor using golang.org/x/tools/go/packages.
Package golang implements the Go import extractor using golang.org/x/tools/go/packages.
extract/loc
Package loc counts source-file line counts for the structural_weight metric.
Package loc counts source-file line counts for the structural_weight metric.
extract/py
Package py implements the Python import extractor using grimp.
Package py implements the Python import extractor using grimp.
extract/runtime
Package runtime detects async/message-bus integration patterns in a repository.
Package runtime detects async/message-bus integration patterns in a repository.
extract/rust
Package rust implements the Rust dependency extractor using `cargo metadata`.
Package rust implements the Rust dependency extractor using `cargo metadata`.
extract/scip
Package scip implements the ports.SymbolResolver port using SCIP indexers.
Package scip implements the ports.SymbolResolver port using SCIP indexers.
extract/ts
Package ts implements the TypeScript import extractor using dependency-cruiser.
Package ts implements the TypeScript import extractor using dependency-cruiser.
facts
Package facts assembles per-module structural facts from already-collected data (symbol graph, file LOC, co-change history, optional gitnexus impact).
Package facts assembles per-module structural facts from already-collected data (symbol graph, file LOC, co-change history, optional gitnexus impact).
fitness
Package fitness detects architecture-intent ENFORCEMENT signals in a repository.
Package fitness detects architecture-intent ENFORCEMENT signals in a repository.
history/git
Package git provides functions for reading git history: changed files between refs, the HEAD ref, repo root, and a churn stats stub.
Package git provides functions for reading git history: changed files between refs, the HEAD ref, repo root, and a churn stats stub.
initcfg
Package initcfg discovers project structure and renders a starter .archfit.yaml.
Package initcfg discovers project structure and renders a starter .archfit.yaml.
labels
Package labels holds the pure logic for pinned coupling-strength labels — the human-reviewed output of `archfit enrich`.
Package labels holds the pure logic for pinned coupling-strength labels — the human-reviewed output of `archfit enrich`.
labels/labelsio
Package labelsio is the I/O adapter for pinned coupling labels: it reads and validates .archfit-labels.yaml from disk.
Package labelsio is the I/O adapter for pinned coupling labels: it reads and validates .archfit-labels.yaml from disk.
llm
Package llm is the thin off-gate LLM provider layer for archfit's enrich and explain commands.
Package llm is the thin off-gate LLM provider layer for archfit's enrich and explain commands.
metrics
Package metrics defines the Metric interface, the generic Calculator[In] adapter, and the New() registry.
Package metrics defines the Metric interface, the generic Calculator[In] adapter, and the New() registry.
metrics/boundary
Package boundary implements the five boundary-health metrics: encapsulation, unbalanced_edge, cycle, coverage, and change_locality.
Package boundary implements the five boundary-health metrics: encapsulation, unbalanced_edge, cycle, coverage, and change_locality.
metrics/internal/modgraph
Package modgraph provides the shared module-level graph and history helpers used by the modularity metrics: collapsing nodes/files to module units, aggregating churn, and computing SCC-condensed blast radius.
Package modgraph provides the shared module-level graph and history helpers used by the modularity metrics: collapsing nodes/files to module units, aggregating churn, and computing SCC-condensed blast radius.
metrics/internal/result
Package result provides shared scoring helpers for the metrics layer: band names, confidence constants, mode constants, and the utility functions that map scores to bands, apply confidence caps, compute deltas, and build indeterminate (n/a) count results.
Package result provides shared scoring helpers for the metrics layer: band names, confidence constants, mode constants, and the utility functions that map scores to bands, apply confidence caps, compute deltas, and build indeterminate (n/a) count results.
metrics/intramodule
Package intramodule holds the intra-module quality metrics — signals about the internals of a module (function complexity, architecture-enforcement presence) rather than its coupling to other modules.
Package intramodule holds the intra-module quality metrics — signals about the internals of a module (function complexity, architecture-enforcement presence) rather than its coupling to other modules.
metrics/metricstest
Package metricstest provides shared test helpers for metric family packages.
Package metricstest provides shared test helpers for metric family packages.
metrics/modularity
Package modularity implements the modularity metrics (blast_radius, change_amplification, hidden_coupling, structural_weight, cohesion_lcom) and the functional_candidates metric.
Package modularity implements the modularity metrics (blast_radius, change_amplification, hidden_coupling, structural_weight, cohesion_lcom) and the functional_candidates metric.
metrics/risk
Package risk implements the risk_hub architecture metric, which surfaces modules whose cross-module symbol-surface breadth and explicit config volatility identify the highest-risk coupling hubs.
Package risk implements the risk_hub architecture metric, which surfaces modules whose cross-module symbol-surface breadth and explicit config volatility identify the highest-risk coupling hubs.
model/clone
Package clone holds the neutral value types for clone-detection results so that core-ring packages (e.g.
Package clone holds the neutral value types for clone-detection results so that core-ring packages (e.g.
model/coupling
Package coupling defines the Balanced Coupling classification types: Strength, Distance, Volatility, Explicitness, and the Classification index.
Package coupling defines the Balanced Coupling classification types: Strength, Distance, Volatility, Explicitness, and the Classification index.
model/diagnostic
Package diagnostic defines the top-level output contract: Diagnostic, MetricResult, Verdict, and the supporting summary and coverage types.
Package diagnostic defines the top-level output contract: Diagnostic, MetricResult, Verdict, and the supporting summary and coverage types.
model/finding
Package finding defines the Finding type and its sealed constructor.
Package finding defines the Finding type and its sealed constructor.
model/graph
Package graph defines the sealed graph types: Node, Edge, and the Build constructor that deduplicates, sorts, and freezes extractor output into a deterministic, immutable Graph.
Package graph defines the sealed graph types: Node, Edge, and the Build constructor that deduplicates, sorts, and freezes extractor output into a deterministic, immutable Graph.
model/pattern
Package pattern defines the shared structural match result type produced by pattern-search ports (PatternProvider) and consumed by rules as evidence.
Package pattern defines the shared structural match result type produced by pattern-search ports (PatternProvider) and consumed by rules as evidence.
model/signal
Package signal defines the carrier types that flow between signal producers (fitness scanner, git history, complexity tool) and the metrics layer: the per-family metric inputs (CommonInput, HistoryInput, SymbolInput, SizeInput, ComplexityInput, FitnessInput, DuplicationInput), the CollectedSignals bag the engine assembles and projects per family, and the RunSignals bundle the cmd layer produces.
Package signal defines the carrier types that flow between signal producers (fitness scanner, git history, complexity tool) and the metrics layer: the per-family metric inputs (CommonInput, HistoryInput, SymbolInput, SizeInput, ComplexityInput, FitnessInput, DuplicationInput), the CollectedSignals bag the engine assembles and projects per family, and the RunSignals bundle the cmd layer produces.
model/symbol
Package symbol defines the domain type for per-symbol metadata extracted from a SCIP (or equivalent) index.
Package symbol defines the domain type for per-symbol metadata extracted from a SCIP (or equivalent) index.
output/console
Package console renders a Diagnostic as clean, LLM-friendly plain text: labelled sections of aligned key/value lines, no box-drawing tables.
Package console renders a Diagnostic as clean, LLM-friendly plain text: labelled sections of aligned key/value lines, no box-drawing tables.
output/jsonout
Package jsonout implements the JSON renderer for diagnostic output.
Package jsonout implements the JSON renderer for diagnostic output.
output/markdown
Package markdown renders a Diagnostic as Balanced-Coupling-aligned Markdown: lint-message advisory format, BC vocabulary throughout, clearly-labeled "Supporting structural metrics (beyond Balanced Coupling)" and "Distance confidence" sections.
Package markdown renders a Diagnostic as Balanced-Coupling-aligned Markdown: lint-message advisory format, BC vocabulary throughout, clearly-labeled "Supporting structural metrics (beyond Balanced Coupling)" and "Distance confidence" sections.
output/sarif
Package sarif renders a Diagnostic as a SARIF 2.1.0 log (spec §12) so CI systems (GitHub code scanning et al.) can surface findings inline on PRs — the agent/CI half of the feedback loop.
Package sarif renders a Diagnostic as a SARIF 2.1.0 log (spec §12) so CI systems (GitHub code scanning et al.) can surface findings inline on PRs — the agent/CI half of the feedback loop.
output/scorecard
Package scorecard renders a Diagnostic as the architect skill's seven-dimension banded scorecard: an overall 0-100 value plus one block per dimension with its value, band, confidence, evidence references, and a one-line summary.
Package scorecard renders a Diagnostic as the architect skill's seven-dimension banded scorecard: an overall 0-100 value plus one block per dimension with its value, band, confidence, evidence references, and a one-line summary.
ownership
Package ownership resolves module owners from CODEOWNERS files or git-author history, producing a module→owner map that fills gaps in config-authored ownership metadata.
Package ownership resolves module owners from CODEOWNERS files or git-author history, producing a module→owner map that fills gaps in config-authored ownership metadata.
ports
Package ports defines the hexagonal port interfaces that separate the engine orchestrator from its adapters.
Package ports defines the hexagonal port interfaces that separate the engine orchestrator from its adapters.
rules
Package rules defines the Rule interface and the built-in rule implementations: forbidden_dependency, public_api_only, forbidden_layer_direction, internal_api_access, new_cross_module_dependency, and cycle.
Package rules defines the Rule interface and the built-in rule implementations: forbidden_dependency, public_api_only, forbidden_layer_direction, internal_api_access, new_cross_module_dependency, and cycle.
scope
Package scope resolves the analysis scope: the repo root, changed files (delta mode), and the mode (full vs delta) for a run.
Package scope resolves the analysis scope: the repo root, changed files (delta mode), and the mode (full vs delta) for a run.
score
Package score synthesises an already-computed Diagnostic into the architect skill's seven-dimension banded scorecard (boundary_integrity, coupling_balance, dependency_graph_health, cohesion_modularity, change_locality, architecture_fitness, analysis_confidence), each with a 0-100 value, a band, a confidence level, and at least one evidence reference.
Package score synthesises an already-computed Diagnostic into the architect skill's seven-dimension banded scorecard (boundary_integrity, coupling_balance, dependency_graph_health, cohesion_modularity, change_locality, architecture_fitness, analysis_confidence), each with a 0-100 value, a band, a confidence level, and at least one evidence reference.
staleness
Package staleness detects map-quality advisory findings: uncovered paths, dead module rules, and stale reviewed_at timestamps.
Package staleness detects map-quality advisory findings: uncovered paths, dead module rules, and stale reviewed_at timestamps.
status
Package status assigns status labels (new, baseline, excepted, expired_exception, fixed) to findings by comparing against a baseline and exception set.
Package status assigns status labels (new, baseline, excepted, expired_exception, fixed) to findings by comparing against a baseline and exception set.
toolrun
Package toolrun defines the Runner interface and its concrete implementation.
Package toolrun defines the Runner interface and its concrete implementation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL