@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.
@@ -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}} result
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
  }