@variance-authority/core 0.5.10 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @variance-authority/core
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c9a35ca: `@variance-authority/core/relate` exports are renamed, and the old names are removed
8
+
9
+ The old names are removed, not kept as aliases:
10
+
11
+ | Was | Is |
12
+ |---|---|
13
+ | `movedBy` | `affectedBy` |
14
+ | `Reached` | `Affected` |
15
+ | `MovedOptions` | `AffectedOptions` |
16
+ | `movedBefore` | `changedBefore` |
17
+ | `Reach` | `Traversal` |
18
+ | `ReachOptions` | `TraversalOptions` |
19
+ | `Reach.reached` | `Traversal.nodes` |
20
+ | `Reached.reach` | `Affected.traversal` |
21
+
22
+ `dependentsOf` and `dependenciesOf` return a `Traversal`, and `trailOf` takes
23
+ one as its argument.
24
+ - 03984ae: A file whose imports could not all be read no longer widens selection
25
+
26
+ A `require(name)` or `import('./' + name)` has no written target. The walk uses
27
+ the edges that were read in such a file, and the recorded run answers the one
28
+ that was not: the module loads under the test however it was named.
29
+ `affectedBy` seeds only the changed files, the closure digest does not mark such
30
+ a file volatile, and it does not void a deviation baseline.
31
+
32
+ Removed, not kept as aliases: `Affected.opaque`, `Hole`, `ReachReport.opaque`,
33
+ `ReachHole` and `ReachedComponent.throughUnread`.
34
+
3
35
  ## 0.5.10
4
36
 
5
37
  Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
package/README.md CHANGED
@@ -254,7 +254,7 @@ observation profiles differ.
254
254
  | `isolateRegions` / `attributeRegions` | `cell` and `limit` bound mask work; `origin` and `containment` describe the coordinate origin and how much a node must contain a region |
255
255
  | `fingerprintOfMask` | `grid` controls the shape sample and `coverage` the minimum occupied share |
256
256
  | `compareLocales` / ignore validation | `slack` permits a declared locale distance; `sites` supplies resolved ignore locations and `now` evaluates expiry |
257
- | `buildDocket` / dependency reach | `sampleSize` limits review examples; `through` selects graph edge kinds, `avoid` names nodes a walk never covers, and `shadows` lists per file the modules its run never reaches, so `movedBy` leaves out a file every trail to which crosses one of its own shadows; `depends` adds the install — which package rests on which — so a dependency bump is a seed like any other and affects only the files that import it |
257
+ | `buildDocket` / dependency reach | `sampleSize` limits review examples; `through` selects graph edge kinds, `avoid` names nodes a walk never covers, and `shadows` lists per file the modules its run never loads, so `affectedBy` leaves out a file every trail to which crosses one of its own shadows; `depends` adds the install — which package rests on which — so a dependency bump is a seed like any other and affects only the files that import it |
258
258
  | `beforeReach` | `sensed` names the directories the scan already answers for, and the descent from a declared entry point stops at the first file under one of them rather than dragging the repository's own source in behind the harness |
259
259
  | `sharedClosures` | `floor` drops a shared subtree below a node count |
260
260
  | `lexiconOf` | `examples`, `declaredIn` and `regions` supply what the instances cannot: which components a subject is the example of, the files declaring each component, and the regions its journey covered |
@@ -5,7 +5,7 @@
5
5
  * the tests into the repository's own files, and goes out past them into the
6
6
  * dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every
7
7
  * question it asks is asked *against* the arrows: what depends on the thing
8
- * that moved.
8
+ * that changed.
9
9
  *
10
10
  * The far-left end of that line answers no such question. A `vitest.config.ts`,
11
11
  * a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a
@@ -48,7 +48,7 @@
48
48
  * it into the harness would trade an exact answer for a whole run — the one
49
49
  * direction this is not allowed to be wrong in. The walk avoids sensed files,
50
50
  * and avoiding a node stops the walk there rather than stepping over it: what
51
- * lies below a sensed file is reached *through* it, and it already answers.
51
+ * lies below a sensed file is imported *through* it, and it already answers.
52
52
  *
53
53
  * ## Where the two ends meet
54
54
  *
@@ -60,7 +60,7 @@
60
60
  *
61
61
  * That cost is real and is the operator's to spend: a config that imports a
62
62
  * bundler reaches everything the bundler rests on, so a bump inside that set
63
- * widens. It is the correct answer — the harness did move — and a repository
63
+ * widens. It is the correct answer — the harness did change — and a repository
64
64
  * that finds it too wide narrows what it declares rather than what this
65
65
  * concludes.
66
66
  */
@@ -106,7 +106,7 @@ export interface BeforeReachOptions {
106
106
  *
107
107
  * Along the arrows, unlike every other walk here, because this is the one
108
108
  * question whose subject has no dependents: *what does the harness rest on*.
109
- * Type-only edges are not followed — a config's `import type` is erased before
109
+ * Type-only edges are not walked — a config's `import type` is erased before
110
110
  * the suite runs, exactly as a component's is.
111
111
  */
112
112
  export declare function beforeReach(relations: Relations, entries: readonly string[], options?: BeforeReachOptions): BeforeReach;
@@ -115,15 +115,15 @@ export declare function beforeReach(relations: Relations, entries: readonly stri
115
115
  *
116
116
  * Files and packages together and in that order, because the caller's next act
117
117
  * is to print them in one sentence and a reader does not care which list a name
118
- * came out of. Empty is a real answer: the diff moved nothing the run rests on.
118
+ * came out of. Empty is a real answer: the diff changed nothing the run rests on.
119
119
  *
120
120
  * A declared entry claims every path under it, so naming a directory of
121
121
  * workflows is one line of configuration rather than one per file. A file the
122
- * walk found claims only itself: it was reached, so it is known exactly, and
122
+ * walk found claims only itself: the walk visited it, so it is known exactly, and
123
123
  * widening it to its directory would put its neighbours before reach without
124
124
  * anybody saying they were.
125
125
  */
126
- export declare function movedBefore(before: BeforeReach, changed: readonly string[], packages?: readonly string[]): readonly string[];
126
+ export declare function changedBefore(before: BeforeReach, changed: readonly string[], packages?: readonly string[]): readonly string[];
127
127
  /**
128
128
  * Whether a path lies under one of these directories.
129
129
  *
@@ -5,7 +5,7 @@
5
5
  * the tests into the repository's own files, and goes out past them into the
6
6
  * dependencies ([`records.ts`](./records.ts) holds all three as nodes). Every
7
7
  * question it asks is asked *against* the arrows: what depends on the thing
8
- * that moved.
8
+ * that changed.
9
9
  *
10
10
  * The far-left end of that line answers no such question. A `vitest.config.ts`,
11
11
  * a `jest.config.js`, a Next.js setup, a CI workflow, the Node version in a
@@ -48,7 +48,7 @@
48
48
  * it into the harness would trade an exact answer for a whole run — the one
49
49
  * direction this is not allowed to be wrong in. The walk avoids sensed files,
50
50
  * and avoiding a node stops the walk there rather than stepping over it: what
51
- * lies below a sensed file is reached *through* it, and it already answers.
51
+ * lies below a sensed file is imported *through* it, and it already answers.
52
52
  *
53
53
  * ## Where the two ends meet
54
54
  *
@@ -60,7 +60,7 @@
60
60
  *
61
61
  * That cost is real and is the operator's to spend: a config that imports a
62
62
  * bundler reaches everything the bundler rests on, so a bump inside that set
63
- * widens. It is the correct answer — the harness did move — and a repository
63
+ * widens. It is the correct answer — the harness did change — and a repository
64
64
  * that finds it too wide narrows what it declares rather than what this
65
65
  * concludes.
66
66
  */
@@ -71,7 +71,7 @@ import { dependenciesOf } from './reach.js';
71
71
  *
72
72
  * Along the arrows, unlike every other walk here, because this is the one
73
73
  * question whose subject has no dependents: *what does the harness rest on*.
74
- * Type-only edges are not followed — a config's `import type` is erased before
74
+ * Type-only edges are not walked — a config's `import type` is erased before
75
75
  * the suite runs, exactly as a component's is.
76
76
  */
77
77
  export function beforeReach(relations, entries, options = {}) {
@@ -107,7 +107,7 @@ export function beforeReach(relations, entries, options = {}) {
107
107
  if (!entered.has(id) && within(name, sensed))
108
108
  avoid.push(id);
109
109
  }
110
- for (const id of dependenciesOf(relations, seeds, { avoid }).reached) {
110
+ for (const id of dependenciesOf(relations, seeds, { avoid }).nodes) {
111
111
  const node = nodeAt(relations, id);
112
112
  if (node === undefined)
113
113
  continue;
@@ -123,15 +123,15 @@ export function beforeReach(relations, entries, options = {}) {
123
123
  *
124
124
  * Files and packages together and in that order, because the caller's next act
125
125
  * is to print them in one sentence and a reader does not care which list a name
126
- * came out of. Empty is a real answer: the diff moved nothing the run rests on.
126
+ * came out of. Empty is a real answer: the diff changed nothing the run rests on.
127
127
  *
128
128
  * A declared entry claims every path under it, so naming a directory of
129
129
  * workflows is one line of configuration rather than one per file. A file the
130
- * walk found claims only itself: it was reached, so it is known exactly, and
130
+ * walk found claims only itself: the walk visited it, so it is known exactly, and
131
131
  * widening it to its directory would put its neighbours before reach without
132
132
  * anybody saying they were.
133
133
  */
134
- export function movedBefore(before, changed, packages = []) {
134
+ export function changedBefore(before, changed, packages = []) {
135
135
  return [
136
136
  ...changed.filter((file) => before.files.has(file) || within(file, before.entries)),
137
137
  ...packages.filter((name) => before.packages.has(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 moved.\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 `setup.ts` the config loads, a fixture module only\n * that setup imports, a polyfill, the environment package it names: each is an\n * ordinary file the scan already holds, that nothing imports, whose change\n * reaches no component and narrows the run to nothing. Declared, the entry\n * point is walked *along* the arrows once and that whole set comes with it.\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 reached *through* it, and it already answers.\n *\n * ## Where the two ends meet\n *\n * The same walk carries into the package layer, and that is the whole of the\n * mixed case. A config imports `jest-environment-jsdom`, which rests on\n * `jsdom`; `jsdom` is bumped; the install comparison names it. It is a package\n * the harness reaches, so the run does not narrow — and no file in the\n * repository ever wrote the word. A change beyond reach, arriving before it.\n *\n * That cost is real and is the operator's to spend: a config that imports a\n * bundler reaches everything the bundler rests on, so a bump inside that set\n * widens. It is the correct answer — the harness did move — and a repository\n * that finds it too wide narrows what it declares rather than what this\n * concludes.\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 /** Packages they rest on, direct and transitive. */\n readonly packages: 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 followed — 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 packages = new Set<string>();\n const unread: string[] = [];\n if (entries.length === 0) return { entries, files, packages, 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 const seeds: NodeId[] = [];\n for (const entry of entries) {\n const id = held.get(entry);\n if (id === undefined) unread.push(entry);\n else seeds.push(id);\n }\n if (seeds.length === 0) return { entries, files, packages, 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 }).reached) {\n const node = nodeAt(relations, id);\n if (node === undefined) continue;\n if (node.kind === 'file') files.add(node.name);\n else if (node.kind === 'package') packages.add(node.name);\n }\n\n return { entries, files, packages, unread };\n}\n\n/**\n * Which of a diff's own inputs the harness rests on, as they were named.\n *\n * Files and packages together and in that order, because the caller's next act\n * is to print them in one sentence and a reader does not care which list a name\n * came out of. Empty is a real answer: the diff moved 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: it was reached, 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 movedBefore(\n before: BeforeReach,\n changed: readonly string[],\n packages: readonly string[] = [],\n): readonly string[] {\n return [\n ...changed.filter((file) => before.files.has(file) || within(file, before.entries)),\n ...packages.filter((name) => before.packages.has(name)),\n ];\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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAEH,OAAO,EAAE,MAAM,EAA+B,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwC5C;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,SAAoB,EACpB,OAA0B,EAC1B,OAAO,GAAuB,EAAE;IAEhC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEtE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACnC,IAAI,IAAI,EAAE,IAAI,KAAK,MAAM;YAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,EAAE,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YACpC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEpE,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,cAAc,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACnE,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QACjC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAmB,EACnB,OAA0B,EAC1B,QAAQ,GAAsB,EAAE;IAEhC,OAAO;QACL,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACnF,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,IAAuB;IAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;QACvB,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,OAAO,UAAU,KAAK,GAAG,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC","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 `setup.ts` the config loads, a fixture module only\n * that setup imports, a polyfill, the environment package it names: each is an\n * ordinary file the scan already holds, that nothing imports, whose change\n * reaches no component and narrows the run to nothing. Declared, the entry\n * point is walked *along* the arrows once and that whole set comes with it.\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 * The same walk carries into the package layer, and that is the whole of the\n * mixed case. A config imports `jest-environment-jsdom`, which rests on\n * `jsdom`; `jsdom` is bumped; the install comparison names it. It is a package\n * the harness reaches, so the run does not narrow — and no file in the\n * repository ever wrote the word. A change beyond reach, arriving before it.\n *\n * That cost is real and is the operator's to spend: a config that imports a\n * bundler reaches everything the bundler rests on, so a bump inside that set\n * widens. It is the correct answer — the harness did change — and a repository\n * that finds it too wide narrows what it declares rather than what this\n * concludes.\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 /** Packages they rest on, direct and transitive. */\n readonly packages: 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 packages = new Set<string>();\n const unread: string[] = [];\n if (entries.length === 0) return { entries, files, packages, 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 const seeds: NodeId[] = [];\n for (const entry of entries) {\n const id = held.get(entry);\n if (id === undefined) unread.push(entry);\n else seeds.push(id);\n }\n if (seeds.length === 0) return { entries, files, packages, 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 === undefined) continue;\n if (node.kind === 'file') files.add(node.name);\n else if (node.kind === 'package') packages.add(node.name);\n }\n\n return { entries, files, packages, unread };\n}\n\n/**\n * Which of a diff's own inputs the harness rests on, as they were named.\n *\n * Files and packages together and in that order, because the caller's next act\n * is to print them in one sentence and a reader does not care which list a name\n * came out of. 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(\n before: BeforeReach,\n changed: readonly string[],\n packages: readonly string[] = [],\n): readonly string[] {\n return [\n ...changed.filter((file) => before.files.has(file) || within(file, before.entries)),\n ...packages.filter((name) => before.packages.has(name)),\n ];\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"]}
@@ -54,7 +54,7 @@
54
54
  * A `package` earns its kind for the same reason and one more: it is the far end
55
55
  * of the same line. A run starts at the harness, arrives at our code through the
56
56
  * tests, and leaves it again at the packages — so a bumped dependency is a node
57
- * like any other, and *which of our files a new `jsdom` can have moved* is the
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
59
  * ([`docs/selecting.md`](../../../../docs/selecting.md)).
60
60
  *
@@ -98,8 +98,8 @@ export declare const EDGE_KINDS: readonly [
98
98
  'depends-on'];
99
99
  export type EdgeKind = (typeof EDGE_KINDS)[number];
100
100
  /**
101
- * The edge kinds something at runtime can follow: every kind but `type`. The
102
- * default for a reach, and the list a caller widens from. A closure folds a
101
+ * The edge kinds a runtime loads through: every kind but `type`. The
102
+ * default for a traversal, and the list a caller widens from. A closure folds a
103
103
  * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and
104
104
  * one of these kinds leads nowhere any bytes are.
105
105
  */
@@ -147,10 +147,8 @@ export interface Relations {
147
147
  * `1` where this node's outgoing edges could not be enumerated.
148
148
  *
149
149
  * A parse failure, a specifier that was not a literal, a file in a language
150
- * nothing here reads. It is *not* "this file has no dependencies", and the
151
- * difference is the whole safety argument: a file whose edges are unknown may
152
- * depend on anything that changed, so selection seeds the traversal with every
153
- * one of them (ADR-0002 — absent is not empty).
150
+ * nothing here reads. Kept for whoever reports on the scan. No walk reads it:
151
+ * the edge a reader could not see is one a recorded run sees load.
154
152
  */
155
153
  readonly unknown: Uint8Array;
156
154
  /**
@@ -172,7 +170,7 @@ export interface Relations {
172
170
  * Carried with the graph rather than handed to each walk, because the graph
173
171
  * crosses every seam a walk does and a table left behind at one of them is a
174
172
  * selection that quietly widened. Empty for a graph nobody tainted. A walk
175
- * against the arrows consults it: a file is moved by a change only when some
173
+ * against the arrows consults it: a change affects a file only when some
176
174
  * trail from the change arrives without crossing one of that file's shadows.
177
175
  */
178
176
  readonly shadows: ReadonlyMap<string, readonly string[]>;
@@ -54,7 +54,7 @@
54
54
  * A `package` earns its kind for the same reason and one more: it is the far end
55
55
  * of the same line. A run starts at the harness, arrives at our code through the
56
56
  * tests, and leaves it again at the packages — so a bumped dependency is a node
57
- * like any other, and *which of our files a new `jsdom` can have moved* is the
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
59
  * ([`docs/selecting.md`](../../../../docs/selecting.md)).
60
60
  *
@@ -97,8 +97,8 @@ export const EDGE_KINDS = [
97
97
  'depends-on',
98
98
  ];
99
99
  /**
100
- * The edge kinds something at runtime can follow: every kind but `type`. The
101
- * default for a reach, and the list a caller widens from. A closure folds a
100
+ * The edge kinds a runtime loads through: every kind but `type`. The
101
+ * default for a traversal, and the list a caller widens from. A closure folds a
102
102
  * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and
103
103
  * one of these kinds leads nowhere any bytes are.
104
104
  */
@@ -1 +1 @@
1
- {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * Three kinds and the joins between them: a `file` graph built from imports, the\n * `component` nodes the source index already knows how to find, and a `package`\n * for each name an install resolved. That is deliberate rather than incidental. A\n * component relation — *`TodoFooter` renders `Chip`* — is another edge kind\n * between nodes of another kind in this same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * A `package` earns its kind for the same reason and one more: it is the far end\n * of the same line. A run starts at the harness, arrives at our code through the\n * tests, and leaves it again at the packages — so a bumped dependency is a node\n * like any other, and *which of our files a new `jsdom` can have moved* is the\n * one traversal this file already performs, seeded on the other side\n * ([`docs/selecting.md`](../../../../docs/selecting.md)).\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component', 'package'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding. One of them also\n * decides a walk: `type` is erased by every compiler, so nothing behind it runs\n * and nothing behind it renders, and the default traversal leaves it out\n * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a\n * runtime — a docgen that reads prop types, a type-check gate — passes\n * `EDGE_KINDS` and walks it.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n /** A package to a package its install resolved beneath it. */\n 'depends-on',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\n/**\n * The edge kinds something at runtime can follow: every kind but `type`. The\n * default for a reach, and the list a caller widens from. A closure folds a\n * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and\n * one of these kinds leads nowhere any bytes are.\n */\nexport const RUNTIME_EDGES: readonly EdgeKind[] = EDGE_KINDS.filter((kind) => kind !== 'type');\n\nexport interface Node {\n readonly kind: NodeKind;\n /**\n * Repository-relative for a file, the identifier for a component, and for a\n * package the name source code imports it by — `@mui/material`, never a\n * version and never a resolution. Which copy a resolver handed any one\n * importer is unanswerable without running that resolver, and the question\n * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).\n */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. It is *not* \"this file has no dependencies\", and the\n * difference is the whole safety argument: a file whose edges are unknown may\n * depend on anything that changed, so selection seeds the traversal with every\n * one of them (ADR-0002 — absent is not empty).\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n\n /**\n * Per file, the files its run never reaches at any depth: what a mock\n * replaces for the whole of a test's run.\n *\n * Carried with the graph rather than handed to each walk, because the graph\n * crosses every seam a walk does and a table left behind at one of them is a\n * selection that quietly widened. Empty for a graph nobody tainted. A walk\n * against the arrows consults it: a file is moved by a change only when some\n * trail from the change arrives without crossing one of that file's shadows.\n */\n readonly shadows: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of a few\n * fixed words and none contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\n/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\n/** The node an id names: its kind and name, or `undefined` past the end. */\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/relate/graph.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What reaches what, stored so that asking is cheap.\n *\n * Every question this project asks about a change is a reachability question. A\n * diff names files; a subject is made of components; a component is declared in a\n * file that imports other files. *Which subjects could this diff have changed* is\n * one traversal of one graph, and the only reason it is not asked that way today\n * is that there has never been a graph to traverse.\n *\n * ## Why a structure rather than a `Map`\n *\n * The obvious shape is `Map<string, string[]>`, and it is wrong at the size this\n * runs at. A monorepo scan produces tens of thousands of files and several times\n * as many edges; the map holds one array object and one string copy per edge, and\n * a traversal chases a pointer for every step. What replaces it here is the\n * standard answer, and it is standard because nothing has beaten it:\n *\n * - **strings are interned once.** A node is an integer. Comparison is an integer\n * compare, membership is an index into a byte array, and a visited set is a\n * `Uint8Array` rather than a `Set<string>`.\n * - **edges are two typed arrays** in compressed-sparse-row form: `offset` says\n * where a node's row starts, `target` holds the rows end to end. One node's\n * edges are contiguous, so a traversal walks memory forwards instead of\n * following pointers into the heap.\n * - **both directions are materialized.** *What does this depend on* and *what\n * depends on this* are different questions, and a graph answering only the\n * first makes the second a whole-graph scan. The transpose is a counting sort —\n * one pass to count, one to place — so holding it costs `O(n + m)` once instead\n * of `O(n + m)` per query.\n *\n * ## Edges point at what a thing depends on\n *\n * One convention, held everywhere: **`A → B` means A depends on B**, so a change\n * in B may move A. Every affected-set question is therefore a walk against the\n * arrows, which is what `dependentsOf` does, and every *what would I have to read\n * to understand this* question is a walk along them.\n *\n * It decides the direction of the less obvious edges too. A component is\n * `declared-in` the file that declares it — the component depends on the file and\n * not the other way round — so editing that file reaches the component in the\n * same traversal that reaches every importer.\n *\n * ## Nodes are typed, because files are not the last kind\n *\n * Three kinds and the joins between them: a `file` graph built from imports, the\n * `component` nodes the source index already knows how to find, and a `package`\n * for each name an install resolved. That is deliberate rather than incidental. A\n * component relation — *`TodoFooter` renders `Chip`* — is another edge kind\n * between nodes of another kind in this same structure, and\n * [`composition.md`](../../../../docs/composition.md) already computes exactly\n * that relation from what a run rendered. One graph means the static answer and\n * the rendered answer are joinable rather than adjacent.\n *\n * A `package` earns its kind for the same reason and one more: it is the far end\n * of the same line. A run starts at the harness, arrives at our code through the\n * tests, and leaves it again at the packages — so a bumped dependency is a node\n * like any other, and *which of our files a new `jsdom` can affect* is the\n * one traversal this file already performs, seeded on the other side\n * ([`docs/selecting.md`](../../../../docs/selecting.md)).\n *\n * ## What it is not\n *\n * **Not a build graph.** It has no notion of a task, an output or a cache key,\n * and it does not know what an install produced. `nx` and `turbo` own that layer,\n * and what reads them reads their answer rather than competing with it.\n *\n * **Not a resolver.** Nothing here opens a file or knows what a specifier means.\n * `core` performs no I/O (ADR-0006); the records this folds are somebody else's\n * disk.\n */\n\n/** Node kinds, in id order. An id is an index into this, never the word. */\nexport const NODE_KINDS = ['file', 'component', 'package'] as const;\nexport type NodeKind = (typeof NODE_KINDS)[number];\n\n/**\n * Edge kinds, in id order.\n *\n * The distinctions are kept because they explain a finding. One of them also\n * decides a walk: `type` is erased by every compiler, so nothing behind it runs\n * and nothing behind it renders, and the default traversal leaves it out\n * (`RUNTIME_EDGES`). A caller whose question is about source rather than about a\n * runtime — a docgen that reads prop types, a type-check gate — passes\n * `EDGE_KINDS` and walks it.\n */\nexport const EDGE_KINDS = [\n /** `import x from './y'` — a value import. */\n 'imports',\n /** `export … from './y'` — an import that also republishes. */\n 'reexports',\n /** `import('./y')`, with a specifier that was a literal. */\n 'dynamic',\n /** `import type { T } from './y'` — erased before anything renders. */\n 'type',\n /** A stylesheet's `@import`, or a `url()` reaching a font or an image. */\n 'asset',\n /** A component to the file that declares it. */\n 'declared-in',\n /** A package to a package its install resolved beneath it. */\n 'depends-on',\n] as const;\nexport type EdgeKind = (typeof EDGE_KINDS)[number];\n\n/**\n * The edge kinds a runtime loads through: every kind but `type`. The\n * default for a traversal, and the list a caller widens from. A closure folds a\n * shorter list (`CLOSURE_EDGES`), because a digest is a claim about bytes and\n * one of these kinds leads nowhere any bytes are.\n */\nexport const RUNTIME_EDGES: readonly EdgeKind[] = EDGE_KINDS.filter((kind) => kind !== 'type');\n\nexport interface Node {\n readonly kind: NodeKind;\n /**\n * Repository-relative for a file, the identifier for a component, and for a\n * package the name source code imports it by — `@mui/material`, never a\n * version and never a resolution. Which copy a resolver handed any one\n * importer is unanswerable without running that resolver, and the question\n * this graph is asked never needs it ([`sense/lock`](../../../sense/src/lock/index.ts)).\n */\n readonly name: string;\n}\n\nexport interface Relation {\n readonly from: Node;\n readonly to: Node;\n readonly kind: EdgeKind;\n}\n\n/** An index into `Relations.names`. Valid only against the graph it came from. */\nexport type NodeId = number;\n\n/**\n * One direction of the edge set, compressed-sparse-row.\n *\n * Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`\n * describes `target[j]`. `offset` holds one more entry than there are nodes, so\n * the last row needs no special case.\n */\nexport interface Adjacency {\n readonly offset: Uint32Array;\n readonly target: Uint32Array;\n readonly kind: Uint8Array;\n}\n\nexport interface Relations {\n /** Node names by id, ordered by kind and then by code unit. */\n readonly names: readonly string[];\n /** The `NODE_KINDS` index of each node. */\n readonly kinds: Uint8Array;\n\n /** What each node depends on. */\n readonly depends: Adjacency;\n /** What depends on each node — the transpose, materialized. */\n readonly dependents: Adjacency;\n\n /**\n * `1` where this node's outgoing edges could not be enumerated.\n *\n * A parse failure, a specifier that was not a literal, a file in a language\n * nothing here reads. Kept for whoever reports on the scan. No walk reads it:\n * the edge a reader could not see is one a recorded run sees load.\n */\n readonly unknown: Uint8Array;\n\n /**\n * Why, for each of those nodes, in the words whoever read the file used.\n *\n * Sparse — a repository the scan understands carries none of these — and kept\n * apart from the mask because the mask is what the inner loop tests and this is\n * what an operator reads. Without it, a run that observed forty subjects instead\n * of twelve can say *because eight files could not be read* and can never say\n * **which**, which is the difference between a fact and a work item.\n */\n readonly reasons: ReadonlyMap<NodeId, string>;\n\n /** `keyOf` to id — the interning table, kept for lookup. */\n readonly index: ReadonlyMap<string, NodeId>;\n\n /**\n * Per file, the files its run never reaches at any depth: what a mock\n * replaces for the whole of a test's run.\n *\n * Carried with the graph rather than handed to each walk, because the graph\n * crosses every seam a walk does and a table left behind at one of them is a\n * selection that quietly widened. Empty for a graph nobody tainted. A walk\n * against the arrows consults it: a change affects a file only when some\n * trail from the change arrives without crossing one of that file's shadows.\n */\n readonly shadows: ReadonlyMap<string, readonly string[]>;\n}\n\n/**\n * The lookup key for a node.\n *\n * Runtime only; nothing serializes this, and no code parses it back — the\n * separator only has to be injective, and it is, because a kind is one of a few\n * fixed words and none contains a `:`. A name that contains one is therefore\n * fine.\n *\n * The obvious separator is a NUL, and it is the wrong one: a source file\n * carrying that byte stops being text to `grep`, `file` and `git diff`, which is\n * a search finding nothing looking exactly like a search that returned nothing.\n * `tools/boundaries.check.ts` fails a build over it.\n */\nexport function keyOf(kind: NodeKind, name: string): string {\n return `${kind}:${name}`;\n}\n\n/** The id a node is known by in these relations, or `undefined` for a name the scan never met. */\nexport function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined {\n return relations.index.get(keyOf(kind, name));\n}\n\n/** The node an id names: its kind and name, or `undefined` past the end. */\nexport function nodeAt(relations: Relations, id: NodeId): Node | undefined {\n const name = relations.names[id];\n if (name === undefined) return undefined;\n return { kind: NODE_KINDS[relations.kinds[id]!]!, name };\n}\n\n/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */\nexport function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[] {\n const wanted = NODE_KINDS.indexOf(kind);\n const found: NodeId[] = [];\n for (let id = 0; id < relations.kinds.length; id += 1) {\n if (relations.kinds[id] === wanted) found.push(id);\n }\n return found;\n}\n\n/**\n * Fold relations into the queryable form.\n *\n * Deterministic to the byte: nodes are ordered by kind and then by **code unit** —\n * never `localeCompare`, which would make an id a promise about `LANG` — and edges\n * are sorted and deduplicated, so two runs over the same records produce identical\n * arrays and a serialized graph is stable.\n *\n * `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The\n * transpose is a counting sort rather than a second pass over a map, which is why\n * both directions cost one build instead of one per query.\n */\nexport function relationsOf(input: {\n readonly relations: Iterable<Relation>;\n /**\n * Nodes whose outgoing edges are not fully known, and must be treated as such.\n *\n * A bare node says *this one is blind*. A pair says why, in whatever words the\n * reader of the file used — and the sentence is the half an operator can act on,\n * so it travels with the fact rather than being reconstructed from it.\n */\n readonly unknown?: Iterable<Node | readonly [Node, string]>;\n /** Nodes with no edges at all, which would otherwise be absent from the graph. */\n readonly isolated?: Iterable<Node>;\n /** Per file, the files its run never reaches; see {@link Relations.shadows}. */\n readonly shadows?: ReadonlyMap<string, readonly string[]>;\n}): Relations {\n const relations = [...input.relations];\n const unknownNodes = [...(input.unknown ?? [])].map((entry) =>\n Array.isArray(entry) ? entry : ([entry, undefined] as const),\n ) as readonly (readonly [Node, string | undefined])[];\n\n const seen = new Map<string, Node>();\n const see = (node: Node): void => {\n const key = keyOf(node.kind, node.name);\n if (!seen.has(key)) seen.set(key, node);\n };\n\n for (const relation of relations) {\n see(relation.from);\n see(relation.to);\n }\n for (const [node] of unknownNodes) see(node);\n for (const node of input.isolated ?? []) see(node);\n\n // Kind-major, then code unit. Kind-major keeps one kind's nodes contiguous,\n // which is what lets a caller filter to files without touching a string.\n const ordered = [...seen.values()].sort(\n (a, b) =>\n NODE_KINDS.indexOf(a.kind) - NODE_KINDS.indexOf(b.kind) ||\n (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),\n );\n\n const index = new Map<string, NodeId>();\n const names: string[] = Array.from<string>({ length: ordered.length });\n const kinds = new Uint8Array(ordered.length);\n\n for (const [id, node] of ordered.entries()) {\n index.set(keyOf(node.kind, node.name), id);\n names[id] = node.name;\n kinds[id] = NODE_KINDS.indexOf(node.kind);\n }\n\n const edges = sortedEdges(relations, index);\n const depends = adjacencyOf(edges, ordered.length, 'from');\n const dependents = adjacencyOf(edges, ordered.length, 'to');\n\n const unknown = new Uint8Array(ordered.length);\n const reasons = new Map<NodeId, string>();\n for (const [node, because] of unknownNodes) {\n const id = index.get(keyOf(node.kind, node.name));\n if (id === undefined) continue;\n\n unknown[id] = 1;\n if (because !== undefined) reasons.set(id, because);\n }\n\n return { names, kinds, depends, dependents, unknown, reasons, index, shadows: input.shadows ?? new Map() };\n}\n\ninterface Edge {\n readonly from: NodeId;\n readonly to: NodeId;\n readonly kind: number;\n}\n\n/**\n * Edges as ids, sorted and deduplicated.\n *\n * Sorting the whole list once is what makes every row of *both* adjacencies\n * sorted without sorting any row: each fill walks the list in order, so a row\n * receives its targets in order. Two files related twice — a value import and a\n * type import — stay two edges, because the kinds differ and one of them explains\n * a finding the other does not.\n */\nfunction sortedEdges(\n relations: readonly Relation[],\n index: ReadonlyMap<string, NodeId>,\n): readonly Edge[] {\n const edges: Edge[] = [];\n\n for (const relation of relations) {\n const from = index.get(keyOf(relation.from.kind, relation.from.name));\n const to = index.get(keyOf(relation.to.kind, relation.to.name));\n if (from === undefined || to === undefined) continue;\n edges.push({ from, to, kind: EDGE_KINDS.indexOf(relation.kind) });\n }\n\n edges.sort((a, b) => a.from - b.from || a.to - b.to || a.kind - b.kind);\n\n const unique: Edge[] = [];\n for (const edge of edges) {\n const last = unique[unique.length - 1];\n const same =\n last !== undefined && last.from === edge.from && last.to === edge.to && last.kind === edge.kind;\n if (!same) unique.push(edge);\n }\n\n return unique;\n}\n\n/**\n * One direction, by counting sort.\n *\n * `by` names the endpoint a row is keyed on: `from` produces *what this depends\n * on*, `to` produces the transpose. Two passes — count the degrees, then place —\n * and no per-node array is ever allocated.\n */\nfunction adjacencyOf(edges: readonly Edge[], nodes: number, by: 'from' | 'to'): Adjacency {\n const offset = new Uint32Array(nodes + 1);\n for (const edge of edges) offset[edge[by] + 1] = offset[edge[by] + 1]! + 1;\n for (let id = 0; id < nodes; id += 1) offset[id + 1] = offset[id + 1]! + offset[id]!;\n\n const target = new Uint32Array(edges.length);\n const kind = new Uint8Array(edges.length);\n const cursor = Uint32Array.from(offset.subarray(0, nodes));\n\n for (const edge of edges) {\n const row = edge[by];\n const at = cursor[row]!;\n target[at] = by === 'from' ? edge.to : edge.from;\n kind[at] = edge.kind;\n cursor[row] = at + 1;\n }\n\n return { offset, target, kind };\n}\n"]}
@@ -10,8 +10,8 @@
10
10
  * See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.
11
11
  */
12
12
  export { EDGE_KINDS, NODE_KINDS, RUNTIME_EDGES, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, type Adjacency, type EdgeKind, type Node, type NodeId, type NodeKind, type Relation, type Relations, } from './graph.js';
13
- export { beforeReach, movedBefore, within, type BeforeReach, type BeforeReachOptions, } from './before.js';
14
- export { dependenciesOf, dependentsOf, trailOf, type Reach, type ReachOptions, } from './reach.js';
13
+ export { beforeReach, changedBefore, within, type BeforeReach, type BeforeReachOptions, } from './before.js';
14
+ export { dependenciesOf, dependentsOf, trailOf, type Traversal, type TraversalOptions, } from './reach.js';
15
15
  export { CLOSURE_EDGES, closureOf, driftedBetween, type Closure, type ClosureInput, type Drift, } from './merkle.js';
16
- export { explain, movedBy, relationsOfFiles, type FileEdge, type FileRecord, type Hole, type MovedOptions, type PackageEdge, type RelationsOptions, type Reached, } from './records.js';
16
+ export { explain, affectedBy, relationsOfFiles, type FileEdge, type FileRecord, type AffectedOptions, type PackageEdge, type RelationsOptions, type Affected, } from './records.js';
17
17
  //# sourceMappingURL=index.d.ts.map
@@ -10,8 +10,8 @@
10
10
  * See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.
11
11
  */
12
12
  export { EDGE_KINDS, NODE_KINDS, RUNTIME_EDGES, idOf, keyOf, nodeAt, nodesOfKind, relationsOf, } from './graph.js';
13
- export { beforeReach, movedBefore, within, } from './before.js';
13
+ export { beforeReach, changedBefore, within, } from './before.js';
14
14
  export { dependenciesOf, dependentsOf, trailOf, } from './reach.js';
15
15
  export { CLOSURE_EDGES, closureOf, driftedBetween, } from './merkle.js';
16
- export { explain, movedBy, relationsOfFiles, } from './records.js';
16
+ export { explain, affectedBy, relationsOfFiles, } from './records.js';
17
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/relate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,IAAI,EACJ,KAAK,EACL,MAAM,EACN,WAAW,EACX,WAAW,GAQZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,WAAW,EACX,MAAM,GAGP,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,OAAO,GAGR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,GAIf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,OAAO,EACP,OAAO,EACP,gBAAgB,GAQjB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `core/relate` — what reaches what.\n *\n * A typed, interned, bidirectional graph and the two traversals over it, plus the\n * file-shaped record the scanner produces and the fold that turns a pile of them\n * into the structure. No I/O, no parser, no resolver: the expensive half belongs\n * to whoever owns the disk, and this is the half that has to be fast and has to\n * be provable without one.\n *\n * See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.\n */\n\nexport {\n EDGE_KINDS,\n NODE_KINDS,\n RUNTIME_EDGES,\n idOf,\n keyOf,\n nodeAt,\n nodesOfKind,\n relationsOf,\n type Adjacency,\n type EdgeKind,\n type Node,\n type NodeId,\n type NodeKind,\n type Relation,\n type Relations,\n} from './graph.js';\n\nexport {\n beforeReach,\n movedBefore,\n within,\n type BeforeReach,\n type BeforeReachOptions,\n} from './before.js';\n\nexport {\n dependenciesOf,\n dependentsOf,\n trailOf,\n type Reach,\n type ReachOptions,\n} from './reach.js';\n\nexport {\n CLOSURE_EDGES,\n closureOf,\n driftedBetween,\n type Closure,\n type ClosureInput,\n type Drift,\n} from './merkle.js';\n\nexport {\n explain,\n movedBy,\n relationsOfFiles,\n type FileEdge,\n type FileRecord,\n type Hole,\n type MovedOptions,\n type PackageEdge,\n type RelationsOptions,\n type Reached,\n} from './records.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/relate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,IAAI,EACJ,KAAK,EACL,MAAM,EACN,WAAW,EACX,WAAW,GAQZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,WAAW,EACX,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,OAAO,GAGR,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,cAAc,GAIf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,OAAO,EACP,UAAU,EACV,gBAAgB,GAOjB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * `core/relate` — what reaches what.\n *\n * A typed, interned, bidirectional graph and the two traversals over it, plus the\n * file-shaped record the scanner produces and the fold that turns a pile of them\n * into the structure. No I/O, no parser, no resolver: the expensive half belongs\n * to whoever owns the disk, and this is the half that has to be fast and has to\n * be provable without one.\n *\n * See [`docs/selecting.md`](../../../../docs/selecting.md) for what it is for.\n */\n\nexport {\n EDGE_KINDS,\n NODE_KINDS,\n RUNTIME_EDGES,\n idOf,\n keyOf,\n nodeAt,\n nodesOfKind,\n relationsOf,\n type Adjacency,\n type EdgeKind,\n type Node,\n type NodeId,\n type NodeKind,\n type Relation,\n type Relations,\n} from './graph.js';\n\nexport {\n beforeReach,\n changedBefore,\n within,\n type BeforeReach,\n type BeforeReachOptions,\n} from './before.js';\n\nexport {\n dependenciesOf,\n dependentsOf,\n trailOf,\n type Traversal,\n type TraversalOptions,\n} from './reach.js';\n\nexport {\n CLOSURE_EDGES,\n closureOf,\n driftedBetween,\n type Closure,\n type ClosureInput,\n type Drift,\n} from './merkle.js';\n\nexport {\n explain,\n affectedBy,\n relationsOfFiles,\n type FileEdge,\n type FileRecord,\n type AffectedOptions,\n type PackageEdge,\n type RelationsOptions,\n type Affected,\n} from './records.js';\n"]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The graph, hashed the way a build system hashes an action's inputs.
3
3
  *
4
- * Reachability answers *what could this diff have moved*. It needs a diff, which
4
+ * Reachability answers *what could this diff affect*. It needs a diff, which
5
5
  * means it needs `git`, a ref that exists, a checkout deep enough to contain it,
6
6
  * and the assumption that the ref is where this branch actually diverged. Every
7
7
  * one of those is a thing CI gets wrong, and each failure narrows a run.
@@ -20,7 +20,7 @@
20
20
  * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |
21
21
  * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |
22
22
  * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |
23
- * | a dependency changed two hops away | reached, and observed | different digest, and observed |
23
+ * | a dependency changed two hops away | affected, and observed | different digest, and observed |
24
24
  * | *why* a subject is being observed | a chain of files | a digest that differs |
25
25
  *
26
26
  * Neither replaces the other. The trail is the explanation, and the digest is the
@@ -31,16 +31,19 @@
31
31
  * A dependency graph has them and a Merkle tree cannot. Strongly connected
32
32
  * components are condensed and hashed as a unit, so every file in a cycle carries
33
33
  * the same digest — which is exactly the truth about a cycle: no member of one can
34
- * be called unchanged while another moved.
34
+ * be called unchanged while another changed.
35
35
  *
36
36
  * ## The digest that must never lie
37
37
  *
38
- * A digest says *these inputs are the same*. A file whose content was not supplied,
39
- * or whose own imports could not be read, breaks that claim — its closure may have
40
- * moved with no digest in this structure changing. Those nodes are marked
41
- * **volatile**, the mark propagates to everything that rests on them, and a
42
- * volatile node is treated as changed however its digest compares. A cache that
43
- * cannot be trusted must not be silently trusted.
38
+ * A digest says *these inputs are the same*. A file whose content was not supplied
39
+ * breaks that claim — its bytes may have changed with no digest in this structure
40
+ * changing. Those nodes are marked **volatile**, the mark propagates to everything
41
+ * that rests on them, and a volatile node is treated as changed however its digest
42
+ * compares. A cache that cannot be trusted must not be silently trusted.
43
+ *
44
+ * A file whose imports could not all be read is not volatile. Its bytes are
45
+ * hashed, and the edges that were read are folded; the one nobody could read is
46
+ * left to a recorded run, which sees the module load whatever named it.
44
47
  */
45
48
  import { type Digest } from '../format/hash.js';
46
49
  import { type EdgeKind, type Relations } from './graph.js';
@@ -49,7 +52,7 @@ export interface ClosureInput {
49
52
  /**
50
53
  * Content digest per file, keyed by the file's path.
51
54
  *
52
- * A file with no entry is volatile: nothing here can tell whether it moved.
55
+ * A file with no entry is volatile: nothing here can tell whether it changed.
53
56
  * Component nodes take no entry — a component has no bytes of its own, and its
54
57
  * content is the file that declares it, which it already depends on.
55
58
  */
@@ -61,7 +64,7 @@ export interface Closure {
61
64
  /** `keyOf(kind, name)` → the digest of everything that node rests on. */
62
65
  readonly digests: ReadonlyMap<string, Digest>;
63
66
  /**
64
- * Nodes whose digest cannot prove sameness, and everything resting on them.
67
+ * Nodes whose content was not supplied, and everything resting on them.
65
68
  *
66
69
  * Not an error. It is the honest half of the answer, and a caller that ignores
67
70
  * it converts a missing input into a subject nobody observed.
@@ -87,7 +90,7 @@ export interface Drift {
87
90
  readonly gone: readonly string[];
88
91
  }
89
92
  /**
90
- * What moved between two closures, without consulting a repository.
93
+ * What changed between two closures, without consulting a repository.
91
94
  *
92
95
  * Three ways a node lands in `changed`, and only the first is a difference: its
93
96
  * digest differs, it is **new**, or it is **volatile** — the last because a node
@@ -111,7 +114,7 @@ export declare function driftedBetween(before: Closure, after: Closure): Drift;
111
114
  *
112
115
  * `depends-on` is not in it either, for the opposite reason. It runs between two
113
116
  * packages, and a package node is a name rather than a content digest — an
114
- * install that moved is a fact the diff carries, never one a hash of names could
117
+ * install that changed is a fact the diff carries, never one a hash of names could
115
118
  * reveal. Folding it would add a term that cannot change and a walk that cannot
116
119
  * answer.
117
120
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The graph, hashed the way a build system hashes an action's inputs.
3
3
  *
4
- * Reachability answers *what could this diff have moved*. It needs a diff, which
4
+ * Reachability answers *what could this diff affect*. It needs a diff, which
5
5
  * means it needs `git`, a ref that exists, a checkout deep enough to contain it,
6
6
  * and the assumption that the ref is where this branch actually diverged. Every
7
7
  * one of those is a thing CI gets wrong, and each failure narrows a run.
@@ -20,7 +20,7 @@
20
20
  * | a change, then a revert | both commits are in the diff, so it widens | identical digest, nothing runs |
21
21
  * | rebase, squash, branch switch | the merge base moves and the diff with it | unaffected — no ref is consulted |
22
22
  * | a shallow clone with no merge base | cannot answer; runs everything | unaffected |
23
- * | a dependency changed two hops away | reached, and observed | different digest, and observed |
23
+ * | a dependency changed two hops away | affected, and observed | different digest, and observed |
24
24
  * | *why* a subject is being observed | a chain of files | a digest that differs |
25
25
  *
26
26
  * Neither replaces the other. The trail is the explanation, and the digest is the
@@ -31,16 +31,19 @@
31
31
  * A dependency graph has them and a Merkle tree cannot. Strongly connected
32
32
  * components are condensed and hashed as a unit, so every file in a cycle carries
33
33
  * the same digest — which is exactly the truth about a cycle: no member of one can
34
- * be called unchanged while another moved.
34
+ * be called unchanged while another changed.
35
35
  *
36
36
  * ## The digest that must never lie
37
37
  *
38
- * A digest says *these inputs are the same*. A file whose content was not supplied,
39
- * or whose own imports could not be read, breaks that claim — its closure may have
40
- * moved with no digest in this structure changing. Those nodes are marked
41
- * **volatile**, the mark propagates to everything that rests on them, and a
42
- * volatile node is treated as changed however its digest compares. A cache that
43
- * cannot be trusted must not be silently trusted.
38
+ * A digest says *these inputs are the same*. A file whose content was not supplied
39
+ * breaks that claim — its bytes may have changed with no digest in this structure
40
+ * changing. Those nodes are marked **volatile**, the mark propagates to everything
41
+ * that rests on them, and a volatile node is treated as changed however its digest
42
+ * compares. A cache that cannot be trusted must not be silently trusted.
43
+ *
44
+ * A file whose imports could not all be read is not volatile. Its bytes are
45
+ * hashed, and the edges that were read are folded; the one nobody could read is
46
+ * left to a recorded run, which sees the module load whatever named it.
44
47
  */
45
48
  import { digestCombine, digestString } from '../format/hash.js';
46
49
  import { EDGE_KINDS, NODE_KINDS, keyOf, } from './graph.js';
@@ -71,7 +74,7 @@ export function closureOf(input) {
71
74
  const kind = NODE_KINDS[relations.kinds[node]];
72
75
  const name = relations.names[node];
73
76
  const own = kind === 'file' ? content.get(name) : SETTLED;
74
- if (own === undefined || relations.unknown[node] === 1)
77
+ if (own === undefined)
75
78
  shaky = true;
76
79
  terms.push(`${kind}\u0000${name}\u0000${own ?? UNREAD}`);
77
80
  const { offset, target, kind: edgeKind } = relations.depends;
@@ -105,7 +108,7 @@ const UNREAD = '(unread)';
105
108
  /** The term a node that has no content of its own contributes. */
106
109
  const SETTLED = '(declared)';
107
110
  /**
108
- * What moved between two closures, without consulting a repository.
111
+ * What changed between two closures, without consulting a repository.
109
112
  *
110
113
  * Three ways a node lands in `changed`, and only the first is a difference: its
111
114
  * digest differs, it is **new**, or it is **volatile** — the last because a node
@@ -227,7 +230,7 @@ function condense(relations, allowed) {
227
230
  *
228
231
  * `depends-on` is not in it either, for the opposite reason. It runs between two
229
232
  * packages, and a package node is a name rather than a content digest — an
230
- * install that moved is a fact the diff carries, never one a hash of names could
233
+ * install that changed is a fact the diff carries, never one a hash of names could
231
234
  * reveal. Folding it would add a term that cannot change and a walk that cannot
232
235
  * answer.
233
236
  */