@variance-authority/core 0.1.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 +79 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/artifact.d.ts +47 -0
- package/dist/artifact.js +2 -0
- package/dist/artifact.js.map +1 -0
- package/dist/attribute/because.d.ts +43 -0
- package/dist/attribute/because.js +64 -0
- package/dist/attribute/because.js.map +1 -0
- package/dist/attribute/boundary.d.ts +191 -0
- package/dist/attribute/boundary.js +328 -0
- package/dist/attribute/boundary.js.map +1 -0
- package/dist/attribute/call-site.d.ts +96 -0
- package/dist/attribute/call-site.js +183 -0
- package/dist/attribute/call-site.js.map +1 -0
- package/dist/attribute/component-hash.d.ts +188 -0
- package/dist/attribute/component-hash.js +312 -0
- package/dist/attribute/component-hash.js.map +1 -0
- package/dist/attribute/composition-fixture.d.ts +18 -0
- package/dist/attribute/composition-fixture.js +53 -0
- package/dist/attribute/composition-fixture.js.map +1 -0
- package/dist/attribute/composition.d.ts +182 -0
- package/dist/attribute/composition.js +169 -0
- package/dist/attribute/composition.js.map +1 -0
- package/dist/attribute/control.d.ts +43 -0
- package/dist/attribute/control.js +54 -0
- package/dist/attribute/control.js.map +1 -0
- package/dist/attribute/coverage.d.ts +99 -0
- package/dist/attribute/coverage.js +86 -0
- package/dist/attribute/coverage.js.map +1 -0
- package/dist/attribute/divergence.d.ts +102 -0
- package/dist/attribute/divergence.js +152 -0
- package/dist/attribute/divergence.js.map +1 -0
- package/dist/attribute/index.d.ts +43 -0
- package/dist/attribute/index.js +29 -0
- package/dist/attribute/index.js.map +1 -0
- package/dist/attribute/instability.d.ts +89 -0
- package/dist/attribute/instability.js +183 -0
- package/dist/attribute/instability.js.map +1 -0
- package/dist/attribute/instances.d.ts +145 -0
- package/dist/attribute/instances.js +140 -0
- package/dist/attribute/instances.js.map +1 -0
- package/dist/attribute/locate.d.ts +22 -0
- package/dist/attribute/locate.js +100 -0
- package/dist/attribute/locate.js.map +1 -0
- package/dist/attribute/mask.d.ts +156 -0
- package/dist/attribute/mask.js +201 -0
- package/dist/attribute/mask.js.map +1 -0
- package/dist/attribute/movement.d.ts +211 -0
- package/dist/attribute/movement.js +181 -0
- package/dist/attribute/movement.js.map +1 -0
- package/dist/attribute/region.d.ts +188 -0
- package/dist/attribute/region.js +144 -0
- package/dist/attribute/region.js.map +1 -0
- package/dist/attribute/source-map.d.ts +98 -0
- package/dist/attribute/source-map.js +264 -0
- package/dist/attribute/source-map.js.map +1 -0
- package/dist/attribute/source.d.ts +77 -0
- package/dist/attribute/source.js +95 -0
- package/dist/attribute/source.js.map +1 -0
- package/dist/attribute/stack.d.ts +80 -0
- package/dist/attribute/stack.js +108 -0
- package/dist/attribute/stack.js.map +1 -0
- package/dist/compare/band.d.ts +85 -0
- package/dist/compare/band.js +70 -0
- package/dist/compare/band.js.map +1 -0
- package/dist/compare/cascade.d.ts +45 -0
- package/dist/compare/cascade.js +71 -0
- package/dist/compare/cascade.js.map +1 -0
- package/dist/compare/derive.d.ts +78 -0
- package/dist/compare/derive.js +39 -0
- package/dist/compare/derive.js.map +1 -0
- package/dist/compare/diff/attribution.d.ts +23 -0
- package/dist/compare/diff/attribution.js +244 -0
- package/dist/compare/diff/attribution.js.map +1 -0
- package/dist/compare/diff/compare-nodes.d.ts +15 -0
- package/dist/compare/diff/compare-nodes.js +227 -0
- package/dist/compare/diff/compare-nodes.js.map +1 -0
- package/dist/compare/diff/components.d.ts +20 -0
- package/dist/compare/diff/components.js +76 -0
- package/dist/compare/diff/components.js.map +1 -0
- package/dist/compare/diff/delta.d.ts +165 -0
- package/dist/compare/diff/delta.js +12 -0
- package/dist/compare/diff/delta.js.map +1 -0
- package/dist/compare/diff/index.d.ts +69 -0
- package/dist/compare/diff/index.js +101 -0
- package/dist/compare/diff/index.js.map +1 -0
- package/dist/compare/diff/match.d.ts +37 -0
- package/dist/compare/diff/match.js +167 -0
- package/dist/compare/diff/match.js.map +1 -0
- package/dist/compare/explain.d.ts +23 -0
- package/dist/compare/explain.js +190 -0
- package/dist/compare/explain.js.map +1 -0
- package/dist/compare/holding-diff.d.ts +25 -0
- package/dist/compare/holding-diff.js +74 -0
- package/dist/compare/holding-diff.js.map +1 -0
- package/dist/compare/impact.d.ts +56 -0
- package/dist/compare/impact.js +124 -0
- package/dist/compare/impact.js.map +1 -0
- package/dist/compare/index.d.ts +30 -0
- package/dist/compare/index.js +22 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/compare/instance.d.ts +30 -0
- package/dist/compare/instance.js +145 -0
- package/dist/compare/instance.js.map +1 -0
- package/dist/compare/observability.d.ts +49 -0
- package/dist/compare/observability.js +57 -0
- package/dist/compare/observability.js.map +1 -0
- package/dist/compare/parting.d.ts +179 -0
- package/dist/compare/parting.js +244 -0
- package/dist/compare/parting.js.map +1 -0
- package/dist/compare/slice.d.ts +74 -0
- package/dist/compare/slice.js +79 -0
- package/dist/compare/slice.js.map +1 -0
- package/dist/compare/value.d.ts +60 -0
- package/dist/compare/value.js +111 -0
- package/dist/compare/value.js.map +1 -0
- package/dist/format/accessibility.d.ts +27 -0
- package/dist/format/accessibility.js +17 -0
- package/dist/format/accessibility.js.map +1 -0
- package/dist/format/canonical.d.ts +38 -0
- package/dist/format/canonical.js +65 -0
- package/dist/format/canonical.js.map +1 -0
- package/dist/format/capture.d.ts +212 -0
- package/dist/format/capture.js +2 -0
- package/dist/format/capture.js.map +1 -0
- package/dist/format/document.d.ts +279 -0
- package/dist/format/document.js +65 -0
- package/dist/format/document.js.map +1 -0
- package/dist/format/environment.d.ts +120 -0
- package/dist/format/environment.js +53 -0
- package/dist/format/environment.js.map +1 -0
- package/dist/format/hash.d.ts +45 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -0
- package/dist/format/holding.d.ts +134 -0
- package/dist/format/holding.js +2 -0
- package/dist/format/holding.js.map +1 -0
- package/dist/format/index.d.ts +39 -0
- package/dist/format/index.js +25 -0
- package/dist/format/index.js.map +1 -0
- package/dist/format/intervention.d.ts +87 -0
- package/dist/format/intervention.js +11 -0
- package/dist/format/intervention.js.map +1 -0
- package/dist/format/profile.d.ts +59 -0
- package/dist/format/profile.js +66 -0
- package/dist/format/profile.js.map +1 -0
- package/dist/format/provenance.d.ts +179 -0
- package/dist/format/provenance.js +222 -0
- package/dist/format/provenance.js.map +1 -0
- package/dist/format/sha256.d.ts +34 -0
- package/dist/format/sha256.js +152 -0
- package/dist/format/sha256.js.map +1 -0
- package/dist/format/snapshot.d.ts +263 -0
- package/dist/format/snapshot.js +2 -0
- package/dist/format/snapshot.js.map +1 -0
- package/dist/format/stabilize.d.ts +190 -0
- package/dist/format/stabilize.js +333 -0
- package/dist/format/stabilize.js.map +1 -0
- package/dist/format/tier.d.ts +19 -0
- package/dist/format/tier.js +11 -0
- package/dist/format/tier.js.map +1 -0
- package/dist/format/value.d.ts +115 -0
- package/dist/format/value.js +182 -0
- package/dist/format/value.js.map +1 -0
- package/dist/format/wiring.d.ts +115 -0
- package/dist/format/wiring.js +60 -0
- package/dist/format/wiring.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/judge/docket.d.ts +85 -0
- package/dist/judge/docket.js +162 -0
- package/dist/judge/docket.js.map +1 -0
- package/dist/judge/fingerprint.d.ts +53 -0
- package/dist/judge/fingerprint.js +144 -0
- package/dist/judge/fingerprint.js.map +1 -0
- package/dist/judge/ignore.d.ts +206 -0
- package/dist/judge/ignore.js +237 -0
- package/dist/judge/ignore.js.map +1 -0
- package/dist/judge/index.d.ts +44 -0
- package/dist/judge/index.js +34 -0
- package/dist/judge/index.js.map +1 -0
- package/dist/judge/inspect.d.ts +148 -0
- package/dist/judge/inspect.js +208 -0
- package/dist/judge/inspect.js.map +1 -0
- package/dist/judge/intent.d.ts +146 -0
- package/dist/judge/intent.js +150 -0
- package/dist/judge/intent.js.map +1 -0
- package/dist/judge/locale.d.ts +111 -0
- package/dist/judge/locale.js +247 -0
- package/dist/judge/locale.js.map +1 -0
- package/dist/judge/scope.d.ts +41 -0
- package/dist/judge/scope.js +47 -0
- package/dist/judge/scope.js.map +1 -0
- package/dist/judge/sensitivity.d.ts +202 -0
- package/dist/judge/sensitivity.js +152 -0
- package/dist/judge/sensitivity.js.map +1 -0
- package/dist/judge/trail.d.ts +122 -0
- package/dist/judge/trail.js +96 -0
- package/dist/judge/trail.js.map +1 -0
- package/dist/judge/verdict.d.ts +39 -0
- package/dist/judge/verdict.js +32 -0
- package/dist/judge/verdict.js.map +1 -0
- package/dist/plan/index.d.ts +12 -0
- package/dist/plan/index.js +11 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/tool.d.ts +85 -0
- package/dist/plan/tool.js +87 -0
- package/dist/plan/tool.js.map +1 -0
- package/dist/relate/graph.d.ts +190 -0
- package/dist/relate/graph.js +234 -0
- package/dist/relate/graph.js.map +1 -0
- package/dist/relate/index.d.ts +16 -0
- package/dist/relate/index.js +16 -0
- package/dist/relate/index.js.map +1 -0
- package/dist/relate/merkle.d.ts +110 -0
- package/dist/relate/merkle.js +246 -0
- package/dist/relate/merkle.js.map +1 -0
- package/dist/relate/reach.d.ts +65 -0
- package/dist/relate/reach.js +111 -0
- package/dist/relate/reach.js.map +1 -0
- package/dist/relate/records.d.ts +129 -0
- package/dist/relate/records.js +112 -0
- package/dist/relate/records.js.map +1 -0
- package/dist/rules/index.d.ts +19 -0
- package/dist/rules/index.js +17 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/normalize/alias.d.ts +50 -0
- package/dist/rules/normalize/alias.js +111 -0
- package/dist/rules/normalize/alias.js.map +1 -0
- package/dist/rules/normalize/cascade.d.ts +85 -0
- package/dist/rules/normalize/cascade.js +324 -0
- package/dist/rules/normalize/cascade.js.map +1 -0
- package/dist/rules/normalize/color.d.ts +30 -0
- package/dist/rules/normalize/color.js +185 -0
- package/dist/rules/normalize/color.js.map +1 -0
- package/dist/rules/normalize/fixture.d.ts +83 -0
- package/dist/rules/normalize/fixture.js +74 -0
- package/dist/rules/normalize/fixture.js.map +1 -0
- package/dist/rules/normalize/index.d.ts +39 -0
- package/dist/rules/normalize/index.js +268 -0
- package/dist/rules/normalize/index.js.map +1 -0
- package/dist/rules/normalize/project.d.ts +42 -0
- package/dist/rules/normalize/project.js +83 -0
- package/dist/rules/normalize/project.js.map +1 -0
- package/dist/rules/normalize/shorthand.d.ts +38 -0
- package/dist/rules/normalize/shorthand.js +388 -0
- package/dist/rules/normalize/shorthand.js.map +1 -0
- package/dist/rules/normalize/value.d.ts +23 -0
- package/dist/rules/normalize/value.js +162 -0
- package/dist/rules/normalize/value.js.map +1 -0
- package/dist/rules/normalize/wrapper.d.ts +16 -0
- package/dist/rules/normalize/wrapper.js +137 -0
- package/dist/rules/normalize/wrapper.js.map +1 -0
- package/dist/rules/ruleset.d.ts +93 -0
- package/dist/rules/ruleset.js +175 -0
- package/dist/rules/ruleset.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +69 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verdict model (spec §7).
|
|
3
|
+
*
|
|
4
|
+
* A verdict is the system's only output that anyone acts on. Everything upstream
|
|
5
|
+
* — snapshots, diffs, bands, attribution — exists to produce one of these six
|
|
6
|
+
* words with a defensible reason attached.
|
|
7
|
+
*/
|
|
8
|
+
export type Verdict =
|
|
9
|
+
/** Hash hit, or never reachable from the change. */
|
|
10
|
+
'unchanged'
|
|
11
|
+
/** Fully explained by an adjudication that already happened upstream. */
|
|
12
|
+
| 'inherited'
|
|
13
|
+
/** Matches declared intent, or policy auto-approves this band. */
|
|
14
|
+
| 'authorized'
|
|
15
|
+
/** Band and policy require a human to sign off. One action per root. */
|
|
16
|
+
| 'needs-review'
|
|
17
|
+
/** Policy forbids it. An agent must fix this before opening a PR. */
|
|
18
|
+
| 'violation'
|
|
19
|
+
/**
|
|
20
|
+
* Raster residue with no semantic cause. The highest severity in the system.
|
|
21
|
+
*
|
|
22
|
+
* Inverted relative to pixel-diff tools, and deliberately so (spec §1): a
|
|
23
|
+
* change the pipeline cannot explain means provenance failed, so the tool has
|
|
24
|
+
* lost the thread rather than found a small problem.
|
|
25
|
+
*/
|
|
26
|
+
| 'unexplained';
|
|
27
|
+
/**
|
|
28
|
+
* A band the acting profile could not observe.
|
|
29
|
+
*
|
|
30
|
+
* Not a verdict, and never collapsed into one. A tier that cannot see a band must
|
|
31
|
+
* say so rather than pass it (ADR-0002); folding this into `unchanged` is exactly
|
|
32
|
+
* the false-negative the profile system exists to prevent.
|
|
33
|
+
*/
|
|
34
|
+
export declare const UNOBSERVED: 'unobserved';
|
|
35
|
+
export type BandOutcome = Verdict | typeof UNOBSERVED;
|
|
36
|
+
export declare function severityOf(verdict: Verdict): number;
|
|
37
|
+
/** Whether a verdict should fail CI. Policy may promote, never demote. */
|
|
38
|
+
export declare function blocks(verdict: Verdict): boolean;
|
|
39
|
+
//# sourceMappingURL=verdict.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verdict model (spec §7).
|
|
3
|
+
*
|
|
4
|
+
* A verdict is the system's only output that anyone acts on. Everything upstream
|
|
5
|
+
* — snapshots, diffs, bands, attribution — exists to produce one of these six
|
|
6
|
+
* words with a defensible reason attached.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A band the acting profile could not observe.
|
|
10
|
+
*
|
|
11
|
+
* Not a verdict, and never collapsed into one. A tier that cannot see a band must
|
|
12
|
+
* say so rather than pass it (ADR-0002); folding this into `unchanged` is exactly
|
|
13
|
+
* the false-negative the profile system exists to prevent.
|
|
14
|
+
*/
|
|
15
|
+
export const UNOBSERVED = 'unobserved';
|
|
16
|
+
/** Ordered by severity. Later entries win when a subject carries several. */
|
|
17
|
+
const SEVERITY = [
|
|
18
|
+
'unchanged',
|
|
19
|
+
'inherited',
|
|
20
|
+
'authorized',
|
|
21
|
+
'needs-review',
|
|
22
|
+
'violation',
|
|
23
|
+
'unexplained',
|
|
24
|
+
];
|
|
25
|
+
export function severityOf(verdict) {
|
|
26
|
+
return SEVERITY.indexOf(verdict);
|
|
27
|
+
}
|
|
28
|
+
/** Whether a verdict should fail CI. Policy may promote, never demote. */
|
|
29
|
+
export function blocks(verdict) {
|
|
30
|
+
return verdict === 'violation' || verdict === 'unexplained' || verdict === 'needs-review';
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=verdict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../../src/judge/verdict.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAsBH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,YAAqB,CAAC;AAIhD,6EAA6E;AAC7E,MAAM,QAAQ,GAAuB;IACnC,WAAW;IACX,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,aAAa;CACd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAAgB;IACzC,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,MAAM,CAAC,OAAgB;IACrC,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,KAAK,aAAa,IAAI,OAAO,KAAK,cAAc,CAAC;AAC5F,CAAC","sourcesContent":["/**\n * The verdict model (spec §7).\n *\n * A verdict is the system's only output that anyone acts on. Everything upstream\n * — snapshots, diffs, bands, attribution — exists to produce one of these six\n * words with a defensible reason attached.\n */\n\nexport type Verdict =\n /** Hash hit, or never reachable from the change. */\n | 'unchanged'\n /** Fully explained by an adjudication that already happened upstream. */\n | 'inherited'\n /** Matches declared intent, or policy auto-approves this band. */\n | 'authorized'\n /** Band and policy require a human to sign off. One action per root. */\n | 'needs-review'\n /** Policy forbids it. An agent must fix this before opening a PR. */\n | 'violation'\n /**\n * Raster residue with no semantic cause. The highest severity in the system.\n *\n * Inverted relative to pixel-diff tools, and deliberately so (spec §1): a\n * change the pipeline cannot explain means provenance failed, so the tool has\n * lost the thread rather than found a small problem.\n */\n | 'unexplained';\n\n/**\n * A band the acting profile could not observe.\n *\n * Not a verdict, and never collapsed into one. A tier that cannot see a band must\n * say so rather than pass it (ADR-0002); folding this into `unchanged` is exactly\n * the false-negative the profile system exists to prevent.\n */\nexport const UNOBSERVED = 'unobserved' as const;\n\nexport type BandOutcome = Verdict | typeof UNOBSERVED;\n\n/** Ordered by severity. Later entries win when a subject carries several. */\nconst SEVERITY: readonly Verdict[] = [\n 'unchanged',\n 'inherited',\n 'authorized',\n 'needs-review',\n 'violation',\n 'unexplained',\n];\n\nexport function severityOf(verdict: Verdict): number {\n return SEVERITY.indexOf(verdict);\n}\n\n/** Whether a verdict should fail CI. Policy may promote, never demote. */\nexport function blocks(verdict: Verdict): boolean {\n return verdict === 'violation' || verdict === 'unexplained' || verdict === 'needs-review';\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/plan` — a composition as a value.
|
|
3
|
+
*
|
|
4
|
+
* The declaration every tool carries, the list a caller assembles from them, and
|
|
5
|
+
* the identity derived from that list. Its own entrypoint because assembling a
|
|
6
|
+
* pipeline is a thing somebody does *before* they have any of the values the
|
|
7
|
+
* other groups describe — and because the identity is the mechanism that makes
|
|
8
|
+
* "declare what you did" enforceable rather than promised.
|
|
9
|
+
*/
|
|
10
|
+
export { planIdentity, validatePlan, planForTier, describePlan } from './tool.js';
|
|
11
|
+
export type { ToolKind, Tier, IdentityContribution, ToolDeclaration, Plan, PlanProblem, } from './tool.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@variance-authority/core/plan` — a composition as a value.
|
|
3
|
+
*
|
|
4
|
+
* The declaration every tool carries, the list a caller assembles from them, and
|
|
5
|
+
* the identity derived from that list. Its own entrypoint because assembling a
|
|
6
|
+
* pipeline is a thing somebody does *before* they have any of the values the
|
|
7
|
+
* other groups describe — and because the identity is the mechanism that makes
|
|
8
|
+
* "declare what you did" enforceable rather than promised.
|
|
9
|
+
*/
|
|
10
|
+
export { planIdentity, validatePlan, planForTier, describePlan } from './tool.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/plan/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC","sourcesContent":["/**\n * `@variance-authority/core/plan` — a composition as a value.\n *\n * The declaration every tool carries, the list a caller assembles from them, and\n * the identity derived from that list. Its own entrypoint because assembling a\n * pipeline is a thing somebody does *before* they have any of the values the\n * other groups describe — and because the identity is the mechanism that makes\n * \"declare what you did\" enforceable rather than promised.\n */\n\nexport { planIdentity, validatePlan, planForTier, describePlan } from './tool.js';\nexport type {\n ToolKind,\n Tier,\n IdentityContribution,\n ToolDeclaration,\n Plan,\n PlanProblem,\n} from './tool.js';\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Digest } from '../format/hash.js';
|
|
2
|
+
import { type Tier } from '../format/tier.js';
|
|
3
|
+
/**
|
|
4
|
+
* The declaration every tool carries, and the plan a caller assembles from them.
|
|
5
|
+
*
|
|
6
|
+
* The architecture says a pipeline is something a user composes rather than
|
|
7
|
+
* something this project ships. That is only true if a composition is a *value*:
|
|
8
|
+
* something that can be listed, checked, printed, and — the part that matters —
|
|
9
|
+
* whose identity is derived from its parts instead of hand-written somewhere
|
|
10
|
+
* else.
|
|
11
|
+
*
|
|
12
|
+
* The hand-written version is the bug this file exists to remove. When the
|
|
13
|
+
* identity of a result enumerates the machine, the scale, the fonts and the
|
|
14
|
+
* tricks, swapping a *tool* — a different pruner, a different comparator, a
|
|
15
|
+
* different clustering — leaves that identity untouched. Two results produced by
|
|
16
|
+
* different code then compare as though they were produced by the same code, and
|
|
17
|
+
* the difference is attributed to whichever component happens to sit under it.
|
|
18
|
+
* Contract 2 says declare what you did; a list somebody maintains by hand is a
|
|
19
|
+
* promise, and this is the mechanism.
|
|
20
|
+
*/
|
|
21
|
+
export type ToolKind = 'acquire' | 'prepare' | 'render' | 'hash' | 'compare' | 'isolate' | 'map' | 'judge' | 'record';
|
|
22
|
+
export type { Tier };
|
|
23
|
+
/**
|
|
24
|
+
* What a tool contributes to the identity of what it produces.
|
|
25
|
+
*
|
|
26
|
+
* `'nothing'` is a decision, not a default. A tool that genuinely cannot change
|
|
27
|
+
* an answer says so out loud, and the difference between "contributes nothing"
|
|
28
|
+
* and "nobody thought about it" stays visible — the same distinction contract 3
|
|
29
|
+
* makes everywhere else, applied to provenance.
|
|
30
|
+
*/
|
|
31
|
+
export type IdentityContribution = Digest | 'nothing';
|
|
32
|
+
export interface ToolDeclaration {
|
|
33
|
+
/**
|
|
34
|
+
* Stable identifier, and part of the plan's identity.
|
|
35
|
+
*
|
|
36
|
+
* Renaming a tool is therefore a new identity. That is correct: nothing
|
|
37
|
+
* downstream can know that two names referred to one implementation.
|
|
38
|
+
*/
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly kind: ToolKind;
|
|
41
|
+
readonly needs: Tier;
|
|
42
|
+
/** One sentence: what it does, and what it costs. */
|
|
43
|
+
readonly because: string;
|
|
44
|
+
/**
|
|
45
|
+
* Everything about this tool's configuration that could change its answer.
|
|
46
|
+
*
|
|
47
|
+
* A version, a policy, a threshold, a ruleset. Not its inputs — those are
|
|
48
|
+
* addressed separately — only what the tool *is*.
|
|
49
|
+
*/
|
|
50
|
+
readonly identity: IdentityContribution;
|
|
51
|
+
}
|
|
52
|
+
export type Plan = readonly ToolDeclaration[];
|
|
53
|
+
/**
|
|
54
|
+
* The identity of a composition.
|
|
55
|
+
*
|
|
56
|
+
* Order-sensitive, unlike an intervention recipe. Two plans containing the same
|
|
57
|
+
* tools in a different order do not produce the same answer — isolating before
|
|
58
|
+
* mapping and mapping before isolating are different systems — so the sequence
|
|
59
|
+
* is part of what is addressed.
|
|
60
|
+
*/
|
|
61
|
+
export declare function planIdentity(plan: Plan): Digest;
|
|
62
|
+
export interface PlanProblem {
|
|
63
|
+
readonly severity: 'error' | 'warning';
|
|
64
|
+
readonly tool: string;
|
|
65
|
+
readonly because: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* What is wrong with a composition, before it runs.
|
|
69
|
+
*
|
|
70
|
+
* Returned rather than thrown. A caller assembling a bespoke plan is entitled to
|
|
71
|
+
* a list, and some of what is reported here is a legitimate choice — running a
|
|
72
|
+
* raster tool at a semantic tier is a mistake, but a plan that produces no
|
|
73
|
+
* verdict is a perfectly good extraction pipeline.
|
|
74
|
+
*/
|
|
75
|
+
export declare function validatePlan(plan: Plan, tier: Tier): readonly PlanProblem[];
|
|
76
|
+
/** The tools a plan will actually run at a given tier. */
|
|
77
|
+
export declare function planForTier(plan: Plan, tier: Tier): Plan;
|
|
78
|
+
/**
|
|
79
|
+
* The plan as something a person reads before trusting a number that came out of it.
|
|
80
|
+
*
|
|
81
|
+
* Printed beside a verdict, this is what lets a reader tell "the button changed"
|
|
82
|
+
* from "the button changed, according to a composition that skipped attribution".
|
|
83
|
+
*/
|
|
84
|
+
export declare function describePlan(plan: Plan, tier: Tier): string;
|
|
85
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { digestCombine, digestValue } from '../format/hash.js';
|
|
2
|
+
import { tierReaches } from '../format/tier.js';
|
|
3
|
+
/**
|
|
4
|
+
* The identity of a composition.
|
|
5
|
+
*
|
|
6
|
+
* Order-sensitive, unlike an intervention recipe. Two plans containing the same
|
|
7
|
+
* tools in a different order do not produce the same answer — isolating before
|
|
8
|
+
* mapping and mapping before isolating are different systems — so the sequence
|
|
9
|
+
* is part of what is addressed.
|
|
10
|
+
*/
|
|
11
|
+
export function planIdentity(plan) {
|
|
12
|
+
return digestCombine('plan', plan.map((tool) => digestValue({
|
|
13
|
+
id: tool.id,
|
|
14
|
+
kind: tool.kind,
|
|
15
|
+
needs: tool.needs,
|
|
16
|
+
identity: tool.identity,
|
|
17
|
+
})));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What is wrong with a composition, before it runs.
|
|
21
|
+
*
|
|
22
|
+
* Returned rather than thrown. A caller assembling a bespoke plan is entitled to
|
|
23
|
+
* a list, and some of what is reported here is a legitimate choice — running a
|
|
24
|
+
* raster tool at a semantic tier is a mistake, but a plan that produces no
|
|
25
|
+
* verdict is a perfectly good extraction pipeline.
|
|
26
|
+
*/
|
|
27
|
+
export function validatePlan(plan, tier) {
|
|
28
|
+
const problems = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const tool of plan) {
|
|
31
|
+
if (seen.has(tool.id)) {
|
|
32
|
+
// Two tools under one name make the plan's identity ambiguous: the digest
|
|
33
|
+
// cannot distinguish the composition from one containing either alone.
|
|
34
|
+
problems.push({
|
|
35
|
+
severity: 'error',
|
|
36
|
+
tool: tool.id,
|
|
37
|
+
because: 'appears more than once, so the plan identity cannot address it',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
seen.add(tool.id);
|
|
41
|
+
if (!tierReaches(tier, tool.needs)) {
|
|
42
|
+
problems.push({
|
|
43
|
+
severity: 'error',
|
|
44
|
+
tool: tool.id,
|
|
45
|
+
because: `needs the ${tool.needs} tier and this plan runs at ${tier}, so it cannot answer ` +
|
|
46
|
+
'its question — and a tool that runs anyway reports whatever it managed to see',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!plan.some((tool) => tool.kind === 'judge')) {
|
|
51
|
+
problems.push({
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
tool: '(plan)',
|
|
54
|
+
because: 'contains no judge, so it produces observations and no verdict. Legitimate for an ' +
|
|
55
|
+
'extraction pipeline; a mistake if anything downstream expects a pass or a fail',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return problems;
|
|
59
|
+
}
|
|
60
|
+
/** The tools a plan will actually run at a given tier. */
|
|
61
|
+
export function planForTier(plan, tier) {
|
|
62
|
+
return plan.filter((tool) => tierReaches(tier, tool.needs));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The plan as something a person reads before trusting a number that came out of it.
|
|
66
|
+
*
|
|
67
|
+
* Printed beside a verdict, this is what lets a reader tell "the button changed"
|
|
68
|
+
* from "the button changed, according to a composition that skipped attribution".
|
|
69
|
+
*/
|
|
70
|
+
export function describePlan(plan, tier) {
|
|
71
|
+
if (plan.length === 0)
|
|
72
|
+
return 'an empty plan, which observes nothing';
|
|
73
|
+
const running = planForTier(plan, tier);
|
|
74
|
+
const skipped = plan.filter((tool) => !running.includes(tool));
|
|
75
|
+
return [
|
|
76
|
+
`${running.length} tool(s) at the ${tier} tier — ${planIdentity(plan)}`,
|
|
77
|
+
...running.map((tool) => ` ${tool.kind.padEnd(8)} ${tool.id} — ${tool.because}`),
|
|
78
|
+
...(skipped.length > 0
|
|
79
|
+
? [
|
|
80
|
+
'',
|
|
81
|
+
`${skipped.length} tool(s) need a more expensive tier and will not run:`,
|
|
82
|
+
...skipped.map((tool) => ` ${tool.id} needs ${tool.needs}`),
|
|
83
|
+
]
|
|
84
|
+
: []),
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/plan/tool.ts"],"names":[],"mappings":"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","sourcesContent":["import { digestCombine, digestValue, type Digest } from '../format/hash.js';\nimport { tierReaches, type Tier } from '../format/tier.js';\n\n/**\n * The declaration every tool carries, and the plan a caller assembles from them.\n *\n * The architecture says a pipeline is something a user composes rather than\n * something this project ships. That is only true if a composition is a *value*:\n * something that can be listed, checked, printed, and — the part that matters —\n * whose identity is derived from its parts instead of hand-written somewhere\n * else.\n *\n * The hand-written version is the bug this file exists to remove. When the\n * identity of a result enumerates the machine, the scale, the fonts and the\n * tricks, swapping a *tool* — a different pruner, a different comparator, a\n * different clustering — leaves that identity untouched. Two results produced by\n * different code then compare as though they were produced by the same code, and\n * the difference is attributed to whichever component happens to sit under it.\n * Contract 2 says declare what you did; a list somebody maintains by hand is a\n * promise, and this is the mechanism.\n */\n\nexport type ToolKind =\n | 'acquire'\n | 'prepare'\n | 'render'\n | 'hash'\n | 'compare'\n | 'isolate'\n | 'map'\n | 'judge'\n | 'record';\n\nexport type { Tier };\n\n/**\n * What a tool contributes to the identity of what it produces.\n *\n * `'nothing'` is a decision, not a default. A tool that genuinely cannot change\n * an answer says so out loud, and the difference between \"contributes nothing\"\n * and \"nobody thought about it\" stays visible — the same distinction contract 3\n * makes everywhere else, applied to provenance.\n */\nexport type IdentityContribution = Digest | 'nothing';\n\nexport interface ToolDeclaration {\n /**\n * Stable identifier, and part of the plan's identity.\n *\n * Renaming a tool is therefore a new identity. That is correct: nothing\n * downstream can know that two names referred to one implementation.\n */\n readonly id: string;\n readonly kind: ToolKind;\n readonly needs: Tier;\n /** One sentence: what it does, and what it costs. */\n readonly because: string;\n /**\n * Everything about this tool's configuration that could change its answer.\n *\n * A version, a policy, a threshold, a ruleset. Not its inputs — those are\n * addressed separately — only what the tool *is*.\n */\n readonly identity: IdentityContribution;\n}\n\nexport type Plan = readonly ToolDeclaration[];\n\n/**\n * The identity of a composition.\n *\n * Order-sensitive, unlike an intervention recipe. Two plans containing the same\n * tools in a different order do not produce the same answer — isolating before\n * mapping and mapping before isolating are different systems — so the sequence\n * is part of what is addressed.\n */\nexport function planIdentity(plan: Plan): Digest {\n return digestCombine(\n 'plan',\n plan.map((tool) =>\n digestValue({\n id: tool.id,\n kind: tool.kind,\n needs: tool.needs,\n identity: tool.identity,\n }),\n ),\n );\n}\n\nexport interface PlanProblem {\n readonly severity: 'error' | 'warning';\n readonly tool: string;\n readonly because: string;\n}\n\n/**\n * What is wrong with a composition, before it runs.\n *\n * Returned rather than thrown. A caller assembling a bespoke plan is entitled to\n * a list, and some of what is reported here is a legitimate choice — running a\n * raster tool at a semantic tier is a mistake, but a plan that produces no\n * verdict is a perfectly good extraction pipeline.\n */\nexport function validatePlan(plan: Plan, tier: Tier): readonly PlanProblem[] {\n const problems: PlanProblem[] = [];\n const seen = new Set<string>();\n\n for (const tool of plan) {\n if (seen.has(tool.id)) {\n // Two tools under one name make the plan's identity ambiguous: the digest\n // cannot distinguish the composition from one containing either alone.\n problems.push({\n severity: 'error',\n tool: tool.id,\n because: 'appears more than once, so the plan identity cannot address it',\n });\n }\n seen.add(tool.id);\n\n if (!tierReaches(tier, tool.needs)) {\n problems.push({\n severity: 'error',\n tool: tool.id,\n because:\n `needs the ${tool.needs} tier and this plan runs at ${tier}, so it cannot answer ` +\n 'its question — and a tool that runs anyway reports whatever it managed to see',\n });\n }\n }\n\n if (!plan.some((tool) => tool.kind === 'judge')) {\n problems.push({\n severity: 'warning',\n tool: '(plan)',\n because:\n 'contains no judge, so it produces observations and no verdict. Legitimate for an ' +\n 'extraction pipeline; a mistake if anything downstream expects a pass or a fail',\n });\n }\n\n return problems;\n}\n\n/** The tools a plan will actually run at a given tier. */\nexport function planForTier(plan: Plan, tier: Tier): Plan {\n return plan.filter((tool) => tierReaches(tier, tool.needs));\n}\n\n/**\n * The plan as something a person reads before trusting a number that came out of it.\n *\n * Printed beside a verdict, this is what lets a reader tell \"the button changed\"\n * from \"the button changed, according to a composition that skipped attribution\".\n */\nexport function describePlan(plan: Plan, tier: Tier): string {\n if (plan.length === 0) return 'an empty plan, which observes nothing';\n\n const running = planForTier(plan, tier);\n const skipped = plan.filter((tool) => !running.includes(tool));\n\n return [\n `${running.length} tool(s) at the ${tier} tier — ${planIdentity(plan)}`,\n ...running.map((tool) => ` ${tool.kind.padEnd(8)} ${tool.id} — ${tool.because}`),\n ...(skipped.length > 0\n ? [\n '',\n `${skipped.length} tool(s) need a more expensive tier and will not run:`,\n ...skipped.map((tool) => ` ${tool.id} needs ${tool.needs}`),\n ]\n : []),\n ].join('\\n');\n}\n"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What reaches what, stored so that asking is cheap.
|
|
3
|
+
*
|
|
4
|
+
* Every question this project asks about a change is a reachability question. A
|
|
5
|
+
* diff names files; a subject is made of components; a component is declared in a
|
|
6
|
+
* file that imports other files. *Which subjects could this diff have changed* is
|
|
7
|
+
* one traversal of one graph, and the only reason it is not asked that way today
|
|
8
|
+
* is that there has never been a graph to traverse.
|
|
9
|
+
*
|
|
10
|
+
* ## Why a structure rather than a `Map`
|
|
11
|
+
*
|
|
12
|
+
* The obvious shape is `Map<string, string[]>`, and it is wrong at the size this
|
|
13
|
+
* runs at. A monorepo scan produces tens of thousands of files and several times
|
|
14
|
+
* as many edges; the map holds one array object and one string copy per edge, and
|
|
15
|
+
* a traversal chases a pointer for every step. What replaces it here is the
|
|
16
|
+
* standard answer, and it is standard because nothing has beaten it:
|
|
17
|
+
*
|
|
18
|
+
* - **strings are interned once.** A node is an integer. Comparison is an integer
|
|
19
|
+
* compare, membership is an index into a byte array, and a visited set is a
|
|
20
|
+
* `Uint8Array` rather than a `Set<string>`.
|
|
21
|
+
* - **edges are two typed arrays** in compressed-sparse-row form: `offset` says
|
|
22
|
+
* where a node's row starts, `target` holds the rows end to end. One node's
|
|
23
|
+
* edges are contiguous, so a traversal walks memory forwards instead of
|
|
24
|
+
* following pointers into the heap.
|
|
25
|
+
* - **both directions are materialized.** *What does this depend on* and *what
|
|
26
|
+
* depends on this* are different questions, and a graph answering only the
|
|
27
|
+
* first makes the second a whole-graph scan. The transpose is a counting sort —
|
|
28
|
+
* one pass to count, one to place — so holding it costs `O(n + m)` once instead
|
|
29
|
+
* of `O(n + m)` per query.
|
|
30
|
+
*
|
|
31
|
+
* ## Edges point at what a thing depends on
|
|
32
|
+
*
|
|
33
|
+
* One convention, held everywhere: **`A → B` means A depends on B**, so a change
|
|
34
|
+
* in B may move A. Every affected-set question is therefore a walk against the
|
|
35
|
+
* arrows, which is what `dependentsOf` does, and every *what would I have to read
|
|
36
|
+
* to understand this* question is a walk along them.
|
|
37
|
+
*
|
|
38
|
+
* It decides the direction of the less obvious edges too. A component is
|
|
39
|
+
* `declared-in` the file that declares it — the component depends on the file and
|
|
40
|
+
* not the other way round — so editing that file reaches the component in the
|
|
41
|
+
* same traversal that reaches every importer.
|
|
42
|
+
*
|
|
43
|
+
* ## Nodes are typed, because files are not the last kind
|
|
44
|
+
*
|
|
45
|
+
* This ships with two kinds and the join between them: a `file` graph built from
|
|
46
|
+
* imports, and the `component` nodes the source index already knows how to find.
|
|
47
|
+
* That is deliberate rather than incidental. A component relation — *`TodoFooter`
|
|
48
|
+
* renders `Chip`* — is another edge kind between nodes of another kind in this
|
|
49
|
+
* same structure, and
|
|
50
|
+
* [`composition.md`](../../../../docs/composition.md) already computes exactly
|
|
51
|
+
* that relation from what a run rendered. One graph means the static answer and
|
|
52
|
+
* the rendered answer are joinable rather than adjacent.
|
|
53
|
+
*
|
|
54
|
+
* ## What it is not
|
|
55
|
+
*
|
|
56
|
+
* **Not a build graph.** It has no notion of a task, an output or a cache key,
|
|
57
|
+
* and it does not know what an install produced. `nx` and `turbo` own that layer,
|
|
58
|
+
* and what reads them reads their answer rather than competing with it.
|
|
59
|
+
*
|
|
60
|
+
* **Not a resolver.** Nothing here opens a file or knows what a specifier means.
|
|
61
|
+
* `core` performs no I/O (ADR-0006); the records this folds are somebody else's
|
|
62
|
+
* disk.
|
|
63
|
+
*/
|
|
64
|
+
/** Node kinds, in id order. An id is an index into this, never the word. */
|
|
65
|
+
export declare const NODE_KINDS: readonly ['file', 'component'];
|
|
66
|
+
export type NodeKind = (typeof NODE_KINDS)[number];
|
|
67
|
+
/**
|
|
68
|
+
* Edge kinds, in id order.
|
|
69
|
+
*
|
|
70
|
+
* The distinctions are kept because they explain a finding, not because the
|
|
71
|
+
* default traversal narrows on them. `type` is erased by every compiler and can
|
|
72
|
+
* change no rendering, and it is still walked unless a caller says otherwise —
|
|
73
|
+
* for the reason every rule in selection resolves the same way: a subject skipped
|
|
74
|
+
* in error is a green run over an unwatched surface, and a subject observed in
|
|
75
|
+
* error costs a collection.
|
|
76
|
+
*/
|
|
77
|
+
export declare const EDGE_KINDS: readonly [
|
|
78
|
+
/** `import x from './y'` — a value import. */
|
|
79
|
+
'imports',
|
|
80
|
+
/** `export … from './y'` — an import that also republishes. */
|
|
81
|
+
'reexports',
|
|
82
|
+
/** `import('./y')`, with a specifier that was a literal. */
|
|
83
|
+
'dynamic',
|
|
84
|
+
/** `import type { T } from './y'` — erased before anything renders. */
|
|
85
|
+
'type',
|
|
86
|
+
/** A stylesheet's `@import`, or a `url()` reaching a font or an image. */
|
|
87
|
+
'asset',
|
|
88
|
+
/** A component to the file that declares it. */
|
|
89
|
+
'declared-in'];
|
|
90
|
+
export type EdgeKind = (typeof EDGE_KINDS)[number];
|
|
91
|
+
export interface Node {
|
|
92
|
+
readonly kind: NodeKind;
|
|
93
|
+
/** Repository-relative for a file; the identifier for a component. */
|
|
94
|
+
readonly name: string;
|
|
95
|
+
}
|
|
96
|
+
export interface Relation {
|
|
97
|
+
readonly from: Node;
|
|
98
|
+
readonly to: Node;
|
|
99
|
+
readonly kind: EdgeKind;
|
|
100
|
+
}
|
|
101
|
+
/** An index into `Relations.names`. Valid only against the graph it came from. */
|
|
102
|
+
export type NodeId = number;
|
|
103
|
+
/**
|
|
104
|
+
* One direction of the edge set, compressed-sparse-row.
|
|
105
|
+
*
|
|
106
|
+
* Node `i`'s edges are `target[offset[i] … offset[i + 1])`, and `kind[j]`
|
|
107
|
+
* describes `target[j]`. `offset` holds one more entry than there are nodes, so
|
|
108
|
+
* the last row needs no special case.
|
|
109
|
+
*/
|
|
110
|
+
export interface Adjacency {
|
|
111
|
+
readonly offset: Uint32Array;
|
|
112
|
+
readonly target: Uint32Array;
|
|
113
|
+
readonly kind: Uint8Array;
|
|
114
|
+
}
|
|
115
|
+
export interface Relations {
|
|
116
|
+
/** Node names by id, ordered by kind and then by code unit. */
|
|
117
|
+
readonly names: readonly string[];
|
|
118
|
+
/** The `NODE_KINDS` index of each node. */
|
|
119
|
+
readonly kinds: Uint8Array;
|
|
120
|
+
/** What each node depends on. */
|
|
121
|
+
readonly depends: Adjacency;
|
|
122
|
+
/** What depends on each node — the transpose, materialized. */
|
|
123
|
+
readonly dependents: Adjacency;
|
|
124
|
+
/**
|
|
125
|
+
* `1` where this node's outgoing edges could not be enumerated.
|
|
126
|
+
*
|
|
127
|
+
* A parse failure, a specifier that was not a literal, a file in a language
|
|
128
|
+
* nothing here reads. It is *not* "this file has no dependencies", and the
|
|
129
|
+
* difference is the whole safety argument: a file whose edges are unknown may
|
|
130
|
+
* depend on anything that changed, so selection seeds the traversal with every
|
|
131
|
+
* one of them (ADR-0002 — absent is not empty).
|
|
132
|
+
*/
|
|
133
|
+
readonly unknown: Uint8Array;
|
|
134
|
+
/**
|
|
135
|
+
* Why, for each of those nodes, in the words whoever read the file used.
|
|
136
|
+
*
|
|
137
|
+
* Sparse — a repository the scan understands carries none of these — and kept
|
|
138
|
+
* apart from the mask because the mask is what the inner loop tests and this is
|
|
139
|
+
* what an operator reads. Without it, a run that observed forty subjects instead
|
|
140
|
+
* of twelve can say *because eight files could not be read* and can never say
|
|
141
|
+
* **which**, which is the difference between a fact and a work item.
|
|
142
|
+
*/
|
|
143
|
+
readonly reasons: ReadonlyMap<NodeId, string>;
|
|
144
|
+
/** `keyOf` to id — the interning table, kept for lookup. */
|
|
145
|
+
readonly index: ReadonlyMap<string, NodeId>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The lookup key for a node.
|
|
149
|
+
*
|
|
150
|
+
* Runtime only; nothing serializes this, and no code parses it back — the
|
|
151
|
+
* separator only has to be injective, and it is, because a kind is one of two
|
|
152
|
+
* fixed words and neither contains a `:`. A name that contains one is therefore
|
|
153
|
+
* fine.
|
|
154
|
+
*
|
|
155
|
+
* The obvious separator is a NUL, and it is the wrong one: a source file
|
|
156
|
+
* carrying that byte stops being text to `grep`, `file` and `git diff`, which is
|
|
157
|
+
* a search finding nothing looking exactly like a search that returned nothing.
|
|
158
|
+
* `tools/boundaries.check.ts` fails a build over it.
|
|
159
|
+
*/
|
|
160
|
+
export declare function keyOf(kind: NodeKind, name: string): string;
|
|
161
|
+
export declare function idOf(relations: Relations, kind: NodeKind, name: string): NodeId | undefined;
|
|
162
|
+
export declare function nodeAt(relations: Relations, id: NodeId): Node | undefined;
|
|
163
|
+
/** Every node of one kind, as ids. Contiguous, because the order is kind-major. */
|
|
164
|
+
export declare function nodesOfKind(relations: Relations, kind: NodeKind): readonly NodeId[];
|
|
165
|
+
/**
|
|
166
|
+
* Fold relations into the queryable form.
|
|
167
|
+
*
|
|
168
|
+
* Deterministic to the byte: nodes are ordered by kind and then by **code unit** —
|
|
169
|
+
* never `localeCompare`, which would make an id a promise about `LANG` — and edges
|
|
170
|
+
* are sorted and deduplicated, so two runs over the same records produce identical
|
|
171
|
+
* arrays and a serialized graph is stable.
|
|
172
|
+
*
|
|
173
|
+
* `O(n log n + m log m)` for the two sorts and `O(n + m)` for everything else. The
|
|
174
|
+
* transpose is a counting sort rather than a second pass over a map, which is why
|
|
175
|
+
* both directions cost one build instead of one per query.
|
|
176
|
+
*/
|
|
177
|
+
export declare function relationsOf(input: {
|
|
178
|
+
readonly relations: Iterable<Relation>;
|
|
179
|
+
/**
|
|
180
|
+
* Nodes whose outgoing edges are not fully known, and must be treated as such.
|
|
181
|
+
*
|
|
182
|
+
* A bare node says *this one is blind*. A pair says why, in whatever words the
|
|
183
|
+
* reader of the file used — and the sentence is the half an operator can act on,
|
|
184
|
+
* so it travels with the fact rather than being reconstructed from it.
|
|
185
|
+
*/
|
|
186
|
+
readonly unknown?: Iterable<Node | readonly [Node, string]>;
|
|
187
|
+
/** Nodes with no edges at all, which would otherwise be absent from the graph. */
|
|
188
|
+
readonly isolated?: Iterable<Node>;
|
|
189
|
+
}): Relations;
|
|
190
|
+
//# sourceMappingURL=graph.d.ts.map
|