archfit

module
v1.5.1 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: Apache-2.0

README

archfit

CI Release Version GHCR Go Reference Go Report Card License

Does this change keep the architecture healthy? archfit answers — with a decision, not a number.

archfit is a one-command architecture-fitness CLI. It reads how your code is actually wired (from language analyzers like go list, dependency-cruiser, ast-grep, grimp), checks it against the architecture you declared in .archfit.yaml, and gives you a clear verdict: a decision, a CI gate, a banded scorecard, and — when an AI agent breaks a boundary — a structured repair task.

Built for AI agent and CI workflows: deterministic output, pipe-friendly, leads with what to do.

$ archfit

ARCHFIT RESULT

Decision   ACCEPTABLE WITH WATCH ITEMS
Gate       PASS  ·  0 blocking
Warnings   55 advisory
Score      43 / 100  mixed

Acceptable with watch items. Monitor flagged areas.

No blockers. Use this run for architecture-improvement planning,
not to stop development.

RECOMMENDATIONS

  MUST FIX
    none
  SHOULD FIX
    · bc/imbalanced_coupling — high fan-in into session state
  WATCH
    · lazy_cycle — lazy import SCC

WHY THE SCORE IS LOW

  coupling_balance  43/100  [mixed]
    304 warning edges, mostly functional + high volatility.

WHAT WOULD IMPROVE THE SCORE

  coupling_balance
    Reduce high-fan-in functional edges across module boundaries or introduce stable contracts.

TARGETS
  Current     43  mixed
  Near-term   61-80  serviceable
  Main goal   keep blocking findings at 0

Run it bare for a human-readable review; add --gate in CI for exit codes; add --json to pipe it anywhere. Progress streams to stderr, so archfit --json | jq stays clean.

Why

AI agents are great at local edits and bad at holding the whole system design in context. An agent can make a change that passes every test while it quietly imports across a forbidden boundary, bypasses a module's public API, shortcuts between layers, or grows a dependency cycle. Each one looks harmless; together they become the architecture, and every later fix costs more human context, more tokens, and more retries.

archfit puts an architecture-fitness check in that loop:

flowchart LR
    A[Agent edits files] --> C["archfit analyze --gate"]
    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;

A failed gate isn't a vague log line — it's a repair order the agent can act on:

{
  "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 analyze --gate -c .archfit.yaml --full"]
}

Goal, constraints, the files to touch, and the command that proves the fix.

Quick start

# install (or use the Docker image with all analyzers bundled: ghcr.io/alexei-led/archfit)
go install github.com/alexei-led/archfit/cmd/archfit@latest

archfit doctor                      # which analyzers are available
archfit config init --root .        # generate a starter .archfit.yaml
archfit                             # human review: the decision report above
archfit --gate --full               # CI gate: exit 0 clean / 1 violation / 2 warn / 3 error

Accept current known debt as a baseline so it doesn't mask new findings:

archfit baseline --full

Starter configs for common project shapes live in examples/. Full setup — Docker, CI, optional analyzers, platform packages — is in the guide.

What you get

  • One command, many outputs. archfit analyze → terminal report (default), --json, --sarif, --markdown, or --format scorecard. --gate turns on CI exit codes; without it the run is report-only.
  • A decision, not just a scoreHEALTHY / ACCEPTABLE WITH WATCH ITEMS / NEEDS ATTENTION / FAIL, with blocking-vs-advisory split, categorized recommendations, and evidence for why the score is low / what would improve it.
  • Deterministic gates for forbidden dependencies, public-API boundaries, layer direction, cycles, and configured thresholds. Same input → byte-identical JSON, safe for CI.
  • agent_tasks repair blocks so an AI agent gets the fix, not just the error.
  • A banded coupling_balance scorecard built on Balanced Coupling (integration strength × distance × volatility), reported as a 0–100 score — optionally gates the build via coupling.gate (band floor / max score drop).
  • Visible evidence quality — SCIP strength overlays, Rust deep-analysis coverage, distance-basis context, and dynamic connascence signals are reported separately so missing or report-only evidence is not mistaken for score input.
  • Honest coverage — a missing analyzer degrades the affected metric to n/a with the install step, never a false green.
  • Content-addressed fact cache — warm runs skip unchanged extractor subprocesses (typically 3–5× faster gates), byte-identical to a cold run; --no-cache forces a clean control run (details).
  • Off-gate LLM enrichment (analyze --llm, config enrich labels/abstained, config init --llm, config update --llm) that may only draft labels, propose review material, explain, and prioritize collected evidence — it never decides the gate.
  • Multi-language — Go, TypeScript/JavaScript, Python, Rust (details).

How it works

archfit separates facts, gates, and narration. Language adapters collect dependency facts; a deterministic, LLM-free core classifies them, runs the gates, computes metrics, and synthesizes the decision; optional LLM features sit strictly off to the side.

flowchart TB
    tools["External analyzers<br/>go list · dependency-cruiser · ast-grep · grimp"]
    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] --> DE[decision]
    end
    core --> OUT["text · JSON · SARIF · Markdown<br/>agent_tasks · scorecard"]
    OUT -. off-gate · advisory only .-> LLM["analyze --llm · config enrich · config init --llm"]
    classDef side fill:#f3f0ff,stroke:#7048e8,color:#000;
    class LLM side;
    style core fill:#e7f5ff,stroke:#1971c2,color:#000;

archfit doesn't replace architecture review — it makes repeatable evidence cheap to collect and safe to run in CI. It sits one level above single-language boundary linters (dependency-cruiser, import-linter, ArchUnit): they supply facts for one ecosystem; archfit turns facts across languages into one verdict, a Balanced-Coupling risk read, score movement, and agent repair tasks.

Documentation

License

Apache-2.0. See LICENSE.

Directories

Path Synopsis
cmd
archfit command
enrich: the off-gate LLM workflow that drafts coupling-strength label refinements for human review.
enrich: the off-gate LLM workflow that drafts coupling-strength label refinements for human review.
calibrate command
Package main is a standalone scorer-calibration tool.
Package main is a standalone scorer-calibration tool.
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.
configschema
Package configschema generates a JSON Schema for archfit's .archfit.yaml configuration file.
Package configschema generates a JSON Schema for archfit's .archfit.yaml configuration file.
decision
Package decision converts an already-computed Diagnostic and Scorecard into a human-decision view-model (Report) that renderers format for display.
Package decision converts an already-computed Diagnostic and Scorecard into a human-decision view-model (Report) that renderers format for display.
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/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/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 FileLOC facts (used by SCIP file-facts and the coverage metric).
Package loc counts source-file line counts for FileLOC facts (used by SCIP file-facts and the coverage metric).
extract/manifest
Package manifest detects locally-declared deprecation and retraction markers in checked-in manifest files: go.mod retract directives and package.json top-level "deprecated" fields.
Package manifest detects locally-declared deprecation and retraction markers in checked-in manifest files: go.mod retract directives and package.json top-level "deprecated" fields.
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.
factcache
Package factcache is the extractor-fact cache adapter: content-addressed JSON blobs under .archfit-cache/facts/<analyzer>/<key>.json that let a warm run skip expensive out-of-process extractor work (docs/design/fact-cache.md).
Package factcache is the extractor-fact cache adapter: content-addressed JSON blobs under .archfit-cache/facts/<analyzer>/<key>.json that let a warm run skip expensive out-of-process extractor work (docs/design/fact-cache.md).
facts
Package facts assembles per-module structural facts from already-collected data (symbol graph, file LOC).
Package facts assembles per-module structural facts from already-collected data (symbol graph, file LOC).
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 boundary-health metrics: encapsulation, unbalanced_edge, cycle, and coverage.
Package boundary implements the boundary-health metrics: encapsulation, unbalanced_edge, cycle, and coverage.
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/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.
Package modularity implements the modularity metrics.
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/fileclass
Package fileclass defines the FileClass type used to categorise source files as Production, Test, Generated, or Vendor.
Package fileclass defines the FileClass type used to categorise source files as Production, Test, Generated, or Vendor.
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/module
Package module holds the shared module vocabulary: module definitions, path-to-module resolution, and architectural roles.
Package module holds the shared module vocabulary: module definitions, path-to-module resolution, and architectural roles.
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 and the metrics layer: the per-family metric input (CommonInput), 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 and the metrics layer: the per-family metric input (CommonInput), 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 an archfit decision.Report as terminal-native plain text: a decision-led summary (decision band, gate/advisory counts, score), categorized recommendations, per-dimension "why low / what moves it", an optional delta, and targets.
Package console renders an archfit decision.Report as terminal-native plain text: a decision-led summary (decision band, gate/advisory counts, score), categorized recommendations, per-dimension "why low / what moves it", an optional delta, and targets.
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 turns a Diagnostic into the banded scorecard.
Package score turns a Diagnostic into the banded scorecard.
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, waived, expired_waiver, fixed) to findings by comparing against a baseline and exception set.
Package status assigns status labels (new, baseline, waived, expired_waiver, fixed) to findings by comparing against a baseline and exception set.
syntax
Package syntax classifies each source file into a FileClass (Production/Test/Generated/Vendor) and exposes file-level helpers (LookupFileClass, IsTestFile) used by the LOC walk and by metrics that filter on Production files.
Package syntax classifies each source file into a FileClass (Production/Test/Generated/Vendor) and exposes file-level helpers (LookupFileClass, IsTestFile) used by the LOC walk and by metrics that filter on Production files.
toolrun
Package toolrun defines the Runner interface and its concrete implementation.
Package toolrun defines the Runner interface and its concrete implementation.
view
Package view holds the stage-contract types: narrow, data-only views of the archfit configuration that each pipeline stage consumes.
Package view holds the stage-contract types: narrow, data-only views of the archfit configuration that each pipeline stage consumes.
scripts
eval/coverage command
coverage generates a gap-closure coverage table mapping each frozen inventory finding to one of: surfaced | llm-routed-by-design | agree | not-surfaced.
coverage generates a gap-closure coverage table mapping each frozen inventory finding to one of: surfaced | llm-routed-by-design | agree | not-surfaced.

Jump to

Keyboard shortcuts

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