@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.
Files changed (229) hide show
  1. package/dist/baseline/compute-baseline.d.ts +8 -0
  2. package/dist/baseline/compute-baseline.d.ts.map +1 -1
  3. package/dist/baseline/compute-baseline.js +10 -8
  4. package/dist/baseline/diff-baseline.d.ts +28 -0
  5. package/dist/baseline/diff-baseline.d.ts.map +1 -1
  6. package/dist/baseline/diff-baseline.js +29 -0
  7. package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
  8. package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
  9. package/dist/evaluate/boundary-unit-finding.js +21 -0
  10. package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
  11. package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
  12. package/dist/evaluate/boundary-unit-kind.js +14 -0
  13. package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
  14. package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
  15. package/dist/evaluate/evaluate-boundaries.js +481 -53
  16. package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
  17. package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
  18. package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
  19. package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
  20. package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
  21. package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
  22. package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
  23. package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
  24. package/dist/evaluate/i-boundary-unit-finding.js +1 -0
  25. package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
  26. package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
  27. package/dist/evaluate/settle-boundary-rule.js +73 -0
  28. package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
  29. package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
  30. package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
  31. package/dist/extract/code-zones.d.ts +95 -3
  32. package/dist/extract/code-zones.d.ts.map +1 -1
  33. package/dist/extract/code-zones.js +313 -4
  34. package/dist/extract/extract-tokens.d.ts +49 -1
  35. package/dist/extract/extract-tokens.d.ts.map +1 -1
  36. package/dist/extract/extract-tokens.js +283 -25
  37. package/dist/extract/i-labeled-source.d.ts +12 -0
  38. package/dist/extract/i-labeled-source.d.ts.map +1 -0
  39. package/dist/extract/i-labeled-source.js +1 -0
  40. package/dist/extract/import-edges.d.ts +50 -0
  41. package/dist/extract/import-edges.d.ts.map +1 -0
  42. package/dist/extract/import-edges.js +170 -0
  43. package/dist/extract/inspect-source.d.ts +38 -3
  44. package/dist/extract/inspect-source.d.ts.map +1 -1
  45. package/dist/extract/inspect-source.js +56 -8
  46. package/dist/extract/parse-imports.d.ts +80 -0
  47. package/dist/extract/parse-imports.d.ts.map +1 -0
  48. package/dist/extract/parse-imports.js +254 -0
  49. package/dist/extract/scan-literals.d.ts +12 -1
  50. package/dist/extract/scan-literals.d.ts.map +1 -1
  51. package/dist/extract/scan-literals.js +15 -1
  52. package/dist/extract/source-liveness-request.d.ts +24 -0
  53. package/dist/extract/source-liveness-request.d.ts.map +1 -0
  54. package/dist/extract/source-liveness-request.js +45 -0
  55. package/dist/generated/check-provenance.d.ts +20 -2
  56. package/dist/generated/check-provenance.d.ts.map +1 -1
  57. package/dist/generated/check-provenance.js +27 -6
  58. package/dist/generated/read-regen-tree.d.ts +16 -0
  59. package/dist/generated/read-regen-tree.d.ts.map +1 -0
  60. package/dist/generated/read-regen-tree.js +61 -0
  61. package/dist/generated/scan-generated.d.ts +57 -3
  62. package/dist/generated/scan-generated.d.ts.map +1 -1
  63. package/dist/generated/scan-generated.js +111 -11
  64. package/dist/index.d.ts +51 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +54 -0
  67. package/dist/model/boundary-intended-empty-line.d.ts +10 -0
  68. package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
  69. package/dist/model/boundary-intended-empty-line.js +11 -0
  70. package/dist/model/boundary-markable-list.d.ts +15 -0
  71. package/dist/model/boundary-markable-list.d.ts.map +1 -0
  72. package/dist/model/boundary-markable-list.js +15 -0
  73. package/dist/model/boundary-rule-input-keys.d.ts +11 -0
  74. package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
  75. package/dist/model/boundary-rule-input-keys.js +29 -0
  76. package/dist/model/boundary-rule-input.d.ts +23 -0
  77. package/dist/model/boundary-rule-input.d.ts.map +1 -0
  78. package/dist/model/boundary-rule-input.js +1 -0
  79. package/dist/model/boundary-rule-key-problems.d.ts +11 -0
  80. package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
  81. package/dist/model/boundary-rule-key-problems.js +36 -0
  82. package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
  83. package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
  84. package/dist/model/boundary-rule-marker-problems.js +94 -0
  85. package/dist/model/boundary-rule-scope.d.ts +88 -0
  86. package/dist/model/boundary-rule-scope.d.ts.map +1 -0
  87. package/dist/model/boundary-rule-scope.js +175 -0
  88. package/dist/model/boundary-rule.d.ts +120 -5
  89. package/dist/model/boundary-rule.d.ts.map +1 -1
  90. package/dist/model/boundary-rule.js +148 -1
  91. package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
  92. package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
  93. package/dist/model/boundary-unit-problem-issue.js +16 -0
  94. package/dist/model/normalize-boundary-rule.d.ts +21 -0
  95. package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
  96. package/dist/model/normalize-boundary-rule.js +62 -0
  97. package/dist/policy/evaluate-policy.d.ts +80 -3
  98. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  99. package/dist/policy/evaluate-policy.js +152 -19
  100. package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
  101. package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
  102. package/dist/policy/i-policy-rule-liveness.js +1 -0
  103. package/dist/policy/run-policy.d.ts +1 -1
  104. package/dist/policy/run-policy.d.ts.map +1 -1
  105. package/dist/policy/run-policy.js +171 -21
  106. package/dist/registry/load-boundary-rules.d.ts +29 -1
  107. package/dist/registry/load-boundary-rules.d.ts.map +1 -1
  108. package/dist/registry/load-boundary-rules.js +38 -8
  109. package/dist/scan/glob.d.ts +90 -0
  110. package/dist/scan/glob.d.ts.map +1 -1
  111. package/dist/scan/glob.js +205 -0
  112. package/dist/scan/i-glob-unit-measure.d.ts +22 -0
  113. package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
  114. package/dist/scan/i-glob-unit-measure.js +1 -0
  115. package/dist/scan/import-pattern.d.ts +109 -0
  116. package/dist/scan/import-pattern.d.ts.map +1 -0
  117. package/dist/scan/import-pattern.js +191 -0
  118. package/dist/scan/node-builtin-package-names.d.ts +13 -0
  119. package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
  120. package/dist/scan/node-builtin-package-names.js +25 -0
  121. package/dist/scan/scan-imports.d.ts +36 -3
  122. package/dist/scan/scan-imports.d.ts.map +1 -1
  123. package/dist/scan/scan-imports.js +114 -45
  124. package/dist/util/blank-run-hazard-finding.d.ts +34 -0
  125. package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
  126. package/dist/util/blank-run-hazard-finding.js +1 -0
  127. package/dist/util/blank-run-hazard.d.ts +29 -0
  128. package/dist/util/blank-run-hazard.d.ts.map +1 -0
  129. package/dist/util/blank-run-hazard.js +450 -0
  130. package/dist/util/dead-glob-units.d.ts +46 -0
  131. package/dist/util/dead-glob-units.d.ts.map +1 -0
  132. package/dist/util/dead-glob-units.js +116 -0
  133. package/dist/util/glob-list-liveness-input.d.ts +31 -0
  134. package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
  135. package/dist/util/glob-list-liveness-input.js +65 -0
  136. package/dist/util/i-dead-glob-unit.d.ts +24 -0
  137. package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
  138. package/dist/util/i-dead-glob-unit.js +1 -0
  139. package/dist/util/i-glob-list-units.d.ts +25 -0
  140. package/dist/util/i-glob-list-units.d.ts.map +1 -0
  141. package/dist/util/i-glob-list-units.js +1 -0
  142. package/dist/util/i-glob-liveness-list.d.ts +23 -0
  143. package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
  144. package/dist/util/i-glob-liveness-list.js +1 -0
  145. package/dist/util/i-glob-liveness-request.d.ts +11 -0
  146. package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
  147. package/dist/util/i-glob-liveness-request.js +1 -0
  148. package/dist/util/i-glob-negation.d.ts +13 -0
  149. package/dist/util/i-glob-negation.d.ts.map +1 -0
  150. package/dist/util/i-glob-negation.js +1 -0
  151. package/dist/util/matched-files.d.ts +22 -0
  152. package/dist/util/matched-files.d.ts.map +1 -0
  153. package/dist/util/matched-files.js +1 -0
  154. package/dist/util/negation-cause.d.ts +15 -0
  155. package/dist/util/negation-cause.d.ts.map +1 -0
  156. package/dist/util/negation-cause.js +21 -0
  157. package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
  158. package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
  159. package/dist/util/plane-scan-exclude-dirs.js +22 -0
  160. package/dist/util/read-glob-list-liveness.d.ts +20 -0
  161. package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
  162. package/dist/util/read-glob-list-liveness.js +20 -0
  163. package/dist/util/read-scope-coverage.d.ts +90 -0
  164. package/dist/util/read-scope-coverage.d.ts.map +1 -0
  165. package/dist/util/read-scope-coverage.js +173 -0
  166. package/dist/util/read-scope.d.ts +14 -0
  167. package/dist/util/read-scope.d.ts.map +1 -0
  168. package/dist/util/read-scope.js +1 -0
  169. package/dist/util/read-selected-files.d.ts +16 -0
  170. package/dist/util/read-selected-files.d.ts.map +1 -0
  171. package/dist/util/read-selected-files.js +25 -0
  172. package/dist/util/settle-glob-lists.d.ts +12 -0
  173. package/dist/util/settle-glob-lists.d.ts.map +1 -0
  174. package/dist/util/settle-glob-lists.js +13 -0
  175. package/dist/util/unread-file-reason.d.ts +26 -0
  176. package/dist/util/unread-file-reason.d.ts.map +1 -0
  177. package/dist/util/unread-file-reason.js +26 -0
  178. package/dist/util/unread-file.d.ts +10 -0
  179. package/dist/util/unread-file.d.ts.map +1 -0
  180. package/dist/util/unread-file.js +1 -0
  181. package/dist/util/walk-files.d.ts +90 -7
  182. package/dist/util/walk-files.d.ts.map +1 -1
  183. package/dist/util/walk-files.js +206 -19
  184. package/dist/wiring/evaluate-wiring.d.ts +88 -7
  185. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  186. package/dist/wiring/evaluate-wiring.js +276 -16
  187. package/dist/wiring/explain-wiring.d.ts +56 -14
  188. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  189. package/dist/wiring/explain-wiring.js +136 -10
  190. package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
  191. package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
  192. package/dist/wiring/i-idiom-role-coverage.js +1 -0
  193. package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
  194. package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
  195. package/dist/wiring/i-registration-query-verdict.js +1 -0
  196. package/dist/wiring/i-registration-roles.d.ts +71 -0
  197. package/dist/wiring/i-registration-roles.d.ts.map +1 -0
  198. package/dist/wiring/i-registration-roles.js +1 -0
  199. package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
  200. package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
  201. package/dist/wiring/measure-idiom-role-coverage.js +20 -0
  202. package/dist/wiring/measure-registration-roles.d.ts +25 -0
  203. package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
  204. package/dist/wiring/measure-registration-roles.js +124 -0
  205. package/dist/wiring/plan-wiring-fix.d.ts +16 -1
  206. package/dist/wiring/plan-wiring-fix.d.ts.map +1 -1
  207. package/dist/wiring/plan-wiring-fix.js +177 -4
  208. package/dist/wiring/registration-graph.d.ts +13 -1
  209. package/dist/wiring/registration-graph.d.ts.map +1 -1
  210. package/dist/wiring/registration-graph.js +22 -6
  211. package/dist/wiring/registration-query-verdict.d.ts +23 -0
  212. package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
  213. package/dist/wiring/registration-query-verdict.js +118 -0
  214. package/dist/wiring/registry-query.d.ts +9 -1
  215. package/dist/wiring/registry-query.d.ts.map +1 -1
  216. package/dist/wiring/registry-query.js +17 -6
  217. package/dist/wiring/scan-wiring-files.d.ts +5 -3
  218. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  219. package/dist/wiring/scan-wiring-files.js +55 -14
  220. package/dist/wiring/sink-imports.d.ts +95 -0
  221. package/dist/wiring/sink-imports.d.ts.map +1 -0
  222. package/dist/wiring/sink-imports.js +124 -0
  223. package/dist/wiring/trace-literal.d.ts +6 -0
  224. package/dist/wiring/trace-literal.d.ts.map +1 -1
  225. package/dist/wiring/trace-literal.js +5 -2
  226. package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
  227. package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
  228. package/dist/wiring/wiring-labeled-sources.js +20 -0
  229. package/package.json +2 -2
@@ -1,27 +1,49 @@
1
- import { matchesAny } from "../scan/glob.js";
1
+ import { normalizeWiringRule, resolveSourceGlobs } from '@shrkcrft/core';
2
+ import { globListSelects } from "../scan/glob.js";
2
3
  import { readMatchingFiles } from "../util/walk-files.js";
3
- import { evaluateWiring, wiringGlobsOf, } from "./evaluate-wiring.js";
4
+ import { unreadMatching } from "../util/read-scope-coverage.js";
5
+ import { globListUnits } from "../util/dead-glob-units.js";
6
+ import { settleGlobLists } from "../util/settle-glob-lists.js";
7
+ import { sourceLivenessRequest } from "../extract/source-liveness-request.js";
8
+ import { loadTsconfigPaths } from "../scan/tsconfig-aliases.js";
9
+ import { evaluateWiring, wiringGlobsOf, wiringSourcesOf, } from "./evaluate-wiring.js";
10
+ import { wiringLabeledSources } from "./wiring-labeled-sources.js";
4
11
  /** All file globs a rule references: every side / every hop. */
5
12
  function ruleGlobs(rule) {
6
13
  return wiringGlobsOf(rule);
7
14
  }
8
15
  /**
9
16
  * Filesystem-backed wiring run: walks the project once, reads only the files a
10
- * rule references (skipping oversized files), and evaluates every (selected)
11
- * rule. Pure-engine output; the only IO is the read-only tree walk + reads.
17
+ * rule references, and evaluates every (selected) rule. A matched file the
18
+ * reader could not read (over the read cap) is handed to the engine as
19
+ * unread, so the rule's coverage names it instead of passing over it.
20
+ * Pure-engine output; the only IO is the read-only tree walk + reads.
12
21
  */
13
22
  export function runWiring(projectRoot, rules, options = {}) {
14
- let selected = rules;
23
+ // The engine entry normalises idempotently (round 13): a loaded rule comes
24
+ // back equal, a hand-built `{ pattern, expectEmpty }` entry becomes a glob
25
+ // plus a marker. A MALFORMED entry stays on the authored rule, which
26
+ // `evaluateWiring` reports as misconfigured — no glob reader here sees it.
27
+ const malformed = new Set();
28
+ let selected = rules.map((r) => {
29
+ const n = normalizeWiringRule(r);
30
+ if (n.ok)
31
+ return n.value;
32
+ malformed.add(r);
33
+ return r;
34
+ });
15
35
  if (options.only && options.only.length > 0) {
16
36
  const ids = new Set(options.only);
17
37
  selected = selected.filter((r) => ids.has(r.id));
18
38
  }
19
39
  if (options.changedOnly) {
20
40
  const changed = options.changedFiles ?? [];
21
- selected = selected.filter((r) => {
22
- const globs = ruleGlobs(r);
23
- return changed.some((c) => matchesAny(c, globs));
24
- });
41
+ // Per SOURCE, never through the flattened union: a negation subtracts from
42
+ // its own list only, so one side's `!x` cannot hide a changed file another
43
+ // side reads, and a change touching only excluded files selects nothing.
44
+ // A malformed rule has no footprint to prove it out of scope: it stays in.
45
+ selected = selected.filter((r) => malformed.has(r) ||
46
+ wiringSourcesOf(r).some((s) => changed.some((c) => globListSelects(c, resolveSourceGlobs(s)))));
25
47
  }
26
48
  if (selected.length === 0) {
27
49
  return {
@@ -31,12 +53,31 @@ export function runWiring(projectRoot, rules, options = {}) {
31
53
  diagnostics: [],
32
54
  skipped: [],
33
55
  evaluated: 0,
56
+ acceptedEmpty: 0,
34
57
  verdict: 'pass',
35
58
  };
36
59
  }
37
- // Union of all globs across selected rules → one tree walk, cached reads.
38
- const allGlobs = [...new Set(selected.flatMap(ruleGlobs))];
39
- const cache = readMatchingFiles(projectRoot, allGlobs, new Set(options.excludeDirs ?? []));
40
- const entries = [...cache.entries()].map(([path, content]) => ({ path, content }));
41
- return evaluateWiring(selected, (source) => entries.filter((f) => matchesAny(f.path, source.files)));
60
+ // Union of all globs across selected rules → one POSITIVE tree walk, cached
61
+ // reads; each source then selects its own files from it (`globListSelects`).
62
+ const allGlobs = [...new Set(selected.filter((r) => !malformed.has(r)).flatMap(ruleGlobs))];
63
+ const excludeDirs = options.excludeDirs ?? [];
64
+ const matched = readMatchingFiles(projectRoot, allGlobs, new Set(excludeDirs));
65
+ const entries = [...matched.files.entries()].map(([path, content]) => ({ path, content }));
66
+ const walked = entries.map((f) => f.path);
67
+ // THE one dead-unit decision (`globListUnits`, the one `gates coverage` and
68
+ // `policy-lint` read) over this run's positive walk.
69
+ const unitsOf = (globs) => globListUnits(walked, matched.unread, globs);
70
+ // The tsconfig paths let an `import-edges` source resolve alias specifiers the
71
+ // way the compiler does — the same map `check boundaries` resolves with.
72
+ return evaluateWiring(selected, (source) => entries.filter((f) => globListSelects(f.path, resolveSourceGlobs(source))), { tsconfigPaths: loadTsconfigPaths(projectRoot) }, (source) => unreadMatching(matched.unread, resolveSourceGlobs(source)),
73
+ // Which negations emptied a source's list, so its skip reason says so (R12-DOC-2).
74
+ (source) => {
75
+ const units = unitsOf(resolveSourceGlobs(source));
76
+ return units.allExcluded ? units.negations : undefined;
77
+ },
78
+ // Every source's glob units settled with their `expectEmpty` markers
79
+ // (round 13) — THE per-source request `gates coverage` builds too
80
+ // (`sourceLivenessRequest` over the engine's own side labels), so `check
81
+ // wiring` and `gates coverage` accept, and word, one planned glob alike.
82
+ (rule) => settleGlobLists(sourceLivenessRequest(projectRoot, wiringLabeledSources(rule), excludeDirs, rule.id, unitsOf)));
42
83
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Import analysis for a wiring SINK file.
3
+ *
4
+ * `check wiring --fix` appends a token to a registry array. That edit is only
5
+ * safe when the token actually RESOLVES in the sink file. The common registry
6
+ * shape imports every member:
7
+ *
8
+ * ```ts
9
+ * import { ALPHA_HANDLER } from './ALPHA_HANDLER';
10
+ * export const HANDLERS = [ALPHA_HANDLER];
11
+ * ```
12
+ *
13
+ * Appending `NEW_HANDLER` to that array turns the wiring gate GREEN while
14
+ * leaving the file referencing an unbound symbol — it no longer compiles. A
15
+ * gate that goes green by breaking the build is the exact inverse of the
16
+ * point, so the planner has to know what the sink file binds before it writes.
17
+ *
18
+ * This is a lexer, not a compiler: it answers "is this name bound in this
19
+ * file?" and "do the existing members follow one derivable import pattern?" —
20
+ * both of which are decidable from the text, and neither of which is guessed.
21
+ */
22
+ /** How a name entered the file's scope. */
23
+ export type SinkBindingKind = 'named-import' | 'default-import' | 'namespace-import' | 'local';
24
+ /** One value binding visible in the sink file. */
25
+ export interface ISinkBinding {
26
+ /** The name as used in the file (the local name, after any `as` alias). */
27
+ readonly local: string;
28
+ readonly kind: SinkBindingKind;
29
+ /** The exported name, for a named import (differs from `local` when aliased). */
30
+ readonly imported?: string;
31
+ /** Module specifier, for an import binding. */
32
+ readonly specifier?: string;
33
+ }
34
+ /**
35
+ * Top-level value/type declarations — the names a file binds itself.
36
+ *
37
+ * Every whitespace run is single-line (`[ \t]`). The old form opened with
38
+ * `(?:^|\n)\s*`: from every newline of a long blank block, `\s*` ran across all
39
+ * the lines below it and backtracked through each — O(lines × run) per block
40
+ * (`findBlankRunHazards` flags that shape). Modifiers and the declared name sit
41
+ * on the declaration's own line in real TS, so the matches are the same
42
+ * (`r75-blank-run-linearity.test.ts` holds old ≡ new over every source file).
43
+ */
44
+ export declare const LOCAL_DECL: RegExp;
45
+ /**
46
+ * Every value name bound in the file: imports plus its own top-level
47
+ * declarations.
48
+ *
49
+ * Local declarations count because a registry that declares its members inline
50
+ * needs no import at all — refusing to fix those would be a false refusal.
51
+ */
52
+ export declare function collectSinkBindings(content: string): ISinkBinding[];
53
+ /** Whether `name` already resolves in the sink file. */
54
+ export declare function isBoundInSink(content: string, name: string): boolean;
55
+ /** A uniform import pattern the existing members all follow. */
56
+ export interface IImportTemplate {
57
+ /** The specifier with every occurrence of the member name replaced by `{}`. */
58
+ readonly specifierTemplate: string;
59
+ /** Quote character used by the existing imports. */
60
+ readonly quote: string;
61
+ /** Whether existing import statements end in a semicolon. */
62
+ readonly semicolon: boolean;
63
+ /** How many members the template was derived from. */
64
+ readonly derivedFrom: number;
65
+ }
66
+ /**
67
+ * Derive the import pattern the sink's existing members follow, if there is
68
+ * exactly one.
69
+ *
70
+ * Derivation succeeds only when the specifier is a pure FUNCTION OF THE MEMBER
71
+ * NAME — every member `M` imported as `import { M } from '<something>M<something>'`
72
+ * with the same surrounding text. A barrel (`from './handlers'`), mixed paths,
73
+ * or an aliased import make the next specifier unknowable, and the planner must
74
+ * never invent a path: those return `undefined` so the caller refuses instead.
75
+ */
76
+ export declare function deriveImportTemplate(content: string, members: readonly string[]): IImportTemplate | undefined;
77
+ /** Render the import statement this template produces for `token`. */
78
+ export declare function renderImport(template: IImportTemplate, token: string): string;
79
+ /** The specifier a template yields for `token`, without the surrounding syntax. */
80
+ export declare function renderSpecifier(template: IImportTemplate, token: string): string;
81
+ /** Where a new import line goes, and the resulting content. */
82
+ export interface IImportInsertion {
83
+ readonly nextContent: string;
84
+ /** 1-based line the inserted statement lands on. */
85
+ readonly line: number;
86
+ }
87
+ /**
88
+ * Insert an import statement after the LAST existing import.
89
+ *
90
+ * Appending to the import block (rather than sorting into it) keeps the edit a
91
+ * pure insertion: no existing line moves, so the diff shows exactly one added
92
+ * line and a reviewer can see the whole change at a glance.
93
+ */
94
+ export declare function insertImportStatement(content: string, statement: string): IImportInsertion | undefined;
95
+ //# sourceMappingURL=sink-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sink-imports.d.ts","sourceRoot":"","sources":["../../src/wiring/sink-imports.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,2CAA2C;AAC3C,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,OAAO,CAAC;AAE/F,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,QAC0I,CAAC;AAElK;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAsBnE;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,eAAe,GAAG,SAAS,CA6B7B;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,gBAAgB,GAAG,SAAS,CAe9B"}
@@ -0,0 +1,124 @@
1
+ import { lineOf } from "../extract/scan-literals.js";
2
+ import { parseImportStatements } from "../extract/parse-imports.js";
3
+ /**
4
+ * Top-level value/type declarations — the names a file binds itself.
5
+ *
6
+ * Every whitespace run is single-line (`[ \t]`). The old form opened with
7
+ * `(?:^|\n)\s*`: from every newline of a long blank block, `\s*` ran across all
8
+ * the lines below it and backtracked through each — O(lines × run) per block
9
+ * (`findBlankRunHazards` flags that shape). Modifiers and the declared name sit
10
+ * on the declaration's own line in real TS, so the matches are the same
11
+ * (`r75-blank-run-linearity.test.ts` holds old ≡ new over every source file).
12
+ */
13
+ export const LOCAL_DECL = /(?:^|\n)[ \t]*(?:export[ \t]+)?(?:declare[ \t]+)?(?:default[ \t]+)?(?:const|let|var|function(?:[ \t]*\*)?|class|enum|interface|type)[ \t]+([A-Za-z_$][\w$]*)/g;
14
+ /**
15
+ * Every value name bound in the file: imports plus its own top-level
16
+ * declarations.
17
+ *
18
+ * Local declarations count because a registry that declares its members inline
19
+ * needs no import at all — refusing to fix those would be a false refusal.
20
+ */
21
+ export function collectSinkBindings(content) {
22
+ const bindings = [];
23
+ for (const statement of parseImportStatements(content)) {
24
+ // A type-only import binds no VALUE, so it can never make an array element
25
+ // resolve. Counting it would let the planner write a reference that erases
26
+ // at compile time.
27
+ if (statement.typeOnly)
28
+ continue;
29
+ for (const b of statement.bindings) {
30
+ bindings.push({
31
+ local: b.local,
32
+ kind: `${b.kind}-import`,
33
+ ...(b.imported !== undefined ? { imported: b.imported } : {}),
34
+ specifier: statement.specifier,
35
+ });
36
+ }
37
+ }
38
+ LOCAL_DECL.lastIndex = 0;
39
+ let m;
40
+ while ((m = LOCAL_DECL.exec(content)) !== null) {
41
+ bindings.push({ local: m[1], kind: 'local' });
42
+ }
43
+ return bindings;
44
+ }
45
+ /** Whether `name` already resolves in the sink file. */
46
+ export function isBoundInSink(content, name) {
47
+ return collectSinkBindings(content).some((b) => b.local === name);
48
+ }
49
+ /**
50
+ * Derive the import pattern the sink's existing members follow, if there is
51
+ * exactly one.
52
+ *
53
+ * Derivation succeeds only when the specifier is a pure FUNCTION OF THE MEMBER
54
+ * NAME — every member `M` imported as `import { M } from '<something>M<something>'`
55
+ * with the same surrounding text. A barrel (`from './handlers'`), mixed paths,
56
+ * or an aliased import make the next specifier unknowable, and the planner must
57
+ * never invent a path: those return `undefined` so the caller refuses instead.
58
+ */
59
+ export function deriveImportTemplate(content, members) {
60
+ const bindings = collectSinkBindings(content);
61
+ const byLocal = new Map(bindings.map((b) => [b.local, b]));
62
+ let template;
63
+ let derivedFrom = 0;
64
+ for (const member of members) {
65
+ const binding = byLocal.get(member);
66
+ if (!binding || binding.kind === 'local')
67
+ continue;
68
+ // An alias means the local name is NOT the exported name, so the next
69
+ // token's exported name cannot be derived from the name we would write.
70
+ if (binding.kind !== 'named-import' || binding.imported !== binding.local)
71
+ return undefined;
72
+ if (!binding.specifier || !binding.specifier.includes(member))
73
+ return undefined;
74
+ const candidate = binding.specifier.split(member).join('{}');
75
+ if (template === undefined)
76
+ template = candidate;
77
+ else if (template !== candidate)
78
+ return undefined;
79
+ derivedFrom += 1;
80
+ }
81
+ if (template === undefined || derivedFrom === 0)
82
+ return undefined;
83
+ // Mirror the file's existing punctuation so the inserted line does not fight
84
+ // the formatter that will run over it next.
85
+ const raw = parseImportStatements(content).find((st) => st.kind === 'import')?.raw ?? '';
86
+ return {
87
+ specifierTemplate: template,
88
+ quote: raw.includes('"') ? '"' : "'",
89
+ semicolon: raw.trimEnd().endsWith(';'),
90
+ derivedFrom,
91
+ };
92
+ }
93
+ /** Render the import statement this template produces for `token`. */
94
+ export function renderImport(template, token) {
95
+ const specifier = template.specifierTemplate.split('{}').join(token);
96
+ return `import { ${token} } from ${template.quote}${specifier}${template.quote}${template.semicolon ? ';' : ''}`;
97
+ }
98
+ /** The specifier a template yields for `token`, without the surrounding syntax. */
99
+ export function renderSpecifier(template, token) {
100
+ return template.specifierTemplate.split('{}').join(token);
101
+ }
102
+ /**
103
+ * Insert an import statement after the LAST existing import.
104
+ *
105
+ * Appending to the import block (rather than sorting into it) keeps the edit a
106
+ * pure insertion: no existing line moves, so the diff shows exactly one added
107
+ * line and a reviewer can see the whole change at a glance.
108
+ */
109
+ export function insertImportStatement(content, statement) {
110
+ // Only the STATIC import block is a valid insertion point: landing after a
111
+ // dynamic `import()` deep in the file would put a top-level import in the
112
+ // middle of a function body.
113
+ const statements = parseImportStatements(content).filter((st) => st.kind === 'import');
114
+ const last = statements[statements.length - 1];
115
+ if (!last)
116
+ return undefined;
117
+ const endOfLast = last.index + last.raw.length;
118
+ // Land immediately after the statement's own newline so the insertion never
119
+ // splits a line, whatever trails the specifier.
120
+ const newline = content.indexOf('\n', endOfLast);
121
+ const at = newline === -1 ? content.length : newline + 1;
122
+ const nextContent = content.slice(0, at) + statement + '\n' + content.slice(at);
123
+ return { nextContent, line: lineOf(nextContent, at) };
124
+ }
@@ -1,3 +1,4 @@
1
+ import type { IUnreadFile } from '../util/unread-file.js';
1
2
  export declare const TRACE_SCHEMA: "sharkcraft.trace/v1";
2
3
  /** Default source globs scanned when `trace` is given no `--glob`. */
3
4
  export declare const TRACE_DEFAULT_GLOBS: readonly string[];
@@ -41,6 +42,11 @@ export interface ITraceReport {
41
42
  readonly byRole: Readonly<Record<TraceRole, readonly ITraceSite[]>>;
42
43
  /** Const names found bound to the literal (`const NAME = 'literal'`), if any. */
43
44
  readonly aliases: readonly string[];
45
+ /**
46
+ * Matched files the reader did not read (over the read cap, or unreadable),
47
+ * so a site inside one is missing from `byRole`. Set only when non-empty.
48
+ */
49
+ readonly unread?: readonly IUnreadFile[];
44
50
  }
45
51
  export interface ITraceOptions {
46
52
  /** Override the default source globs. */
@@ -1 +1 @@
1
- {"version":3,"file":"trace-literal.d.ts","sourceRoot":"","sources":["../../src/wiring/trace-literal.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,EAAG,qBAA8B,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAShD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,oBAAY,SAAS;IACnB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,SAAS,cAAc;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,YAAY,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC;IACpE,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AA6ED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,YAAY,CAuFd"}
1
+ {"version":3,"file":"trace-literal.d.ts","sourceRoot":"","sources":["../../src/wiring/trace-literal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D,eAAO,MAAM,YAAY,EAAG,qBAA8B,CAAC;AAE3D,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAShD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,oBAAY,SAAS;IACnB,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,SAAS,cAAc;CACxB;AAED,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,YAAY,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,UAAU,EAAE,CAAC,CAAC,CAAC;IACpE,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AA6ED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,YAAY,CA0Fd"}
@@ -1,4 +1,4 @@
1
- import { readMatchingFiles } from "../util/walk-files.js";
1
+ import { readSelectedFiles } from "../util/read-selected-files.js";
2
2
  export const TRACE_SCHEMA = 'sharkcraft.trace/v1';
3
3
  /** Default source globs scanned when `trace` is given no `--glob`. */
4
4
  export const TRACE_DEFAULT_GLOBS = [
@@ -125,7 +125,9 @@ const ALIAS_NAME = /^[A-Z][\w$]*$/;
125
125
  export function traceLiteral(projectRoot, literal, options = {}) {
126
126
  const globs = options.globs && options.globs.length > 0 ? options.globs : TRACE_DEFAULT_GLOBS;
127
127
  const exclude = new Set(options.excludeDirs ?? []);
128
- const cache = readMatchingFiles(projectRoot, globs, exclude);
128
+ // One glob list (the caller's `--glob`s, or the defaults): a `!` excludes.
129
+ const matched = readSelectedFiles(projectRoot, globs, exclude);
130
+ const cache = matched.files;
129
131
  const sites = [];
130
132
  const aliasNames = new Set();
131
133
  // Exact quoted-literal match: the closing quote immediately follows, so the
@@ -205,5 +207,6 @@ export function traceLiteral(projectRoot, literal, options = {}) {
205
207
  [TraceRole.Reference]: sortSites(byRole[TraceRole.Reference]),
206
208
  },
207
209
  aliases: [...aliasNames].sort(),
210
+ ...(matched.unread.length > 0 ? { unread: matched.unread } : {}),
208
211
  };
209
212
  }
@@ -0,0 +1,12 @@
1
+ import type { IWiringRule } from '@shrkcrft/core';
2
+ import type { ILabeledSource } from '../extract/i-labeled-source.js';
3
+ /**
4
+ * THE enumeration of a wiring rule's sources, each named by its side —
5
+ * `declared`, `registered` (or `registered[i]` when a rule unions several
6
+ * sinks), `chain[i]`. `gates coverage` / `gates explain` (the CLI's
7
+ * `gateRuleLabeledSources`) and the wiring engine's liveness settle
8
+ * (`runWiring`) both read it, so a marker or a dead glob is named by one side
9
+ * label on every surface.
10
+ */
11
+ export declare function wiringLabeledSources(rule: IWiringRule): readonly ILabeledSource[];
12
+ //# sourceMappingURL=wiring-labeled-sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiring-labeled-sources.d.ts","sourceRoot":"","sources":["../../src/wiring/wiring-labeled-sources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,gBAAgB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,cAAc,EAAE,CAWjF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * THE enumeration of a wiring rule's sources, each named by its side —
3
+ * `declared`, `registered` (or `registered[i]` when a rule unions several
4
+ * sinks), `chain[i]`. `gates coverage` / `gates explain` (the CLI's
5
+ * `gateRuleLabeledSources`) and the wiring engine's liveness settle
6
+ * (`runWiring`) both read it, so a marker or a dead glob is named by one side
7
+ * label on every surface.
8
+ */
9
+ export function wiringLabeledSources(rule) {
10
+ const registered = Array.isArray(rule.registered)
11
+ ? rule.registered
12
+ : rule.registered
13
+ ? [rule.registered]
14
+ : [];
15
+ return [
16
+ ...(rule.declared ? [{ label: 'declared', source: rule.declared }] : []),
17
+ ...registered.map((source, i) => ({ label: registered.length > 1 ? `registered[${i}]` : 'registered', source })),
18
+ ...(rule.chain ?? []).map((source, i) => ({ label: `chain[${i}]`, source })),
19
+ ];
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/boundaries",
3
- "version": "0.1.0-alpha.29",
3
+ "version": "0.1.0-alpha.31",
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.29"
46
+ "@shrkcrft/core": "^0.1.0-alpha.31"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public"