@ecoma-io/archkeep 0.24.0 → 0.25.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.
Files changed (79) hide show
  1. package/package.json +1 -1
  2. package/src/analysis/csharp.mjs +3 -1
  3. package/src/analysis/dotnet/csproj.mjs +5 -1
  4. package/src/analysis/dotnet/namespaces.mjs +1 -0
  5. package/src/analysis/go.mjs +6 -0
  6. package/src/analysis/java.mjs +2 -0
  7. package/src/analysis/jvm/gradle.mjs +3 -1
  8. package/src/analysis/jvm/maven.mjs +6 -1
  9. package/src/analysis/jvm/packages.mjs +1 -0
  10. package/src/analysis/jvm/resolve.mjs +4 -2
  11. package/src/analysis/kotlin.mjs +2 -0
  12. package/src/analysis/markdown.mjs +1 -0
  13. package/src/analysis/python.mjs +8 -0
  14. package/src/analysis/rust.mjs +5 -1
  15. package/src/analysis/source-util.mjs +1 -1
  16. package/src/analysis/typescript.mjs +2 -0
  17. package/src/architecture-intent/model.mjs +11 -7
  18. package/src/architecture-intent/selectors.mjs +2 -1
  19. package/src/commands/change-intent.mjs +10 -9
  20. package/src/commands/change.mjs +86 -23
  21. package/src/commands/check.mjs +11 -9
  22. package/src/commands/completeness.mjs +7 -6
  23. package/src/commands/coverage-acceptance.mjs +46 -0
  24. package/src/commands/custom-rules.mjs +1 -0
  25. package/src/commands/delta-classify.mjs +3 -0
  26. package/src/commands/delta-snapshot.mjs +27 -5
  27. package/src/commands/delta.mjs +73 -39
  28. package/src/commands/diff.mjs +3 -9
  29. package/src/commands/drift.mjs +1 -1
  30. package/src/commands/evaluation-primitives.mjs +4 -4
  31. package/src/commands/evolution.mjs +2 -0
  32. package/src/commands/explain.mjs +2 -0
  33. package/src/commands/graph.mjs +39 -17
  34. package/src/commands/history.mjs +36 -0
  35. package/src/commands/plan-context-command.mjs +4 -1
  36. package/src/commands/policy.mjs +5 -2
  37. package/src/commands/scenario-evaluation.mjs +1 -1
  38. package/src/commands/snapshot-meta.mjs +33 -0
  39. package/src/commands/trajectory.mjs +2 -1
  40. package/src/config.mjs +1 -1
  41. package/src/custom-rules/host.mjs +3 -3
  42. package/src/custom-rules/values.mjs +1 -4
  43. package/src/eslint-config.mjs +1 -0
  44. package/src/fixtures/evolution-lifecycle/workspace.mjs +15 -4
  45. package/src/go-work.mjs +1 -1
  46. package/src/governance/adr-registry.mjs +4 -1
  47. package/src/governance/debt-ledger.mjs +1 -1
  48. package/src/governance/decision-fitness.mjs +2 -0
  49. package/src/governance/decision-graph.mjs +1 -0
  50. package/src/governance/discovery-proposal.mjs +8 -2
  51. package/src/governance/evolution-event.mjs +42 -0
  52. package/src/governance/fitness-registry.mjs +16 -1
  53. package/src/governance/preset-fingerprints.json +14 -14
  54. package/src/governance/profile-registry.mjs +22 -3
  55. package/src/governance/provenance-record.mjs +4 -1
  56. package/src/governance/reconcile-score.mjs +4 -0
  57. package/src/governance/row-schema.mjs +1 -0
  58. package/src/governance/verdict.mjs +37 -4
  59. package/src/governance/waiver.mjs +1 -0
  60. package/src/intent/intent-manifest.json +18 -12
  61. package/src/intent/mask-non-code.mjs +1 -0
  62. package/src/lsp/diagnostics.mjs +3 -2
  63. package/src/lsp/protocol.mjs +2 -1
  64. package/src/lsp/server.mjs +3 -0
  65. package/src/lsp/workspace-index.mjs +3 -1
  66. package/src/providers/native/differential.fixtures.mjs +29 -11
  67. package/src/providers/native/index.mjs +2 -1
  68. package/src/providers/native/model.mjs +4 -0
  69. package/src/report/envelope-shape.mjs +2 -0
  70. package/src/report/json.mjs +4 -2
  71. package/src/report/sarif.mjs +21 -8
  72. package/src/report/snapshot-text.mjs +3 -3
  73. package/src/report/text.mjs +10 -2
  74. package/src/rules/match.mjs +7 -5
  75. package/src/rules/specifiers.mjs +2 -0
  76. package/src/rules/tags.mjs +3 -2
  77. package/src/rules/topology.mjs +6 -1
  78. package/src/values.mjs +11 -0
  79. package/src/workspace.mjs +1 -0
@@ -41,11 +41,13 @@ import { nativeProvider } from "./index.mjs";
41
41
  * @param {Record<string, string>} files `{relativePath: text}`.
42
42
  */
43
43
  export function writeAll(write, files) {
44
+ // used by its own test
44
45
  for (const [path, text] of Object.entries(files)) write(path, text);
45
46
  }
46
47
 
47
48
  /** @param {string} root */
48
49
  export function writeIn(root) {
50
+ // used by its own test
49
51
  return (relativePath, text) => {
50
52
  mkdirSync(join(root, relativePath, ".."), { recursive: true });
51
53
  writeFileSync(join(root, relativePath), text);
@@ -53,7 +55,7 @@ export function writeIn(root) {
53
55
  }
54
56
 
55
57
  /** @param {string} root */
56
- export function readFileFrom(root) {
58
+ function readFileFrom(root) {
57
59
  return (path) => {
58
60
  try {
59
61
  return readFileSync(join(root, path), "utf8");
@@ -76,6 +78,7 @@ function analyze(root, graph, files, tsConfig) {
76
78
 
77
79
  /** `{name → {root, type, tags}}`, comparable whichever provider produced it. */
78
80
  export const nodeShape = (nodes) =>
81
+ // used by its own test
79
82
  Object.fromEntries(
80
83
  Object.entries(nodes).map(([name, node]) => [
81
84
  name,
@@ -105,13 +108,14 @@ export const nodeShape = (nodes) =>
105
108
  * canonicalisation consistent with the code under test rather than a weaker
106
109
  * copy of it. */
107
110
  export const dependencyShape = (dependencies) =>
111
+ // used by its own test
108
112
  Object.values(dependencies)
109
113
  .flat()
110
114
  .map(({ source, target, type }) => JSON.stringify([source, target, type]))
111
115
  .sort();
112
116
 
113
117
  /** `["messageId sourceFile:line:column", ...]`, sorted the same way. */
114
- export const verdictShape = (violations) =>
118
+ const verdictShape = (violations) =>
115
119
  violations.map((v) => `${v.messageId} ${v.sourceFile}:${v.line}:${v.column}`).sort();
116
120
 
117
121
  /**
@@ -128,6 +132,7 @@ export const verdictShape = (violations) =>
128
132
  * @type {typeof runProcess}
129
133
  */
130
134
  export function runNxGraphSpawn(file, args, cwd) {
135
+ // used by its own test
131
136
  const previous = process.env.NX_DAEMON;
132
137
  process.env.NX_DAEMON = "false";
133
138
  try {
@@ -157,6 +162,7 @@ export function runNxGraphSpawn(file, args, cwd) {
157
162
  * @throws {Error} naming every failing file, when either list is non-empty.
158
163
  */
159
164
  export function assertNoAnalysisFailures(nxFailures, nativeFailures) {
165
+ // used by its own test
160
166
  const named = [
161
167
  ...nxFailures.map((f) => `nx:${f.sourceFile} (${f.reason})`),
162
168
  ...nativeFailures.map((f) => `native:${f.sourceFile} (${f.reason})`),
@@ -188,6 +194,7 @@ export function assertNoAnalysisFailures(nxFailures, nativeFailures) {
188
194
  * inherits the `NX_DAEMON` handling rather than choosing between the two.
189
195
  */
190
196
  export async function runBothProviders({
197
+ // used by its own test
191
198
  nxRoot,
192
199
  nxFiles,
193
200
  nativeRoot,
@@ -280,6 +287,7 @@ export async function runBothProviders({
280
287
  * @returns {{kind: string, subject: string, field: string, nx: unknown, native: unknown}[]}
281
288
  */
282
289
  export function diffGraphs(nxSide, nativeSide) {
290
+ // used by its own test
283
291
  const rows = [];
284
292
 
285
293
  const nxNames = new Set(Object.keys(nxSide.nodes));
@@ -417,6 +425,7 @@ export function diffGraphs(nxSide, nativeSide) {
417
425
  * detection) are scoped to one fixture pair and cannot silently absorb a
418
426
  * disagreement in another. */
419
427
  export function namespaced(rows, label) {
428
+ // used by its own test
420
429
  return rows.map((row) => ({ ...row, subject: `${label}:${row.subject}` }));
421
430
  }
422
431
 
@@ -458,7 +467,7 @@ export function namespaced(rows, label) {
458
467
  * `classifyDifferences`, not a search for every place `"native-only"` was
459
468
  * spelled as a bare string.
460
469
  */
461
- export const LEDGER_DIRECTIONS = Object.freeze(/** @type {const} */ (["native-only"]));
470
+ const LEDGER_DIRECTIONS = Object.freeze(/** @type {const} */ (["native-only"]));
462
471
 
463
472
  /**
464
473
  * Every entry here is a **difference native reports and Nx does not** — the
@@ -479,7 +488,7 @@ export const LEDGER_DIRECTIONS = Object.freeze(/** @type {const} */ (["native-on
479
488
  *
480
489
  * @type {readonly LedgerRow[]}
481
490
  */
482
- export const LEDGER = Object.freeze([]);
491
+ export const LEDGER = Object.freeze([]); // used by its own test
483
492
 
484
493
  /**
485
494
  * Every fixture-pair label this file actually runs a comparison over — the
@@ -487,7 +496,7 @@ export const LEDGER = Object.freeze([]);
487
496
  * list every `assertPairAgrees`/`pairProblems` call site draws its `label`
488
497
  * argument from, so the two can never drift into naming different pairs.
489
498
  */
490
- export const PAIR_LABELS = Object.freeze(["simple", "composite", "layout"]);
499
+ export const PAIR_LABELS = Object.freeze(["simple", "composite", "layout"]); // used by its own test
491
500
 
492
501
  /**
493
502
  * The direction a `diffGraphs` row itself claims, in `LedgerRow.direction`'s
@@ -560,6 +569,7 @@ function differenceDirection(difference) {
560
569
  * difference no ledger row may ever cover.
561
570
  */
562
571
  export function classifyDifferences(differences, ledger) {
572
+ // used by its own test
563
573
  for (const row of ledger) {
564
574
  if (!row.reason?.trim()) {
565
575
  throw new Error(`ledger row for "${row.subject}"/"${row.field}" has an empty reason`);
@@ -632,6 +642,7 @@ export function classifyDifferences(differences, ledger) {
632
642
  * @returns {string[]}
633
643
  */
634
644
  export function emptyVerdictBreaches(label, counts) {
645
+ // used by its own test
635
646
  const breaches = [];
636
647
  for (const [engine, count] of Object.entries(counts)) {
637
648
  if (count === 0) {
@@ -659,6 +670,7 @@ export function emptyVerdictBreaches(label, counts) {
659
670
  * @returns {string[]}
660
671
  */
661
672
  export function perMessageBreaches(label, nxViolations, nativeViolations) {
673
+ // used by its own test
662
674
  /** @param {{messageId: string}[]} violations @returns {Map<string, number>} */
663
675
  const countBy = (violations) => {
664
676
  const counts = new Map();
@@ -696,6 +708,7 @@ export function perMessageBreaches(label, nxViolations, nativeViolations) {
696
708
  * @returns {string[]}
697
709
  */
698
710
  export function pairProblems(label, result, ledger = LEDGER) {
711
+ // used by its own test
699
712
  const problems = [
700
713
  ...emptyVerdictBreaches(label, {
701
714
  nx: result.nx.violations.length,
@@ -734,6 +747,7 @@ export function pairProblems(label, result, ledger = LEDGER) {
734
747
  * @param {readonly LedgerRow[]} [ledger]
735
748
  */
736
749
  export function assertPairAgrees(label, result, ledger = LEDGER) {
750
+ // used by its own test
737
751
  const problems = pairProblems(label, result, ledger);
738
752
  if (problems.length > 0) {
739
753
  throw new Error(`${label}: provider differential disagreement:\n ${problems.join("\n ")}`);
@@ -758,6 +772,7 @@ export function assertPairAgrees(label, result, ledger = LEDGER) {
758
772
  * @returns {LedgerRow[]}
759
773
  */
760
774
  export function unknownLabelRows(ledger, knownLabels) {
775
+ // used by its own test
761
776
  return ledger.filter((row) => !knownLabels.includes(row.subject.split(":")[0]));
762
777
  }
763
778
 
@@ -773,7 +788,7 @@ export function unknownLabelRows(ledger, knownLabels) {
773
788
  // staying importable and spawn-free at import time for exactly that reuse,
774
789
  // and a second copy here is the drift `../../../../../AGENTS.md`'s "never
775
790
  // state a rule twice" rule exists to catch.
776
- export const SIMPLE_BOUNDARY_CONFIG = `export const depConstraints = [
791
+ export const SIMPLE_BOUNDARY_CONFIG = `export const depConstraints = [ // used by its own test
777
792
  { sourceTag: "layer:domain", onlyDependOnLibsWithTags: ["layer:domain"] },
778
793
  { sourceTag: "layer:adapter", onlyDependOnLibsWithTags: ["layer:domain", "layer:adapter"] },
779
794
  ];
@@ -794,6 +809,7 @@ export const moduleBoundaryOptions = {
794
809
  // the wrong way (`domain` reaching into `adapter`). Exported for the same
795
810
  // reuse reason as `SIMPLE_BOUNDARY_CONFIG` above.
796
811
  export const SIMPLE_GO_FILES = {
812
+ // used by its own test
797
813
  "libs/domain/go.mod": "module example.com/domain\n\ngo 1.24\n",
798
814
  "libs/adapter/go.mod": "module example.com/adapter\n\ngo 1.24\n",
799
815
  "libs/adapter/adapter.go": 'package adapter\n\nvar Name = "adapter"\n',
@@ -819,6 +835,7 @@ var _ = adapter.Name
819
835
  * @returns {string[]}
820
836
  */
821
837
  export function buildSimpleNxTree(root, { boundaryConfig = "module-boundaries.config.mjs" } = {}) {
838
+ // used by its own test
822
839
  const write = writeIn(root);
823
840
  write(
824
841
  "nx.json",
@@ -851,7 +868,7 @@ export function buildSimpleNxTree(root, { boundaryConfig = "module-boundaries.co
851
868
  * @param {{boundaryConfig?: string}} [options]
852
869
  * @returns {string[]}
853
870
  */
854
- export function buildSimpleNativeTree(
871
+ export function buildSimpleNativeTree( // used by its own test
855
872
  root,
856
873
  { boundaryConfig = "module-boundaries.config.mjs" } = {},
857
874
  ) {
@@ -950,7 +967,7 @@ var _ = parent.Name
950
967
  * @param {{boundaryConfig?: string}} [options]
951
968
  * @returns {string[]}
952
969
  */
953
- export function buildCompositeNxTree(
970
+ export function buildCompositeNxTree( // used by its own test
954
971
  root,
955
972
  { boundaryConfig = "module-boundaries.config.mjs" } = {},
956
973
  ) {
@@ -1029,7 +1046,7 @@ export function buildCompositeNxTree(
1029
1046
  * @param {{boundaryConfig?: string}} [options]
1030
1047
  * @returns {string[]}
1031
1048
  */
1032
- export function buildCompositeNativeTree(
1049
+ export function buildCompositeNativeTree( // used by its own test
1033
1050
  root,
1034
1051
  { boundaryConfig = "module-boundaries.config.mjs" } = {},
1035
1052
  ) {
@@ -1175,7 +1192,7 @@ export function buildCompositeNativeTree(
1175
1192
  const LAYOUT_BOUNDARY_CONFIG = `export const depConstraints = [
1176
1193
  { sourceTag: "layer:thing", onlyDependOnLibsWithTags: ["layer:thing"] },
1177
1194
  ];
1178
- export const moduleBoundaryOptions = {
1195
+ export const moduleBoundaryOptions = { // used by its own test
1179
1196
  allow: [],
1180
1197
  buildTargets: ["build"],
1181
1198
  enforceBuildableLibDependency: false,
@@ -1227,6 +1244,7 @@ var _ = blocked.Name
1227
1244
  * @returns {string[]}
1228
1245
  */
1229
1246
  export function buildLayoutNxTree(root, { boundaryConfig = "module-boundaries.config.mjs" } = {}) {
1247
+ // used by its own test
1230
1248
  const write = writeIn(root);
1231
1249
  write(
1232
1250
  "nx.json",
@@ -1262,7 +1280,7 @@ export function buildLayoutNxTree(root, { boundaryConfig = "module-boundaries.co
1262
1280
  * @param {{boundaryConfig?: string}} [options]
1263
1281
  * @returns {string[]}
1264
1282
  */
1265
- export function buildLayoutNativeTree(
1283
+ export function buildLayoutNativeTree( // used by its own test
1266
1284
  root,
1267
1285
  { boundaryConfig = "module-boundaries.config.mjs" } = {},
1268
1286
  ) {
@@ -61,6 +61,7 @@ function projectOfFactory(projects) {
61
61
  * stale `coverage.exempt` row.
62
62
  */
63
63
  export function discover({ root, files, readFile }) {
64
+ // used by its own test
64
65
  const model = loadNativeModel(root, { readFile });
65
66
  const { projects, failures: discoveryFailures } = discoverNativeProjects({
66
67
  root,
@@ -128,7 +129,7 @@ export function discover({ root, files, readFile }) {
128
129
  * @param {{discovered: ReturnType<typeof discover>, importSites: object[]}} args
129
130
  * @returns {{nodes: Record<string, object>, dependencies: Record<string, object[]>, workspaceLayout?: {appsDir: string, libsDir: string}, exemptedFiles?: string[]}}
130
131
  */
131
- export function buildGraph({ discovered, importSites }) {
132
+ function buildGraph({ discovered, importSites }) {
132
133
  return buildNativeGraph({
133
134
  projects: discovered.projects,
134
135
  importSites,
@@ -191,6 +191,7 @@ function parseArchkeepJson(text) {
191
191
  * for a tree that kept Nx-shaped project boundaries without keeping Nx.
192
192
  */
193
193
  export const DEFAULT_MANIFEST_NAMES = Object.freeze([
194
+ // used by its own test
194
195
  "project.json",
195
196
  "package.json",
196
197
  "go.mod",
@@ -255,6 +256,7 @@ const PROJECT_TYPES = ["app", "lib", "e2e"];
255
256
  * @see DEFAULT_MANIFEST_NAMES
256
257
  */
257
258
  export const DEFAULT_INFER_EXCLUDE = Object.freeze([
259
+ // used by its own test
258
260
  "**/docs/**",
259
261
  "**/fixtures/**",
260
262
  "**/__fixtures__/**",
@@ -549,6 +551,7 @@ const TOP_LEVEL_KEYS = [
549
551
  * @returns {string[]}
550
552
  */
551
553
  export function findNativeModelViolations(raw) {
554
+ // used by its own test
552
555
  if (!isPlainObject(raw)) return [`archkeep.json: expected an object, got ${describe(raw)}`];
553
556
 
554
557
  const violations = [];
@@ -699,6 +702,7 @@ export function findNativeModelViolations(raw) {
699
702
  * @returns {object} `NativeModel` — see `./index.mjs`.
700
703
  */
701
704
  export function normalizeNativeModel(raw) {
705
+ // used by its own test
702
706
  // `raw.projects` may be absent — `findNativeModelViolations` above validates
703
707
  // that shape the same way it validates `projects: {}`, so this has to read
704
708
  // it back the same way rather than assume the key is always present.
@@ -89,6 +89,7 @@ function typeName(value) {
89
89
  * JSON document can carry.
90
90
  */
91
91
  export function envelopeFieldPaths(envelope) {
92
+ // used by its own test
92
93
  if (envelope === null || typeof envelope !== "object" || Array.isArray(envelope)) {
93
94
  throw new Error(
94
95
  `archkeep: refusing to build an envelope roster over ${envelope === null ? "null" : `a ${Array.isArray(envelope) ? "array" : typeof envelope}`} ` +
@@ -152,6 +153,7 @@ function walk(value, path, byPath) {
152
153
  * @returns {{added: string[], removed: string[]}}
153
154
  */
154
155
  export function compareFieldPaths(recorded, observed) {
156
+ // used by its own test
155
157
  const recordedSet = new Set(recorded);
156
158
  const observedSet = new Set(observed);
157
159
  return {
@@ -39,6 +39,7 @@
39
39
  */
40
40
  import { EXIT_FOR_STATUS } from "../verdict.mjs";
41
41
  import { verdictForStatus } from "../governance/verdict.mjs";
42
+ import { describe, isNonEmptyString } from "../values.mjs";
42
43
  import { createRequire } from "node:module";
43
44
 
44
45
  const require = createRequire(import.meta.url);
@@ -163,10 +164,11 @@ export function jsonEnvelope({ command, context, status, exitCode, coverage, res
163
164
  // `result`. A hand-built "fail" with no findings is still a loud lie
164
165
  // (status "findings", exitCode 1, verdict "fail"), never the silent
165
166
  // direction.
166
- if (decision.verdict === "unknown" && decision.reason === undefined) {
167
+ if (decision.verdict === "unknown" && !isNonEmptyString(decision.reason)) {
167
168
  throw new Error(
168
169
  `archkeep: refusing to build a JSON envelope where an "unknown" decision has no reason — ` +
169
- `I3: an unknown verdict must say why no verdict was reached, or it reads as a shrug. ` +
170
+ `I3: an unknown verdict must say why no verdict was reached, or it reads as a shrug ` +
171
+ `(a reason of ${describe(decision.reason)} is not a reason). ` +
170
172
  `This is a bug in the command that built the envelope.`,
171
173
  );
172
174
  }
@@ -62,15 +62,15 @@ import { TSCONFIG_PATHS_MESSAGE_IDS, TSCONFIG_PATHS_MESSAGES } from "../tsconfig
62
62
  import { formatConstraint } from "./text.mjs";
63
63
 
64
64
  /** The schema every consumer of this file validates against. */
65
- export const SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
66
- export const SARIF_VERSION = "2.1.0";
65
+ const SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
66
+ const SARIF_VERSION = "2.1.0";
67
67
 
68
68
  /**
69
69
  * The single rule id every failing fitness function's result shares — see
70
70
  * `sarifRules()`'s own comment for why one id stands in for the whole
71
71
  * open-ended, workspace-declared set.
72
72
  */
73
- export const FITNESS_FAILED_RULE_ID = "fitnessFunctionFailed";
73
+ export const FITNESS_FAILED_RULE_ID = "fitnessFunctionFailed"; // used by its own test
74
74
 
75
75
  /**
76
76
  * A workspace-relative path as a URI reference: each segment percent-encoded,
@@ -106,6 +106,7 @@ export const FITNESS_FAILED_RULE_ID = "fitnessFunctionFailed";
106
106
  * @returns {string}
107
107
  */
108
108
  export function toUriReference(path) {
109
+ // used by its own test
109
110
  return path.split("/").map(encodeURIComponent).join("/");
110
111
  }
111
112
 
@@ -139,6 +140,7 @@ export function toUriReference(path) {
139
140
  * @returns {object[]}
140
141
  */
141
142
  export function sarifRules(customCatalogue = []) {
143
+ // used by its own test
142
144
  return [
143
145
  ...MESSAGE_IDS.map((id) => ({
144
146
  id,
@@ -231,6 +233,7 @@ const CUSTOM_RULE_INDEX_BASE =
231
233
  * @returns {object}
232
234
  */
233
235
  export function sarifCustomRuleResult(finding, ruleIndex) {
236
+ // used by its own test
234
237
  const physicalLocation =
235
238
  finding.sourceFile === undefined
236
239
  ? null
@@ -283,6 +286,7 @@ export function sarifCustomRuleResult(finding, ruleIndex) {
283
286
  * @returns {object}
284
287
  */
285
288
  export function sarifCustomRuleNotification(decision) {
289
+ // used by its own test
286
290
  const posture =
287
291
  decision.verdict === "not_applicable" ? "did not apply to this run" : "could not be judged";
288
292
  return {
@@ -305,6 +309,7 @@ export function sarifCustomRuleNotification(decision) {
305
309
  * @returns {object}
306
310
  */
307
311
  export function sarifResult(violation) {
312
+ // used by its own test
308
313
  const detail =
309
314
  `Import ${JSON.stringify(violation.specifier)} (${violation.kind}) ` +
310
315
  `from ${violation.sourceProject ?? "(no project)"} ` +
@@ -364,7 +369,7 @@ export function sarifResult(violation) {
364
369
  * @param {object} finding A finding from `../go-work.mjs` `compareGoWork`.
365
370
  * @returns {object}
366
371
  */
367
- export function sarifGoWorkResult(finding) {
372
+ function sarifGoWorkResult(finding) {
368
373
  const physicalLocation = { artifactLocation: { uri: toUriReference(finding.file) } };
369
374
  if (finding.line !== null) {
370
375
  physicalLocation.region = { startLine: finding.line, startColumn: finding.column };
@@ -395,7 +400,7 @@ export function sarifGoWorkResult(finding) {
395
400
  * @param {object} finding A finding from `../tsconfig-paths.mjs`.
396
401
  * @returns {object}
397
402
  */
398
- export function sarifTsconfigPathsResult(finding) {
403
+ function sarifTsconfigPathsResult(finding) {
399
404
  return {
400
405
  ruleId: finding.messageId,
401
406
  ruleIndex:
@@ -444,7 +449,7 @@ export function sarifTsconfigPathsResult(finding) {
444
449
  * `declaredEdgeViolationsForCheck`, extended with `file` — workspace-relative.
445
450
  * @returns {object}
446
451
  */
447
- export function sarifDeclaredEdgeResult(finding) {
452
+ function sarifDeclaredEdgeResult(finding) {
448
453
  return {
449
454
  ruleId: finding.messageId,
450
455
  ruleIndex: MESSAGE_IDS.indexOf(finding.messageId),
@@ -472,6 +477,7 @@ export function sarifDeclaredEdgeResult(finding) {
472
477
  * @returns {object}
473
478
  */
474
479
  export function sarifIntentResult(finding) {
480
+ // used by its own test
475
481
  return {
476
482
  ruleId: finding.rule,
477
483
  ruleIndex:
@@ -516,6 +522,7 @@ export function sarifIntentResult(finding) {
516
522
  * @returns {object}
517
523
  */
518
524
  export function sarifFitnessResult(decision, policySource = null) {
525
+ // used by its own test
519
526
  const result = {
520
527
  ruleId: FITNESS_FAILED_RULE_ID,
521
528
  ruleIndex:
@@ -548,6 +555,7 @@ export function sarifFitnessResult(decision, policySource = null) {
548
555
  * @returns {object}
549
556
  */
550
557
  export function sarifFitnessNotification(decision) {
558
+ // used by its own test
551
559
  return {
552
560
  level: "warning",
553
561
  message: {
@@ -567,7 +575,7 @@ export function sarifFitnessNotification(decision) {
567
575
  * @param {object} failure An `AnalysisFailure`.
568
576
  * @returns {object}
569
577
  */
570
- export function sarifNotification(failure) {
578
+ function sarifNotification(failure) {
571
579
  const physicalLocation = { artifactLocation: { uri: toUriReference(failure.sourceFile) } };
572
580
  if (failure.line !== null) {
573
581
  physicalLocation.region = { startLine: failure.line, startColumn: failure.column };
@@ -601,6 +609,7 @@ export function sarifNotification(failure) {
601
609
  * @returns {object}
602
610
  */
603
611
  export function sarifIntentNotification(entry) {
612
+ // used by its own test
604
613
  return {
605
614
  level: "warning",
606
615
  message: {
@@ -641,6 +650,7 @@ const UNOWNED_SAMPLE_LIMIT = 10;
641
650
  * @returns {object}
642
651
  */
643
652
  export function sarifCoverageGapNotification(gap) {
653
+ // used by its own test
644
654
  const manifests = gap.manifests ?? [];
645
655
  const found = manifests.length > 0 ? `: ${manifests.join(", ")}` : "";
646
656
  if (gap.kind === "unregistered-plugin") {
@@ -760,7 +770,7 @@ export function sarifCoverageGapNotification(gap) {
760
770
  * @param {string} decisionRef The cited value, exactly as the row spelled it.
761
771
  * @returns {object}
762
772
  */
763
- export function sarifDecisionRefNotification(decisionRef) {
773
+ function sarifDecisionRefNotification(decisionRef) {
764
774
  return {
765
775
  level: "warning",
766
776
  message: {
@@ -853,6 +863,7 @@ export function sarifDecisionRefNotification(decisionRef) {
853
863
  * @returns {object} A SARIF 2.1.0 log, ready to `JSON.stringify`.
854
864
  */
855
865
  export function buildSarifLog({
866
+ // used by its own test
856
867
  violations,
857
868
  failures,
858
869
  goWork,
@@ -1016,6 +1027,7 @@ export function buildSarifLog({
1016
1027
  * @returns {object}
1017
1028
  */
1018
1029
  export function sarifDeltaResult(entry, site) {
1030
+ // used by its own test
1019
1031
  const target = entry.targetIsSpecifier
1020
1032
  ? `specifier ${JSON.stringify(entry.target)}`
1021
1033
  : entry.target;
@@ -1102,6 +1114,7 @@ export function sarifDeltaResult(entry, site) {
1102
1114
  * @returns {object} A SARIF 2.1.0 log, ready to `JSON.stringify`.
1103
1115
  */
1104
1116
  export function buildDeltaSarifLog({ delta, coverage, customCatalogue = [] }) {
1117
+ // used by its own test
1105
1118
  const customRuleIndex = new Map(
1106
1119
  customCatalogue.map((entry, index) => [entry.ruleId, CUSTOM_RULE_INDEX_BASE + index]),
1107
1120
  );
@@ -35,7 +35,7 @@ export function sanitize(text) {
35
35
  * @param {{name: string, root: string, tags: string[]}} project
36
36
  * @returns {string}
37
37
  */
38
- export function formatProject(project) {
38
+ function formatProject(project) {
39
39
  const tags =
40
40
  project.tags.length > 0 ? ` [${project.tags.map((t) => sanitize(t)).join(", ")}]` : "";
41
41
  return ` ${sanitize(project.name)} ${sanitize(project.root)}${tags}`;
@@ -47,7 +47,7 @@ export function formatProject(project) {
47
47
  * @param {{source: string, target: string, type: string}} edge
48
48
  * @returns {string}
49
49
  */
50
- export function formatEdge(edge) {
50
+ function formatEdge(edge) {
51
51
  return ` ${sanitize(edge.source)} → ${sanitize(edge.target)} (${sanitize(edge.type)})`;
52
52
  }
53
53
 
@@ -57,7 +57,7 @@ export function formatEdge(edge) {
57
57
  * @param {{field: string, baseline: *, head: *}} change
58
58
  * @returns {string}
59
59
  */
60
- export function formatChange(change) {
60
+ function formatChange(change) {
61
61
  const formatValue = (v) => {
62
62
  if (Array.isArray(v)) return v.length > 0 ? v.map((x) => sanitize(x)).join(", ") : "(none)";
63
63
  if (v === null || v === undefined) return "(none)";
@@ -94,6 +94,7 @@ function formatEdge(violation) {
94
94
  * @returns {string}
95
95
  */
96
96
  export function formatViolation(violation, unresolvedDecisionRefs) {
97
+ // used by its own test
97
98
  const message = violation.message
98
99
  .split("\n")
99
100
  .map((line) => (line === "" ? "" : `${CONTINUED}${line}`))
@@ -159,6 +160,7 @@ const formatFailure = (failure) =>
159
160
  * @returns {string}
160
161
  */
161
162
  export function formatFailures(failures) {
163
+ // used by its own test
162
164
  if (failures.length === 0) return "";
163
165
  const unchecked = failures.filter(isWholeFileFailure);
164
166
  const blind = failures.filter((failure) => !isWholeFileFailure(failure));
@@ -236,6 +238,7 @@ export function formatFailures(failures) {
236
238
  * @returns {string} Empty exactly when there is no go.work verdict to render.
237
239
  */
238
240
  export function formatGoWork(goWork) {
241
+ // used by its own test
239
242
  if (goWork == null) return "";
240
243
  const { findings, moduleProjects } = goWork;
241
244
  const modules = `${moduleProjects} Go module project${moduleProjects === 1 ? "" : "s"}`;
@@ -291,6 +294,7 @@ export function formatGoWork(goWork) {
291
294
  * @returns {string} Empty exactly when there is no declared-edge verdict to render.
292
295
  */
293
296
  export function formatDeclaredEdges(declaredEdges, unresolvedDecisionRefs) {
297
+ // used by its own test
294
298
  if (declaredEdges == null) return "";
295
299
  const { findings, judged, declaration = "implicitDependencies" } = declaredEdges;
296
300
  const label = `${judged} implicit edge${judged === 1 ? "" : "s"} judged`;
@@ -345,6 +349,7 @@ export function formatDeclaredEdges(declaredEdges, unresolvedDecisionRefs) {
345
349
  * @returns {string} Empty exactly when there is no paths verdict to render.
346
350
  */
347
351
  export function formatTsconfigPaths(tsconfigPaths) {
352
+ // used by its own test
348
353
  if (tsconfigPaths == null) return "";
349
354
  const { tsConfig, findings, aliases, unjudged } = tsconfigPaths;
350
355
  const judged =
@@ -393,7 +398,7 @@ export function formatTsconfigPaths(tsconfigPaths) {
393
398
  * unresolvedDecisionRefs?: {kind: string, decisionRef: string}[]}|null|undefined} intent
394
399
  * @returns {string} Empty exactly when there is no intent verdict to render.
395
400
  */
396
- export function formatIntentSection(intent) {
401
+ function formatIntentSection(intent) {
397
402
  if (intent == null) return "";
398
403
  const { verdict, findings, unresolved, boundaries, unresolvedDecisionRefs = [] } = intent;
399
404
  const count = boundaries.length;
@@ -542,6 +547,7 @@ function customFindingSite(finding) {
542
547
  * @returns {string} Empty exactly when the policy declared no custom rules.
543
548
  */
544
549
  export function formatCustomRulesSection(customRules) {
550
+ // used by its own test
545
551
  if (customRules == null || customRules.decisions.length === 0) return "";
546
552
  const { decisions, overall } = customRules;
547
553
  const entries = decisions.map((decision) => {
@@ -596,6 +602,7 @@ export function formatCustomRulesSection(customRules) {
596
602
  * @returns {string} Empty exactly when there is no coverage gap to render.
597
603
  */
598
604
  export function formatCoverageGaps(coverageGaps) {
605
+ // used by its own test
599
606
  if (coverageGaps.length === 0) return "";
600
607
  return coverageGaps.map(formatCoverageGap).join("\n");
601
608
  }
@@ -792,6 +799,7 @@ function formatUntrackedFilesGap(gap) {
792
799
  * @returns {string} Empty exactly when no policy identity was supplied.
793
800
  */
794
801
  export function formatPolicy(policy) {
802
+ // used by its own test
795
803
  if (policy == null) return "";
796
804
  const { profile, source, fingerprint } = policy;
797
805
  const law = profile === null ? source : `profile "${profile}" from ${source}`;
@@ -812,7 +820,7 @@ export function formatPolicy(policy) {
812
820
  * @param {Set<string>} [unresolvedDecisionRefs] Forwarded to `formatViolation`.
813
821
  * @returns {string}
814
822
  */
815
- export function formatAcceptedViolations(waived, unresolvedDecisionRefs) {
823
+ function formatAcceptedViolations(waived, unresolvedDecisionRefs) {
816
824
  const rows = waived.map((violation) => {
817
825
  const waiver = violation.waivedBy;
818
826
  return [
@@ -85,7 +85,7 @@ import { posix } from "node:path";
85
85
  * costs is bounded at the other end, by `MAX_SPECIFIER_LENGTH` below, because
86
86
  * a degree-2 pattern is still quadratic in a subject nothing else limits.
87
87
  */
88
- export const MAX_RESPLIT_REPETITIONS = 2;
88
+ const MAX_RESPLIT_REPETITIONS = 2;
89
89
 
90
90
  /**
91
91
  * The most repetitions a SINGLE-DELIMITER chain may carry — the one shape
@@ -114,7 +114,7 @@ export const MAX_RESPLIT_REPETITIONS = 2;
114
114
  * path pattern naming every segment of `libs/<area>/<name>/<entry>` needs
115
115
  * four.
116
116
  */
117
- export const MAX_DELIMITED_SEGMENTS = 8;
117
+ export const MAX_DELIMITED_SEGMENTS = 8; // used by its own test
118
118
 
119
119
  /**
120
120
  * The longest import specifier this engine will match a consumer-written
@@ -148,7 +148,7 @@ export const MAX_DELIMITED_SEGMENTS = 8;
148
148
  * 1024 `b`s. That is the number to compare a change here against; before this
149
149
  * bound and the refusals above, the same sweep found 59 SECONDS.
150
150
  */
151
- export const MAX_SPECIFIER_LENGTH = 1024;
151
+ export const MAX_SPECIFIER_LENGTH = 1024; // used by its own test
152
152
 
153
153
  /**
154
154
  * The deepest group nesting this model reads. Past it a pattern is refused
@@ -1123,6 +1123,7 @@ function tradesWithNeighbour(elements, index, last) {
1123
1123
  * @returns {string|null}
1124
1124
  */
1125
1125
  export function regexComplexityError(source) {
1126
+ // used by its own test
1126
1127
  const { root, tooDeep } = parsePattern(source);
1127
1128
  if (tooDeep) {
1128
1129
  return (
@@ -1174,7 +1175,7 @@ function resplitReason(degree) {
1174
1175
  * @param {string} specifier
1175
1176
  * @returns {string|null}
1176
1177
  */
1177
- export function specifierLengthError(specifier) {
1178
+ function specifierLengthError(specifier) {
1178
1179
  if (specifier.length <= MAX_SPECIFIER_LENGTH) return null;
1179
1180
  return (
1180
1181
  `is ${specifier.length} characters, past the ${MAX_SPECIFIER_LENGTH} this engine will match ` +
@@ -1538,7 +1539,7 @@ export function findMatchingProjects(patterns, nodes) {
1538
1539
  * anything a real suppression, exemption or project rule plausibly needs to
1539
1540
  * name from ONE glob string.
1540
1541
  */
1541
- export const MAX_GLOB_EXPANSIONS = 512;
1542
+ export const MAX_GLOB_EXPANSIONS = 512; // used by its own test
1542
1543
 
1543
1544
  /**
1544
1545
  * A brace group's content matches one of these two shapes instead of a
@@ -1620,6 +1621,7 @@ function rangeCardinality(content) {
1620
1621
  * certain to exceed `cap`.
1621
1622
  */
1622
1623
  export function braceExpansionCount(pattern, cap) {
1624
+ // used by its own test
1623
1625
  const limit = cap + 1;
1624
1626
  /** @type {{alternatives: number, branchProduct: number, start: number}[]} */
1625
1627
  const frames = [{ alternatives: 0, branchProduct: 1, start: 0 }];
@@ -48,6 +48,7 @@ export const DEFAULT_WORKSPACE_LAYOUT = Object.freeze({ libsDir: "libs", appsDir
48
48
 
49
49
  /** `./x` or `../x` — upstream's `isRelative`, from `runtime-lint-utils`. */
50
50
  export function isRelative(s) {
51
+ // used by its own test
51
52
  return s.startsWith("./") || s.startsWith("../");
52
53
  }
53
54
 
@@ -191,6 +192,7 @@ export function getTargetProjectBasedOnRelativeImport(imp, sourceFile, projectRo
191
192
  * @returns {boolean}
192
193
  */
193
194
  export function isConstraintBanningProject(externalProject, constraint, imp) {
195
+ // used by its own test
194
196
  assertMatchableSpecifier(imp, "import specifier judged against the constraint table");
195
197
  const { allowedExternalImports, bannedExternalImports } = constraint;
196
198
  const { packageName } = externalProject.data;