@shrkcrft/boundaries 0.1.0-alpha.30 → 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 (226) 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 +8 -7
  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 +30 -0
  35. package/dist/extract/extract-tokens.d.ts.map +1 -1
  36. package/dist/extract/extract-tokens.js +233 -27
  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 +1 -0
  41. package/dist/extract/import-edges.d.ts.map +1 -1
  42. package/dist/extract/import-edges.js +20 -7
  43. package/dist/extract/inspect-source.d.ts +32 -3
  44. package/dist/extract/inspect-source.d.ts.map +1 -1
  45. package/dist/extract/inspect-source.js +50 -7
  46. package/dist/extract/parse-imports.d.ts +34 -12
  47. package/dist/extract/parse-imports.d.ts.map +1 -1
  48. package/dist/extract/parse-imports.js +119 -20
  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.map +1 -1
  56. package/dist/generated/check-provenance.js +3 -1
  57. package/dist/generated/read-regen-tree.d.ts +16 -0
  58. package/dist/generated/read-regen-tree.d.ts.map +1 -0
  59. package/dist/generated/read-regen-tree.js +61 -0
  60. package/dist/generated/scan-generated.d.ts +8 -0
  61. package/dist/generated/scan-generated.d.ts.map +1 -1
  62. package/dist/generated/scan-generated.js +25 -10
  63. package/dist/index.d.ts +48 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +51 -0
  66. package/dist/model/boundary-intended-empty-line.d.ts +10 -0
  67. package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
  68. package/dist/model/boundary-intended-empty-line.js +11 -0
  69. package/dist/model/boundary-markable-list.d.ts +15 -0
  70. package/dist/model/boundary-markable-list.d.ts.map +1 -0
  71. package/dist/model/boundary-markable-list.js +15 -0
  72. package/dist/model/boundary-rule-input-keys.d.ts +11 -0
  73. package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
  74. package/dist/model/boundary-rule-input-keys.js +29 -0
  75. package/dist/model/boundary-rule-input.d.ts +23 -0
  76. package/dist/model/boundary-rule-input.d.ts.map +1 -0
  77. package/dist/model/boundary-rule-input.js +1 -0
  78. package/dist/model/boundary-rule-key-problems.d.ts +11 -0
  79. package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
  80. package/dist/model/boundary-rule-key-problems.js +36 -0
  81. package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
  82. package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
  83. package/dist/model/boundary-rule-marker-problems.js +94 -0
  84. package/dist/model/boundary-rule-scope.d.ts +88 -0
  85. package/dist/model/boundary-rule-scope.d.ts.map +1 -0
  86. package/dist/model/boundary-rule-scope.js +175 -0
  87. package/dist/model/boundary-rule.d.ts +120 -5
  88. package/dist/model/boundary-rule.d.ts.map +1 -1
  89. package/dist/model/boundary-rule.js +148 -1
  90. package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
  91. package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
  92. package/dist/model/boundary-unit-problem-issue.js +16 -0
  93. package/dist/model/normalize-boundary-rule.d.ts +21 -0
  94. package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
  95. package/dist/model/normalize-boundary-rule.js +62 -0
  96. package/dist/policy/evaluate-policy.d.ts +80 -3
  97. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  98. package/dist/policy/evaluate-policy.js +152 -19
  99. package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
  100. package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
  101. package/dist/policy/i-policy-rule-liveness.js +1 -0
  102. package/dist/policy/run-policy.d.ts +1 -1
  103. package/dist/policy/run-policy.d.ts.map +1 -1
  104. package/dist/policy/run-policy.js +171 -21
  105. package/dist/registry/load-boundary-rules.d.ts +29 -1
  106. package/dist/registry/load-boundary-rules.d.ts.map +1 -1
  107. package/dist/registry/load-boundary-rules.js +38 -8
  108. package/dist/scan/glob.d.ts +90 -0
  109. package/dist/scan/glob.d.ts.map +1 -1
  110. package/dist/scan/glob.js +205 -0
  111. package/dist/scan/i-glob-unit-measure.d.ts +22 -0
  112. package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
  113. package/dist/scan/i-glob-unit-measure.js +1 -0
  114. package/dist/scan/import-pattern.d.ts +109 -0
  115. package/dist/scan/import-pattern.d.ts.map +1 -0
  116. package/dist/scan/import-pattern.js +191 -0
  117. package/dist/scan/node-builtin-package-names.d.ts +13 -0
  118. package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
  119. package/dist/scan/node-builtin-package-names.js +25 -0
  120. package/dist/scan/scan-imports.d.ts +36 -3
  121. package/dist/scan/scan-imports.d.ts.map +1 -1
  122. package/dist/scan/scan-imports.js +114 -45
  123. package/dist/util/blank-run-hazard-finding.d.ts +34 -0
  124. package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
  125. package/dist/util/blank-run-hazard-finding.js +1 -0
  126. package/dist/util/blank-run-hazard.d.ts +29 -0
  127. package/dist/util/blank-run-hazard.d.ts.map +1 -0
  128. package/dist/util/blank-run-hazard.js +450 -0
  129. package/dist/util/dead-glob-units.d.ts +46 -0
  130. package/dist/util/dead-glob-units.d.ts.map +1 -0
  131. package/dist/util/dead-glob-units.js +116 -0
  132. package/dist/util/glob-list-liveness-input.d.ts +31 -0
  133. package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
  134. package/dist/util/glob-list-liveness-input.js +65 -0
  135. package/dist/util/i-dead-glob-unit.d.ts +24 -0
  136. package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
  137. package/dist/util/i-dead-glob-unit.js +1 -0
  138. package/dist/util/i-glob-list-units.d.ts +25 -0
  139. package/dist/util/i-glob-list-units.d.ts.map +1 -0
  140. package/dist/util/i-glob-list-units.js +1 -0
  141. package/dist/util/i-glob-liveness-list.d.ts +23 -0
  142. package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
  143. package/dist/util/i-glob-liveness-list.js +1 -0
  144. package/dist/util/i-glob-liveness-request.d.ts +11 -0
  145. package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
  146. package/dist/util/i-glob-liveness-request.js +1 -0
  147. package/dist/util/i-glob-negation.d.ts +13 -0
  148. package/dist/util/i-glob-negation.d.ts.map +1 -0
  149. package/dist/util/i-glob-negation.js +1 -0
  150. package/dist/util/matched-files.d.ts +22 -0
  151. package/dist/util/matched-files.d.ts.map +1 -0
  152. package/dist/util/matched-files.js +1 -0
  153. package/dist/util/negation-cause.d.ts +15 -0
  154. package/dist/util/negation-cause.d.ts.map +1 -0
  155. package/dist/util/negation-cause.js +21 -0
  156. package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
  157. package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
  158. package/dist/util/plane-scan-exclude-dirs.js +22 -0
  159. package/dist/util/read-glob-list-liveness.d.ts +20 -0
  160. package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
  161. package/dist/util/read-glob-list-liveness.js +20 -0
  162. package/dist/util/read-scope-coverage.d.ts +90 -0
  163. package/dist/util/read-scope-coverage.d.ts.map +1 -0
  164. package/dist/util/read-scope-coverage.js +173 -0
  165. package/dist/util/read-scope.d.ts +14 -0
  166. package/dist/util/read-scope.d.ts.map +1 -0
  167. package/dist/util/read-scope.js +1 -0
  168. package/dist/util/read-selected-files.d.ts +16 -0
  169. package/dist/util/read-selected-files.d.ts.map +1 -0
  170. package/dist/util/read-selected-files.js +25 -0
  171. package/dist/util/settle-glob-lists.d.ts +12 -0
  172. package/dist/util/settle-glob-lists.d.ts.map +1 -0
  173. package/dist/util/settle-glob-lists.js +13 -0
  174. package/dist/util/unread-file-reason.d.ts +26 -0
  175. package/dist/util/unread-file-reason.d.ts.map +1 -0
  176. package/dist/util/unread-file-reason.js +26 -0
  177. package/dist/util/unread-file.d.ts +10 -0
  178. package/dist/util/unread-file.d.ts.map +1 -0
  179. package/dist/util/unread-file.js +1 -0
  180. package/dist/util/walk-files.d.ts +74 -10
  181. package/dist/util/walk-files.d.ts.map +1 -1
  182. package/dist/util/walk-files.js +137 -31
  183. package/dist/wiring/evaluate-wiring.d.ts +79 -5
  184. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  185. package/dist/wiring/evaluate-wiring.js +267 -12
  186. package/dist/wiring/explain-wiring.d.ts +49 -5
  187. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  188. package/dist/wiring/explain-wiring.js +118 -10
  189. package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
  190. package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
  191. package/dist/wiring/i-idiom-role-coverage.js +1 -0
  192. package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
  193. package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
  194. package/dist/wiring/i-registration-query-verdict.js +1 -0
  195. package/dist/wiring/i-registration-roles.d.ts +71 -0
  196. package/dist/wiring/i-registration-roles.d.ts.map +1 -0
  197. package/dist/wiring/i-registration-roles.js +1 -0
  198. package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
  199. package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
  200. package/dist/wiring/measure-idiom-role-coverage.js +20 -0
  201. package/dist/wiring/measure-registration-roles.d.ts +25 -0
  202. package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
  203. package/dist/wiring/measure-registration-roles.js +124 -0
  204. package/dist/wiring/plan-wiring-fix.js +2 -2
  205. package/dist/wiring/registration-graph.d.ts +12 -0
  206. package/dist/wiring/registration-graph.d.ts.map +1 -1
  207. package/dist/wiring/registration-graph.js +16 -5
  208. package/dist/wiring/registration-query-verdict.d.ts +23 -0
  209. package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
  210. package/dist/wiring/registration-query-verdict.js +118 -0
  211. package/dist/wiring/registry-query.d.ts +8 -0
  212. package/dist/wiring/registry-query.d.ts.map +1 -1
  213. package/dist/wiring/registry-query.js +12 -5
  214. package/dist/wiring/scan-wiring-files.d.ts +4 -2
  215. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  216. package/dist/wiring/scan-wiring-files.js +52 -15
  217. package/dist/wiring/sink-imports.d.ts +11 -0
  218. package/dist/wiring/sink-imports.d.ts.map +1 -1
  219. package/dist/wiring/sink-imports.js +11 -2
  220. package/dist/wiring/trace-literal.d.ts +6 -0
  221. package/dist/wiring/trace-literal.d.ts.map +1 -1
  222. package/dist/wiring/trace-literal.js +5 -2
  223. package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
  224. package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
  225. package/dist/wiring/wiring-labeled-sources.js +20 -0
  226. package/package.json +2 -2
@@ -1,17 +1,19 @@
1
1
  /**
2
- * The ONE import-statement parser.
2
+ * Which text an import statement may be read from.
3
3
  *
4
- * Two features need to read import statements out of source text: the
5
- * `--fix` planner (does this sink bind the token, and how?) and the
6
- * `import-edges` extractor (which file imports what, from where?). A second
7
- * parser would drift from the first, and the two would then disagree about
8
- * what a file importswhich is precisely the class of silent disagreement
9
- * this engine exists to eliminate. So both consume this.
10
- *
11
- * It is a lexer, not a compiler: it answers what the text says, which is the
12
- * largest thing that stays honest across `.ts`, `.tsx`, `.mts` and plain JS
13
- * with one code path.
4
+ * - `code` (the default): the statement's keyword must start in executable
5
+ * code and its specifier must be a real string literal; comments are
6
+ * blanked before matching, so an apostrophe inside a comment within a
7
+ * multi-line clause can no longer cut a real import short.
8
+ * - `all`: the raw file textevery byte, comments included. The explicit
9
+ * escape hatch (`check boundaries --include-comments`, `scan: 'all'`).
14
10
  */
11
+ export type ImportParseZone = 'code' | 'all';
12
+ /** Options for {@link parseImportStatements}. */
13
+ export interface IParseImportsOptions {
14
+ /** Default `code`. */
15
+ readonly zone?: ImportParseZone;
16
+ }
15
17
  /** How a name entered scope through an import. */
16
18
  export type ImportBindingKind = 'named' | 'default' | 'namespace';
17
19
  /** One name bound by an import statement. */
@@ -53,6 +55,26 @@ export interface IParsedImport {
53
55
  * dependency graph legitimately counts it as an edge. Returning it and letting
54
56
  * each caller decide keeps that judgement at the call site instead of baking
55
57
  * one feature's answer into the shared parser.
58
+ *
59
+ * In the default `code` zone a statement is kept only when its keyword starts
60
+ * in a code zone AND its specifier quote opens a real string literal — the
61
+ * same {@link zoneKeepsAt} authority the policy plane and the extraction DSL
62
+ * use. Offsets, lines and `raw` always refer to the ORIGINAL text (blanking
63
+ * preserves length), so callers that splice `content` stay correct.
64
+ */
65
+ export declare function parseImportStatements(content: string, options?: IParseImportsOptions): IParsedImport[];
66
+ /**
67
+ * {@link parseImportStatements}, plus how many characters the zone BLANKED
68
+ * before matching: the comment characters under `code`, `0` under `all`.
69
+ *
70
+ * The `import-edges` extractor zones itself (a pre-blanked buffer would erase
71
+ * every specifier), so this is the figure it reports as its `blankedChars` —
72
+ * the number `wiring explain` / `gates explain` print as
73
+ * `scan: code (N chars blanked)`. Without it the note claimed a zone removed
74
+ * nothing while it had dropped a commented-out edge.
56
75
  */
57
- export declare function parseImportStatements(content: string): IParsedImport[];
76
+ export declare function parseImportStatementsWithMeta(content: string, options?: IParseImportsOptions): {
77
+ readonly statements: IParsedImport[];
78
+ readonly blankedChars: number;
79
+ };
58
80
  //# sourceMappingURL=parse-imports.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-imports.d.ts","sourceRoot":"","sources":["../../src/extract/parse-imports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,kDAAkD;AAClD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhG,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AA0FD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAgDtE"}
1
+ {"version":3,"file":"parse-imports.d.ts","sourceRoot":"","sources":["../../src/extract/parse-imports.ts"],"names":[],"mappings":"AA8BA;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7C,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,sBAAsB;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,CAAC;CACjC;AAED,kDAAkD;AAClD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhG,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;CACpC;AAsID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,aAAa,EAAE,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC;IAAE,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CA4EzE"}
@@ -11,19 +11,43 @@
11
11
  * It is a lexer, not a compiler: it answers what the text says, which is the
12
12
  * largest thing that stays honest across `.ts`, `.tsx`, `.mts` and plain JS
13
13
  * with one code path.
14
+ *
15
+ * Round 11 (6.1a) made it the ONLY import extractor: `scanImports` (every
16
+ * boundary check, drift, impact, review packet…) and the `import-edges` DSL
17
+ * extractor both read through here, so "what does this file import" has one
18
+ * answer. By default it reads CODE only — a commented-out import, an import in
19
+ * a doc-comment code fence, or `"import x from 'y'"` inside a string is not a
20
+ * dependency. Measured against the TypeScript oracle over this repo (1716
21
+ * files): 79 phantom edges and 1 real miss before, 0 / 0 after.
14
22
  */
23
+ import { blankZoneKinds, lexCodeZones, zoneContaining, zoneKeepsAt, } from "./code-zones.js";
15
24
  /**
16
25
  * Matches one import/re-export statement up to its specifier.
17
26
  *
18
27
  * The clause may span lines (a multi-line `{ … }` block is normal) but never
19
- * contains a `;` or a quote, which is what keeps the lazy match from running
20
- * past the end of its own statement into the next one.
28
+ * contains a `;`, a quote or a BACKTICK, which is what keeps the lazy match
29
+ * from running past the end of its own statement into the next one. No real
30
+ * import clause holds a backtick; prose does — a doc comment's
31
+ * `` `shrk import <format> --populate` `` used to open a clause that ran on to
32
+ * the next real `from '…'`, so the raw reading (`--include-comments`,
33
+ * `scan: 'all'`) filed that import on the comment's line. The old scanImports
34
+ * regex excluded the backtick too; raw-mode lines match it again.
21
35
  *
22
36
  * `export … from '…'` is matched too: a re-export is an edge in the dependency
23
37
  * graph exactly like an import, and a barrel that is invisible here would make
24
38
  * every consumer behind it invisible as well.
39
+ *
40
+ * Linear by construction (round 11, 6.1(b)): the old form
41
+ * `\s+([^;'"]*?)\s*from` put three whitespace-consuming quantifiers side by
42
+ * side, which is fine on raw source and O(run²) once comments are blanked into
43
+ * long runs of spaces (6 / 24 / 94 / 371 ms as a JSDoc block doubled). Here a
44
+ * single `\s` separates the keyword from ONE lazy class, and `\bfrom` needs no
45
+ * whitespace quantifier in front of it, so each keyword costs one forward scan
46
+ * to its statement's end. Verified byte-identical to the old form on the raw
47
+ * text of every file in this repo. Do not reintroduce adjacent `\s*`/`\s+`
48
+ * around a lazy class — `r75-import-scan-zones.test.ts` times it.
25
49
  */
26
- const IMPORT_STATEMENT = /\b(import|export)\s+([^;'"]*?)\s*from\s*['"]([^'"]+)['"]\s*;?/g;
50
+ const IMPORT_STATEMENT = /\b(import|export)\s([^;'"`]*?)\bfrom\s*['"]([^'"]+)['"]\s*;?/g;
27
51
  /**
28
52
  * The specifier-only forms, which bind no name: a side-effect import, a dynamic
29
53
  * `import()`, and `require()`. Each is a real dependency edge — the boundary
@@ -90,14 +114,42 @@ function parseClause(clause) {
90
114
  }
91
115
  return bindings;
92
116
  }
93
- /** 1-based line number of `index` within `content`. */
94
- function lineAt(content, index) {
95
- let line = 1;
96
- for (let i = 0; i < index && i < content.length; i += 1) {
97
- if (content[i] === '\n')
98
- line += 1;
117
+ /** Offsets of every `\n` in `content`, for O(log n) line lookups. */
118
+ function newlineOffsets(content) {
119
+ const out = [];
120
+ for (let i = content.indexOf('\n'); i !== -1; i = content.indexOf('\n', i + 1))
121
+ out.push(i);
122
+ return out;
123
+ }
124
+ /**
125
+ * 1-based line of `index`: one plus the number of newlines BEFORE it. The
126
+ * index is the keyword's own offset — the old `scanImports` regex consumed the
127
+ * preceding `\n` with `(?:^|\s)`, so every import after line 1 was reported
128
+ * one line early (round 11, 6.1a#line-numbers).
129
+ */
130
+ function lineAt(newlines, index) {
131
+ let lo = 0;
132
+ let hi = newlines.length;
133
+ while (lo < hi) {
134
+ const mid = (lo + hi) >> 1;
135
+ if (newlines[mid] < index)
136
+ lo = mid + 1;
137
+ else
138
+ hi = mid;
99
139
  }
100
- return line;
140
+ return lo + 1;
141
+ }
142
+ /** Zones blanked before matching in `code` mode: comments only — the specifier is a string. */
143
+ const COMMENT_ZONES = new Set(['comment']);
144
+ /**
145
+ * Offset (within the match) of the specifier's OPENING quote. Every pattern
146
+ * ends `<quote><specifier><quote>` followed only by whitespace, `;` or `)`,
147
+ * and no clause may contain a quote, so the last quote in the match closes the
148
+ * specifier.
149
+ */
150
+ function specifierQuoteOffset(match, specifier) {
151
+ const closing = Math.max(match.lastIndexOf("'"), match.lastIndexOf('"'));
152
+ return closing - specifier.length - 1;
101
153
  }
102
154
  /**
103
155
  * Every import / re-export statement in `content`, with the names it binds.
@@ -107,22 +159,66 @@ function lineAt(content, index) {
107
159
  * dependency graph legitimately counts it as an edge. Returning it and letting
108
160
  * each caller decide keeps that judgement at the call site instead of baking
109
161
  * one feature's answer into the shared parser.
162
+ *
163
+ * In the default `code` zone a statement is kept only when its keyword starts
164
+ * in a code zone AND its specifier quote opens a real string literal — the
165
+ * same {@link zoneKeepsAt} authority the policy plane and the extraction DSL
166
+ * use. Offsets, lines and `raw` always refer to the ORIGINAL text (blanking
167
+ * preserves length), so callers that splice `content` stay correct.
168
+ */
169
+ export function parseImportStatements(content, options = {}) {
170
+ return parseImportStatementsWithMeta(content, options).statements;
171
+ }
172
+ /**
173
+ * {@link parseImportStatements}, plus how many characters the zone BLANKED
174
+ * before matching: the comment characters under `code`, `0` under `all`.
175
+ *
176
+ * The `import-edges` extractor zones itself (a pre-blanked buffer would erase
177
+ * every specifier), so this is the figure it reports as its `blankedChars` —
178
+ * the number `wiring explain` / `gates explain` print as
179
+ * `scan: code (N chars blanked)`. Without it the note claimed a zone removed
180
+ * nothing while it had dropped a commented-out edge.
110
181
  */
111
- export function parseImportStatements(content) {
182
+ export function parseImportStatementsWithMeta(content, options = {}) {
183
+ const zone = options.zone ?? 'code';
184
+ const zones = zone === 'code' ? lexCodeZones(content) : undefined;
185
+ const blanked = zones ? blankZoneKinds(content, zones, COMMENT_ZONES) : undefined;
186
+ const text = blanked ? blanked.content : content;
187
+ // Built on first use: a file whose statements are all zoned out (or that has
188
+ // none) never needs a line table.
189
+ let newlines;
190
+ const lineOfIndex = (index) => lineAt((newlines ??= newlineOffsets(content)), index);
191
+ const keep = (index, match, specifier) => {
192
+ if (!zones)
193
+ return true;
194
+ const quote = index + specifierQuoteOffset(match, specifier);
195
+ // The specifier's quote must OPEN a plain string literal. "Inside a string"
196
+ // is not enough: `export const t = \`import z from 'w'\`` starts with a
197
+ // code `export`, and its `'w'` sits inside the template literal.
198
+ const specZone = zoneContaining(zones, quote);
199
+ return (zoneKeepsAt('code', zones, index) &&
200
+ zoneKeepsAt('strings', zones, quote) &&
201
+ specZone !== undefined &&
202
+ specZone.start === quote &&
203
+ specZone.template !== true);
204
+ };
112
205
  const out = [];
113
206
  const claimed = [];
114
207
  let m;
115
208
  IMPORT_STATEMENT.lastIndex = 0;
116
- while ((m = IMPORT_STATEMENT.exec(content)) !== null) {
209
+ while ((m = IMPORT_STATEMENT.exec(text)) !== null) {
210
+ const specifier = m[3];
211
+ if (!keep(m.index, m[0], specifier))
212
+ continue;
117
213
  const clause = m[2];
118
214
  const typeOnly = /^type\b/.test(clause.trim());
119
215
  claimed.push({ start: m.index, end: m.index + m[0].length });
120
216
  out.push({
121
- specifier: m[3],
217
+ specifier,
122
218
  bindings: typeOnly ? [] : parseClause(clause),
123
- line: lineAt(content, m.index),
219
+ line: lineOfIndex(m.index),
124
220
  index: m.index,
125
- raw: m[0],
221
+ raw: content.slice(m.index, m.index + m[0].length),
126
222
  typeOnly,
127
223
  kind: m[1] === 'export' ? 'reexport' : 'import',
128
224
  });
@@ -137,19 +233,22 @@ export function parseImportStatements(content) {
137
233
  [REQUIRE_CALL, 'require'],
138
234
  ]) {
139
235
  re.lastIndex = 0;
140
- while ((m = re.exec(content)) !== null) {
236
+ while ((m = re.exec(text)) !== null) {
141
237
  if (overlapsClaimed(m.index))
142
238
  continue;
239
+ const specifier = m[1];
240
+ if (!keep(m.index, m[0], specifier))
241
+ continue;
143
242
  out.push({
144
- specifier: m[1],
243
+ specifier,
145
244
  bindings: [],
146
- line: lineAt(content, m.index),
245
+ line: lineOfIndex(m.index),
147
246
  index: m.index,
148
- raw: m[0],
247
+ raw: content.slice(m.index, m.index + m[0].length),
149
248
  typeOnly: false,
150
249
  kind,
151
250
  });
152
251
  }
153
252
  }
154
- return out.sort((a, b) => a.index - b.index);
253
+ return { statements: out.sort((a, b) => a.index - b.index), blankedChars: blanked?.blankedChars ?? 0 };
155
254
  }
@@ -11,7 +11,18 @@
11
11
  * comment syntax differs from the C/JS family (e.g. `#` comments) is scanned as
12
12
  * plain code.
13
13
  */
14
- /** Index of the closing quote of the string starting at `start` (handles escapes). */
14
+ /**
15
+ * Index of the closing quote of the string starting at `start` (handles escapes).
16
+ *
17
+ * A `'…'` or `"…"` literal ends at an unescaped NEWLINE (JS semantics — only a
18
+ * template literal spans lines): the returned index is then the last character
19
+ * before the newline. Without this a quote the lexer could not recognise as
20
+ * code — the `'` inside a regex literal such as `/[&<>"']/g`, an apostrophe in
21
+ * prose — swallowed the rest of the FILE as one phantom string, hiding every
22
+ * real construct after it (round 11, 6.1a: a real `import()` 60 lines later
23
+ * vanished). Bounding the mis-lex to one line is strictly more accurate for
24
+ * every caller: zone lexing, the policy plane and array extraction alike.
25
+ */
15
26
  export declare function skipString(content: string, start: number): number;
16
27
  /**
17
28
  * If a comment starts at `start`, the index of its LAST character; otherwise
@@ -1 +1 @@
1
- {"version":3,"file":"scan-literals.d.ts","sourceRoot":"","sources":["../../src/extract/scan-literals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA0CD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAgC7E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ7D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsB7D"}
1
+ {"version":3,"file":"scan-literals.d.ts","sourceRoot":"","sources":["../../src/extract/scan-literals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYjE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AA0CD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAgC7E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ7D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAsB7D"}
@@ -11,9 +11,21 @@
11
11
  * comment syntax differs from the C/JS family (e.g. `#` comments) is scanned as
12
12
  * plain code.
13
13
  */
14
- /** Index of the closing quote of the string starting at `start` (handles escapes). */
14
+ /**
15
+ * Index of the closing quote of the string starting at `start` (handles escapes).
16
+ *
17
+ * A `'…'` or `"…"` literal ends at an unescaped NEWLINE (JS semantics — only a
18
+ * template literal spans lines): the returned index is then the last character
19
+ * before the newline. Without this a quote the lexer could not recognise as
20
+ * code — the `'` inside a regex literal such as `/[&<>"']/g`, an apostrophe in
21
+ * prose — swallowed the rest of the FILE as one phantom string, hiding every
22
+ * real construct after it (round 11, 6.1a: a real `import()` 60 lines later
23
+ * vanished). Bounding the mis-lex to one line is strictly more accurate for
24
+ * every caller: zone lexing, the policy plane and array extraction alike.
25
+ */
15
26
  export function skipString(content, start) {
16
27
  const quote = content[start];
28
+ const singleLine = quote === "'" || quote === '"';
17
29
  for (let i = start + 1; i < content.length; i += 1) {
18
30
  if (content[i] === '\\') {
19
31
  i += 1;
@@ -21,6 +33,8 @@ export function skipString(content, start) {
21
33
  }
22
34
  if (content[i] === quote)
23
35
  return i;
36
+ if (singleLine && content[i] === '\n')
37
+ return i - 1;
24
38
  }
25
39
  return content.length - 1;
26
40
  }
@@ -0,0 +1,24 @@
1
+ import type { IGlobListUnits } from '../util/i-glob-list-units.js';
2
+ import type { IGlobLivenessRequest } from '../util/i-glob-liveness-request.js';
3
+ import type { ILabeledSource } from './i-labeled-source.js';
4
+ /**
5
+ * THE glob lists of a rule's extraction sources as the gate-plane liveness
6
+ * settle reads them (round 13) — every source's `files` and, NEW this round,
7
+ * an `import-edges` source's `to.files` (judged for liveness, so a typo'd fence
8
+ * target is no longer a silent pass, and markable, so a fence to a planned
9
+ * subtree is sayable).
10
+ *
11
+ * Each list is qualified by its side (`declared.files`, `compute.source.to.files`
12
+ * — `qualifyListPath`), and each source's markers the same way
13
+ * (`qualifyUnitMarks`), so one settle spans a rule's every source without two
14
+ * sides' `files` colliding. In a multi-source rule a unit prints `<side>: <glob>`
15
+ * (today's dead-glob selector); `to.files` units print `[<side> ]to.files: <glob>`.
16
+ *
17
+ * `gates coverage` (`buildGateCoverage`), the wiring engine (`runWiring`) and
18
+ * the registration role measurement all build their request here, so the one
19
+ * rule's units read identically on every surface. `unitsOf` lets a caller
20
+ * judge a `files` list off a walk it already did; `to.files` matches resolved
21
+ * import targets, so it is judged off the one reader's own walk of its globs.
22
+ */
23
+ export declare function sourceLivenessRequest(projectRoot: string, sources: readonly ILabeledSource[], excludeDirs?: readonly string[], subject?: string, unitsOf?: (globs: readonly string[]) => IGlobListUnits): IGlobLivenessRequest;
24
+ //# sourceMappingURL=source-liveness-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-liveness-request.d.ts","sourceRoot":"","sources":["../../src/extract/source-liveness-request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,WAAW,GAAE,SAAS,MAAM,EAAO,EACnC,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,cAAc,GACrD,oBAAoB,CAuBtB"}
@@ -0,0 +1,45 @@
1
+ import { qualifyListPath, qualifyUnitMarks } from '@shrkcrft/core';
2
+ import { readGlobListUnits } from "../util/dead-glob-units.js";
3
+ /**
4
+ * THE glob lists of a rule's extraction sources as the gate-plane liveness
5
+ * settle reads them (round 13) — every source's `files` and, NEW this round,
6
+ * an `import-edges` source's `to.files` (judged for liveness, so a typo'd fence
7
+ * target is no longer a silent pass, and markable, so a fence to a planned
8
+ * subtree is sayable).
9
+ *
10
+ * Each list is qualified by its side (`declared.files`, `compute.source.to.files`
11
+ * — `qualifyListPath`), and each source's markers the same way
12
+ * (`qualifyUnitMarks`), so one settle spans a rule's every source without two
13
+ * sides' `files` colliding. In a multi-source rule a unit prints `<side>: <glob>`
14
+ * (today's dead-glob selector); `to.files` units print `[<side> ]to.files: <glob>`.
15
+ *
16
+ * `gates coverage` (`buildGateCoverage`), the wiring engine (`runWiring`) and
17
+ * the registration role measurement all build their request here, so the one
18
+ * rule's units read identically on every surface. `unitsOf` lets a caller
19
+ * judge a `files` list off a walk it already did; `to.files` matches resolved
20
+ * import targets, so it is judged off the one reader's own walk of its globs.
21
+ */
22
+ export function sourceLivenessRequest(projectRoot, sources, excludeDirs = [], subject, unitsOf) {
23
+ const multi = sources.length > 1;
24
+ const exclude = new Set(excludeDirs);
25
+ const judge = unitsOf ?? ((globs) => readGlobListUnits(projectRoot, globs, exclude));
26
+ const lists = [];
27
+ const marks = [];
28
+ for (const { label, source } of sources) {
29
+ const files = source.files ?? [];
30
+ if (files.length > 0) {
31
+ lists.push({ list: qualifyListPath(label, 'files'), ...(multi ? { label } : {}), globs: files, units: judge(files) });
32
+ }
33
+ const to = source.to?.files ?? [];
34
+ if (to.length > 0) {
35
+ lists.push({
36
+ list: qualifyListPath(label, 'to.files'),
37
+ label: multi ? `${label} to.files` : 'to.files',
38
+ globs: to,
39
+ units: readGlobListUnits(projectRoot, to, exclude),
40
+ });
41
+ }
42
+ marks.push(...qualifyUnitMarks(source.expectEmptyUnits ?? [], label));
43
+ }
44
+ return { ...(subject !== undefined ? { subject } : {}), lists, marks };
45
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"check-provenance.d.ts","sourceRoot":"","sources":["../../src/generated/check-provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,0CAA0C;AAC1C,MAAM,MAAM,qBAAqB;AAC/B,qEAAqE;AACnE,gBAAgB;AAClB,mFAAmF;GACjF,YAAY;AACd,qEAAqE;GACnE,kBAAkB;AACpB,0EAA0E;GACxE,cAAc;AAChB,4DAA4D;GAC1D,uBAAuB,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAO7E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,cAAc,GAAE,uBAA4B,GAC3C;IAAE,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgF7D"}
1
+ {"version":3,"file":"check-provenance.d.ts","sourceRoot":"","sources":["../../src/generated/check-provenance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAI7D,0CAA0C;AAC1C,MAAM,MAAM,qBAAqB;AAC/B,qEAAqE;AACnE,gBAAgB;AAClB,mFAAmF;GACjF,YAAY;AACd,qEAAqE;GACnE,kBAAkB;AACpB,0EAA0E;GACxE,cAAc;AAChB,4DAA4D;GAC1D,uBAAuB,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQ7E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACtC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,cAAc,GAAE,uBAA4B,GAC3C;IAAE,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgF7D"}
@@ -1,3 +1,4 @@
1
+ import { globListWalkGlobs } from "../scan/glob.js";
1
2
  import { safeCompile } from "../util/safe-regex.js";
2
3
  /** The head of a file, as the header contract sees it. */
3
4
  function headOf(content, withinLines) {
@@ -11,7 +12,8 @@ function headOf(content, withinLines) {
11
12
  */
12
13
  export function deriveOutsideGlobs(generatedGlob) {
13
14
  const exts = new Set();
14
- for (const g of generatedGlob) {
15
+ // Inclusion globs only: a `!gen/**/*.hand.ts` names no extension to search.
16
+ for (const g of globListWalkGlobs(generatedGlob)) {
15
17
  const m = /\.([A-Za-z0-9]+)$/.exec(g);
16
18
  if (m)
17
19
  exts.add(m[1]);
@@ -0,0 +1,16 @@
1
+ import type { IMatchedFiles } from '../util/matched-files.js';
2
+ /**
3
+ * Read a REGENERATED temp tree (what a `generatedArtifacts[].regen` wrote into
4
+ * `{TMP}`): every file under `root`, keyed by POSIX path relative to it.
5
+ *
6
+ * Same contract as the one reader (`readMatchingFiles`): a file it did not read
7
+ * (over {@link MAX_REGEN_FILE_BYTES}, or unreadable) is returned in `unread`
8
+ * with its reason, never dropped. It used to be dropped, so a regen that wrote
9
+ * a NEW 2.1MB file read "byte-identical to a fresh regen ✓" while the same
10
+ * file at 100 bytes failed as "regen produces it but it is not committed".
11
+ * The caller decides what an unread output means: one keyed onto a committed
12
+ * file cannot be byte-compared (a coverage gap), one keyed onto nothing
13
+ * committed is drift its path alone proves.
14
+ */
15
+ export declare function readRegenTree(root: string): IMatchedFiles;
16
+ //# sourceMappingURL=read-regen-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-regen-tree.d.ts","sourceRoot":"","sources":["../../src/generated/read-regen-tree.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAuCzD"}
@@ -0,0 +1,61 @@
1
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import * as nodePath from 'node:path';
3
+ import { UnreadFileReason } from "../util/unread-file-reason.js";
4
+ import { MAX_REGEN_FILE_BYTES } from "../util/walk-files.js";
5
+ /**
6
+ * Read a REGENERATED temp tree (what a `generatedArtifacts[].regen` wrote into
7
+ * `{TMP}`): every file under `root`, keyed by POSIX path relative to it.
8
+ *
9
+ * Same contract as the one reader (`readMatchingFiles`): a file it did not read
10
+ * (over {@link MAX_REGEN_FILE_BYTES}, or unreadable) is returned in `unread`
11
+ * with its reason, never dropped. It used to be dropped, so a regen that wrote
12
+ * a NEW 2.1MB file read "byte-identical to a fresh regen ✓" while the same
13
+ * file at 100 bytes failed as "regen produces it but it is not committed".
14
+ * The caller decides what an unread output means: one keyed onto a committed
15
+ * file cannot be byte-compared (a coverage gap), one keyed onto nothing
16
+ * committed is drift its path alone proves.
17
+ */
18
+ export function readRegenTree(root) {
19
+ const files = new Map();
20
+ const unread = [];
21
+ const visit = (abs) => {
22
+ let entries;
23
+ try {
24
+ entries = readdirSync(abs, { withFileTypes: true });
25
+ }
26
+ catch {
27
+ return;
28
+ }
29
+ for (const e of entries) {
30
+ const child = nodePath.join(abs, e.name);
31
+ if (e.isDirectory()) {
32
+ visit(child);
33
+ continue;
34
+ }
35
+ if (!e.isFile())
36
+ continue;
37
+ const rel = nodePath.relative(root, child).split(nodePath.sep).join('/');
38
+ let size;
39
+ try {
40
+ size = statSync(child).size;
41
+ }
42
+ catch {
43
+ unread.push({ path: rel, reason: UnreadFileReason.Unreadable });
44
+ continue;
45
+ }
46
+ if (size > MAX_REGEN_FILE_BYTES) {
47
+ unread.push({ path: rel, reason: UnreadFileReason.OverRegenCap, bytes: size });
48
+ continue;
49
+ }
50
+ try {
51
+ files.set(rel, readFileSync(child, 'utf8'));
52
+ }
53
+ catch {
54
+ unread.push({ path: rel, reason: UnreadFileReason.Unreadable, bytes: size });
55
+ }
56
+ }
57
+ };
58
+ visit(root);
59
+ unread.sort((a, b) => a.path.localeCompare(b.path));
60
+ return { files, unread: Object.freeze(unread) };
61
+ }
@@ -1,4 +1,5 @@
1
1
  import type { IGeneratedArtifactRule } from '@shrkcrft/core';
2
+ import type { IReadScope } from '../util/read-scope.js';
2
3
  /** One writer's slice of a mixed generated tree. */
3
4
  export interface IGeneratedSourceSlice {
4
5
  /** Label for output — the writer's `id`, or `source[<index>]`. */
@@ -43,6 +44,13 @@ export interface IGeneratedScan {
43
44
  readonly outside: ReadonlyMap<string, string>;
44
45
  /** Globs actually used for the outside scan (derived or configured). */
45
46
  readonly outsideGlobs: readonly string[];
47
+ /**
48
+ * The rule's read scope: every file read (committed generated files, plus
49
+ * the outside candidates when `forbidOutside` is on), and every matched file
50
+ * the reader did NOT read (over the read cap, or unreadable). An unread
51
+ * committed file was never header-checked or byte-compared.
52
+ */
53
+ readonly readScope: IReadScope;
46
54
  }
47
55
  /**
48
56
  * Read + CLASSIFY the committed side of a generated-artifact rule.
@@ -1 +1 @@
1
- {"version":3,"file":"scan-generated.d.ts","sourceRoot":"","sources":["../../src/generated/scan-generated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAM7D,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,sBAAsB,EAC5B,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,cAAc,CAiGhB"}
1
+ {"version":3,"file":"scan-generated.d.ts","sourceRoot":"","sources":["../../src/generated/scan-generated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAIxD,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,gFAAgF;IAChF,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;CAChC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,sBAAsB,EAC5B,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,cAAc,CAkHhB"}
@@ -1,6 +1,7 @@
1
- import { matchesAny } from "../scan/glob.js";
1
+ import { globListSelects } from "../scan/glob.js";
2
2
  import { safeCompile } from "../util/safe-regex.js";
3
- import { readMatchingFiles } from "../util/walk-files.js";
3
+ import { readSelectedFiles } from "../util/read-selected-files.js";
4
+ import { mergeReadScopes, unreadEntryMatches, unreadEntryWhollyMatches } from "../util/read-scope-coverage.js";
4
5
  import { deriveOutsideGlobs } from "./check-provenance.js";
5
6
  /**
6
7
  * Read + CLASSIFY the committed side of a generated-artifact rule.
@@ -19,7 +20,10 @@ import { deriveOutsideGlobs } from "./check-provenance.js";
19
20
  */
20
21
  export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
21
22
  const exclude = new Set(excludeDirs);
22
- const generated = readMatchingFiles(projectRoot, rule.generatedGlob, exclude);
23
+ // The files `generatedGlob` SELECTS: a `!` entry (`!gen/**/*.hand.ts`)
24
+ // excludes, so a hand file carved out of the tree is never header-checked.
25
+ const generatedRead = readSelectedFiles(projectRoot, rule.generatedGlob, exclude);
26
+ const generated = generatedRead.files;
23
27
  const blessPatterns = rule.handMaintained ?? [];
24
28
  // The in-file marker is read from the same head window the header contract
25
29
  // uses, so "the top of the file" means one thing for both classifications.
@@ -37,7 +41,7 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
37
41
  const markedHandMaintained = [];
38
42
  const checkable = new Map();
39
43
  for (const [path, content] of generated) {
40
- if (blessPatterns.length > 0 && matchesAny(path, blessPatterns)) {
44
+ if (blessPatterns.length > 0 && globListSelects(path, blessPatterns)) {
41
45
  handMaintained.push(path);
42
46
  continue;
43
47
  }
@@ -52,13 +56,17 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
52
56
  markedHandMaintained.sort();
53
57
  // A bless is pinned to a NAMED file (the schema forbids a wildcard basename),
54
58
  // so one that matches nothing points at a file that has been renamed away.
59
+ // A file over the read cap was still MATCHED, so a bless naming it is live.
60
+ // So is one that could match beneath a directory the walk could not list.
61
+ const matchedPaths = [...generated.keys()];
55
62
  const staleHandMaintained = blessPatterns
56
- .filter((pattern) => ![...generated.keys()].some((p) => matchesAny(p, [pattern])))
63
+ .filter((pattern) => !matchedPaths.some((p) => globListSelects(p, [pattern])) &&
64
+ !generatedRead.unread.some((u) => unreadEntryMatches(u, [pattern])))
57
65
  .sort();
58
66
  const slices = (rule.sources ?? []).map((src, i) => {
59
67
  const files = new Map();
60
68
  for (const [path, content] of checkable) {
61
- if (matchesAny(path, src.glob))
69
+ if (globListSelects(path, src.glob))
62
70
  files.set(path, content);
63
71
  }
64
72
  return {
@@ -73,7 +81,7 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
73
81
  // everything its glob matched, so nothing there can be unclassified.
74
82
  const unclassified = slices.length === 0
75
83
  ? []
76
- : [...checkable.keys()].filter((p) => !slices.some((s) => matchesAny(p, s.glob))).sort();
84
+ : [...checkable.keys()].filter((p) => !slices.some((s) => globListSelects(p, s.glob))).sort();
77
85
  const wantOutside = rule.provenanceHeader?.forbidOutside === true;
78
86
  if (!wantOutside) {
79
87
  return {
@@ -86,18 +94,24 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
86
94
  slices,
87
95
  outside: new Map(),
88
96
  outsideGlobs: [],
97
+ readScope: { read: generated.size, unread: generatedRead.unread },
89
98
  };
90
99
  }
91
100
  const outsideGlobs = rule.provenanceHeader?.outsideGlob && rule.provenanceHeader.outsideGlob.length > 0
92
101
  ? [...rule.provenanceHeader.outsideGlob]
93
102
  : deriveOutsideGlobs(rule.generatedGlob);
94
- const all = readMatchingFiles(projectRoot, outsideGlobs, exclude);
103
+ const all = readSelectedFiles(projectRoot, outsideGlobs, exclude);
95
104
  const outside = new Map();
96
- for (const [path, content] of all) {
97
- if (matchesAny(path, rule.generatedGlob))
105
+ for (const [path, content] of all.files) {
106
+ // A file `generatedGlob` excludes is OUTSIDE the generated tree: carrying
107
+ // the "do not edit" header there is exactly a mislabel.
108
+ if (globListSelects(path, rule.generatedGlob))
98
109
  continue;
99
110
  outside.set(path, content);
100
111
  }
112
+ // An unlistable directory is "outside" unless the generated glob covers ALL
113
+ // of it (it may hold both); `mergeReadScopes` counts a shared path once.
114
+ const outsideUnread = all.unread.filter((u) => !unreadEntryWhollyMatches(u, rule.generatedGlob));
101
115
  return {
102
116
  generated,
103
117
  checkable,
@@ -108,5 +122,6 @@ export function scanGeneratedFiles(projectRoot, rule, excludeDirs = []) {
108
122
  slices,
109
123
  outside,
110
124
  outsideGlobs,
125
+ readScope: mergeReadScopes(generated.size + outside.size, [generatedRead.unread, outsideUnread]),
111
126
  };
112
127
  }