@starci/eslint-canon-be 3.0.9 → 3.1.0

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 (151) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/LICENSE +21 -0
  3. package/README.md +2 -1
  4. package/builders.mjs +1 -1
  5. package/comments.mjs +12 -21
  6. package/config-owner.mjs +1 -1
  7. package/idempotency.mjs +54 -5
  8. package/index.mjs +2 -0
  9. package/lib/config.mjs +4 -1
  10. package/lib/doors.mjs +3 -1
  11. package/module-shape.mjs +1 -2
  12. package/package.json +3 -2
  13. package/resilience.mjs +1 -1
  14. package/runtime/config.example.yaml +187 -0
  15. package/runtime/engine/by-code-unit.mjs +12 -0
  16. package/runtime/engine/config.mjs +247 -273
  17. package/runtime/engine/invalid-config.mjs +5 -5
  18. package/runtime/engine/model-config.mjs +90 -0
  19. package/runtime/engine/orca-config.mjs +3 -1
  20. package/runtime/engine/removed-vocabulary.mjs +28 -0
  21. package/runtime/engine/resources-config.mjs +52 -0
  22. package/runtime/engine/runtime-root.mjs +17 -0
  23. package/runtime/engine/secrets.mjs +134 -0
  24. package/runtime/engine/sonar-config.mjs +21 -0
  25. package/runtime/engine/temp-root.mjs +32 -0
  26. package/runtime/knowledge/hfs/canon-pins.yaml +18 -18
  27. package/runtime/knowledge/hfs/rules.yaml +102 -69
  28. package/runtime/knowledge/hfs/slots.yaml +11 -14
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  30. package/runtime/modules/models/registry.yaml +25 -397
  31. package/runtime/modules/models/runtimes.yaml +92 -262
  32. package/runtime/modules/models/tiers.yaml +65 -0
  33. package/runtime/scripts/api/git/lib.mjs +36 -1
  34. package/runtime/scripts/hfs/allows.mjs +3 -3
  35. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  36. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  37. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  38. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  39. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  40. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  41. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  42. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  43. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  44. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  45. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  46. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  47. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  48. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  49. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  50. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  51. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  52. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  53. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  54. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  55. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  56. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  57. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  58. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  59. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  60. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +3 -2
  61. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  62. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  63. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  64. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  65. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  66. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  67. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  68. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  69. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  70. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  71. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  72. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  73. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  74. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  75. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  76. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  77. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  78. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  79. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  80. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  81. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  82. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  83. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  84. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  85. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  86. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  87. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  88. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  89. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  90. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  91. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  92. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  93. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  94. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  95. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  96. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  97. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  98. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  99. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  100. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  101. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  102. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  103. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  104. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  105. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  106. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  107. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  108. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  109. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  110. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  111. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  112. package/runtime/scripts/hfs/project-graph.mjs +4 -1
  113. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  114. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  115. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  116. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  117. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  118. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  119. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  120. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  121. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  122. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  123. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  124. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  125. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  126. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  127. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  128. package/runtime/scripts/hfs/slots.mjs +72 -693
  129. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  130. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  131. package/runtime/scripts/hfs/view.mjs +1 -1
  132. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  133. package/runtime/scripts/lib/git.mjs +2 -2
  134. package/runtime/scripts/lib/i18n.mjs +3 -2
  135. package/runtime/scripts/lib/jsdoc.mjs +35 -0
  136. package/runtime/scripts/lib/language.mjs +3 -3
  137. package/runtime/scripts/lib/list.mjs +8 -1
  138. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  139. package/runtime/scripts/lib/path-key.mjs +42 -9
  140. package/runtime/scripts/lib/regex.mjs +1 -1
  141. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  142. package/runtime/scripts/lib/sonar-syntax-rules.mjs +225 -0
  143. package/runtime/scripts/lib/sops-envelope.mjs +103 -2
  144. package/runtime/scripts/lib/stack-services.mjs +1 -2
  145. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  146. package/runtime/scripts/lib/walk.mjs +6 -1
  147. package/schema-authority.mjs +1 -1
  148. package/service-deps.mjs +1 -1
  149. package/sonar-parity.mjs +19 -0
  150. package/suppression.mjs +2 -6
  151. package/test-world.mjs +2 -10
@@ -36,9 +36,9 @@ export function slotAdmitsFile(resolver, file) {
36
36
  if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
37
37
  const slot = resolver.slot(classified.slot);
38
38
  const relative = relativeToRoot(classified.path, classified.root);
39
- const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).find(matcher => matcher.expression.test(relative));
40
- const forbidden = matchersOf(slot.forbids, classified.bindings).find(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
41
- return Boolean(hit) && !forbidden;
39
+ const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).some(matcher => matcher.expression.test(relative));
40
+ const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
41
+ return hit && !forbidden;
42
42
  }
43
43
 
44
44
  /**
@@ -1,19 +1,20 @@
1
1
  // ast-walks.mjs - AST walks the architecture checkers share (moved out of typescript.mjs, which keeps the program loading and module resolution).
2
2
  import { canonical, isInside } from './config.mjs';
3
- import { sourceLocation } from '../../lib/ts-ast.mjs';
3
+ import { hasAnyFlag, isConstVariable, sourceLocation } from '../../lib/ts-ast.mjs';
4
4
  import { UNPROVEN_FRAMEWORK, isUnshadowedCommonJsRequire, relativePath, unwrapExpression } from './typescript.mjs';
5
5
 
6
6
  /** `expression` unwrapped by the caller's own predicate set (a predicate absent on an older ts reads as never). */
7
7
  export function unwrapEach(ts, expression, predicates) {
8
- while (expression && predicates.some((predicate) => predicate?.(expression))) expression = expression.expression;
9
- return expression;
8
+ let current = expression;
9
+ while (current && predicates.some((predicate) => predicate?.(current))) current = current.expression;
10
+ return current;
10
11
  }
11
12
 
12
13
  /** `value` with every TS alias hop resolved (a re-exported import reads as its target symbol). */
13
- export function normalizedSymbolValue(ts, checker, value) {
14
- let symbol = value ?? null;
14
+ export function normalizedSymbolValue(ts, checker, value = null) {
15
+ let symbol = value;
15
16
  const seen = new Set();
16
- while (symbol && (symbol.flags & ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
+ while (symbol && hasAnyFlag(symbol.flags, ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
18
  seen.add(symbol);
18
19
  const target = checker.getAliasedSymbol(symbol);
19
20
  if (!target || target === symbol) break;
@@ -48,7 +49,7 @@ export function valueSymbol(ts, checker, node, seen = new Set(), { unselected =
48
49
  seen.add(symbol);
49
50
  const declarations = symbol.getDeclarations?.() ?? [];
50
51
  if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
51
- && (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) {
52
+ && isConstVariable(ts, declarations[0])) {
52
53
  return valueSymbol(ts, checker, declarations[0].initializer, seen, { unselected });
53
54
  }
54
55
  return symbol;
@@ -102,50 +103,59 @@ export function anyDescendant(ts, node, match) {
102
103
  * selected node's symbols to a kind (default `targets.get(symbol)`); `shorthandOf` (when set) also resolves
103
104
  * shorthand-property declarations and descends property assignments; `newExpression` descends `new X()` callees.
104
105
  */
106
+ const addKinds = (target, source) => { for (const kind of source) target.add(kind); };
107
+
108
+ const kindsFromReturns = (ts, checker, declaration, targets, options) => {
109
+ const kinds = new Set();
110
+ for (const returned of returnedExpressions(ts, declaration)) addKinds(kinds, tracedFrameworkKinds(ts, checker, returned, targets, options));
111
+ return kinds;
112
+ };
113
+
114
+ const kindsFromDeclaration = (ts, checker, declaration, targets, options, shorthandOf) => {
115
+ const kinds = new Set();
116
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer)
117
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
118
+ if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
119
+ const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
120
+ const kind = shorthandOf(ts, checker, target, targets);
121
+ if (kind) kinds.add(kind);
122
+ }
123
+ if (shorthandOf && ts.isPropertyAssignment(declaration))
124
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
125
+ addKinds(kinds, kindsFromReturns(ts, checker, declaration, targets, options));
126
+ return kinds;
127
+ };
128
+
129
+ const kindsFromSymbol = (ts, checker, symbol, targets, seen, options) => {
130
+ const nextSeen = new Set(seen).add(symbol);
131
+ const declarationOptions = { ...options, seen: nextSeen };
132
+ const kinds = new Set();
133
+ for (const declaration of symbol.getDeclarations?.() ?? [])
134
+ addKinds(kinds, kindsFromDeclaration(ts, checker, declaration, targets, declarationOptions, options.shorthandOf));
135
+ return kinds;
136
+ };
137
+
138
+ const defaultDirectOf = (ts, checker, { symbol, targets }) => targets.get(symbol) ?? null;
139
+
105
140
  export function tracedFrameworkKinds(ts, checker, expression, targets,
106
141
  { seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
107
142
  if (!expression) return new Set();
108
143
  if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
109
- expression = unwrapExpression(ts, expression);
110
- const selected = selectedNode(ts, expression);
144
+ const unwrapped = unwrapExpression(ts, expression);
145
+ const selected = selectedNode(ts, unwrapped);
111
146
  const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
112
- const direct = symbol ? (directOf ?? ((ts2, c, { symbol: s, targets: t }) => t.get(s) ?? null))(ts, checker, { symbol, selected, targets }) : null;
147
+ const direct = symbol ? (directOf ?? defaultDirectOf)(ts, checker, { symbol, selected, targets }) : null;
113
148
  if (direct) return new Set([direct]);
114
- const opts = { depth: depth + 1, directOf, shorthandOf, newExpression };
115
- if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
116
- const kinds = new Set();
117
- for (const returned of returnedExpressions(ts, expression)) {
118
- for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen })) kinds.add(kind);
119
- }
120
- return kinds;
121
- }
122
- if (ts.isCallExpression(expression)) {
123
- const kinds = tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
149
+ const options = { depth: depth + 1, directOf, shorthandOf, newExpression };
150
+ if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) return kindsFromReturns(ts, checker, unwrapped, targets, { ...options, seen });
151
+ if (ts.isCallExpression(unwrapped)) {
152
+ const kinds = tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
124
153
  if (kinds.size) return kinds;
125
154
  }
126
- if (newExpression && ts.isNewExpression(expression)) {
127
- return tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
128
- }
155
+ if (newExpression && ts.isNewExpression(unwrapped))
156
+ return tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
129
157
  if (!symbol || seen.has(symbol)) return new Set();
130
- const nextSeen = new Set(seen).add(symbol);
131
- const kinds = new Set();
132
- for (const declaration of symbol.getDeclarations?.() ?? []) {
133
- if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
134
- for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
135
- }
136
- if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
137
- const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
138
- const kind = shorthandOf(ts, checker, target, targets);
139
- if (kind) kinds.add(kind);
140
- }
141
- if (shorthandOf && ts.isPropertyAssignment(declaration)) {
142
- for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
143
- }
144
- for (const returned of returnedExpressions(ts, declaration)) {
145
- for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
146
- }
147
- }
148
- return kinds;
158
+ return kindsFromSymbol(ts, checker, symbol, targets, seen, options);
149
159
  }
150
160
 
151
161
  /** 'unproven framework' | 'multiple framework' | the one kind | null, for a decorator whose expression constructs. */
@@ -153,7 +163,8 @@ export function constructedDecoratorKind(ts, checker, decorator, targets, traceO
153
163
  const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
154
164
  const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
155
165
  if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
156
- return kinds.size === 1 ? [...kinds][0] : kinds.size ? 'multiple framework' : null;
166
+ if (kinds.size === 1) return [...kinds][0];
167
+ return kinds.size ? 'multiple framework' : null;
157
168
  }
158
169
 
159
170
  /** The target a decorator resolves to through a `let`/`var` alias's single initializer, or null. */
@@ -163,7 +174,7 @@ export function mutableDecoratorKind(ts, checker, decorator, targets) {
163
174
  const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
164
175
  const declarations = symbol?.getDeclarations?.() ?? [];
165
176
  if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
166
- || (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) return null;
177
+ || isConstVariable(ts, declarations[0])) return null;
167
178
  return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
168
179
  }
169
180
 
@@ -178,11 +189,12 @@ export function decoratorCallee(ts, decorator) {
178
189
  * not resolve. Null when the statement names no static specifier.
179
190
  */
180
191
  export function moduleExportsOf(ts, checker, statement) {
181
- const moduleSpecifier = (ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
182
- && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier) ? statement.moduleSpecifier : null;
183
- const importEquals = ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
184
- && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)
185
- ? statement.moduleReference.expression : null;
192
+ let moduleSpecifier = null;
193
+ if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
194
+ && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) moduleSpecifier = statement.moduleSpecifier;
195
+ let importEquals = null;
196
+ if (ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
197
+ && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)) importEquals = statement.moduleReference.expression;
186
198
  const specifierNode = moduleSpecifier ?? importEquals;
187
199
  const specifier = specifierNode?.text;
188
200
  if (specifier == null) return null;
@@ -208,7 +220,8 @@ export function programSourcesOf(context, checker, localFiles, { root = null } =
208
220
  * The CommonJS require() caveat every framework scan reports: each `require('<specifier>')` `accepted` pushes
209
221
  * `${filePath} uses a CommonJS <label ?? specifier> <detail>` (the import-shape reasoning cannot prove that binding).
210
222
  */
211
- export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail, label = null) {
223
+ export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail) {
224
+ const label = arguments[7] ?? null;
212
225
  const visit = node => {
213
226
  if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)
214
227
  && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]) && accepted(node.arguments[0].text)) {
@@ -0,0 +1,96 @@
1
+ import { readTextFile } from '../../lib/read-text.mjs';
2
+
3
+ // Owner ruling 2026-09-29 (layout 2026-09-30): integration, e2e and contract run MANUALLY only. No hook, default typecheck,
4
+ // coverage run or automatic CI trigger may include those trees or run those projects. Linting the e2e files is not running them: ESLint reads them
5
+ // as syntax in the one repository-wide lint run (the factory's e2e block), so no `lint:e2e` command exists to judge.
6
+ const E2E_COMMAND = new RegExp([
7
+ String.raw`\btest:(?:e2e|integration|contract)\b|`,
8
+ String.raw`\btypecheck:tests\b|`,
9
+ String.raw`--selectProjects\s+(?:e2e|integration|contract)\b|`,
10
+ String.raw`src\/tests\/(?:world|integration|e2e|contract)\b|`,
11
+ String.raw`jest[^\n|&;]*(?:e2e|integration|contract)`,
12
+ ].join(''), 'u');
13
+ const UNIT_RUN_SCRIPTS = ['test', 'test:unit', 'test:ci', 'test:affected', 'test:coverage', 'test:cov'];
14
+
15
+ const runsE2e = text => E2E_COMMAND.test(String(text).replace(/--ignore-pattern[= ]+(?:"[^"]*"|'[^']*'|\S+)/gu, ''));
16
+ const withoutComments = text => text.split('\n').filter(line => !/^\s*#/u.test(line)).join('\n');
17
+
18
+ function inspectHooks(state) {
19
+ for (const hook of ['.husky/pre-commit', '.husky/pre-push']) {
20
+ const text = readTextFile(state.root, hook);
21
+ if (text === null) continue;
22
+ const body = withoutComments(text);
23
+ if (runsE2e(body)) state.finding(state.rule, hook, `${hook} runs integration, e2e or contract. They are manual only; hooks run unit, lint and typecheck.`);
24
+ for (const match of body.matchAll(/npm\s+run\s+([\w:.-]+)/gu)) state.pending.push(match[1]);
25
+ }
26
+ }
27
+
28
+ function inspectLintStaged(state) {
29
+ const lintStaged = state.pkg?.['lint-staged'];
30
+ if (lintStaged && runsE2e(Object.values(lintStaged).flat().join('\n')))
31
+ state.finding(state.rule, 'package.json', 'lint-staged runs an integration, e2e or contract command. They are manual only.');
32
+ }
33
+
34
+ function inspectHookScripts(state) {
35
+ for (const name of state.pending) {
36
+ if (state.called.has(name)) continue;
37
+ state.called.add(name);
38
+ const command = state.scripts[name];
39
+ if (typeof command !== 'string') continue;
40
+ if (runsE2e(command)) state.finding(state.rule, 'package.json', `Script ${name} is run by a husky hook and touches integration, e2e or contract. They are manual only.`);
41
+ for (const match of command.matchAll(/npm\s+run\s+([\w:.-]+)/gu)) state.pending.push(match[1]);
42
+ }
43
+ }
44
+
45
+ function inspectUnitGates(state) {
46
+ if (!state.tree.hasFile(state.jestConfig)) return;
47
+ for (const name of UNIT_RUN_SCRIPTS) {
48
+ const command = state.scripts[name];
49
+ if (typeof command === 'string' && /\bjest\b/u.test(command) && !/--selectProjects\s+unit\b/u.test(command))
50
+ state.finding(state.rule, 'package.json', `Script ${name} runs jest without --selectProjects unit and would run the integration, e2e or contract project.`);
51
+ }
52
+ const jestText = readTextFile(state.root, state.jestConfig) ?? '';
53
+ if (/collectCoverageFrom/u.test(jestText) && !/!src\/tests\/(?:\*\*|e2e)/u.test(jestText))
54
+ state.finding(state.rule, state.jestConfig, 'collectCoverageFrom must exclude src/tests/** so no integration, e2e or contract file counts toward coverage.');
55
+ }
56
+
57
+ function inspectWorkflows(state) {
58
+ for (const file of state.tree.files().filter(entry => /^\.github\/workflows\/[^/]+\.ya?ml$/u.test(entry))) {
59
+ const text = readTextFile(state.root, file);
60
+ if (text === null) continue;
61
+ const trigger = /^on:.*(?:\n(?:[ \t].*)?$)*/mu.exec(text)?.[0] ?? '';
62
+ if (!/\b(?:push|pull_request)\b/u.test(trigger)) continue;
63
+ if (runsE2e(withoutComments(text)))
64
+ state.finding(state.rule, file, `${file} runs e2e on push or pull_request. Move the e2e job to its own workflow with on: workflow_dispatch only.`);
65
+ }
66
+ }
67
+
68
+ /** Check hooks, transitive hook scripts, unit scripts, coverage and automatic workflows. */
69
+ export function e2eInAutomaticGate({ root, tree, jestConfig, finding }) {
70
+ const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
71
+ let pkg = null;
72
+ try { pkg = JSON.parse(readTextFile(root, 'package.json') ?? ''); } catch { /* the package checks own invalid JSON */ }
73
+ const state = { root, tree, jestConfig, finding, rule, pkg, scripts: pkg?.scripts ?? {}, pending: [], called: new Set() };
74
+ inspectHooks(state);
75
+ inspectLintStaged(state);
76
+ inspectHookScripts(state);
77
+ inspectUnitGates(state);
78
+ inspectWorkflows(state);
79
+ }
80
+
81
+ /** The be side's default tsconfig excludes the world, integration, e2e and contract trees (they are checked by src/tests/tsconfig.json). */
82
+ export function testTreesOutOfDefaultProgram({ root, tree, finding }) {
83
+ const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
84
+ const tsconfigText = readTextFile(root, 'tsconfig.json');
85
+ let tsconfig = null;
86
+ try { tsconfig = tsconfigText === null ? null : JSON.parse(tsconfigText); } catch { /* the typecheck itself owns parsing */ }
87
+ if (tsconfig) {
88
+ const excludedText = JSON.stringify(tsconfig.exclude ?? []);
89
+ const excludesTestTrees = ['world', 'integration', 'e2e', 'contract'].every(tree => excludedText.includes(`src/tests/${tree}`));
90
+ const defaultAll = tsconfig.include === undefined && tsconfig.files === undefined;
91
+ const files = tree.files();
92
+ if (files.some(file => /^src\/tests\/(?:world|integration|e2e|contract)\/.+\.[cm]?tsx?$/u.test(file)) && !excludesTestTrees &&
93
+ (defaultAll || JSON.stringify(tsconfig.include ?? []).includes('src')))
94
+ finding(rule, 'tsconfig.json', 'The default tsconfig includes src/tests/{world,integration,e2e,contract}/**. Exclude those trees and check them with src/tests/tsconfig.json (typecheck:tests).');
95
+ }
96
+ }