@geonosis/oxlint-plugin-biological-architecture 1.3.0 → 2.0.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 (53) hide show
  1. package/README.md +1 -1
  2. package/corpus/.oxlintrc.json +97 -5
  3. package/corpus/domains/document/workflows/bad-per-kind-workflow.ts +5 -0
  4. package/corpus/domains/document/workflows/good-minted-workflow.ts +8 -0
  5. package/corpus/engine/bad-durable-free-read.ts +20 -0
  6. package/corpus/engine/bad-no-retry.ts +8 -0
  7. package/corpus/engine/good-durable-other-spelling.ts +10 -0
  8. package/corpus/engine/good-durable-read-is-a-step.ts +18 -0
  9. package/corpus/engine/good-durable-read-through-helper.ts +20 -0
  10. package/corpus/engine/good-inline-free-read.ts +7 -0
  11. package/corpus/engine/good-retry-budget.ts +9 -0
  12. package/corpus/floors/domains/alpha/src/good-downhill.ts +3 -0
  13. package/corpus/floors/domains/alpha/src/index.ts +1 -0
  14. package/corpus/floors/domains/alpha/src/reaches-own-ui.ts +3 -0
  15. package/corpus/floors/domains/alpha/src/reaches-sideways-type.ts +3 -0
  16. package/corpus/floors/domains/alpha/src/reaches-sideways.ts +3 -0
  17. package/corpus/floors/domains/alpha/src/ui/panel.ts +3 -0
  18. package/corpus/floors/domains/beta/src/index.ts +3 -0
  19. package/corpus/floors/foundations/above/src/good-downhill.ts +3 -0
  20. package/corpus/floors/foundations/base/src/good-downhill.ts +3 -0
  21. package/corpus/floors/foundations/base/src/index.ts +1 -0
  22. package/corpus/floors/foundations/base/src/reaches-sideways.ts +3 -0
  23. package/corpus/floors/foundations/base/src/reaches-up.ts +3 -0
  24. package/corpus/floors/packages/method/src/own.ts +1 -0
  25. package/corpus/floors/packages/method/src/reaches-up.ts +3 -0
  26. package/corpus/floors.oxlintrc.json +86 -0
  27. package/corpus/manifest.json +4 -0
  28. package/corpus/subpaths/bad-root-barrel.ts +5 -0
  29. package/corpus/subpaths/good-spans-two-subpaths.ts +5 -0
  30. package/corpus/subpaths/good-subpath.ts +4 -0
  31. package/corpus/subpaths/good-type-only.ts +4 -0
  32. package/corpus/time/bad-boundary-type.ts +3 -0
  33. package/corpus/time/bad-second-door.ts +6 -0
  34. package/corpus/time/door.ts +15 -0
  35. package/corpus/time/generated/from-openapi.ts +1 -0
  36. package/corpus/time/good-boundary-primitive.ts +3 -0
  37. package/corpus/todos/bad-unplanned.ts +2 -0
  38. package/corpus/todos/good-planned.ts +2 -0
  39. package/corpus/todos/good-string-not-a-comment.ts +2 -0
  40. package/corpus/workers/good-with-cells.ts +7 -0
  41. package/corpus/workers/good-with-connection.ts +7 -0
  42. package/corpus/workflows/bad-unscoped-host.ts +7 -0
  43. package/corpus/workflows/good-host-config-elsewhere.ts +6 -0
  44. package/corpus/workflows/good-scoped-entrypoint.ts +17 -0
  45. package/corpus/workflows/good-scoped-host.ts +11 -0
  46. package/dist/{chunk-UVENNNFM.js → chunk-LTDP2733.js} +36 -3
  47. package/dist/index.d.ts +17 -2
  48. package/dist/index.js +1115 -370
  49. package/dist/presets.d.ts +31 -2
  50. package/dist/presets.js +5 -1
  51. package/package.json +2 -2
  52. /package/corpus/{packages/workflows/src → domains/document/workflows}/bad-per-kind-saga.ts +0 -0
  53. /package/corpus/{apps/api/src → workers}/bad-unscoped-worker.ts +0 -0
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  PRESETS,
3
+ PRESUMED_ENGINES,
4
+ RENAMED_TO,
3
5
  rulesOfPreset
4
- } from "./chunk-UVENNNFM.js";
6
+ } from "./chunk-LTDP2733.js";
5
7
 
6
8
  // src/index.ts
7
9
  import { createRequire } from "module";
@@ -19,6 +21,15 @@ var literalNamedBy = (node, callee) => {
19
21
  // src/rules/lib/paths.ts
20
22
  var normalise = (filename) => filename.replaceAll("\\", "/");
21
23
  var fileOf = (path2) => path2.split("/").pop() || path2;
24
+ var isDoor = (filename, door) => {
25
+ const path2 = normalise(filename);
26
+ if (path2 === door || path2.endsWith(`/${door}`)) return true;
27
+ try {
28
+ return new RegExp(door).test(path2);
29
+ } catch {
30
+ return false;
31
+ }
32
+ };
22
33
 
23
34
  // src/rules/lib/imports.ts
24
35
  var REQUIRE = "require";
@@ -160,6 +171,252 @@ tier or into a plain \`lib/\` module, never the other way round.`,
160
171
  });
161
172
  var atom_no_deps_default = atomNoDeps;
162
173
 
174
+ // src/rules/lib/nodes.ts
175
+ var walkNode = (node, visit) => {
176
+ const seen = /* @__PURE__ */ new Set();
177
+ const stack = [node];
178
+ while (stack.length > 0) {
179
+ const at = stack.pop();
180
+ if (at === null || typeof at !== "object" || seen.has(at)) continue;
181
+ seen.add(at);
182
+ if (Array.isArray(at)) {
183
+ stack.push(...at);
184
+ continue;
185
+ }
186
+ const record2 = at;
187
+ if (typeof record2.type === "string") visit(record2);
188
+ for (const [key, value] of Object.entries(record2)) {
189
+ if (key === "parent") continue;
190
+ if (value !== null && typeof value === "object") stack.push(value);
191
+ }
192
+ }
193
+ };
194
+ var propertyNameOf = (node) => {
195
+ if (node.type !== "MemberExpression" || node.computed === true) return null;
196
+ const property = node.property;
197
+ return property?.type === "Identifier" && typeof property.name === "string" ? property.name : null;
198
+ };
199
+ var calleeNameOf = (node) => {
200
+ const callee = node.callee;
201
+ return callee?.type === "Identifier" && typeof callee.name === "string" ? callee.name : null;
202
+ };
203
+
204
+ // src/rules/lib/options.ts
205
+ var NOTHING = "nothing";
206
+ var isMissing = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
207
+ var refusalHeader = (rule, option) => `biological-architecture/${rule} requires option \`${option}\`.`;
208
+ var OptionMissing = class extends Error {
209
+ option;
210
+ rule;
211
+ constructor(rule, option, message) {
212
+ super(message);
213
+ this.name = "OptionMissing";
214
+ this.option = option;
215
+ this.rule = rule;
216
+ }
217
+ };
218
+ var requireOption = (rule, value, option, accepts) => {
219
+ if (isMissing(value)) {
220
+ throw new OptionMissing(
221
+ rule,
222
+ option,
223
+ `${refusalHeader(rule, option)} It is enabled but its \`${option}\` option is ${NOTHING}, so it can never fire. Configure \`${option}\`, or turn the rule off \u2014 an enabled rule that cannot fire reads exactly like a rule that found nothing.${accepts === void 0 ? "" : ` It accepts ${accepts}.`}`
224
+ );
225
+ }
226
+ return value;
227
+ };
228
+ var requireOptionHere = (rule, value, option, file, accepts) => {
229
+ if (isMissing(value)) {
230
+ throw new OptionMissing(
231
+ rule,
232
+ option,
233
+ `${refusalHeader(rule, option)} It is enabled over "${file}", which it would judge, and its \`${option}\` option is ${NOTHING}. Configure \`${option}\`, or turn the rule off for this tree.${accepts === void 0 ? "" : ` It accepts ${accepts}.`}`
234
+ );
235
+ }
236
+ return value;
237
+ };
238
+ var raisedOver = /* @__PURE__ */ new Map();
239
+ var fileOf2 = (context) => {
240
+ const named = context?.filename;
241
+ return typeof named === "string" ? named : "";
242
+ };
243
+ var refusingOncePerRun = (create) => (context) => {
244
+ try {
245
+ return create(context);
246
+ } catch (error) {
247
+ if (!(error instanceof OptionMissing)) throw error;
248
+ const file = fileOf2(context);
249
+ if (file === "") throw error;
250
+ const key = `${error.rule}/${error.option}`;
251
+ const first = raisedOver.get(key);
252
+ if (first !== void 0 && first !== file) return {};
253
+ raisedOver.set(key, file);
254
+ throw error;
255
+ }
256
+ };
257
+
258
+ // src/rules/lib/probe.ts
259
+ var bagOf = (options) => typeof options[0] === "object" && options[0] !== null && !Array.isArray(options[0]) ? options[0] : {};
260
+ var firstOf = (configured, fallback) => {
261
+ const [first] = configured !== void 0 && configured.length > 0 ? configured : fallback;
262
+ if (first === void 0) throw new Error("no value and no default to build a probe from");
263
+ return first;
264
+ };
265
+ var LOOKAHEAD = /\(\?[!=][^)]*\)/g;
266
+ var OPTIONAL_GROUP = /\(\?:[^)]*\)\?/g;
267
+ var FIRST_ALTERNATIVE = /\(\?:([^)|]*)\|[^)]*\)/g;
268
+ var GROUP = /\(\?:([^)]*)\)/g;
269
+ var ANY_SEGMENT = /\[\^\/\]\+|\.\+|\.\*/g;
270
+ var OPTIONAL_CLASS = /\[[^\]]*\]\?/g;
271
+ var CLASS = /\[([^\]])[^\]]*\]/g;
272
+ var OPTIONAL_CHAR = /[^/]\?/g;
273
+ var QUANTIFIER = /[*+]/g;
274
+ var LINTABLE = /\.[cm]?[jt]sx?$/;
275
+ var EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
276
+ var pathMatching = (pattern, leaf) => {
277
+ const literal = pattern.replaceAll(LOOKAHEAD, "").replaceAll(OPTIONAL_GROUP, "").replaceAll(FIRST_ALTERNATIVE, "$1").replaceAll(GROUP, "$1").replaceAll(ANY_SEGMENT, "probe").replaceAll(OPTIONAL_CLASS, "").replaceAll(CLASS, "$1").replaceAll(String.raw`\.`, ".").replaceAll(String.raw`\/`, "/").replaceAll(OPTIONAL_CHAR, "").replaceAll("^", "").replaceAll("$", "").replaceAll(QUANTIFIER, "");
278
+ const here = new RegExp(pattern);
279
+ const candidates = [
280
+ literal.endsWith("/") ? `${literal}${leaf}` : literal,
281
+ ...EXTENSIONS.map((extension) => `${literal}${extension}`)
282
+ ];
283
+ const found = candidates.find((one) => LINTABLE.test(one) && here.test(one));
284
+ if (found === void 0) {
285
+ throw new Error(
286
+ `no probe path can be built from the pattern ${pattern}: nothing of the shape "${candidates[0] ?? ""}" both matches it and is a file oxlint would parse. Write the file by hand, or narrow the option to a path a probe can be placed at.`
287
+ );
288
+ }
289
+ return found;
290
+ };
291
+
292
+ // src/rules/boundary-time-is-primitive.ts
293
+ var RULE = "boundary-time-is-primitive";
294
+ var ACCEPTS = "{ types: string[] (type or namespace names), doors: string[] (a path, a specifier, or a regex over the filename; default none) }";
295
+ var asNode = (value) => typeof value === "object" && value !== null ? value : void 0;
296
+ var nameOf = (value) => {
297
+ const name = asNode(value)?.["name"];
298
+ return typeof name === "string" ? name : void 0;
299
+ };
300
+ var referencedType = (reference) => {
301
+ const typeName = asNode(reference["typeName"]);
302
+ if (typeName === void 0) return void 0;
303
+ if (typeName.type === "Identifier") return nameOf(typeName);
304
+ if (typeName.type !== "TSQualifiedName") return void 0;
305
+ const namespace = nameOf(typeName["left"]);
306
+ const member = nameOf(typeName["right"]);
307
+ return namespace === void 0 || member === void 0 ? void 0 : `${namespace}.${member}`;
308
+ };
309
+ var forbids = (types, referenced) => types.includes(referenced) || types.includes(referenced.split(".")[0] ?? referenced);
310
+ var functionSignature = (value) => {
311
+ const fn = asNode(value);
312
+ if (fn?.type !== "ArrowFunctionExpression" && fn?.type !== "FunctionExpression") return [];
313
+ return [fn["params"], fn["returnType"]];
314
+ };
315
+ var signatureOf = (declaration) => {
316
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction") {
317
+ return [declaration["params"], declaration["returnType"]];
318
+ }
319
+ if (declaration.type === "VariableDeclaration") {
320
+ const declarators = Array.isArray(declaration["declarations"]) ? declaration["declarations"] : [];
321
+ return declarators.flatMap((one) => [
322
+ asNode(one)?.["id"] === void 0 ? void 0 : asNode(asNode(one)?.["id"])?.["typeAnnotation"],
323
+ ...functionSignature(asNode(one)?.["init"])
324
+ ]);
325
+ }
326
+ if (declaration.type === "TSTypeAliasDeclaration") return [declaration["typeAnnotation"]];
327
+ if (declaration.type === "TSInterfaceDeclaration") return [declaration["body"]];
328
+ if (declaration.type === "ClassDeclaration") {
329
+ const members = asNode(declaration["body"])?.["body"];
330
+ return (Array.isArray(members) ? members : []).flatMap((member) => [
331
+ asNode(member)?.["typeAnnotation"],
332
+ ...functionSignature(asNode(member)?.["value"])
333
+ ]);
334
+ }
335
+ return [];
336
+ };
337
+ var declaredName = (declaration) => {
338
+ const own = nameOf(declaration["id"]);
339
+ if (own !== void 0) return own;
340
+ const declarators = Array.isArray(declaration["declarations"]) ? declaration["declarations"] : [];
341
+ return nameOf(asNode(declarators[0])?.["id"]) ?? "the export";
342
+ };
343
+ var behindADoor = (filename, doors) => doors.some((door) => isDoor(filename, door));
344
+ var boundaryTimeIsPrimitive = {
345
+ create(context) {
346
+ const types = requireOption(RULE, context.options?.[0]?.types, "types", ACCEPTS);
347
+ const doors = context.options?.[0]?.doors ?? [];
348
+ if (behindADoor(context.filename, doors)) return {};
349
+ const behind = doors.length === 0 ? "the module that owns the library" : doors.join(", ");
350
+ const report = (node) => {
351
+ const declaration = asNode(node.declaration);
352
+ if (declaration === void 0) return;
353
+ for (const root of signatureOf(declaration)) {
354
+ walkNode(root, (found) => {
355
+ if (found.type !== "TSTypeReference") return;
356
+ const referenced = referencedType(found);
357
+ if (referenced === void 0 || !forbids(types, referenced)) return;
358
+ context.report({
359
+ data: { doors: behind, name: declaredName(declaration), type: referenced },
360
+ messageId: "libraryTypeAtBoundary",
361
+ node: found
362
+ });
363
+ });
364
+ }
365
+ };
366
+ return {
367
+ ExportNamedDeclaration: report,
368
+ ExportDefaultDeclaration: report
369
+ };
370
+ },
371
+ fixShape: `An exported signature carries time as a PRIMITIVE: epoch milliseconds for an instant, an ISO date
372
+ string (\`YYYY-MM-DD\`) for a business date. A \`Date\`, a \`Dayjs\` or a \`Temporal\` type in a parameter,
373
+ a return or an exported shape hands every caller the library and its configuration, and no database
374
+ column, wire message or contract can hold one at all \u2014 the boundary is where the conversion belongs.
375
+ Convert at the door the \`doors\` option names; that module is the one place allowed to say the
376
+ library's type out loud, and a display string is produced at the edge from the primitive rather than
377
+ carried across as a formatted value. Only the signature is judged: a \`Date\` a function builds and
378
+ keeps inside its own body is its own business.`,
379
+ meta: {
380
+ docs: {
381
+ description: "A date-library type in an exported signature couples every caller to the library and its configuration. Time crosses a boundary as a primitive \u2014 epoch milliseconds or an ISO date string \u2014 and the library type stays inside the module the `doors` option names. Enabled without `types` it refuses the run."
382
+ },
383
+ messages: {
384
+ libraryTypeAtBoundary: '"{{name}}" puts `{{type}}` in an exported signature. Time crosses a boundary as a primitive \u2014 epoch milliseconds for an instant, an ISO date string for a business date \u2014 and `{{type}}` stays behind {{doors}}.'
385
+ },
386
+ schema: [
387
+ {
388
+ additionalProperties: false,
389
+ properties: {
390
+ doors: { items: { type: "string" }, type: "array" },
391
+ types: { items: { type: "string" }, type: "array" }
392
+ },
393
+ type: "object"
394
+ }
395
+ ],
396
+ type: "problem"
397
+ },
398
+ /**
399
+ * A file that is not one of the declared doors, exporting the repo's own first forbidden type.
400
+ * `String(at)` rather than a second annotation: one finding is the claim, and a probe that fired
401
+ * twice would pass a count nobody asked for.
402
+ */
403
+ probe(options) {
404
+ const { doors = [], types = [] } = bagOf(options);
405
+ const type = firstOf(types, []);
406
+ const path2 = ["geonosis-probe-boundary.ts", "src/geonosis-probe-boundary.ts"].find(
407
+ (candidate) => !behindADoor(candidate, doors)
408
+ );
409
+ if (path2 === void 0) {
410
+ throw new Error(
411
+ `biological-architecture/${RULE}: every path a probe could be written at is itself one of the declared \`doors\` (${doors.join(", ")}), so there is nowhere left for this rule to speak.`
412
+ );
413
+ }
414
+ return { path: path2, source: `export const boundary = (at: ${type}): string => String(at)
415
+ ` };
416
+ }
417
+ };
418
+ var boundary_time_is_primitive_default = boundaryTimeIsPrimitive;
419
+
163
420
  // src/rules/cell-must-be-stateful.ts
164
421
  import * as fs from "fs";
165
422
  var STATE_SIGNATURE = /\buse[A-Z]\w*\s*\(|from\s+['"][^'"]*\/organelles(?:\/|['"])|from\s+['"][^'"]*\/stores?(?:\/|['"])|createContext\s*\(/;
@@ -590,66 +847,44 @@ configs. Everywhere else, use the dialect-free aliases the seam exports (\`Datab
590
847
  };
591
848
  var dialect_through_the_seam_default = dialectThroughTheSeam;
592
849
 
593
- // src/rules/lib/options.ts
594
- var NOTHING = "nothing";
595
- var isMissing = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
596
- var requireOption = (rule, value, option, accepts) => {
597
- if (isMissing(value)) {
598
- throw new Error(
599
- `biological-architecture/${rule} is enabled but its \`${option}\` option is ${NOTHING}, so it can never fire. Configure \`${option}\`, or turn the rule off \u2014 an enabled rule that cannot fire reads exactly like a rule that found nothing.${accepts === void 0 ? "" : ` It accepts ${accepts}.`}`
600
- );
601
- }
602
- return value;
603
- };
604
-
605
- // src/rules/lib/probe.ts
606
- var bagOf = (options) => typeof options[0] === "object" && options[0] !== null && !Array.isArray(options[0]) ? options[0] : {};
607
- var firstOf = (configured, fallback) => {
608
- const [first] = configured !== void 0 && configured.length > 0 ? configured : fallback;
609
- if (first === void 0) throw new Error("no value and no default to build a probe from");
610
- return first;
611
- };
612
- var LOOKAHEAD = /\(\?[!=][^)]*\)/g;
613
- var OPTIONAL_GROUP = /\(\?:[^)]*\)\?/g;
614
- var FIRST_ALTERNATIVE = /\(\?:([^)|]*)\|[^)]*\)/g;
615
- var GROUP = /\(\?:([^)]*)\)/g;
616
- var ANY_SEGMENT = /\[\^\/\]\+|\.\+|\.\*/g;
617
- var OPTIONAL_CLASS = /\[[^\]]*\]\?/g;
618
- var CLASS = /\[([^\]])[^\]]*\]/g;
619
- var OPTIONAL_CHAR = /[^/]\?/g;
620
- var QUANTIFIER = /[*+]/g;
621
- var LINTABLE = /\.[cm]?[jt]sx?$/;
622
- var EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
623
- var pathMatching = (pattern, leaf) => {
624
- const literal = pattern.replaceAll(LOOKAHEAD, "").replaceAll(OPTIONAL_GROUP, "").replaceAll(FIRST_ALTERNATIVE, "$1").replaceAll(GROUP, "$1").replaceAll(ANY_SEGMENT, "probe").replaceAll(OPTIONAL_CLASS, "").replaceAll(CLASS, "$1").replaceAll(String.raw`\.`, ".").replaceAll(String.raw`\/`, "/").replaceAll(OPTIONAL_CHAR, "").replaceAll("^", "").replaceAll("$", "").replaceAll(QUANTIFIER, "");
625
- const here = new RegExp(pattern);
626
- const candidates = [
627
- literal.endsWith("/") ? `${literal}${leaf}` : literal,
628
- ...EXTENSIONS.map((extension) => `${literal}${extension}`)
629
- ];
630
- const found = candidates.find((one) => LINTABLE.test(one) && here.test(one));
631
- if (found === void 0) {
632
- throw new Error(
633
- `no probe path can be built from the pattern ${pattern}: nothing of the shape "${candidates[0] ?? ""}" both matches it and is a file oxlint would parse. Write the file by hand, or narrow the option to a path a probe can be placed at.`
634
- );
635
- }
636
- return found;
637
- };
638
-
639
850
  // src/rules/document-sagas-are-generic.ts
640
851
  var escapeForRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
852
+ var RULE2 = "document-sagas-are-generic";
853
+ var DEFAULT_FACTORIES = ["saga", "createWorkflow"];
854
+ var perEntityNameOf = (node, factories) => {
855
+ for (const factory of factories) {
856
+ const name = literalNamedBy(node, factory);
857
+ if (name !== null) return name;
858
+ }
859
+ return null;
860
+ };
861
+ var declaresAFlow = (text, factories) => factories.some((factory) => new RegExp(`\\b${escapeForRegex(factory)}\\s*\\(`).test(text));
641
862
  var documentSagasAreGeneric = {
642
863
  create(context) {
643
- const prefixes = requireOption(
644
- "document-sagas-are-generic",
864
+ const factories = requireOption(
865
+ RULE2,
866
+ context.options?.[0]?.factories ?? DEFAULT_FACTORIES,
867
+ "factories"
868
+ );
869
+ const filename = normalise(context.filename);
870
+ const named = context.options?.[0]?.within;
871
+ const insideTree = named === void 0 || named === "" ? void 0 : new RegExp(named).test(filename);
872
+ if (insideTree === false) return {};
873
+ if (insideTree === void 0 && !declaresAFlow(context.sourceCode?.text ?? "", factories)) {
874
+ return {};
875
+ }
876
+ const prefixes = requireOptionHere(
877
+ RULE2,
645
878
  context.options?.[0]?.perKindPrefixes,
646
- "perKindPrefixes"
879
+ "perKindPrefixes",
880
+ filename
647
881
  );
648
- if (!/\/packages\/workflows\/src\//.test(normalise(context.filename))) return {};
882
+ const within = requireOptionHere(RULE2, named, "within", filename);
883
+ if (!new RegExp(within).test(filename)) return {};
649
884
  const perKindPrefix = new RegExp(`^(?:${prefixes.map(escapeForRegex).join("|")})\\.`);
650
885
  return {
651
886
  CallExpression(node) {
652
- const name = literalNamedBy(node, "saga");
887
+ const name = perEntityNameOf(node, factories);
653
888
  if (name === null || !perKindPrefix.test(name)) return;
654
889
  context.report({ data: { name }, messageId: "perKindSaga", node });
655
890
  }
@@ -657,34 +892,41 @@ var documentSagasAreGeneric = {
657
892
  },
658
893
  fixShape: `A saga named for one document kind (\`invoice.issue\`, \`quote.accept\`) is the hand-written fork the
659
894
  generic base entity replaced. Mint it from the generic factory \u2014 \`documentSagas(entity, effects)\` \u2014
660
- and pass the kind as data. Configure the kind vocabulary through the rule's \`perKindPrefixes\` option; enabled without it the
661
- rule refuses the run.`,
895
+ and pass the kind as data. The same holds for a workflow: \`createWorkflow('invoice.create', \u2026)\` is
896
+ the fork, \`documentWorkflows(entity)\` is the mint. A name assembled from the entity \u2014 a template
897
+ literal \u2014 is what minting looks like and is left alone by construction. Configure the kind vocabulary
898
+ through \`perKindPrefixes\`, the tree that holds the flows through \`within\` (a regex over the
899
+ filename), and \`factories\` if the repo declares them under other names; enabled without
900
+ \`perKindPrefixes\` or \`within\` the rule refuses the run.`,
662
901
  meta: {
663
902
  docs: {
664
- description: "Document sagas are minted, not written: `documentSagas(entity, effects)` gives every entity that extends Document its create/save/setStatus/remove/send/convert/duplicate/share from one body. A saga literally named for one entity is a fork."
903
+ description: "Document sagas are minted, not written: `documentSagas(entity, effects)` gives every entity that extends Document its create/save/setStatus/remove/send/convert/duplicate/share from one body. A saga or workflow named with a LITERAL for one entity is a fork; a name assembled from the entity is the mint and is unreadable here on purpose. `factories`, `perKindPrefixes` and `within` are options."
665
904
  },
666
905
  messages: {
667
- perKindSaga: '"{{name}}" is a hand-written saga for one entity. Every entity that extends Document gets its sagas from `documentSagas(entity, effects)`; put what this entity decides in its `Document.extend` definition or in its `DocumentEffects`.'
906
+ perKindSaga: '"{{name}}" is a hand-written flow for one entity. Every entity that extends Document gets its sagas from `documentSagas(entity, effects)` and its workflows from the same mint; put what this entity decides in its `Document.extend` definition or in its `DocumentEffects`.'
668
907
  },
669
908
  schema: [
670
909
  {
671
910
  additionalProperties: false,
672
911
  properties: {
673
- perKindPrefixes: { items: { type: "string" }, type: "array" }
912
+ factories: { items: { type: "string" }, type: "array" },
913
+ perKindPrefixes: { items: { type: "string" }, type: "array" },
914
+ within: { type: "string" }
674
915
  },
675
916
  type: "object"
676
917
  }
677
918
  ],
678
919
  type: "problem"
679
920
  },
680
- /** The guarded tree is compiled in, so only the kind vocabulary has to come from the options. */
921
+ /** Every half comes from the options now: the vocabulary, the tree, and the factory it is minted by. */
681
922
  probe(options) {
682
- const { perKindPrefixes = [] } = bagOf(options);
923
+ const { perKindPrefixes = [], within = "" } = bagOf(options);
924
+ const factory = firstOf(bagOf(options).factories, DEFAULT_FACTORIES);
683
925
  return {
684
- path: "packages/workflows/src/geonosis-probe-saga.ts",
685
- source: `import { saga } from 'sagaflow-js'
926
+ path: pathMatching(within, "geonosis-probe-saga.ts"),
927
+ source: `import { ${factory} } from 'sagaflow-js'
686
928
 
687
- export const geonosisProbe = saga('${perKindPrefixes[0] ?? ""}.geonosis-probe', {})
929
+ export const geonosisProbe = ${factory}('${perKindPrefixes[0] ?? ""}.geonosis-probe', {})
688
930
  `
689
931
  };
690
932
  }
@@ -752,6 +994,170 @@ export const geonosisProbe = pgTable('${perKindTables[0] ?? ""}', { id: text('id
752
994
  };
753
995
  var documents_share_one_table_default = documentsShareOneTable;
754
996
 
997
+ // src/rules/lib/objects.ts
998
+ var propertiesOf = (node) => {
999
+ const object = node;
1000
+ if (object?.type !== "ObjectExpression") return null;
1001
+ const properties = object.properties ?? [];
1002
+ return properties.every((one) => one.type === "Property") ? properties : null;
1003
+ };
1004
+ var valueOf = (properties, name) => {
1005
+ const found = properties.find((one) => {
1006
+ const key = one.key;
1007
+ return key?.type === "Identifier" && key.name === name || key?.type === "Literal" && key.value === name;
1008
+ });
1009
+ return found?.value;
1010
+ };
1011
+ var hasProperty = (properties, name) => valueOf(properties, name) !== void 0;
1012
+ var stringOf = (node) => {
1013
+ const literal = node;
1014
+ return literal?.type === "Literal" && typeof literal.value === "string" ? literal.value : null;
1015
+ };
1016
+
1017
+ // src/rules/lib/steps.ts
1018
+ var DEFAULT_STEP_FACTORIES = ["createStep"];
1019
+ var stepFactoriesOf = (rule, configured) => new Set(requireOption(rule, configured ?? DEFAULT_STEP_FACTORIES, "stepFactories"));
1020
+ var isStepCall = (node, factories) => {
1021
+ const callee = calleeNameOf(node);
1022
+ return callee !== null && factories.has(callee);
1023
+ };
1024
+ var argumentsOf = (node) => node.arguments ?? [];
1025
+ var configOf = (node) => {
1026
+ const [first] = argumentsOf(node);
1027
+ if (first === void 0) return { kind: "unreadable" };
1028
+ if (stringOf(first) !== null) return { kind: "bare" };
1029
+ const properties = propertiesOf(first);
1030
+ return properties === null ? { kind: "unreadable" } : { kind: "object", properties };
1031
+ };
1032
+ var stepNameOf = (node, config) => {
1033
+ const [first] = argumentsOf(node);
1034
+ const bare = stringOf(first);
1035
+ if (bare !== null) return bare;
1036
+ if (config.kind === "object") {
1037
+ const named = config.properties.find((one) => {
1038
+ const key = one.key;
1039
+ return key?.type === "Identifier" && key.name === "name";
1040
+ });
1041
+ const literal = stringOf(named?.value);
1042
+ if (literal !== null) return literal;
1043
+ }
1044
+ return calleeNameOf(node) ?? "a step";
1045
+ };
1046
+
1047
+ // src/rules/durable-body-reads-through-steps.ts
1048
+ var RULE3 = "durable-body-reads-through-steps";
1049
+ var ACCEPTS2 = "{ ports: string[] (required), durableKey: string, durableValue: boolean | string, workflowFactories: string[], stepFactories: string[] }";
1050
+ var DEFAULT_WORKFLOW_FACTORIES = ["createWorkflow"];
1051
+ var DEFAULT_DURABLE_KEY = "execution";
1052
+ var DEFAULT_DURABLE_VALUE = "durable";
1053
+ var declaresDurable = (node, key, value) => argumentsOf(node).some((argument) => {
1054
+ const properties = propertiesOf(argument);
1055
+ if (properties === null) return false;
1056
+ const declared = valueOf(properties, key);
1057
+ return declared?.type === "Literal" && declared.value === value;
1058
+ });
1059
+ var portReachedBy = (node, ports) => {
1060
+ const direct = calleeNameOf(node);
1061
+ if (direct !== null) return ports.has(direct) ? direct : null;
1062
+ let at = node.callee ?? {};
1063
+ while (at.type === "MemberExpression") {
1064
+ const named = propertyNameOf(at);
1065
+ if (named !== null && ports.has(named)) return named;
1066
+ at = at.object;
1067
+ }
1068
+ return at.type === "Identifier" && ports.has(at.name) ? at.name : null;
1069
+ };
1070
+ var insideAStep = (node, factories) => {
1071
+ const memoised = /* @__PURE__ */ new Set();
1072
+ walkNode(node, (inner) => {
1073
+ if (isStepCall(inner, factories)) walkNode(inner, (one) => memoised.add(one));
1074
+ });
1075
+ return memoised;
1076
+ };
1077
+ var durableBodyReadsThroughSteps = {
1078
+ create(context) {
1079
+ const bag = context.options?.[0] ?? {};
1080
+ const ports = new Set(requireOption(RULE3, bag.ports, "ports", ACCEPTS2));
1081
+ const steps = stepFactoriesOf(RULE3, bag.stepFactories);
1082
+ const workflows = new Set(
1083
+ requireOption(RULE3, bag.workflowFactories ?? DEFAULT_WORKFLOW_FACTORIES, "workflowFactories")
1084
+ );
1085
+ const durableKey = requireOption(RULE3, bag.durableKey ?? DEFAULT_DURABLE_KEY, "durableKey");
1086
+ const durableValue = bag.durableValue ?? DEFAULT_DURABLE_VALUE;
1087
+ return {
1088
+ CallExpression(node) {
1089
+ const factory = calleeNameOf(node);
1090
+ if (factory === null || !workflows.has(factory)) return;
1091
+ if (!declaresDurable(node, durableKey, durableValue)) return;
1092
+ const [name] = argumentsOf(node);
1093
+ const workflow = stringOf(name) ?? factory;
1094
+ const memoised = insideAStep(node, steps);
1095
+ walkNode(node, (inner) => {
1096
+ if (inner.type !== "CallExpression" || memoised.has(inner)) return;
1097
+ const port = portReachedBy(inner, ports);
1098
+ if (port === null) return;
1099
+ context.report({ data: { port, workflow }, messageId: "freeRead", node: inner });
1100
+ });
1101
+ }
1102
+ };
1103
+ },
1104
+ fixShape: `In a DURABLE workflow body every value that gates a branch or feeds a step comes from the workflow
1105
+ input or from a step result. A port read written straight into the body \u2014 \`store.read(id)\` \u2014 is
1106
+ inline-only: the platform memoises steps by name and does not journal reads, so on a replay the read
1107
+ sees what the first invocation wrote, the branch flips, and the run unwinds work it had completed.
1108
+ Move the decision into a step (\`const doc = await ensureDraft(id)\`) and it is memoised with the run.
1109
+ Name this repo's ports in \`ports\`; enabled without them the rule refuses the run. What it can SEE is a
1110
+ call on a name \`ports\` lists: a read moved one function out \u2014 \`readStatus(id)\`, with the port inside
1111
+ it \u2014 is invisible here, and no lint rule that reads one file can follow it. Name the helper in
1112
+ \`ports\` too when a body reaches the outside world through one; a helper that is really a step is
1113
+ better made one. \`durableKey\` /
1114
+ \`durableValue\` say how a workflow declares itself durable, and \`workflowFactories\` /
1115
+ \`stepFactories\` how it declares its parts.`,
1116
+ meta: {
1117
+ docs: {
1118
+ description: "A durable workflow body reads through its steps. A platform memoises steps by name and journals no reads, so a free port read that gates a branch takes the other branch on a replay and reverses half a completed run. The same read inside a step is memoised with it; an inline body is invoked once and is exempt. `ports` is required."
1119
+ },
1120
+ messages: {
1121
+ freeRead: 'This durable body ("{{workflow}}") reads through `{{port}}` outside a step. A replay is answered from the run record for steps and re-runs everything else, so this read returns what the first invocation wrote and the branch it gates flips \u2014 reversing work the run had already completed. Make the decision a step, or take the value from the workflow input.'
1122
+ },
1123
+ schema: [
1124
+ {
1125
+ additionalProperties: false,
1126
+ properties: {
1127
+ durableKey: { type: "string" },
1128
+ durableValue: { type: ["boolean", "string"] },
1129
+ ports: { items: { type: "string" }, type: "array" },
1130
+ stepFactories: { items: { type: "string" }, type: "array" },
1131
+ workflowFactories: { items: { type: "string" }, type: "array" }
1132
+ },
1133
+ type: "object"
1134
+ }
1135
+ ],
1136
+ type: "problem"
1137
+ },
1138
+ /**
1139
+ * A durable body with one free read and no step at all: a step would be the remedy, and a probe
1140
+ * that carried one would be claiming a finding from the shape the rule accepts.
1141
+ */
1142
+ probe(options) {
1143
+ const bag = bagOf(options);
1144
+ const factory = firstOf(bag.workflowFactories, DEFAULT_WORKFLOW_FACTORIES);
1145
+ const port = firstOf(bag.ports, []);
1146
+ const key = bag.durableKey ?? DEFAULT_DURABLE_KEY;
1147
+ const value = bag.durableValue ?? DEFAULT_DURABLE_VALUE;
1148
+ return {
1149
+ path: "geonosis-probe-durable-body.ts",
1150
+ source: `export const geonosisProbe = ${factory}(
1151
+ 'geonosis-probe',
1152
+ async (input) => ${port}.read(input),
1153
+ { ${key}: ${typeof value === "string" ? `'${value}'` : value} },
1154
+ )
1155
+ `
1156
+ };
1157
+ }
1158
+ };
1159
+ var durable_body_reads_through_steps_default = durableBodyReadsThroughSteps;
1160
+
755
1161
  // src/rules/effect-hook-naming.ts
756
1162
  var HOOK_FILE_PATTERN = /(?:features\/[^/]+\/)?organelles\/.*\.ts$/;
757
1163
  var effectHookNaming = {
@@ -823,22 +1229,22 @@ file and the export together \u2014 half the pair still fires.`,
823
1229
  var effect_hook_naming_default = effectHookNaming;
824
1230
 
825
1231
  // src/rules/emit-declares-attempts.ts
826
- var RULE = "emit-declares-attempts";
1232
+ var RULE4 = "emit-declares-attempts";
827
1233
  var DEFAULT_EMITTERS = ["emitEventStep"];
828
1234
  var OPTIONS = "options";
829
1235
  var ATTEMPTS = "attempts";
830
1236
  var EVENT_NAME = "eventName";
831
1237
  var NO_RETRY = 1;
832
1238
  var objectOf = (node) => node?.type === "ObjectExpression" ? node : null;
833
- var propertiesOf = (object) => {
1239
+ var propertiesOf2 = (object) => {
834
1240
  const properties = object.properties ?? [];
835
1241
  return properties.every((one) => one.type === "Property") ? properties : null;
836
1242
  };
837
- var valueOf = (properties, name) => properties.find((one) => one.key?.type === "Identifier" && one.key.name === name)?.value;
1243
+ var valueOf2 = (properties, name) => properties.find((one) => one.key?.type === "Identifier" && one.key.name === name)?.value;
838
1244
  var emitDeclaresAttempts = {
839
1245
  create(context) {
840
1246
  const emitters = new Set(
841
- requireOption(RULE, context.options?.[0]?.emitters ?? DEFAULT_EMITTERS, "emitters")
1247
+ requireOption(RULE4, context.options?.[0]?.emitters ?? DEFAULT_EMITTERS, "emitters")
842
1248
  );
843
1249
  const budgetHelpers = new Set(context.options?.[0]?.budgetHelpers ?? []);
844
1250
  return {
@@ -848,11 +1254,11 @@ var emitDeclaresAttempts = {
848
1254
  if (!emitters.has(emitter)) return;
849
1255
  const payload = objectOf(node.arguments?.[0]);
850
1256
  if (payload === null) return;
851
- const properties = propertiesOf(payload);
1257
+ const properties = propertiesOf2(payload);
852
1258
  if (properties === null) return;
853
- const named = valueOf(properties, EVENT_NAME);
1259
+ const named = valueOf2(properties, EVENT_NAME);
854
1260
  const site = named?.type === "Literal" && typeof named.value === "string" ? named.value : emitter;
855
- const options = valueOf(properties, OPTIONS);
1261
+ const options = valueOf2(properties, OPTIONS);
856
1262
  if (options !== void 0 && objectOf(options) === null) {
857
1263
  if (budgetHelpers.size === 0) {
858
1264
  context.report({ data: { emitter, site }, messageId: "undeclaredHelpers", node });
@@ -875,9 +1281,9 @@ var emitDeclaresAttempts = {
875
1281
  });
876
1282
  return;
877
1283
  }
878
- const bag = options === void 0 ? [] : propertiesOf(objectOf(options));
1284
+ const bag = options === void 0 ? [] : propertiesOf2(objectOf(options));
879
1285
  if (bag === null) return;
880
- const attempts = valueOf(bag, ATTEMPTS);
1286
+ const attempts = valueOf2(bag, ATTEMPTS);
881
1287
  if (attempts === void 0) {
882
1288
  context.report({ data: { emitter, site }, messageId: "noBudget", node });
883
1289
  return;
@@ -1095,14 +1501,39 @@ var font_roles_only_default = fontRolesOnly;
1095
1501
 
1096
1502
  // src/rules/layer-walls.ts
1097
1503
  var isExternal = (source) => !source.startsWith("./") && !source.startsWith("../") && !source.startsWith("/");
1504
+ var PROBE_TARGET = "geonosis-probe-target.ts";
1505
+ var PROBE_WALL = "geonosis-probe-wall.ts";
1506
+ var inverts = (pattern, leaf) => {
1507
+ try {
1508
+ pathMatching(pattern, leaf);
1509
+ return true;
1510
+ } catch {
1511
+ return false;
1512
+ }
1513
+ };
1514
+ var placeIn = (paths, tried) => {
1515
+ for (const pattern of paths) {
1516
+ let candidate;
1517
+ try {
1518
+ candidate = pathMatching(pattern, PROBE_WALL);
1519
+ } catch {
1520
+ tried.push(pattern);
1521
+ continue;
1522
+ }
1523
+ if (new RegExp(pattern).test(`/repo-root/${candidate}`)) return candidate;
1524
+ tried.push(pattern);
1525
+ }
1526
+ return void 0;
1527
+ };
1098
1528
  var layerWalls = {
1099
1529
  create(context) {
1100
- const { externals = "allow", layers = [] } = context.options?.[0] ?? {};
1530
+ const { externals = "allow", layers = [], typeOnly = "skip" } = context.options?.[0] ?? {};
1101
1531
  requireOption("layer-walls", layers, "layers");
1102
1532
  const compiled = layers.map((layer) => ({
1103
1533
  mayImport: new Set(layer.mayImport ?? []),
1104
1534
  name: layer.name,
1105
- paths: layer.paths.map((source) => new RegExp(source))
1535
+ paths: layer.paths.map((source) => new RegExp(source)),
1536
+ typeOnly: layer.typeOnly ?? typeOnly
1106
1537
  }));
1107
1538
  const layerOf = (pathish) => compiled.find((layer) => layer.paths.some((pattern) => pattern.test(pathish)));
1108
1539
  const filename = normalise(context.filename);
@@ -1125,7 +1556,7 @@ var layerWalls = {
1125
1556
  node
1126
1557
  });
1127
1558
  };
1128
- return onImportSource(check);
1559
+ return onImportSource(check, { typeOnly: here.typeOnly === "count" ? "visit" : "skip" });
1129
1560
  },
1130
1561
  fixShape: `Layers are declared in the rule's \`layers\` option \u2014 \`{ name, paths, mayImport }\` \u2014 and a file may
1131
1562
  import its own layer plus the layers it names, nothing else. When this fires, MOVE something: the
@@ -1134,10 +1565,13 @@ belongs in the layer it keeps reaching for. Widening \`mayImport\` to make the d
1134
1565
  deleting the wall, and the wall is the only reason the layers are worth naming.
1135
1566
 
1136
1567
  A relative source is resolved against the importing file before it is judged, so \`../../shell/boot\`
1137
- is the same breach as naming the layer outright. Type-only imports and re-exports are exempt; a
1138
- package or a \`node:\` builtin is not a wall unless \`externals\` is \`"deny"\`. A file in no declared
1139
- layer is unconstrained, so a repo can wall one corner of its tree and leave the rest alone. Enabled
1140
- with no \`layers\` at all the rule refuses the run: walls nobody declared are not walls held.`,
1568
+ is the same breach as naming the layer outright. Type-only imports and re-exports are exempt unless
1569
+ \`typeOnly\` is \`"count"\` \u2014 rule-wide, or on the one layer whose wall is about which side may KNOW
1570
+ about which, since \`import type\` reaches that far exactly as a value does, while a layer borrowing a
1571
+ type to keep a package out of its runtime graph is the reason the default is \`"skip"\`. A package or a \`node:\` builtin
1572
+ is not a wall unless \`externals\` is \`"deny"\`. A file in no declared layer is unconstrained, so a
1573
+ repo can wall one corner of its tree and leave the rest alone. Enabled with no \`layers\` at all the
1574
+ rule refuses the run: walls nobody declared are not walls held.`,
1141
1575
  meta: {
1142
1576
  docs: {
1143
1577
  description: "Import direction between a repo's own declared layers. Each layer names the files it owns (regex sources) and the layers it may import; a file may always import its own layer. Relative sources are resolved against the importing file first. Enabled with no layers configured it refuses the run."
@@ -1157,13 +1591,15 @@ with no \`layers\` at all the rule refuses the run: walls nobody declared are no
1157
1591
  properties: {
1158
1592
  mayImport: { items: { type: "string" }, type: "array" },
1159
1593
  name: { type: "string" },
1160
- paths: { items: { type: "string" }, type: "array" }
1594
+ paths: { items: { type: "string" }, type: "array" },
1595
+ typeOnly: { enum: ["count", "skip"], type: "string" }
1161
1596
  },
1162
1597
  required: ["name", "paths"],
1163
1598
  type: "object"
1164
1599
  },
1165
1600
  type: "array"
1166
- }
1601
+ },
1602
+ typeOnly: { enum: ["count", "skip"], type: "string" }
1167
1603
  },
1168
1604
  type: "object"
1169
1605
  }
@@ -1181,21 +1617,27 @@ with no \`layers\` at all the rule refuses the run: walls nobody declared are no
1181
1617
  */
1182
1618
  probe(options) {
1183
1619
  const { layers = [] } = bagOf(options);
1620
+ const tried = [];
1184
1621
  for (const here of layers) {
1185
1622
  const mayImport = new Set(here.mayImport ?? []);
1186
1623
  const target = layers.find((other) => other.name !== here.name && !mayImport.has(other.name));
1187
- const [herePath] = here.paths;
1188
- const [targetPath] = target?.paths ?? [];
1189
- if (target === void 0 || herePath === void 0 || targetPath === void 0) continue;
1190
- const source = pathMatching(targetPath, "geonosis-probe-target.ts");
1624
+ const path2 = placeIn(here.paths, tried);
1625
+ const targetPath = target?.paths.find((one) => inverts(one, PROBE_TARGET));
1626
+ if (target === void 0 || path2 === void 0 || targetPath === void 0) continue;
1627
+ const source = pathMatching(targetPath, PROBE_TARGET);
1191
1628
  return {
1192
- path: pathMatching(herePath, "geonosis-probe-wall.ts"),
1629
+ path: path2,
1193
1630
  source: `import { geonosisProbe } from '${source}'
1194
1631
 
1195
1632
  export const geonosisProbeWall = geonosisProbe
1196
1633
  `
1197
1634
  };
1198
1635
  }
1636
+ if (tried.length > 0) {
1637
+ throw new Error(
1638
+ `biological-architecture/layer-walls: no layer here names a place a probe file could be put \u2014 tried ${tried.join(", ")}. Each of those matches an import specifier and can never match a filename, so a probe built from one lands outside the layer it is meant to be inside. Give the layer a directory pattern as well (#119).`
1639
+ );
1640
+ }
1199
1641
  throw new Error(
1200
1642
  "biological-architecture/layer-walls: every declared layer may import every other, so no single file can cross a wall \u2014 there is no wall here to probe."
1201
1643
  );
@@ -1377,36 +1819,6 @@ primitives count as composition; a pure \`export \u2026 from\` shim is not a mol
1377
1819
  };
1378
1820
  var molecule_must_compose_default = moleculeMustCompose;
1379
1821
 
1380
- // src/rules/lib/nodes.ts
1381
- var walkNode = (node, visit) => {
1382
- const seen = /* @__PURE__ */ new Set();
1383
- const stack = [node];
1384
- while (stack.length > 0) {
1385
- const at = stack.pop();
1386
- if (at === null || typeof at !== "object" || seen.has(at)) continue;
1387
- seen.add(at);
1388
- if (Array.isArray(at)) {
1389
- stack.push(...at);
1390
- continue;
1391
- }
1392
- const record2 = at;
1393
- if (typeof record2.type === "string") visit(record2);
1394
- for (const [key, value] of Object.entries(record2)) {
1395
- if (key === "parent") continue;
1396
- if (value !== null && typeof value === "object") stack.push(value);
1397
- }
1398
- }
1399
- };
1400
- var propertyNameOf = (node) => {
1401
- if (node.type !== "MemberExpression" || node.computed === true) return null;
1402
- const property = node.property;
1403
- return property?.type === "Identifier" && typeof property.name === "string" ? property.name : null;
1404
- };
1405
- var calleeNameOf = (node) => {
1406
- const callee = node.callee;
1407
- return callee?.type === "Identifier" && typeof callee.name === "string" ? callee.name : null;
1408
- };
1409
-
1410
1822
  // src/rules/lib/mutations.ts
1411
1823
  var DEFAULT_MUTATION_PREFIXES = [
1412
1824
  "create",
@@ -1427,64 +1839,19 @@ var writesWith = (prefixes) => (node) => {
1427
1839
  };
1428
1840
  var writeNameOf = (node) => propertyNameOf(node.callee ?? {}) ?? "a service write";
1429
1841
 
1430
- // src/rules/lib/objects.ts
1431
- var propertiesOf2 = (node) => {
1432
- const object = node;
1433
- if (object?.type !== "ObjectExpression") return null;
1434
- const properties = object.properties ?? [];
1435
- return properties.every((one) => one.type === "Property") ? properties : null;
1436
- };
1437
- var valueOf2 = (properties, name) => {
1438
- const found = properties.find((one) => {
1439
- const key = one.key;
1440
- return key?.type === "Identifier" && key.name === name || key?.type === "Literal" && key.value === name;
1441
- });
1442
- return found?.value;
1443
- };
1444
- var hasProperty = (properties, name) => valueOf2(properties, name) !== void 0;
1445
- var stringOf = (node) => {
1446
- const literal = node;
1447
- return literal?.type === "Literal" && typeof literal.value === "string" ? literal.value : null;
1448
- };
1449
-
1450
- // src/rules/lib/steps.ts
1451
- var DEFAULT_STEP_FACTORIES = ["createStep"];
1452
- var stepFactoriesOf = (rule, configured) => new Set(requireOption(rule, configured ?? DEFAULT_STEP_FACTORIES, "stepFactories"));
1453
- var isStepCall = (node, factories) => {
1454
- const callee = calleeNameOf(node);
1455
- return callee !== null && factories.has(callee);
1456
- };
1457
- var argumentsOf = (node) => node.arguments ?? [];
1458
- var configOf = (node) => {
1459
- const [first] = argumentsOf(node);
1460
- if (first === void 0) return { kind: "unreadable" };
1461
- if (stringOf(first) !== null) return { kind: "bare" };
1462
- const properties = propertiesOf2(first);
1463
- return properties === null ? { kind: "unreadable" } : { kind: "object", properties };
1464
- };
1465
- var stepNameOf = (node, config) => {
1466
- const [first] = argumentsOf(node);
1467
- const bare = stringOf(first);
1468
- if (bare !== null) return bare;
1469
- if (config.kind === "object") {
1470
- const named = config.properties.find((one) => {
1471
- const key = one.key;
1472
- return key?.type === "Identifier" && key.name === "name";
1473
- });
1474
- const literal = stringOf(named?.value);
1475
- if (literal !== null) return literal;
1476
- }
1477
- return calleeNameOf(node) ?? "a step";
1478
- };
1479
-
1480
1842
  // src/rules/mutating-step-declares-retry.ts
1481
- var RULE2 = "mutating-step-declares-retry";
1482
- var MAX_RETRIES = "maxRetries";
1843
+ var RULE5 = "mutating-step-declares-retry";
1844
+ var DEFAULT_RETRY_KEY = "maxRetries";
1483
1845
  var NO_RETRY2 = 0;
1484
1846
  var mutatingStepDeclaresRetry = {
1485
1847
  create(context) {
1486
- const factories = stepFactoriesOf(RULE2, context.options?.[0]?.stepFactories);
1487
- const isWrite = writesWith(mutationPrefixesOf(RULE2, context.options?.[0]?.mutationPrefixes));
1848
+ const factories = stepFactoriesOf(RULE5, context.options?.[0]?.stepFactories);
1849
+ const isWrite = writesWith(mutationPrefixesOf(RULE5, context.options?.[0]?.mutationPrefixes));
1850
+ const retryKey = requireOption(
1851
+ RULE5,
1852
+ context.options?.[0]?.retryKey ?? DEFAULT_RETRY_KEY,
1853
+ "retryKey"
1854
+ );
1488
1855
  return {
1489
1856
  CallExpression(node) {
1490
1857
  if (!isStepCall(node, factories)) return;
@@ -1499,18 +1866,18 @@ var mutatingStepDeclaresRetry = {
1499
1866
  const step = stepNameOf(node, config);
1500
1867
  if (config.kind === "unreadable") return;
1501
1868
  if (config.kind === "bare") {
1502
- context.report({ data: { step }, messageId: "noConfig", node });
1869
+ context.report({ data: { key: retryKey, step }, messageId: "noConfig", node });
1503
1870
  return;
1504
1871
  }
1505
- const budget = valueOf2(config.properties, MAX_RETRIES);
1872
+ const budget = valueOf(config.properties, retryKey);
1506
1873
  if (budget === void 0) {
1507
- context.report({ data: { step }, messageId: "noRetry", node });
1874
+ context.report({ data: { key: retryKey, step }, messageId: "noRetry", node });
1508
1875
  return;
1509
1876
  }
1510
1877
  if (budget.type !== "Literal" || typeof budget.value !== "number") return;
1511
1878
  if (budget.value > NO_RETRY2) return;
1512
1879
  context.report({
1513
- data: { budget: String(budget.value), step },
1880
+ data: { budget: String(budget.value), key: retryKey, step },
1514
1881
  messageId: "zeroRetries",
1515
1882
  node
1516
1883
  });
@@ -1518,27 +1885,29 @@ var mutatingStepDeclaresRetry = {
1518
1885
  };
1519
1886
  },
1520
1887
  fixShape: `A step whose invoke body WRITES declares a retry class in its config object:
1521
- \`createStep({ name: 'place-order', ...RETRY_DB }, invoke, compensate)\`. \`maxRetries\` defaults to 0,
1522
- so a step named with a bare string cannot have one \u2014 take the object form and put the budget in it,
1523
- from the repo's shared retry constants rather than as a number at the call site. A budget the rule
1524
- cannot evaluate (a constant, a spread) passes: it says nothing rather than pushing the number back
1525
- inline. Read-only and compute steps are exempt \u2014 there is nothing to revert. Configure
1526
- \`stepFactories\` if the repo wraps \`createStep\`, and \`mutationPrefixes\` if its writes are named
1527
- otherwise.`,
1888
+ \`createStep({ name: 'place-order', ...RETRY_DB }, invoke, compensate)\`. A retry budget defaults to
1889
+ none, so a step named with a bare string cannot have one \u2014 take the object form and put the budget in
1890
+ it, from the repo's shared retry constants rather than as a number at the call site. A budget the
1891
+ rule cannot evaluate (a constant, a spread, a nested \`{ limit, backoff }\`) passes: it says nothing
1892
+ rather than pushing the number back inline. Read-only and compute steps are exempt \u2014 there is nothing
1893
+ to revert. Configure \`retryKey\` to the key this engine writes the budget under (Medusa's
1894
+ \`maxRetries\`, the org engine's \`retry\`), \`stepFactories\` if the repo wraps \`createStep\`, and
1895
+ \`mutationPrefixes\` if its writes are named otherwise.`,
1528
1896
  meta: {
1529
1897
  docs: {
1530
- description: "A mutating step declares a retry class. Medusa's `maxRetries` defaults to 0, so a transient failure reverts real state a retry would have absorbed. Read-only steps are exempt; a non-literal budget passes. `stepFactories` and `mutationPrefixes` are options."
1898
+ description: "A mutating step declares a retry class. A budget left unsaid defaults to zero retries, so a transient failure reverts real state a retry would have absorbed. Read-only steps are exempt; a non-literal budget passes. `retryKey`, `stepFactories` and `mutationPrefixes` are options."
1531
1899
  },
1532
1900
  messages: {
1533
- noConfig: 'Step "{{step}}" writes but is named with a bare string, so it carries no retry class \u2014 and `maxRetries` defaults to 0, which means a transient failure reverts real state. Take the object form and give it a retry class from the shared constants.',
1534
- noRetry: 'Mutating step "{{step}}" declares no retry class. `maxRetries` defaults to 0, so the first transient failure reverts every write this step made. Add the repo\'s retry constant to its config.',
1535
- zeroRetries: 'Mutating step "{{step}}" declares `maxRetries: {{budget}}`, which is the default spelled out \u2014 no retries at all. Take the budget from the shared retry constants instead.'
1901
+ noConfig: 'Step "{{step}}" writes but is named with a bare string, so it carries no retry class \u2014 and `{{key}}` defaults to 0, which means a transient failure reverts real state. Take the object form and give it a retry class from the shared constants.',
1902
+ noRetry: 'Mutating step "{{step}}" declares no `{{key}}`. Without one the budget is zero, so the first transient failure reverts every write this step made. Add the repo\'s retry constant to its config.',
1903
+ zeroRetries: 'Mutating step "{{step}}" declares `{{key}}: {{budget}}`, which is the default spelled out \u2014 no retries at all. Take the budget from the shared retry constants instead.'
1536
1904
  },
1537
1905
  schema: [
1538
1906
  {
1539
1907
  additionalProperties: false,
1540
1908
  properties: {
1541
1909
  mutationPrefixes: { items: { type: "string" }, type: "array" },
1910
+ retryKey: { type: "string" },
1542
1911
  stepFactories: { items: { type: "string" }, type: "array" }
1543
1912
  },
1544
1913
  type: "object"
@@ -1550,13 +1919,13 @@ otherwise.`,
1550
1919
  var mutating_step_declares_retry_default = mutatingStepDeclaresRetry;
1551
1920
 
1552
1921
  // src/rules/named-when-block.ts
1553
- var RULE3 = "named-when-block";
1922
+ var RULE6 = "named-when-block";
1554
1923
  var DEFAULT_BLOCKS = ["when"];
1555
1924
  var NAMED = 3;
1556
1925
  var namedWhenBlock = {
1557
1926
  create(context) {
1558
1927
  const blocks = new Set(
1559
- requireOption(RULE3, context.options?.[0]?.blocks ?? DEFAULT_BLOCKS, "blocks")
1928
+ requireOption(RULE6, context.options?.[0]?.blocks ?? DEFAULT_BLOCKS, "blocks")
1560
1929
  );
1561
1930
  return {
1562
1931
  CallExpression(node) {
@@ -2322,26 +2691,26 @@ A component returns UI; a hook returns state.`,
2322
2691
  var no_hook_in_component_disguise_default = noHookInComponentDisguise;
2323
2692
 
2324
2693
  // src/rules/no-index-count-as-exact.ts
2325
- var RULE4 = "no-index-count-as-exact";
2694
+ var RULE7 = "no-index-count-as-exact";
2326
2695
  var DEFAULT_ESTIMATE_FIELDS = ["estimate_count", "count_estimate"];
2327
2696
  var DEFAULT_LABEL_KEY = "count_is_estimate";
2328
2697
  var noIndexCountAsExact = {
2329
2698
  create(context) {
2330
2699
  const fields = new Set(
2331
2700
  requireOption(
2332
- RULE4,
2701
+ RULE7,
2333
2702
  context.options?.[0]?.estimateFields ?? DEFAULT_ESTIMATE_FIELDS,
2334
2703
  "estimateFields"
2335
2704
  )
2336
2705
  );
2337
2706
  const label = requireOption(
2338
- RULE4,
2707
+ RULE7,
2339
2708
  context.options?.[0]?.labelKey ?? DEFAULT_LABEL_KEY,
2340
2709
  "labelKey"
2341
2710
  );
2342
2711
  return {
2343
2712
  ObjectExpression(node) {
2344
- const properties = propertiesOf2(node);
2713
+ const properties = propertiesOf(node);
2345
2714
  if (properties === null || hasProperty(properties, label)) return;
2346
2715
  let field = null;
2347
2716
  for (const property of properties) {
@@ -2591,20 +2960,20 @@ logic in \`lib/\`.`,
2591
2960
  var no_invalid_feature_folders_default = noInvalidFeatureFolders;
2592
2961
 
2593
2962
  // src/rules/no-loader-side-effect-in-index-file.ts
2594
- var RULE5 = "no-loader-side-effect-in-index-file";
2963
+ var RULE8 = "no-loader-side-effect-in-index-file";
2595
2964
  var DEFAULT_DIRECTORIES = ["(?:^|/)subscribers/", "(?:^|/)jobs/"];
2596
2965
  var DEFAULT_RESOURCE_FACTORIES = ["createWorkflow", "createStep", "createHook"];
2597
2966
  var INDEX = /(?:^|\/)index\.[cm]?[jt]sx?$/;
2598
2967
  var noLoaderSideEffectInIndexFile = {
2599
2968
  create(context) {
2600
2969
  const directories = requireOption(
2601
- RULE5,
2970
+ RULE8,
2602
2971
  context.options?.[0]?.directories ?? DEFAULT_DIRECTORIES,
2603
2972
  "directories"
2604
2973
  ).map((source) => new RegExp(source));
2605
2974
  const factories = new Set(
2606
2975
  requireOption(
2607
- RULE5,
2976
+ RULE8,
2608
2977
  context.options?.[0]?.resourceFactories ?? DEFAULT_RESOURCE_FACTORIES,
2609
2978
  "resourceFactories"
2610
2979
  )
@@ -2767,7 +3136,7 @@ the same component. Thin preset wrappers over one imported component do not coun
2767
3136
  var no_logic_in_component_files_default = noLogicInComponentFiles;
2768
3137
 
2769
3138
  // src/rules/no-mock-db-in-integration.ts
2770
- var RULE6 = "no-mock-db-in-integration";
3139
+ var RULE9 = "no-mock-db-in-integration";
2771
3140
  var BUN_MOCK = "mock.module";
2772
3141
  var MOCK_CALLS = /* @__PURE__ */ new Set(["jest.mock", BUN_MOCK, "vi.mock"]);
2773
3142
  var calleeOf = (node) => {
@@ -2785,8 +3154,8 @@ var inDirectory = (filename, directory) => {
2785
3154
  var noMockDbInIntegration = {
2786
3155
  create(context) {
2787
3156
  const options = context.options?.[0] ?? {};
2788
- const dirs = requireOption(RULE6, options.integrationDirs, "integrationDirs");
2789
- const modules = requireOption(RULE6, options.dbModules, "dbModules");
3157
+ const dirs = requireOption(RULE9, options.integrationDirs, "integrationDirs");
3158
+ const modules = requireOption(RULE9, options.dbModules, "dbModules");
2790
3159
  const mockNames = new Set(options.mockNames ?? []);
2791
3160
  const filename = normalise(context.filename);
2792
3161
  const spans = /* @__PURE__ */ new Set();
@@ -2887,14 +3256,14 @@ below the seam, and a caller that names it has crossed it.`,
2887
3256
  var no_orm_outside_db_default = noOrmOutsideDb;
2888
3257
 
2889
3258
  // src/rules/no-raw-html-atoms.ts
2890
- var RULE7 = "no-raw-html-atoms";
3259
+ var RULE10 = "no-raw-html-atoms";
2891
3260
  var WHERE_RAW_HTML_IS_THE_POINT = "atoms";
2892
3261
  var noRawHtmlAtoms = {
2893
3262
  create(context) {
2894
- const restrictions = requireOption(RULE7, context.options[0], "element\u2192atom map");
3263
+ const restrictions = requireOption(RULE10, context.options[0], "element\u2192atom map");
2895
3264
  const scope = context.options[1]?.scope;
2896
3265
  const filename = context.filename;
2897
- const inScope = scope === void 0 ? !inTier(filename, WHERE_RAW_HTML_IS_THE_POINT) : requireTiers(RULE7, scope).some((tier) => inTier(filename, tier));
3266
+ const inScope = scope === void 0 ? !inTier(filename, WHERE_RAW_HTML_IS_THE_POINT) : requireTiers(RULE10, scope).some((tier) => inTier(filename, tier));
2898
3267
  if (!inScope) return {};
2899
3268
  const elementMap = /* @__PURE__ */ new Map();
2900
3269
  for (const r of restrictions) {
@@ -3420,8 +3789,8 @@ var no_type_definitions_in_components_default = noTypeDefinitionsInComponents;
3420
3789
 
3421
3790
  // src/rules/no-unregistered-plugin-dir.ts
3422
3791
  import { readFileSync as readFileSync3 } from "fs";
3423
- var RULE8 = "no-unregistered-plugin-dir";
3424
- var ACCEPTS = "{ roots: string[], registry: string | string[], manifests: string[] }";
3792
+ var RULE11 = "no-unregistered-plugin-dir";
3793
+ var ACCEPTS3 = "{ roots: string[], registry: string | string[], manifests: string[] }";
3425
3794
  var trimSlashes = (path2) => path2.replace(/^\/+/, "").replace(/\/+$/, "");
3426
3795
  var withoutComments = (source) => source.replaceAll(/\/\*[\s\S]*?\*\//g, " ").replaceAll(/\/\/[^\n]*/g, " ");
3427
3796
  var literalsOf = (source) => [...withoutComments(source).matchAll(/['"`]([^'"`\n]+)['"`]/g)].map((match) => match[1] ?? "");
@@ -3438,10 +3807,10 @@ var rootedAt = (filename, root) => {
3438
3807
  var noUnregisteredPluginDir = {
3439
3808
  create(context) {
3440
3809
  const options = context.options?.[0] ?? {};
3441
- const roots = requireOption(RULE8, options.roots, "roots", ACCEPTS).map(trimSlashes);
3442
- const declared = requireOption(RULE8, options.registry, "registry", ACCEPTS);
3810
+ const roots = requireOption(RULE11, options.roots, "roots", ACCEPTS3).map(trimSlashes);
3811
+ const declared = requireOption(RULE11, options.registry, "registry", ACCEPTS3);
3443
3812
  const registries = (typeof declared === "string" ? [declared] : declared).map(trimSlashes);
3444
- const manifests = new Set(requireOption(RULE8, options.manifests, "manifests", ACCEPTS));
3813
+ const manifests = new Set(requireOption(RULE11, options.manifests, "manifests", ACCEPTS3));
3445
3814
  const filename = normalise(context.filename);
3446
3815
  return {
3447
3816
  Program(node) {
@@ -3695,16 +4064,16 @@ source. The CELL is the only level where several sources meet; it derives and pa
3695
4064
  var organelle_single_source_default = organelleSingleSource;
3696
4065
 
3697
4066
  // src/rules/plugin-route-namespaced.ts
3698
- var RULE9 = "plugin-route-namespaced";
4067
+ var RULE12 = "plugin-route-namespaced";
3699
4068
  var DEFAULT_SCOPES = ["store", "admin"];
3700
4069
  var ROUTE_FILE = /(?:^|\/)api\/(.+\/)?route\.[cm]?[jt]sx?$/;
3701
4070
  var NAMESPACED_FROM = 2;
3702
4071
  var readPackages = (configured) => {
3703
- const rules2 = requireOption(RULE9, configured, "packages");
4072
+ const rules2 = requireOption(RULE12, configured, "packages");
3704
4073
  for (const rule of rules2) {
3705
4074
  if (typeof rule.under !== "string" || rule.under === "") {
3706
4075
  throw new Error(
3707
- `biological-architecture/${RULE9}: every \`packages\` entry needs \`under\` \u2014 the directory whose children are packages. Without it the rule cannot tell which files belong to a plugin.`
4076
+ `biological-architecture/${RULE12}: every \`packages\` entry needs \`under\` \u2014 the directory whose children are packages. Without it the rule cannot tell which files belong to a plugin.`
3708
4077
  );
3709
4078
  }
3710
4079
  }
@@ -3731,7 +4100,7 @@ var pluginRouteNamespaced = {
3731
4100
  create(context) {
3732
4101
  const rules2 = readPackages(context.options?.[0]?.packages);
3733
4102
  const scopes = new Set(
3734
- requireOption(RULE9, context.options?.[0]?.scopes ?? DEFAULT_SCOPES, "scopes")
4103
+ requireOption(RULE12, context.options?.[0]?.scopes ?? DEFAULT_SCOPES, "scopes")
3735
4104
  );
3736
4105
  const path2 = normalise(context.filename);
3737
4106
  const route = routePathOf(path2);
@@ -3918,7 +4287,7 @@ decides what an event MEANS and throws when it cannot.`,
3918
4287
  var queue_loop_is_the_library_default = queueLoopIsTheLibrary;
3919
4288
 
3920
4289
  // src/rules/route-no-inline-mutation.ts
3921
- var RULE10 = "route-no-inline-mutation";
4290
+ var RULE13 = "route-no-inline-mutation";
3922
4291
  var DEFAULT_PATHS = [String.raw`(?:^|/)api/.*/route\.[cm]?[jt]sx?$`];
3923
4292
  var DEFAULT_INLINE_MUTATION_OK = "arch:inline-mutation-ok";
3924
4293
  var commentsOf2 = (sourceCode) => sourceCode?.getAllComments?.() ?? sourceCode?.ast?.comments ?? [];
@@ -3962,15 +4331,15 @@ var annotated = (found, comments, marker) => {
3962
4331
  };
3963
4332
  var routeNoInlineMutation = {
3964
4333
  create(context) {
3965
- const paths = requireOption(RULE10, context.options?.[0]?.paths ?? DEFAULT_PATHS, "paths").map(
4334
+ const paths = requireOption(RULE13, context.options?.[0]?.paths ?? DEFAULT_PATHS, "paths").map(
3966
4335
  (source) => new RegExp(source)
3967
4336
  );
3968
4337
  const marker = requireOption(
3969
- RULE10,
4338
+ RULE13,
3970
4339
  context.options?.[0]?.inlineMutationOk ?? DEFAULT_INLINE_MUTATION_OK,
3971
4340
  "inlineMutationOk"
3972
4341
  );
3973
- const isWrite = writesWith(mutationPrefixesOf(RULE10, context.options?.[0]?.mutationPrefixes));
4342
+ const isWrite = writesWith(mutationPrefixesOf(RULE13, context.options?.[0]?.mutationPrefixes));
3974
4343
  const filename = normalise(context.filename);
3975
4344
  if (!paths.some((pattern) => pattern.test(filename))) return {};
3976
4345
  return {
@@ -4040,7 +4409,7 @@ var route_no_inline_mutation_default = routeNoInlineMutation;
4040
4409
 
4041
4410
  // src/rules/router-schema-parity.ts
4042
4411
  import { existsSync as existsSync2 } from "fs";
4043
- var RULE11 = "router-schema-parity";
4412
+ var RULE14 = "router-schema-parity";
4044
4413
  var EXTENSIONS2 = [".ts", ".tsx"];
4045
4414
  var trimSlashes2 = (path2) => path2.replace(/^\/+/, "").replace(/\/+$/, "");
4046
4415
  var baseOf = (file) => file.replace(/\.[cm]?[jt]sx?$/, "");
@@ -4058,8 +4427,8 @@ var directlyUnder = (filename, directory) => {
4058
4427
  var routerSchemaParity = {
4059
4428
  create(context) {
4060
4429
  const options = context.options?.[0] ?? {};
4061
- const routers = trimSlashes2(requireOption(RULE11, options.routers, "routers"));
4062
- const schemas = trimSlashes2(requireOption(RULE11, options.schemas, "schemas"));
4430
+ const routers = trimSlashes2(requireOption(RULE14, options.routers, "routers"));
4431
+ const schemas = trimSlashes2(requireOption(RULE14, options.schemas, "schemas"));
4063
4432
  const suffix = options.suffix ?? "";
4064
4433
  const ignored = new Set(options.ignore ?? []);
4065
4434
  const mounts = options.mounts ?? {};
@@ -4131,7 +4500,7 @@ repo-root-relative, because in one consumer they live in different packages.`,
4131
4500
  var router_schema_parity_default = routerSchemaParity;
4132
4501
 
4133
4502
  // src/rules/service-name-not-reserved.ts
4134
- var RULE12 = "service-name-not-reserved";
4503
+ var RULE15 = "service-name-not-reserved";
4135
4504
  var DEFAULT_MODULE_FACTORIES = ["Module"];
4136
4505
  var MODULES_2_19 = [
4137
4506
  "analytics",
@@ -4171,13 +4540,13 @@ var serviceNameNotReserved = {
4171
4540
  create(context) {
4172
4541
  const factories = new Set(
4173
4542
  requireOption(
4174
- RULE12,
4543
+ RULE15,
4175
4544
  context.options?.[0]?.moduleFactories ?? DEFAULT_MODULE_FACTORIES,
4176
4545
  "moduleFactories"
4177
4546
  )
4178
4547
  );
4179
4548
  const reserved = new Set(
4180
- requireOption(RULE12, context.options?.[0]?.reserved ?? MODULES_2_19, "reserved")
4549
+ requireOption(RULE15, context.options?.[0]?.reserved ?? MODULES_2_19, "reserved")
4181
4550
  );
4182
4551
  const constants = /* @__PURE__ */ new Map();
4183
4552
  const pending = [];
@@ -4369,7 +4738,7 @@ var isFactShaped = (literal) => {
4369
4738
  if (/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/.test(literal)) return true;
4370
4739
  return literal.length >= 16 && !/^[a-z0-9:\-[\]/.%#!]+$/.test(literal);
4371
4740
  };
4372
- var nameOf = (node) => {
4741
+ var nameOf2 = (node) => {
4373
4742
  if (node === void 0) return void 0;
4374
4743
  if (typeof node === "string") return node;
4375
4744
  if (node.type === "Identifier" || node.type === "JSXIdentifier") return node.name;
@@ -4380,12 +4749,12 @@ var isSpelledAsStyleOrModule = (node) => {
4380
4749
  let current = node.parent;
4381
4750
  let child = node;
4382
4751
  while (current && typeof current.type === "string" && current.type !== "Program") {
4383
- if (current.type === "JSXAttribute") return styleAttributes.has(nameOf(current.name) ?? "");
4752
+ if (current.type === "JSXAttribute") return styleAttributes.has(nameOf2(current.name) ?? "");
4384
4753
  if (current.type === "Property" && current.key === child) return true;
4385
- if (current.type === "Property" && styleAttributes.has(nameOf(current.key) ?? "")) return true;
4754
+ if (current.type === "Property" && styleAttributes.has(nameOf2(current.key) ?? "")) return true;
4386
4755
  if (current.type === "CallExpression") {
4387
4756
  const callee = current.callee;
4388
- const calleeName = callee?.type === "MemberExpression" ? nameOf(callee.property) : nameOf(callee);
4757
+ const calleeName = callee?.type === "MemberExpression" ? nameOf2(callee.property) : nameOf2(callee);
4389
4758
  if (styleCallees.has(calleeName ?? "") || formFieldCallees.has(calleeName ?? "")) return true;
4390
4759
  const firstArgument = current.arguments?.[0];
4391
4760
  if (declarationCallees.has(calleeName ?? "") && firstArgument === child) return true;
@@ -4473,12 +4842,12 @@ can be misspelled and a second default nobody agreed to.`,
4473
4842
  var ssot_no_process_env_default = ssotNoProcessEnv;
4474
4843
 
4475
4844
  // src/rules/step-declares-compensation-or-none.ts
4476
- var RULE13 = "step-declares-compensation-or-none";
4845
+ var RULE16 = "step-declares-compensation-or-none";
4477
4846
  var NO_COMPENSATION = "noCompensation";
4478
4847
  var WITH_COMPENSATION = 3;
4479
4848
  var stepDeclaresCompensationOrNone = {
4480
4849
  create(context) {
4481
- const factories = stepFactoriesOf(RULE13, context.options?.[0]?.stepFactories);
4850
+ const factories = stepFactoriesOf(RULE16, context.options?.[0]?.stepFactories);
4482
4851
  return {
4483
4852
  CallExpression(node) {
4484
4853
  if (!isStepCall(node, factories)) return;
@@ -4486,7 +4855,7 @@ var stepDeclaresCompensationOrNone = {
4486
4855
  const config = configOf(node);
4487
4856
  if (config.kind === "unreadable") return;
4488
4857
  if (config.kind === "object") {
4489
- const flag = valueOf2(config.properties, NO_COMPENSATION);
4858
+ const flag = valueOf(config.properties, NO_COMPENSATION);
4490
4859
  if (flag?.type === "Literal" && flag.value === true) return;
4491
4860
  }
4492
4861
  context.report({ data: { step: stepNameOf(node, config) }, messageId: "undeclared", node });
@@ -4522,20 +4891,52 @@ variable \u2014 passes, since the flag may be in the base. Configure \`stepFacto
4522
4891
  var step_declares_compensation_or_none_default = stepDeclaresCompensationOrNone;
4523
4892
 
4524
4893
  // src/rules/step-opens-its-own-cell.ts
4525
- var RULE14 = "step-opens-its-own-cell";
4894
+ var RULE17 = "step-opens-its-own-cell";
4526
4895
  var STEP = "step";
4896
+ var ACCEPTS4 = "{ within: string (required), hosts: string[], scopeKey: string, engine: string, wrapper: string }";
4897
+ var DEFAULT_HOSTS = ["createRuntime", "entrypointFor"];
4898
+ var DEFAULT_SCOPE_KEY = "perStep";
4527
4899
  var readable = (pattern) => pattern.replaceAll("\\", "").replace(/\$$/, "");
4900
+ var isHostCall = (node, hosts) => {
4901
+ const callee = calleeNameOf(node);
4902
+ return callee !== null && hosts.has(callee);
4903
+ };
4904
+ var configOf2 = (node) => propertiesOf((node.arguments ?? [])[0]);
4905
+ var opaque = (call, hosts) => {
4906
+ let unreadable = false;
4907
+ walkNode(call, (inner) => {
4908
+ if (inner.type === "CallExpression" && isHostCall(inner, hosts) && configOf2(inner) === null) {
4909
+ unreadable = true;
4910
+ }
4911
+ });
4912
+ return unreadable;
4913
+ };
4914
+ var namesKey = (node, key) => {
4915
+ if (node.type !== "Property") return false;
4916
+ const named = node.key;
4917
+ return named?.type === "Identifier" && named.name === key || named?.type === "Literal" && named.value === key;
4918
+ };
4919
+ var declaresScope = (call, scopeKey) => {
4920
+ let declared = false;
4921
+ walkNode(call, (inner) => {
4922
+ if (namesKey(inner, scopeKey)) declared = true;
4923
+ });
4924
+ return declared;
4925
+ };
4528
4926
  var stepOpensItsOwnCell = {
4529
4927
  create(context) {
4530
- const { wrapper } = context.options?.[0] ?? {};
4531
- const engine = requireOption(RULE14, context.options?.[0]?.engine, "engine");
4532
- const within = requireOption(RULE14, context.options?.[0]?.within, "within");
4928
+ const bag = context.options?.[0] ?? {};
4929
+ const within = requireOption(RULE17, bag.within, "within", ACCEPTS4);
4930
+ const hosts = new Set(requireOption(RULE17, bag.hosts ?? DEFAULT_HOSTS, "hosts", ACCEPTS4));
4931
+ const scopeKey = requireOption(RULE17, bag.scopeKey ?? DEFAULT_SCOPE_KEY, "scopeKey", ACCEPTS4);
4932
+ const { engine, wrapper } = bag;
4533
4933
  const filename = normalise(context.filename);
4534
4934
  if (!new RegExp(within).test(filename)) return {};
4535
- if (wrapper !== void 0 && new RegExp(wrapper).test(filename)) return {};
4536
4935
  const file = filename.split("/").pop() ?? filename;
4936
+ const isWrapper = wrapper !== void 0 && new RegExp(wrapper).test(filename);
4537
4937
  return {
4538
4938
  ImportDeclaration(node) {
4939
+ if (engine === void 0 || isWrapper) return;
4539
4940
  if (node.importKind === "type") return;
4540
4941
  if (node.source.value !== engine) return;
4541
4942
  for (const specifier of node.specifiers ?? []) {
@@ -4552,26 +4953,58 @@ var stepOpensItsOwnCell = {
4552
4953
  node
4553
4954
  });
4554
4955
  }
4956
+ },
4957
+ Program(node) {
4958
+ const found = [];
4959
+ walkNode(node, (one) => {
4960
+ if (one.type === "CallExpression" && isHostCall(one, hosts)) found.push(one);
4961
+ });
4962
+ const nested = /* @__PURE__ */ new Set();
4963
+ for (const call of found) {
4964
+ walkNode(call, (inner) => {
4965
+ if (inner !== call && inner.type === "CallExpression" && isHostCall(inner, hosts)) {
4966
+ nested.add(inner);
4967
+ }
4968
+ });
4969
+ }
4970
+ for (const call of found) {
4971
+ if (nested.has(call)) continue;
4972
+ if (opaque(call, hosts)) continue;
4973
+ if (declaresScope(call, scopeKey)) continue;
4974
+ context.report({
4975
+ data: { file, host: calleeNameOf(call) ?? "a durable host", scopeKey },
4976
+ messageId: "unscopedHost",
4977
+ node: call
4978
+ });
4979
+ }
4555
4980
  }
4556
4981
  };
4557
4982
  },
4558
- fixShape: `Inside the guarded workflow tree, import \`step\` from the repo's own wrapper, never from the engine
4559
- package. A durable run hibernates and retries between steps, so each step body must open the
4560
- connection it uses rather than share one opened once per run \u2014 the wrapper does that, the engine's
4561
- \`step\` does not. The wrapper file is the one place allowed to reach the engine. Configure
4562
- \`engine\`, \`within\` and \`wrapper\`; enabled without \`engine\` or \`within\` the rule refuses the run.`,
4983
+ fixShape: `Inside the guarded workflow tree, a durable host declares the scope its steps run in:
4984
+ \`createRuntime({ journal, scope: { perStep: () => withConnection(env) } })\`, and the adapter's entry
4985
+ (\`entrypointFor\`) declares it in the runtime it builds per run. A durable instance hibernates and
4986
+ retries between steps, so the connection a step uses has to be opened inside that step rather than
4987
+ shared across the run \u2014 \`perStep\` is asked for a scope per unit of work, which is what makes that
4988
+ possible. A host whose config cannot be read whole (a spread, a variable) is not judged.
4989
+ On \`sagaflow-js\` there is no such seam and the older shape stays reachable: set \`engine\` and
4990
+ \`wrapper\`, and import \`step\` from the repo's own wrapper rather than from the engine package.
4991
+ Configure \`within\`; \`hosts\`, \`scopeKey\`, \`engine\` and \`wrapper\` have defaults or are off. Enabled
4992
+ without \`within\` the rule refuses the run.`,
4563
4993
  meta: {
4564
4994
  docs: {
4565
- description: "A durable saga takes its `step` from the repo's own wrapper, which opens a connection inside each step body \u2014 a durable run hibernates and retries, so a handle shared across steps is dead by the time a later step uses it. The engine module, the guarded tree and the wrapper file are options; enabled without them it refuses the run."
4995
+ description: "A durable host declares the scope its steps run in \u2014 `scope.perStep` on the runtime it builds \u2014 because a durable run hibernates and retries, so a handle opened once per run is dead by the time a later step uses it. A repo on an engine with no such seam names its `engine` and `wrapper` instead, and a saga importing the engine's `step` around that wrapper is the finding. The guarded tree is required; enabled without it the rule refuses the run."
4566
4996
  },
4567
4997
  messages: {
4568
- engineStep: '"{{file}}" imports `step` from `{{engine}}`. Import it from {{wrapper}} instead: a durable run spans hibernation and retries, so the connection a step uses has to be opened inside that step rather than shared across steps. The wrapper opens it; the engine\'s `step` does not.'
4998
+ engineStep: '"{{file}}" imports `step` from `{{engine}}`. Import it from {{wrapper}} instead: a durable run spans hibernation and retries, so the connection a step uses has to be opened inside that step rather than shared across steps. The wrapper opens it; the engine\'s `step` does not.',
4999
+ unscopedHost: '"{{file}}" builds a durable host with `{{host}}` and declares no `{{scopeKey}}`. A durable instance hibernates between steps and replays them on retry, so a handle opened once per run is closed by the time the second step reaches for it \u2014 and every step here would share it. Declare `scope: {{ {{scopeKey}}: () => \u2026 }}` on the runtime this host builds, so each step body and each undo opens the one it uses.'
4569
5000
  },
4570
5001
  schema: [
4571
5002
  {
4572
5003
  additionalProperties: false,
4573
5004
  properties: {
4574
5005
  engine: { type: "string" },
5006
+ hosts: { items: { type: "string" }, type: "array" },
5007
+ scopeKey: { type: "string" },
4575
5008
  within: { type: "string" },
4576
5009
  wrapper: { type: "string" }
4577
5010
  },
@@ -4581,18 +5014,23 @@ connection it uses rather than share one opened once per run \u2014 the wrapper
4581
5014
  type: "problem"
4582
5015
  },
4583
5016
  /**
4584
- * during.day guards `packages/workflows/src/` and the shipped corpus guards
4585
- * `corpus/workflows/`, so its fixture lands outside the tree this repo asked about and the rule
4586
- * reads as firing nowhere in a package that enforces it on every file. This is the same fixture
4587
- * moved to where the option points — and out of the `wrapper`, which is the one file allowed
4588
- * to reach the engine.
5017
+ * The probe is built for the spelling the OPTIONS select. A repo that named an `engine` is on the
5018
+ * older one and is exercised by a saga reaching past its wrapper — which is also why the path has
5019
+ * to land inside `within` and outside `wrapper`, the one file allowed to make that import.
4589
5020
  */
4590
5021
  probe(options) {
4591
- const { engine = "", within = "", wrapper } = bagOf(options);
5022
+ const { engine, hosts, within = "", wrapper } = bagOf(options);
5023
+ if (engine === void 0) {
5024
+ return {
5025
+ path: pathMatching(within, "geonosis-probe-durable-host.ts"),
5026
+ source: `export const geonosisProbe = ${firstOf(hosts, DEFAULT_HOSTS)}({ journal: {} })
5027
+ `
5028
+ };
5029
+ }
4592
5030
  const path2 = pathMatching(within, "geonosis-probe-engine-step.ts");
4593
5031
  if (wrapper !== void 0 && new RegExp(wrapper).test(path2)) {
4594
5032
  throw new Error(
4595
- `biological-architecture/${RULE14}: the probe path "${path2}" is also the \`wrapper\`, which is the one file allowed to reach \`${engine}\` \u2014 no single file inside \`within\` can both land where the rule looks and be somewhere it speaks.`
5033
+ `biological-architecture/${RULE17}: the probe path "${path2}" is also the \`wrapper\`, which is the one file allowed to reach \`${engine}\` \u2014 no single file inside \`within\` can both land where the rule looks and be somewhere it speaks.`
4596
5034
  );
4597
5035
  }
4598
5036
  return {
@@ -4661,13 +5099,126 @@ belongs on the admin surface.`,
4661
5099
  };
4662
5100
  var store_route_scopes_tenant_data_default = storeRouteScopesTenantData;
4663
5101
 
5102
+ // src/rules/subpath-over-barrel.ts
5103
+ var RULE18 = "subpath-over-barrel";
5104
+ var ACCEPTS5 = "{ packages: { name: string, subpaths: { [subpath: string]: string[] } }[] (required) }";
5105
+ var homesOf = (declared) => {
5106
+ const subpaths = requireOption(RULE18, Object.keys(declared.subpaths ?? {}), "subpaths", ACCEPTS5);
5107
+ return new Map(
5108
+ subpaths.flatMap(
5109
+ (subpath) => (declared.subpaths?.[subpath] ?? []).map((name) => [name, subpath])
5110
+ )
5111
+ );
5112
+ };
5113
+ var importedNamesOf = (node) => {
5114
+ if (node.importKind === "type") return [];
5115
+ const specifiers = node.specifiers ?? [];
5116
+ if (specifiers.length === 0) return null;
5117
+ const names2 = [];
5118
+ for (const specifier of specifiers) {
5119
+ if (specifier.type !== "ImportSpecifier") return null;
5120
+ if (specifier.importKind === "type") continue;
5121
+ const imported = specifier.imported;
5122
+ if (imported?.type !== "Identifier" || typeof imported.name !== "string") return null;
5123
+ names2.push(imported.name);
5124
+ }
5125
+ return names2;
5126
+ };
5127
+ var subpathOverBarrel = {
5128
+ create(context) {
5129
+ const declared = requireOption(RULE18, context.options?.[0]?.packages, "packages", ACCEPTS5);
5130
+ const homes = new Map(
5131
+ declared.map((one) => [requireOption(RULE18, one.name, "name", ACCEPTS5), homesOf(one)])
5132
+ );
5133
+ return {
5134
+ ImportDeclaration(node) {
5135
+ const source = node.source?.value;
5136
+ if (typeof source !== "string") return;
5137
+ const home = homes.get(source);
5138
+ if (home === void 0) return;
5139
+ const names2 = importedNamesOf(node);
5140
+ if (names2 === null || names2.length === 0) return;
5141
+ const reached = new Set(names2.map((name) => home.get(name)));
5142
+ if (reached.size !== 1 || reached.has(void 0)) return;
5143
+ const [only] = [...reached];
5144
+ context.report({
5145
+ data: { package: source, subpath: `${source}/${only ?? ""}` },
5146
+ messageId: "rootBarrel",
5147
+ node
5148
+ });
5149
+ }
5150
+ };
5151
+ },
5152
+ fixShape: `Import from the SUBPATH that owns the names, not from the package root:
5153
+ \`import { Document } from '@repo/domains/document'\`. A subpath exists because its dependency
5154
+ footprint differs from the root's, so the root entry is the union of them all and a file that names
5155
+ it to reach one pays for every other \u2014 at install, at bundle and at worker start, with nothing to
5156
+ show for it. An import whose names span two subpaths is what the root is for and is left alone, as
5157
+ is a type-only import, which erases before any of that happens. Declare the map in \`packages\`
5158
+ (\`{ name, subpaths: { document: ['Document', \u2026] } }\`); enabled without it the rule refuses the run.`,
5159
+ meta: {
5160
+ docs: {
5161
+ description: "A root import whose names all live under one of the package's declared subpaths names the subpath instead. The root entry is the union of every subpath's module graph, so reaching one through it pays for all of them. Names spanning two subpaths, and type-only imports, are exempt. The package\u2192subpath map is a required option."
5162
+ },
5163
+ messages: {
5164
+ rootBarrel: "Every name imported here from `{{package}}` lives under `{{subpath}}`. Import from the subpath: the root entry re-exports all of them, so this file takes the union of every subpath\u2019s module graph to reach one of them."
5165
+ },
5166
+ schema: [
5167
+ {
5168
+ additionalProperties: false,
5169
+ properties: {
5170
+ packages: {
5171
+ items: {
5172
+ additionalProperties: false,
5173
+ properties: {
5174
+ name: { type: "string" },
5175
+ subpaths: {
5176
+ additionalProperties: {
5177
+ items: { type: "string" },
5178
+ type: "array"
5179
+ },
5180
+ type: "object"
5181
+ }
5182
+ },
5183
+ type: "object"
5184
+ },
5185
+ type: "array"
5186
+ }
5187
+ },
5188
+ type: "object"
5189
+ }
5190
+ ],
5191
+ type: "problem"
5192
+ },
5193
+ /** One import of one declared name, at the root — the smallest shape that must fire. */
5194
+ probe(options) {
5195
+ const [first] = bagOf(options).packages ?? [];
5196
+ const name = first?.name ?? "";
5197
+ const [subpath] = Object.keys(first?.subpaths ?? {});
5198
+ const [exported] = first?.subpaths?.[subpath ?? ""] ?? [];
5199
+ if (exported === void 0) {
5200
+ throw new Error(
5201
+ `biological-architecture/${RULE18}: the configured packages declare no subpath with a name in it, so there is nothing a probe could import from the root.`
5202
+ );
5203
+ }
5204
+ return {
5205
+ path: "geonosis-probe-subpath.ts",
5206
+ source: `import { ${exported} } from '${name}'
5207
+
5208
+ export const geonosisProbe = ${exported}
5209
+ `
5210
+ };
5211
+ }
5212
+ };
5213
+ var subpath_over_barrel_default = subpathOverBarrel;
5214
+
4664
5215
  // src/rules/subscriber-declares-id.ts
4665
- var RULE15 = "subscriber-declares-id";
5216
+ var RULE19 = "subscriber-declares-id";
4666
5217
  var DEFAULT_PATHS2 = ["(?:^|/)subscribers/"];
4667
5218
  var CONFIG = "config";
4668
5219
  var CONTEXT = "context";
4669
5220
  var SUBSCRIBER_ID = "subscriberId";
4670
- var declaredName = (node) => {
5221
+ var declaredName2 = (node) => {
4671
5222
  const declaration = node.declaration;
4672
5223
  if (declaration?.type !== "VariableDeclaration") return null;
4673
5224
  const [first] = declaration.declarations ?? [];
@@ -4676,7 +5227,7 @@ var declaredName = (node) => {
4676
5227
  };
4677
5228
  var subscriberDeclaresId = {
4678
5229
  create(context) {
4679
- const paths = requireOption(RULE15, context.options?.[0]?.paths ?? DEFAULT_PATHS2, "paths").map(
5230
+ const paths = requireOption(RULE19, context.options?.[0]?.paths ?? DEFAULT_PATHS2, "paths").map(
4680
5231
  (source) => new RegExp(source)
4681
5232
  );
4682
5233
  const filename = normalise(context.filename);
@@ -4684,13 +5235,13 @@ var subscriberDeclaresId = {
4684
5235
  const file = filename.split("/").slice(-2).join("/") || fileOf(filename);
4685
5236
  return {
4686
5237
  ExportNamedDeclaration(node) {
4687
- if (declaredName(node) !== CONFIG) return;
5238
+ if (declaredName2(node) !== CONFIG) return;
4688
5239
  const [first] = node.declaration.declarations ?? [];
4689
- const properties = propertiesOf2(first?.init);
5240
+ const properties = propertiesOf(first?.init);
4690
5241
  if (properties === null) return;
4691
- const inner = propertiesOf2(valueOf2(properties, CONTEXT));
4692
- if (inner !== null && valueOf2(inner, SUBSCRIBER_ID) !== void 0) return;
4693
- if (valueOf2(properties, CONTEXT) !== void 0 && inner === null) return;
5242
+ const inner = propertiesOf(valueOf(properties, CONTEXT));
5243
+ if (inner !== null && valueOf(inner, SUBSCRIBER_ID) !== void 0) return;
5244
+ if (valueOf(properties, CONTEXT) !== void 0 && inner === null) return;
4694
5245
  context.report({ data: { file }, messageId: "inferred", node });
4695
5246
  }
4696
5247
  };
@@ -4799,8 +5350,10 @@ table to \`schema/control/\`.`,
4799
5350
  var tenant_tables_carry_org_id_default = tenantTablesCarryOrgId;
4800
5351
 
4801
5352
  // src/rules/time-through-the-door.ts
4802
- var timeDoor = /\/packages\/utils\/src\/time\.ts$/;
4803
- var exemptFiles3 = [timeDoor, /\/__tests__\//, /\/scripts\//, /\/testing\//, /\/seeds\//];
5353
+ var RULE20 = "time-through-the-door";
5354
+ var ACCEPTS6 = "{ door: string (a regex over the filename), exempt: string[] (regex sources, default the test paths: __tests__/, .test., .spec.), libraries: string[] (default ['dayjs']) }";
5355
+ var DEFAULT_LIBRARIES = ["dayjs"];
5356
+ var DEFAULT_EXEMPT = ["__tests__/", String.raw`\.test\.`, String.raw`\.spec\.`];
4804
5357
  var dateStatics = /* @__PURE__ */ new Set(["parse", "UTC"]);
4805
5358
  var dateInstanceMethods = /* @__PURE__ */ new Set([
4806
5359
  "toISOString",
@@ -4822,19 +5375,37 @@ var memberName = (callee) => ({
4822
5375
  object: callee.object?.type === "Identifier" ? callee.object.name : void 0,
4823
5376
  property: callee.property?.type === "Identifier" ? callee.property.name : void 0
4824
5377
  });
5378
+ var readable2 = (pattern) => pattern.replaceAll("\\", "").replace(/\$$/, "");
4825
5379
  var timeThroughTheDoor = {
4826
5380
  create(context) {
4827
- const normalized = normalise(context.filename);
4828
- if (exemptFiles3.some((file) => file.test(normalized))) return {};
5381
+ const door = requireOption(RULE20, context.options?.[0]?.door, "door", ACCEPTS6);
5382
+ const exempt = context.options?.[0]?.exempt ?? DEFAULT_EXEMPT;
5383
+ const libraries = requireOption(
5384
+ RULE20,
5385
+ context.options?.[0]?.libraries ?? DEFAULT_LIBRARIES,
5386
+ "libraries",
5387
+ ACCEPTS6
5388
+ );
5389
+ const filename = normalise(context.filename);
5390
+ if (isDoor(filename, door)) return {};
5391
+ if (exempt.some((pattern) => new RegExp(pattern).test(filename))) return {};
5392
+ const where = readable2(door);
4829
5393
  return {
4830
5394
  ImportDeclaration(node) {
4831
- if (node.source.value === "dayjs" || node.source.value.startsWith("dayjs/")) {
4832
- context.report({ messageId: "dayjsOutsideDoor", node });
5395
+ const library = libraries.find(
5396
+ (one) => node.source.value === one || node.source.value.startsWith(`${one}/`)
5397
+ );
5398
+ if (library !== void 0) {
5399
+ context.report({ data: { door: where, library }, messageId: "dayjsOutsideDoor", node });
4833
5400
  }
4834
5401
  },
4835
5402
  NewExpression(node) {
4836
5403
  if (node.callee.type === "Identifier" && node.callee.name === "Date") {
4837
- context.report({ messageId: "outsideTimeDoor", node, data: { what: "new Date" } });
5404
+ context.report({
5405
+ data: { door: where, what: "new Date" },
5406
+ messageId: "outsideTimeDoor",
5407
+ node
5408
+ });
4838
5409
  }
4839
5410
  },
4840
5411
  CallExpression(node) {
@@ -4844,27 +5415,76 @@ var timeThroughTheDoor = {
4844
5415
  if (object === "Date" && property === "now") return;
4845
5416
  if (object === "Date" && dateStatics.has(property) || dateInstanceMethods.has(property)) {
4846
5417
  context.report({
5418
+ data: { door: where, what: `${object ?? "\u2026"}.${property}` },
4847
5419
  messageId: "outsideTimeDoor",
4848
- node,
4849
- data: { what: `${object ?? "\u2026"}.${property}` }
5420
+ node
4850
5421
  });
4851
5422
  }
4852
5423
  }
4853
5424
  };
4854
5425
  },
4855
- fixShape: `\`new Date(...)\`, \`Date.parse\`, \`toISOString\`, \`toLocaleDateString\`, \`dayjs\` \u2014 every time operation
4856
- goes through the one time module, which owns the plugins, the zone and the formats. Import
4857
- \`isoDate\` / \`today\` / \`addDays\` from it. \`Date.now()\` is allowed anywhere.`,
5426
+ fixShape: `\`new Date(...)\`, \`Date.parse\`, \`toISOString\`, \`toLocaleDateString\` and the repo's date
5427
+ library \u2014 every time operation goes through the one time module the \`door\` option names, which owns
5428
+ the plugins, the zone and the formats. Import \`isoDate\` / \`today\` / \`addDays\` from it, or add the
5429
+ operation there. A second module that imports the library is a second configured library: plugin
5430
+ state is global, so the two disagree the moment one of them extends something the other did not.
5431
+ Name the library in \`libraries\` (default \`['dayjs']\`) \u2014 a repo on date-fns or luxon says so and
5432
+ gets the whole rule rather than its raw-\`Date\` half.
5433
+
5434
+ Three kinds of time, never mixed. An INSTANT is epoch milliseconds. A BUSINESS DATE is an ISO date
5435
+ string, \`YYYY-MM-DD\`, parsed strictly in UTC \u2014 a calendar day is not an instant and rendering one as
5436
+ one moves it by a timezone. DISPLAY is a rendering: a formatter belongs at the edge, in the
5437
+ component or the response, never in a query, a saga or a stored value.
5438
+
5439
+ The clock is injected, never ambient. Take \`nowMs: number\` as a parameter and let the composition
5440
+ root pass \`Date.now()\` in \u2014 \`Date.now()\` is the one call allowed anywhere, and a function that reads
5441
+ it internally cannot be tested at a chosen instant.`,
4858
5442
  meta: {
4859
5443
  docs: {
4860
- description: "Time is handled by dayjs behind the time module (`utils/time`); nothing else constructs, parses or formats a Date. Date.now() is the one call allowed everywhere."
5444
+ description: "Time is handled behind one module \u2014 the `door` option names it \u2014 and nothing else constructs, parses or formats a Date or imports dayjs. Date.now() is the one call allowed everywhere. Enabled without a `door` it refuses the run."
4861
5445
  },
4862
5446
  messages: {
4863
- outsideTimeDoor: "{{what}} outside the time door. Import the operation from the time module (`utils/time`) (isoDate, today, addDays, addMonths, \u2026) or add it there.",
4864
- dayjsOutsideDoor: "dayjs is imported outside the time module (`utils/time`). The door owns the plugins and formats; import from it."
5447
+ outsideTimeDoor: "{{what}} outside the time door. Import the operation from `{{door}}` (isoDate, today, addDays, addMonths, \u2026) or add it there.",
5448
+ dayjsOutsideDoor: "`{{library}}` is imported outside the time door. `{{door}}` owns the plugins, the zone and the formats; import from it \u2014 a second configured instance of the library diverges from the first with no symptom."
4865
5449
  },
4866
- schema: [],
5450
+ schema: [
5451
+ {
5452
+ additionalProperties: false,
5453
+ properties: {
5454
+ door: { type: "string" },
5455
+ exempt: { items: { type: "string" }, type: "array" },
5456
+ libraries: { items: { type: "string" }, type: "array" }
5457
+ },
5458
+ type: "object"
5459
+ }
5460
+ ],
4867
5461
  type: "problem"
5462
+ },
5463
+ /**
5464
+ * A file OUTSIDE the door, which is where this rule speaks — the inverse of every other probe
5465
+ * here, whose options say where to look rather than where not to.
5466
+ *
5467
+ * The candidates are tried against the repo's own `door` and `exempt` rather than assumed clear:
5468
+ * a repo whose door pattern is broad enough to swallow all of them has configured a rule that
5469
+ * can never fire, and a probe that reported a plant it had not checked would call that green.
5470
+ */
5471
+ probe(options) {
5472
+ const { door = "", exempt = [], libraries = DEFAULT_LIBRARIES } = bagOf(options);
5473
+ const outside = ["geonosis-probe-clock.ts", "src/geonosis-probe-clock.ts"].find(
5474
+ (path2) => !new RegExp(door).test(path2) && !exempt.some((pattern) => new RegExp(pattern).test(path2))
5475
+ );
5476
+ if (outside === void 0) {
5477
+ throw new Error(
5478
+ `biological-architecture/${RULE20}: every path a probe could be written at is inside the \`door\` (${door}) or one of its \`exempt\` patterns, so there is nowhere left for this rule to speak.`
5479
+ );
5480
+ }
5481
+ return {
5482
+ path: outside,
5483
+ source: `import theClock from '${libraries[0] ?? DEFAULT_LIBRARIES[0]}'
5484
+
5485
+ export const clock = (): unknown => theClock
5486
+ `
5487
+ };
4868
5488
  }
4869
5489
  };
4870
5490
  var time_through_the_door_default = timeThroughTheDoor;
@@ -5263,6 +5883,83 @@ the tissue arrange that cell.`,
5263
5883
  });
5264
5884
  var tissue_no_stores_default = tissueNoStores;
5265
5885
 
5886
+ // src/rules/todo-names-its-plan.ts
5887
+ var ID_DEFAULT = String.raw`\d{3,}`;
5888
+ var commentsOf3 = (sourceCode) => sourceCode?.getAllComments?.() ?? sourceCode?.ast?.comments ?? [];
5889
+ var escapeForRegex3 = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
5890
+ var todoNamesItsPlan = {
5891
+ create(context) {
5892
+ const { id = ID_DEFAULT, markers = [] } = context.options?.[0] ?? {};
5893
+ requireOption("todo-names-its-plan", markers, "markers", "{ markers: string[], id?: string }");
5894
+ const marker = new RegExp(
5895
+ `\\b(${markers.map(escapeForRegex3).join("|")})\\b(?:\\(([^)]*)\\))?`,
5896
+ "g"
5897
+ );
5898
+ const names2 = new RegExp(`^${id}$`);
5899
+ return {
5900
+ Program(node) {
5901
+ for (const comment of commentsOf3(context.sourceCode)) {
5902
+ for (const found of comment.value.matchAll(marker)) {
5903
+ const cited = found[2];
5904
+ if (cited !== void 0 && names2.test(cited)) continue;
5905
+ context.report({
5906
+ data: {
5907
+ line: String(comment.loc.start.line),
5908
+ marker: found[1] ?? ""
5909
+ },
5910
+ loc: comment.loc,
5911
+ messageId: "unplannedTodo",
5912
+ node
5913
+ });
5914
+ }
5915
+ }
5916
+ }
5917
+ };
5918
+ },
5919
+ fixShape: `A debt marker names the plan that owns it: \`// TODO(025): the cache lands with the batteries floor\`.
5920
+ Write the plan number of the plan whose acceptance criterion this work belongs to \u2014 \`geonosis-ledger
5921
+ plan new <slug>\` makes one, and \`plan ready\` is what reasons over them. A marker with no plan is a
5922
+ promise with no owner and no date, and nothing downstream can schedule it. If the work is not worth a
5923
+ plan, delete the marker and the code it apologises for. Configure \`markers\` with the words this repo
5924
+ treats as debt (TODO, FIXME, HACK\u2026) and \`id\` with the shape of a plan id if it is not \`NNN\`;
5925
+ enabled with no \`markers\` the rule refuses the run, because a marker rule that knows no markers
5926
+ reads exactly like a clean tree.`,
5927
+ meta: {
5928
+ docs: {
5929
+ description: "A debt marker (TODO, FIXME, \u2026) must name the plan that owns it \u2014 `TODO(025):`. The marker words are an option and the plan-id shape is an option; enabled with no markers the rule refuses the run. Comments come from the parser, so the word in a string literal is not a finding."
5930
+ },
5931
+ messages: {
5932
+ unplannedTodo: "{{marker}} at line {{line}} names no plan. Write `{{marker}}(NNN):` with the plan that owns this work, or delete it \u2014 a marker outside the plan graph is work nobody scheduled."
5933
+ },
5934
+ schema: [
5935
+ {
5936
+ additionalProperties: false,
5937
+ properties: {
5938
+ id: { type: "string" },
5939
+ markers: { items: { type: "string" }, type: "array" }
5940
+ },
5941
+ type: "object"
5942
+ }
5943
+ ],
5944
+ type: "problem"
5945
+ },
5946
+ /**
5947
+ * The repo's OWN first marker, in a comment, citing nothing. It has to be the configured word:
5948
+ * a probe hardcoding one spelling would report the rule as unreachable in every repo that spells
5949
+ * its debt with another, which is the whole class of finding probes exist to end.
5950
+ */
5951
+ probe(options) {
5952
+ const { markers } = bagOf(options);
5953
+ return {
5954
+ path: "geonosis-probe-todo.ts",
5955
+ source: `// ${firstOf(markers, ["TODO"])}: a probe that names no plan
5956
+ export const geonosisProbeTodo = 1
5957
+ `
5958
+ };
5959
+ }
5960
+ };
5961
+ var todo_names_its_plan_default = todoNamesItsPlan;
5962
+
5266
5963
  // src/rules/lib/colors.ts
5267
5964
  var FUNCTIONS = [
5268
5965
  "color",
@@ -5602,9 +6299,9 @@ var isNamedOnly = (value) => /^[a-z]+$/i.test(value.trim());
5602
6299
  var tokens_only_colors_default = tokensOnlyColors;
5603
6300
 
5604
6301
  // src/rules/unique-query-step-name.ts
5605
- var RULE16 = "unique-query-step-name";
5606
- var DEFAULT_QUERY_STEP_CALLS = ["useQueryGraphStep", "useRemoteQueryStep"];
5607
- var DEFAULT_WORKFLOW_FACTORIES = ["createWorkflow"];
6302
+ var RULE21 = "unique-query-step-name";
6303
+ var ACCEPTS7 = "{ queryStepCalls: string[] (required), workflowFactories: string[] }";
6304
+ var DEFAULT_WORKFLOW_FACTORIES2 = ["createWorkflow"];
5608
6305
  var CONFIG2 = "config";
5609
6306
  var NAME = "name";
5610
6307
  var COLLIDES_FROM = 2;
@@ -5623,9 +6320,9 @@ var chainedName = (node, steps) => {
5623
6320
  const inner = callee.object;
5624
6321
  if (inner === void 0 || !isCallOf(inner, steps)) return null;
5625
6322
  const [first] = node.arguments ?? [];
5626
- const properties = propertiesOf2(first);
6323
+ const properties = propertiesOf(first);
5627
6324
  if (properties === null) return [inner, UNREADABLE];
5628
- const value = valueOf2(properties, NAME);
6325
+ const value = valueOf(properties, NAME);
5629
6326
  if (value === void 0) return [inner, ABSENT];
5630
6327
  const literal = stringOf(value);
5631
6328
  return [inner, literal === null ? UNREADABLE : { kind: "named", value: literal }];
@@ -5646,17 +6343,13 @@ var uniqueQueryStepName = {
5646
6343
  create(context) {
5647
6344
  const factories = new Set(
5648
6345
  requireOption(
5649
- RULE16,
5650
- context.options?.[0]?.workflowFactories ?? DEFAULT_WORKFLOW_FACTORIES,
6346
+ RULE21,
6347
+ context.options?.[0]?.workflowFactories ?? DEFAULT_WORKFLOW_FACTORIES2,
5651
6348
  "workflowFactories"
5652
6349
  )
5653
6350
  );
5654
6351
  const calls = new Set(
5655
- requireOption(
5656
- RULE16,
5657
- context.options?.[0]?.queryStepCalls ?? DEFAULT_QUERY_STEP_CALLS,
5658
- "queryStepCalls"
5659
- )
6352
+ requireOption(RULE21, context.options?.[0]?.queryStepCalls, "queryStepCalls", ACCEPTS7)
5660
6353
  );
5661
6354
  return {
5662
6355
  CallExpression(node) {
@@ -5690,11 +6383,12 @@ var uniqueQueryStepName = {
5690
6383
  \`useQueryGraphStep({ entity: 'order' }).config({ name: 'get-orders' })\`. Without it the step-handler
5691
6384
  map last-wins and one of the two reads silently never happens. A single un-named step is fine \u2014 it
5692
6385
  collides with nothing. A name held in a constant passes, since the rule cannot evaluate it.
5693
- Configure \`workflowFactories\` if the repo wraps \`createWorkflow\`, and \`queryStepCalls\` if it
5694
- wraps the query steps.`,
6386
+ Name the repo's query steps in \`queryStepCalls\` \u2014 enabled without it the rule refuses the run,
6387
+ because the collision it reports is one framework's step-handler map and not every engine's \u2014 and
6388
+ \`workflowFactories\` if the repo wraps \`createWorkflow\`.`,
5695
6389
  meta: {
5696
6390
  docs: {
5697
- description: "Every query step in a workflow body that holds more than one carries a unique `.config({ name })`. Medusa's step-handler map last-wins, so two un-named or same-named steps silently collide. A single un-named step is exempt. `workflowFactories` and `queryStepCalls` are options."
6391
+ description: "Every query step in a workflow body that holds more than one carries a unique `.config({ name })`. Medusa's step-handler map last-wins, so two un-named or same-named steps silently collide. A single un-named step is exempt. `workflowFactories` is an option; enabled without `queryStepCalls` it refuses the run."
5698
6392
  },
5699
6393
  messages: {
5700
6394
  duplicate: "Two query steps in this workflow both answer to `.config({ name: '{{name}}' })`. The step-handler map last-wins, so one of the two reads silently never happens \u2014 give each its own name.",
@@ -5719,8 +6413,8 @@ wraps the query steps.`,
5719
6413
  */
5720
6414
  probe(options) {
5721
6415
  const bag = bagOf(options);
5722
- const factory = firstOf(bag.workflowFactories, DEFAULT_WORKFLOW_FACTORIES);
5723
- const step = firstOf(bag.queryStepCalls, DEFAULT_QUERY_STEP_CALLS);
6416
+ const factory = firstOf(bag.workflowFactories, DEFAULT_WORKFLOW_FACTORIES2);
6417
+ const step = firstOf(bag.queryStepCalls, []);
5724
6418
  return {
5725
6419
  path: "geonosis-probe-query-steps.ts",
5726
6420
  source: `export const geonosisProbe = ${factory}('geonosis-probe', () => {
@@ -5736,7 +6430,8 @@ wraps the query steps.`,
5736
6430
  var unique_query_step_name_default = uniqueQueryStepName;
5737
6431
 
5738
6432
  // src/rules/worker-handles-are-scoped.ts
5739
- var cellWorkerPattern = /\/apps\/api\/src\//;
6433
+ var RULE22 = "worker-handles-are-scoped";
6434
+ var ACCEPTS8 = "{ within: string (a regex over the filename), wrapper: string[] }";
5740
6435
  var handlerKeys = /* @__PURE__ */ new Set(["fetch", "queue", "scheduled", "email"]);
5741
6436
  var entrypointMakers = /* @__PURE__ */ new Set([
5742
6437
  "WorkflowEntrypoint",
@@ -5745,8 +6440,10 @@ var entrypointMakers = /* @__PURE__ */ new Set([
5745
6440
  ]);
5746
6441
  var cellHandlesAreScoped = {
5747
6442
  create(context) {
5748
- const normalized = normalise(context.filename);
5749
- if (!cellWorkerPattern.test(normalized)) return {};
6443
+ const within = requireOption(RULE22, context.options?.[0]?.within, "within", ACCEPTS8);
6444
+ const wrappers = requireOption(RULE22, context.options?.[0]?.wrapper, "wrapper", ACCEPTS8);
6445
+ if (!new RegExp(within).test(normalise(context.filename))) return {};
6446
+ const scopedBy = new Set(wrappers);
5750
6447
  let handler = null;
5751
6448
  let handlerNode = null;
5752
6449
  let scoped = false;
@@ -5760,7 +6457,7 @@ var cellHandlesAreScoped = {
5760
6457
  },
5761
6458
  CallExpression(node) {
5762
6459
  if (node.callee?.type !== "Identifier") return;
5763
- if (node.callee.name === "withCells") scoped = true;
6460
+ if (node.callee.name !== void 0 && scopedBy.has(node.callee.name)) scoped = true;
5764
6461
  if (node.callee.name === "createWorkflowEntrypoint") {
5765
6462
  handler = "the workflow entrypoint";
5766
6463
  handlerNode = node;
@@ -5777,26 +6474,48 @@ var cellHandlesAreScoped = {
5777
6474
  "Program:exit"(node) {
5778
6475
  if (handler === null || scoped) return;
5779
6476
  context.report({
5780
- data: { handler },
6477
+ data: { handler, wrapper: wrappers[0] ?? "" },
5781
6478
  messageId: "handlerOutsideWithCells",
5782
6479
  node: handlerNode ?? node
5783
6480
  });
5784
6481
  }
5785
6482
  };
5786
6483
  },
5787
- fixShape: `A worker that opens a database-cell handle wraps every invocation in \`withCells\` \u2014 the module
5788
- handlers (\`fetch\`, \`queue\`, \`scheduled\`, \`email\`) and the workflow entrypoint alike \u2014 so the handle
5789
- is closed when the invocation ends: \`withCells(() => \u2026, (closing) => ctx.waitUntil(closing))\`. A
5790
- handler that declares itself and never calls it opens a pool per call and never closes one.`,
6484
+ fixShape: `A worker that opens a database-cell handle wraps every invocation in the repo's scope wrapper \u2014 the
6485
+ module handlers (\`fetch\`, \`queue\`, \`scheduled\`, \`email\`) and the workflow entrypoint alike \u2014 so the
6486
+ handle is closed when the invocation ends: \`withCells(() => \u2026, (closing) => ctx.waitUntil(closing))\`.
6487
+ A handler that declares itself and never calls it opens a pool per call and never closes one. Name
6488
+ the wrapper in \`wrapper\` and the tree the cell workers live in in \`within\`; enabled without either
6489
+ the rule refuses the run.`,
5791
6490
  meta: {
5792
6491
  docs: {
5793
- description: "Every invocation of a cell worker \u2014 the module handlers and the Workflow entrypoint \u2014 runs inside `withCells`, which shares one cell handle per invocation and closes it when the invocation ends. A file that declares such an invocation and never calls `withCells` opens a pool it never closes."
6492
+ description: "Every invocation of a cell worker \u2014 the module handlers and the Workflow entrypoint \u2014 runs inside the scope wrapper the `wrapper` option names, which shares one cell handle per invocation and closes it when the invocation ends. A file that declares such an invocation and never calls it opens a pool it never closes. The wrapper and the guarded tree are options; enabled without them it refuses the run."
5794
6493
  },
5795
6494
  messages: {
5796
- handlerOutsideWithCells: "This file declares {{handler}} and never calls `withCells`. Wrap the invocation \u2014 `withCells(() => \u2026, (closing) => ctx.waitUntil(closing))` \u2014 so the cell handle it opens is closed when it ends."
6495
+ handlerOutsideWithCells: "This file declares {{handler}} and never calls `{{wrapper}}`. Wrap the invocation \u2014 `{{wrapper}}(() => \u2026, (closing) => ctx.waitUntil(closing))` \u2014 so the cell handle it opens is closed when it ends."
5797
6496
  },
5798
- schema: [],
6497
+ schema: [
6498
+ {
6499
+ additionalProperties: false,
6500
+ properties: {
6501
+ within: { type: "string" },
6502
+ wrapper: { items: { type: "string" }, type: "array" }
6503
+ },
6504
+ type: "object"
6505
+ }
6506
+ ],
5799
6507
  type: "problem"
6508
+ },
6509
+ /** A module handler inside the configured tree that never opens a scope — the smallest breach. */
6510
+ probe(options) {
6511
+ const { within = "" } = bagOf(options);
6512
+ return {
6513
+ path: pathMatching(within, "geonosis-probe-worker.ts"),
6514
+ source: `export default {
6515
+ queue: async (): Promise<void> => undefined,
6516
+ }
6517
+ `
6518
+ };
5800
6519
  }
5801
6520
  };
5802
6521
  var worker_handles_are_scoped_default = cellHandlesAreScoped;
@@ -5893,80 +6612,105 @@ change. One field: \`useStore((s) => s.field)\`. Several fields: ONE call with
5893
6612
  var zustand_v5_best_practices_default = zustandV5BestPractices;
5894
6613
 
5895
6614
  // src/index.ts
5896
- var rules = {
5897
- "atom-no-deps": atom_no_deps_default,
5898
- "cell-must-be-stateful": cell_must_be_stateful_default,
5899
- "cell-must-not-compose-cell": cell_must_not_compose_cell_default,
5900
- "cell-no-cross-feature-organelles": cell_no_cross_feature_organelles_default,
5901
- "cell-no-tissues": cell_no_tissues_default,
5902
- "cells-folder-index-is-barrel": cells_folder_index_is_barrel_default,
5903
- "compound-must-be-stateless": compound_must_be_stateless_default,
5904
- "compound-no-stores": compound_no_stores_default,
5905
- "constants-in-constants-file": constants_in_constants_file_default,
5906
- "dialect-through-the-seam": dialect_through_the_seam_default,
5907
- "document-sagas-are-generic": document_sagas_are_generic_default,
5908
- "documents-share-one-table": documents_share_one_table_default,
5909
- "effect-hook-naming": effect_hook_naming_default,
5910
- "emit-declares-attempts": emit_declares_attempts_default,
5911
- "font-roles-only": font_roles_only_default,
5912
- "layer-walls": layer_walls_default,
5913
- "max-comment-density": max_comment_density_default,
5914
- "molecule-atoms-only": molecule_atoms_only_default,
5915
- "molecule-must-compose": molecule_must_compose_default,
5916
- "mutating-step-declares-retry": mutating_step_declares_retry_default,
5917
- "plugin-route-namespaced": plugin_route_namespaced_default,
5918
- "route-no-inline-mutation": route_no_inline_mutation_default,
5919
- "unique-query-step-name": unique_query_step_name_default,
5920
- "named-when-block": named_when_block_default,
5921
- "next-route-segment-is-thin-delegate": next_route_segment_is_thin_delegate_default,
5922
- "no-brand-names": no_brand_names_default,
5923
- "no-card-shaped-div": no_card_shaped_div_default,
5924
- "no-cross-feature-stores": no_cross_feature_stores_default,
5925
- "no-d1-transaction": no_d1_transaction_default,
5926
- "no-duplicate-jsx-patterns": no_duplicate_jsx_patterns_default,
5927
- "no-hook-in-component-disguise": no_hook_in_component_disguise_default,
5928
- "no-index-count-as-exact": no_index_count_as_exact_default,
5929
- "no-inert-hidden-jsx": no_inert_hidden_jsx_default,
5930
- "no-inline-data-in-jsx": no_inline_data_in_jsx_default,
5931
- "no-invalid-feature-folders": no_invalid_feature_folders_default,
5932
- "no-loader-side-effect-in-index-file": no_loader_side_effect_in_index_file_default,
5933
- "no-logic-in-component-files": no_logic_in_component_files_default,
5934
- "no-mock-db-in-integration": no_mock_db_in_integration_default,
5935
- "no-orm-outside-db": no_orm_outside_db_default,
5936
- "no-raw-html-atoms": no_raw_html_atoms_default,
5937
- "no-raw-sql-outside-allowed": no_raw_sql_outside_allowed_default,
5938
- "no-react-namespace": no_react_namespace_default,
5939
- "no-renamed-html-props": no_renamed_html_props_default,
5940
- "no-render-prop-reader": no_render_prop_reader_default,
5941
- "no-trivial-wrapper-component": no_trivial_wrapper_component_default,
5942
- "no-ts-in-bio-folders": no_ts_in_bio_folders_default,
5943
- "no-type-definitions-in-components": no_type_definitions_in_components_default,
5944
- "no-unregistered-plugin-dir": no_unregistered_plugin_dir_default,
5945
- "no-void-port": no_void_port_default,
5946
- "organelle-dependency": organelle_dependency_default,
5947
- "organelle-single-source": organelle_single_source_default,
5948
- "queries-require-org-scope": queries_require_org_scope_default,
5949
- "queue-loop-is-the-library": queue_loop_is_the_library_default,
5950
- "router-schema-parity": router_schema_parity_default,
5951
- "service-name-not-reserved": service_name_not_reserved_default,
5952
- "ssot-no-inline-facts": ssot_no_inline_facts_default,
5953
- "ssot-no-process-env": ssot_no_process_env_default,
5954
- "step-declares-compensation-or-none": step_declares_compensation_or_none_default,
5955
- "step-opens-its-own-cell": step_opens_its_own_cell_default,
5956
- "store-route-scopes-tenant-data": store_route_scopes_tenant_data_default,
5957
- "subscriber-declares-id": subscriber_declares_id_default,
5958
- "tables-declare-their-plane": tables_declare_their_plane_default,
5959
- "tenant-tables-carry-org-id": tenant_tables_carry_org_id_default,
5960
- "time-through-the-door": time_through_the_door_default,
5961
- "tokens-only-colors": tokens_only_colors_default,
5962
- "tissue-must-compose": tissue_must_compose_default,
5963
- "tissue-no-data-props": tissue_no_data_props_default,
5964
- "tissue-no-hooks": tissue_no_hooks_default,
5965
- "tissue-no-organelles": tissue_no_organelles_default,
5966
- "tissue-no-stores": tissue_no_stores_default,
5967
- "worker-handles-are-scoped": worker_handles_are_scoped_default,
5968
- "zustand-v5-best-practices": zustand_v5_best_practices_default
6615
+ var refusingOnce = (declared) => Object.fromEntries(
6616
+ Object.entries(declared).map(([name, rule]) => [
6617
+ name,
6618
+ { ...rule, create: refusingOncePerRun(rule.create) }
6619
+ ])
6620
+ );
6621
+ var withEngines = (declared) => {
6622
+ const presumed = new Map(
6623
+ PRESUMED_ENGINES.flatMap(
6624
+ (one) => one.rules.map((name) => [name, { engine: one.engine, packages: one.packages }])
6625
+ )
6626
+ );
6627
+ return Object.fromEntries(
6628
+ Object.entries(declared).map(([name, rule]) => {
6629
+ const engine = presumed.get(name);
6630
+ return [name, engine === void 0 ? rule : { ...rule, presumes: engine }];
6631
+ })
6632
+ );
5969
6633
  };
6634
+ var rules = refusingOnce(
6635
+ withEngines({
6636
+ "atom-no-deps": atom_no_deps_default,
6637
+ "boundary-time-is-primitive": boundary_time_is_primitive_default,
6638
+ "cell-must-be-stateful": cell_must_be_stateful_default,
6639
+ "cell-must-not-compose-cell": cell_must_not_compose_cell_default,
6640
+ "cell-no-cross-feature-organelles": cell_no_cross_feature_organelles_default,
6641
+ "cell-no-tissues": cell_no_tissues_default,
6642
+ "cells-folder-index-is-barrel": cells_folder_index_is_barrel_default,
6643
+ "compound-must-be-stateless": compound_must_be_stateless_default,
6644
+ "compound-no-stores": compound_no_stores_default,
6645
+ "constants-in-constants-file": constants_in_constants_file_default,
6646
+ "dialect-through-the-seam": dialect_through_the_seam_default,
6647
+ "document-sagas-are-generic": document_sagas_are_generic_default,
6648
+ "documents-share-one-table": documents_share_one_table_default,
6649
+ "durable-body-reads-through-steps": durable_body_reads_through_steps_default,
6650
+ "effect-hook-naming": effect_hook_naming_default,
6651
+ "emit-declares-attempts": emit_declares_attempts_default,
6652
+ "font-roles-only": font_roles_only_default,
6653
+ "layer-walls": layer_walls_default,
6654
+ "max-comment-density": max_comment_density_default,
6655
+ "molecule-atoms-only": molecule_atoms_only_default,
6656
+ "molecule-must-compose": molecule_must_compose_default,
6657
+ "mutating-step-declares-retry": mutating_step_declares_retry_default,
6658
+ "plugin-route-namespaced": plugin_route_namespaced_default,
6659
+ "route-no-inline-mutation": route_no_inline_mutation_default,
6660
+ "unique-query-step-name": unique_query_step_name_default,
6661
+ "named-when-block": named_when_block_default,
6662
+ "next-route-segment-is-thin-delegate": next_route_segment_is_thin_delegate_default,
6663
+ "no-brand-names": no_brand_names_default,
6664
+ "no-card-shaped-div": no_card_shaped_div_default,
6665
+ "no-cross-feature-stores": no_cross_feature_stores_default,
6666
+ "no-d1-transaction": no_d1_transaction_default,
6667
+ "no-duplicate-jsx-patterns": no_duplicate_jsx_patterns_default,
6668
+ "no-hook-in-component-disguise": no_hook_in_component_disguise_default,
6669
+ "no-index-count-as-exact": no_index_count_as_exact_default,
6670
+ "no-inert-hidden-jsx": no_inert_hidden_jsx_default,
6671
+ "no-inline-data-in-jsx": no_inline_data_in_jsx_default,
6672
+ "no-invalid-feature-folders": no_invalid_feature_folders_default,
6673
+ "no-loader-side-effect-in-index-file": no_loader_side_effect_in_index_file_default,
6674
+ "no-logic-in-component-files": no_logic_in_component_files_default,
6675
+ "no-mock-db-in-integration": no_mock_db_in_integration_default,
6676
+ "no-orm-outside-db": no_orm_outside_db_default,
6677
+ "no-raw-html-atoms": no_raw_html_atoms_default,
6678
+ "no-raw-sql-outside-allowed": no_raw_sql_outside_allowed_default,
6679
+ "no-react-namespace": no_react_namespace_default,
6680
+ "no-renamed-html-props": no_renamed_html_props_default,
6681
+ "no-render-prop-reader": no_render_prop_reader_default,
6682
+ "no-trivial-wrapper-component": no_trivial_wrapper_component_default,
6683
+ "no-ts-in-bio-folders": no_ts_in_bio_folders_default,
6684
+ "no-type-definitions-in-components": no_type_definitions_in_components_default,
6685
+ "no-unregistered-plugin-dir": no_unregistered_plugin_dir_default,
6686
+ "no-void-port": no_void_port_default,
6687
+ "organelle-dependency": organelle_dependency_default,
6688
+ "organelle-single-source": organelle_single_source_default,
6689
+ "queries-require-org-scope": queries_require_org_scope_default,
6690
+ "queue-loop-is-the-library": queue_loop_is_the_library_default,
6691
+ "router-schema-parity": router_schema_parity_default,
6692
+ "service-name-not-reserved": service_name_not_reserved_default,
6693
+ "ssot-no-inline-facts": ssot_no_inline_facts_default,
6694
+ "ssot-no-process-env": ssot_no_process_env_default,
6695
+ "step-declares-compensation-or-none": step_declares_compensation_or_none_default,
6696
+ "step-opens-its-own-cell": step_opens_its_own_cell_default,
6697
+ "store-route-scopes-tenant-data": store_route_scopes_tenant_data_default,
6698
+ "subpath-over-barrel": subpath_over_barrel_default,
6699
+ "subscriber-declares-id": subscriber_declares_id_default,
6700
+ "tables-declare-their-plane": tables_declare_their_plane_default,
6701
+ "tenant-tables-carry-org-id": tenant_tables_carry_org_id_default,
6702
+ "time-through-the-door": time_through_the_door_default,
6703
+ "tokens-only-colors": tokens_only_colors_default,
6704
+ "tissue-must-compose": tissue_must_compose_default,
6705
+ "tissue-no-data-props": tissue_no_data_props_default,
6706
+ "tissue-no-hooks": tissue_no_hooks_default,
6707
+ "tissue-no-organelles": tissue_no_organelles_default,
6708
+ "tissue-no-stores": tissue_no_stores_default,
6709
+ "todo-names-its-plan": todo_names_its_plan_default,
6710
+ "worker-handles-are-scoped": worker_handles_are_scoped_default,
6711
+ "zustand-v5-best-practices": zustand_v5_best_practices_default
6712
+ })
6713
+ );
5970
6714
  var { version } = createRequire(import.meta.url)("../package.json");
5971
6715
  var plugin = {
5972
6716
  configs: PRESETS,
@@ -5979,6 +6723,7 @@ var plugin = {
5979
6723
  var index_default = plugin;
5980
6724
  export {
5981
6725
  PRESETS,
6726
+ RENAMED_TO,
5982
6727
  index_default as default,
5983
6728
  rules,
5984
6729
  rulesOfPreset