@shrkcrft/boundaries 0.1.0-alpha.28 → 0.1.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/baseline/compute-baseline.d.ts.map +1 -1
  2. package/dist/baseline/compute-baseline.js +2 -1
  3. package/dist/extract/extract-tokens.d.ts +19 -1
  4. package/dist/extract/extract-tokens.d.ts.map +1 -1
  5. package/dist/extract/extract-tokens.js +53 -1
  6. package/dist/extract/import-edges.d.ts +49 -0
  7. package/dist/extract/import-edges.d.ts.map +1 -0
  8. package/dist/extract/import-edges.js +157 -0
  9. package/dist/extract/inspect-source.d.ts +6 -0
  10. package/dist/extract/inspect-source.d.ts.map +1 -1
  11. package/dist/extract/inspect-source.js +6 -1
  12. package/dist/extract/parse-imports.d.ts +58 -0
  13. package/dist/extract/parse-imports.d.ts.map +1 -0
  14. package/dist/extract/parse-imports.js +155 -0
  15. package/dist/generated/check-provenance.d.ts +20 -2
  16. package/dist/generated/check-provenance.d.ts.map +1 -1
  17. package/dist/generated/check-provenance.js +24 -5
  18. package/dist/generated/scan-generated.d.ts +49 -3
  19. package/dist/generated/scan-generated.d.ts.map +1 -1
  20. package/dist/generated/scan-generated.js +90 -5
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -0
  24. package/dist/policy/evaluate-policy.d.ts +1 -1
  25. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  26. package/dist/policy/evaluate-policy.js +2 -1
  27. package/dist/util/walk-files.d.ts +21 -2
  28. package/dist/util/walk-files.d.ts.map +1 -1
  29. package/dist/util/walk-files.js +85 -4
  30. package/dist/wiring/evaluate-wiring.d.ts +10 -3
  31. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  32. package/dist/wiring/evaluate-wiring.js +12 -7
  33. package/dist/wiring/explain-wiring.d.ts +7 -9
  34. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  35. package/dist/wiring/explain-wiring.js +18 -0
  36. package/dist/wiring/plan-wiring-fix.d.ts +75 -0
  37. package/dist/wiring/plan-wiring-fix.d.ts.map +1 -0
  38. package/dist/wiring/plan-wiring-fix.js +302 -0
  39. package/dist/wiring/registration-graph.d.ts +1 -1
  40. package/dist/wiring/registration-graph.d.ts.map +1 -1
  41. package/dist/wiring/registration-graph.js +8 -3
  42. package/dist/wiring/registry-query.d.ts +1 -1
  43. package/dist/wiring/registry-query.d.ts.map +1 -1
  44. package/dist/wiring/registry-query.js +7 -3
  45. package/dist/wiring/scan-wiring-files.d.ts +1 -1
  46. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  47. package/dist/wiring/scan-wiring-files.js +5 -1
  48. package/dist/wiring/sink-imports.d.ts +84 -0
  49. package/dist/wiring/sink-imports.d.ts.map +1 -0
  50. package/dist/wiring/sink-imports.js +115 -0
  51. package/package.json +2 -2
@@ -11,6 +11,13 @@ export interface IWiringSideExplain {
11
11
  readonly filesScanned: number;
12
12
  /** Misconfiguration (bad regex / no capture group / bad source), if any. */
13
13
  readonly error?: string;
14
+ /**
15
+ * Named extractor this side resolved from, when it used one. Surfaced so an
16
+ * author reading an explain can tell a SHARED selector from a local copy of
17
+ * it — the two look identical in the extracted ids, and only one of them is
18
+ * guaranteed to stay in step with the other planes.
19
+ */
20
+ readonly viaExtractor?: string;
14
21
  }
15
22
  /**
16
23
  * The full intermediate output of evaluating ONE wiring rule against the live
@@ -48,14 +55,5 @@ export interface IExplainWiringOptions {
48
55
  /** Project-relative directories to prune from the walk. */
49
56
  readonly excludeDirs?: readonly string[];
50
57
  }
51
- /**
52
- * Dry-run a single wiring rule against the live tree and return what each side
53
- * extracted (declared set, registered set, the set-difference, the verdict) —
54
- * WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
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.
59
- */
60
58
  export declare function explainWiring(projectRoot: string, rule: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
61
59
  //# 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,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
+ {"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;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;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;AAiCD,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,qBAA0B,GAClC,cAAc,CA2EhB"}
@@ -14,6 +14,22 @@ function sortSites(sites) {
14
14
  * committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
15
15
  * the gate exactly (incl. `groupBy` membership). Never throws.
16
16
  */
17
+ /**
18
+ * The extractor a rule's registered side resolved from — only when EVERY sink
19
+ * shares it. A union of sinks where one is shared and another is inline has no
20
+ * single answer, and naming just one would be a claim the config does not make.
21
+ */
22
+ function registeredExtractorRef(rule) {
23
+ const sinks = Array.isArray(rule.registered)
24
+ ? rule.registered
25
+ : rule.registered
26
+ ? [rule.registered]
27
+ : [];
28
+ if (sinks.length === 0)
29
+ return undefined;
30
+ const first = sinks[0].$use;
31
+ return first !== undefined && sinks.every((s) => s.$use === first) ? first : undefined;
32
+ }
17
33
  export function explainWiring(projectRoot, rule, options = {}) {
18
34
  const sourceSide = wiringSourceSide(rule);
19
35
  // For a chain, the reported "registered" side is the LAST hop (the far end).
@@ -63,12 +79,14 @@ export function explainWiring(projectRoot, rule, options = {}) {
63
79
  distinctCount: ruleResult?.declaredCount ?? 0,
64
80
  filesScanned: declaredFiles.length,
65
81
  ...(declaredRes.error ? { error: declaredRes.error } : {}),
82
+ ...(rule.declared?.$use ? { viaExtractor: rule.declared.$use } : {}),
66
83
  },
67
84
  registered: {
68
85
  sites: sortSites(registeredSites),
69
86
  distinctCount: ruleResult?.registeredCount ?? 0,
70
87
  filesScanned: registeredFiles.size,
71
88
  ...(registeredError ? { error: registeredError } : {}),
89
+ ...(registeredExtractorRef(rule) ? { viaExtractor: registeredExtractorRef(rule) } : {}),
72
90
  },
73
91
  declaredNotRegistered: byDirection('declared-missing'),
74
92
  registeredNotDeclared: byDirection('registered-missing'),
@@ -0,0 +1,75 @@
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
+ /**
17
+ * The sink IMPORTS its array members, so appending to the array alone would
18
+ * leave an unbound reference — a green gate over a file that does not
19
+ * compile. Raised whenever the needed import cannot be derived with
20
+ * certainty.
21
+ */
22
+ | 'needs-import';
23
+ /** One planned edit: append `token` into the anchor array of `file`. */
24
+ export interface IWiringFixEdit {
25
+ readonly ruleId: string;
26
+ readonly token: string;
27
+ /** Project-relative sink file the edit lands in. */
28
+ readonly file: string;
29
+ /** 1-based line of the array's closing bracket, where the token is inserted before. */
30
+ readonly line: number;
31
+ /** The exact text inserted, including indentation and trailing comma. */
32
+ readonly insert: string;
33
+ /** The full new file contents, ready to write. */
34
+ readonly nextContent: string;
35
+ /**
36
+ * The import statement added alongside the array append, when the sink
37
+ * imports its members and the specifier was derivable. Absent when the token
38
+ * was already bound in the sink file.
39
+ */
40
+ readonly importInsert?: string;
41
+ /** 1-based line the import statement lands on. */
42
+ readonly importLine?: number;
43
+ }
44
+ /** A violation the planner deliberately did not touch. */
45
+ export interface IWiringFixSkip {
46
+ readonly ruleId: string;
47
+ readonly token: string;
48
+ readonly reason: WiringFixRefusal;
49
+ readonly detail: string;
50
+ }
51
+ export interface IWiringFixPlan {
52
+ readonly schema: typeof WIRING_FIX_SCHEMA;
53
+ readonly edits: readonly IWiringFixEdit[];
54
+ readonly skipped: readonly IWiringFixSkip[];
55
+ }
56
+ /** A file the planner may read/rewrite. */
57
+ export interface IWiringFixFile {
58
+ readonly path: string;
59
+ readonly content: string;
60
+ }
61
+ /**
62
+ * Plan the mechanically-unambiguous fixes for `declared-but-not-registered`
63
+ * violations.
64
+ *
65
+ * The bar is deliberately high, because a wrong autofix in a gate is worse than
66
+ * no autofix: the tool would be writing the very thing it is supposed to
67
+ * verify. An edit is planned ONLY when there is exactly one registered sink,
68
+ * that sink is an `array-members` source, its glob resolves to exactly one
69
+ * file, and the anchor array is found exactly once in it. Every other shape is
70
+ * reported as skipped WITH the reason — never guessed at.
71
+ *
72
+ * Pure: the caller supplies file contents and performs any writes.
73
+ */
74
+ export declare function planWiringFix(rule: IWiringRule, violations: readonly IWiringViolation[], files: readonly IWiringFixFile[]): IWiringFixPlan;
75
+ //# 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;AAIjE,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAWhF,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;AACxB;;;;;GAKG;GACD,cAAc,CAAC;AAEnB,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;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,kDAAkD;IAClD,QAAQ,CAAC,UAAU,CAAC,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;AAuKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,SAAS,gBAAgB,EAAE,EACvC,KAAK,EAAE,SAAS,cAAc,EAAE,GAC/B,cAAc,CA2KhB"}
@@ -0,0 +1,302 @@
1
+ import { matchesAny } from "../scan/glob.js";
2
+ import { elementToken, escapeRegex, scanBalanced } from "../extract/scan-literals.js";
3
+ import { extractTokens } from "../extract/extract-tokens.js";
4
+ import { registeredSources } from "./evaluate-wiring.js";
5
+ import { collectSinkBindings, deriveImportTemplate, insertImportStatement, renderImport, renderSpecifier, } from "./sink-imports.js";
6
+ import { resolveExtractorAnchor, resolveExtractorKind } from '@shrkcrft/core';
7
+ export const WIRING_FIX_SCHEMA = 'sharkcraft.wiring-fix/v1';
8
+ /** Indentation of the line containing `index`. */
9
+ function indentOf(content, index) {
10
+ const lineStart = content.lastIndexOf('\n', index - 1) + 1;
11
+ const m = /^[ \t]*/.exec(content.slice(lineStart, index));
12
+ return m ? m[0] : ' ';
13
+ }
14
+ function lineOf(content, index) {
15
+ let line = 1;
16
+ for (let i = 0; i < index && i < content.length; i += 1) {
17
+ if (content[i] === '\n')
18
+ line += 1;
19
+ }
20
+ return line;
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
+ }
150
+ /**
151
+ * Plan the mechanically-unambiguous fixes for `declared-but-not-registered`
152
+ * violations.
153
+ *
154
+ * The bar is deliberately high, because a wrong autofix in a gate is worse than
155
+ * no autofix: the tool would be writing the very thing it is supposed to
156
+ * verify. An edit is planned ONLY when there is exactly one registered sink,
157
+ * that sink is an `array-members` source, its glob resolves to exactly one
158
+ * file, and the anchor array is found exactly once in it. Every other shape is
159
+ * reported as skipped WITH the reason — never guessed at.
160
+ *
161
+ * Pure: the caller supplies file contents and performs any writes.
162
+ */
163
+ export function planWiringFix(rule, violations, files) {
164
+ const edits = [];
165
+ const skipped = [];
166
+ const declaredMissing = violations.filter((v) => v.ruleId === rule.id && v.direction === 'declared-missing');
167
+ if (declaredMissing.length === 0)
168
+ return { schema: WIRING_FIX_SCHEMA, edits, skipped };
169
+ const refuseAll = (reason, detail) => ({
170
+ schema: WIRING_FIX_SCHEMA,
171
+ edits: [],
172
+ skipped: declaredMissing.map((v) => ({ ruleId: rule.id, token: v.token, reason, detail })),
173
+ });
174
+ // A chain rule has no single "the sink"; refuse rather than pick one.
175
+ const sinks = rule.chain
176
+ ? []
177
+ : registeredSources(rule.registered);
178
+ if (sinks.length !== 1) {
179
+ return refuseAll('ambiguous-sink', sinks.length === 0
180
+ ? 'rule has no single registered sink (chain rules are never auto-fixed)'
181
+ : `rule has ${sinks.length} registered sinks — which one should the token join?`);
182
+ }
183
+ const sink = sinks[0];
184
+ if (resolveExtractorKind(sink) !== 'array-members') {
185
+ return refuseAll('sink-not-an-array', `sink uses extract "${resolveExtractorKind(sink) ?? 'unknown'}" — only an array literal can be appended to`);
186
+ }
187
+ const anchor = resolveExtractorAnchor(sink);
188
+ if (anchor === undefined)
189
+ return refuseAll('sink-not-an-array', 'sink declares no anchor');
190
+ const sinkFiles = files.filter((f) => matchesAny(f.path, sink.files ?? []));
191
+ if (sinkFiles.length !== 1) {
192
+ return refuseAll('ambiguous-sink-file', `sink glob matched ${sinkFiles.length} files — the insertion point must be unique`);
193
+ }
194
+ const target = sinkFiles[0];
195
+ // Locate the anchor array. More than one occurrence is ambiguous.
196
+ const head = new RegExp(`(?<![\\w$])${escapeRegex(anchor)}\\s*(?::[^=\\n]*)?[:=]\\s*(?:[A-Za-z_$][\\w$.]*\\s*\\(\\s*)?\\[`, 'g');
197
+ const opens = [];
198
+ let m;
199
+ while ((m = head.exec(target.content)) !== null)
200
+ opens.push(m.index + m[0].length - 1);
201
+ if (opens.length !== 1) {
202
+ return refuseAll('array-not-found', opens.length === 0
203
+ ? `array \`${anchor}\` not found in ${target.path}`
204
+ : `array \`${anchor}\` appears ${opens.length} times in ${target.path}`);
205
+ }
206
+ const openIndex = opens[0];
207
+ const { elements, end } = scanBalanced(target.content, openIndex);
208
+ // Match the shape of what is already in the array: bare identifiers vs
209
+ // quoted strings. Inserting the wrong shape would compile-fail or silently
210
+ // not match on the next run.
211
+ const firstEl = elements[0]?.text ?? '';
212
+ const quoted = firstEl.startsWith("'") || firstEl.startsWith('"');
213
+ const quoteChar = firstEl.startsWith('"') ? '"' : "'";
214
+ // Build all edits against one evolving buffer so multiple tokens accumulate.
215
+ let content = target.content;
216
+ let insertAt = end;
217
+ for (const v of declaredMissing) {
218
+ // Re-locate the closing bracket after each insertion.
219
+ const reHead = new RegExp(head.source, 'g');
220
+ const mm = reHead.exec(content);
221
+ if (!mm) {
222
+ skipped.push({
223
+ ruleId: rule.id,
224
+ token: v.token,
225
+ reason: 'array-not-found',
226
+ detail: `array \`${anchor}\` vanished mid-plan in ${target.path}`,
227
+ });
228
+ continue;
229
+ }
230
+ const open = mm.index + mm[0].length - 1;
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
+ }
251
+ insertAt = scan.end;
252
+ const last = scan.elements[scan.elements.length - 1];
253
+ const indent = last ? indentOf(content, last.index) : ' ';
254
+ const multiline = content.slice(open, insertAt).includes('\n');
255
+ const rendered = quoted ? `${quoteChar}${v.token}${quoteChar}` : v.token;
256
+ // Insert after the last non-whitespace character, and KEEP the whitespace
257
+ // run that precedes the closing bracket — trimming it would drag `]` up
258
+ // onto the last element's line and reformat code the user did not ask us
259
+ // to touch.
260
+ const beforeInsert = content.slice(0, insertAt).replace(/\s+$/, '');
261
+ const cut = beforeInsert.length;
262
+ const tail = content.slice(cut, insertAt);
263
+ const needsComma = scan.elements.length > 0 && !beforeInsert.endsWith(',');
264
+ const insert = multiline
265
+ ? `${needsComma ? ',' : ''}\n${indent}${rendered},`
266
+ : `${needsComma ? ', ' : ''}${rendered}`;
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
+ }
291
+ edits.push({
292
+ ruleId: rule.id,
293
+ token: v.token,
294
+ file: target.path,
295
+ line: arrayLine,
296
+ insert: insert.trim(),
297
+ nextContent: content,
298
+ ...(importInsert !== undefined ? { importInsert, importLine: importLine } : {}),
299
+ });
300
+ }
301
+ return { schema: WIRING_FIX_SCHEMA, edits, skipped };
302
+ }
@@ -1,4 +1,4 @@
1
- import type { IRegistrationIdiom } from '@shrkcrft/core';
1
+ import { type IRegistrationIdiom } from '@shrkcrft/core';
2
2
  import { type IWiringFileEntry } from './evaluate-wiring.js';
3
3
  export declare const REGISTRATION_GRAPH_SCHEMA: "sharkcraft.registration-graph/v1";
4
4
  /** A token role-site: which idiom + file:line it was found at. */
@@ -1 +1 @@
1
- {"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registration-graph.d.ts","sourceRoot":"","sources":["../../src/wiring/registration-graph.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { statSync } from 'node:fs';
3
3
  import * as nodePath from 'node:path';
4
+ import { resolveSourceGlobs } from '@shrkcrft/core';
4
5
  import { matchesAny } from "../scan/glob.js";
5
6
  import { readMatchingFiles, walkMatching } from "../util/walk-files.js";
6
7
  import { collectSourceSites } from "./evaluate-wiring.js";
@@ -11,7 +12,11 @@ function sortSites(sites) {
11
12
  /** Union (deduped) of every glob any role of any idiom references. */
12
13
  function registrationGlobs(idioms) {
13
14
  return [
14
- ...new Set(idioms.flatMap((i) => [...i.declared.files, ...i.provided.files, ...i.consumed.files])),
15
+ ...new Set(idioms.flatMap((i) => [
16
+ ...resolveSourceGlobs(i.declared),
17
+ ...resolveSourceGlobs(i.provided),
18
+ ...resolveSourceGlobs(i.consumed),
19
+ ])),
15
20
  ];
16
21
  }
17
22
  /**
@@ -28,7 +33,7 @@ export function buildRegistrationGraph(projectRoot, idioms, options = {}) {
28
33
  path,
29
34
  content,
30
35
  }));
31
- const filesFor = (source) => entries.filter((f) => matchesAny(f.path, source.files));
36
+ const filesFor = (source) => entries.filter((f) => matchesAny(f.path, resolveSourceGlobs(source)));
32
37
  const declared = new Map();
33
38
  const provided = new Map();
34
39
  const consumed = new Map();
@@ -96,7 +101,7 @@ export function registrationGraphSignature(projectRoot, idioms, options = {}) {
96
101
  export function providedTokensFromEntries(idioms, entries) {
97
102
  const tokens = new Set();
98
103
  for (const idiom of idioms) {
99
- const files = entries.filter((f) => matchesAny(f.path, idiom.provided.files));
104
+ const files = entries.filter((f) => matchesAny(f.path, resolveSourceGlobs(idiom.provided)));
100
105
  for (const s of collectSourceSites(idiom.provided, files).sites)
101
106
  tokens.add(s.token);
102
107
  }
@@ -1,4 +1,4 @@
1
- import type { IRegistryDeclaration } from '@shrkcrft/core';
1
+ import { type IRegistryDeclaration } from '@shrkcrft/core';
2
2
  export declare const REGISTRY_SCHEMA: "sharkcraft.registry-inventory/v1";
3
3
  /** Where an id was found (project-relative path + 1-based line). */
4
4
  export interface IRegistrySite {
@@ -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;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,kBAAkB,GAAG,SAAS,cAAc,EAAE,CAE3F"}
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;AAK/E,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,CAkDpB;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,3 +1,4 @@
1
+ import { resolveSourceGlobs } from '@shrkcrft/core';
1
2
  import { matchesAny } from "../scan/glob.js";
2
3
  import { readMatchingFiles } from "../util/walk-files.js";
3
4
  import { collectSourceSites } from "./evaluate-wiring.js";
@@ -12,18 +13,21 @@ function sortSites(sites) {
12
13
  */
13
14
  export function scanRegistry(projectRoot, decl, options = {}) {
14
15
  const globs = [
15
- ...new Set([...decl.source.files, ...(decl.consumer ? decl.consumer.files : [])]),
16
+ ...new Set([
17
+ ...resolveSourceGlobs(decl.source),
18
+ ...(decl.consumer ? resolveSourceGlobs(decl.consumer) : []),
19
+ ]),
16
20
  ];
17
21
  const cache = readMatchingFiles(projectRoot, globs, new Set(options.excludeDirs ?? []));
18
22
  const entries = [...cache.entries()].map(([path, content]) => ({ path, content }));
19
23
  const diagnostics = [];
20
- const sourceFiles = entries.filter((f) => matchesAny(f.path, decl.source.files));
24
+ const sourceFiles = entries.filter((f) => matchesAny(f.path, resolveSourceGlobs(decl.source)));
21
25
  const declared = collectSourceSites(decl.source, sourceFiles);
22
26
  if (declared.error)
23
27
  diagnostics.push(`registry "${decl.name}" source: ${declared.error}`);
24
28
  const consumerByToken = new Map();
25
29
  if (decl.consumer) {
26
- const consumerFiles = entries.filter((f) => matchesAny(f.path, decl.consumer.files));
30
+ const consumerFiles = entries.filter((f) => matchesAny(f.path, resolveSourceGlobs(decl.consumer)));
27
31
  const consumed = collectSourceSites(decl.consumer, consumerFiles);
28
32
  if (consumed.error)
29
33
  diagnostics.push(`registry "${decl.name}" consumer: ${consumed.error}`);
@@ -1,4 +1,4 @@
1
- import type { IWiringRule } from '@shrkcrft/core';
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. */
@@ -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,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
+ {"version":3,"file":"scan-wiring-files.d.ts","sourceRoot":"","sources":["../../src/wiring/scan-wiring-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAItE,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,CAqCf"}
@@ -1,5 +1,7 @@
1
+ import { resolveSourceGlobs } from '@shrkcrft/core';
1
2
  import { matchesAny } from "../scan/glob.js";
2
3
  import { readMatchingFiles } from "../util/walk-files.js";
4
+ import { loadTsconfigPaths } from "../scan/tsconfig-aliases.js";
3
5
  import { evaluateWiring, wiringGlobsOf, } from "./evaluate-wiring.js";
4
6
  /** All file globs a rule references: every side / every hop. */
5
7
  function ruleGlobs(rule) {
@@ -38,5 +40,7 @@ export function runWiring(projectRoot, rules, options = {}) {
38
40
  const allGlobs = [...new Set(selected.flatMap(ruleGlobs))];
39
41
  const cache = readMatchingFiles(projectRoot, allGlobs, new Set(options.excludeDirs ?? []));
40
42
  const entries = [...cache.entries()].map(([path, content]) => ({ path, content }));
41
- return evaluateWiring(selected, (source) => entries.filter((f) => matchesAny(f.path, source.files)));
43
+ // The tsconfig paths let an `import-edges` source resolve alias specifiers the
44
+ // way the compiler does — the same map `check boundaries` resolves with.
45
+ return evaluateWiring(selected, (source) => entries.filter((f) => matchesAny(f.path, resolveSourceGlobs(source))), { tsconfigPaths: loadTsconfigPaths(projectRoot) });
42
46
  }