obs: repo-health snapshot command aggregating existing analyzers into one JSON - #1471
Conversation
…into one JSON Adds `pnpm repo-health [--json]`, an offline command that aggregates the signals this repo already computes into one deterministic JSON snapshot by reusing each analyzer (depgraph, layering ratchets, coverage json-summary, size-report, fallow baselines, slow-test ratchet, bench/skillgym registries) rather than reimplementing any metric. Carries mandatory v1 provenance (schemaVersion, commit, per-analyzer content hashes, input provenance) so #1424 can persist history. Component metrics (instability/abstractness/main-sequence distance) are observatory-only. The only gating behaviour is the depgraph-vs-layering R6 consistency assertion, already wired into the Layering Guard job via scripts/depgraph/model.test.ts. Closes #1423 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Size Report
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. |
|
|
Correction to the previous comment: P1 — repo-health can label stale coverage/size data as the current commit. collectSnapshot reads the coverage summary and size report whenever they exist, while provenance records only their paths and sets the commit from current HEAD. After a source change without rerunning those producers, the snapshot pairs prior metrics with the new SHA; #1424 would persist a false commit-indexed history entry. Bind artifacts to the producing commit/config, or hash and explicitly mark them stale/unavailable; include relevant analyzer/config provenance and add a stale-artifact regression case. |
Coverage and size are artifacts repo-health reads but does not produce, so they carry no producing-commit stamp. Previously provenance recorded only their paths and the current HEAD, so a snapshot taken after a source edit without rerunning those producers would pair prior metrics with the new SHA — #1424 would persist a false commit-indexed history entry. Now each read artifact is bound via artifactProvenance() to its content hash (so history keys on the bytes the metrics came from, not a commit they may predate) and an explicit `stale` flag (true when a production source file is newer than the artifact). The coverage/size analyzer-config hashes are added to provenance.tool, and the human summary marks stale artifacts. Adds a pure stale-artifact regression test. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Re: the P1 on stale coverage/size vs. the current commit — fixed in 6e351df. Coverage and size are artifacts repo-health reads but doesn't produce, so they carry no producing-commit stamp. Provenance now treats them as unversioned inputs instead of implying they match
Absent artifacts still degrade to |
|
The stale-artifact fix still has a provenance false-negative. Please bind each artifact to the complete relevant producer input fingerprint/provenance, or conservatively report freshness as unknown/stale when that cannot be proven. Add a regression where a non- |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The stale check derived freshness only from listSourceFiles() (src/*.ts) and applied it to both coverage and size. Coverage also depends on tests and vitest config; size-report.mjs reads package.json and runs npm pack — so a change to a non-src producer input could leave an old artifact marked stale:false while provenance recorded the current HEAD, a commit the metrics predate. Each read artifact now declares its full producer input set (PRODUCER_INPUTS) and is flagged stale when ANY tracked input in that set is newer than the artifact (via git ls-files mtimes). Freshness that cannot be proven — an empty observable input set — is reported stale, never falsely fresh. The input set is recorded as provenance.inputs.*.producerInputs. Adds isArtifactStale() with a regression covering a non-src input newer than the artifact and the unprovable case. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Good catch — fixed in be01065. Freshness is no longer derived from Each read artifact now declares its full producer input set and is stale if ANY tracked input in that set is newer than the artifact (mtimes via
Freshness that can't be proven — an empty observable input set — is reported Regression: |
|
Re-reviewed exact head
Exact-head CI is green, but these code findings block readiness. |
… route git through runCmdSync Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Both fixed in P1 — freshness is now proven, not inferred. You're right that mtime can't prove producer provenance (copy/restore/CI-cache download resets it) and that a hand-written input list is never provably complete. So I removed both signals entirely — no more
Today neither producer stamps a commit, so both honestly report On the test: the unit test now drives the actual collector ( P2 — routed through format/lint/typecheck, fallow ( |
|
Re-reviewed exact head : both blockers are genuinely fixed. Artifact freshness no longer relies on incomplete input lists or transport-unsafe mtimes; unproven artifacts are conservatively , and the regression drives the full raw-bytes → stamped-commit → hash/status collector. Git subprocesses now route through . Exact-head checks are green; code quality and validation are clean, ready for human review. |
|
Correction to my previous comment: Re-reviewed exact head 10a4195. Both blockers are genuinely fixed. Artifact freshness no longer relies on incomplete input lists or transport-unsafe mtimes; unproven artifacts are conservatively marked unknown, and the regression drives the full raw-bytes to stamped-commit to hash/status collector. Git subprocesses now route through the shared execution helper. Exact-head checks are green; code quality and validation are clean. Ready for human review. |
Summary
Adds
pnpm repo-health [--json](#1423, Track C of #1412): one offline command that aggregates the signals this repo already computes across 24+ checks into a single deterministic JSON snapshot, so an agent can query repo state in one read instead of scraping job logs. It is data, not a dashboard — no rendering, no history (that is #1424), and the component metrics never gate.Runs from a clean checkout in ~1s with no network (measured 0.85s). Every metric is reused from an existing analyzer, never reimplemented.
Where each number comes from (reuse, not reimplementation)
depgraph(files, edges, cycles-by-kind,redundantEdges,backEdges,typeInversionEdges)scripts/depgraph/model.tsbuildGraph()(which itself reuses the layering edge model)layering(typeInversionBaseline/Total,typeCycleBaseline)scripts/layering/check.tsTYPE_INVERSION_BASELINE+TYPE_CYCLE_BASELINEcoveragecoverage/coverage-summary.json(vitestjson-summaryreporter)size.tmp/size-report.json(scripts/size-report.mjs)fallow(findings + suppressions)fallow-baselines/*.json+.fallowrc.jsonslowTest(budgets)scripts/vitest-slow-test-budgets.tsbench/skillgym(counts)help-conformance-cases.mjs/ the smoke-suite registrycoverageandsizeread the artifacts their own gates already emit and degrade to{ "available": false }(naming the producing command) on a fresh checkout, rather than recomputing them and blowing the 2-minute / no-network budget.Schema & provenance
Top level is
{ schemaVersion, provenance, metrics, consistency }. Field names are the stable delta contract #1424 will consume; any change bumpsschemaVersionand #1424 refuses to diff across versions without a migration note. Full schema documented inscripts/repo-health/README.md.Provenance is mandatory in v1 (the issue's 2026-07-27 amendment):
Stale-artifact safety (review P1). Coverage and size are artifacts repo-health reads but does not produce, so pairing them with the current HEAD would let a snapshot taken after an edit (without rerunning the producer) record prior metrics against the new SHA — a false history entry for #1424. Freshness is therefore proven only from a producing commit the artifact stamps in its own bytes — never
mtime(a copy/restore/CI-cache download resets it) nor an enumerated producer-input list (never provably complete):Today neither producer stamps a commit, so both honestly report
unknown; if a producer starts emittingcommit/producerCommitit is verified automatically. The bytes are still content-hashed (sha256) so #1424 keys history on the exact metrics read. Consumers must treatunknownandstalealike as not-current.Component metrics — observatory only
metrics.components.byZoneandmetrics.mainSequence.concreteHighFanIncarry per-zone instabilityI = Ce/(Ca+Ce), abstractnessA(first approximation: type-only share of afferent edges), and main-sequence distance|A+I-1|. These are observatory data only and must never become CI thresholds; they are extracted into their owncomponents.tsso that constraint stays visible.Acceptance spot-check passes — both surface as concrete + high fan-in:
R6 consistency — the only gating behaviour
The command fails (exit 1, prints the diff) if the graph it builds does not reproduce the layering gate's
TYPE_INVERSION_BASELINE, pair for pair (#1410). This is surfaced asconsistency.r6in the snapshot.CI cost
Zero new CI jobs or steps. The depgraph-vs-layering R6 assertion the issue asks to wire in is already run by the Layering Guard job via
scripts/depgraph/model.test.ts— this PR reuses that existing assertion rather than adding a duplicate.repo-healthitself is not added to the PR gate. The new tests run inside the existingtest:unitprojects: the pure model test inunit-core, and the subprocess CLI test insubprocess-stub(per the umbrella rule that subprocess-spawning tests never go inunit-core).Notable non-obvious changes
--productiongraph, exposing its config-onlydefaultexport. Rather than waive it, the budget constants are extracted into a data-onlyscripts/vitest-slow-test-budgets.tsthatrepo-healthimports directly, keeping the reporter out of the production graph and preserving a single source of truth.src/utils/exec.tsper the repo hard rule:run.ts's git calls userunCmdSync, notnode:child_process.Test plan
pnpm format:check && pnpm lint && pnpm typecheck— greenpnpm test:unit && pnpm check:layering && pnpm test:output-economy— greenpnpm check:fallow --base origin/mainandpnpm check:production-exports— no issuespnpm repo-health/--json/--outexercised end-to-end; runs offline in ~0.85s; artifact freshness verified through the real CLI for all three states (unknownfor an unstamped coverage-summary shape,freshfor a HEAD-stamped artifact,stalefor a mismatched stamp)Closes #1423
Link to Devin session: https://app.devin.ai/sessions/7b13d733a337410aa8227ce6fa1c4c10
Requested by: @thymikee