Skip to content

v0.4.7

Choose a tag to compare

@github-actions github-actions released this 25 Aug 06:19
· 21 commits to main since this release
21c2358
Layers conformance (#256)

* layers: stop three false-positive classes in pattern violations

- di/config packages become Neutral: classified, but in no dependency
  direction, since every layer both uses them and is used by them.
- android-clean orders data innermost per Android's own guidance; ios-clean
  keeps the opposite order on purpose — the two disagree about which way
  domain and data depend.
- the Rails autoload rule no longer claims app/ children Rails does not
  autoload, which is where front-end apps live.

* layers: confidence is graded/scanned, floored at 0.20 on the winner

Drops the layerCoverage term, whose denominator was the layer table rather than
the repository. Graded counts classified modules in non-neutral layers; both
counts are over distinct module names, since pattern.Modules is keyed by name.

The floor lives in thickEnough() on bestPattern()'s result, not in
detectPatterns(): applied at admission it dropped a thin framework-gated match
and let the ungated hexagonal pattern take the repo instead.

* layers: score each taxonomy over its language cohort

patternDef.languages gated on the repo's dominant language; it is now appliesTo,
the languages a taxonomy may classify, and cohort() restricts the modules it is
scored over. bestPattern becomes selectPatterns: strongest first, skipped when
its cohort overlaps one already claimed, so an ungated taxonomy yields instead of
competing everywhere. A floored winner still claims its languages.

Violation titles carry the taxonomy name — two cohorts can produce the same layer
pair. This changes finding identity, so pinned baselines churn once.

* layers: add php-layered, drop core from the hexagonal domain patterns

`core` named a container, not a layer: a platform keeping its product under
src/Core/ had 1049 of 1491 classified modules read as domain on that segment
alone, and 339 findings followed. Same argument the Angular taxonomy already
makes for the same word.

php-layered is gated on the language, not a framework — there is no single PHP
framework — and built from vocabulary that recurs across unrelated PHP trees.
Its listener/subscriber/provider/exception tier is Neutral.

countSignatureLayers stays presence-based. Requiring two modules per signature
layer drops one wrong statement and one right one, and neither an absolute nor a
relative threshold separates them; the wrong one is recorded in the corpus.

detectViolations still reads only `imports`, now with the measurement that says
why: 3065 module pairs reachable through calls/instantiates/injects/implements
across four repositories yield 5 wrong-direction edges.

* layers: add nuxt and sveltekit, record four ecosystems measured and left out

Both taxonomies come from the frameworks' prescribed directory structures, not
from what the corpus repositories share, so neither takes a signature gate — the
framework fact already settles what the repository is. Nuxt's server/ is the
Nitro backend, classified and unordered.

Python, Rust, Swift and the routers/services/models Go layout are documented
above patternDefs with what was measured: three of them have no vocabulary that
holds across unrelated repositories, and the fourth would re-rank pkg/services
everywhere on the evidence of one example.

* llmcontext: reserve the architecture sections, derive the feature guide

On two corpus repositories the repository map spent all 64,000 characters before
the architecture section was reached, so the file rendered a module list and no
statement at all. Architecture Pattern and the guide are now reserved, and the
layer mapping is capped per pattern.

layers.GuideFor exports the taxonomy order with an example module, replacing a
switch that had prose for a few taxonomies by name and generic advice for every
other repository. Layers of one level render together — rails-mvc puts a dozen
directories on its domain tier deliberately, and numbering them asserted a
sequence that does not exist. No recognised order now emits no section.

* llmcontext: bound the repository map and cap any one section's share

The map was one unbounded row per module sorted by name, so a large repository
spent the whole budget on an alphabetical census and every later section was
omitted. Above maxRepoMapRows it summarises by area with the largest modules;
entry points group by kind, listing mains in full and sampling populations.

The share cap is the real fix: bounding sections one at a time moved the problem
down the list twice. A section over its share is truncated, not dropped.

Repo labels are prefixed to area names only when the snapshot holds more than
one repository.

* llmcontext: summarise the remaining per-item sections

Routes group by path prefix, storage by kind, dependency edges by out-degree —
which no other section reports, since Critical Modules ranks fan-in. Risk Zones
is capped and ordered by confidence; it was emitted in explainer order, so a
truncation kept whichever findings came first rather than the likeliest.

With the map and entry points from the previous commit, a large monolith's digest
goes from 64,000 characters holding two sections to 28,000 holding ten, with no
truncation left to do.

* explain: report every cohort's architecture, not just the strongest

Report kept one Architecture string, so a repository with two layer orders in
force reported one and dropped the other. Architectures carries all of them; the
scalar stays as the strongest so the JSON shape does not move.

Verified against a pre-change baseline: check --fail-on=layers exits 0 across the
violation-title change, which lands as incidental shifts rather than new findings.

* layers: record .NET as measured and left out

Five .NET repositories share no order beyond the clean-architecture layout
already covered. The one that looks layered is the trap: a media server's
MediaBrowser.Controller is its domain abstractions assembly, so matching
"Controller" across dotted project names — the only way .NET matching works —
would read fifty modules of interfaces as a delivery layer.

* layers: prefer unordered layers when classifying a module

Matching is position-blind, so a wiring directory nested inside an ordered one
took the enclosing layer: core/data/…/data/di classified as data, …/service/config
as service. Neutral-first is the fail-safe direction — misclassifying into a
neutral layer silences a verdict, out of one invents a verdict about a directory
every layer references by design.

* Updating docs

* facts: add Insight.Metrics, and build the feature guide from it

Explainers state their numbers in prose and every caller parses them back out:
pkg/explain regexes ints from titles, bench-arch regexed "N of M modules
classified" until the cohort language was added to that sentence. Metrics is the
machine-readable mirror, shaped like Fact.Props and read through accessors that
tolerate the float64 a round-trip leaves.

It mirrors rather than replaces: internal/diff enumerates Title, Confidence,
Description and Evidence, so a number living only in Metrics would stop being
visible to the gate. A test pins both halves of that.

layers publishes its denominators, conformance counts and layer order for
recognised AND declared patterns, which is what lets renderFeatureGuide serve a
repository that states its own architecture — enola's own tree included.
GuideFor is removed.