@shrkcrft/boundaries 0.1.0-alpha.27 → 0.1.0-alpha.29
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/canonicalize.d.ts +35 -0
- package/dist/baseline/canonicalize.d.ts.map +1 -0
- package/dist/baseline/canonicalize.js +87 -0
- package/dist/baseline/compute-baseline.d.ts +23 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -0
- package/dist/baseline/compute-baseline.js +30 -0
- package/dist/baseline/diff-baseline.d.ts +68 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -0
- package/dist/baseline/diff-baseline.js +110 -0
- package/dist/baseline/json-path-keys.d.ts +10 -0
- package/dist/baseline/json-path-keys.d.ts.map +1 -0
- package/dist/baseline/json-path-keys.js +13 -0
- package/dist/extract/code-zones.d.ts +32 -0
- package/dist/extract/code-zones.d.ts.map +1 -0
- package/dist/extract/code-zones.js +59 -0
- package/dist/extract/extract-tokens.d.ts +30 -0
- package/dist/extract/extract-tokens.d.ts.map +1 -0
- package/dist/extract/extract-tokens.js +328 -0
- package/dist/extract/inspect-source.d.ts +24 -0
- package/dist/extract/inspect-source.d.ts.map +1 -0
- package/dist/extract/inspect-source.js +27 -0
- package/dist/extract/scan-literals.d.ts +54 -0
- package/dist/extract/scan-literals.d.ts.map +1 -0
- package/dist/extract/scan-literals.js +177 -0
- package/dist/generated/check-provenance.d.ts +37 -0
- package/dist/generated/check-provenance.d.ts.map +1 -0
- package/dist/generated/check-provenance.js +89 -0
- package/dist/generated/compare-trees.d.ts +26 -0
- package/dist/generated/compare-trees.d.ts.map +1 -0
- package/dist/generated/compare-trees.js +41 -0
- package/dist/generated/scan-generated.d.ts +17 -0
- package/dist/generated/scan-generated.d.ts.map +1 -0
- package/dist/generated/scan-generated.js +27 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/policy/evaluate-policy.d.ts +42 -6
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +113 -10
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +24 -3
- package/dist/wiring/evaluate-wiring.d.ts +81 -28
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +0 -0
- package/dist/wiring/explain-wiring.d.ts +15 -7
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +28 -27
- package/dist/wiring/plan-wiring-fix.d.ts +60 -0
- package/dist/wiring/plan-wiring-fix.d.ts.map +1 -0
- package/dist/wiring/plan-wiring-fix.js +129 -0
- package/dist/wiring/registry-query.d.ts +9 -0
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +11 -0
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +4 -7
- package/package.json +2 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import { type IExtractFileEntry, type IExtractedSite } from '../extract/extract-tokens.js';
|
|
2
3
|
export declare const WIRING_SCHEMA: "sharkcraft.wiring/v1";
|
|
3
4
|
/** A file made available to the engine. */
|
|
4
|
-
export
|
|
5
|
-
/** Project-relative POSIX path. */
|
|
6
|
-
readonly path: string;
|
|
7
|
-
readonly content: string;
|
|
8
|
-
}
|
|
5
|
+
export type IWiringFileEntry = IExtractFileEntry;
|
|
9
6
|
/** A captured token + where it was captured. */
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export type IWiringTokenSite = IExtractedSite;
|
|
8
|
+
/**
|
|
9
|
+
* Per-rule outcome. `skipped` is deliberately DISTINCT from `passed`: a rule
|
|
10
|
+
* whose source side extracted nothing checked nothing, and reporting that as a
|
|
11
|
+
* pass is the single failure mode that lets a stale selector ship a real
|
|
12
|
+
* violation. See {@link IWiringSkip}.
|
|
13
|
+
*/
|
|
14
|
+
export type WiringRuleStatus = 'passed' | 'failed' | 'skipped' | 'error';
|
|
15
15
|
export interface IWiringViolation {
|
|
16
16
|
readonly ruleId: string;
|
|
17
|
-
/** The token that is missing from the other side. */
|
|
17
|
+
/** The token that is missing from (or wrongly shared with) the other side. */
|
|
18
18
|
readonly token: string;
|
|
19
19
|
/** Declaring (or registering) file (project-relative) + 1-based line. */
|
|
20
20
|
readonly file: string;
|
|
@@ -23,20 +23,53 @@ export interface IWiringViolation {
|
|
|
23
23
|
/**
|
|
24
24
|
* Which side the token is missing from. `declared-missing` (the subset case):
|
|
25
25
|
* declared but not registered. `registered-missing` (parity only): registered
|
|
26
|
-
* but never declared.
|
|
26
|
+
* but never declared. `overlap` (disjoint only): present on both sides.
|
|
27
27
|
*/
|
|
28
|
-
readonly direction?: 'declared-missing' | 'registered-missing';
|
|
28
|
+
readonly direction?: 'declared-missing' | 'registered-missing' | 'overlap';
|
|
29
|
+
/** For a `chain` rule: which hop pair failed (0-based). */
|
|
30
|
+
readonly hop?: number;
|
|
31
|
+
/** Rendered `message` template, when the rule sets one. */
|
|
32
|
+
readonly message?: string;
|
|
29
33
|
readonly hint?: string;
|
|
30
34
|
}
|
|
35
|
+
/** A rule that could not be evaluated because its source side matched nothing. */
|
|
36
|
+
export interface IWiringSkip {
|
|
37
|
+
readonly ruleId: string;
|
|
38
|
+
/** Human-readable cause (`0 files matched` / `0 ids extracted`). */
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
/** True when the rule set `failOnEmpty` — the skip counts as a failure. */
|
|
41
|
+
readonly failed: boolean;
|
|
42
|
+
readonly severity: 'error' | 'warning';
|
|
43
|
+
}
|
|
44
|
+
/** One `hop_i ⊆ hop_i+1` step of a multi-hop chain rule. */
|
|
45
|
+
export interface IWiringHopResult {
|
|
46
|
+
readonly index: number;
|
|
47
|
+
readonly fromCount: number;
|
|
48
|
+
readonly toCount: number;
|
|
49
|
+
readonly missing: number;
|
|
50
|
+
}
|
|
31
51
|
export interface IWiringRuleResult {
|
|
32
52
|
readonly ruleId: string;
|
|
33
53
|
readonly description?: string;
|
|
34
54
|
readonly severity: 'error' | 'warning';
|
|
55
|
+
readonly status: WiringRuleStatus;
|
|
35
56
|
readonly declaredCount: number;
|
|
36
57
|
readonly registeredCount: number;
|
|
58
|
+
/** Files actually scanned per side (after glob resolution) — the stale-glob signal. */
|
|
59
|
+
readonly declaredFiles: number;
|
|
60
|
+
readonly registeredFiles: number;
|
|
37
61
|
readonly violations: readonly IWiringViolation[];
|
|
38
62
|
/** Set when the rule is misconfigured (bad regex / no capture group / bad source). */
|
|
39
63
|
readonly error?: string;
|
|
64
|
+
/**
|
|
65
|
+
* True when the SINK extracted 0 ids while the source extracted some. Every
|
|
66
|
+
* declared token then "fails", which is a real failure — but almost always
|
|
67
|
+
* because the sink glob went stale, so the diff is annotated rather than
|
|
68
|
+
* silently reported as N unrelated violations.
|
|
69
|
+
*/
|
|
70
|
+
readonly emptySink?: boolean;
|
|
71
|
+
/** Per-hop breakdown for a `chain` rule. */
|
|
72
|
+
readonly hops?: readonly IWiringHopResult[];
|
|
40
73
|
}
|
|
41
74
|
export interface IWiringReport {
|
|
42
75
|
readonly schema: typeof WIRING_SCHEMA;
|
|
@@ -44,34 +77,54 @@ export interface IWiringReport {
|
|
|
44
77
|
readonly violations: readonly IWiringViolation[];
|
|
45
78
|
/** Rule-level misconfiguration messages (never throws — degrades gracefully). */
|
|
46
79
|
readonly diagnostics: readonly string[];
|
|
80
|
+
/** Rules that checked nothing, reported loudly instead of as a green pass. */
|
|
81
|
+
readonly skipped: readonly IWiringSkip[];
|
|
47
82
|
/**
|
|
48
|
-
* Count of rules that actually ran a comparison. A rule whose
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* error is not swallowed.
|
|
83
|
+
* Count of rules that actually ran a comparison. A rule whose source side
|
|
84
|
+
* matched 0 files or extracted 0 ids is NOT evaluated (see {@link skipped}).
|
|
85
|
+
* Misconfigured rules count as evaluated so their error is not swallowed.
|
|
52
86
|
*/
|
|
53
87
|
readonly evaluated: number;
|
|
54
88
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
55
89
|
}
|
|
56
90
|
/** Resolves a rule-side's globs to the concrete files (path + content) to scan. */
|
|
57
91
|
export type WiringFileResolver = (source: IWiringSource) => readonly IWiringFileEntry[];
|
|
92
|
+
/** Normalize the `registered` field (single source or union array) to an array. */
|
|
93
|
+
export declare function registeredSources(reg: IWiringRule['registered']): readonly IWiringSource[];
|
|
94
|
+
/**
|
|
95
|
+
* The rule's SOURCE side — the set whose membership is being asserted. For a
|
|
96
|
+
* chain rule that is hop 0. Used for glob collection, the loud-skip test, and
|
|
97
|
+
* `selfTest` expectations.
|
|
98
|
+
*/
|
|
99
|
+
export declare function wiringSourceSide(rule: IWiringRule): IWiringSource | undefined;
|
|
100
|
+
/** Every source a rule references, in declaration order. */
|
|
101
|
+
export declare function wiringSourcesOf(rule: IWiringRule): readonly IWiringSource[];
|
|
102
|
+
/** Every glob a rule references (both sides / every hop). */
|
|
103
|
+
export declare function wiringGlobsOf(rule: IWiringRule): string[];
|
|
58
104
|
/**
|
|
59
|
-
* Public: extract every token site from ONE wiring source
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* (never throws).
|
|
105
|
+
* Public: extract every token site from ONE wiring source over the given files.
|
|
106
|
+
* Shared by the wiring evaluator, the registry-inventory engine, and the
|
|
107
|
+
* extractor-backed baseline compute, so all three honour identical semantics. A
|
|
108
|
+
* misconfigured source returns an `error` and no sites (never throws).
|
|
64
109
|
*/
|
|
65
110
|
export declare function collectSourceSites(source: IWiringSource, files: readonly IWiringFileEntry[]): {
|
|
66
111
|
sites: readonly IWiringTokenSite[];
|
|
67
112
|
error?: string;
|
|
68
113
|
};
|
|
114
|
+
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
115
|
+
export declare function validateWiringRule(rule: IWiringRule): string | undefined;
|
|
69
116
|
/**
|
|
70
|
-
* Pure wiring evaluation.
|
|
71
|
-
*
|
|
72
|
-
* the
|
|
73
|
-
*
|
|
74
|
-
*
|
|
117
|
+
* Pure wiring evaluation.
|
|
118
|
+
*
|
|
119
|
+
* For each rule the SOURCE token set is compared against the SINK sets per
|
|
120
|
+
* `mode` (`subset` / `parity` / `disjoint`) and `registeredMode` (`union` /
|
|
121
|
+
* `intersection`). A `chain` rule runs the same comparison across each adjacent
|
|
122
|
+
* hop pair. With `groupBy`, membership is checked within the same dir/package.
|
|
123
|
+
*
|
|
124
|
+
* Three outcomes are kept distinct, because collapsing them is what makes a
|
|
125
|
+
* homegrown gate untrustworthy: a rule that PASSED, a rule that FAILED, and a
|
|
126
|
+
* rule that checked NOTHING (`skipped`, promoted to a failure by `failOnEmpty`).
|
|
127
|
+
*
|
|
75
128
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
76
129
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
77
130
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IWiringRule } from '@shrkcrft/core';
|
|
2
|
-
import { type IWiringTokenSite } from './evaluate-wiring.js';
|
|
2
|
+
import { type IWiringHopResult, type IWiringTokenSite } from './evaluate-wiring.js';
|
|
3
3
|
export declare const WIRING_EXPLAIN_SCHEMA: "sharkcraft.wiring-explain/v1";
|
|
4
4
|
/** One side (declared or registered) of a wiring rule, as extracted from the tree. */
|
|
5
5
|
export interface IWiringSideExplain {
|
|
@@ -22,7 +22,8 @@ export interface IWiringExplain {
|
|
|
22
22
|
readonly schema: typeof WIRING_EXPLAIN_SCHEMA;
|
|
23
23
|
readonly ruleId: string;
|
|
24
24
|
readonly description?: string;
|
|
25
|
-
readonly mode: 'subset' | 'parity';
|
|
25
|
+
readonly mode: 'subset' | 'parity' | 'disjoint';
|
|
26
|
+
readonly registeredMode: 'union' | 'intersection';
|
|
26
27
|
readonly groupBy?: 'dir' | 'package';
|
|
27
28
|
readonly severity: 'error' | 'warning';
|
|
28
29
|
readonly declared: IWiringSideExplain;
|
|
@@ -31,6 +32,14 @@ export interface IWiringExplain {
|
|
|
31
32
|
readonly declaredNotRegistered: readonly IWiringTokenSite[];
|
|
32
33
|
/** Registered tokens absent from the declared set (parity-only `registered-missing`). */
|
|
33
34
|
readonly registeredNotDeclared: readonly IWiringTokenSite[];
|
|
35
|
+
/** Tokens present on BOTH sides (disjoint-only `overlap`). */
|
|
36
|
+
readonly overlap: readonly IWiringTokenSite[];
|
|
37
|
+
/** Per-hop breakdown when the rule is a multi-hop `chain`. */
|
|
38
|
+
readonly hops?: readonly IWiringHopResult[];
|
|
39
|
+
/** `passed` / `failed` / `skipped` / `error` — skipped is never a pass. */
|
|
40
|
+
readonly status: 'passed' | 'failed' | 'skipped' | 'error';
|
|
41
|
+
/** Why the rule checked nothing, when it was skipped. */
|
|
42
|
+
readonly skipReason?: string;
|
|
34
43
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
35
44
|
/** Rule-level misconfiguration messages (engine degrades gracefully). */
|
|
36
45
|
readonly diagnostics: readonly string[];
|
|
@@ -43,11 +52,10 @@ export interface IExplainWiringOptions {
|
|
|
43
52
|
* Dry-run a single wiring rule against the live tree and return what each side
|
|
44
53
|
* extracted (declared set, registered set, the set-difference, the verdict) —
|
|
45
54
|
* WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
|
|
46
|
-
* <candidate>`, and `check wiring --explain <ruleId>`: the
|
|
47
|
-
*
|
|
48
|
-
* a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
49
|
-
* exactly (incl. `groupBy` membership)
|
|
50
|
-
* with the shared {@link collectSourceSites}. Never throws.
|
|
55
|
+
* <candidate>`, `gates explain <id>`, and `check wiring --explain <ruleId>`: the
|
|
56
|
+
* author can SEE the cross-file set-difference the gate computes before
|
|
57
|
+
* committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
58
|
+
* the gate exactly (incl. `groupBy` membership). Never throws.
|
|
51
59
|
*/
|
|
52
60
|
export declare function explainWiring(projectRoot: string, rule: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
|
|
53
61
|
//# sourceMappingURL=explain-wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,
|
|
1
|
+
{"version":3,"file":"explain-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/explain-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,qBAAqB,EAAG,8BAAuC,CAAC;AAE7E,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,qBAAqB,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,cAAc,EAAE,OAAO,GAAG,cAAc,CAAC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,oFAAoF;IACpF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,yFAAyF;IACzF,QAAQ,CAAC,qBAAqB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAC3D,yDAAyD;IACzD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;IACjD,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAQD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAyEhB"}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { matchesAny } from "../scan/glob.js";
|
|
2
2
|
import { readMatchingFiles } from "../util/walk-files.js";
|
|
3
|
-
import { collectSourceSites, evaluateWiring, } from "./evaluate-wiring.js";
|
|
3
|
+
import { collectSourceSites, evaluateWiring, registeredSources, wiringGlobsOf, wiringSourceSide, } from "./evaluate-wiring.js";
|
|
4
4
|
export const WIRING_EXPLAIN_SCHEMA = 'sharkcraft.wiring-explain/v1';
|
|
5
|
-
function registeredSources(reg) {
|
|
6
|
-
return Array.isArray(reg) ? reg : [reg];
|
|
7
|
-
}
|
|
8
5
|
function sortSites(sites) {
|
|
9
6
|
return [...sites].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.token.localeCompare(b.token));
|
|
10
7
|
}
|
|
@@ -12,31 +9,32 @@ function sortSites(sites) {
|
|
|
12
9
|
* Dry-run a single wiring rule against the live tree and return what each side
|
|
13
10
|
* extracted (declared set, registered set, the set-difference, the verdict) —
|
|
14
11
|
* WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
|
|
15
|
-
* <candidate>`, and `check wiring --explain <ruleId>`: the
|
|
16
|
-
*
|
|
17
|
-
* a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
18
|
-
* exactly (incl. `groupBy` membership)
|
|
19
|
-
* with the shared {@link collectSourceSites}. Never throws.
|
|
12
|
+
* <candidate>`, `gates explain <id>`, and `check wiring --explain <ruleId>`: the
|
|
13
|
+
* author can SEE the cross-file set-difference the gate computes before
|
|
14
|
+
* committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
15
|
+
* the gate exactly (incl. `groupBy` membership). Never throws.
|
|
20
16
|
*/
|
|
21
17
|
export function explainWiring(projectRoot, rule, options = {}) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const sourceSide = wiringSourceSide(rule);
|
|
19
|
+
// For a chain, the reported "registered" side is the LAST hop (the far end).
|
|
20
|
+
const sinkSources = rule.chain && rule.chain.length >= 2
|
|
21
|
+
? [rule.chain[rule.chain.length - 1]]
|
|
22
|
+
: registeredSources(rule.registered);
|
|
23
|
+
const allGlobs = [...new Set(wiringGlobsOf(rule))];
|
|
26
24
|
const cache = readMatchingFiles(projectRoot, allGlobs, new Set(options.excludeDirs ?? []));
|
|
27
25
|
const entries = [...cache.entries()].map(([path, content]) => ({
|
|
28
26
|
path,
|
|
29
27
|
content,
|
|
30
28
|
}));
|
|
31
|
-
const filesFor = (source) => entries.filter((f) => matchesAny(f.path, source.files));
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
const filesFor = (source) => entries.filter((f) => matchesAny(f.path, source.files ?? []));
|
|
30
|
+
const declaredFiles = sourceSide ? filesFor(sourceSide) : [];
|
|
31
|
+
const declaredRes = sourceSide
|
|
32
|
+
? collectSourceSites(sourceSide, declaredFiles)
|
|
33
|
+
: { sites: [], error: 'rule sets no source side' };
|
|
36
34
|
const registeredFiles = new Set();
|
|
37
35
|
const registeredSites = [];
|
|
38
36
|
let registeredError;
|
|
39
|
-
for (const source of
|
|
37
|
+
for (const source of sinkSources) {
|
|
40
38
|
const files = filesFor(source);
|
|
41
39
|
for (const f of files)
|
|
42
40
|
registeredFiles.add(f.path);
|
|
@@ -48,17 +46,16 @@ export function explainWiring(projectRoot, rule, options = {}) {
|
|
|
48
46
|
// Canonical diff + counts + verdict from the gate engine (same groupBy logic).
|
|
49
47
|
const report = evaluateWiring([rule], filesFor);
|
|
50
48
|
const ruleResult = report.rules[0];
|
|
51
|
-
const
|
|
52
|
-
.filter((v) => v.direction ===
|
|
53
|
-
.map((v) => ({ token: v.token, file: v.file, line: v.line })));
|
|
54
|
-
const registeredNotDeclared = sortSites((ruleResult?.violations ?? [])
|
|
55
|
-
.filter((v) => v.direction === 'registered-missing')
|
|
49
|
+
const byDirection = (d) => sortSites((ruleResult?.violations ?? [])
|
|
50
|
+
.filter((v) => v.direction === d)
|
|
56
51
|
.map((v) => ({ token: v.token, file: v.file, line: v.line })));
|
|
52
|
+
const skip = report.skipped[0];
|
|
57
53
|
return {
|
|
58
54
|
schema: WIRING_EXPLAIN_SCHEMA,
|
|
59
55
|
ruleId: rule.id,
|
|
60
56
|
...(rule.description ? { description: rule.description } : {}),
|
|
61
|
-
mode: rule.mode
|
|
57
|
+
mode: rule.mode ?? 'subset',
|
|
58
|
+
registeredMode: rule.registeredMode ?? 'union',
|
|
62
59
|
...(rule.groupBy ? { groupBy: rule.groupBy } : {}),
|
|
63
60
|
severity: rule.severity ?? 'error',
|
|
64
61
|
declared: {
|
|
@@ -73,8 +70,12 @@ export function explainWiring(projectRoot, rule, options = {}) {
|
|
|
73
70
|
filesScanned: registeredFiles.size,
|
|
74
71
|
...(registeredError ? { error: registeredError } : {}),
|
|
75
72
|
},
|
|
76
|
-
declaredNotRegistered,
|
|
77
|
-
registeredNotDeclared,
|
|
73
|
+
declaredNotRegistered: byDirection('declared-missing'),
|
|
74
|
+
registeredNotDeclared: byDirection('registered-missing'),
|
|
75
|
+
overlap: byDirection('overlap'),
|
|
76
|
+
...(ruleResult?.hops ? { hops: ruleResult.hops } : {}),
|
|
77
|
+
status: ruleResult?.status ?? 'error',
|
|
78
|
+
...(skip ? { skipReason: skip.reason } : {}),
|
|
78
79
|
verdict: report.verdict,
|
|
79
80
|
diagnostics: report.diagnostics,
|
|
80
81
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IWiringRule } from '@shrkcrft/core';
|
|
2
|
+
import { type IWiringViolation } from './evaluate-wiring.js';
|
|
3
|
+
export declare const WIRING_FIX_SCHEMA: "sharkcraft.wiring-fix/v1";
|
|
4
|
+
/** Why a violation could not be fixed mechanically. */
|
|
5
|
+
export type WiringFixRefusal =
|
|
6
|
+
/** More than one registered sink — which array should it go in? */
|
|
7
|
+
'ambiguous-sink'
|
|
8
|
+
/** The sink is not an array literal the engine can append to. */
|
|
9
|
+
| 'sink-not-an-array'
|
|
10
|
+
/** The sink glob resolves to several files, or none. */
|
|
11
|
+
| 'ambiguous-sink-file'
|
|
12
|
+
/** The array literal could not be located in the sink file. */
|
|
13
|
+
| 'array-not-found'
|
|
14
|
+
/** The token is a quoted string in the sink; the declared side is a symbol (or vice versa). */
|
|
15
|
+
| 'token-shape-mismatch';
|
|
16
|
+
/** One planned edit: append `token` into the anchor array of `file`. */
|
|
17
|
+
export interface IWiringFixEdit {
|
|
18
|
+
readonly ruleId: string;
|
|
19
|
+
readonly token: string;
|
|
20
|
+
/** Project-relative sink file the edit lands in. */
|
|
21
|
+
readonly file: string;
|
|
22
|
+
/** 1-based line of the array's closing bracket, where the token is inserted before. */
|
|
23
|
+
readonly line: number;
|
|
24
|
+
/** The exact text inserted, including indentation and trailing comma. */
|
|
25
|
+
readonly insert: string;
|
|
26
|
+
/** The full new file contents, ready to write. */
|
|
27
|
+
readonly nextContent: string;
|
|
28
|
+
}
|
|
29
|
+
/** A violation the planner deliberately did not touch. */
|
|
30
|
+
export interface IWiringFixSkip {
|
|
31
|
+
readonly ruleId: string;
|
|
32
|
+
readonly token: string;
|
|
33
|
+
readonly reason: WiringFixRefusal;
|
|
34
|
+
readonly detail: string;
|
|
35
|
+
}
|
|
36
|
+
export interface IWiringFixPlan {
|
|
37
|
+
readonly schema: typeof WIRING_FIX_SCHEMA;
|
|
38
|
+
readonly edits: readonly IWiringFixEdit[];
|
|
39
|
+
readonly skipped: readonly IWiringFixSkip[];
|
|
40
|
+
}
|
|
41
|
+
/** A file the planner may read/rewrite. */
|
|
42
|
+
export interface IWiringFixFile {
|
|
43
|
+
readonly path: string;
|
|
44
|
+
readonly content: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Plan the mechanically-unambiguous fixes for `declared-but-not-registered`
|
|
48
|
+
* violations.
|
|
49
|
+
*
|
|
50
|
+
* The bar is deliberately high, because a wrong autofix in a gate is worse than
|
|
51
|
+
* no autofix: the tool would be writing the very thing it is supposed to
|
|
52
|
+
* verify. An edit is planned ONLY when there is exactly one registered sink,
|
|
53
|
+
* that sink is an `array-members` source, its glob resolves to exactly one
|
|
54
|
+
* file, and the anchor array is found exactly once in it. Every other shape is
|
|
55
|
+
* reported as skipped WITH the reason — never guessed at.
|
|
56
|
+
*
|
|
57
|
+
* Pure: the caller supplies file contents and performs any writes.
|
|
58
|
+
*/
|
|
59
|
+
export declare function planWiringFix(rule: IWiringRule, violations: readonly IWiringViolation[], files: readonly IWiringFixFile[]): IWiringFixPlan;
|
|
60
|
+
//# sourceMappingURL=plan-wiring-fix.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-wiring-fix.d.ts","sourceRoot":"","sources":["../../src/wiring/plan-wiring-fix.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,eAAO,MAAM,iBAAiB,EAAG,0BAAmC,CAAC;AAErE,uDAAuD;AACvD,MAAM,MAAM,gBAAgB;AAC1B,mEAAmE;AACjE,gBAAgB;AAClB,iEAAiE;GAC/D,mBAAmB;AACrB,wDAAwD;GACtD,qBAAqB;AACvB,+DAA+D;GAC7D,iBAAiB;AACnB,+FAA+F;GAC7F,sBAAsB,CAAC;AAE3B,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;CAC7C;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,SAAS,gBAAgB,EAAE,EACvC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,cAAc,CAqHhB"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { matchesAny } from "../scan/glob.js";
|
|
2
|
+
import { escapeRegex, scanBalanced } from "../extract/scan-literals.js";
|
|
3
|
+
import { registeredSources } from "./evaluate-wiring.js";
|
|
4
|
+
import { resolveExtractorAnchor, resolveExtractorKind } from '@shrkcrft/core';
|
|
5
|
+
export const WIRING_FIX_SCHEMA = 'sharkcraft.wiring-fix/v1';
|
|
6
|
+
/** Indentation of the line containing `index`. */
|
|
7
|
+
function indentOf(content, index) {
|
|
8
|
+
const lineStart = content.lastIndexOf('\n', index - 1) + 1;
|
|
9
|
+
const m = /^[ \t]*/.exec(content.slice(lineStart, index));
|
|
10
|
+
return m ? m[0] : ' ';
|
|
11
|
+
}
|
|
12
|
+
function lineOf(content, index) {
|
|
13
|
+
let line = 1;
|
|
14
|
+
for (let i = 0; i < index && i < content.length; i += 1) {
|
|
15
|
+
if (content[i] === '\n')
|
|
16
|
+
line += 1;
|
|
17
|
+
}
|
|
18
|
+
return line;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Plan the mechanically-unambiguous fixes for `declared-but-not-registered`
|
|
22
|
+
* violations.
|
|
23
|
+
*
|
|
24
|
+
* The bar is deliberately high, because a wrong autofix in a gate is worse than
|
|
25
|
+
* no autofix: the tool would be writing the very thing it is supposed to
|
|
26
|
+
* verify. An edit is planned ONLY when there is exactly one registered sink,
|
|
27
|
+
* that sink is an `array-members` source, its glob resolves to exactly one
|
|
28
|
+
* file, and the anchor array is found exactly once in it. Every other shape is
|
|
29
|
+
* reported as skipped WITH the reason — never guessed at.
|
|
30
|
+
*
|
|
31
|
+
* Pure: the caller supplies file contents and performs any writes.
|
|
32
|
+
*/
|
|
33
|
+
export function planWiringFix(rule, violations, files) {
|
|
34
|
+
const edits = [];
|
|
35
|
+
const skipped = [];
|
|
36
|
+
const declaredMissing = violations.filter((v) => v.ruleId === rule.id && v.direction === 'declared-missing');
|
|
37
|
+
if (declaredMissing.length === 0)
|
|
38
|
+
return { schema: WIRING_FIX_SCHEMA, edits, skipped };
|
|
39
|
+
const refuseAll = (reason, detail) => ({
|
|
40
|
+
schema: WIRING_FIX_SCHEMA,
|
|
41
|
+
edits: [],
|
|
42
|
+
skipped: declaredMissing.map((v) => ({ ruleId: rule.id, token: v.token, reason, detail })),
|
|
43
|
+
});
|
|
44
|
+
// A chain rule has no single "the sink"; refuse rather than pick one.
|
|
45
|
+
const sinks = rule.chain
|
|
46
|
+
? []
|
|
47
|
+
: registeredSources(rule.registered);
|
|
48
|
+
if (sinks.length !== 1) {
|
|
49
|
+
return refuseAll('ambiguous-sink', sinks.length === 0
|
|
50
|
+
? 'rule has no single registered sink (chain rules are never auto-fixed)'
|
|
51
|
+
: `rule has ${sinks.length} registered sinks — which one should the token join?`);
|
|
52
|
+
}
|
|
53
|
+
const sink = sinks[0];
|
|
54
|
+
if (resolveExtractorKind(sink) !== 'array-members') {
|
|
55
|
+
return refuseAll('sink-not-an-array', `sink uses extract "${resolveExtractorKind(sink) ?? 'unknown'}" — only an array literal can be appended to`);
|
|
56
|
+
}
|
|
57
|
+
const anchor = resolveExtractorAnchor(sink);
|
|
58
|
+
if (anchor === undefined)
|
|
59
|
+
return refuseAll('sink-not-an-array', 'sink declares no anchor');
|
|
60
|
+
const sinkFiles = files.filter((f) => matchesAny(f.path, sink.files ?? []));
|
|
61
|
+
if (sinkFiles.length !== 1) {
|
|
62
|
+
return refuseAll('ambiguous-sink-file', `sink glob matched ${sinkFiles.length} files — the insertion point must be unique`);
|
|
63
|
+
}
|
|
64
|
+
const target = sinkFiles[0];
|
|
65
|
+
// Locate the anchor array. More than one occurrence is ambiguous.
|
|
66
|
+
const head = new RegExp(`(?<![\\w$])${escapeRegex(anchor)}\\s*(?::[^=\\n]*)?[:=]\\s*(?:[A-Za-z_$][\\w$.]*\\s*\\(\\s*)?\\[`, 'g');
|
|
67
|
+
const opens = [];
|
|
68
|
+
let m;
|
|
69
|
+
while ((m = head.exec(target.content)) !== null)
|
|
70
|
+
opens.push(m.index + m[0].length - 1);
|
|
71
|
+
if (opens.length !== 1) {
|
|
72
|
+
return refuseAll('array-not-found', opens.length === 0
|
|
73
|
+
? `array \`${anchor}\` not found in ${target.path}`
|
|
74
|
+
: `array \`${anchor}\` appears ${opens.length} times in ${target.path}`);
|
|
75
|
+
}
|
|
76
|
+
const openIndex = opens[0];
|
|
77
|
+
const { elements, end } = scanBalanced(target.content, openIndex);
|
|
78
|
+
// Match the shape of what is already in the array: bare identifiers vs
|
|
79
|
+
// quoted strings. Inserting the wrong shape would compile-fail or silently
|
|
80
|
+
// not match on the next run.
|
|
81
|
+
const firstEl = elements[0]?.text ?? '';
|
|
82
|
+
const quoted = firstEl.startsWith("'") || firstEl.startsWith('"');
|
|
83
|
+
const quoteChar = firstEl.startsWith('"') ? '"' : "'";
|
|
84
|
+
// Build all edits against one evolving buffer so multiple tokens accumulate.
|
|
85
|
+
let content = target.content;
|
|
86
|
+
let insertAt = end;
|
|
87
|
+
for (const v of declaredMissing) {
|
|
88
|
+
// Re-locate the closing bracket after each insertion.
|
|
89
|
+
const reHead = new RegExp(head.source, 'g');
|
|
90
|
+
const mm = reHead.exec(content);
|
|
91
|
+
if (!mm) {
|
|
92
|
+
skipped.push({
|
|
93
|
+
ruleId: rule.id,
|
|
94
|
+
token: v.token,
|
|
95
|
+
reason: 'array-not-found',
|
|
96
|
+
detail: `array \`${anchor}\` vanished mid-plan in ${target.path}`,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const open = mm.index + mm[0].length - 1;
|
|
101
|
+
const scan = scanBalanced(content, open);
|
|
102
|
+
insertAt = scan.end;
|
|
103
|
+
const last = scan.elements[scan.elements.length - 1];
|
|
104
|
+
const indent = last ? indentOf(content, last.index) : ' ';
|
|
105
|
+
const multiline = content.slice(open, insertAt).includes('\n');
|
|
106
|
+
const rendered = quoted ? `${quoteChar}${v.token}${quoteChar}` : v.token;
|
|
107
|
+
// Insert after the last non-whitespace character, and KEEP the whitespace
|
|
108
|
+
// run that precedes the closing bracket — trimming it would drag `]` up
|
|
109
|
+
// onto the last element's line and reformat code the user did not ask us
|
|
110
|
+
// to touch.
|
|
111
|
+
const beforeInsert = content.slice(0, insertAt).replace(/\s+$/, '');
|
|
112
|
+
const cut = beforeInsert.length;
|
|
113
|
+
const tail = content.slice(cut, insertAt);
|
|
114
|
+
const needsComma = scan.elements.length > 0 && !beforeInsert.endsWith(',');
|
|
115
|
+
const insert = multiline
|
|
116
|
+
? `${needsComma ? ',' : ''}\n${indent}${rendered},`
|
|
117
|
+
: `${needsComma ? ', ' : ''}${rendered}`;
|
|
118
|
+
content = content.slice(0, cut) + insert + tail + content.slice(insertAt);
|
|
119
|
+
edits.push({
|
|
120
|
+
ruleId: rule.id,
|
|
121
|
+
token: v.token,
|
|
122
|
+
file: target.path,
|
|
123
|
+
line: lineOf(content, cut + insert.length),
|
|
124
|
+
insert: insert.trim(),
|
|
125
|
+
nextContent: content,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return { schema: WIRING_FIX_SCHEMA, edits, skipped };
|
|
129
|
+
}
|
|
@@ -37,4 +37,13 @@ export declare function scanRegistry(projectRoot: string, decl: IRegistryDeclara
|
|
|
37
37
|
export declare function registryExists(inventory: IRegistryInventory, id: string): boolean;
|
|
38
38
|
/** The entry for `id`, or undefined if not declared. */
|
|
39
39
|
export declare function registryWhere(inventory: IRegistryInventory, id: string): IRegistryEntry | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Ids declared in more than one place.
|
|
42
|
+
*
|
|
43
|
+
* A latent bug the compiler cannot see: two roots both claiming the same
|
|
44
|
+
* registry id compile fine, and whichever registration wins at runtime is an
|
|
45
|
+
* accident of load order. Reported with EVERY site so the duplicate can be
|
|
46
|
+
* resolved, not just detected.
|
|
47
|
+
*/
|
|
48
|
+
export declare function registryDuplicates(inventory: IRegistryInventory): readonly IRegistryEntry[];
|
|
40
49
|
//# sourceMappingURL=registry-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAK3D,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;CACzC;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,CA+CpB;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"}
|
|
1
|
+
{"version":3,"file":"registry-query.d.ts","sourceRoot":"","sources":["../../src/wiring/registry-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAK3D,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;CACzC;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,CA+CpB;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"}
|
|
@@ -63,3 +63,14 @@ export function registryExists(inventory, id) {
|
|
|
63
63
|
export function registryWhere(inventory, id) {
|
|
64
64
|
return inventory.entries.find((e) => e.id === id);
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Ids declared in more than one place.
|
|
68
|
+
*
|
|
69
|
+
* A latent bug the compiler cannot see: two roots both claiming the same
|
|
70
|
+
* registry id compile fine, and whichever registration wins at runtime is an
|
|
71
|
+
* accident of load order. Reported with EVERY site so the duplicate can be
|
|
72
|
+
* resolved, not just detected.
|
|
73
|
+
*/
|
|
74
|
+
export function registryDuplicates(inventory) {
|
|
75
|
+
return inventory.entries.filter((e) => e.sites.length > 1);
|
|
76
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAO9B,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;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,GAAE,iBAAsB,GAC9B,aAAa,CA+Bf"}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { matchesAny } from "../scan/glob.js";
|
|
2
2
|
import { readMatchingFiles } from "../util/walk-files.js";
|
|
3
|
-
import { evaluateWiring, } from "./evaluate-wiring.js";
|
|
4
|
-
/** All file globs a rule references:
|
|
3
|
+
import { evaluateWiring, wiringGlobsOf, } from "./evaluate-wiring.js";
|
|
4
|
+
/** All file globs a rule references: every side / every hop. */
|
|
5
5
|
function ruleGlobs(rule) {
|
|
6
|
-
|
|
7
|
-
const sources = Array.isArray(reg)
|
|
8
|
-
? reg
|
|
9
|
-
: [reg];
|
|
10
|
-
return [...rule.declared.files, ...sources.flatMap((s) => [...s.files])];
|
|
6
|
+
return wiringGlobsOf(rule);
|
|
11
7
|
}
|
|
12
8
|
/**
|
|
13
9
|
* Filesystem-backed wiring run: walks the project once, reads only the files a
|
|
@@ -33,6 +29,7 @@ export function runWiring(projectRoot, rules, options = {}) {
|
|
|
33
29
|
rules: [],
|
|
34
30
|
violations: [],
|
|
35
31
|
diagnostics: [],
|
|
32
|
+
skipped: [],
|
|
36
33
|
evaluated: 0,
|
|
37
34
|
verdict: 'pass',
|
|
38
35
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shrkcrft/boundaries",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.29",
|
|
4
4
|
"description": "SharkCraft boundary rules: detect when a repository violates its own architecture (forbidden imports across folder/package/layer boundaries).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SharkCraft contributors",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shrkcrft/core": "^0.1.0-alpha.
|
|
46
|
+
"@shrkcrft/core": "^0.1.0-alpha.29"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|