Back to Langfuse

structure:stats — the project-structure RFC dashboard

web/scripts/structure/README.md

4.6.05.4 KB
Original Source

structure:stats — the project-structure RFC dashboard

Counts violations of the web project-structure RFC (meta LFE-14748) per rule, so migration progress is one visible number.

sh
pnpm structure:stats                          # per-rule counts (+ Δ vs baseline)
pnpm structure:stats --rule 8                 # list rule 8's offending imports
pnpm structure:stats --scope src/features/traces   # counts for one subtree
pnpm structure:stats --diff                   # what got fixed / added vs baseline
pnpm structure:stats --baseline               # re-snapshot .structure-baseline.json
pnpm structure:stats --next [n]               # top n ranked work items (default 6)
pnpm structure:stats --json                   # machine-readable (also with --next)

A full run takes ~3s (dependency-cruiser graph) + ~1s (TS-parse census). .structure-baseline.json is committed; regenerate it deliberately after a fix batch so the Δ column and --diff track real progress.

What to fix next

--next turns the violation lists into ranked work items, each sized for one small PR: every violation is attributed to the path where its fix lands (the file to split, the folder to move, the feature that needs an index.ts), subjects roll up to a directory when one rule dominates the subtree, and a greedy pass picks the highest-leverage item, consumes its violations, and rescores. Leverage = violations cleared × rule weight (RULE_WEIGHTS in next.mjs — runtime hazards outrank naming nits). The intended loop: --next --scope <area> → fix item 1 as its own PR → re-run.

Rule → mechanism

RuleWhatCounted by
1–4component/hook/fn/store/context file shape + namingcensus (TS parse)
5kind folders closed listcensus (dir walk)
6single-feature files live in the featuregraph (used-in inversion)
7no importing another component's internalsgraph + .dependency-cruiser.js
8cross-feature imports via feature index.tsgraph + .dependency-cruiser.js
9index.ts only at feature roots, re-exports onlycensus
10no client → server/ (types excepted)graph + .dependency-cruiser.js
11no runtime import cyclesgraph + .dependency-cruiser.js
12src/pages files import only a Page componentgraph + .dependency-cruiser.js
13components/ui frozencensus (file count, baseline ratchets adds)
14, 15design-system purity; git-mv movesreview / process — not counted
16ESLint ignores at file level onlycensus (line-level disables)
17baseline only shrinksthis baseline + --diff
18fn/hook tests colocated flatcensus
19only tests import __tests__graph + .dependency-cruiser.js
20no unused exportsgraph (file-level orphans; symbol-level needs a knip config — follow-up)

.dependency-cruiser.js carries the import rules as CI-ready warnings; the detectors here are the exact reference implementation (the config's regex approximations under-count some nested-component cases — see its header).

Calibration notes (as of the reworked traces feature, #15784)

  • Component boundary (rules 7/9) = any PascalCase directory; its public entry is <Name>.tsx (index files are tolerated by rule 7 so rule 9 flags each exactly once). Lowercase dirs (components/ui, components/table) are legacy containers, not boundaries.
  • Context modules: FooContext.tsx exporting FooContext + FooProvider + useFoo* counts as one unit; anything beyond flags rule 3. The RFC has no explicit contexts pattern yet — policy gap, see the audit in LFE-14781.
  • Cycles that a type-only edge breaks are not runtime hazards; they are reported as a survey metric, not rule 11.
  • Server code placed outside server/ (e.g. *Router.ts beside components) surfaces as rule-10 hits of its imports; moving it into server/ clears them. server/ internals themselves are not structured by the RFC (rules 5/9 skip below server/).
  • Rule 6/20 caveat: string-referenced modules (worker URLs, route strings) are invisible to the graph; src/workers, scripts/, Next entries are excluded from rule 20.