@ecoma-io/archkeep 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +262 -0
- package/cli.mjs +2792 -0
- package/index.mjs +85 -0
- package/lsp.mjs +81 -0
- package/nx.mjs +24 -0
- package/package.json +81 -0
- package/presets/clean-architecture.json +78 -0
- package/presets/ddd-bounded-contexts.json +88 -0
- package/presets/hexagonal.json +68 -0
- package/presets/layered.json +92 -0
- package/presets/modular-monolith.json +85 -0
- package/presets/vertical-slice.json +68 -0
- package/src/analysis/analyze.mjs +218 -0
- package/src/analysis/contract.md +259 -0
- package/src/analysis/go.mjs +414 -0
- package/src/analysis/manifest-util.mjs +68 -0
- package/src/analysis/python.mjs +1266 -0
- package/src/analysis/registry.mjs +74 -0
- package/src/analysis/rust.mjs +674 -0
- package/src/analysis/source-util.mjs +230 -0
- package/src/analysis/typescript.mjs +1034 -0
- package/src/analysis/vue.mjs +156 -0
- package/src/architecture-intent/intent-fingerprint.mjs +29 -0
- package/src/architecture-intent/judge.mjs +539 -0
- package/src/architecture-intent/model.mjs +703 -0
- package/src/architecture-intent/selectors.mjs +170 -0
- package/src/canonical.mjs +48 -0
- package/src/commands/README.md +266 -0
- package/src/commands/adr.mjs +248 -0
- package/src/commands/check.mjs +989 -0
- package/src/commands/context-command.mjs +212 -0
- package/src/commands/context.mjs +790 -0
- package/src/commands/custom-rules.mjs +428 -0
- package/src/commands/debt.mjs +218 -0
- package/src/commands/diff.mjs +523 -0
- package/src/commands/discover.mjs +159 -0
- package/src/commands/drift.mjs +473 -0
- package/src/commands/edge-constraints.mjs +355 -0
- package/src/commands/explain.mjs +359 -0
- package/src/commands/fitness.mjs +226 -0
- package/src/commands/graph.mjs +297 -0
- package/src/commands/health.mjs +213 -0
- package/src/commands/history.mjs +614 -0
- package/src/commands/impact.mjs +226 -0
- package/src/commands/plan-context-command.mjs +496 -0
- package/src/commands/policy.mjs +138 -0
- package/src/commands/provenance-command.mjs +352 -0
- package/src/commands/provenance.mjs +159 -0
- package/src/commands/reconcile.mjs +219 -0
- package/src/commands/report.mjs +553 -0
- package/src/commands/snapshot-meta.mjs +107 -0
- package/src/commands/waivers.mjs +240 -0
- package/src/config.mjs +1308 -0
- package/src/containment.mjs +234 -0
- package/src/custom-rules/evidence.mjs +340 -0
- package/src/custom-rules/host.mjs +1023 -0
- package/src/custom-rules/values.mjs +43 -0
- package/src/entry-point.mjs +55 -0
- package/src/errors.mjs +36 -0
- package/src/eslint-config.mjs +542 -0
- package/src/go-work.mjs +394 -0
- package/src/governance/adr-registry.mjs +539 -0
- package/src/governance/clock.mjs +69 -0
- package/src/governance/debt-ledger.mjs +274 -0
- package/src/governance/discovery-proposal.mjs +423 -0
- package/src/governance/fitness-registry.mjs +504 -0
- package/src/governance/fitness-rules.mjs +668 -0
- package/src/governance/metrics.mjs +392 -0
- package/src/governance/preset-fingerprints.json +16 -0
- package/src/governance/profile-registry.mjs +366 -0
- package/src/governance/provenance-record.mjs +177 -0
- package/src/governance/reconcile-candidates.mjs +301 -0
- package/src/governance/reconcile-score.mjs +503 -0
- package/src/governance/row-schema.mjs +208 -0
- package/src/governance/verdict.mjs +127 -0
- package/src/governance/waiver.mjs +105 -0
- package/src/graph/create-dependencies.mjs +96 -0
- package/src/intent/intent-manifest.json +347 -0
- package/src/intent/mask-non-code.mjs +640 -0
- package/src/lsp/boundary-config.mjs +225 -0
- package/src/lsp/diagnose.mjs +202 -0
- package/src/lsp/diagnostics.mjs +241 -0
- package/src/lsp/protocol.mjs +215 -0
- package/src/lsp/server.mjs +922 -0
- package/src/lsp/workspace-index.mjs +891 -0
- package/src/nx-json.mjs +95 -0
- package/src/options.mjs +611 -0
- package/src/process.mjs +91 -0
- package/src/providers/moon.mjs +733 -0
- package/src/providers/native/README.md +204 -0
- package/src/providers/native/coverage.mjs +74 -0
- package/src/providers/native/differential.fixtures.mjs +1277 -0
- package/src/providers/native/discover.mjs +431 -0
- package/src/providers/native/graph.mjs +234 -0
- package/src/providers/native/index.mjs +152 -0
- package/src/providers/native/model.mjs +755 -0
- package/src/providers/nx.mjs +178 -0
- package/src/report/README.md +89 -0
- package/src/report/adr-text.mjs +129 -0
- package/src/report/context-text.mjs +109 -0
- package/src/report/debt-text.mjs +105 -0
- package/src/report/diff-text.mjs +219 -0
- package/src/report/discover-text.mjs +186 -0
- package/src/report/drift-text.mjs +194 -0
- package/src/report/envelope-shape.mjs +161 -0
- package/src/report/evidence.mjs +157 -0
- package/src/report/explain-text.mjs +159 -0
- package/src/report/graph-text.mjs +116 -0
- package/src/report/health-text.mjs +123 -0
- package/src/report/history-text.mjs +204 -0
- package/src/report/impact-text.mjs +128 -0
- package/src/report/json.mjs +173 -0
- package/src/report/plan-context-text.mjs +159 -0
- package/src/report/provenance-text.mjs +78 -0
- package/src/report/reconcile-text.mjs +159 -0
- package/src/report/report-text.mjs +264 -0
- package/src/report/sarif.mjs +953 -0
- package/src/report/text.mjs +823 -0
- package/src/report/waivers-text.mjs +100 -0
- package/src/rules/README.md +123 -0
- package/src/rules/index.mjs +962 -0
- package/src/rules/match.mjs +1708 -0
- package/src/rules/messages.mjs +73 -0
- package/src/rules/reachability.mjs +224 -0
- package/src/rules/specifiers.mjs +300 -0
- package/src/rules/tags.mjs +238 -0
- package/src/rules/topology.mjs +333 -0
- package/src/tsconfig-paths.mjs +237 -0
- package/src/verdict.mjs +145 -0
- package/src/workspace.mjs +580 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# `src/providers/native/` — the `archkeep.json` project model
|
|
2
|
+
|
|
3
|
+
A `ProjectModelProvider` for a workspace with no `nx.json` and no Nx installed:
|
|
4
|
+
`model.mjs` loads and validates `archkeep.json`, `discover.mjs` resolves the
|
|
5
|
+
project list declared∪inferred from it, `coverage.mjs` judges which tracked,
|
|
6
|
+
analyzable files none of those projects own, and `graph.mjs` reduces both into
|
|
7
|
+
the same `{nodes, dependencies}` shape `../nx.mjs`'s Nx path builds from a real
|
|
8
|
+
`nx graph`. `index.mjs` composes the four into `nativeProvider`, the object
|
|
9
|
+
`../../../cli.mjs` selects when a workspace root carries `archkeep.json` rather than
|
|
10
|
+
`nx.json`.
|
|
11
|
+
|
|
12
|
+
Every module here is reachable with no `nx` package resolvable and no
|
|
13
|
+
TypeScript compiler loaded at import time — `index.mjs`'s header documents the
|
|
14
|
+
one import (`../../analysis/analyze.mjs`, for `languageOf` in `coverage.mjs`)
|
|
15
|
+
that does load `typescript`, and why that one is fine: `typescript` is a
|
|
16
|
+
mandatory dependency of this package, unlike `nx`, which is an optional peer
|
|
17
|
+
that a workspace running the native path has no reason to install.
|
|
18
|
+
|
|
19
|
+
## Declared limits
|
|
20
|
+
|
|
21
|
+
Six things this provider does not attempt, each because reaching further would
|
|
22
|
+
either duplicate a fact this package derives elsewhere or answer a question
|
|
23
|
+
nobody asked it:
|
|
24
|
+
|
|
25
|
+
1. **Git is required.** Project discovery and coverage both walk the tracked
|
|
26
|
+
file list `../../workspace.mjs`'s `listTrackedFiles` returns from `git
|
|
27
|
+
ls-files`; a tree with no git repository has no file list to discover
|
|
28
|
+
projects or judge coverage from, and `../../../cli.mjs` reports that loudly
|
|
29
|
+
(exit 3) rather than reading an empty list as an empty, clean workspace.
|
|
30
|
+
2. **No `externalNodes` are emitted.** `../../rules/specifiers.mjs`'s
|
|
31
|
+
`findTransitiveExternalDependencies` already treats an absent
|
|
32
|
+
`graph.externalNodes` as "none", and `../../rules/index.mjs`'s
|
|
33
|
+
`externalNodeFor` synthesises a node for an external target on demand — a
|
|
34
|
+
provider declaring npm-registry bookkeeping here would be a second source of
|
|
35
|
+
truth for a fact this package already derives from the analysis records.
|
|
36
|
+
3. **`data.targets` names, never runs.** `archkeep.json`'s declared
|
|
37
|
+
projects may carry a `targets` field (`model.mjs` validates its shape) and
|
|
38
|
+
`graph.mjs` synthesises `{[name]: {executor: "archkeep:declared"}}` for each
|
|
39
|
+
declared name, which is enough for `../../rules/topology.mjs`'s
|
|
40
|
+
`hasBuildExecutor` to read the project as buildable and for
|
|
41
|
+
`enforceBuildableLibDependency` to run on a native tree. What it does not
|
|
42
|
+
do is anything an executor or a config would: this provider has no build
|
|
43
|
+
system to ask, so `"archkeep:declared"` is a fixed placeholder string, never
|
|
44
|
+
a real executor identity a rule could branch on.
|
|
45
|
+
4. **`workspaceLayout` is taken from the model, or absent — never inferred**
|
|
46
|
+
from directory names. A workspace that wants `appsDir`/`libsDir` judged
|
|
47
|
+
states them in `archkeep.json`; one that states neither gets the rule
|
|
48
|
+
engine's own default (`../../rules/index.mjs`), the same default the Nx path
|
|
49
|
+
falls back to when Nx's own `workspaceLayout` is unset.
|
|
50
|
+
5. **Tag spellings are not validated against any vocabulary.** `archkeep.json`
|
|
51
|
+
can declare or infer any string as a tag; whether `layer:adapter` is a tag
|
|
52
|
+
this workspace's boundary config actually constrains is a `module-boundaries.config.mjs`
|
|
53
|
+
question, judged by `../../rules/`, never by this provider.
|
|
54
|
+
6. **No tag is ever inferred from `package.json`.** Nx's own built-in
|
|
55
|
+
js/package-json plugin synthesises `npm:public`/`npm:private` from a
|
|
56
|
+
directory's `package.json` `private` field the moment that file exists,
|
|
57
|
+
with no opt-in — this provider does not replicate that inference, by
|
|
58
|
+
design: `discover.mjs` reads a `package.json` only for its `name`, never
|
|
59
|
+
its `private` field, so a native tree gets no tag from `package.json` it did
|
|
60
|
+
not declare or that a `projectRules` row did not add explicitly. The silent
|
|
61
|
+
direction this refusal creates: a workspace migrating from Nx to
|
|
62
|
+
`archkeep.json`, carrying `depConstraints` rows keyed on `npm:public` or
|
|
63
|
+
`npm:private`, will see those constraints stop matching anything the moment
|
|
64
|
+
the tree drops `nx.json` — not because the projects changed, but because
|
|
65
|
+
the tag that used to appear automatically no longer does. The fix is one
|
|
66
|
+
line per affected project: add the matching tag to that project's
|
|
67
|
+
`projects.declared` row or to a `projectRules` row that matches it, the
|
|
68
|
+
same way `differential.fixtures.mjs`'s `composite` fixture states
|
|
69
|
+
`npm:private` on `pkgnamed` explicitly rather than relying on inference
|
|
70
|
+
that does not happen here.
|
|
71
|
+
|
|
72
|
+
## Two failure classes, both loud
|
|
73
|
+
|
|
74
|
+
A **model defect** — a `projectRules` row matching no project, a declared root
|
|
75
|
+
with no tracked file, two projects resolving to the same name, a stale
|
|
76
|
+
`coverage.exempt` waiver, a workspace describing zero projects — throws out of
|
|
77
|
+
`discover()`. `../../../cli.mjs` turns that into exit 3: the workspace description
|
|
78
|
+
itself does not hold up against the tree, so nothing downstream of discovery
|
|
79
|
+
can be trusted enough to try.
|
|
80
|
+
|
|
81
|
+
An **unclaimed file** — a tracked, analyzable file no discovered project owns —
|
|
82
|
+
is not a defect in the model; it is a coverage hole in the _tree_, and
|
|
83
|
+
`coverage.mjs` reports it as a whole-file `fileFailure`
|
|
84
|
+
(`../../analysis/source-util.mjs`) riding in `discover()`'s return value rather
|
|
85
|
+
than thrown. `../../../cli.mjs` merges it into the same `failures` array a
|
|
86
|
+
language analyzer's own unreadable-file failure already occupies, so
|
|
87
|
+
`../../report/text.mjs`'s `formatFailures` and the exit-3 `unchecked` count both
|
|
88
|
+
need no native-specific branch to report it.
|
|
89
|
+
|
|
90
|
+
## What proves this provider against a tree it was not tested on
|
|
91
|
+
|
|
92
|
+
Every test above this line runs against a fixture this package's own tests
|
|
93
|
+
built — an in-memory tree, or (`differential.integration.test.mjs`'s Oracle 1)
|
|
94
|
+
a synthetic tree constructed under `packages/archkeep/` itself, close enough to
|
|
95
|
+
resolve this repository's own `node_modules` for the Nx side of that
|
|
96
|
+
comparison. Neither is what a real consumer does: `pnpm pack`, `pnpm install`
|
|
97
|
+
the tarball into a workspace this repository never built, and run the bin
|
|
98
|
+
entries as installed.
|
|
99
|
+
|
|
100
|
+
`differential.integration.test.mjs`'s Oracle 1 covers seven axes across three
|
|
101
|
+
fixture pairs, not one pair per axis — the cost driver is the real `nx graph
|
|
102
|
+
--file=` spawn, so the axes are packed to hold the file to three spawns total.
|
|
103
|
+
The shared machinery it drives — the fixture builders, `diffGraphs`, `LEDGER`,
|
|
104
|
+
and the breach checks — lives in `./differential.fixtures.mjs`, a plain module
|
|
105
|
+
with no `vitest` import, so a future differential can reuse it without running
|
|
106
|
+
this suite's own cases as a side effect of the import.
|
|
107
|
+
|
|
108
|
+
`simple` (unchanged) stays the minimal, single-violation pair a failure is
|
|
109
|
+
diagnosable from without reading a diff row. `composite` packs six
|
|
110
|
+
identity/topology axes into one tree — name precedence (a declared name, a
|
|
111
|
+
`package.json` name, and a bare `basename(root)` fallback), project type (the
|
|
112
|
+
`-e2e` suffix rule), the workspace root itself as a project (`root: ""`), tag
|
|
113
|
+
union across all THREE sources at once on one project (a declared row, a
|
|
114
|
+
`projectRules` row, and its own `project.json`, all on `parent`), implicit
|
|
115
|
+
dependencies (a literal project name spelled on a declared row, a literal name
|
|
116
|
+
spelled in `project.json`, and a `tag:`-pattern entry), and a project nested
|
|
117
|
+
inside another project's own directory — and asserts each axis by name via
|
|
118
|
+
`diffGraphs`, a per-node/per-edge/per-verdict comparison, rather than one
|
|
119
|
+
`deepEqual` over two whole graphs. `layout` isolates `workspaceLayout` alone,
|
|
120
|
+
because it is workspace-global rather than per-project and folding it into
|
|
121
|
+
`composite` would move every other axis's expected shape at once. The two
|
|
122
|
+
providers now AGREE on this pair — both report the same two violations —
|
|
123
|
+
because `readProjectGraph` in `../nx.mjs` merges `nx.json`'s own
|
|
124
|
+
`workspaceLayout` back onto the graph `nx graph --file=` returns (that
|
|
125
|
+
function's own header), so the Nx side reads the identical non-default
|
|
126
|
+
`libsDir`/`appsDir` the native side already read from `archkeep.json`. `LEDGER`
|
|
127
|
+
is empty and frozen for this pair — the one row it ever carried
|
|
128
|
+
(https://github.com/ecoma-io/archkeep/issues/31) retired with that merge — and
|
|
129
|
+
the ledger's stale-row check (`classifyDifferences`'s `stale` return,
|
|
130
|
+
asserted in `differential.integration.test.mjs`'s "the ledger's stale-row
|
|
131
|
+
rule") is what keeps that claim honest: a row reintroduced here without a
|
|
132
|
+
matching difference to explain would fail the suite rather than sit unread.
|
|
133
|
+
|
|
134
|
+
Only ONE direction is ledgerable, and it is enforced structurally rather than
|
|
135
|
+
by convention: a `LedgerRow` now carries a `direction` field
|
|
136
|
+
(`differential.fixtures.mjs`'s `LEDGER_DIRECTIONS`), and its only member is
|
|
137
|
+
`"native-only"` — native reporting something Nx does not. `classifyDifferences`
|
|
138
|
+
throws, unconditionally, on any verdict-count difference where Nx's count
|
|
139
|
+
exceeds native's, before it ever checks whether a `LEDGER` row matches that
|
|
140
|
+
difference's subject and field — no row, however its `reason` is worded, gets
|
|
141
|
+
a vote on that direction. `emptyVerdictBreaches` catches the aggregate shape of
|
|
142
|
+
the same failure (an engine reporting zero violations on a pair built to
|
|
143
|
+
contain one) and `perMessageBreaches` catches it per `messageId` even when
|
|
144
|
+
neither side's total is literally zero (`{nx: 3, native: 1}` on one rule is a
|
|
145
|
+
breach exactly as much as `{nx: 1, native: 0}` is); both take no `ledger`
|
|
146
|
+
parameter at all, so the suppression is not just untested, it has nowhere to be
|
|
147
|
+
written.
|
|
148
|
+
|
|
149
|
+
`../../../../../scripts/verify-package.mjs` closes that gap for both providers,
|
|
150
|
+
not only Nx. It packs the real tarball once and installs it into TWO throwaway
|
|
151
|
+
consumer workspaces — one with `nx.json`, one with `archkeep.json` and no `nx`
|
|
152
|
+
package requested at all — then runs the same three questions against both: the
|
|
153
|
+
checker exits 0 on a clean tree and states what it inspected, exits 1 on a
|
|
154
|
+
violating one naming the rule and the site, and the language server answers
|
|
155
|
+
`initialize` when launched through the symlinked path an installed plugin is
|
|
156
|
+
launched by. It also asserts `nx` genuinely does not resolve in the native
|
|
157
|
+
consumer's `node_modules` — the optional-peer claim (`../../../AGENTS.md`'s
|
|
158
|
+
"Nx is a peer dependency but an optional one") checked against a real install,
|
|
159
|
+
not only against the manifest's `peerDependenciesMeta`. It runs in CI on every
|
|
160
|
+
pull request (`../../../../../.github/workflows/ci.yml`'s "Prove the packed
|
|
161
|
+
artifact works outside this workspace" step already invoked it for the Nx
|
|
162
|
+
path; the native path rides the same invocation, no separate CI step needed)
|
|
163
|
+
and again in the release lane before `npm publish`.
|
|
164
|
+
|
|
165
|
+
`verify-package.mjs` still proves the provider only against fixtures someone
|
|
166
|
+
here built, even installed for real — the checker exits the right code on a
|
|
167
|
+
tree this repository authored to exit that code. Two more things close the gap
|
|
168
|
+
`verify-package.mjs` cannot: source nobody here wrote, and this repository's
|
|
169
|
+
own tree.
|
|
170
|
+
|
|
171
|
+
`../../../../../scripts/differential-real-trees.mjs`'s native leg answers "does
|
|
172
|
+
this provider's discovery-plus-graph pipeline reproduce a real tool's own
|
|
173
|
+
answer, on a tree neither of us built." It derives a `archkeep.json`-equivalent
|
|
174
|
+
model mechanically from a real Nx workspace's own `nx graph --file=` output
|
|
175
|
+
(`deriveNativeModel`, one `projects.declared` row per node) — never
|
|
176
|
+
hand-authored, so the model is a measurement of the real tree rather than a
|
|
177
|
+
fixture this package's author already knew the answer to — runs
|
|
178
|
+
`nativeProvider.discover`/`buildGraph` over it, and compares the node set, edge
|
|
179
|
+
set, and rule verdicts against the same tree's real Nx-graph-based run,
|
|
180
|
+
classified through the differential's existing ledger. The first real run
|
|
181
|
+
(2026-08-12, against `code-pushup` at its pinned commit) found a populated
|
|
182
|
+
ledger, not an empty one — the expected outcome, argued in that file's own
|
|
183
|
+
`LEDGER` doc comment — and every row traces to a real, investigated cause
|
|
184
|
+
rather than an unknown: Nx's own root-project spelling (`root: "."`) needing
|
|
185
|
+
renormalisation before it reaches `archkeep.json`'s dialect (which rejects that
|
|
186
|
+
exact spelling by name), and two narrower, pre-existing gaps in the shared
|
|
187
|
+
analysis pipeline that this leg surfaced but does not own fixing (a root-"."
|
|
188
|
+
project's own files going unowned by `createWorkspace`, and a TypeScript
|
|
189
|
+
import-type query the analyzer's AST walk does not visit) — both logged in
|
|
190
|
+
`LEDGER` with the reason, and both out of scope for this provider to change:
|
|
191
|
+
either fix moves what every consumer's boundary check reports on an unchanged
|
|
192
|
+
workspace, which is a breaking change on its own, argued separately from this
|
|
193
|
+
provider's own correctness.
|
|
194
|
+
|
|
195
|
+
`../../../../../.github/workflows/ci.yml`'s "Check this repository's own
|
|
196
|
+
module boundaries (native provider)" step answers the other half: does this
|
|
197
|
+
provider meet THIS repository's own real source, under a tag vocabulary
|
|
198
|
+
(`type:package`, `scope:nx`) nothing under `src/` has any knowledge of — the
|
|
199
|
+
same argument the Nx-based self-check step just above it makes for that path.
|
|
200
|
+
That step's own comment carries the full account of why this repository
|
|
201
|
+
cannot carry a root `archkeep.json` alongside its own `nx.json`, and how the
|
|
202
|
+
throwaway copy the step runs against is built and proved to share this
|
|
203
|
+
repository's real `module-boundaries.config.mjs` — read it there rather than
|
|
204
|
+
here, so the mechanism has one description instead of two that could drift.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage judgment for a native workspace: which tracked, analyzable files
|
|
3
|
+
* no discovered project owns.
|
|
4
|
+
*
|
|
5
|
+
* Nx has no equivalent question — every file Nx's own graph omits is simply
|
|
6
|
+
* outside any project as far as `nx graph` is concerned, and this package's
|
|
7
|
+
* Nx path (`../nx.mjs`, `../../workspace.mjs`) has no unclaimed-file check of
|
|
8
|
+
* its own: both compute imports and violations only for files a project
|
|
9
|
+
* already claims. A native workspace gets no `nx graph` to lean on, so `archkeep.json` states
|
|
10
|
+
* the projects itself — and a file that fits none of them is exactly the
|
|
11
|
+
* silent hole `../../../../../AGENTS.md`'s invariant is about: it is analyzed by
|
|
12
|
+
* nothing, judged by nothing, and an empty violation list would read
|
|
13
|
+
* identically to a file that really was clean.
|
|
14
|
+
*
|
|
15
|
+
* An unclaimed file becomes a whole-file `fileFailure`
|
|
16
|
+
* (`../../analysis/source-util.mjs`) — the SAME record shape a language
|
|
17
|
+
* analyzer produces for a file it could not read, so `../../../cli.mjs` and
|
|
18
|
+
* `../../report/text.mjs`'s `formatFailures` need no native-specific branch
|
|
19
|
+
* to report it: `formatFailures` already splits whole-file holes from
|
|
20
|
+
* site-level ones (`isWholeFileFailure`, `failure.line === null`).
|
|
21
|
+
*/
|
|
22
|
+
import { languageOf } from "../../analysis/registry.mjs";
|
|
23
|
+
import { fileFailure } from "../../analysis/source-util.mjs";
|
|
24
|
+
import { matchesGlob } from "./model.mjs";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Judges coverage over a discovered native workspace.
|
|
28
|
+
*
|
|
29
|
+
* `files` and `claimed` ride alongside `unclaimed`/`exempted`/`stale` rather
|
|
30
|
+
* than replacing any of them — additive, so an existing caller destructuring
|
|
31
|
+
* this object keeps working unchanged. They exist for the same reason
|
|
32
|
+
* `../../../AGENTS.md`'s "What is a stub" section gives `cli.mjs check` its
|
|
33
|
+
* own inspected-file count: "no violations" is a claim about coverage too,
|
|
34
|
+
* and a caller that wants to state what it inspected (not just what it
|
|
35
|
+
* found) needs the denominator, not only the failing files.
|
|
36
|
+
*
|
|
37
|
+
* @param {{files: string[], projectOf: (file: string) => string|undefined, exempt: {path: string, reason: string}[]}} args
|
|
38
|
+
* @returns {{
|
|
39
|
+
* files: number,
|
|
40
|
+
* claimed: number,
|
|
41
|
+
* unclaimed: string[],
|
|
42
|
+
* exempted: string[],
|
|
43
|
+
* stale: {path: string, reason: string}[],
|
|
44
|
+
* failures: {sourceFile: string, line: null, column: null, reason: string}[],
|
|
45
|
+
* }}
|
|
46
|
+
*/
|
|
47
|
+
export function judgeCoverage({ files, projectOf, exempt }) {
|
|
48
|
+
const analyzable = files.filter((file) => languageOf(file) !== null);
|
|
49
|
+
const unowned = analyzable.filter((file) => projectOf(file) === undefined);
|
|
50
|
+
|
|
51
|
+
const matchesFor = exempt.map((row) => ({
|
|
52
|
+
row,
|
|
53
|
+
files: unowned.filter((file) => matchesGlob(file, row.path)),
|
|
54
|
+
}));
|
|
55
|
+
const exemptedFiles = new Set(matchesFor.flatMap((m) => m.files));
|
|
56
|
+
const unclaimed = unowned.filter((file) => !exemptedFiles.has(file));
|
|
57
|
+
const stale = matchesFor.filter((m) => m.files.length === 0).map((m) => m.row);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
files: analyzable.length,
|
|
61
|
+
claimed: analyzable.length - unowned.length,
|
|
62
|
+
unclaimed,
|
|
63
|
+
exempted: [...exemptedFiles],
|
|
64
|
+
stale,
|
|
65
|
+
failures: unclaimed.map((file) =>
|
|
66
|
+
fileFailure(
|
|
67
|
+
file,
|
|
68
|
+
"is not owned by any project declared or inferred from archkeep.json — every " +
|
|
69
|
+
"analyzable tracked file must belong to exactly one project, or be named in " +
|
|
70
|
+
"archkeep.json's coverage.exempt with a reason",
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
};
|
|
74
|
+
}
|