@shrkcrft/boundaries 0.1.0-alpha.29 → 0.1.0-alpha.31
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/dist/baseline/compute-baseline.d.ts +8 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -1
- package/dist/baseline/compute-baseline.js +10 -8
- package/dist/baseline/diff-baseline.d.ts +28 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -1
- package/dist/baseline/diff-baseline.js +29 -0
- package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
- package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-finding.js +21 -0
- package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
- package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-kind.js +14 -0
- package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
- package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
- package/dist/evaluate/evaluate-boundaries.js +481 -53
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-unit-finding.js +1 -0
- package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
- package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
- package/dist/evaluate/settle-boundary-rule.js +73 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
- package/dist/extract/code-zones.d.ts +95 -3
- package/dist/extract/code-zones.d.ts.map +1 -1
- package/dist/extract/code-zones.js +313 -4
- package/dist/extract/extract-tokens.d.ts +49 -1
- package/dist/extract/extract-tokens.d.ts.map +1 -1
- package/dist/extract/extract-tokens.js +283 -25
- package/dist/extract/i-labeled-source.d.ts +12 -0
- package/dist/extract/i-labeled-source.d.ts.map +1 -0
- package/dist/extract/i-labeled-source.js +1 -0
- package/dist/extract/import-edges.d.ts +50 -0
- package/dist/extract/import-edges.d.ts.map +1 -0
- package/dist/extract/import-edges.js +170 -0
- package/dist/extract/inspect-source.d.ts +38 -3
- package/dist/extract/inspect-source.d.ts.map +1 -1
- package/dist/extract/inspect-source.js +56 -8
- package/dist/extract/parse-imports.d.ts +80 -0
- package/dist/extract/parse-imports.d.ts.map +1 -0
- package/dist/extract/parse-imports.js +254 -0
- package/dist/extract/scan-literals.d.ts +12 -1
- package/dist/extract/scan-literals.d.ts.map +1 -1
- package/dist/extract/scan-literals.js +15 -1
- package/dist/extract/source-liveness-request.d.ts +24 -0
- package/dist/extract/source-liveness-request.d.ts.map +1 -0
- package/dist/extract/source-liveness-request.js +45 -0
- package/dist/generated/check-provenance.d.ts +20 -2
- package/dist/generated/check-provenance.d.ts.map +1 -1
- package/dist/generated/check-provenance.js +27 -6
- package/dist/generated/read-regen-tree.d.ts +16 -0
- package/dist/generated/read-regen-tree.d.ts.map +1 -0
- package/dist/generated/read-regen-tree.js +61 -0
- package/dist/generated/scan-generated.d.ts +57 -3
- package/dist/generated/scan-generated.d.ts.map +1 -1
- package/dist/generated/scan-generated.js +111 -11
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -0
- package/dist/model/boundary-intended-empty-line.d.ts +10 -0
- package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
- package/dist/model/boundary-intended-empty-line.js +11 -0
- package/dist/model/boundary-markable-list.d.ts +15 -0
- package/dist/model/boundary-markable-list.d.ts.map +1 -0
- package/dist/model/boundary-markable-list.js +15 -0
- package/dist/model/boundary-rule-input-keys.d.ts +11 -0
- package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
- package/dist/model/boundary-rule-input-keys.js +29 -0
- package/dist/model/boundary-rule-input.d.ts +23 -0
- package/dist/model/boundary-rule-input.d.ts.map +1 -0
- package/dist/model/boundary-rule-input.js +1 -0
- package/dist/model/boundary-rule-key-problems.d.ts +11 -0
- package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-key-problems.js +36 -0
- package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
- package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-marker-problems.js +94 -0
- package/dist/model/boundary-rule-scope.d.ts +88 -0
- package/dist/model/boundary-rule-scope.d.ts.map +1 -0
- package/dist/model/boundary-rule-scope.js +175 -0
- package/dist/model/boundary-rule.d.ts +120 -5
- package/dist/model/boundary-rule.d.ts.map +1 -1
- package/dist/model/boundary-rule.js +148 -1
- package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
- package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
- package/dist/model/boundary-unit-problem-issue.js +16 -0
- package/dist/model/normalize-boundary-rule.d.ts +21 -0
- package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
- package/dist/model/normalize-boundary-rule.js +62 -0
- package/dist/policy/evaluate-policy.d.ts +80 -3
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +152 -19
- package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
- package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
- package/dist/policy/i-policy-rule-liveness.js +1 -0
- package/dist/policy/run-policy.d.ts +1 -1
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +171 -21
- package/dist/registry/load-boundary-rules.d.ts +29 -1
- package/dist/registry/load-boundary-rules.d.ts.map +1 -1
- package/dist/registry/load-boundary-rules.js +38 -8
- package/dist/scan/glob.d.ts +90 -0
- package/dist/scan/glob.d.ts.map +1 -1
- package/dist/scan/glob.js +205 -0
- package/dist/scan/i-glob-unit-measure.d.ts +22 -0
- package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
- package/dist/scan/i-glob-unit-measure.js +1 -0
- package/dist/scan/import-pattern.d.ts +109 -0
- package/dist/scan/import-pattern.d.ts.map +1 -0
- package/dist/scan/import-pattern.js +191 -0
- package/dist/scan/node-builtin-package-names.d.ts +13 -0
- package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
- package/dist/scan/node-builtin-package-names.js +25 -0
- package/dist/scan/scan-imports.d.ts +36 -3
- package/dist/scan/scan-imports.d.ts.map +1 -1
- package/dist/scan/scan-imports.js +114 -45
- package/dist/util/blank-run-hazard-finding.d.ts +34 -0
- package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
- package/dist/util/blank-run-hazard-finding.js +1 -0
- package/dist/util/blank-run-hazard.d.ts +29 -0
- package/dist/util/blank-run-hazard.d.ts.map +1 -0
- package/dist/util/blank-run-hazard.js +450 -0
- package/dist/util/dead-glob-units.d.ts +46 -0
- package/dist/util/dead-glob-units.d.ts.map +1 -0
- package/dist/util/dead-glob-units.js +116 -0
- package/dist/util/glob-list-liveness-input.d.ts +31 -0
- package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
- package/dist/util/glob-list-liveness-input.js +65 -0
- package/dist/util/i-dead-glob-unit.d.ts +24 -0
- package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
- package/dist/util/i-dead-glob-unit.js +1 -0
- package/dist/util/i-glob-list-units.d.ts +25 -0
- package/dist/util/i-glob-list-units.d.ts.map +1 -0
- package/dist/util/i-glob-list-units.js +1 -0
- package/dist/util/i-glob-liveness-list.d.ts +23 -0
- package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-list.js +1 -0
- package/dist/util/i-glob-liveness-request.d.ts +11 -0
- package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-request.js +1 -0
- package/dist/util/i-glob-negation.d.ts +13 -0
- package/dist/util/i-glob-negation.d.ts.map +1 -0
- package/dist/util/i-glob-negation.js +1 -0
- package/dist/util/matched-files.d.ts +22 -0
- package/dist/util/matched-files.d.ts.map +1 -0
- package/dist/util/matched-files.js +1 -0
- package/dist/util/negation-cause.d.ts +15 -0
- package/dist/util/negation-cause.d.ts.map +1 -0
- package/dist/util/negation-cause.js +21 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
- package/dist/util/plane-scan-exclude-dirs.js +22 -0
- package/dist/util/read-glob-list-liveness.d.ts +20 -0
- package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
- package/dist/util/read-glob-list-liveness.js +20 -0
- package/dist/util/read-scope-coverage.d.ts +90 -0
- package/dist/util/read-scope-coverage.d.ts.map +1 -0
- package/dist/util/read-scope-coverage.js +173 -0
- package/dist/util/read-scope.d.ts +14 -0
- package/dist/util/read-scope.d.ts.map +1 -0
- package/dist/util/read-scope.js +1 -0
- package/dist/util/read-selected-files.d.ts +16 -0
- package/dist/util/read-selected-files.d.ts.map +1 -0
- package/dist/util/read-selected-files.js +25 -0
- package/dist/util/settle-glob-lists.d.ts +12 -0
- package/dist/util/settle-glob-lists.d.ts.map +1 -0
- package/dist/util/settle-glob-lists.js +13 -0
- package/dist/util/unread-file-reason.d.ts +26 -0
- package/dist/util/unread-file-reason.d.ts.map +1 -0
- package/dist/util/unread-file-reason.js +26 -0
- package/dist/util/unread-file.d.ts +10 -0
- package/dist/util/unread-file.d.ts.map +1 -0
- package/dist/util/unread-file.js +1 -0
- package/dist/util/walk-files.d.ts +90 -7
- package/dist/util/walk-files.d.ts.map +1 -1
- package/dist/util/walk-files.js +206 -19
- package/dist/wiring/evaluate-wiring.d.ts +88 -7
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +276 -16
- package/dist/wiring/explain-wiring.d.ts +56 -14
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +136 -10
- package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
- package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/i-idiom-role-coverage.js +1 -0
- package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
- package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/i-registration-query-verdict.js +1 -0
- package/dist/wiring/i-registration-roles.d.ts +71 -0
- package/dist/wiring/i-registration-roles.d.ts.map +1 -0
- package/dist/wiring/i-registration-roles.js +1 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/measure-idiom-role-coverage.js +20 -0
- package/dist/wiring/measure-registration-roles.d.ts +25 -0
- package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
- package/dist/wiring/measure-registration-roles.js +124 -0
- package/dist/wiring/plan-wiring-fix.d.ts +16 -1
- package/dist/wiring/plan-wiring-fix.d.ts.map +1 -1
- package/dist/wiring/plan-wiring-fix.js +177 -4
- package/dist/wiring/registration-graph.d.ts +13 -1
- package/dist/wiring/registration-graph.d.ts.map +1 -1
- package/dist/wiring/registration-graph.js +22 -6
- package/dist/wiring/registration-query-verdict.d.ts +23 -0
- package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/registration-query-verdict.js +118 -0
- package/dist/wiring/registry-query.d.ts +9 -1
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +17 -6
- package/dist/wiring/scan-wiring-files.d.ts +5 -3
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +55 -14
- package/dist/wiring/sink-imports.d.ts +95 -0
- package/dist/wiring/sink-imports.d.ts.map +1 -0
- package/dist/wiring/sink-imports.js +124 -0
- package/dist/wiring/trace-literal.d.ts +6 -0
- package/dist/wiring/trace-literal.d.ts.map +1 -1
- package/dist/wiring/trace-literal.js +5 -2
- package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
- package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
- package/dist/wiring/wiring-labeled-sources.js +20 -0
- package/package.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { escapeRegex, scanBalanced } from "../extract/scan-literals.js";
|
|
1
|
+
import { globListSelects } from "../scan/glob.js";
|
|
2
|
+
import { elementToken, escapeRegex, scanBalanced } from "../extract/scan-literals.js";
|
|
3
|
+
import { extractTokens } from "../extract/extract-tokens.js";
|
|
3
4
|
import { registeredSources } from "./evaluate-wiring.js";
|
|
5
|
+
import { collectSinkBindings, deriveImportTemplate, insertImportStatement, renderImport, renderSpecifier, } from "./sink-imports.js";
|
|
4
6
|
import { resolveExtractorAnchor, resolveExtractorKind } from '@shrkcrft/core';
|
|
5
7
|
export const WIRING_FIX_SCHEMA = 'sharkcraft.wiring-fix/v1';
|
|
6
8
|
/** Indentation of the line containing `index`. */
|
|
@@ -17,6 +19,134 @@ function lineOf(content, index) {
|
|
|
17
19
|
}
|
|
18
20
|
return line;
|
|
19
21
|
}
|
|
22
|
+
/** Extensions a derived specifier may resolve through, in probe order. */
|
|
23
|
+
const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'];
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a relative specifier against the sink's directory, POSIX-style.
|
|
26
|
+
*
|
|
27
|
+
* Only relative specifiers are resolvable this way. A bare specifier is a
|
|
28
|
+
* package (or a tsconfig alias) whose target this engine cannot know without a
|
|
29
|
+
* resolver, so those are reported unresolved and the caller refuses.
|
|
30
|
+
*/
|
|
31
|
+
function resolveRelative(sinkPath, specifier) {
|
|
32
|
+
if (!specifier.startsWith('.'))
|
|
33
|
+
return undefined;
|
|
34
|
+
const dir = sinkPath.includes('/') ? sinkPath.slice(0, sinkPath.lastIndexOf('/')) : '';
|
|
35
|
+
const parts = [...dir.split('/').filter(Boolean), ...specifier.split('/')];
|
|
36
|
+
const stack = [];
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
if (part === '' || part === '.')
|
|
39
|
+
continue;
|
|
40
|
+
if (part === '..') {
|
|
41
|
+
if (stack.length === 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
stack.pop();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
stack.push(part);
|
|
47
|
+
}
|
|
48
|
+
return stack.join('/');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether a derived specifier really points at the file that DECLARES the
|
|
52
|
+
* token.
|
|
53
|
+
*
|
|
54
|
+
* This is the safety net that makes the derived-import path trustworthy rather
|
|
55
|
+
* than merely plausible. The template says what the specifier *should* be; this
|
|
56
|
+
* confirms it names the file the gate found the token in. A template that
|
|
57
|
+
* happens to fit the existing members but misses for the new one is caught
|
|
58
|
+
* here instead of being written to disk.
|
|
59
|
+
*/
|
|
60
|
+
function specifierPointsAtDeclaration(sinkPath, specifier, declaringFile) {
|
|
61
|
+
const base = resolveRelative(sinkPath, specifier);
|
|
62
|
+
if (base === undefined)
|
|
63
|
+
return false;
|
|
64
|
+
if (base === declaringFile)
|
|
65
|
+
return true;
|
|
66
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
67
|
+
if (base + ext === declaringFile)
|
|
68
|
+
return true;
|
|
69
|
+
if (`${base}/index${ext}` === declaringFile)
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `file` exports `token`, per the same extractor the declared side of a
|
|
76
|
+
* rule most often uses.
|
|
77
|
+
*
|
|
78
|
+
* Reuses `extractTokens` rather than a second regex so "what counts as an
|
|
79
|
+
* export" has exactly one definition — a private answer here that drifted from
|
|
80
|
+
* the extractor would either block valid fixes or wave through invalid ones.
|
|
81
|
+
*/
|
|
82
|
+
function exportsToken(content, token) {
|
|
83
|
+
const res = extractTokens({ files: ['*'], extract: 'export-names' }, [{ path: 'x', content }]);
|
|
84
|
+
return res.sites.some((site) => site.token === token);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Decide whether appending `token` to the sink needs an import, and whether
|
|
88
|
+
* that import can be derived.
|
|
89
|
+
*
|
|
90
|
+
* The condition is an INCONSISTENCY check, not a resolvability check. An import
|
|
91
|
+
* is needed exactly when the sink's existing members are import-bound but the
|
|
92
|
+
* new token would not be — that is the case where the append introduces a
|
|
93
|
+
* reference the file cannot resolve, and where the gate would go green over a
|
|
94
|
+
* file that no longer compiles.
|
|
95
|
+
*
|
|
96
|
+
* Deliberately NOT "the token must be bound". If the existing members are
|
|
97
|
+
* themselves unbound (ambient globals, a `/// <reference>`, a fixture), then
|
|
98
|
+
* whatever makes them resolve applies equally to the new one, and the append is
|
|
99
|
+
* consistent with the file's own convention. Refusing there would be a false
|
|
100
|
+
* refusal on a file the edit does not make any worse.
|
|
101
|
+
*/
|
|
102
|
+
function decideImport(sinkPath, sinkContent, members, token, declaringFile, declaringContent) {
|
|
103
|
+
const bindings = collectSinkBindings(sinkContent);
|
|
104
|
+
// Already resolvable here — imported, or declared inline in the sink.
|
|
105
|
+
if (bindings.some((b) => b.local === token))
|
|
106
|
+
return { kind: 'append-only' };
|
|
107
|
+
// Only members brought in by an IMPORT establish a binding convention the new
|
|
108
|
+
// token has to follow. A locally-declared member says nothing about where an
|
|
109
|
+
// external one would come from.
|
|
110
|
+
const importedMembers = members.filter((m) => bindings.some((b) => b.local === m && b.kind !== 'local'));
|
|
111
|
+
// No member arrives by import, so there is no convention the new token is
|
|
112
|
+
// breaking — the append leaves the file exactly as consistent as it was.
|
|
113
|
+
if (importedMembers.length === 0)
|
|
114
|
+
return { kind: 'append-only' };
|
|
115
|
+
const template = deriveImportTemplate(sinkContent, members);
|
|
116
|
+
if (!template) {
|
|
117
|
+
return {
|
|
118
|
+
kind: 'refuse',
|
|
119
|
+
detail: `sink ${sinkPath} imports its members, but their import paths are not a function of the member name ` +
|
|
120
|
+
'(a barrel, mixed paths, or an aliased import) — the specifier for this token cannot be derived',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (declaringFile === undefined) {
|
|
124
|
+
return {
|
|
125
|
+
kind: 'refuse',
|
|
126
|
+
detail: `sink ${sinkPath} imports its members and the declaring file of "${token}" is unknown, so the import cannot be verified`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// A named import only works if the target actually EXPORTS the token. The
|
|
130
|
+
// gate may have found it with any extractor (`regex-capture` over `const (\w+)`
|
|
131
|
+
// matches un-exported locals too), so being declared there does not imply
|
|
132
|
+
// being exported from there.
|
|
133
|
+
if (declaringContent !== undefined && !exportsToken(declaringContent, token)) {
|
|
134
|
+
return {
|
|
135
|
+
kind: 'refuse',
|
|
136
|
+
detail: `"${token}" is declared in ${declaringFile} but not exported from it — ` +
|
|
137
|
+
'a named import would not resolve, so the array append is refused too',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const specifier = renderSpecifier(template, token);
|
|
141
|
+
if (!specifierPointsAtDeclaration(sinkPath, specifier, declaringFile)) {
|
|
142
|
+
return {
|
|
143
|
+
kind: 'refuse',
|
|
144
|
+
detail: `sink ${sinkPath} imports its members; the derived specifier "${specifier}" does not resolve to ` +
|
|
145
|
+
`${declaringFile}, where "${token}" is declared — refusing rather than writing a guess`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { kind: 'derived', template, statement: renderImport(template, token) };
|
|
149
|
+
}
|
|
20
150
|
/**
|
|
21
151
|
* Plan the mechanically-unambiguous fixes for `declared-but-not-registered`
|
|
22
152
|
* violations.
|
|
@@ -57,7 +187,7 @@ export function planWiringFix(rule, violations, files) {
|
|
|
57
187
|
const anchor = resolveExtractorAnchor(sink);
|
|
58
188
|
if (anchor === undefined)
|
|
59
189
|
return refuseAll('sink-not-an-array', 'sink declares no anchor');
|
|
60
|
-
const sinkFiles = files.filter((f) =>
|
|
190
|
+
const sinkFiles = files.filter((f) => globListSelects(f.path, sink.files ?? []));
|
|
61
191
|
if (sinkFiles.length !== 1) {
|
|
62
192
|
return refuseAll('ambiguous-sink-file', `sink glob matched ${sinkFiles.length} files — the insertion point must be unique`);
|
|
63
193
|
}
|
|
@@ -99,6 +229,25 @@ export function planWiringFix(rule, violations, files) {
|
|
|
99
229
|
}
|
|
100
230
|
const open = mm.index + mm[0].length - 1;
|
|
101
231
|
const scan = scanBalanced(content, open);
|
|
232
|
+
// Decide the IMPORT before touching the array. An append that leaves the
|
|
233
|
+
// token unbound compiles-fails, and a gate that goes green by breaking the
|
|
234
|
+
// build is worse than one that refuses — so a token we cannot bind is
|
|
235
|
+
// skipped with the reason, never half-written.
|
|
236
|
+
const currentMembers = scan.elements
|
|
237
|
+
.map((el) => elementToken(el.text))
|
|
238
|
+
.filter((t) => t !== undefined);
|
|
239
|
+
const decision = quoted
|
|
240
|
+
? { kind: 'append-only' } // string members bind nothing
|
|
241
|
+
: decideImport(target.path, content, currentMembers, v.token, v.file, v.file ? files.find((f) => f.path === v.file)?.content : undefined);
|
|
242
|
+
if (decision.kind === 'refuse') {
|
|
243
|
+
skipped.push({
|
|
244
|
+
ruleId: rule.id,
|
|
245
|
+
token: v.token,
|
|
246
|
+
reason: 'needs-import',
|
|
247
|
+
detail: decision.detail,
|
|
248
|
+
});
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
102
251
|
insertAt = scan.end;
|
|
103
252
|
const last = scan.elements[scan.elements.length - 1];
|
|
104
253
|
const indent = last ? indentOf(content, last.index) : ' ';
|
|
@@ -116,13 +265,37 @@ export function planWiringFix(rule, violations, files) {
|
|
|
116
265
|
? `${needsComma ? ',' : ''}\n${indent}${rendered},`
|
|
117
266
|
: `${needsComma ? ', ' : ''}${rendered}`;
|
|
118
267
|
content = content.slice(0, cut) + insert + tail + content.slice(insertAt);
|
|
268
|
+
const arrayLine = lineOf(content, cut + insert.length);
|
|
269
|
+
// Add the derived import LAST, so the array line number reported above is
|
|
270
|
+
// computed before the insertion shifts it — the two edits are reported
|
|
271
|
+
// against the file the reviewer will actually see.
|
|
272
|
+
let importInsert;
|
|
273
|
+
let importLine;
|
|
274
|
+
if (decision.kind === 'derived') {
|
|
275
|
+
const inserted = insertImportStatement(content, decision.statement);
|
|
276
|
+
if (!inserted) {
|
|
277
|
+
// Unreachable in practice (a template implies an import exists), but a
|
|
278
|
+
// planner that silently dropped the import would reintroduce the bug.
|
|
279
|
+
skipped.push({
|
|
280
|
+
ruleId: rule.id,
|
|
281
|
+
token: v.token,
|
|
282
|
+
reason: 'needs-import',
|
|
283
|
+
detail: `could not locate an import block in ${target.path} to extend`,
|
|
284
|
+
});
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
content = inserted.nextContent;
|
|
288
|
+
importInsert = decision.statement;
|
|
289
|
+
importLine = inserted.line;
|
|
290
|
+
}
|
|
119
291
|
edits.push({
|
|
120
292
|
ruleId: rule.id,
|
|
121
293
|
token: v.token,
|
|
122
294
|
file: target.path,
|
|
123
|
-
line:
|
|
295
|
+
line: arrayLine,
|
|
124
296
|
insert: insert.trim(),
|
|
125
297
|
nextContent: content,
|
|
298
|
+
...(importInsert !== undefined ? { importInsert, importLine: importLine } : {}),
|
|
126
299
|
});
|
|
127
300
|
}
|
|
128
301
|
return { schema: WIRING_FIX_SCHEMA, edits, skipped };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IRegistrationIdiom } from '@shrkcrft/core';
|
|
2
|
+
import type { IReadScope } from '../util/read-scope.js';
|
|
2
3
|
import { type IWiringFileEntry } from './evaluate-wiring.js';
|
|
3
4
|
export declare const REGISTRATION_GRAPH_SCHEMA: "sharkcraft.registration-graph/v1";
|
|
4
5
|
/** A token role-site: which idiom + file:line it was found at. */
|
|
@@ -31,6 +32,13 @@ export interface IRegistrationGraph {
|
|
|
31
32
|
readonly tokens: readonly IRegistrationNode[];
|
|
32
33
|
/** Misconfiguration messages (bad regex / no capture group / bad source). */
|
|
33
34
|
readonly diagnostics: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Set only when a file an idiom's globs matched was NOT read (over the read
|
|
37
|
+
* cap, or unreadable): the files read, and the unread ones. A token declared,
|
|
38
|
+
* provided or consumed only there is missing from `tokens`, so an
|
|
39
|
+
* "unprovided" or "orphan" answer over them proves nothing.
|
|
40
|
+
*/
|
|
41
|
+
readonly readScope?: IReadScope;
|
|
34
42
|
}
|
|
35
43
|
export interface IBuildRegistrationGraphOptions {
|
|
36
44
|
/** Project-relative directories to prune from the walk. */
|
|
@@ -53,6 +61,10 @@ export declare function buildRegistrationGraph(projectRoot: string, idioms: read
|
|
|
53
61
|
* persisted registration-graph cache — a source edit shifts the signature even
|
|
54
62
|
* when no reindex has run, so a stale wiring verdict is impossible. Walk-and-stat
|
|
55
63
|
* only (no file reads), so it stays far cheaper than a full build. Never throws.
|
|
64
|
+
*
|
|
65
|
+
* It signs the POSITIVE union walk, not each role's selected set: a file a
|
|
66
|
+
* role's `!` excludes still shifts the signature. That over-inclusion can only
|
|
67
|
+
* cause a spurious cache miss — the safe direction — never a stale verdict.
|
|
56
68
|
*/
|
|
57
69
|
export declare function registrationGraphSignature(projectRoot: string, idioms: readonly IRegistrationIdiom[], options?: IBuildRegistrationGraphOptions): string;
|
|
58
70
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { statSync } from 'node:fs';
|
|
3
3
|
import * as nodePath from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveSourceGlobs } from '@shrkcrft/core';
|
|
5
|
+
import { globListSelects } from "../scan/glob.js";
|
|
5
6
|
import { readMatchingFiles, walkMatching } from "../util/walk-files.js";
|
|
7
|
+
import { readScopeOfLists } from "../util/read-scope-coverage.js";
|
|
6
8
|
import { collectSourceSites } from "./evaluate-wiring.js";
|
|
7
9
|
export const REGISTRATION_GRAPH_SCHEMA = 'sharkcraft.registration-graph/v1';
|
|
8
10
|
function sortSites(sites) {
|
|
@@ -11,7 +13,11 @@ function sortSites(sites) {
|
|
|
11
13
|
/** Union (deduped) of every glob any role of any idiom references. */
|
|
12
14
|
function registrationGlobs(idioms) {
|
|
13
15
|
return [
|
|
14
|
-
...new Set(idioms.flatMap((i) => [
|
|
16
|
+
...new Set(idioms.flatMap((i) => [
|
|
17
|
+
...resolveSourceGlobs(i.declared),
|
|
18
|
+
...resolveSourceGlobs(i.provided),
|
|
19
|
+
...resolveSourceGlobs(i.consumed),
|
|
20
|
+
])),
|
|
15
21
|
];
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
@@ -23,12 +29,14 @@ function registrationGlobs(idioms) {
|
|
|
23
29
|
* diagnostic.
|
|
24
30
|
*/
|
|
25
31
|
export function buildRegistrationGraph(projectRoot, idioms, options = {}) {
|
|
26
|
-
const
|
|
27
|
-
const entries = [...
|
|
32
|
+
const matched = readMatchingFiles(projectRoot, registrationGlobs(idioms), new Set(options.excludeDirs ?? []));
|
|
33
|
+
const entries = [...matched.files.entries()].map(([path, content]) => ({
|
|
28
34
|
path,
|
|
29
35
|
content,
|
|
30
36
|
}));
|
|
31
|
-
|
|
37
|
+
// The walk is the POSITIVE union of every role of every idiom; each role
|
|
38
|
+
// selects its own files, so one role's `!x` never hides another role's file.
|
|
39
|
+
const filesFor = (source) => entries.filter((f) => globListSelects(f.path, resolveSourceGlobs(source)));
|
|
32
40
|
const declared = new Map();
|
|
33
41
|
const provided = new Map();
|
|
34
42
|
const consumed = new Map();
|
|
@@ -55,11 +63,15 @@ export function buildRegistrationGraph(projectRoot, idioms, options = {}) {
|
|
|
55
63
|
provided: sortSites(provided.get(token) ?? []),
|
|
56
64
|
consumed: sortSites(consumed.get(token) ?? []),
|
|
57
65
|
}));
|
|
66
|
+
// Read scope PER ROLE LIST (one file counted once): an unread file every
|
|
67
|
+
// role that could reach it EXCLUDES is out of scope, not a gap.
|
|
68
|
+
const scope = readScopeOfLists(matched, idioms.flatMap((i) => [i.declared, i.provided, i.consumed].map((s) => resolveSourceGlobs(s))));
|
|
58
69
|
return {
|
|
59
70
|
schema: REGISTRATION_GRAPH_SCHEMA,
|
|
60
71
|
idioms: idioms.map((i) => i.name),
|
|
61
72
|
tokens,
|
|
62
73
|
diagnostics,
|
|
74
|
+
...(scope.unread.length > 0 ? { readScope: scope } : {}),
|
|
63
75
|
};
|
|
64
76
|
}
|
|
65
77
|
/**
|
|
@@ -70,6 +82,10 @@ export function buildRegistrationGraph(projectRoot, idioms, options = {}) {
|
|
|
70
82
|
* persisted registration-graph cache — a source edit shifts the signature even
|
|
71
83
|
* when no reindex has run, so a stale wiring verdict is impossible. Walk-and-stat
|
|
72
84
|
* only (no file reads), so it stays far cheaper than a full build. Never throws.
|
|
85
|
+
*
|
|
86
|
+
* It signs the POSITIVE union walk, not each role's selected set: a file a
|
|
87
|
+
* role's `!` excludes still shifts the signature. That over-inclusion can only
|
|
88
|
+
* cause a spurious cache miss — the safe direction — never a stale verdict.
|
|
73
89
|
*/
|
|
74
90
|
export function registrationGraphSignature(projectRoot, idioms, options = {}) {
|
|
75
91
|
const files = walkMatching(projectRoot, registrationGlobs(idioms), new Set(options.excludeDirs ?? [])).sort();
|
|
@@ -96,7 +112,7 @@ export function registrationGraphSignature(projectRoot, idioms, options = {}) {
|
|
|
96
112
|
export function providedTokensFromEntries(idioms, entries) {
|
|
97
113
|
const tokens = new Set();
|
|
98
114
|
for (const idiom of idioms) {
|
|
99
|
-
const files = entries.filter((f) =>
|
|
115
|
+
const files = entries.filter((f) => globListSelects(f.path, resolveSourceGlobs(idiom.provided)));
|
|
100
116
|
for (const s of collectSourceSites(idiom.provided, files).sites)
|
|
101
117
|
tokens.add(s.token);
|
|
102
118
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IRegistrationIdiom, type IVerdictCoverage } from '@shrkcrft/core';
|
|
2
|
+
import type { IIdiomRoleCoverage } from './i-idiom-role-coverage.js';
|
|
3
|
+
import type { IRegistrationQueryVerdict } from './i-registration-query-verdict.js';
|
|
4
|
+
import { type IOrphanRegistration, type IRegistrationGraph, type IUnprovidedToken } from './registration-graph.js';
|
|
5
|
+
/**
|
|
6
|
+
* THE registration graph's read-scope record: the idiom files the reader could
|
|
7
|
+
* not read, named (`examined 3 of 4 files, 1 over the 1MB read cap: …`), or
|
|
8
|
+
* `undefined` when it read every file its idioms matched. `wiring unprovided |
|
|
9
|
+
* orphans`, finish's unprovided sub-gate and MCP `get_wiring_graph` all read
|
|
10
|
+
* it here, so the verb and its siblings cannot disagree about what was read.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registrationGraphCoverage(graph: IRegistrationGraph): IVerdictCoverage | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* `wiring unprovided` settled against what the graph read AND what every
|
|
15
|
+
* idiom's roles examined (`roles`: `measureIdiomRoleCoverage`, THE role
|
|
16
|
+
* authority — required, so no surface can forget the fold). `extra` adds
|
|
17
|
+
* candidates found another way (finish's base-ref provider regressions); they
|
|
18
|
+
* are merged by token and sorted, as finish always did.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registrationUnprovidedVerdict(graph: IRegistrationGraph, idioms: readonly IRegistrationIdiom[], roles: readonly IIdiomRoleCoverage[], changedFiles?: readonly string[], extra?: readonly IUnprovidedToken[]): IRegistrationQueryVerdict<IUnprovidedToken>;
|
|
21
|
+
/** `wiring orphans` settled against what the graph read and what every idiom's roles examined. */
|
|
22
|
+
export declare function registrationOrphansVerdict(graph: IRegistrationGraph, idioms: readonly IRegistrationIdiom[], roles: readonly IIdiomRoleCoverage[], changedFiles?: readonly string[]): IRegistrationQueryVerdict<IOrphanRegistration>;
|
|
23
|
+
//# sourceMappingURL=registration-query-verdict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration-query-verdict.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-query-verdict.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AAKjC;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,kBAAkB,GAAG,gBAAgB,GAAG,SAAS,CASjG;AAwFD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,EAChC,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,GAClC,yBAAyB,CAAC,gBAAgB,CAAC,CAQ7C;AAED,kGAAkG;AAClG,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,GAC/B,yBAAyB,CAAC,mBAAmB,CAAC,CAUhD"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { coverageShortfall, resolveSourceGlobs, ruleVerdictRecords, } from '@shrkcrft/core';
|
|
2
|
+
import { describeUnread, readScopeCoverage, unreadMatching } from "../util/read-scope-coverage.js";
|
|
3
|
+
import { registrationOrphans, registrationUnprovided, } from "./registration-graph.js";
|
|
4
|
+
/** At most this many demoted tokens ride on one coverage record. */
|
|
5
|
+
const TOKEN_LABEL_CAP = 20;
|
|
6
|
+
/**
|
|
7
|
+
* THE registration graph's read-scope record: the idiom files the reader could
|
|
8
|
+
* not read, named (`examined 3 of 4 files, 1 over the 1MB read cap: …`), or
|
|
9
|
+
* `undefined` when it read every file its idioms matched. `wiring unprovided |
|
|
10
|
+
* orphans`, finish's unprovided sub-gate and MCP `get_wiring_graph` all read
|
|
11
|
+
* it here, so the verb and its siblings cannot disagree about what was read.
|
|
12
|
+
*/
|
|
13
|
+
export function registrationGraphCoverage(graph) {
|
|
14
|
+
if (!graph.readScope || graph.readScope.unread.length === 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
...readScopeCoverage({ unit: 'registration idioms', expected: graph.idioms.length, examined: graph.idioms.length }, graph.readScope),
|
|
18
|
+
subject: 'registration graph',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Every idiom's role records, as an absence query settles them (round 13, P4):
|
|
23
|
+
* the role authority's coverage (`examined 2 of 3 roles … declared (0 files)`)
|
|
24
|
+
* plus its expectEmpty acceptance, folded by core's `ruleVerdictRecords` and
|
|
25
|
+
* subject-prefixed with the idiom. A role record that is only the idiom's read
|
|
26
|
+
* gap is dropped while the graph's own read-scope record is carried — that
|
|
27
|
+
* record already names every unread idiom file (a union over idioms); its
|
|
28
|
+
* acceptance is kept, so a record B is never dropped silently.
|
|
29
|
+
*/
|
|
30
|
+
function roleRecords(graph, roles) {
|
|
31
|
+
const graphNamesUnread = registrationGraphCoverage(graph) !== undefined;
|
|
32
|
+
return roles.flatMap((r) => {
|
|
33
|
+
const records = graphNamesUnread && r.readGap
|
|
34
|
+
? r.unitAcceptance
|
|
35
|
+
? [r.unitAcceptance]
|
|
36
|
+
: []
|
|
37
|
+
: ruleVerdictRecords(r.coverage, r.unitAcceptance);
|
|
38
|
+
return records.map((c) => ({ ...c, subject: c.subject ?? r.idiom }));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* THE rule for an absence claim over an incomplete read. The graph merges
|
|
43
|
+
* tokens across idioms, so a site of the missing role in ANY idiom satisfies a
|
|
44
|
+
* token. When an unread file matches any idiom's `missingRole` globs, every
|
|
45
|
+
* candidate could be refuted there: all are demoted to `unproven`, named in a
|
|
46
|
+
* second coverage record. Otherwise the candidates are findings.
|
|
47
|
+
*
|
|
48
|
+
* Round 13 (P4): every idiom's ROLE coverage is carried too, always — the
|
|
49
|
+
* graph's read scope said nothing about a role whose globs matched no file, so
|
|
50
|
+
* "every declared token has a provider" printed a ✓ over a declared role that
|
|
51
|
+
* examined nothing. A dead role never demotes a candidate (a finding stays a
|
|
52
|
+
* finding, as on `gates check`); it keeps a clean answer from being a pass.
|
|
53
|
+
*/
|
|
54
|
+
function absenceVerdict(graph, idioms, roles, missingRole, candidates, unit, subject) {
|
|
55
|
+
const unread = graph.readScope?.unread ?? [];
|
|
56
|
+
// Per idiom's role LIST, never a flattened union: a negation subtracts from
|
|
57
|
+
// its own list only, so one idiom's `!x` cannot hide a refuting file from
|
|
58
|
+
// another idiom that reads it. Deduped by path, sorted.
|
|
59
|
+
const refutingByPath = new Map();
|
|
60
|
+
for (const i of unread.length > 0 ? idioms : []) {
|
|
61
|
+
for (const u of unreadMatching(unread, resolveSourceGlobs(i[missingRole]))) {
|
|
62
|
+
if (!refutingByPath.has(u.path))
|
|
63
|
+
refutingByPath.set(u.path, u);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const refuting = [...refutingByPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
67
|
+
const unproven = refuting.length > 0 ? candidates : [];
|
|
68
|
+
const fileRecord = registrationGraphCoverage(graph);
|
|
69
|
+
const coverage = [
|
|
70
|
+
...(fileRecord ? [fileRecord] : []),
|
|
71
|
+
...roleRecords(graph, roles),
|
|
72
|
+
...(unproven.length > 0
|
|
73
|
+
? [
|
|
74
|
+
{
|
|
75
|
+
unit,
|
|
76
|
+
expected: unproven.length,
|
|
77
|
+
examined: 0,
|
|
78
|
+
unexamined: unproven.slice(0, TOKEN_LABEL_CAP).map((t) => t.token),
|
|
79
|
+
unexaminedTotal: unproven.length,
|
|
80
|
+
reason: `may be ${missingRole} in a file the reader did not read — ${describeUnread(refuting)}`,
|
|
81
|
+
subject,
|
|
82
|
+
},
|
|
83
|
+
]
|
|
84
|
+
: []),
|
|
85
|
+
];
|
|
86
|
+
return {
|
|
87
|
+
findings: refuting.length > 0 ? [] : candidates,
|
|
88
|
+
unproven,
|
|
89
|
+
coverage,
|
|
90
|
+
shortfalls: coverage.flatMap((c) => {
|
|
91
|
+
const s = coverageShortfall(c);
|
|
92
|
+
return s === undefined ? [] : [s];
|
|
93
|
+
}),
|
|
94
|
+
unread,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `wiring unprovided` settled against what the graph read AND what every
|
|
99
|
+
* idiom's roles examined (`roles`: `measureIdiomRoleCoverage`, THE role
|
|
100
|
+
* authority — required, so no surface can forget the fold). `extra` adds
|
|
101
|
+
* candidates found another way (finish's base-ref provider regressions); they
|
|
102
|
+
* are merged by token and sorted, as finish always did.
|
|
103
|
+
*/
|
|
104
|
+
export function registrationUnprovidedVerdict(graph, idioms, roles, changedFiles, extra) {
|
|
105
|
+
let candidates = registrationUnprovided(graph, changedFiles);
|
|
106
|
+
if (extra !== undefined) {
|
|
107
|
+
const byToken = new Map();
|
|
108
|
+
for (const u of [...candidates, ...extra])
|
|
109
|
+
if (!byToken.has(u.token))
|
|
110
|
+
byToken.set(u.token, u);
|
|
111
|
+
candidates = [...byToken.values()].sort((a, b) => a.token.localeCompare(b.token));
|
|
112
|
+
}
|
|
113
|
+
return absenceVerdict(graph, idioms, roles, 'provided', candidates, 'unprovided tokens', 'unprovided');
|
|
114
|
+
}
|
|
115
|
+
/** `wiring orphans` settled against what the graph read and what every idiom's roles examined. */
|
|
116
|
+
export function registrationOrphansVerdict(graph, idioms, roles, changedFiles) {
|
|
117
|
+
return absenceVerdict(graph, idioms, roles, 'consumed', registrationOrphans(graph, changedFiles), 'orphan registrations', 'orphans');
|
|
118
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IRegistryDeclaration } from '@shrkcrft/core';
|
|
2
|
+
import type { IReadScope } from '../util/read-scope.js';
|
|
2
3
|
export declare const REGISTRY_SCHEMA: "sharkcraft.registry-inventory/v1";
|
|
3
4
|
/** Where an id was found (project-relative path + 1-based line). */
|
|
4
5
|
export interface IRegistrySite {
|
|
@@ -22,6 +23,13 @@ export interface IRegistryInventory {
|
|
|
22
23
|
readonly entries: readonly IRegistryEntry[];
|
|
23
24
|
/** Misconfiguration messages (bad regex / no capture group / bad source). */
|
|
24
25
|
readonly diagnostics: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* The SOURCE side's read scope: declaration files read, and matched ones the
|
|
28
|
+
* reader did not read (over the read cap, or unreadable). With any unread
|
|
29
|
+
* the inventory is incomplete: an absent id, "no duplicates" or an empty
|
|
30
|
+
* inventory proves nothing.
|
|
31
|
+
*/
|
|
32
|
+
readonly readScope: IReadScope;
|
|
25
33
|
}
|
|
26
34
|
export interface IScanRegistryOptions {
|
|
27
35
|
/** Project-relative directories to prune from the walk. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,eAAO,MAAM,eAAe,EAAG,kCAA2C,CAAC;AAE3E,oEAAoE;AACpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;CACnD;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,eAAe,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,oBAAoB,EAC1B,OAAO,GAAE,oBAAyB,GACjC,kBAAkB,CAwDpB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEjF;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,SAAS,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEnG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,GAAG,SAAS,cAAc,EAAE,CAE3F"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveSourceGlobs } from '@shrkcrft/core';
|
|
2
|
+
import { globListSelects } from "../scan/glob.js";
|
|
2
3
|
import { readMatchingFiles } from "../util/walk-files.js";
|
|
4
|
+
import { unreadMatching } from "../util/read-scope-coverage.js";
|
|
3
5
|
import { collectSourceSites } from "./evaluate-wiring.js";
|
|
4
6
|
export const REGISTRY_SCHEMA = 'sharkcraft.registry-inventory/v1';
|
|
5
7
|
function sortSites(sites) {
|
|
@@ -12,18 +14,23 @@ function sortSites(sites) {
|
|
|
12
14
|
*/
|
|
13
15
|
export function scanRegistry(projectRoot, decl, options = {}) {
|
|
14
16
|
const globs = [
|
|
15
|
-
...new Set([
|
|
17
|
+
...new Set([
|
|
18
|
+
...resolveSourceGlobs(decl.source),
|
|
19
|
+
...(decl.consumer ? resolveSourceGlobs(decl.consumer) : []),
|
|
20
|
+
]),
|
|
16
21
|
];
|
|
17
|
-
const
|
|
18
|
-
const entries = [...
|
|
22
|
+
const matched = readMatchingFiles(projectRoot, globs, new Set(options.excludeDirs ?? []));
|
|
23
|
+
const entries = [...matched.files.entries()].map(([path, content]) => ({ path, content }));
|
|
19
24
|
const diagnostics = [];
|
|
20
|
-
|
|
25
|
+
// The walk above is the POSITIVE union of both sides; each side selects its
|
|
26
|
+
// own files, so the source's `!` never hides a consumer file (or vice versa).
|
|
27
|
+
const sourceFiles = entries.filter((f) => globListSelects(f.path, resolveSourceGlobs(decl.source)));
|
|
21
28
|
const declared = collectSourceSites(decl.source, sourceFiles);
|
|
22
29
|
if (declared.error)
|
|
23
30
|
diagnostics.push(`registry "${decl.name}" source: ${declared.error}`);
|
|
24
31
|
const consumerByToken = new Map();
|
|
25
32
|
if (decl.consumer) {
|
|
26
|
-
const consumerFiles = entries.filter((f) =>
|
|
33
|
+
const consumerFiles = entries.filter((f) => globListSelects(f.path, resolveSourceGlobs(decl.consumer)));
|
|
27
34
|
const consumed = collectSourceSites(decl.consumer, consumerFiles);
|
|
28
35
|
if (consumed.error)
|
|
29
36
|
diagnostics.push(`registry "${decl.name}" consumer: ${consumed.error}`);
|
|
@@ -53,6 +60,10 @@ export function scanRegistry(projectRoot, decl, options = {}) {
|
|
|
53
60
|
...(decl.description ? { description: decl.description } : {}),
|
|
54
61
|
entries: entriesOut,
|
|
55
62
|
diagnostics,
|
|
63
|
+
readScope: {
|
|
64
|
+
read: sourceFiles.length,
|
|
65
|
+
unread: unreadMatching(matched.unread, resolveSourceGlobs(decl.source)),
|
|
66
|
+
},
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
69
|
/** True if `id` is declared in the registry (alias-blind exact membership). */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IWiringRule } from '@shrkcrft/core';
|
|
2
2
|
import { type IWiringReport } from './evaluate-wiring.js';
|
|
3
3
|
export interface IRunWiringOptions {
|
|
4
4
|
/** Only run rules touched by these (project-relative) changed files. */
|
|
@@ -12,8 +12,10 @@ export interface IRunWiringOptions {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Filesystem-backed wiring run: walks the project once, reads only the files a
|
|
15
|
-
* rule references
|
|
16
|
-
*
|
|
15
|
+
* rule references, and evaluates every (selected) rule. A matched file the
|
|
16
|
+
* reader could not read (over the read cap) is handed to the engine as
|
|
17
|
+
* unread, so the rule's coverage names it instead of passing over it.
|
|
18
|
+
* Pure-engine output; the only IO is the read-only tree walk + reads.
|
|
17
19
|
*/
|
|
18
20
|
export declare function runWiring(projectRoot: string, rules: readonly IWiringRule[], options?: IRunWiringOptions): IWiringReport;
|
|
19
21
|
//# sourceMappingURL=scan-wiring-files.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAQ3F,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAQ9B,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,iGAAiG;IACjG,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,GAAE,iBAAsB,GAC9B,aAAa,CAsEf"}
|