@geonosis/oxlint-plugin-biological-architecture 3.1.0 → 3.2.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 (236) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +48 -5
  3. package/RULES.md +200 -99
  4. package/corpus/.oxlintrc.json +5 -0
  5. package/corpus/data/bad-dynamic-orm.ts +5 -0
  6. package/corpus/data/bad-type-only-orm.ts +3 -0
  7. package/corpus/e2e/bad-list-response.spec.ts +5 -0
  8. package/corpus/e2e/bad-timeout.spec.ts +5 -0
  9. package/corpus/e2e/good-detail-response.spec.ts +5 -0
  10. package/corpus/e2e/good-state.spec.ts +5 -0
  11. package/corpus/manifest.json +2 -0
  12. package/corpus/tissues/bad-awaits-data.tsx +8 -0
  13. package/corpus/tissues/bad-fetches-data.tsx +7 -0
  14. package/corpus/tissues/good-takes-products.tsx +5 -0
  15. package/dist/discover-atoms.d.ts +2 -37
  16. package/dist/discover-atoms.js +1 -104
  17. package/dist/index.d.ts +3 -12
  18. package/dist/index.js +19 -176
  19. package/dist/presets.d.ts +11 -19
  20. package/dist/presets.js +88 -3
  21. package/dist/registry.d.ts +2 -0
  22. package/dist/registry.js +172 -0
  23. package/dist/rules/atom-no-deps.d.ts +1 -0
  24. package/dist/rules/atom-no-deps.js +10 -11
  25. package/dist/rules/boundary-time-is-primitive.d.ts +0 -7
  26. package/dist/rules/cell-must-be-stateful.d.ts +2 -2
  27. package/dist/rules/cell-must-be-stateful.js +24 -23
  28. package/dist/rules/cell-must-not-compose-cell.d.ts +1 -0
  29. package/dist/rules/cell-must-not-compose-cell.js +10 -3
  30. package/dist/rules/cell-no-cross-feature-organelles.d.ts +1 -0
  31. package/dist/rules/cell-no-cross-feature-organelles.js +7 -0
  32. package/dist/rules/cell-no-tissues.d.ts +1 -0
  33. package/dist/rules/cell-no-tissues.js +11 -7
  34. package/dist/rules/cells-folder-index-is-barrel.d.ts +12 -10
  35. package/dist/rules/cells-folder-index-is-barrel.js +66 -34
  36. package/dist/rules/compound-must-be-stateless.d.ts +2 -0
  37. package/dist/rules/compound-must-be-stateless.js +24 -26
  38. package/dist/rules/compound-no-stores.d.ts +1 -0
  39. package/dist/rules/compound-no-stores.js +12 -10
  40. package/dist/rules/constants-in-constants-file.d.ts +2 -8
  41. package/dist/rules/constants-in-constants-file.js +5 -6
  42. package/dist/rules/dialect-through-the-seam.d.ts +0 -13
  43. package/dist/rules/durable-body-reads-through-steps.d.ts +0 -14
  44. package/dist/rules/durable-body-reads-through-steps.js +8 -3
  45. package/dist/rules/effect-hook-naming.d.ts +9 -11
  46. package/dist/rules/effect-hook-naming.js +33 -31
  47. package/dist/rules/emit-declares-attempts.d.ts +0 -17
  48. package/dist/rules/emit-declares-attempts.js +5 -10
  49. package/dist/rules/font-roles-only.d.ts +0 -18
  50. package/dist/rules/font-roles-only.js +19 -22
  51. package/dist/rules/hermetic-e2e-no-wall-clock-waits.d.ts +53 -0
  52. package/dist/rules/hermetic-e2e-no-wall-clock-waits.js +100 -0
  53. package/dist/rules/icon-control-has-name.js +7 -1
  54. package/dist/rules/layer-walls.d.ts +0 -25
  55. package/dist/rules/layer-walls.js +7 -13
  56. package/dist/rules/lib/atom-map.d.ts +24 -0
  57. package/dist/rules/lib/atom-map.js +16 -0
  58. package/dist/rules/lib/cells-barrel.d.ts +0 -1
  59. package/dist/rules/lib/colors.d.ts +0 -19
  60. package/dist/rules/lib/colors.js +10 -243
  61. package/dist/rules/lib/comments.d.ts +19 -0
  62. package/dist/rules/lib/comments.js +5 -0
  63. package/dist/rules/lib/context.d.ts +0 -10
  64. package/dist/rules/lib/direction-rule.d.ts +3 -21
  65. package/dist/rules/lib/direction-rule.js +12 -3
  66. package/dist/rules/lib/doors.d.ts +2 -18
  67. package/dist/rules/lib/doors.js +79 -41
  68. package/dist/rules/lib/hooks.d.ts +2 -17
  69. package/dist/rules/lib/hooks.js +14 -9
  70. package/dist/rules/lib/imports.d.ts +2 -23
  71. package/dist/rules/lib/imports.js +7 -2
  72. package/dist/rules/lib/jsx.d.ts +31 -0
  73. package/dist/rules/lib/jsx.js +11 -0
  74. package/dist/rules/lib/landing.d.ts +0 -40
  75. package/dist/rules/lib/literal-call.d.ts +1 -0
  76. package/dist/rules/lib/literal-call.js +2 -0
  77. package/dist/rules/lib/module-text.d.ts +3 -27
  78. package/dist/rules/lib/module-text.js +19 -4
  79. package/dist/rules/lib/mutations.d.ts +0 -14
  80. package/dist/rules/lib/nodes.d.ts +1 -18
  81. package/dist/rules/lib/nodes.js +1 -0
  82. package/dist/rules/lib/objects.d.ts +2 -11
  83. package/dist/rules/lib/objects.js +21 -0
  84. package/dist/rules/lib/options.d.ts +2 -52
  85. package/dist/rules/lib/options.js +44 -18
  86. package/dist/rules/lib/package-imports.d.ts +0 -5
  87. package/dist/rules/lib/package-imports.js +4 -10
  88. package/dist/rules/lib/paths.d.ts +15 -41
  89. package/dist/rules/lib/paths.js +34 -1
  90. package/dist/rules/lib/pattern-match.d.ts +2 -12
  91. package/dist/rules/lib/pattern-match.js +5 -1
  92. package/dist/rules/lib/probe.d.ts +0 -43
  93. package/dist/rules/lib/reexports.d.ts +0 -18
  94. package/dist/rules/lib/render-tracking.d.ts +0 -8
  95. package/dist/rules/lib/shared-features.d.ts +0 -8
  96. package/dist/rules/lib/shared-features.js +2 -9
  97. package/dist/rules/lib/state-sources.d.ts +8 -0
  98. package/dist/rules/lib/state-sources.js +55 -0
  99. package/dist/rules/lib/steps.d.ts +3 -5
  100. package/dist/rules/lib/steps.js +6 -1
  101. package/dist/rules/lib/styling.d.ts +9 -42
  102. package/dist/rules/lib/styling.js +36 -10
  103. package/dist/rules/lib/tiers.d.ts +11 -24
  104. package/dist/rules/lib/tiers.js +41 -1
  105. package/dist/rules/lib/tissue-entrypoint.d.ts +0 -1
  106. package/dist/rules/lib/tissue-entrypoint.js +4 -5
  107. package/dist/rules/lib/tsconfig-paths.d.ts +0 -4
  108. package/dist/rules/lib/tsconfig-paths.js +47 -15
  109. package/dist/rules/lib/type-members.d.ts +6 -0
  110. package/dist/rules/lib/type-members.js +165 -0
  111. package/dist/rules/lib/visitors.d.ts +0 -22
  112. package/dist/rules/lib/workspace-members.d.ts +2 -0
  113. package/dist/rules/lib/workspace-members.js +115 -0
  114. package/dist/rules/list-take-without-order.js +2 -8
  115. package/dist/rules/max-comment-density.d.ts +3 -18
  116. package/dist/rules/max-comment-density.js +3 -3
  117. package/dist/rules/molecule-atoms-only.d.ts +1 -0
  118. package/dist/rules/molecule-atoms-only.js +11 -7
  119. package/dist/rules/molecule-must-compose.d.ts +2 -0
  120. package/dist/rules/molecule-must-compose.js +17 -21
  121. package/dist/rules/mutating-step-declares-retry.d.ts +2 -7
  122. package/dist/rules/mutating-step-declares-retry.js +29 -11
  123. package/dist/rules/named-when-block.d.ts +0 -2
  124. package/dist/rules/named-when-block.js +2 -8
  125. package/dist/rules/next-route-segment-is-thin-delegate.d.ts +4 -19
  126. package/dist/rules/next-route-segment-is-thin-delegate.js +47 -71
  127. package/dist/rules/no-brand-names.d.ts +0 -9
  128. package/dist/rules/no-brand-names.js +1 -1
  129. package/dist/rules/no-card-shaped-div.d.ts +4 -42
  130. package/dist/rules/no-card-shaped-div.js +12 -4
  131. package/dist/rules/no-cross-feature-stores.d.ts +1 -0
  132. package/dist/rules/no-cross-feature-stores.js +9 -5
  133. package/dist/rules/no-d1-transaction.d.ts +2 -5
  134. package/dist/rules/no-d1-transaction.js +7 -0
  135. package/dist/rules/no-date-string-compare.d.ts +2 -0
  136. package/dist/rules/no-date-string-compare.js +14 -8
  137. package/dist/rules/no-duplicate-jsx-patterns.d.ts +3 -1
  138. package/dist/rules/no-duplicate-jsx-patterns.js +16 -1
  139. package/dist/rules/no-generic-utility-package.d.ts +48 -2
  140. package/dist/rules/no-generic-utility-package.js +48 -9
  141. package/dist/rules/no-hook-in-component-disguise.d.ts +27 -13
  142. package/dist/rules/no-hook-in-component-disguise.js +38 -30
  143. package/dist/rules/no-index-count-as-exact.d.ts +0 -3
  144. package/dist/rules/no-inert-hidden-jsx.d.ts +4 -21
  145. package/dist/rules/no-inert-hidden-jsx.js +13 -12
  146. package/dist/rules/no-inline-data-in-jsx.d.ts +2 -0
  147. package/dist/rules/no-inline-data-in-jsx.js +17 -2
  148. package/dist/rules/no-invalid-feature-folders.d.ts +2 -0
  149. package/dist/rules/no-invalid-feature-folders.js +12 -4
  150. package/dist/rules/no-loader-side-effect-in-index-file.d.ts +2 -9
  151. package/dist/rules/no-logic-in-component-files.d.ts +6 -5
  152. package/dist/rules/no-logic-in-component-files.js +52 -31
  153. package/dist/rules/no-mock-db-in-integration.d.ts +0 -15
  154. package/dist/rules/no-mock-db-in-integration.js +3 -4
  155. package/dist/rules/no-orm-outside-db.d.ts +1 -0
  156. package/dist/rules/no-orm-outside-db.js +9 -7
  157. package/dist/rules/no-raw-html-atoms.d.ts +3 -22
  158. package/dist/rules/no-raw-html-atoms.js +5 -17
  159. package/dist/rules/no-raw-sql-outside-allowed.d.ts +2 -0
  160. package/dist/rules/no-raw-sql-outside-allowed.js +10 -15
  161. package/dist/rules/no-react-namespace.d.ts +2 -0
  162. package/dist/rules/no-react-namespace.js +13 -0
  163. package/dist/rules/no-renamed-html-props.d.ts +2 -0
  164. package/dist/rules/no-renamed-html-props.js +11 -2
  165. package/dist/rules/no-render-prop-reader.d.ts +4 -20
  166. package/dist/rules/no-render-prop-reader.js +13 -5
  167. package/dist/rules/no-runtime-dead-affordance.d.ts +3 -26
  168. package/dist/rules/no-runtime-dead-affordance.js +7 -20
  169. package/dist/rules/no-swallowed-catch.js +2 -8
  170. package/dist/rules/no-trivial-wrapper-component.d.ts +4 -19
  171. package/dist/rules/no-trivial-wrapper-component.js +24 -33
  172. package/dist/rules/no-ts-in-bio-folders.d.ts +2 -13
  173. package/dist/rules/no-ts-in-bio-folders.js +9 -7
  174. package/dist/rules/no-type-definitions-in-components.d.ts +2 -0
  175. package/dist/rules/no-type-definitions-in-components.js +12 -1
  176. package/dist/rules/no-unregistered-plugin-dir.d.ts +0 -41
  177. package/dist/rules/no-unregistered-plugin-dir.js +1 -9
  178. package/dist/rules/no-void-port.d.ts +2 -4
  179. package/dist/rules/no-void-port.js +7 -0
  180. package/dist/rules/organelle-dependency.d.ts +1 -0
  181. package/dist/rules/organelle-dependency.js +10 -8
  182. package/dist/rules/organelle-single-source.d.ts +2 -0
  183. package/dist/rules/organelle-single-source.js +58 -9
  184. package/dist/rules/plugin-route-namespaced.d.ts +0 -11
  185. package/dist/rules/plugin-route-namespaced.js +2 -2
  186. package/dist/rules/queries-require-org-scope.d.ts +0 -3
  187. package/dist/rules/queries-require-org-scope.js +3 -10
  188. package/dist/rules/queue-loop-is-the-library.js +3 -2
  189. package/dist/rules/route-no-inline-mutation.d.ts +1 -25
  190. package/dist/rules/route-no-inline-mutation.js +2 -7
  191. package/dist/rules/router-schema-parity.d.ts +0 -9
  192. package/dist/rules/router-schema-parity.js +5 -9
  193. package/dist/rules/service-name-not-reserved.d.ts +0 -3
  194. package/dist/rules/service-name-not-reserved.js +1 -34
  195. package/dist/rules/ssot-no-inline-facts.js +2 -5
  196. package/dist/rules/ssot-no-process-env.js +4 -12
  197. package/dist/rules/step-declares-compensation-or-none.d.ts +2 -1
  198. package/dist/rules/step-declares-compensation-or-none.js +17 -1
  199. package/dist/rules/step-opens-its-own-cell.d.ts +3 -26
  200. package/dist/rules/step-opens-its-own-cell.js +8 -13
  201. package/dist/rules/store-route-scopes-tenant-data.js +8 -26
  202. package/dist/rules/subpath-over-barrel.d.ts +2 -10
  203. package/dist/rules/subpath-over-barrel.js +48 -9
  204. package/dist/rules/subscriber-declares-id.d.ts +0 -2
  205. package/dist/rules/subscriber-declares-id.js +2 -8
  206. package/dist/rules/tables-declare-their-plane.d.ts +0 -2
  207. package/dist/rules/tables-declare-their-plane.js +4 -10
  208. package/dist/rules/tenant-tables-carry-org-id.d.ts +1 -4
  209. package/dist/rules/tenant-tables-carry-org-id.js +5 -12
  210. package/dist/rules/test-no-clock-polling-for-promise-flips.d.ts +2 -0
  211. package/dist/rules/test-no-clock-polling-for-promise-flips.js +18 -1
  212. package/dist/rules/time-through-the-door.d.ts +0 -24
  213. package/dist/rules/time-through-the-door.js +5 -5
  214. package/dist/rules/tissue-must-compose.d.ts +5 -3
  215. package/dist/rules/tissue-must-compose.js +25 -20
  216. package/dist/rules/tissue-no-data-props.d.ts +57 -1
  217. package/dist/rules/tissue-no-data-props.js +114 -32
  218. package/dist/rules/tissue-no-fetch.d.ts +26 -0
  219. package/dist/rules/tissue-no-fetch.js +57 -0
  220. package/dist/rules/tissue-no-hooks.d.ts +21 -23
  221. package/dist/rules/tissue-no-hooks.js +22 -4
  222. package/dist/rules/tissue-no-organelles.d.ts +1 -0
  223. package/dist/rules/tissue-no-organelles.js +9 -1
  224. package/dist/rules/tissue-no-stores.d.ts +1 -0
  225. package/dist/rules/tissue-no-stores.js +9 -1
  226. package/dist/rules/todo-names-its-plan.d.ts +1 -28
  227. package/dist/rules/todo-names-its-plan.js +2 -2
  228. package/dist/rules/tokens-only-colors.d.ts +2 -37
  229. package/dist/rules/tokens-only-colors.js +33 -18
  230. package/dist/rules/unique-query-step-name.d.ts +0 -7
  231. package/dist/rules/unique-query-step-name.js +6 -10
  232. package/dist/rules/worker-handles-are-scoped.d.ts +12 -9
  233. package/dist/rules/worker-handles-are-scoped.js +12 -5
  234. package/dist/rules/zustand-v5-best-practices.d.ts +8 -18
  235. package/dist/rules/zustand-v5-best-practices.js +32 -56
  236. package/package.json +2 -2
@@ -1,17 +1,8 @@
1
1
  import type { AnyNode } from './nodes.js';
2
- /** The expression under any `as` / `satisfies` — a retyped value is the value it was. */
3
2
  export declare const withoutTypes: (node: unknown) => AnyNode | undefined;
4
- /**
5
- * The properties of an object literal, or **null when it cannot be read whole**.
6
- *
7
- * A spread hides properties, and the posture this plugin already took for `emit-declares-attempts`
8
- * is the one that holds here too: a rule that guessed at what a spread contains would fire on every
9
- * repo that factored its configs out, and people would learn to ignore it. Unreadable is not the
10
- * same as absent, and only one of the two is a finding.
11
- */
12
3
  export declare const propertiesOf: (node: unknown) => AnyNode[] | null;
13
- /** The value of the named property, or undefined. `name` and `'name'` are read alike; `[name]` is not. */
14
4
  export declare const valueOf: (properties: readonly AnyNode[], name: string) => AnyNode | undefined;
15
5
  export declare const hasProperty: (properties: readonly AnyNode[], name: string) => boolean;
16
- /** The string a node literally is, or null — a constant is a value this cannot read. */
17
6
  export declare const stringOf: (node: unknown) => string | null;
7
+ export declare const keyNameOf: (node: AnyNode) => string | null;
8
+ export declare const destructuredNamesOf: (pattern: unknown) => string[] | null;
@@ -26,8 +26,29 @@ var stringOf = (node) => {
26
26
  const literal = node;
27
27
  return literal?.type === "Literal" && typeof literal.value === "string" ? literal.value : null;
28
28
  };
29
+ var keyNameOf = (node) => {
30
+ const key = node.key;
31
+ if (key?.type === "Identifier" && typeof key.name === "string")
32
+ return key.name;
33
+ return stringOf(key);
34
+ };
35
+ var destructuredNamesOf = (pattern) => {
36
+ const at = pattern;
37
+ if (at?.type !== "ObjectPattern")
38
+ return null;
39
+ const names = [];
40
+ for (const one of at.properties ?? []) {
41
+ const name = one.type === "Property" && one.computed !== true ? keyNameOf(one) : null;
42
+ if (name === null)
43
+ return null;
44
+ names.push(name);
45
+ }
46
+ return names;
47
+ };
29
48
  export {
49
+ destructuredNamesOf,
30
50
  hasProperty,
51
+ keyNameOf,
31
52
  propertiesOf,
32
53
  stringOf,
33
54
  valueOf,
@@ -1,34 +1,10 @@
1
- /**
2
- * #199: the rule and the option it REQUIRES, in one fixed position at the front of the finding.
3
- *
4
- * The prose after it quotes every option the rule accepts, which is what a consumer with a
5
- * half-written config needs — and is exactly why a reader matching the option name anywhere in the
6
- * message cannot tell a required option from a mentioned one. `refuses-bare.bin.test.ts` asserted
7
- * that way and passed for a release over an entry claiming `engine` for a rule that requires
8
- * `within`. The doctor and the cli read the pair out of this header and nowhere else.
9
- */
10
1
  export declare const refusalHeader: (rule: string, option: string) => string;
11
- /**
12
- * The signal a rule raises when an option it cannot run without is missing. Carries the pair a
13
- * reader would otherwise parse back out of the sentence. Caught by `reportingOncePerRun` in the
14
- * registry; a `create` reached WITHOUT the registry (a unit test on the rule module) sees it thrown,
15
- * which is the rule saying what it requires, not the plugin's behaviour.
16
- */
17
2
  export declare class OptionMissing extends Error {
18
3
  readonly option: string;
19
4
  readonly rule: string;
20
5
  constructor(rule: string, option: string, message: string);
21
6
  }
22
- /**
23
- * A regex-source option in either shape: a bare string is a one-element list.
24
- *
25
- * The seven tree options #12 added in one commit shipped as three strings and four lists. A
26
- * consumer wrote `seam` as a string, oxlint refused the config at options validation, and every
27
- * rule in it read 0 with exit 1 — a config mistake presenting as a clean zero. One shape rule for
28
- * all of them, read here and declared by `PATTERNS` below, so the reader never has to know which.
29
- */
30
7
  export declare const patternsOf: (value: string | readonly string[] | null | undefined) => string[];
31
- /** The JSON schema for an option `patternsOf` reads: `string | string[]`. */
32
8
  export declare const PATTERNS: {
33
9
  anyOf: ({
34
10
  type: 'string';
@@ -40,35 +16,9 @@ export declare const PATTERNS: {
40
16
  type: 'array';
41
17
  })[];
42
18
  };
43
- /**
44
- * #203: a test file is not a door/seam rule's business anywhere, so the default says so — the
45
- * fallback for an `exempt` option a repo has not configured.
46
- *
47
- * `time-through-the-door` 1.4.0 had `__tests__` compiled in; making every path an option (law 6)
48
- * emptied it, and the reach GREW — a Workers + D1 app's first RC run reported six findings in test
49
- * files that had never been in scope. Test paths are not consumer vocabulary the way a door is:
50
- * every repo spells them these three ways, and a repo with a fourth spelling names its own list and
51
- * replaces this one. `dialect-through-the-seam` held the identical array as its own copy; one now.
52
- */
19
+ export declare const DEFAULT_TENANT_KEY = "orgId";
53
20
  export declare const DEFAULT_TEST_EXEMPT: string[];
54
- /**
55
- * The value, or the requirement raised, naming the rule and the option. Read by the rule, judged
56
- * here: some of these options are keyed and one is positional, and what they have in common is not
57
- * their shape but that the rule cannot fire without them.
58
- *
59
- * `accepts` is the whole option bag written out — `{ roots: string[], registry: string | string[] }`
60
- * — appended so the accepted shape comes back WITH the finding. Without it the only way to learn
61
- * what a rule takes is to read the dist bundle, which is how a consumer whose registry is split
62
- * reached for a list, was told `should be string`, and could not tell from the refusal whether a
63
- * list was a shape the rule had or a shape it lacked. Optional, so a rule that has not written its
64
- * shape down is reported exactly as it always was.
65
- *
66
- * #205 is history now: a `requireOptionHere` once waited for a file a tree-scoped rule would judge
67
- * and named it in the refusal, because a refusal thrown over every package the preset reached was
68
- * noise a consumer learned to route around. Reported once a run, a refusal has no per-package
69
- * noise to wait out, and no rule was calling it, so it is gone.
70
- */
71
21
  export declare const requireOption: <Value>(rule: string, value: Value | null | undefined, option: string, accepts?: string) => Value;
72
22
  type Create<Context> = (context: Context) => Record<string, unknown>;
73
- export declare const reportingOncePerRun: <Context>(create: Create<Context>) => Create<Context>;
23
+ export declare const reportingOncePerRun: <Context>(create: Create<Context>, rule?: string) => Create<Context>;
74
24
  export {};
@@ -1,5 +1,4 @@
1
1
  // src/rules/lib/options.ts
2
- var NOTHING = "nothing";
3
2
  var isMissing = (value) => value === undefined || value === null || value === "" || Array.isArray(value) && value.length === 0;
4
3
  var refusalHeader = (rule, option) => `biological-architecture/${rule} requires option \`${option}\`.`;
5
4
 
@@ -20,40 +19,67 @@ var PATTERNS = {
20
19
  { items: { type: "string" }, type: "array" }
21
20
  ]
22
21
  };
22
+ var DEFAULT_TENANT_KEY = "orgId";
23
23
  var DEFAULT_TEST_EXEMPT = ["__tests__/", String.raw`\.test\.`, String.raw`\.spec\.`];
24
24
  var requireOption = (rule, value, option, accepts) => {
25
25
  if (isMissing(value)) {
26
- throw new OptionMissing(rule, option, `${refusalHeader(rule, option)} It is enabled but its \`${option}\` option is ${NOTHING}, so it judges no file — this finding stands in for every file it did not read, once a run. Configure \`${option}\`, or turn the rule off: an enabled rule that judges nothing would otherwise read exactly like a rule that found nothing.${accepts === undefined ? "" : ` It accepts ${accepts}.`}`);
26
+ throw new OptionMissing(rule, option, `${refusalHeader(rule, option)} It is enabled but its \`${option}\` option is nothing, so it judges no file — this finding stands in for every file it did not read, once a run. Configure \`${option}\`, or turn the rule off: an enabled rule that judges nothing would otherwise read exactly like a rule that found nothing.${accepts === undefined ? "" : ` It accepts ${accepts}.`}`);
27
27
  }
28
28
  return value;
29
29
  };
30
30
  var raisedOver = new Map;
31
- var fileOf = (context) => {
31
+ var filenameOf = (context) => {
32
32
  const named = context?.filename;
33
33
  return typeof named === "string" ? named : "";
34
34
  };
35
- var reportingOncePerRun = (create) => (context) => {
35
+ var badPatternMessage = (rule, error) => `biological-architecture/${rule} threw a SyntaxError — ${error.message}. An option of this rule holds a pattern that is not a regular expression. This finding stands in for every file it could not judge, once a run; fix the pattern, or turn the rule off.`;
36
+ var onceFor = (key, file) => {
37
+ if (file === "")
38
+ return true;
39
+ const first = raisedOver.get(key);
40
+ if (first !== undefined && first !== file)
41
+ return false;
42
+ raisedOver.set(key, file);
43
+ return true;
44
+ };
45
+ var refusalVisitor = (context, key, finding) => onceFor(key, filenameOf(context)) ? {
46
+ Program(node) {
47
+ context.report({ ...finding, node });
48
+ }
49
+ } : {};
50
+ var reportedAtVisit = (visitor, rule, context) => Object.fromEntries(Object.entries(visitor).map(([name, handler]) => [
51
+ name,
52
+ typeof handler === "function" ? (node) => {
53
+ try {
54
+ handler(node);
55
+ } catch (error) {
56
+ if (!(error instanceof SyntaxError))
57
+ throw error;
58
+ if (!onceFor(`${rule}/pattern`, filenameOf(context)))
59
+ return;
60
+ context.report({ message: badPatternMessage(rule, error), node });
61
+ }
62
+ } : handler
63
+ ]));
64
+ var reportingOncePerRun = (create, rule = "") => (context) => {
36
65
  try {
37
- return create(context);
66
+ return reportedAtVisit(create(context), rule, context);
38
67
  } catch (error) {
68
+ if (error instanceof SyntaxError) {
69
+ return refusalVisitor(context, `${rule}/pattern`, {
70
+ message: badPatternMessage(rule, error)
71
+ });
72
+ }
39
73
  if (!(error instanceof OptionMissing))
40
74
  throw error;
41
- const file = fileOf(context);
42
- if (file !== "") {
43
- const key = `${error.rule}/${error.option}`;
44
- const first = raisedOver.get(key);
45
- if (first !== undefined && first !== file)
46
- return {};
47
- raisedOver.set(key, file);
48
- }
49
- return {
50
- Program(node) {
51
- context.report({ message: error.message, node });
52
- }
53
- };
75
+ return refusalVisitor(context, `${error.rule}/${error.option}`, {
76
+ data: { option: error.option, rule: error.rule },
77
+ message: error.message
78
+ });
54
79
  }
55
80
  };
56
81
  export {
82
+ DEFAULT_TENANT_KEY,
57
83
  DEFAULT_TEST_EXEMPT,
58
84
  OptionMissing,
59
85
  PATTERNS,
@@ -1,6 +1 @@
1
- /**
2
- * The places the manifest says a `#` specifier may be, best match first — as bases without
3
- * extensions, for the caller to find the file at. A target that is not relative names a package,
4
- * which is not a file in this repo, and is left out. Empty when no manifest maps it.
5
- */
6
1
  export declare const importCandidates: (importer: string, source: string) => string[];
@@ -1,18 +1,12 @@
1
1
  // src/rules/lib/package-imports.ts
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
- import { conditionTargetOf } from "./module-text.js";
4
+ import { conditionTargetOf, readManifest, recordOf } from "./module-text.js";
5
5
  import { longestMatch } from "./pattern-match.js";
6
6
  var manifests = new Map;
7
7
  var readImports = (file) => {
8
- try {
9
- const parsed = JSON.parse(readFileSync(file, "utf8"));
10
- if (typeof parsed.imports !== "object" || parsed.imports === null)
11
- return null;
12
- return { dir: dirname(file), imports: parsed.imports };
13
- } catch {
14
- return null;
15
- }
8
+ const imports = recordOf(readManifest(file)?.imports);
9
+ return imports === undefined ? null : { dir: dirname(file), imports };
16
10
  };
17
11
  var importsFor = (dir) => {
18
12
  const known = manifests.get(dir);
@@ -1,47 +1,21 @@
1
- /**
2
- * The one place a filename becomes a path.
3
- *
4
- * oxlint hands a rule the name the platform gave it, so on Windows it arrives with backslashes and
5
- * every pattern in this plugin — which is written with `/` — misses. Thirty-seven rules carried
6
- * their own copy of the conversion, spelled three different ways, and one of them fixed only the
7
- * first separator. A single fix to how a path is read had to be found rule by rule; now there is
8
- * one line to find.
9
- */
10
1
  export declare const normalise: (filename: string) => string;
11
- /**
12
- * The last segment of a path — what a diagnostic calls the file. An empty last segment is not a
13
- * name, so a path that ends in a separator answers with itself rather than with `""`.
14
- */
15
2
  export declare const fileOf: (path: string) => string;
16
- /**
17
- * Whether a file IS a door a repo named — one grammar for every rule that has doors (#204).
18
- *
19
- * A repo writes its time module down once and enables two rules with it. `time-through-the-door`
20
- * took a REGEX over the filename; `boundary-time-is-primitive` compared a plain path, so the same
21
- * spelling — `packages/utils/src/time\.ts$`, or the specifier callers import it by — made the
22
- * second rule report the door's own signatures. All three spellings are accepted here: the exact
23
- * path, a path the filename ends with (a specifier is one), and a regex over the filename.
24
- */
25
3
  export declare const isDoor: (filename: string, door: string) => boolean;
26
- /**
27
- * Whether a file IS one of an ENUMERATED list of modules — never a pattern.
28
- *
29
- * The counterpart to a path grant, and deliberately not `isDoor`: a door accepts a regex, and a
30
- * regex is exactly the glob whose exemption stops wherever it happens to stop. An entry here names
31
- * one module, by its full path or by the tail a repo writes it as, so the count of exemptions is a
32
- * number a reader can hold.
33
- */
34
4
  export declare const isModule: (filename: string, module: string) => boolean;
35
- /**
36
- * Whether a NORMALISED path sits inside a `__tests__/` directory — the exemption five rules
37
- * (tissue-no-data-props, next-route-segment-is-thin-delegate, no-inert-hidden-jsx,
38
- * no-hook-in-component-disguise, no-trivial-wrapper-component) each spelled as their own
39
- * `/__tests__\//` literal. Takes an already-`normalise`d path, the shape every call site already
40
- * held in a local before testing it.
41
- */
5
+ export declare const isExempt: (filename: string, { allow, exemptModules }: {
6
+ allow?: string[];
7
+ exemptModules?: string[];
8
+ }) => boolean;
42
9
  export declare const isTestFile: (path: string) => boolean;
43
- /**
44
- * Whether a NORMALISED path matches any of a list of regex sources — `dialect-through-the-seam`
45
- * and `no-d1-transaction` each carried this loop by hand, over the same shape of option.
46
- */
47
10
  export declare const anyOf: (patterns: readonly string[], path: string) => boolean;
11
+ export declare const matchesAny: (patterns: readonly RegExp[], path: string) => boolean;
12
+ export declare const trimSlashes: (path: string) => string;
13
+ export declare const rootedAt: (filename: string, directory: string) => {
14
+ after: string;
15
+ repoRoot: string;
16
+ } | null;
17
+ export declare const HARNESS_PATHS: string[];
18
+ export declare const ROUTE_FALLBACK_BASENAMES: Set<string>;
19
+ export declare const LAYOUT_BASENAMES: Set<string>;
20
+ export declare const ROUTE_BODY_BASENAMES: Set<string>;
21
+ export declare const ROUTE_BASENAMES: Set<string>;
@@ -16,13 +16,46 @@ var isModule = (filename, module) => {
16
16
  const named = module.replace(/^\.?\//, "");
17
17
  return path === named || path.endsWith(`/${named}`);
18
18
  };
19
+ var isExempt = (filename, { allow = [], exemptModules = [] }) => exemptModules.some((module) => isModule(filename, module)) || allow.some((path) => filename.includes(path));
19
20
  var isTestFile = (path) => /__tests__\//.test(path);
20
21
  var anyOf = (patterns, path) => patterns.some((pattern) => new RegExp(pattern).test(path));
22
+ var matchesAny = (patterns, path) => patterns.some((pattern) => pattern.test(path));
23
+ var trimSlashes = (path) => path.replace(/^\/+/, "").replace(/\/+$/, "");
24
+ var rootedAt = (filename, directory) => {
25
+ const marker = `/${directory}/`;
26
+ const at = filename.indexOf(marker);
27
+ return at < 0 ? null : { after: filename.slice(at + marker.length), repoRoot: filename.slice(0, at) };
28
+ };
29
+ var HARNESS_PATHS = [
30
+ String.raw`/__tests__/`,
31
+ String.raw`/scripts/`,
32
+ String.raw`/testing/`,
33
+ String.raw`/seeds/`
34
+ ];
35
+ var ROUTE_FALLBACK_BASENAMES = new Set([
36
+ "default.tsx",
37
+ "error.tsx",
38
+ "global-error.tsx",
39
+ "loading.tsx",
40
+ "not-found.tsx"
41
+ ]);
42
+ var LAYOUT_BASENAMES = new Set(["layout.tsx", "template.tsx"]);
43
+ var ROUTE_BODY_BASENAMES = new Set([...LAYOUT_BASENAMES, "page.tsx"]);
44
+ var ROUTE_BASENAMES = new Set([...ROUTE_FALLBACK_BASENAMES, ...ROUTE_BODY_BASENAMES]);
21
45
  export {
46
+ HARNESS_PATHS,
47
+ LAYOUT_BASENAMES,
48
+ ROUTE_BASENAMES,
49
+ ROUTE_BODY_BASENAMES,
50
+ ROUTE_FALLBACK_BASENAMES,
22
51
  anyOf,
23
52
  fileOf,
24
53
  isDoor,
54
+ isExempt,
25
55
  isModule,
26
56
  isTestFile,
27
- normalise
57
+ matchesAny,
58
+ normalise,
59
+ rootedAt,
60
+ trimSlashes
28
61
  };
@@ -1,16 +1,6 @@
1
- /**
2
- * The longest match for a wildcard pattern against a source string — tsc's own rule for
3
- * `tsconfig.json` `paths`, and the same rule Node uses for `package.json` `imports`. An exact key
4
- * wins outright; a pattern with one `*` matches when the source starts with what comes before it
5
- * and ends with what comes after, and among those the longest fixed (non-star) part wins.
6
- *
7
- * `tsconfig-paths.ts` and `package-imports.ts` each carried this loop by hand, spelled identically
8
- * down to the variable names — one truth about how a wildcard map is searched, read for two
9
- * different maps. What each caller does with the match (a `paths` entry names several targets and
10
- * takes `target.replace`; an `imports` value is one target read through `conditionTargetOf` and
11
- * takes `target.replaceAll`) is NOT unified here, because those two shapes are not the same truth.
12
- */
13
1
  export declare const longestMatch: <Value>(entries: Iterable<[string, Value]>, source: string) => {
14
2
  matched: string;
15
3
  value: Value;
16
4
  } | undefined;
5
+ export declare const escapeForRegex: (value: string) => string;
6
+ export declare const readableSource: (pattern: string) => string;
@@ -24,6 +24,10 @@ var longestMatch = (entries, source) => {
24
24
  }
25
25
  return best === undefined ? undefined : { matched: best.matched, value: best.value };
26
26
  };
27
+ var escapeForRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
28
+ var readableSource = (pattern) => pattern.replaceAll("\\", "").replace(/\$$/, "");
27
29
  export {
28
- longestMatch
30
+ escapeForRegex,
31
+ longestMatch,
32
+ readableSource
29
33
  };
@@ -1,55 +1,12 @@
1
- /**
2
- * The file a rule says MUST fire it, given the options it was handed.
3
- *
4
- * `requireOption` closed one hole: an enabled rule with no option reports that, once a run,
5
- * instead of reading as a clean tree. It leaves the next one open. A rule CONFIGURED with a repo's own
6
- * vocabulary — its own brand, `packages/workflows/src/`, `@repo/db` — cannot be exercised by a corpus
7
- * that ships fixtures saying `acme` and `corpus/workflows/`, so "fires nowhere" is what an
8
- * adoption's doctor reports about a rule the repo is enforcing perfectly well. Both consumers filed
9
- * exactly that on 2026-08-30, and both were right about the fact and wrong about whose defect it is.
10
- *
11
- * A probe is the answer the rule itself can give: hand it the options and it writes the smallest
12
- * source that must fire it under THOSE options, at a path that lands where it looks. It is a claim,
13
- * so it is tested through the real binary — a probe that does not fire is a rule that cannot be
14
- * exercised, and the honest thing then is to say so rather than to widen the probe until it does.
15
- */
16
1
  export type Probe = {
17
- /** Repo-root-relative, `/`-separated. It must satisfy every PATH option the rule reads. */
18
2
  path: string;
19
3
  source: string;
20
4
  };
21
- /**
22
- * A probe that needs a TREE rather than a file.
23
- *
24
- * A rule that reads a sibling off disk — a registry, a schema's twin — cannot be exercised by one
25
- * file however right its source is: `no-unregistered-plugin-dir` returns silently when the registry
26
- * its options name is not there, so the single-file probe fired nothing and every consumer
27
- * enforcing the rule over a real registry read as a permanent FAIL. The answer is not a widened
28
- * probe, which would be the rule claiming evidence it does not have; it is a probe that may declare
29
- * the sibling too, and a reader that mounts all of them.
30
- */
31
5
  export type ProbeTree = {
32
6
  files: readonly Probe[];
33
7
  };
34
8
  export type ProbeFn = (options: readonly unknown[]) => Probe | ProbeTree;
35
- /** The files a probe declares, whichever of the two forms it answered in. */
36
9
  export declare const filesOf: (answer: Probe | ProbeTree) => readonly Probe[];
37
- /** The bag a rule reads its keyed options out of, whatever the caller passed. */
38
10
  export declare const bagOf: <Options>(options: readonly unknown[]) => Partial<Options>;
39
- /**
40
- * The first configured value, or the first default. A probe reads the option the same way `create`
41
- * does, defaults included — otherwise a repo keeping a default would get a probe built for a value
42
- * its rule is not using, which fires nowhere and blames the repo for it.
43
- */
44
11
  export declare const firstOf: (configured: readonly string[] | undefined, fallback: readonly string[]) => string;
45
- /**
46
- * A path a regex option would match, built back out of the regex.
47
- *
48
- * The path options in this plugin are regex SOURCES — `within`, `paths`, `directories`, a layer's
49
- * `paths` — so a probe cannot be written against them without inverting one. This inverts the
50
- * subset those options are actually written in: anchors, non-capturing groups, one-segment
51
- * wildcards and escaped separators. It is deliberately not a general inverse, and it never has to
52
- * be trusted: the caller tests the result against the pattern it came from and refuses when it
53
- * does not match, which turns a wrong guess into a sentence rather than into a silent zero.
54
- */
55
12
  export declare const pathMatching: (pattern: string, leaf: string) => string;
@@ -1,20 +1,2 @@
1
- /**
2
- * A pure re-export shim (only `export … from`, no component) is a compatibility alias left behind
3
- * when a component moves to a feature folder — not the tier it sits in. Three rules read this off
4
- * the file, each with its own copy.
5
- */
6
1
  export declare function isReexportOnly(file: string): boolean;
7
- /**
8
- * Where a shim's code LIVES, when that is not the shim's own tier — or undefined when the file is
9
- * not a pure re-export shim at all.
10
- *
11
- * The three "must" rules exempted every shim outright, and that was #11's hole surviving in three
12
- * rules the direction rules' fix did not reach (row 594): `cells/laundered.tsx` re-exporting a
13
- * stateless compound is a cell that holds no state, and `tissues/alias.tsx` re-exporting a compound
14
- * is a tissue that composes nothing. Each name the shim hands on is followed to the file that
15
- * declares it, as the direction rules follow it; an `export *` is judged at the module it hands
16
- * through. A home in the shim's own tier is the alias the exemption was written for and is not
17
- * named. A re-export nothing on disk answers — a bare package — is not named either: unresolved
18
- * stays exempt, exactly as before.
19
- */
20
2
  export declare const launderedHomesOf: (file: string) => string[] | undefined;
@@ -1,8 +1,3 @@
1
- /**
2
- * Whether a file RENDERS one of the tiers it imports, not merely names it — a gate satisfied by an
3
- * import nobody renders was not satisfied. `molecule-must-compose` and `tissue-must-compose` both
4
- * read this off the import list and the JSX tree; each had its own copy of the reader.
5
- */
6
1
  type ImportSpecifierNode = {
7
2
  imported?: {
8
3
  name?: string;
@@ -45,10 +40,7 @@ export type JsxOpeningElementNode = {
45
40
  };
46
41
  type: 'JSXOpeningElement';
47
42
  };
48
- /** The identifier a JSX tag renders under — `<Foo/>`'s own name, or `<NS.Foo/>`'s namespace root. */
49
43
  export declare const rootTagNameOf: (name: JsxOpeningElementNode['name'] | undefined) => string | undefined;
50
- /** The local names an import brings in, added to `into` when its source composes and it is a value. */
51
44
  export declare const collectCompositionLocals: (node: CompositionImportNode, isComposition: (source: string) => boolean, into: Set<string>) => void;
52
- /** Whether any of the imported locals was actually rendered as a JSX tag. */
53
45
  export declare const rendersAnyOf: (locals: ReadonlySet<string>, renderedTagNames: ReadonlySet<string>) => boolean;
54
46
  export {};
@@ -1,13 +1,6 @@
1
- /**
2
- * Which features may be reached ACROSS feature boundaries is one repo's layout, so it is an
3
- * option — `organelle-dependency` and `cell-no-cross-feature-organelles` draw the same boundary
4
- * from two sides, on the same option, with the same default: the one name every feature-sliced
5
- * repo agrees on.
6
- */
7
1
  export type SharedFeaturesOptions = {
8
2
  sharedFeatures?: string[];
9
3
  };
10
- /** The JSON schema for `{ sharedFeatures?: string[] }`, read by both rules that take it. */
11
4
  export declare const SHARED_FEATURES_SCHEMA: {
12
5
  additionalProperties: boolean;
13
6
  properties: {
@@ -20,5 +13,4 @@ export declare const SHARED_FEATURES_SCHEMA: {
20
13
  };
21
14
  type: 'object';
22
15
  }[];
23
- /** Whether `importedFeature` is a DIFFERENT feature the options have not named as shared. */
24
16
  export declare const crossesFeatureBoundary: (importedFeature: string | undefined, ownFeature: string | undefined, options: SharedFeaturesOptions) => boolean;
@@ -1,14 +1,7 @@
1
1
  // src/rules/lib/shared-features.ts
2
+ import { optionsBag, STRINGS } from "./schema.js";
2
3
  var DEFAULT_SHARED_FEATURES = ["shared"];
3
- var SHARED_FEATURES_SCHEMA = [
4
- {
5
- additionalProperties: false,
6
- properties: {
7
- sharedFeatures: { items: { type: "string" }, type: "array" }
8
- },
9
- type: "object"
10
- }
11
- ];
4
+ var SHARED_FEATURES_SCHEMA = optionsBag({ sharedFeatures: STRINGS });
12
5
  var crossesFeatureBoundary = (importedFeature, ownFeature, options) => importedFeature !== undefined && importedFeature !== ownFeature && !new Set(options.sharedFeatures ?? DEFAULT_SHARED_FEATURES).has(importedFeature);
13
6
  export {
14
7
  SHARED_FEATURES_SCHEMA,
@@ -0,0 +1,8 @@
1
+ export declare const FORM_SOURCES: Set<string>;
2
+ export declare const FORM_CONTEXT_IMPORT_PATTERN: RegExp;
3
+ export declare const isProvidersSource: (source: string) => boolean;
4
+ export interface StateSources {
5
+ forms: Set<string>;
6
+ stores: Set<string>;
7
+ }
8
+ export declare const stateSourcesOf: (file: string, seen?: Set<string>) => StateSources;
@@ -0,0 +1,55 @@
1
+ // src/rules/lib/state-sources.ts
2
+ import { STORE_HOOK_NAME } from "./hooks.js";
3
+ import { isRelative } from "./imports.js";
4
+ import { fileFor, readSource } from "./module-text.js";
5
+ import { inTier } from "./tiers.js";
6
+ var FORM_SOURCES = new Set(["react-hook-form"]);
7
+ var FORM_CONTEXT_IMPORT_PATTERN = /FormContext$/;
8
+ var isProvidersSource = (source) => /(?:^|\/)providers(?:\/|$)/.test(source);
9
+ var IMPORT = /\bimport\s+(type\s+)?([^'";]*?)\s*from\s*['"]([^'"]+)['"]/g;
10
+ var STORE_CALL = /(?<!function\s)\b(use\w+Store)\s*\(/g;
11
+ var DEPTH = 4;
12
+ var localsOf = (clause) => {
13
+ const named = /\{([^}]*)\}/.exec(clause)?.[1] ?? "";
14
+ const bare = clause.replace(/\{[^}]*\}/, "").replaceAll(",", " ").trim();
15
+ const fromBare = /^[A-Za-z_$][\w$]*$/.test(bare) ? [bare] : [];
16
+ const fromNamed = named.split(",").map((one) => one.trim()).filter((one) => one !== "" && !one.startsWith("type ")).map((one) => one.split(/\s+as\s+/).at(-1) ?? "");
17
+ return [...fromBare, ...fromNamed].filter((one) => one !== "");
18
+ };
19
+ var importsOf = (text) => [...text.matchAll(IMPORT)].flatMap((found) => found[1] === undefined ? [{ locals: localsOf(found[2] ?? ""), source: found[3] ?? "" }] : []);
20
+ var isCalled = (text, name) => new RegExp(String.raw`(?<![\w$.])${name.replaceAll("$", String.raw`\$`)}\s*\(`).test(text);
21
+ var gather = (file, seen, depth, into) => {
22
+ if (seen.has(file) || depth > DEPTH)
23
+ return;
24
+ seen.add(file);
25
+ const text = readSource(file);
26
+ if (text === undefined)
27
+ return;
28
+ for (const found of text.matchAll(STORE_CALL)) {
29
+ const name = found[1] ?? "";
30
+ if (STORE_HOOK_NAME.test(name))
31
+ into.stores.add(name);
32
+ }
33
+ for (const one of importsOf(text)) {
34
+ if (FORM_SOURCES.has(one.source))
35
+ into.forms.add(one.source);
36
+ else if (isProvidersSource(one.source) && one.locals.some((local) => FORM_CONTEXT_IMPORT_PATTERN.test(local))) {
37
+ into.forms.add(one.source);
38
+ } else if (isRelative(one.source) && one.locals.some((local) => isCalled(text, local))) {
39
+ const next = fileFor(file, one.source);
40
+ if (next !== undefined && inTier(next, "organelles"))
41
+ gather(next, seen, depth + 1, into);
42
+ }
43
+ }
44
+ };
45
+ var stateSourcesOf = (file, seen = new Set) => {
46
+ const into = { forms: new Set, stores: new Set };
47
+ gather(file, seen, 0, into);
48
+ return into;
49
+ };
50
+ export {
51
+ FORM_CONTEXT_IMPORT_PATTERN,
52
+ FORM_SOURCES,
53
+ isProvidersSource,
54
+ stateSourcesOf
55
+ };
@@ -1,11 +1,10 @@
1
1
  import { type AnyNode } from './nodes.js';
2
+ export declare const DEFAULT_STEP_FACTORIES: string[];
3
+ export declare const DEFAULT_WORKFLOW_FACTORIES: string[];
4
+ export declare const workflowFactoriesOf: (rule: string, configured: string[] | undefined) => Set<string>;
2
5
  export declare const stepFactoriesOf: (rule: string, configured: string[] | undefined) => Set<string>;
3
6
  export declare const isStepCall: (node: AnyNode, factories: ReadonlySet<string>) => boolean;
4
7
  export declare const argumentsOf: (node: AnyNode) => AnyNode[];
5
- /**
6
- * The step's config, told apart three ways: a bare string name (a call that can carry no config at
7
- * all), an object this rule can read, and an object it cannot — a spread, or a variable.
8
- */
9
8
  export type StepConfig = {
10
9
  kind: 'bare';
11
10
  } | {
@@ -15,5 +14,4 @@ export type StepConfig = {
15
14
  kind: 'unreadable';
16
15
  };
17
16
  export declare const configOf: (node: AnyNode) => StepConfig;
18
- /** Whatever names the step in a message: its literal name, its config's `name`, or the factory. */
19
17
  export declare const stepNameOf: (node: AnyNode, config: StepConfig) => string;
@@ -3,6 +3,8 @@ import { calleeNameOf } from "./nodes.js";
3
3
  import { propertiesOf, stringOf, valueOf } from "./objects.js";
4
4
  import { requireOption } from "./options.js";
5
5
  var DEFAULT_STEP_FACTORIES = ["createStep"];
6
+ var DEFAULT_WORKFLOW_FACTORIES = ["createWorkflow"];
7
+ var workflowFactoriesOf = (rule, configured) => new Set(requireOption(rule, configured ?? DEFAULT_WORKFLOW_FACTORIES, "workflowFactories"));
6
8
  var stepFactoriesOf = (rule, configured) => new Set(requireOption(rule, configured ?? DEFAULT_STEP_FACTORIES, "stepFactories"));
7
9
  var isStepCall = (node, factories) => {
8
10
  const callee = calleeNameOf(node);
@@ -29,9 +31,12 @@ var stepNameOf = (node, config) => {
29
31
  return calleeNameOf(node) ?? "a step";
30
32
  };
31
33
  export {
34
+ DEFAULT_STEP_FACTORIES,
35
+ DEFAULT_WORKFLOW_FACTORIES,
32
36
  argumentsOf,
33
37
  configOf,
34
38
  isStepCall,
35
39
  stepFactoriesOf,
36
- stepNameOf
40
+ stepNameOf,
41
+ workflowFactoriesOf
37
42
  };