@ecoma-io/archkeep 0.13.0 → 0.14.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/README.md +1 -1
- package/cli.mjs +158 -2
- package/package.json +1 -1
- package/src/commands/README.md +19 -1
- package/src/commands/check.mjs +82 -16
- package/src/commands/context.mjs +52 -14
- package/src/commands/coverage-acceptance.mjs +113 -0
- package/src/commands/delta-classify.mjs +502 -0
- package/src/commands/delta-snapshot.mjs +517 -0
- package/src/commands/delta.mjs +481 -0
- package/src/commands/explain.mjs +39 -0
- package/src/commands/policy.mjs +36 -1
- package/src/commands/waivers.mjs +53 -3
- package/src/config.mjs +129 -11
- package/src/lsp/boundary-config.mjs +9 -4
- package/src/providers/native/model.mjs +17 -0
- package/src/report/delta-text.mjs +183 -0
- package/src/report/explain-text.mjs +27 -0
- package/src/report/sarif.mjs +25 -0
- package/src/report/text.mjs +36 -0
- package/src/report/waivers-text.mjs +35 -2
package/src/report/text.mjs
CHANGED
|
@@ -588,6 +588,7 @@ const UNOWNED_SAMPLE_LIMIT = 10;
|
|
|
588
588
|
function formatCoverageGap(gap) {
|
|
589
589
|
if (gap.kind === "unregistered-plugin") return formatUnregisteredPluginGap(gap);
|
|
590
590
|
if (gap.kind === "unowned-files") return formatUnownedFilesGap(gap);
|
|
591
|
+
if (gap.kind === "accepted-unowned-files") return formatAcceptedUnownedFilesGap(gap);
|
|
591
592
|
return `⚠ coverage gap "${gap.kind}" — part of this workspace is outside what this run covered`;
|
|
592
593
|
}
|
|
593
594
|
|
|
@@ -655,6 +656,41 @@ function formatUnownedFilesGap(gap) {
|
|
|
655
656
|
);
|
|
656
657
|
}
|
|
657
658
|
|
|
659
|
+
/**
|
|
660
|
+
* The accepted counterpart of the gap above: unowned files the boundary
|
|
661
|
+
* policy's `coverage.unowned` rows accept (`../commands/check.mjs`,
|
|
662
|
+
* `../commands/coverage-acceptance.mjs`). An accepted hole is still a hole,
|
|
663
|
+
* so the section renders on every run the acceptance is in force, with the
|
|
664
|
+
* same bounded sample the warning uses — what it no longer does is ask the
|
|
665
|
+
* reader a question someone already answered, which is the warning's job.
|
|
666
|
+
* The reasons live on the accepting rows, and `archkeep waivers` is the
|
|
667
|
+
* surface that names each row with its reason and current coverage; this
|
|
668
|
+
* face points there rather than restating them.
|
|
669
|
+
*
|
|
670
|
+
* @param {{languages?: string[], files: string[]}} gap
|
|
671
|
+
* @returns {string}
|
|
672
|
+
*/
|
|
673
|
+
function formatAcceptedUnownedFilesGap(gap) {
|
|
674
|
+
const files = gap.files ?? [];
|
|
675
|
+
const count = files.length;
|
|
676
|
+
const languages = gap.languages ?? [];
|
|
677
|
+
const spans = languages.length > 0 ? ` (${languages.join(", ")})` : "";
|
|
678
|
+
const shown = files.slice(0, UNOWNED_SAMPLE_LIMIT);
|
|
679
|
+
const remaining = count - shown.length;
|
|
680
|
+
const lines = shown.map((file) => `${CONTINUED}${file}`);
|
|
681
|
+
if (remaining > 0) {
|
|
682
|
+
lines.push(`${CONTINUED}… and ${remaining} more — the full list is in --format json`);
|
|
683
|
+
}
|
|
684
|
+
const them = count === 1 ? "it" : "them";
|
|
685
|
+
return (
|
|
686
|
+
`⚠ ${count} tracked analyzable file${count === 1 ? "" : "s"}${spans} ` +
|
|
687
|
+
`owned by no project — accepted as coverage holes by the policy's coverage.unowned, ` +
|
|
688
|
+
`so no boundary verdict covers ${them}\n` +
|
|
689
|
+
`${lines.join("\n")}\n` +
|
|
690
|
+
`${DETAIL}each accepting row's reason: archkeep waivers`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
658
694
|
/**
|
|
659
695
|
* The policy-identity line — which law this run enforced — rendered FIRST,
|
|
660
696
|
* ahead of every verdict below it: a reader has to know WHICH law produced a
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {{waivers: object[], covered: number, expired: number, stale: number,
|
|
19
|
-
* suppressions: object[], suppressed: number
|
|
19
|
+
* suppressions: object[], suppressed: number,
|
|
20
|
+
* unownedAcceptances?: {path: string, reason: string, covered: number}[]}} result
|
|
21
|
+
* `unownedAcceptances` is present exactly when the policy declares
|
|
22
|
+
* `coverage.unowned` (`../commands/waivers.mjs`) — the third surface, and
|
|
23
|
+
* the one the acceptances' reasons live on.
|
|
20
24
|
* @returns {string}
|
|
21
25
|
*/
|
|
22
26
|
export function formatWaiversReport({
|
|
@@ -26,10 +30,17 @@ export function formatWaiversReport({
|
|
|
26
30
|
stale,
|
|
27
31
|
suppressions,
|
|
28
32
|
suppressed,
|
|
33
|
+
unownedAcceptances,
|
|
29
34
|
}) {
|
|
30
35
|
const sections = [];
|
|
36
|
+
const acceptances = unownedAcceptances ?? [];
|
|
31
37
|
|
|
32
|
-
if (waivers.length === 0 && suppressions.length === 0) {
|
|
38
|
+
if (waivers.length === 0 && suppressions.length === 0 && acceptances.length === 0) {
|
|
39
|
+
// The all-empty claim may only be made when all THREE surfaces were
|
|
40
|
+
// measured and found empty — a declared `coverage.unowned` row is an
|
|
41
|
+
// acceptance on the table exactly as a suppression is, and this line
|
|
42
|
+
// reading "nothing is accepted" over one would be the module header's
|
|
43
|
+
// defect on a third table.
|
|
33
44
|
return `no waivers — every boundary is enforced, nothing is being accepted temporarily or permanently`;
|
|
34
45
|
}
|
|
35
46
|
|
|
@@ -96,5 +107,27 @@ export function formatWaiversReport({
|
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
|
|
110
|
+
if (acceptances.length > 0) {
|
|
111
|
+
// The coverage half of the table: each row accepts unowned FILES rather
|
|
112
|
+
// than a verdict — `check` still states the files as an accepted
|
|
113
|
+
// coverage gap, and this section is where their reasons live
|
|
114
|
+
// (`../commands/coverage-acceptance.mjs`). A row covering nothing is
|
|
115
|
+
// named the way a stale waiver is; `check` refuses it outright.
|
|
116
|
+
const totalCovered = acceptances.reduce((sum, row) => sum + row.covered, 0);
|
|
117
|
+
sections.push(
|
|
118
|
+
`${acceptances.length} coverage acceptance${acceptances.length === 1 ? "" : "s"} ` +
|
|
119
|
+
`(coverage.unowned) on the table — currently accepting ${totalCovered} unowned ` +
|
|
120
|
+
`file${totalCovered === 1 ? "" : "s"} as recorded coverage holes`,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const row of acceptances) {
|
|
124
|
+
const coverage =
|
|
125
|
+
row.covered === 0
|
|
126
|
+
? "covers no unowned file right now — the files it accepted may be owned by a project now"
|
|
127
|
+
: `currently covers ${row.covered} unowned file${row.covered === 1 ? "" : "s"}`;
|
|
128
|
+
sections.push([`- ${row.path}: ${coverage}`, ` reason: ${row.reason}`].join("\n"));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
return sections.join("\n\n");
|
|
100
133
|
}
|