@variance-authority/core 0.15.0 → 0.17.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 (82) hide show
  1. package/CHANGELOG.md +424 -0
  2. package/README.md +25 -7
  3. package/dist/attribute/component-hash.d.ts +11 -3
  4. package/dist/attribute/component-hash.js +11 -3
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/movement.d.ts +1 -1
  7. package/dist/attribute/movement.js.map +1 -1
  8. package/dist/attribute/region.d.ts +47 -4
  9. package/dist/attribute/region.js +90 -17
  10. package/dist/attribute/region.js.map +1 -1
  11. package/dist/attribute/source.d.ts +1 -1
  12. package/dist/attribute/source.js +1 -1
  13. package/dist/attribute/source.js.map +1 -1
  14. package/dist/compare/band.d.ts +8 -2
  15. package/dist/compare/band.js +7 -2
  16. package/dist/compare/band.js.map +1 -1
  17. package/dist/compare/diff/attribution.js +6 -4
  18. package/dist/compare/diff/attribution.js.map +1 -1
  19. package/dist/compare/diff/compare-nodes.js +9 -1
  20. package/dist/compare/diff/compare-nodes.js.map +1 -1
  21. package/dist/compare/diff/match.d.ts +2 -1
  22. package/dist/compare/diff/match.js +52 -0
  23. package/dist/compare/diff/match.js.map +1 -1
  24. package/dist/compare/parting.js +6 -4
  25. package/dist/compare/parting.js.map +1 -1
  26. package/dist/format/document.d.ts +26 -3
  27. package/dist/format/document.js.map +1 -1
  28. package/dist/format/index.d.ts +7 -0
  29. package/dist/format/index.js +4 -0
  30. package/dist/format/index.js.map +1 -1
  31. package/dist/format/packed.d.ts +32 -0
  32. package/dist/format/packed.js +23 -0
  33. package/dist/format/packed.js.map +1 -0
  34. package/dist/format/shape.d.ts +31 -0
  35. package/dist/format/shape.js +269 -0
  36. package/dist/format/shape.js.map +1 -0
  37. package/dist/format/stabilize.d.ts +7 -1
  38. package/dist/format/stabilize.js +10 -3
  39. package/dist/format/stabilize.js.map +1 -1
  40. package/dist/format/steps.d.ts +44 -0
  41. package/dist/format/steps.js +67 -0
  42. package/dist/format/steps.js.map +1 -0
  43. package/dist/format/width.d.ts +35 -0
  44. package/dist/format/width.js +38 -0
  45. package/dist/format/width.js.map +1 -0
  46. package/dist/judge/docket.js +1 -0
  47. package/dist/judge/docket.js.map +1 -1
  48. package/dist/judge/index.d.ts +1 -0
  49. package/dist/judge/index.js +1 -0
  50. package/dist/judge/index.js.map +1 -1
  51. package/dist/judge/restamp.d.ts +20 -0
  52. package/dist/judge/restamp.js +20 -0
  53. package/dist/judge/restamp.js.map +1 -0
  54. package/dist/relate/before.js +2 -2
  55. package/dist/relate/before.js.map +1 -1
  56. package/dist/relate/carried.d.ts +1 -1
  57. package/dist/relate/carried.js +5 -5
  58. package/dist/relate/carried.js.map +1 -1
  59. package/dist/relate/graph.d.ts +14 -12
  60. package/dist/relate/graph.js +12 -11
  61. package/dist/relate/graph.js.map +1 -1
  62. package/dist/relate/index.d.ts +1 -2
  63. package/dist/relate/index.js +1 -2
  64. package/dist/relate/index.js.map +1 -1
  65. package/dist/relate/reach.d.ts +5 -5
  66. package/dist/relate/reach.js.map +1 -1
  67. package/dist/relate/records.d.ts +5 -4
  68. package/dist/relate/records.js +3 -2
  69. package/dist/relate/records.js.map +1 -1
  70. package/dist/rules/normalize/fixture.d.ts +2 -0
  71. package/dist/rules/normalize/fixture.js +2 -1
  72. package/dist/rules/normalize/fixture.js.map +1 -1
  73. package/dist/shard/placement.d.ts +10 -8
  74. package/dist/shard/placement.js +7 -21
  75. package/dist/shard/placement.js.map +1 -1
  76. package/dist/share/index.d.ts +1 -1
  77. package/dist/share/index.js +1 -1
  78. package/dist/share/index.js.map +1 -1
  79. package/package.json +2 -2
  80. package/dist/relate/dominate.d.ts +0 -19
  81. package/dist/relate/dominate.js +0 -77
  82. package/dist/relate/dominate.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ /** The part of a subject its width is spelled into. */
2
+ export interface Widenable {
3
+ readonly subject: {
4
+ readonly id: string;
5
+ };
6
+ /** What a baseline is filed by under its title; each width carries its own. */
7
+ readonly name?: string;
8
+ }
9
+ /**
10
+ * The subject read at one width: its id and its name, each with the width.
11
+ *
12
+ * A width is a subject of its own — its own baseline, its own verdict — so the
13
+ * width goes into everything a baseline is found by. The id carries it as
14
+ * `<id>@<width>`, and so must the name, or a baseline path filed by title and
15
+ * name would hold every width in one file.
16
+ *
17
+ * One spelling for every party that names a width: the collector that plans or
18
+ * visits one, and the run that asks a store which baselines a story it did not
19
+ * visit may have left at widths it never read.
20
+ */
21
+ export declare function atWidth<S extends Widenable>(subject: S, width: number): S;
22
+ /**
23
+ * The id a width id was read from: `story:card@375` is `story:card` at 375.
24
+ * Absent for an id that carries no width, so that a party asking whether a
25
+ * realized id belongs to a planned one never takes a story whose id merely
26
+ * begins with the planned one for one of its widths.
27
+ */
28
+ export declare function widenedFrom(id: string): string | undefined;
29
+ /**
30
+ * Whether a visit to `planned` may realize `id`: the planned subject itself, or
31
+ * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely
32
+ * begins with the planned one is never filed as one of its readings.
33
+ */
34
+ export declare function realizes(planned: string, id: string): boolean;
35
+ //# sourceMappingURL=width.d.ts.map
@@ -0,0 +1,38 @@
1
+ // compass: variance-authority.normalization
2
+ /**
3
+ * The subject read at one width: its id and its name, each with the width.
4
+ *
5
+ * A width is a subject of its own — its own baseline, its own verdict — so the
6
+ * width goes into everything a baseline is found by. The id carries it as
7
+ * `<id>@<width>`, and so must the name, or a baseline path filed by title and
8
+ * name would hold every width in one file.
9
+ *
10
+ * One spelling for every party that names a width: the collector that plans or
11
+ * visits one, and the run that asks a store which baselines a story it did not
12
+ * visit may have left at widths it never read.
13
+ */
14
+ export function atWidth(subject, width) {
15
+ return {
16
+ ...subject,
17
+ subject: { ...subject.subject, id: `${subject.subject.id}@${width}` },
18
+ ...(subject.name === undefined ? {} : { name: `${subject.name} @${width}` }),
19
+ };
20
+ }
21
+ /**
22
+ * The id a width id was read from: `story:card@375` is `story:card` at 375.
23
+ * Absent for an id that carries no width, so that a party asking whether a
24
+ * realized id belongs to a planned one never takes a story whose id merely
25
+ * begins with the planned one for one of its widths.
26
+ */
27
+ export function widenedFrom(id) {
28
+ return /^(.+)@\d+$/.exec(id)?.[1];
29
+ }
30
+ /**
31
+ * Whether a visit to `planned` may realize `id`: the planned subject itself, or
32
+ * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely
33
+ * begins with the planned one is never filed as one of its readings.
34
+ */
35
+ export function realizes(planned, id) {
36
+ return id === planned || widenedFrom(id) === planned;
37
+ }
38
+ //# sourceMappingURL=width.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"width.js","sourceRoot":"","sources":["../../src/format/width.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAS5C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CAAsB,OAAU,EAAE,KAAa;IACpE,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,EAAE,EAAE;QACrE,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,EAAU;IAClD,OAAO,EAAE,KAAK,OAAO,IAAI,WAAW,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;AACvD,CAAC","sourcesContent":["// compass: variance-authority.normalization\n\n/** The part of a subject its width is spelled into. */\nexport interface Widenable {\n readonly subject: { readonly id: string };\n /** What a baseline is filed by under its title; each width carries its own. */\n readonly name?: string;\n}\n\n/**\n * The subject read at one width: its id and its name, each with the width.\n *\n * A width is a subject of its own — its own baseline, its own verdict — so the\n * width goes into everything a baseline is found by. The id carries it as\n * `<id>@<width>`, and so must the name, or a baseline path filed by title and\n * name would hold every width in one file.\n *\n * One spelling for every party that names a width: the collector that plans or\n * visits one, and the run that asks a store which baselines a story it did not\n * visit may have left at widths it never read.\n */\nexport function atWidth<S extends Widenable>(subject: S, width: number): S {\n return {\n ...subject,\n subject: { ...subject.subject, id: `${subject.subject.id}@${width}` },\n ...(subject.name === undefined ? {} : { name: `${subject.name} @${width}` }),\n };\n}\n\n/**\n * The id a width id was read from: `story:card@375` is `story:card` at 375.\n * Absent for an id that carries no width, so that a party asking whether a\n * realized id belongs to a planned one never takes a story whose id merely\n * begins with the planned one for one of its widths.\n */\nexport function widenedFrom(id: string): string | undefined {\n return /^(.+)@\\d+$/.exec(id)?.[1];\n}\n\n/**\n * Whether a visit to `planned` may realize `id`: the planned subject itself, or\n * it at a width, `<planned>@<digits>`. Nothing else, so a story whose id merely\n * begins with the planned one is never filed as one of its readings.\n */\nexport function realizes(planned: string, id: string): boolean {\n return id === planned || widenedFrom(id) === planned;\n}\n"]}
@@ -117,6 +117,7 @@ function isStructural(delta) {
117
117
  case 'node-added':
118
118
  case 'node-removed':
119
119
  case 'node-moved':
120
+ case 'tag-changed':
120
121
  case 'role-changed':
121
122
  case 'name-changed':
122
123
  case 'text-changed':
@@ -1 +1 @@
1
- {"version":3,"file":"docket.js","sourceRoot":"","sources":["../../src/judge/docket.ts"],"names":[],"mappings":"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","sourcesContent":["import { loudestBand, type Band } from '../compare/band.js';\nimport type { ChangedComponent, Delta, Root, RootKind, SemanticDiff } from '../compare/diff/index.js';\nimport { aggregateImpact, type AggregateImpact, type PropertyImpact } from '../compare/impact.js';\n\n/**\n * The docket: one entry per root cause, across every subject in a change set.\n *\n * A `SemanticDiff` answers \"what changed in this subject\". That is the wrong unit\n * for review, and reviewing at that unit is what makes visual regression\n * unbearable — one design-token edit arrives as three hundred separate subjects\n * to click through, and the three-hundred-and-first gets approved without being\n * read.\n *\n * The docket inverts it. One token edit is **one entry**, carrying the count of\n * what it reached. Spec §6.2 requires that approving it be a single action, which\n * is only possible if the thing being approved is the cause rather than each of\n * its effects.\n *\n * Aggregation is by root **id**, which is why root ids are constructed to be\n * stable across subjects: `token:--color-primary` is the same root wherever it\n * lands, so the grouping needs no similarity heuristic.\n */\n\nexport interface DocketEntry {\n readonly rootId: string;\n readonly kind: RootKind;\n readonly label: string;\n readonly band: Band;\n readonly impact: AggregateImpact;\n\n /** Subjects this root reached, in first-seen order. */\n readonly subjects: readonly string[];\n readonly subjectCount: number;\n /** Individual deltas across all subjects — the collateral count. */\n readonly deltaCount: number;\n\n readonly components: readonly ChangedComponent[];\n\n /**\n * A few subjects to actually look at.\n *\n * Mass re-baselining is only survivable if approval is *informed*, and nobody\n * informs themselves by reviewing three hundred identical diffs. Spec §7.3 asks\n * for a sampled spot-check list, and this is it.\n */\n readonly sample: readonly string[];\n\n /**\n * Distinct places this root's changes were observed, most common first.\n *\n * Carried on the entry rather than derived from deltas at report time, because\n * an entry spans subjects and the deltas behind it are not kept. Three is\n * enough to orient without turning a collapsed root back into a list — the\n * thing collapsing it was for.\n */\n readonly places: readonly string[];\n\n /**\n * `true` when nothing under this root added, removed, moved, or renamed a node.\n *\n * The other half of the sentence the product promises. \"One token change, 300\n * collateral\" is only reassuring alongside \"structure intact\" — that is what\n * makes it a one-action approval rather than three hundred things to check.\n */\n readonly structureIntact: boolean;\n}\n\nexport interface Docket {\n readonly entries: readonly DocketEntry[];\n readonly subjectsCompared: number;\n readonly subjectsUnchanged: number;\n readonly subjectsChanged: number;\n\n /**\n * `true` when every change in the set has one explanation.\n *\n * The condition under which the whole review collapses to a single action.\n */\n readonly singleRoot: boolean;\n}\n\nexport interface DocketOptions {\n /** Subjects listed per entry for spot-checking. Policy, not a constant. */\n readonly sampleSize?: number;\n}\n\nexport function buildDocket(\n diffs: readonly SemanticDiff[],\n options: DocketOptions = {},\n): Docket {\n const sampleSize = options.sampleSize ?? 3;\n const groups = new Map<string, Accumulator>();\n\n for (const diff of diffs) {\n if (diff.identical) continue;\n\n for (const root of diff.roots) {\n const entry = groups.get(root.id) ?? newAccumulator(root);\n groups.set(root.id, entry);\n\n entry.subjects.add(diff.subjectId);\n entry.deltas.push(...root.deltas);\n for (const component of componentsUnder(root, diff.components)) {\n mergeComponent(entry.components, component);\n }\n }\n }\n\n const entries = [...groups.values()]\n .map((entry) => finalize(entry, sampleSize))\n // Widest blast radius first: a root touching forty subjects is the review,\n // and one touching a single subject is a detail underneath it.\n .sort((a, b) => b.subjectCount - a.subjectCount || b.deltaCount - a.deltaCount);\n\n const changed = diffs.filter((diff) => !diff.identical).length;\n\n return {\n entries,\n subjectsCompared: diffs.length,\n subjectsUnchanged: diffs.length - changed,\n subjectsChanged: changed,\n singleRoot: entries.length === 1,\n };\n}\n\n/**\n * The docket as one sentence per entry.\n *\n * Deliberately terse. The audience is a reviewer deciding whether to look\n * further and an agent deciding whether to act, and both are worse served by a\n * paragraph than by a line they can scan.\n */\nexport function summarize(docket: Docket): string {\n if (docket.entries.length === 0) {\n return `${docket.subjectsCompared} subjects compared, nothing changed.`;\n }\n\n const header =\n `${docket.subjectsChanged} of ${docket.subjectsCompared} subjects changed, ` +\n `explained by ${docket.entries.length} root${docket.entries.length === 1 ? '' : 's'}.`;\n\n const lines = docket.entries.map((entry) => {\n const reach =\n entry.subjectCount === 1\n ? `${entry.deltaCount} change${entry.deltaCount === 1 ? '' : 's'}`\n : `${entry.subjectCount} subjects, ${entry.deltaCount} collateral`;\n\n const structure = entry.structureIntact ? 'structure intact' : 'structure changed';\n const components =\n entry.components.length > 0\n ? ` — ${entry.components\n .slice(0, 4)\n .map((component) => component.name)\n .join(', ')}`\n : '';\n\n return (\n ` ${entry.kind}: ${entry.label} — ${reach}, ${entry.band}/${entry.impact}, ` +\n `${structure}${components}`\n );\n });\n\n return [header, ...lines].join('\\n');\n}\n\ninterface Accumulator {\n readonly root: Root;\n readonly subjects: Set<string>;\n readonly deltas: Delta[];\n readonly components: Map<string, MutableComponent>;\n}\n\ninterface MutableComponent {\n name: string;\n role: 'root' | 'collateral';\n deltaCount: number;\n bands: Set<Band>;\n impacts: (PropertyImpact | 'structural')[];\n renderedIn: Set<string>;\n}\n\nfunction newAccumulator(root: Root): Accumulator {\n return { root, subjects: new Set(), deltas: [], components: new Map() };\n}\n\nfunction finalize(entry: Accumulator, sampleSize: number): DocketEntry {\n const subjects = [...entry.subjects];\n\n return {\n rootId: entry.root.id,\n kind: entry.root.kind,\n label: entry.root.label,\n // The root's own band is a floor, not the answer. A token edit whose\n // collateral includes a dropped accessible name is reported at `a11y`: the\n // entry is one review action, so it must be labelled by the loudest thing a\n // reviewer would be signing off on, not by the thing that caused it.\n band: loudestBand([entry.root.band, ...entry.deltas.map((delta) => delta.band)]) ?? entry.root.band,\n impact: aggregateImpact(entry.deltas.map((delta) => delta.impact ?? 'structural')),\n subjects,\n subjectCount: subjects.length,\n deltaCount: entry.deltas.length,\n components: [...entry.components.values()]\n .map((component) => ({\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: [...component.bands],\n impact: aggregateImpact(component.impacts),\n renderedIn: [...component.renderedIn],\n }))\n .sort(\n (a, b) =>\n b.deltaCount - a.deltaCount || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n ),\n sample: subjects.slice(0, sampleSize),\n places: rankedPlaces(entry.deltas),\n structureIntact: !entry.deltas.some(isStructural),\n };\n}\n\n/** Distinct `where` phrases, most frequent first, capped. */\nfunction rankedPlaces(deltas: readonly Delta[]): readonly string[] {\n const counts = new Map<string, number>();\n\n for (const delta of deltas) {\n if (delta.where === undefined || delta.where === '') continue;\n counts.set(delta.where, (counts.get(delta.where) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([where]) => where);\n}\n\n/**\n * Whether a delta changed the shape of the tree rather than a value.\n *\n * A `rect-changed` that was *derived* from a style change does not count: the\n * box moved because a value moved, and calling that a structural change would\n * make every spacing token look like it rearranged the page.\n */\nfunction isStructural(delta: Delta): boolean {\n switch (delta.kind) {\n case 'node-added':\n case 'node-removed':\n case 'node-moved':\n case 'role-changed':\n case 'name-changed':\n case 'text-changed':\n return true;\n case 'rect-changed':\n return delta.derivedFrom === undefined;\n default:\n return false;\n }\n}\n\n/** Components that appear in this root's deltas, from the subject's own view. */\nfunction componentsUnder(\n root: Root,\n components: readonly ChangedComponent[],\n): readonly ChangedComponent[] {\n const names = new Set<string>();\n for (const delta of root.deltas) {\n for (const owner of delta.owners ?? []) names.add(owner.name);\n }\n return components.filter((component) => names.has(component.name));\n}\n\nfunction mergeComponent(\n into: Map<string, MutableComponent>,\n component: ChangedComponent,\n): void {\n const existing = into.get(component.name);\n\n if (!existing) {\n into.set(component.name, {\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: new Set(component.bands),\n impacts: [],\n renderedIn: new Set(component.renderedIn),\n });\n return;\n }\n\n existing.deltaCount += component.deltaCount;\n for (const band of component.bands) existing.bands.add(band);\n for (const parent of component.renderedIn) existing.renderedIn.add(parent);\n // Root in any subject means root: a component that changed internally\n // somewhere is a cause, even if elsewhere it was only along for the ride.\n if (component.role === 'root') existing.role = 'root';\n}\n"]}
1
+ {"version":3,"file":"docket.js","sourceRoot":"","sources":["../../src/judge/docket.ts"],"names":[],"mappings":"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","sourcesContent":["import { loudestBand, type Band } from '../compare/band.js';\nimport type { ChangedComponent, Delta, Root, RootKind, SemanticDiff } from '../compare/diff/index.js';\nimport { aggregateImpact, type AggregateImpact, type PropertyImpact } from '../compare/impact.js';\n\n/**\n * The docket: one entry per root cause, across every subject in a change set.\n *\n * A `SemanticDiff` answers \"what changed in this subject\". That is the wrong unit\n * for review, and reviewing at that unit is what makes visual regression\n * unbearable — one design-token edit arrives as three hundred separate subjects\n * to click through, and the three-hundred-and-first gets approved without being\n * read.\n *\n * The docket inverts it. One token edit is **one entry**, carrying the count of\n * what it reached. Spec §6.2 requires that approving it be a single action, which\n * is only possible if the thing being approved is the cause rather than each of\n * its effects.\n *\n * Aggregation is by root **id**, which is why root ids are constructed to be\n * stable across subjects: `token:--color-primary` is the same root wherever it\n * lands, so the grouping needs no similarity heuristic.\n */\n\nexport interface DocketEntry {\n readonly rootId: string;\n readonly kind: RootKind;\n readonly label: string;\n readonly band: Band;\n readonly impact: AggregateImpact;\n\n /** Subjects this root reached, in first-seen order. */\n readonly subjects: readonly string[];\n readonly subjectCount: number;\n /** Individual deltas across all subjects — the collateral count. */\n readonly deltaCount: number;\n\n readonly components: readonly ChangedComponent[];\n\n /**\n * A few subjects to actually look at.\n *\n * Mass re-baselining is only survivable if approval is *informed*, and nobody\n * informs themselves by reviewing three hundred identical diffs. Spec §7.3 asks\n * for a sampled spot-check list, and this is it.\n */\n readonly sample: readonly string[];\n\n /**\n * Distinct places this root's changes were observed, most common first.\n *\n * Carried on the entry rather than derived from deltas at report time, because\n * an entry spans subjects and the deltas behind it are not kept. Three is\n * enough to orient without turning a collapsed root back into a list — the\n * thing collapsing it was for.\n */\n readonly places: readonly string[];\n\n /**\n * `true` when nothing under this root added, removed, moved, or renamed a node.\n *\n * The other half of the sentence the product promises. \"One token change, 300\n * collateral\" is only reassuring alongside \"structure intact\" — that is what\n * makes it a one-action approval rather than three hundred things to check.\n */\n readonly structureIntact: boolean;\n}\n\nexport interface Docket {\n readonly entries: readonly DocketEntry[];\n readonly subjectsCompared: number;\n readonly subjectsUnchanged: number;\n readonly subjectsChanged: number;\n\n /**\n * `true` when every change in the set has one explanation.\n *\n * The condition under which the whole review collapses to a single action.\n */\n readonly singleRoot: boolean;\n}\n\nexport interface DocketOptions {\n /** Subjects listed per entry for spot-checking. Policy, not a constant. */\n readonly sampleSize?: number;\n}\n\nexport function buildDocket(\n diffs: readonly SemanticDiff[],\n options: DocketOptions = {},\n): Docket {\n const sampleSize = options.sampleSize ?? 3;\n const groups = new Map<string, Accumulator>();\n\n for (const diff of diffs) {\n if (diff.identical) continue;\n\n for (const root of diff.roots) {\n const entry = groups.get(root.id) ?? newAccumulator(root);\n groups.set(root.id, entry);\n\n entry.subjects.add(diff.subjectId);\n entry.deltas.push(...root.deltas);\n for (const component of componentsUnder(root, diff.components)) {\n mergeComponent(entry.components, component);\n }\n }\n }\n\n const entries = [...groups.values()]\n .map((entry) => finalize(entry, sampleSize))\n // Widest blast radius first: a root touching forty subjects is the review,\n // and one touching a single subject is a detail underneath it.\n .sort((a, b) => b.subjectCount - a.subjectCount || b.deltaCount - a.deltaCount);\n\n const changed = diffs.filter((diff) => !diff.identical).length;\n\n return {\n entries,\n subjectsCompared: diffs.length,\n subjectsUnchanged: diffs.length - changed,\n subjectsChanged: changed,\n singleRoot: entries.length === 1,\n };\n}\n\n/**\n * The docket as one sentence per entry.\n *\n * Deliberately terse. The audience is a reviewer deciding whether to look\n * further and an agent deciding whether to act, and both are worse served by a\n * paragraph than by a line they can scan.\n */\nexport function summarize(docket: Docket): string {\n if (docket.entries.length === 0) {\n return `${docket.subjectsCompared} subjects compared, nothing changed.`;\n }\n\n const header =\n `${docket.subjectsChanged} of ${docket.subjectsCompared} subjects changed, ` +\n `explained by ${docket.entries.length} root${docket.entries.length === 1 ? '' : 's'}.`;\n\n const lines = docket.entries.map((entry) => {\n const reach =\n entry.subjectCount === 1\n ? `${entry.deltaCount} change${entry.deltaCount === 1 ? '' : 's'}`\n : `${entry.subjectCount} subjects, ${entry.deltaCount} collateral`;\n\n const structure = entry.structureIntact ? 'structure intact' : 'structure changed';\n const components =\n entry.components.length > 0\n ? ` — ${entry.components\n .slice(0, 4)\n .map((component) => component.name)\n .join(', ')}`\n : '';\n\n return (\n ` ${entry.kind}: ${entry.label} — ${reach}, ${entry.band}/${entry.impact}, ` +\n `${structure}${components}`\n );\n });\n\n return [header, ...lines].join('\\n');\n}\n\ninterface Accumulator {\n readonly root: Root;\n readonly subjects: Set<string>;\n readonly deltas: Delta[];\n readonly components: Map<string, MutableComponent>;\n}\n\ninterface MutableComponent {\n name: string;\n role: 'root' | 'collateral';\n deltaCount: number;\n bands: Set<Band>;\n impacts: (PropertyImpact | 'structural')[];\n renderedIn: Set<string>;\n}\n\nfunction newAccumulator(root: Root): Accumulator {\n return { root, subjects: new Set(), deltas: [], components: new Map() };\n}\n\nfunction finalize(entry: Accumulator, sampleSize: number): DocketEntry {\n const subjects = [...entry.subjects];\n\n return {\n rootId: entry.root.id,\n kind: entry.root.kind,\n label: entry.root.label,\n // The root's own band is a floor, not the answer. A token edit whose\n // collateral includes a dropped accessible name is reported at `a11y`: the\n // entry is one review action, so it must be labelled by the loudest thing a\n // reviewer would be signing off on, not by the thing that caused it.\n band: loudestBand([entry.root.band, ...entry.deltas.map((delta) => delta.band)]) ?? entry.root.band,\n impact: aggregateImpact(entry.deltas.map((delta) => delta.impact ?? 'structural')),\n subjects,\n subjectCount: subjects.length,\n deltaCount: entry.deltas.length,\n components: [...entry.components.values()]\n .map((component) => ({\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: [...component.bands],\n impact: aggregateImpact(component.impacts),\n renderedIn: [...component.renderedIn],\n }))\n .sort(\n (a, b) =>\n b.deltaCount - a.deltaCount || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n ),\n sample: subjects.slice(0, sampleSize),\n places: rankedPlaces(entry.deltas),\n structureIntact: !entry.deltas.some(isStructural),\n };\n}\n\n/** Distinct `where` phrases, most frequent first, capped. */\nfunction rankedPlaces(deltas: readonly Delta[]): readonly string[] {\n const counts = new Map<string, number>();\n\n for (const delta of deltas) {\n if (delta.where === undefined || delta.where === '') continue;\n counts.set(delta.where, (counts.get(delta.where) ?? 0) + 1);\n }\n\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3)\n .map(([where]) => where);\n}\n\n/**\n * Whether a delta changed the shape of the tree rather than a value.\n *\n * A `rect-changed` that was *derived* from a style change does not count: the\n * box moved because a value moved, and calling that a structural change would\n * make every spacing token look like it rearranged the page.\n */\nfunction isStructural(delta: Delta): boolean {\n switch (delta.kind) {\n case 'node-added':\n case 'node-removed':\n case 'node-moved':\n case 'tag-changed':\n case 'role-changed':\n case 'name-changed':\n case 'text-changed':\n return true;\n case 'rect-changed':\n return delta.derivedFrom === undefined;\n default:\n return false;\n }\n}\n\n/** Components that appear in this root's deltas, from the subject's own view. */\nfunction componentsUnder(\n root: Root,\n components: readonly ChangedComponent[],\n): readonly ChangedComponent[] {\n const names = new Set<string>();\n for (const delta of root.deltas) {\n for (const owner of delta.owners ?? []) names.add(owner.name);\n }\n return components.filter((component) => names.has(component.name));\n}\n\nfunction mergeComponent(\n into: Map<string, MutableComponent>,\n component: ChangedComponent,\n): void {\n const existing = into.get(component.name);\n\n if (!existing) {\n into.set(component.name, {\n name: component.name,\n role: component.role,\n deltaCount: component.deltaCount,\n bands: new Set(component.bands),\n impacts: [],\n renderedIn: new Set(component.renderedIn),\n });\n return;\n }\n\n existing.deltaCount += component.deltaCount;\n for (const band of component.bands) existing.bands.add(band);\n for (const parent of component.renderedIn) existing.renderedIn.add(parent);\n // Root in any subject means root: a component that changed internally\n // somewhere is a cause, even if elsewhere it was only along for the ride.\n if (component.role === 'root') existing.role = 'root';\n}\n"]}
@@ -33,6 +33,7 @@ export { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js'
33
33
  export { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';
34
34
  export type { Scoped } from './scope.js';
35
35
  export { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';
36
+ export { restamps } from './restamp.js';
36
37
  export type { MaskFingerprintOptions } from './fingerprint.js';
37
38
  export type { IgnoreRule, IgnoreOptions, IgnoreOutcome, IgnoreRegister, AbsorbedByRule, } from './ignore.js';
38
39
  export { applySensitivity, absorbsEntirely, asIgnore, bandsOf, relaxes, summarizeSensitivity, } from './sensitivity.js';
@@ -28,6 +28,7 @@ export { findingMark, inspect, summarizeFindings } from './inspect.js';
28
28
  export { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';
29
29
  export { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';
30
30
  export { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';
31
+ export { restamps } from './restamp.js';
31
32
  export { applySensitivity, absorbsEntirely, asIgnore, bandsOf, relaxes, summarizeSensitivity, } from './sensitivity.js';
32
33
  export { startTrail, record, progress, summarizeTrail } from './trail.js';
33
34
  export { compareLocales } from './locale.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/judge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAUtF,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * `@variance-authority/core/judge` — from what moved to whether anyone should mind.\n *\n * Verdicts and their ordering, intent claims and their adjudication, and the\n * docket a reader is handed. This is where policy lives, and it is deliberately\n * the last group and the smallest: everything above it produces facts, and a\n * team that disagrees with the policy replaces this and keeps the rest.\n *\n * `UNOBSERVED` is the group's whole argument in one export. A band a profile\n * cannot see is not a band that passed, and the type system will not let the two\n * be spelled the same way.\n *\n * `inspect` is the one member that needs no baseline. Everything else here\n * decides between two snapshots; it decides about one, which is the only way a\n * defect present on the *first* run is ever reported rather than approved into\n * the baseline.\n *\n * `ignore` is the group's second refusal to conflate two things. A difference the\n * operator declined to look at is not a difference that did not happen, so an\n * absorbed delta is counted, attributed to the rule that absorbed it, and the\n * rule is reported when it absorbs nothing — the same treatment `UNOBSERVED`\n * gives a band nobody could see.\n */\n\nexport type { Verdict, BandOutcome } from './verdict.js';\nexport { UNOBSERVED, severityOf, blocks } from './verdict.js';\n\nexport { adjudicate, summarizeAdjudication } from './intent.js';\nexport type { Intent, IntentClaim, Policy, Adjudication, Adjudicated } from './intent.js';\n\nexport { buildDocket, summarize } from './docket.js';\nexport type { Docket, DocketEntry, DocketOptions } from './docket.js';\n\nexport { findingMark, inspect, summarizeFindings } from './inspect.js';\nexport type { Finding, FindingRule, InspectionReportOptions } from './inspect.js';\n\nexport { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';\nexport { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';\nexport type { Scoped } from './scope.js';\nexport { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';\nexport type { MaskFingerprintOptions } from './fingerprint.js';\nexport type {\n IgnoreRule,\n IgnoreOptions,\n IgnoreOutcome,\n IgnoreRegister,\n AbsorbedByRule,\n} from './ignore.js';\n\nexport {\n applySensitivity,\n absorbsEntirely,\n asIgnore,\n bandsOf,\n relaxes,\n summarizeSensitivity,\n} from './sensitivity.js';\nexport type {\n Level,\n SensitivityRule,\n SensitivityOutcome,\n SensitivityRegister,\n Relaxation,\n} from './sensitivity.js';\n\nexport { startTrail, record, progress, summarizeTrail } from './trail.js';\nexport type { Trail, TrailStep, Progress } from './trail.js';\n\nexport { compareLocales } from './locale.js';\nexport type { LocaleComparison, LocaleOptions, Uncompared } from './locale.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/judge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAUxC,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,OAAO,EACP,OAAO,EACP,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * `@variance-authority/core/judge` — from what moved to whether anyone should mind.\n *\n * Verdicts and their ordering, intent claims and their adjudication, and the\n * docket a reader is handed. This is where policy lives, and it is deliberately\n * the last group and the smallest: everything above it produces facts, and a\n * team that disagrees with the policy replaces this and keeps the rest.\n *\n * `UNOBSERVED` is the group's whole argument in one export. A band a profile\n * cannot see is not a band that passed, and the type system will not let the two\n * be spelled the same way.\n *\n * `inspect` is the one member that needs no baseline. Everything else here\n * decides between two snapshots; it decides about one, which is the only way a\n * defect present on the *first* run is ever reported rather than approved into\n * the baseline.\n *\n * `ignore` is the group's second refusal to conflate two things. A difference the\n * operator declined to look at is not a difference that did not happen, so an\n * absorbed delta is counted, attributed to the rule that absorbed it, and the\n * rule is reported when it absorbs nothing — the same treatment `UNOBSERVED`\n * gives a band nobody could see.\n */\n\nexport type { Verdict, BandOutcome } from './verdict.js';\nexport { UNOBSERVED, severityOf, blocks } from './verdict.js';\n\nexport { adjudicate, summarizeAdjudication } from './intent.js';\nexport type { Intent, IntentClaim, Policy, Adjudication, Adjudicated } from './intent.js';\n\nexport { buildDocket, summarize } from './docket.js';\nexport type { Docket, DocketEntry, DocketOptions } from './docket.js';\n\nexport { findingMark, inspect, summarizeFindings } from './inspect.js';\nexport type { Finding, FindingRule, InspectionReportOptions } from './inspect.js';\n\nexport { applyIgnores, summarizeIgnores, validateIgnoreRule } from './ignore.js';\nexport { isUnder, matchesGlob, appliesToSubject, isExpired } from './scope.js';\nexport type { Scoped } from './scope.js';\nexport { fingerprintOfRoot, fingerprintOfMask, shapeOfDelta } from './fingerprint.js';\nexport { restamps } from './restamp.js';\nexport type { MaskFingerprintOptions } from './fingerprint.js';\nexport type {\n IgnoreRule,\n IgnoreOptions,\n IgnoreOutcome,\n IgnoreRegister,\n AbsorbedByRule,\n} from './ignore.js';\n\nexport {\n applySensitivity,\n absorbsEntirely,\n asIgnore,\n bandsOf,\n relaxes,\n summarizeSensitivity,\n} from './sensitivity.js';\nexport type {\n Level,\n SensitivityRule,\n SensitivityOutcome,\n SensitivityRegister,\n Relaxation,\n} from './sensitivity.js';\n\nexport { startTrail, record, progress, summarizeTrail } from './trail.js';\nexport type { Trail, TrailStep, Progress } from './trail.js';\n\nexport { compareLocales } from './locale.js';\nexport type { LocaleComparison, LocaleOptions, Uncompared } from './locale.js';\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether an accepting run rewrites a baseline the run found unchanged.
3
+ *
4
+ * Nothing changed, so there is nothing to accept, but the baseline is left
5
+ * worse than a fresh one in two ways: painted under another identity, it names
6
+ * the same move on every later run (ADR-0090); keeping no snapshot of its
7
+ * nodes, it joins the first change after it against one render (ADR-0027);
8
+ * read from an identity directory, it stays at a path no reviewer
9
+ * can read, and rewriting it is the move.
10
+ *
11
+ * One rule, asked of the signals an observation and its record share, so that
12
+ * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`
13
+ * cannot come to disagree about which unchanged baselines they rewrite.
14
+ */
15
+ export declare function restamps(signals: {
16
+ readonly identity?: unknown;
17
+ readonly snapshot?: 'unrecorded';
18
+ readonly relocated?: unknown;
19
+ } | undefined): boolean;
20
+ //# sourceMappingURL=restamp.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Whether an accepting run rewrites a baseline the run found unchanged.
3
+ *
4
+ * Nothing changed, so there is nothing to accept, but the baseline is left
5
+ * worse than a fresh one in two ways: painted under another identity, it names
6
+ * the same move on every later run (ADR-0090); keeping no snapshot of its
7
+ * nodes, it joins the first change after it against one render (ADR-0027);
8
+ * read from an identity directory, it stays at a path no reviewer
9
+ * can read, and rewriting it is the move.
10
+ *
11
+ * One rule, asked of the signals an observation and its record share, so that
12
+ * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`
13
+ * cannot come to disagree about which unchanged baselines they rewrite.
14
+ */
15
+ export function restamps(signals) {
16
+ return (signals?.identity !== undefined ||
17
+ signals?.snapshot === 'unrecorded' ||
18
+ signals?.relocated !== undefined);
19
+ }
20
+ //# sourceMappingURL=restamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restamp.js","sourceRoot":"","sources":["../../src/judge/restamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,QAAQ,CACtB,OAMa;IAEb,OAAO,CACL,OAAO,EAAE,QAAQ,KAAK,SAAS;QAC/B,OAAO,EAAE,QAAQ,KAAK,YAAY;QAClC,OAAO,EAAE,SAAS,KAAK,SAAS,CACjC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Whether an accepting run rewrites a baseline the run found unchanged.\n *\n * Nothing changed, so there is nothing to accept, but the baseline is left\n * worse than a fresh one in two ways: painted under another identity, it names\n * the same move on every later run (ADR-0090); keeping no snapshot of its\n * nodes, it joins the first change after it against one render (ADR-0027);\n * read from an identity directory, it stays at a path no reviewer\n * can read, and rewriting it is the move.\n *\n * One rule, asked of the signals an observation and its record share, so that\n * Playwright's `--update-snapshots`, vitest's `--update` and `variance accept`\n * cannot come to disagree about which unchanged baselines they rewrite.\n */\nexport function restamps(\n signals:\n | {\n readonly identity?: unknown;\n readonly snapshot?: 'unrecorded';\n readonly relocated?: unknown;\n }\n | undefined,\n): boolean {\n return (\n signals?.identity !== undefined ||\n signals?.snapshot === 'unrecorded' ||\n signals?.relocated !== undefined\n );\n}\n"]}
@@ -64,7 +64,7 @@
64
64
  * harness file it changed. No file in the repository ever wrote the word, and
65
65
  * the run still does not narrow.
66
66
  */
67
- import { nodeAt } from './graph.js';
67
+ import { RUNTIME_EDGES, nodeAt } from './graph.js';
68
68
  import { dependenciesOf } from './reach.js';
69
69
  /**
70
70
  * Walk down from each declared entry point, stopping where the scan takes over.
@@ -115,7 +115,7 @@ export function beforeReach(relations, entries, options = {}) {
115
115
  if (!entered.has(id) && within(name, sensed))
116
116
  avoid.push(id);
117
117
  }
118
- for (const id of dependenciesOf(relations, seeds, { avoid }).nodes) {
118
+ for (const id of dependenciesOf(relations, seeds, { through: RUNTIME_EDGES, avoid }).nodes) {
119
119
  const node = nodeAt(relations, id);
120
120
  if (node?.kind === 'file')
121
121
  files.add(node.name);
@@ -1 +1 @@
1
- {"version":3,"file":"before.js","sourceRoot":"","sources":["../../src/relate/before.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a run rests on that nothing in it imports.\n *\n * Selection walks one line. It starts at the test harness, moves right through\n * the tests into the repository's own files, and goes out past them into the\n * dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every\n * question it asks is asked *against* the arrows: what depends on the thing\n * that changed.\n *\n * The far-left end of that line answers no such question. A `vitest.config.ts`,\n * a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a\n * `.nvmrc`: nothing imports them, so nothing has an edge to them, so a walk\n * from one reaches nothing and a walk to one arrives never. They are *before\n * reach* — able to repaint the whole suite and invisible to the only mechanism\n * that could say so.\n *\n * ## Why the accident is not enough\n *\n * A diff that names only such a file already runs everything, because a diff no\n * part of which is in the graph is refused. That holds only while it is the\n * *whole* diff. A CI workflow edited beside one component file is a diff with a\n * seed in it, and the walk from that seed answers confidently for a change it\n * never looked at. The widening was a side effect of having nothing to say, and\n * a side effect stops the moment somebody else has something to say.\n *\n * ## Why the operator names them\n *\n * Because no rule can. *Every changed path the graph does not hold* is the\n * README, the changelog, the fixture JSON, the editor settings: a whole run\n * each, forever, and an operator who turns that off has turned off the config\n * files too. Which paths govern a run is a fact about the repository that only\n * the repository knows, so it is declared — and once declared it is exact,\n * where a heuristic would be both too wide and too narrow on the same diff.\n *\n * ## What declaring one buys beyond its own name\n *\n * Everything below it. A fixture module only the setup imports, a polyfill,\n * the environment package it registers: each is an ordinary file the scan\n * already holds, that nothing in a test imports, whose change reaches no\n * component and narrows the run to nothing. Declared, the entry point is walked\n * *along* the arrows once and that whole set comes with it.\n *\n * Only along imports. A runner config names its setup files as strings, and a\n * string is no edge, so the setup is declared beside the config rather than\n * found below it. Reading what a runner would make of its own config is the\n * runner's answer, and guessing at it here would be wrong the day it changed.\n *\n * ## Where the descent stops\n *\n * At the first file the scan can already reason about. A setup file that\n * imports `src/theme.ts` does not drag `src/theme.ts` in with it: that file has\n * dependents, a change to it is answered exactly by walking them, and pulling\n * it into the harness would trade an exact answer for a whole run — the one\n * direction this is not allowed to be wrong in. The walk avoids sensed files,\n * and avoiding a node stops the walk there rather than stepping over it: what\n * lies below a sensed file is imported *through* it, and it already answers.\n *\n * ## Where the two ends meet\n *\n * Not here. A setup imports `jest-environment-jsdom`, which rests on `jsdom`;\n * `jsdom` is bumped. [`beyond.ts`](./beyond.ts) reads that bump back into the\n * file whose import rests on it, the setup, before this stage is asked, so the\n * closure holds files alone and a bump the harness rests on arrives as the\n * harness file it changed. No file in the repository ever wrote the word, and\n * the run still does not narrow.\n */\n\nimport { nodeAt, type NodeId, type Relations } from './graph.js';\nimport { dependenciesOf } from './reach.js';\n\n/** The part of a run that sits before the tests, as names. */\nexport interface BeforeReach {\n /**\n * The entry points as they were declared, matched against a diff by path.\n *\n * Kept apart from what they reach because they are matched differently: a\n * declared path claims everything under it, so a directory of workflows is\n * one entry, while a file the walk found is one file.\n */\n readonly entries: readonly string[];\n /** Files below the entry points that the scan does not otherwise sense. */\n readonly files: ReadonlySet<string>;\n /**\n * Declared entry points the graph does not hold, and so has no closure for.\n *\n * Ordinary for most of them: a `.nvmrc`, a CI workflow and a `tsconfig` have\n * nothing below them a scan could read, and they contribute their own name,\n * which is all they have. Not ordinary for a config the scan should have\n * read, where it means the closure that would have come with it — the setup\n * module, the environment — is missing and those files still narrow to\n * nothing. The two are indistinguishable from here, so both are reported and\n * neither is guessed at.\n */\n readonly unread: readonly string[];\n}\n\nexport interface BeforeReachOptions {\n /**\n * Files the scan already answers for, as the directories they live under.\n *\n * The descent stops at these. Absent, nothing stops it, and one setup import\n * into `src/` would put the repository's own source before reach.\n */\n readonly sensed?: readonly string[];\n}\n\n/**\n * Walk down from each declared entry point, stopping where the scan takes over.\n *\n * Along the arrows, unlike every other walk here, because this is the one\n * question whose subject has no dependents: *what does the harness rest on*.\n * Type-only edges are not walked — a config's `import type` is erased before\n * the suite runs, exactly as a component's is.\n */\nexport function beforeReach(\n relations: Relations,\n entries: readonly string[],\n options: BeforeReachOptions = {},\n): BeforeReach {\n const files = new Set<string>();\n const unread: string[] = [];\n if (entries.length === 0) return { entries, files, unread };\n\n const sensed = options.sensed ?? [];\n const held = new Map<string, NodeId>();\n for (let id = 0; id < relations.names.length; id += 1) {\n const node = nodeAt(relations, id);\n if (node?.kind === 'file') held.set(node.name, id);\n }\n\n // A declared directory is every file the graph holds under it, each walked\n // as an entry point: a setup under `test/` loads what it loads whether the\n // operator named the file or the directory it sits in.\n const seeds: NodeId[] = [];\n for (const entry of entries) {\n const id = held.get(entry);\n if (id !== undefined) {\n seeds.push(id);\n continue;\n }\n const under = [...held].filter(([name]) => within(name, [entry]));\n if (under.length === 0) unread.push(entry);\n else for (const [, below] of under) seeds.push(below);\n }\n if (seeds.length === 0) return { entries, files, unread };\n\n // Avoided before the walk rather than filtered after it. A sensed file is not\n // a step on the way to something else here: whatever it imports, it imports\n // as a file the scan holds, and the dependents of *that* file are the exact\n // answer the harness would have replaced with a whole run.\n const entered = new Set(seeds);\n const avoid: NodeId[] = [];\n for (const [name, id] of held) {\n if (!entered.has(id) && within(name, sensed)) avoid.push(id);\n }\n\n for (const id of dependenciesOf(relations, seeds, { avoid }).nodes) {\n const node = nodeAt(relations, id);\n if (node?.kind === 'file') files.add(node.name);\n }\n\n return { entries, files, unread };\n}\n\n/**\n * Which of a diff's changed files the harness rests on, as they were named.\n * Empty is a real answer: the diff changed nothing the run rests on.\n *\n * A declared entry claims every path under it, so naming a directory of\n * workflows is one line of configuration rather than one per file. A file the\n * walk found claims only itself: the walk visited it, so it is known exactly, and\n * widening it to its directory would put its neighbours before reach without\n * anybody saying they were.\n */\nexport function changedBefore(before: BeforeReach, changed: readonly string[]): readonly string[] {\n return changed.filter((file) => before.files.has(file) || within(file, before.entries));\n}\n\n/**\n * Whether a path lies under one of these directories.\n *\n * On directory boundaries rather than characters: `src` must not claim\n * `srcery/`, or a repository would put half of itself before reach and find out\n * from the runs that never narrowed. Exported because every caller that draws\n * this boundary over paths draws the same one, and two spellings of it would\n * disagree on the day one of them was fixed.\n */\nexport function within(file: string, dirs: readonly string[]): boolean {\n return dirs.some((dir) => {\n const normalized = dir.replace(/\\/+$/, '');\n return normalized === '.' || file === normalized || file.startsWith(`${normalized}/`);\n });\n}\n"]}
1
+ {"version":3,"file":"before.js","sourceRoot":"","sources":["../../src/relate/before.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a run rests on that nothing in it imports.\n *\n * Selection walks one line. It starts at the test harness, moves right through\n * the tests into the repository's own files, and goes out past them into the\n * dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every\n * question it asks is asked *against* the arrows: what depends on the thing\n * that changed.\n *\n * The far-left end of that line answers no such question. A `vitest.config.ts`,\n * a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a\n * `.nvmrc`: nothing imports them, so nothing has an edge to them, so a walk\n * from one reaches nothing and a walk to one arrives never. They are *before\n * reach* — able to repaint the whole suite and invisible to the only mechanism\n * that could say so.\n *\n * ## Why the accident is not enough\n *\n * A diff that names only such a file already runs everything, because a diff no\n * part of which is in the graph is refused. That holds only while it is the\n * *whole* diff. A CI workflow edited beside one component file is a diff with a\n * seed in it, and the walk from that seed answers confidently for a change it\n * never looked at. The widening was a side effect of having nothing to say, and\n * a side effect stops the moment somebody else has something to say.\n *\n * ## Why the operator names them\n *\n * Because no rule can. *Every changed path the graph does not hold* is the\n * README, the changelog, the fixture JSON, the editor settings: a whole run\n * each, forever, and an operator who turns that off has turned off the config\n * files too. Which paths govern a run is a fact about the repository that only\n * the repository knows, so it is declared — and once declared it is exact,\n * where a heuristic would be both too wide and too narrow on the same diff.\n *\n * ## What declaring one buys beyond its own name\n *\n * Everything below it. A fixture module only the setup imports, a polyfill,\n * the environment package it registers: each is an ordinary file the scan\n * already holds, that nothing in a test imports, whose change reaches no\n * component and narrows the run to nothing. Declared, the entry point is walked\n * *along* the arrows once and that whole set comes with it.\n *\n * Only along imports. A runner config names its setup files as strings, and a\n * string is no edge, so the setup is declared beside the config rather than\n * found below it. Reading what a runner would make of its own config is the\n * runner's answer, and guessing at it here would be wrong the day it changed.\n *\n * ## Where the descent stops\n *\n * At the first file the scan can already reason about. A setup file that\n * imports `src/theme.ts` does not drag `src/theme.ts` in with it: that file has\n * dependents, a change to it is answered exactly by walking them, and pulling\n * it into the harness would trade an exact answer for a whole run — the one\n * direction this is not allowed to be wrong in. The walk avoids sensed files,\n * and avoiding a node stops the walk there rather than stepping over it: what\n * lies below a sensed file is imported *through* it, and it already answers.\n *\n * ## Where the two ends meet\n *\n * Not here. A setup imports `jest-environment-jsdom`, which rests on `jsdom`;\n * `jsdom` is bumped. [`beyond.ts`](./beyond.ts) reads that bump back into the\n * file whose import rests on it, the setup, before this stage is asked, so the\n * closure holds files alone and a bump the harness rests on arrives as the\n * harness file it changed. No file in the repository ever wrote the word, and\n * the run still does not narrow.\n */\n\nimport { RUNTIME_EDGES, nodeAt, type NodeId, type Relations } from './graph.js';\nimport { dependenciesOf } from './reach.js';\n\n/** The part of a run that sits before the tests, as names. */\nexport interface BeforeReach {\n /**\n * The entry points as they were declared, matched against a diff by path.\n *\n * Kept apart from what they reach because they are matched differently: a\n * declared path claims everything under it, so a directory of workflows is\n * one entry, while a file the walk found is one file.\n */\n readonly entries: readonly string[];\n /** Files below the entry points that the scan does not otherwise sense. */\n readonly files: ReadonlySet<string>;\n /**\n * Declared entry points the graph does not hold, and so has no closure for.\n *\n * Ordinary for most of them: a `.nvmrc`, a CI workflow and a `tsconfig` have\n * nothing below them a scan could read, and they contribute their own name,\n * which is all they have. Not ordinary for a config the scan should have\n * read, where it means the closure that would have come with it — the setup\n * module, the environment — is missing and those files still narrow to\n * nothing. The two are indistinguishable from here, so both are reported and\n * neither is guessed at.\n */\n readonly unread: readonly string[];\n}\n\nexport interface BeforeReachOptions {\n /**\n * Files the scan already answers for, as the directories they live under.\n *\n * The descent stops at these. Absent, nothing stops it, and one setup import\n * into `src/` would put the repository's own source before reach.\n */\n readonly sensed?: readonly string[];\n}\n\n/**\n * Walk down from each declared entry point, stopping where the scan takes over.\n *\n * Along the arrows, unlike every other walk here, because this is the one\n * question whose subject has no dependents: *what does the harness rest on*.\n * Type-only edges are not walked — a config's `import type` is erased before\n * the suite runs, exactly as a component's is.\n */\nexport function beforeReach(\n relations: Relations,\n entries: readonly string[],\n options: BeforeReachOptions = {},\n): BeforeReach {\n const files = new Set<string>();\n const unread: string[] = [];\n if (entries.length === 0) return { entries, files, unread };\n\n const sensed = options.sensed ?? [];\n const held = new Map<string, NodeId>();\n for (let id = 0; id < relations.names.length; id += 1) {\n const node = nodeAt(relations, id);\n if (node?.kind === 'file') held.set(node.name, id);\n }\n\n // A declared directory is every file the graph holds under it, each walked\n // as an entry point: a setup under `test/` loads what it loads whether the\n // operator named the file or the directory it sits in.\n const seeds: NodeId[] = [];\n for (const entry of entries) {\n const id = held.get(entry);\n if (id !== undefined) {\n seeds.push(id);\n continue;\n }\n const under = [...held].filter(([name]) => within(name, [entry]));\n if (under.length === 0) unread.push(entry);\n else for (const [, below] of under) seeds.push(below);\n }\n if (seeds.length === 0) return { entries, files, unread };\n\n // Avoided before the walk rather than filtered after it. A sensed file is not\n // a step on the way to something else here: whatever it imports, it imports\n // as a file the scan holds, and the dependents of *that* file are the exact\n // answer the harness would have replaced with a whole run.\n const entered = new Set(seeds);\n const avoid: NodeId[] = [];\n for (const [name, id] of held) {\n if (!entered.has(id) && within(name, sensed)) avoid.push(id);\n }\n\n for (const id of dependenciesOf(relations, seeds, { through: RUNTIME_EDGES, avoid }).nodes) {\n const node = nodeAt(relations, id);\n if (node?.kind === 'file') files.add(node.name);\n }\n\n return { entries, files, unread };\n}\n\n/**\n * Which of a diff's changed files the harness rests on, as they were named.\n * Empty is a real answer: the diff changed nothing the run rests on.\n *\n * A declared entry claims every path under it, so naming a directory of\n * workflows is one line of configuration rather than one per file. A file the\n * walk found claims only itself: the walk visited it, so it is known exactly, and\n * widening it to its directory would put its neighbours before reach without\n * anybody saying they were.\n */\nexport function changedBefore(before: BeforeReach, changed: readonly string[]): readonly string[] {\n return changed.filter((file) => before.files.has(file) || within(file, before.entries));\n}\n\n/**\n * Whether a path lies under one of these directories.\n *\n * On directory boundaries rather than characters: `src` must not claim\n * `srcery/`, or a repository would put half of itself before reach and find out\n * from the runs that never narrowed. Exported because every caller that draws\n * this boundary over paths draws the same one, and two spellings of it would\n * disagree on the day one of them was fixed.\n */\nexport function within(file: string, dirs: readonly string[]): boolean {\n return dirs.some((dir) => {\n const normalized = dir.replace(/\\/+$/, '');\n return normalized === '.' || file === normalized || file.startsWith(`${normalized}/`);\n });\n}\n"]}
@@ -73,7 +73,7 @@ export interface CarriedStep<T> {
73
73
  hand?(importer: NodeId, arrived: Carry<T>): Carry<T> | undefined;
74
74
  }
75
75
  export interface CarriedOptions {
76
- /** Edge kinds to walk. `RUNTIME_EDGES` when absent: every kind but `type`. */
76
+ /** Edge kinds to walk. Every kind when absent; `RUNTIME_EDGES` for what a run loads. */
77
77
  readonly through?: Iterable<EdgeKind>;
78
78
  /** Nodes the walk never enters, seeds included. */
79
79
  readonly avoid?: Iterable<NodeId>;
@@ -37,7 +37,7 @@
37
37
  * {@link WHOLE}. A narrower walk has to be earned by an answer, so an
38
38
  * unknown can only cost a wider one.
39
39
  */
40
- import { EDGE_KINDS, RUNTIME_EDGES } from './graph.js';
40
+ import { EDGE_KINDS } from './graph.js';
41
41
  /** The whole file: every name it exposes, and what it does as it loads. */
42
42
  export const WHOLE = Symbol('whole');
43
43
  /** Nothing: the importer is not entered, and nothing is handed on from it. */
@@ -53,7 +53,7 @@ export function carried(edges, seeds, step = {}, options = {}) {
53
53
  const nodes = offset.length - 1;
54
54
  const mask = options.mask ?? new Uint8Array(nodes);
55
55
  const { via } = options;
56
- const allowed = options.through === undefined ? RUNTIME : allowedKinds(options.through);
56
+ const allowed = options.through === undefined ? EVERY : allowedKinds(options.through);
57
57
  let avoided;
58
58
  if (options.avoid !== undefined) {
59
59
  // A typed array ignores a write out of its range, so an id the graph does
@@ -261,13 +261,13 @@ export function inIdOrder(mask) {
261
261
  visited.push(id);
262
262
  return visited;
263
263
  }
264
- /** The default filter, built once: selection walks once per changed file. */
265
- const RUNTIME = allowedKinds(undefined);
264
+ /** The default filter, built once: a walk runs once per changed file. */
265
+ const EVERY = allowedKinds(undefined);
266
266
  /**
267
267
  * A kind filter as a byte lookup, so the inner loop tests a byte; `fallback`
268
268
  * when no kinds are named.
269
269
  */
270
- export function allowedKinds(through, fallback = RUNTIME_EDGES) {
270
+ export function allowedKinds(through, fallback = EDGE_KINDS) {
271
271
  const allowed = new Uint8Array(EDGE_KINDS.length);
272
272
  for (const name of through ?? fallback) {
273
273
  const at = EDGE_KINDS.indexOf(name);
@@ -1 +1 @@
1
- {"version":3,"file":"carried.js","sourceRoot":"","sources":["../../src/relate/carried.ts"],"names":[],"mappings":"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","sourcesContent":["// compass: variance-authority.reach.relations\n/**\n * The one walk against the arrows: what a change carries from the files it\n * started in to every file that loads one of them, and on.\n *\n * Every question about what a change reached is this walk with a different\n * step. A plain closure carries the whole file everywhere. A narrowed one\n * carries the exports that moved, and an importer that binds none of them is\n * not entered. Selection carries names to a reader and stops behind the\n * importer the record answers. The walk is the same each time — a worklist of\n * facts `(file, name)`, each fact entered once — so it is written once, here,\n * and each caller says only what one importer does with what reached it.\n * Handed the forward adjacency instead, the same walk runs along the arrows,\n * which is how `dependenciesOf` closes over what a file loads. One plain\n * closure is written again: `Graph::search` in sense's native journey graph,\n * because Rust cannot call this.\n *\n * ## What is carried\n *\n * A {@link Carry} is either {@link WHOLE} — the file moved as it loads, or\n * nobody can say which of its names did — or a map from the names that moved\n * to whatever the caller carries with each. An empty map is nothing, and\n * nothing arrives nowhere: the importer is not entered.\n *\n * ## The kernel owns\n *\n * - **the worklist** — first in, first out, so a node is first entered from a\n * node on a shortest path and the trail through `via` is a shortest one;\n * - **what was entered** — the names each node holds only grow, a name is\n * handed to a node once, and a node that holds the whole file takes nothing\n * more. A name is held as a string, whichever file it arrived from: a caller\n * that needs two files' `default` apart names them apart. The walk ends,\n * whatever the steps return, after at most one entry per node and name, and\n * one more when a node turns whole;\n * - **cycles** — a cycle, a self-import included, is a revisit like any other;\n * - **the step that cannot answer** — `undefined` from a step is\n * {@link WHOLE}. A narrower walk has to be earned by an answer, so an\n * unknown can only cost a wider one.\n */\n\nimport { EDGE_KINDS, RUNTIME_EDGES, type Adjacency, type EdgeKind, type NodeId } from './graph.js';\n\n/** The whole file: every name it exposes, and what it does as it loads. */\nexport const WHOLE: unique symbol = Symbol('whole');\nexport type Whole = typeof WHOLE;\n\n/** What reaches a node: the whole file, or the names that moved, each with the caller's payload. */\nexport type Carry<T> = ReadonlyMap<string, T> | Whole;\n\n/** Nothing: the importer is not entered, and nothing is handed on from it. */\nexport const NOTHING: Carry<never> = new Map<string, never>();\n\n/** What a node holds so far: the whole file, or the names handed to it. Live — read it, never keep it. */\nexport type Held = ReadonlySet<string> | Whole;\n\n/**\n * What one importer does with what reached it. A step left out passes what it\n * was given on unchanged.\n */\nexport interface CarriedStep<T> {\n /**\n * What crosses one edge: `carried` left `from`, and `importer` loads `from`\n * through an edge of `kind` (an index into `EDGE_KINDS`). `held` is\n * everything `from` holds when it is expanded, which `carried` is part of:\n * the whole file when `carried` is, and a seed's starting names even when\n * `seedsHold` is `false`.\n * {@link NOTHING} keeps the edge from entering `importer`; `undefined` is\n * the whole file. Not asked of an edge into a node that already holds the\n * whole file, since nothing it returns could add to it.\n */\n arrive?(importer: NodeId, from: NodeId, kind: number, carried: Carry<T>, held: Held): Carry<T> | undefined;\n /**\n * What `importer` hands on, given what arrived that it did not already hold\n * — never empty. Asked once per new arrival, as the walk expands it, in the\n * order the walk reached them, and never of a seed as it starts. Every\n * arrival is asked, so a step that answers {@link NOTHING} for one importer\n * still sees each arrival that reached it. {@link NOTHING} stops the walk\n * behind it; `undefined` hands on the whole file.\n */\n hand?(importer: NodeId, arrived: Carry<T>): Carry<T> | undefined;\n}\n\nexport interface CarriedOptions {\n /** Edge kinds to walk. `RUNTIME_EDGES` when absent: every kind but `type`. */\n readonly through?: Iterable<EdgeKind>;\n /** Nodes the walk never enters, seeds included. */\n readonly avoid?: Iterable<NodeId>;\n /**\n * Marks to write into instead of a fresh array; `1` where a node was entered.\n * A node already marked when the walk starts counts as holding the whole\n * file, so it is neither entered nor handed anything.\n */\n readonly mask?: Uint8Array;\n /**\n * Where to record the node each one was first entered from. Not recorded\n * when absent. A seed's entry is left as it was, `-1` by convention.\n */\n readonly via?: Int32Array;\n /**\n * Whether a seed holds what it starts with. When `false` it holds nothing,\n * so what a cycle carries back to it is new, and handed to it: a file can\n * read its own names back through an importer.\n */\n readonly seedsHold?: boolean;\n}\n\nexport interface Reached {\n /** `1` where a node was entered, including the seeds. */\n readonly mask: Uint8Array;\n /** The entered nodes, in the order they were first entered. */\n readonly entered: readonly NodeId[];\n}\n\n/**\n * Walk `edges` from `seeds`, each with what its change carries, and hand each\n * node it enters what reaches it through `step`. `edges` is the reverse\n * adjacency for what a change reaches, the forward one for what a file loads.\n * A seed listed more than once starts with everything its listings carry.\n */\nexport function carried<T>(\n edges: Adjacency,\n seeds: Iterable<readonly [NodeId, Carry<T>]>,\n step: CarriedStep<T> = {},\n options: CarriedOptions = {},\n): Reached {\n const { offset, target, kind } = edges;\n const nodes = offset.length - 1;\n const mask = options.mask ?? new Uint8Array(nodes);\n const { via } = options;\n const allowed = options.through === undefined ? RUNTIME : allowedKinds(options.through);\n let avoided: Uint8Array | undefined;\n if (options.avoid !== undefined) {\n // A typed array ignores a write out of its range, so an id the graph does\n // not hold avoids nothing.\n for (const id of options.avoid) {\n avoided ??= new Uint8Array(nodes);\n avoided[id] = 1;\n }\n }\n\n // An entered node absent here holds the whole file.\n let partial: Map<NodeId, Set<string>> | undefined;\n // The worklist, one number an entry: the node itself when it carries the\n // whole file, `~k` when it carries names, held at `k` aside. Each entry is\n // what arrived, and is handed on as it is expanded: the same entries in the\n // same order as asking on arrival, with the edge loop left free of the step.\n // While every entry is a node's first, the worklist is the list of entered\n // nodes too, so a walk pushes one number a node and nothing else. The first\n // entry that is not a node's first sets `entered` apart, and from then on it\n // is kept on its own.\n const queued: number[] = [];\n let aside: Aside<T> | undefined;\n let entered: NodeId[] | undefined;\n const { arrive, hand } = step;\n\n const holdsNothing = options.seedsHold === false;\n for (const [seed, carry] of seeds) {\n if (seed < 0 || seed >= nodes || avoided?.[seed] === 1) continue;\n let start: Carry<T> = carry;\n if (mask[seed] !== 1) {\n mask[seed] = 1;\n entered?.push(seed);\n if (holdsNothing) (partial ??= new Map()).set(seed, new Set());\n else if (carry !== WHOLE) (partial ??= new Map()).set(seed, namesOf(carry));\n } else {\n // Marked before the walk, which holds the whole file, or listed again.\n const holds = partial?.get(seed);\n if (holds === undefined) continue;\n // A seed that holds nothing of what it starts with starts every listing.\n if (!holdsNothing) {\n if (carry === WHOLE) partial!.delete(seed);\n else start = addedTo(holds, carry);\n }\n if (start !== WHOLE && start.size === 0) continue;\n entered ??= nodesOf(queued, aside, []);\n }\n queued.push(start === WHOLE ? seed : named((aside ??= { nodes: [], carries: [] }), seed, start));\n }\n const starts = queued.length;\n\n // With no step on the edge and every seed whole, nothing is held by name\n // until a hand answers with names, and the walk runs in a loop of its own\n // until then: a closure and a cut walk are this loop, unslowed by the walks\n // that narrow.\n let from = 0;\n let handed: ReadonlyMap<string, T> | undefined;\n if (arrive === undefined && partial === undefined && aside === undefined) {\n const stop = wholeFrom(edges, queued, starts, step, mask, via, allowed, avoided);\n if (stop === undefined) return { mask, entered: queued };\n ({ head: from, carry: handed } = stop);\n }\n\n for (let head = from; head < queued.length; head += 1) {\n const entry = queued[head]!;\n const node = entry >= 0 ? entry : aside!.nodes[~entry]!;\n let carry: Carry<T> = entry >= 0 ? WHOLE : aside!.carries[~entry]!;\n if (handed !== undefined && head === from) carry = handed;\n else if (hand !== undefined && head >= starts) carry = step.hand!(node, carry) ?? WHOLE;\n if (carry !== WHOLE && carry.size === 0) continue;\n // What it holds, which what it carries is part of: a seed that holds\n // nothing of what it starts with still starts with it.\n const holds = carry === WHOLE ? undefined : partial?.get(node);\n let held: Held = holds ?? WHOLE;\n if (holdsNothing && head < starts && holds !== undefined && carry !== WHOLE) {\n const both = namesOf(carry);\n for (const name of holds) both.add(name);\n held = both;\n }\n const end = offset[node + 1]!;\n\n for (let edge = offset[node]!; edge < end; edge += 1) {\n const edgeKind = kind[edge]!;\n if (allowed[edgeKind] !== 1) continue;\n const next = target[edge]!;\n if (avoided?.[next] === 1) continue;\n // Entered and absent from `partial`: it holds the whole file already.\n const seen = mask[next] === 1;\n if (seen && (partial === undefined || !partial.has(next))) continue;\n\n const out = arrive === undefined ? carry : (step.arrive!(next, node, edgeKind, carry, held) ?? WHOLE);\n if (out !== WHOLE && out.size === 0) continue;\n let arrived = out;\n if (!seen) {\n mask[next] = 1;\n if (via !== undefined) via[next] = node;\n entered?.push(next);\n if (out !== WHOLE) (partial ??= new Map()).set(next, namesOf(out));\n } else {\n if (out === WHOLE) partial!.delete(next);\n else {\n arrived = addedTo(partial!.get(next)!, out);\n if (arrived.size === 0) continue;\n }\n entered ??= nodesOf(queued, aside, []);\n }\n queued.push(arrived === WHOLE ? next : named((aside ??= { nodes: [], carries: [] }), next, arrived));\n }\n }\n\n // Nothing carried by name, nothing to decode: every entry is a node.\n return { mask, entered: entered ?? (aside === undefined ? queued : nodesOf(queued, aside, queued)) };\n}\n\n/** Where a whole walk met a hand that answered with names, and the names. */\ninterface Handed<T> {\n readonly head: number;\n readonly carry: ReadonlyMap<string, T>;\n}\n\n/**\n * The walk while every entry carries the whole file: an entered node takes\n * nothing more, so the mask is the whole record of what was entered. Returns\n * where a hand first answered with names, or `undefined` when the walk ended\n * whole.\n */\nfunction wholeFrom<T>(\n edges: Adjacency,\n queued: NodeId[],\n starts: number,\n step: CarriedStep<T>,\n mask: Uint8Array,\n via: Int32Array | undefined,\n allowed: Uint8Array,\n avoided: Uint8Array | undefined,\n): Handed<T> | undefined {\n const { offset, target, kind } = edges;\n const asks = step.hand !== undefined;\n for (let head = 0; head < queued.length; head += 1) {\n const node = queued[head]!;\n if (asks && head >= starts) {\n const carry = step.hand!(node, WHOLE);\n if (carry !== undefined && carry !== WHOLE) {\n if (carry.size === 0) continue;\n return { head, carry };\n }\n }\n const end = offset[node + 1]!;\n for (let edge = offset[node]!; edge < end; edge += 1) {\n if (allowed[kind[edge]!] !== 1) continue;\n const next = target[edge]!;\n if (mask[next] === 1 || avoided?.[next] === 1) continue;\n mask[next] = 1;\n if (via !== undefined) via[next] = node;\n queued.push(next);\n }\n }\n return undefined;\n}\n\n/** What the worklist carries by name, out of line so a whole entry is one number. */\ninterface Aside<T> {\n readonly nodes: NodeId[];\n readonly carries: Array<ReadonlyMap<string, T>>;\n}\n\n/** The worklist entry for `node` carrying the names in `carry`. */\nfunction named<T>(aside: Aside<T>, node: NodeId, carry: ReadonlyMap<string, T>): number {\n aside.carries.push(carry);\n return ~(aside.nodes.push(node) - 1);\n}\n\n/** The nodes of `entries`, written into `into`, which may be `entries` itself. */\nfunction nodesOf<T>(entries: readonly number[], aside: Aside<T> | undefined, into: NodeId[]): NodeId[] {\n for (let at = 0; at < entries.length; at += 1) {\n const entry = entries[at]!;\n into[at] = entry >= 0 ? entry : aside!.nodes[~entry]!;\n }\n return into;\n}\n\n/** What `carry` adds to `holds`, which takes it in. */\nfunction addedTo<T>(holds: Set<string>, carry: ReadonlyMap<string, T>): ReadonlyMap<string, T> {\n const added = new Map<string, T>();\n for (const [name, payload] of carry) {\n if (holds.has(name)) continue;\n holds.add(name);\n added.set(name, payload);\n }\n return added;\n}\n\n/**\n * The names a carry holds, as a set the walk grows. Added one by one: V8 builds\n * a `Set` from a map's key iterator several times slower than this loop.\n */\nfunction namesOf<T>(carry: ReadonlyMap<string, T>): Set<string> {\n const names = new Set<string>();\n for (const name of carry.keys()) names.add(name);\n return names;\n}\n\n/**\n * The marked nodes in id order. The walk's own order depends on seed order;\n * this one does not, so a report built from it is byte-stable whatever order\n * the caller listed its changed files in.\n */\nexport function inIdOrder(mask: Uint8Array): NodeId[] {\n const visited: NodeId[] = [];\n for (let id = 0; id < mask.length; id += 1) if (mask[id] === 1) visited.push(id);\n return visited;\n}\n\n/** The default filter, built once: selection walks once per changed file. */\nconst RUNTIME = allowedKinds(undefined);\n\n/**\n * A kind filter as a byte lookup, so the inner loop tests a byte; `fallback`\n * when no kinds are named.\n */\nexport function allowedKinds(\n through: Iterable<EdgeKind> | undefined,\n fallback: readonly EdgeKind[] = RUNTIME_EDGES,\n): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n for (const name of through ?? fallback) {\n const at = EDGE_KINDS.indexOf(name);\n if (at !== -1) allowed[at] = 1;\n }\n return allowed;\n}\n"]}
1
+ {"version":3,"file":"carried.js","sourceRoot":"","sources":["../../src/relate/carried.ts"],"names":[],"mappings":"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","sourcesContent":["// compass: variance-authority.reach.relations\n/**\n * The one walk against the arrows: what a change carries from the files it\n * started in to every file that loads one of them, and on.\n *\n * Every question about what a change reached is this walk with a different\n * step. A plain closure carries the whole file everywhere. A narrowed one\n * carries the exports that moved, and an importer that binds none of them is\n * not entered. Selection carries names to a reader and stops behind the\n * importer the record answers. The walk is the same each time — a worklist of\n * facts `(file, name)`, each fact entered once — so it is written once, here,\n * and each caller says only what one importer does with what reached it.\n * Handed the forward adjacency instead, the same walk runs along the arrows,\n * which is how `dependenciesOf` closes over what a file loads. One plain\n * closure is written again: `Graph::search` in sense's native journey graph,\n * because Rust cannot call this.\n *\n * ## What is carried\n *\n * A {@link Carry} is either {@link WHOLE} — the file moved as it loads, or\n * nobody can say which of its names did — or a map from the names that moved\n * to whatever the caller carries with each. An empty map is nothing, and\n * nothing arrives nowhere: the importer is not entered.\n *\n * ## The kernel owns\n *\n * - **the worklist** — first in, first out, so a node is first entered from a\n * node on a shortest path and the trail through `via` is a shortest one;\n * - **what was entered** — the names each node holds only grow, a name is\n * handed to a node once, and a node that holds the whole file takes nothing\n * more. A name is held as a string, whichever file it arrived from: a caller\n * that needs two files' `default` apart names them apart. The walk ends,\n * whatever the steps return, after at most one entry per node and name, and\n * one more when a node turns whole;\n * - **cycles** — a cycle, a self-import included, is a revisit like any other;\n * - **the step that cannot answer** — `undefined` from a step is\n * {@link WHOLE}. A narrower walk has to be earned by an answer, so an\n * unknown can only cost a wider one.\n */\n\nimport { EDGE_KINDS, type Adjacency, type EdgeKind, type NodeId } from './graph.js';\n\n/** The whole file: every name it exposes, and what it does as it loads. */\nexport const WHOLE: unique symbol = Symbol('whole');\nexport type Whole = typeof WHOLE;\n\n/** What reaches a node: the whole file, or the names that moved, each with the caller's payload. */\nexport type Carry<T> = ReadonlyMap<string, T> | Whole;\n\n/** Nothing: the importer is not entered, and nothing is handed on from it. */\nexport const NOTHING: Carry<never> = new Map<string, never>();\n\n/** What a node holds so far: the whole file, or the names handed to it. Live — read it, never keep it. */\nexport type Held = ReadonlySet<string> | Whole;\n\n/**\n * What one importer does with what reached it. A step left out passes what it\n * was given on unchanged.\n */\nexport interface CarriedStep<T> {\n /**\n * What crosses one edge: `carried` left `from`, and `importer` loads `from`\n * through an edge of `kind` (an index into `EDGE_KINDS`). `held` is\n * everything `from` holds when it is expanded, which `carried` is part of:\n * the whole file when `carried` is, and a seed's starting names even when\n * `seedsHold` is `false`.\n * {@link NOTHING} keeps the edge from entering `importer`; `undefined` is\n * the whole file. Not asked of an edge into a node that already holds the\n * whole file, since nothing it returns could add to it.\n */\n arrive?(importer: NodeId, from: NodeId, kind: number, carried: Carry<T>, held: Held): Carry<T> | undefined;\n /**\n * What `importer` hands on, given what arrived that it did not already hold\n * — never empty. Asked once per new arrival, as the walk expands it, in the\n * order the walk reached them, and never of a seed as it starts. Every\n * arrival is asked, so a step that answers {@link NOTHING} for one importer\n * still sees each arrival that reached it. {@link NOTHING} stops the walk\n * behind it; `undefined` hands on the whole file.\n */\n hand?(importer: NodeId, arrived: Carry<T>): Carry<T> | undefined;\n}\n\nexport interface CarriedOptions {\n /** Edge kinds to walk. Every kind when absent; `RUNTIME_EDGES` for what a run loads. */\n readonly through?: Iterable<EdgeKind>;\n /** Nodes the walk never enters, seeds included. */\n readonly avoid?: Iterable<NodeId>;\n /**\n * Marks to write into instead of a fresh array; `1` where a node was entered.\n * A node already marked when the walk starts counts as holding the whole\n * file, so it is neither entered nor handed anything.\n */\n readonly mask?: Uint8Array;\n /**\n * Where to record the node each one was first entered from. Not recorded\n * when absent. A seed's entry is left as it was, `-1` by convention.\n */\n readonly via?: Int32Array;\n /**\n * Whether a seed holds what it starts with. When `false` it holds nothing,\n * so what a cycle carries back to it is new, and handed to it: a file can\n * read its own names back through an importer.\n */\n readonly seedsHold?: boolean;\n}\n\nexport interface Reached {\n /** `1` where a node was entered, including the seeds. */\n readonly mask: Uint8Array;\n /** The entered nodes, in the order they were first entered. */\n readonly entered: readonly NodeId[];\n}\n\n/**\n * Walk `edges` from `seeds`, each with what its change carries, and hand each\n * node it enters what reaches it through `step`. `edges` is the reverse\n * adjacency for what a change reaches, the forward one for what a file loads.\n * A seed listed more than once starts with everything its listings carry.\n */\nexport function carried<T>(\n edges: Adjacency,\n seeds: Iterable<readonly [NodeId, Carry<T>]>,\n step: CarriedStep<T> = {},\n options: CarriedOptions = {},\n): Reached {\n const { offset, target, kind } = edges;\n const nodes = offset.length - 1;\n const mask = options.mask ?? new Uint8Array(nodes);\n const { via } = options;\n const allowed = options.through === undefined ? EVERY : allowedKinds(options.through);\n let avoided: Uint8Array | undefined;\n if (options.avoid !== undefined) {\n // A typed array ignores a write out of its range, so an id the graph does\n // not hold avoids nothing.\n for (const id of options.avoid) {\n avoided ??= new Uint8Array(nodes);\n avoided[id] = 1;\n }\n }\n\n // An entered node absent here holds the whole file.\n let partial: Map<NodeId, Set<string>> | undefined;\n // The worklist, one number an entry: the node itself when it carries the\n // whole file, `~k` when it carries names, held at `k` aside. Each entry is\n // what arrived, and is handed on as it is expanded: the same entries in the\n // same order as asking on arrival, with the edge loop left free of the step.\n // While every entry is a node's first, the worklist is the list of entered\n // nodes too, so a walk pushes one number a node and nothing else. The first\n // entry that is not a node's first sets `entered` apart, and from then on it\n // is kept on its own.\n const queued: number[] = [];\n let aside: Aside<T> | undefined;\n let entered: NodeId[] | undefined;\n const { arrive, hand } = step;\n\n const holdsNothing = options.seedsHold === false;\n for (const [seed, carry] of seeds) {\n if (seed < 0 || seed >= nodes || avoided?.[seed] === 1) continue;\n let start: Carry<T> = carry;\n if (mask[seed] !== 1) {\n mask[seed] = 1;\n entered?.push(seed);\n if (holdsNothing) (partial ??= new Map()).set(seed, new Set());\n else if (carry !== WHOLE) (partial ??= new Map()).set(seed, namesOf(carry));\n } else {\n // Marked before the walk, which holds the whole file, or listed again.\n const holds = partial?.get(seed);\n if (holds === undefined) continue;\n // A seed that holds nothing of what it starts with starts every listing.\n if (!holdsNothing) {\n if (carry === WHOLE) partial!.delete(seed);\n else start = addedTo(holds, carry);\n }\n if (start !== WHOLE && start.size === 0) continue;\n entered ??= nodesOf(queued, aside, []);\n }\n queued.push(start === WHOLE ? seed : named((aside ??= { nodes: [], carries: [] }), seed, start));\n }\n const starts = queued.length;\n\n // With no step on the edge and every seed whole, nothing is held by name\n // until a hand answers with names, and the walk runs in a loop of its own\n // until then: a closure and a cut walk are this loop, unslowed by the walks\n // that narrow.\n let from = 0;\n let handed: ReadonlyMap<string, T> | undefined;\n if (arrive === undefined && partial === undefined && aside === undefined) {\n const stop = wholeFrom(edges, queued, starts, step, mask, via, allowed, avoided);\n if (stop === undefined) return { mask, entered: queued };\n ({ head: from, carry: handed } = stop);\n }\n\n for (let head = from; head < queued.length; head += 1) {\n const entry = queued[head]!;\n const node = entry >= 0 ? entry : aside!.nodes[~entry]!;\n let carry: Carry<T> = entry >= 0 ? WHOLE : aside!.carries[~entry]!;\n if (handed !== undefined && head === from) carry = handed;\n else if (hand !== undefined && head >= starts) carry = step.hand!(node, carry) ?? WHOLE;\n if (carry !== WHOLE && carry.size === 0) continue;\n // What it holds, which what it carries is part of: a seed that holds\n // nothing of what it starts with still starts with it.\n const holds = carry === WHOLE ? undefined : partial?.get(node);\n let held: Held = holds ?? WHOLE;\n if (holdsNothing && head < starts && holds !== undefined && carry !== WHOLE) {\n const both = namesOf(carry);\n for (const name of holds) both.add(name);\n held = both;\n }\n const end = offset[node + 1]!;\n\n for (let edge = offset[node]!; edge < end; edge += 1) {\n const edgeKind = kind[edge]!;\n if (allowed[edgeKind] !== 1) continue;\n const next = target[edge]!;\n if (avoided?.[next] === 1) continue;\n // Entered and absent from `partial`: it holds the whole file already.\n const seen = mask[next] === 1;\n if (seen && (partial === undefined || !partial.has(next))) continue;\n\n const out = arrive === undefined ? carry : (step.arrive!(next, node, edgeKind, carry, held) ?? WHOLE);\n if (out !== WHOLE && out.size === 0) continue;\n let arrived = out;\n if (!seen) {\n mask[next] = 1;\n if (via !== undefined) via[next] = node;\n entered?.push(next);\n if (out !== WHOLE) (partial ??= new Map()).set(next, namesOf(out));\n } else {\n if (out === WHOLE) partial!.delete(next);\n else {\n arrived = addedTo(partial!.get(next)!, out);\n if (arrived.size === 0) continue;\n }\n entered ??= nodesOf(queued, aside, []);\n }\n queued.push(arrived === WHOLE ? next : named((aside ??= { nodes: [], carries: [] }), next, arrived));\n }\n }\n\n // Nothing carried by name, nothing to decode: every entry is a node.\n return { mask, entered: entered ?? (aside === undefined ? queued : nodesOf(queued, aside, queued)) };\n}\n\n/** Where a whole walk met a hand that answered with names, and the names. */\ninterface Handed<T> {\n readonly head: number;\n readonly carry: ReadonlyMap<string, T>;\n}\n\n/**\n * The walk while every entry carries the whole file: an entered node takes\n * nothing more, so the mask is the whole record of what was entered. Returns\n * where a hand first answered with names, or `undefined` when the walk ended\n * whole.\n */\nfunction wholeFrom<T>(\n edges: Adjacency,\n queued: NodeId[],\n starts: number,\n step: CarriedStep<T>,\n mask: Uint8Array,\n via: Int32Array | undefined,\n allowed: Uint8Array,\n avoided: Uint8Array | undefined,\n): Handed<T> | undefined {\n const { offset, target, kind } = edges;\n const asks = step.hand !== undefined;\n for (let head = 0; head < queued.length; head += 1) {\n const node = queued[head]!;\n if (asks && head >= starts) {\n const carry = step.hand!(node, WHOLE);\n if (carry !== undefined && carry !== WHOLE) {\n if (carry.size === 0) continue;\n return { head, carry };\n }\n }\n const end = offset[node + 1]!;\n for (let edge = offset[node]!; edge < end; edge += 1) {\n if (allowed[kind[edge]!] !== 1) continue;\n const next = target[edge]!;\n if (mask[next] === 1 || avoided?.[next] === 1) continue;\n mask[next] = 1;\n if (via !== undefined) via[next] = node;\n queued.push(next);\n }\n }\n return undefined;\n}\n\n/** What the worklist carries by name, out of line so a whole entry is one number. */\ninterface Aside<T> {\n readonly nodes: NodeId[];\n readonly carries: Array<ReadonlyMap<string, T>>;\n}\n\n/** The worklist entry for `node` carrying the names in `carry`. */\nfunction named<T>(aside: Aside<T>, node: NodeId, carry: ReadonlyMap<string, T>): number {\n aside.carries.push(carry);\n return ~(aside.nodes.push(node) - 1);\n}\n\n/** The nodes of `entries`, written into `into`, which may be `entries` itself. */\nfunction nodesOf<T>(entries: readonly number[], aside: Aside<T> | undefined, into: NodeId[]): NodeId[] {\n for (let at = 0; at < entries.length; at += 1) {\n const entry = entries[at]!;\n into[at] = entry >= 0 ? entry : aside!.nodes[~entry]!;\n }\n return into;\n}\n\n/** What `carry` adds to `holds`, which takes it in. */\nfunction addedTo<T>(holds: Set<string>, carry: ReadonlyMap<string, T>): ReadonlyMap<string, T> {\n const added = new Map<string, T>();\n for (const [name, payload] of carry) {\n if (holds.has(name)) continue;\n holds.add(name);\n added.set(name, payload);\n }\n return added;\n}\n\n/**\n * The names a carry holds, as a set the walk grows. Added one by one: V8 builds\n * a `Set` from a map's key iterator several times slower than this loop.\n */\nfunction namesOf<T>(carry: ReadonlyMap<string, T>): Set<string> {\n const names = new Set<string>();\n for (const name of carry.keys()) names.add(name);\n return names;\n}\n\n/**\n * The marked nodes in id order. The walk's own order depends on seed order;\n * this one does not, so a report built from it is byte-stable whatever order\n * the caller listed its changed files in.\n */\nexport function inIdOrder(mask: Uint8Array): NodeId[] {\n const visited: NodeId[] = [];\n for (let id = 0; id < mask.length; id += 1) if (mask[id] === 1) visited.push(id);\n return visited;\n}\n\n/** The default filter, built once: a walk runs once per changed file. */\nconst EVERY = allowedKinds(undefined);\n\n/**\n * A kind filter as a byte lookup, so the inner loop tests a byte; `fallback`\n * when no kinds are named.\n */\nexport function allowedKinds(\n through: Iterable<EdgeKind> | undefined,\n fallback: readonly EdgeKind[] = EDGE_KINDS,\n): Uint8Array {\n const allowed = new Uint8Array(EDGE_KINDS.length);\n for (const name of through ?? fallback) {\n const at = EDGE_KINDS.indexOf(name);\n if (at !== -1) allowed[at] = 1;\n }\n return allowed;\n}\n"]}
@@ -47,7 +47,7 @@
47
47
  * for each name an install resolved. That is deliberate rather than incidental. A
48
48
  * component relation — *`TodoFooter` renders `Chip`* — is another edge kind
49
49
  * between nodes of another kind in this same structure, and
50
- * [`composition.md`](../../../../docs/composition.md) already computes exactly
50
+ * [`composition.md`](https://variance-authority.dev/docs/composition) already computes exactly
51
51
  * that relation from what a run rendered. One graph means the static answer and
52
52
  * the rendered answer are joinable rather than adjacent.
53
53
  *
@@ -56,7 +56,7 @@
56
56
  * tests, and leaves it again at the packages — so a bumped dependency is a node
57
57
  * like any other, and *which of our files a new `jsdom` can affect* is the
58
58
  * one traversal this file already performs, seeded on the other side
59
- * ([`docs/selecting.md`](../../../../docs/selecting.md)).
59
+ * ([`docs/selecting.md`](https://variance-authority.dev/docs/selecting)).
60
60
  *
61
61
  * ## What it is not
62
62
  *
@@ -76,11 +76,11 @@ export type NodeKind = (typeof NODE_KINDS)[number];
76
76
  * Edge kinds, in id order.
77
77
  *
78
78
  * The distinctions are kept because they explain a finding. One of them also
79
- * decides a walk: `type` is erased by every compiler, so nothing behind it runs
80
- * and nothing behind it renders, and the default traversal leaves it out
81
- * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a
82
- * runtime — a docgen that reads prop types, a type-check gate — passes
83
- * `EDGE_KINDS` and walks it.
79
+ * decides a runtime walk: `type` is erased by every compiler, so nothing behind
80
+ * it runs and nothing behind it renders. A traversal walks every kind unless its
81
+ * caller names others, because a type-only import is still a dependency; a
82
+ * caller whose question is what a run loads — test selection — passes
83
+ * `RUNTIME_EDGES` and leaves it out.
84
84
  */
85
85
  export declare const EDGE_KINDS: readonly [
86
86
  /** `import x from './y'` — a value import. */
@@ -101,10 +101,11 @@ export declare const EDGE_KINDS: readonly [
101
101
  'depends'];
102
102
  export type EdgeKind = (typeof EDGE_KINDS)[number];
103
103
  /**
104
- * The edge kinds a runtime loads through: every kind but `type`. The
105
- * default for a traversal, and the list a caller widens from. A closure folds a
106
- * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and
107
- * one of these kinds leads nowhere any bytes are.
104
+ * The edge kinds a runtime loads through: every kind but `type`. What a caller
105
+ * asking a runtime question, such as test selection, passes as `through`; a
106
+ * traversal walks every kind when it is not told. A closure folds a shorter
107
+ * list (`CLOSURE_EDGES`), because a digest is a claim about bytes and one of
108
+ * these kinds leads nowhere any bytes are.
108
109
  */
109
110
  export declare const RUNTIME_EDGES: readonly EdgeKind[];
110
111
  export interface Node {
@@ -114,10 +115,11 @@ export interface Node {
114
115
  * package the name source code imports it by — `@mui/material`, never a
115
116
  * version and never a resolution. Which copy a resolver handed any one
116
117
  * importer is unanswerable without running that resolver, and the question
117
- * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).
118
+ * this graph is asked never needs it ([`sense/lock`](https://github.com/Variance-Authority/variance-authority/blob/main/packages/sense/src/lock/index.ts)).
118
119
  */
119
120
  readonly name: string;
120
121
  }
122
+ /** One edge as a reader of a file states it: `from` reaches `to` through an edge of `kind`. */
121
123
  export interface Relation {
122
124
  readonly from: Node;
123
125
  readonly to: Node;