Skip to main content

Project Health

An open source project health checker for teams working with AI coding tools.

AI can speed up code production. Project Health asks whether the resulting project is still easy to read, change, test, and navigate—and whether its health is improving or deteriorating.

The CLI produces a one-page report with evidence, changes since a baseline, and up to three prioritized improvements. It runs local analysis without model calls and produces bounded evidence packets for AI tools.

Status: installable Python pilot with Graphify as the default structural graph provider. Local analysis, Git baseline comparison, console/Markdown reports, and JSON output are implemented. Maintainer validation remains pending.

Version 0.3.0 adds potential change-impact previews, compact agent briefs, declarative architecture boundaries, co-change investigation candidates, and accepted findings with review dates.

Version 0.4.0 adds default checks for god-module/function/class candidates and entangled file groups from Graphify evidence.

Version 0.5.0 adds supporting-metric regressions, conservative rename continuity, maintenance hotspots, changed-line/branch coverage, and local cognitive complexity.

Install and run

Requires Python 3.10+ and Git for revision/history analysis.

After the first PyPI release, install the CLI and its default Graphify provider together:

uv tool install 'project-health[graphify]' --python 3.12
project-health check .

Alternatively, use python -m pip install 'project-health[graphify]' in a Python environment. The base project-health package supports --graph-provider ast; the default Graphify provider requires the extra or a separately installed graphify executable. The tested Graphify dependency is pinned to 0.9.74.

For installation from this checkout:

uv tool install graphifyy==0.9.74 --python 3.12
uv tool install --editable . --python 3.12
project-health check .
project-health check /path/to/repository --baseline main --json-output /tmp/health.json
project-health check . --baseline HEAD~1 --format json > /tmp/health.json
project-health check . --baseline HEAD~1 --format markdown --output /tmp/health.md

The default prints a console page bounded to 45 lines at 96 columns, with up to three grouped actions. JSON includes all findings, source metrics, import edges, provenance, history and evidence gaps. Place output outside the scanned tree to avoid including it in a later run. Without --baseline, the report describes a snapshot and makes no deterioration claim.

An editable installation uses this checkout's code; keep the repository in place. For a regular installation, use uv tool install . --python 3.12. To uninstall, use uv tool uninstall project-health.

What the pilot measures

The pilot uses Radon 6.0.1 for function complexity and per-file Maintainability Index, exact Python AST function bodies for duplication, and Graphify for structural evidence. It also collects Git churn and co-change evidence. Centrality alone is not a defect. Radon's programmatic API supplies the quality metrics; no analyzer score is combined into an overall numerical health score.

Graphify runs automatically using extract --code-only --no-cluster --max-workers 1 on a temporary copy of each scoped Python snapshot. Model credentials and provider settings are not passed to the subprocess; automatic skill refresh is disabled. No graph files or hooks are written to your repository. The console identifies Graphify's version, node/edge counts, relationship counts and baseline changes. JSON preserves nodes, edges, calls, imports, inheritance, inferred/extracted tags, and hubs.

Only extracted local imports contribute to cycle findings. Extracted imports, calls and inheritance contribute to file dependency degree used in prioritization; inferred edges remain visible evidence without raising that priority. Static relationships require review in project context. Dynamic behavior, semantic duplication, stale documentation and behavioral-test loss are not detected. Selected exported SQL schemas have separate checks; other source languages are listed as unassessed. Clustering/community labels are not requested in this integration.

Graphify must be on PATH. A missing/failed extraction produces an explicit analysis error rather than silently switching providers. Use --graphify-timeout 180 to adjust the per-snapshot timeout, or explicitly select the earlier lightweight import graph with --graph-provider ast. The tested Graphify version is 0.9.74; its installed version is recorded in every graph report.

God nodes and graph entanglement

These checks run automatically with Graphify, using extracted local dependencies and Python workload metrics:

Candidate Default evidence required
God module At least 5 incoming and 5 outgoing file dependencies, plus a function complexity of at least 15 or at least 300 module lines.
God function or method At least 5 distinct local call targets, plus complexity of at least 15 or a span of at least 80 lines.
God class At least 10 direct methods, plus summed method complexity of at least 40 or a span of at least 250 lines.
Entangled file group At least 3 mutually reachable files with internal directed dependency density of at least 0.5.

Entanglement uses imports, calls and inheritance together. Density is unique internal directed file dependencies divided by n * (n - 1). Containment, inferred links, unresolved endpoints and file self-edges do not inflate connectivity. Repeated calls to the same target count once. A heavily reused utility or a small orchestration hub alone does not create a god-node finding. These are review candidates with low confidence, not proof that a central component should be split; source spans include comments and docstrings.

project-health check . --baseline main --json-output /tmp/health.json
project-health check . --god-node-min-degree 8 --god-node-min-complexity 20 \
  --entanglement-min-size 4 --entanglement-min-density 0.6

The same thresholds apply to both snapshots. god-node and graph-entanglement findings participate in comparisons, active priorities, accepted-findings review and --fail-on-new. The console shows candidate counts even when other findings lead the three priorities. JSON includes supporting graph-edge indices, workload/connectivity metrics and unflagged mutually dependent groups below the density threshold. With the AST-only provider these broader checks are explicitly unavailable.

Working scans include tracked and Git-unignored untracked files. Committed scans read Git blobs directly, without switching branches or executing project code. Standalone folders work too, with default directory exclusions. Repeat --exclude to add component/path globs:

project-health check . --exclude benchmarks --exclude 'generated/*'
project-health check . --baseline v1.0 --revision HEAD
project-health check . --complexity-threshold 15 --history-commits 50

Function complexity findings exceed the configured threshold (default 10); duplication requires identical AST bodies spanning at least 10 lines. Ranking favors new/worsened findings with explicit history, dependencies and verified line/branch coverage signals. Weights are heuristics, not calibrated defect probabilities. Ambiguous renames and body edits may appear as resolved/new findings.

Maintenance additions

Supporting metrics: comparisons inspect each numeric finding measurement, including size, complexity, connectivity and density. A god-function whose call count stays unchanged can still worsen when its complexity increases. JSON records metric_changes, absolute tolerances and metric_regressions; any regression makes the finding worsened, even if another measurement improves. Categories and booleans do not participate. These measurements are interpreted as higher-is-worse; the per-file Maintainability Index is not treated this way. Configure allowed absolute changes in .project-health.toml:

schema_version = 1
[metric_tolerances]
complexity = 2
lines = 10

Unspecified tolerances are zero; value controls the primary measurement. Tolerances affect baseline trend decisions, while acceptance uses explicit maximum allowances. Add accepted_metrics = { complexity = 18, lines = 100 } to an accepted-finding table to review those supporting measurements. Exceeding an allowance or losing its measurement reopens the decision even without a baseline. Existing acceptance entries continue to review only the primary value unless supporting allowances are supplied.

Rename continuity: with an explicit baseline, Git compares isolated trees of scoped Python sources at an 80% similarity threshold. Only unambiguous same qualified symbols or exactly mapped group memberships inherit baseline finding IDs. Current locations remain current; JSON records native and canonical IDs plus mapping evidence. Copies, renamed symbols, duplicate declarations and competing similar files are not silently matched. Each side is capped at 200 candidates and combined screening input at 20 MB. No repository index, checkout or objects are changed. This is inferred snapshot continuity, not proof of a recorded rename. Standalone scans retain native IDs; pre-rename churn is not aggregated.

Maintenance hotspots: a separate investigation list includes files changed in at least three observed commits with maximum function complexity at least five, even when no warning threshold is crossed. Order is commit count, then maximum complexity, then changed lines, with raw history, dependency and coverage context. Hotspots are not defect findings or CI failures. Shallow history is explicit. Adjust with --hotspot-min-commits, --hotspot-min-complexity and --hotspot-cap (default 10); the console shows the leading candidate.

Changed coverage: baseline comparisons use supplied revision-matched coverage.py JSON to report added/replaced executable lines and branch outcomes originating on changed lines. Deleted lines are recorded separately. Coverage exclusions follow the artifact; unknown files and unmeasured branches remain explicit. An unchanged matched rename introduces no added lines. Changes to a branch destination alone are not classified as changes to its source decision. Changed coverage is evidence for review, not a separate CI failure rule, and tests are never executed by the checker.

Cognitive complexity: all Python functions receive a local AST understandability score and contributing lines. Findings exceed --cognitive-threshold (default 15) and join normal comparison, review, priority and CI policies. This versioned heuristic is inspired by Sonar's definition, but does not reproduce SonarPython scores: nested definitions are independent, recursion is excluded, comprehensions count nested flow, match counts once with guards, and loop else counts as an alternative. JSON records the algorithm and deviations; a score is not proof of readability.

project-health check . --baseline main --json-output /tmp/health.json
project-health check . --baseline main --revision HEAD --coverage /tmp/coverage.json \
  --artifact-revision HEAD --json-output /tmp/health.json
project-health check . --cognitive-threshold 20 --hotspot-min-commits 5

Code churn rate

Version 0.6.0 reports committed Python code churn automatically in console, full JSON and optional agent maintenance context. Churn is added plus deleted lines, not net growth. Two explicit rates are available:

  • lines_per_commit: changed lines divided by all observed repository commits in the selected history window.
  • churn_percent: 100 times changed lines divided by analyzed Python snapshot lines. Repeated edits and creation commits can make this exceed 100%.

Use --history-commits 50 to choose the window (default 100). Files, findings and hotspots include per-file rates with the same repository-commit denominator. Aggregate churn includes scoped historical Python paths that were deleted; their individual size rate is unavailable. Sizes include comments and blank lines. Working edits affect snapshot size but are not counted as committed churn. Historical renames are not followed; moves may count as deletion plus addition. Shallow history is partial, and missing history or binary line evidence leaves rates unavailable.

Baseline scans calculate separate windows ending at each selected revision, with numeric rate deltas in JSON. These describe activity changes, not deterioration or a CI failure. Absolute commit windows are not calendar-time rates, and high churn alone does not prove poor maintainability.

project-health check . --history-commits 50 --json-output /tmp/health.json

Database schema practices

Version 0.7.0 inspects exported SQL schemas locally, without connecting to a database or executing SQL. It discovers schema.sql and *.schema.sql automatically. Select differently named or split exports with repeatable --db-schema repository-relative paths. Use --db-dialect postgres (default) or --db-dialect sqlite; arbitrary ORM models and migration histories are not inspected.

project-health check . --db-schema db/export.sql --db-dialect postgres \
  --baseline main --json-output /tmp/health.json
project-health check . --db-schema db/schema.sql --db-schema db/indexes.sql \
  --db-dialect sqlite
Finding Evidence and interpretation
db-missing-primary-key No declared primary key on a parsed table. A low-confidence row-identity review candidate; staging/log tables can be intentional exceptions.
db-unindexed-foreign-key No declared primary/unique key or observed full plain-column B-tree index covering the foreign-key columns as a leading group. A low-confidence performance review candidate; decide using workload and query plans. Composite key order within the leading equality group may differ.
db-set-null-not-null ON DELETE/UPDATE SET NULL targets explicitly required columns, or PostgreSQL primary-key columns. Review conflicting referential actions. SQLite primary keys alone are not assumed universally non-null.

Rules follow PostgreSQL constraint guidance and SQLite foreign-key/index guidance. Missing business constraints, nullable foreign keys, omitted deletion policies and naming conventions are not automatically defects. Findings include dialect, source line, table/column evidence and a supporting documentation URL.

Supported input is one final schema across selected files: CREATE TABLE, CREATE INDEX and ALTER TABLE ADD CONSTRAINT. Primary/unique constraints establish implicit index coverage. Partial, expression and non-B-tree indexes leave that table's index check unknown and explicitly skipped; they do not prove coverage or a defect. Unsupported mutations, duplicate table definitions or malformed input suppress schema findings and produce analysis errors (exit 2). Data statements are ignored and never executed. This is a bounded declaration review, not full database validation, migration replay or a query optimizer.

Schema findings participate in baseline comparisons, accepted-finding review, grouped priorities, JSON/agent briefs and --fail-on-new. SQL-only projects can produce a supported report. The console always identifies schema status/dialect and table/finding counts; no detected export means unavailable schema evidence, not a healthy database. SQL inputs respect Git snapshots, exclusions, symlink rejection, UTF-8 decoding and the existing 2 MB per-file bound. Graphify remains the Python graph provider.

Existing validation results

Supply existing coverage.py JSON and JUnit XML; the checker never runs project tests:

project-health check . --revision HEAD --coverage /tmp/coverage.json \
  --junit /tmp/junit.xml --artifact-revision HEAD --json-output /tmp/health.json

--artifact-revision asserts which commit produced the artifacts. It cannot independently prove their origin. Artifacts only influence ranking when that commit matches the scanned revision, or a clean working tree at that commit. Missing, mismatched and invalid artifacts remain explicit. Coverage describes finding line ranges, not behavioral correctness.

Agents and CI

Use --format json for machine-readable stdout or --json-output alongside the console report. See the JSON contract. A successful scan exits 0 even when findings exist; --fail-on-new exits 1 for active new/worsened findings or reopened review decisions. Invalid revisions, unreadable/malformed inputs and incomplete source/schema analysis exit 2. No supported Python files or SQL tables yields an insufficient-evidence report rather than a healthy verdict.

For focused agent context, export up to three actions with bounded source snippets and evidence:

project-health check . --baseline main --format agent-json --max-context-bytes 12000

The limit covers serialized UTF-8 bytes, including JSON escaping; mandatory evidence that cannot fit produces an explicit input error. Committed snippets match selected Git blobs, and working snippets require fresh matching hashes. This does not execute verification steps or send source to a model.

Impact and co-change investigations

project-health check . --baseline main --impact --impact-depth 2 --impact-cap 100
project-health check . --impact-file src/validation.py --json-output /tmp/impact.json

Impact traverses extracted Graphify dependencies in reverse to identify potential callers/dependents. It includes evidence paths, snapshot provenance and truncation limits in JSON. Deleted-file seeds use the baseline graph. Without a baseline or explicit seed it reports unavailable evidence. Inferred links and containment do not contribute, and reachability is not proof of breakage.

Co-change investigations run when at least ten history commits are available. The initial thresholds are three shared commits and a Jaccard ratio of at least 0.6: shared commits divided by the number of commits touching either file. Candidates must have extracted endpoint coverage and lack a direct extracted import/call/inheritance dependency in either direction. Use --coupling-min-shared, --coupling-min-ratio and --coupling-min-history to adjust thresholds.

Candidates are investigation aids rather than defect findings or CI failures. JSON records counts, ratios, shallow-history warnings, skipped endpoints, bulk-commit omissions and pair truncation. Renames are currently treated as path changes.

Architecture rules and reviewed debt

Add .project-health.toml at the repository root, or select another file with --config. Relative overrides resolve from the repository root. The same selected policy applies to baseline and current source; its hash is recorded.

schema_version = 1

[[boundaries]]
id = "domain-does-not-import-api"
from = ["src/domain/**"]
deny = ["src/api/**"]
reason = "Domain logic must remain independent of the API layer."

[[accepted_findings]]
finding_id = "copy-the-exact-id-from-your-json-report"
reason = "Shared implementation retained until the compatibility release."
review_on = "2026-12-01"
accepted_value = 19

Boundary rules inspect only extracted local Graphify imports. Globs are case-sensitive POSIX paths: *, ? and character classes match within one segment; a whole ** segment spans zero or more directories. Rule/file-pair findings compare across revisions and participate in active priorities and CI decisions. Missing/partial graph evidence remains explicit.

Acceptance requires a current canonical finding ID, reason, review date and positive observed value, with optional accepted_metrics allowances. Accepted findings remain in full JSON but leave active priorities. They reopen on the review date or when a reviewed value exceeds its allowance, including without a source baseline. Rename continuity preserves accepted IDs only with an explicit baseline and unambiguous evidence; unmatched stale IDs remain visible. Acceptance does not hide analysis errors or imply that reviewed debt is healthy.

project-health check . --config .project-health.toml --as-of 2026-11-30 --fail-on-new

Use --as-of for reproducible review decisions; otherwise the checker uses today's local date. Review the example's ID, value and date before using it.

project-health:
  image: python:3.12
  variables:
    GIT_DEPTH: "0"
  script:
    - pip install . graphifyy==0.9.74
    - project-health check . --baseline "$CI_MERGE_REQUEST_DIFF_BASE_SHA" --format markdown --output health.md --json-output health.json --fail-on-new
  artifacts:
    when: always
    paths: [health.md, health.json]
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

The CI example installs this project from its checkout. Installation may access package indexes. Analysis makes no model/network calls, consumes zero model tokens, and executes no project commands.

Run implementation checks with uv run python -m unittest discover -s tests -v after installing Graphify. The tests exercise live Graphify calls/inheritance, extraction isolation, inferred-edge handling, regression/repair, cycles, centrality controls, repeatability, artifact mismatches/errors, output formats and exit codes. Maintainer report usefulness remains unvalidated.

Proposal and research

Guiding principles

  • Measure outcomes regardless of who or what wrote the code.
  • Compare the project with its own history.
  • Make every finding traceable to evidence.
  • Keep analysis cheap, reproducible, and useful without an LLM.
  • Show missing evidence instead of presenting false confidence.

Contributing

Feedback on useful health signals, misleading metrics, and real repositories for evaluation is welcome through GitLab issues and merge requests. Implementation proposals should explain their evidence, limitations, and analysis cost.

License

MIT.

Metadata

Release files for project-health 0.7.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 project-health 0.7.0
File Size Uploaded
project_health-0.7.0.tar.gz 87.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for project-health 0.7.0
File Interpreter ABI Platform
project_health-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 152.9 kB

Release files / project_health-0.7.0.tar.gz

Download URL project_health-0.7.0.tar.gz
Size 87.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1e31dcb1ec8388416e9d4ab9084b32fdfacd60ff67e4e33caed957e8362e20b8
BLAKE2b-256 checksum
How to use checksums
4d33b217777cfb45401f4098385a331f9447a0889149202eff02fb0ea5014bfc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / project_health-0.7.0-py3-none-any.whl

Download URL project_health-0.7.0-py3-none-any.whl
Size 65.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6a9d388ae78929276ff66067355c9952f6d7128655d86b5ad867c55ba13bbdd0
BLAKE2b-256 checksum
How to use checksums
74d808b0b2d0bda842017015a7af1ee20fddc0095fba10b5c3d7f9524dea4cb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.7.0 This release

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