@ecoma-io/archkeep 0.23.0 → 0.24.1

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 (83) 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 +341 -0
  13. package/src/analysis/python.mjs +8 -0
  14. package/src/analysis/rust.mjs +5 -1
  15. package/src/analysis/source-util.mjs +6 -5
  16. package/src/analysis/typescript.mjs +148 -0
  17. package/src/architecture-intent/model.mjs +13 -8
  18. package/src/architecture-intent/selectors.mjs +2 -1
  19. package/src/commands/change-intent.mjs +10 -9
  20. package/src/commands/change.mjs +17 -1
  21. package/src/commands/check.mjs +177 -17
  22. package/src/commands/completeness.mjs +7 -6
  23. package/src/commands/context-command.mjs +12 -20
  24. package/src/commands/coverage-acceptance.mjs +46 -0
  25. package/src/commands/coverage-verdict.mjs +12 -2
  26. package/src/commands/custom-rules.mjs +1 -0
  27. package/src/commands/delta-classify.mjs +3 -0
  28. package/src/commands/delta-snapshot.mjs +3 -6
  29. package/src/commands/delta.mjs +35 -21
  30. package/src/commands/drift.mjs +1 -1
  31. package/src/commands/evaluation-primitives.mjs +4 -4
  32. package/src/commands/evolution.mjs +2 -0
  33. package/src/commands/explain.mjs +19 -20
  34. package/src/commands/graph.mjs +44 -15
  35. package/src/commands/health.mjs +4 -0
  36. package/src/commands/history.mjs +2 -0
  37. package/src/commands/plan-context-command.mjs +9 -2
  38. package/src/commands/policy.mjs +8 -5
  39. package/src/commands/provenance.mjs +8 -2
  40. package/src/commands/scenario-evaluation.mjs +1 -1
  41. package/src/commands/trajectory.mjs +2 -1
  42. package/src/config.mjs +170 -2
  43. package/src/custom-rules/host.mjs +3 -3
  44. package/src/errors.mjs +23 -1
  45. package/src/eslint-config.mjs +3 -5
  46. package/src/fixtures/evolution-lifecycle/workspace.mjs +15 -4
  47. package/src/go-work.mjs +1 -1
  48. package/src/governance/adr-registry.mjs +6 -2
  49. package/src/governance/debt-ledger.mjs +1 -1
  50. package/src/governance/decision-fitness.mjs +2 -0
  51. package/src/governance/decision-graph.mjs +1 -0
  52. package/src/governance/discovery-proposal.mjs +8 -2
  53. package/src/governance/evolution-event.mjs +42 -0
  54. package/src/governance/evolution-store.mjs +3 -2
  55. package/src/governance/fitness-registry.mjs +2 -3
  56. package/src/governance/preset-fingerprints.json +14 -14
  57. package/src/governance/profile-registry.mjs +22 -3
  58. package/src/governance/provenance-record.mjs +4 -1
  59. package/src/governance/reconcile-score.mjs +4 -0
  60. package/src/governance/row-schema.mjs +1 -3
  61. package/src/governance/verdict.mjs +1 -0
  62. package/src/governance/waiver.mjs +1 -0
  63. package/src/intent/intent-manifest.json +6 -6
  64. package/src/intent/mask-non-code.mjs +1 -0
  65. package/src/lsp/diagnostics.mjs +3 -2
  66. package/src/lsp/protocol.mjs +2 -1
  67. package/src/lsp/server.mjs +3 -0
  68. package/src/lsp/workspace-index.mjs +3 -1
  69. package/src/providers/moon.mjs +5 -5
  70. package/src/providers/native/differential.fixtures.mjs +29 -11
  71. package/src/providers/native/index.mjs +2 -1
  72. package/src/providers/native/model.mjs +4 -0
  73. package/src/report/envelope-shape.mjs +2 -0
  74. package/src/report/sarif.mjs +21 -8
  75. package/src/report/snapshot-text.mjs +3 -3
  76. package/src/report/text.mjs +10 -2
  77. package/src/rules/index.mjs +30 -0
  78. package/src/rules/match.mjs +7 -5
  79. package/src/rules/specifiers.mjs +2 -0
  80. package/src/rules/tags.mjs +3 -2
  81. package/src/rules/topology.mjs +6 -1
  82. package/src/verdict.mjs +33 -2
  83. package/src/workspace.mjs +1 -0
@@ -20,7 +20,7 @@
20
20
  "type": "architecture-test",
21
21
  "path": "src/conformance/boundary.test.mjs",
22
22
  "assertion": "SHIPPED_PACKAGES allow-list prevents unapproved dependencies; specifiersIn() walk catches all imports including createRequire; no provider import found in core layers",
23
- "sha256": "12d4e179f88de2622a41e56f42b2bab1c88a677ec2232430752138c57ba5fb18"
23
+ "sha256": "e4e24ed9f36e31126249c7d079b3656d17ac78ee4f12033321467ffa6666f033"
24
24
  },
25
25
  {
26
26
  "type": "source-evidence",
@@ -104,13 +104,13 @@
104
104
  "type": "source-evidence",
105
105
  "path": "src/commands/graph.mjs",
106
106
  "assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2",
107
- "sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
107
+ "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
108
108
  },
109
109
  {
110
110
  "type": "source-evidence",
111
111
  "path": "src/commands/graph.mjs",
112
112
  "assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy",
113
- "sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
113
+ "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
114
114
  }
115
115
  ],
116
116
  "status": "proven"
@@ -248,7 +248,7 @@
248
248
  "type": "source-evidence",
249
249
  "path": "src/commands/context-command.mjs",
250
250
  "assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
251
- "sha256": "1f054400e864c381e65904bc20095f4eb6df3198b19ffcb985895af7ec31d80d"
251
+ "sha256": "f906671ff4a5720d80ea5a3dab2779f1cfd3648fbc1e6a1f3a6d0e6efef21feb"
252
252
  },
253
253
  {
254
254
  "type": "source-evidence",
@@ -286,7 +286,7 @@
286
286
  "type": "source-evidence",
287
287
  "path": "src/commands/graph.mjs",
288
288
  "assertion": "Plain string comparison throughout; never localeCompare",
289
- "sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
289
+ "sha256": "436902ab3ca994c233318437a896630f969a2c8a22b0f93e598d309faed39d49"
290
290
  }
291
291
  ],
292
292
  "status": "proven"
@@ -312,7 +312,7 @@
312
312
  "type": "source-evidence",
313
313
  "path": "src/providers/moon.mjs",
314
314
  "assertion": "inferWorkspaceLayout returns null for partial layouts — same all-or-nothing contract as Nx and Native",
315
- "sha256": "f461cfaa2c8267a78fa9afa4b3267e92be4a85ff5f370b9e4b613a1fb85fc211"
315
+ "sha256": "2b874da56bda5bc6cfcfb4a984556ace0eafe3a6a98d964c78204bd96cc30e48"
316
316
  },
317
317
  {
318
318
  "type": "behavioral-test",
@@ -536,6 +536,7 @@ function scanTemplate(src, i) {
536
536
  * same line structure.
537
537
  */
538
538
  export function maskNonCode(src) {
539
+ // used by its own test
539
540
  /**
540
541
  * Chunks of the result, joined once at the end. Code is copied through in
541
542
  * RUNS rather than a token at a time — `plainFrom` is where the current
@@ -38,7 +38,7 @@ import { DIAGNOSTIC_SEVERITY, SERVER_INFO } from "./protocol.mjs";
38
38
  * prints beside the message to say which tool spoke. Taken from the server's
39
39
  * own identity so the two can never disagree.
40
40
  */
41
- export const DIAGNOSTIC_SOURCE = SERVER_INFO.name;
41
+ export const DIAGNOSTIC_SOURCE = SERVER_INFO.name; // used by its own test
42
42
 
43
43
  /**
44
44
  * The `code` on a diagnostic that reports the ABSENCE of a verdict rather than
@@ -46,7 +46,7 @@ export const DIAGNOSTIC_SOURCE = SERVER_INFO.name;
46
46
  * against `MESSAGE_IDS` at load so a future upstream id cannot silently collide
47
47
  * with it.
48
48
  */
49
- export const ANALYSIS_FAILURE_CODE = "analysisFailure";
49
+ export const ANALYSIS_FAILURE_CODE = "analysisFailure"; // used by its own test
50
50
 
51
51
  if (MESSAGE_IDS.includes(ANALYSIS_FAILURE_CODE)) {
52
52
  throw new Error(
@@ -100,6 +100,7 @@ const QUOTES = new Set(['"', "'", "`"]);
100
100
  * @returns {{start: {line: number, character: number}, end: {line: number, character: number}}}
101
101
  */
102
102
  export function rangeAt(at, lines) {
103
+ // used by its own test
103
104
  // A failure about the file as a whole carries no position (`contract.md`
104
105
  // fixes it as an explicit `null`). It gets the first line, whole: a
105
106
  // zero-width range at the origin renders as an invisible caret in most
@@ -95,7 +95,7 @@ export const DIAGNOSTIC_SEVERITY = Object.freeze({
95
95
  * more is not a supported conversation, and saying so beats waiting for bytes
96
96
  * that will never arrive.
97
97
  */
98
- export const MAX_CONTENT_LENGTH = 64 * 1024 * 1024;
98
+ export const MAX_CONTENT_LENGTH = 64 * 1024 * 1024; // used by its own test
99
99
 
100
100
  /**
101
101
  * LSP `MessageType`, as `window/showMessage` reports it.
@@ -211,5 +211,6 @@ export function uriToPath(uri) {
211
211
  * @returns {string}
212
212
  */
213
213
  export function pathToUri(path) {
214
+ // used by its own test
214
215
  return pathToFileURL(path).href;
215
216
  }
@@ -292,6 +292,7 @@ const POLYGLOT_GRAPH_MANIFESTS = Object.freeze([
292
292
  * @returns {readonly string[]}
293
293
  */
294
294
  export function watchedFilesFor(options, { unresolved = false } = {}) {
295
+ // used by its own test
295
296
  return Object.freeze([
296
297
  ...(typeof options.boundaryConfig === "string" ? [options.boundaryConfig] : []),
297
298
  ...(unresolved
@@ -376,6 +377,7 @@ function markersAt(root) {
376
377
  * convention chain can find while carrying files that need one.
377
378
  */
378
379
  export function readWorkspaceOptions(root) {
380
+ // used by its own test
379
381
  const { hasNx, hasNative } = markersAt(root);
380
382
  if (hasNx && hasNative) {
381
383
  throw new Error(
@@ -450,6 +452,7 @@ const WATCHER_REGISTRATION_ID = "archkeep/watched-files";
450
452
  * text than the one on screen.
451
453
  */
452
454
  export const SERVER_CAPABILITIES = Object.freeze({
455
+ // used by its own test
453
456
  textDocumentSync: Object.freeze({
454
457
  openClose: true,
455
458
  change: TEXT_DOCUMENT_SYNC_KIND.full,
@@ -156,7 +156,7 @@ export { PROJECT_CONFIG_FILE, nodeTypeOf, buildDependencies };
156
156
  * @returns {object} Whatever the JSON describes.
157
157
  * @throws {Error} when neither parser can read it.
158
158
  */
159
- export const parseProjectJson = parseNxJson;
159
+ const parseProjectJson = parseNxJson;
160
160
 
161
161
  /**
162
162
  * Every file git considers part of the working tree, workspace-relative and
@@ -205,6 +205,7 @@ const directoryOf = (file) => {
205
205
  * @returns {{projects: {name: string, root: string, config: object}[], skipped: {file: string, reason: string}[]}}
206
206
  */
207
207
  export function discoverProjects({ files, readFile }) {
208
+ // used by its own test
208
209
  const projects = [];
209
210
  const skipped = [];
210
211
  for (const file of files) {
@@ -263,6 +264,7 @@ export function discoverProjects({ files, readFile }) {
263
264
  * publishes the collision through `indexGaps`.
264
265
  */
265
266
  export function buildNodes(projects) {
267
+ // used by its own test
266
268
  // Null-prototype for the same reason `../providers/native/graph.mjs` and
267
269
  // `../providers/moon.mjs` use them: every key here is a project NAME, and
268
270
  // project names come from a `project.json`'s own `name` field —
@@ -31,6 +31,7 @@ import { existsSync } from "node:fs";
31
31
  import { delimiter, join, posix } from "node:path";
32
32
 
33
33
  import { environmentForTree, runProcess } from "../process.mjs";
34
+ import { isEnoent } from "../errors.mjs";
34
35
  import { buildDependencies } from "./native/graph.mjs";
35
36
 
36
37
  /**
@@ -905,16 +906,15 @@ function resolveMoonCli(workspaceRoot, { resolveMoon = () => "moon" } = {}) {
905
906
  * `nxCli` guards against from the other direction, where only a
906
907
  * `MODULE_NOT_FOUND` earns the "not installed" story.
907
908
  *
908
- * `../process.mjs`'s `runProcess` wraps the child's failure and carries the
909
- * original on `cause`, so the code is read from there; the direct `code` is
910
- * read too, for a `run` seam that surfaces a spawn error unwrapped.
909
+ * The shape test itself is `../errors.mjs`'s `isEnoent` the name stays
910
+ * because what this file asks is not "is this ENOENT" but "does this failure
911
+ * carry an install action".
911
912
  *
912
913
  * @param {unknown} error
913
914
  * @returns {boolean}
914
915
  */
915
916
  function isMoonBinaryMissing(error) {
916
- const thrown = /** @type {{code?: unknown, cause?: {code?: unknown}}|null|undefined} */ (error);
917
- return thrown?.code === "ENOENT" || thrown?.cause?.code === "ENOENT";
917
+ return isEnoent(error);
918
918
  }
919
919
 
920
920
  /**
@@ -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 {
@@ -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)";