@prestack/thread 0.0.1

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 (248) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/dist/attach.d.ts +1 -0
  3. package/dist/attach.js +1 -0
  4. package/dist/core/actor.d.ts +12 -0
  5. package/dist/core/actor.js +8 -0
  6. package/dist/core/asking.d.ts +180 -0
  7. package/dist/core/asking.js +1109 -0
  8. package/dist/core/attach.d.ts +54 -0
  9. package/dist/core/attach.js +510 -0
  10. package/dist/core/chain.d.ts +16 -0
  11. package/dist/core/chain.js +114 -0
  12. package/dist/core/cli.d.ts +2 -0
  13. package/dist/core/cli.js +1682 -0
  14. package/dist/core/condition.d.ts +52 -0
  15. package/dist/core/condition.js +198 -0
  16. package/dist/core/descend.d.ts +81 -0
  17. package/dist/core/descend.js +879 -0
  18. package/dist/core/finding.d.ts +12 -0
  19. package/dist/core/finding.js +23 -0
  20. package/dist/core/generators/meta.d.ts +2 -0
  21. package/dist/core/generators/meta.js +823 -0
  22. package/dist/core/init.d.ts +15 -0
  23. package/dist/core/init.js +134 -0
  24. package/dist/core/invoked.d.ts +9 -0
  25. package/dist/core/invoked.js +41 -0
  26. package/dist/core/round.d.ts +1 -0
  27. package/dist/core/round.js +57 -0
  28. package/dist/core/rows.d.ts +26 -0
  29. package/dist/core/rows.js +354 -0
  30. package/dist/core/scope.d.ts +6 -0
  31. package/dist/core/scope.js +12 -0
  32. package/dist/core/thread.d.ts +26 -0
  33. package/dist/core/thread.js +5 -0
  34. package/dist/core/tree.d.ts +12 -0
  35. package/dist/core/tree.js +56 -0
  36. package/dist/core/types.d.ts +49 -0
  37. package/dist/core/types.js +311 -0
  38. package/dist/derivation/attach.d.ts +2 -0
  39. package/dist/derivation/attach.js +61 -0
  40. package/dist/derivation/generators/build.d.ts +16 -0
  41. package/dist/derivation/generators/build.js +217 -0
  42. package/dist/derivation/generators/check.d.ts +18 -0
  43. package/dist/derivation/generators/check.js +78 -0
  44. package/dist/derivation/generators/derivation.d.ts +3 -0
  45. package/dist/derivation/generators/derivation.js +273 -0
  46. package/dist/derivation/generators/determinism.d.ts +20 -0
  47. package/dist/derivation/generators/determinism.js +110 -0
  48. package/dist/derivation/generators/release.d.ts +3 -0
  49. package/dist/derivation/generators/release.js +183 -0
  50. package/dist/derivation/makers/release.d.ts +1 -0
  51. package/dist/derivation/makers/release.js +107 -0
  52. package/dist/derivation/release.d.ts +10 -0
  53. package/dist/derivation/release.js +12 -0
  54. package/dist/integrations/attach.d.ts +6 -0
  55. package/dist/integrations/attach.js +177 -0
  56. package/dist/integrations/generators/read.d.ts +1 -0
  57. package/dist/integrations/generators/read.js +92 -0
  58. package/dist/integrations/generators/sdk.d.ts +1 -0
  59. package/dist/integrations/generators/sdk.js +148 -0
  60. package/dist/integrations/generators/secret.d.ts +1 -0
  61. package/dist/integrations/generators/secret.js +233 -0
  62. package/dist/integrations/generators/target.d.ts +1 -0
  63. package/dist/integrations/generators/target.js +765 -0
  64. package/dist/integrations/generators/vault.d.ts +1 -0
  65. package/dist/integrations/generators/vault.js +164 -0
  66. package/dist/integrations/sdk.d.ts +6 -0
  67. package/dist/integrations/sdk.js +151 -0
  68. package/dist/integrations/secret.d.ts +16 -0
  69. package/dist/integrations/secret.js +30 -0
  70. package/dist/integrations/target.d.ts +49 -0
  71. package/dist/integrations/target.js +225 -0
  72. package/dist/integrations/vault.d.ts +12 -0
  73. package/dist/integrations/vault.js +104 -0
  74. package/dist/integrity/attach.d.ts +2 -0
  75. package/dist/integrity/attach.js +213 -0
  76. package/dist/integrity/check-threads.d.ts +10 -0
  77. package/dist/integrity/check-threads.js +153 -0
  78. package/dist/integrity/constraint.d.ts +16 -0
  79. package/dist/integrity/constraint.js +1105 -0
  80. package/dist/integrity/generators/compiler.d.ts +1 -0
  81. package/dist/integrity/generators/compiler.js +130 -0
  82. package/dist/integrity/generators/gate.d.ts +1 -0
  83. package/dist/integrity/generators/gate.js +172 -0
  84. package/dist/integrity/generators/issues.d.ts +1 -0
  85. package/dist/integrity/generators/issues.js +100 -0
  86. package/dist/integrity/generators/publish.d.ts +1 -0
  87. package/dist/integrity/generators/publish.js +186 -0
  88. package/dist/integrity/generators/rule.d.ts +2 -0
  89. package/dist/integrity/generators/rule.js +213 -0
  90. package/dist/integrity/generators/seal.d.ts +4 -0
  91. package/dist/integrity/generators/seal.js +101 -0
  92. package/dist/integrity/generators/trial.d.ts +39 -0
  93. package/dist/integrity/generators/trial.js +254 -0
  94. package/dist/integrity/lint.d.ts +50 -0
  95. package/dist/integrity/lint.js +161 -0
  96. package/dist/integrity/lsp/bin.d.ts +2 -0
  97. package/dist/integrity/lsp/bin.js +35 -0
  98. package/dist/integrity/lsp/diagnostics.d.ts +25 -0
  99. package/dist/integrity/lsp/diagnostics.js +57 -0
  100. package/dist/integrity/lsp/findings.d.ts +20 -0
  101. package/dist/integrity/lsp/findings.js +39 -0
  102. package/dist/integrity/lsp/lint.d.ts +7 -0
  103. package/dist/integrity/lsp/lint.js +95 -0
  104. package/dist/integrity/lsp/protocol.d.ts +20 -0
  105. package/dist/integrity/lsp/protocol.js +57 -0
  106. package/dist/integrity/lsp/server.d.ts +15 -0
  107. package/dist/integrity/lsp/server.js +171 -0
  108. package/dist/integrity/sealed.d.ts +1 -0
  109. package/dist/integrity/sealed.js +56 -0
  110. package/dist/integrity/slopnator.d.ts +90 -0
  111. package/dist/integrity/slopnator.js +267 -0
  112. package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
  113. package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
  114. package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
  115. package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
  116. package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
  117. package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
  118. package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
  119. package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
  120. package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +85 -0
  122. package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
  123. package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
  124. package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
  125. package/dist/integrity/slopnators/sl_missing_row.js +180 -0
  126. package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
  127. package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
  128. package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
  129. package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
  130. package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
  131. package/dist/integrity/slopnators/sl_no_comment.js +41 -0
  132. package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
  134. package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
  135. package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
  136. package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
  138. package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
  139. package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
  140. package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
  141. package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
  142. package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
  143. package/dist/integrity/slopnators/sl_stale_path.js +92 -0
  144. package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
  145. package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
  146. package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
  147. package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
  149. package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
  150. package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
  151. package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
  152. package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
  153. package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
  155. package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
  156. package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
  157. package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
  158. package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
  160. package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
  161. package/dist/integrity/slopnators/sl_undescribed.js +54 -0
  162. package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
  164. package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
  165. package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
  167. package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
  168. package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
  169. package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
  171. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
  172. package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
  173. package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
  174. package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
  175. package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
  176. package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
  177. package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
  178. package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
  180. package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
  181. package/dist/integrity/slopnators/sl_unversioned.js +31 -0
  182. package/dist/integrity/sweep.d.ts +24 -0
  183. package/dist/integrity/sweep.js +264 -0
  184. package/dist/knowledge/attach.d.ts +2 -0
  185. package/dist/knowledge/attach.js +339 -0
  186. package/dist/knowledge/generators/docsgen.d.ts +1 -0
  187. package/dist/knowledge/generators/docsgen.js +145 -0
  188. package/dist/knowledge/generators/glossary.d.ts +1 -0
  189. package/dist/knowledge/generators/glossary.js +56 -0
  190. package/dist/knowledge/generators/skills.d.ts +1 -0
  191. package/dist/knowledge/generators/skills.js +222 -0
  192. package/dist/knowledge/glossary.d.ts +6 -0
  193. package/dist/knowledge/glossary.js +1 -0
  194. package/dist/operations/access.d.ts +27 -0
  195. package/dist/operations/access.js +61 -0
  196. package/dist/operations/attach.d.ts +6 -0
  197. package/dist/operations/attach.js +414 -0
  198. package/dist/operations/capabilities.d.ts +37 -0
  199. package/dist/operations/capabilities.js +35 -0
  200. package/dist/operations/cost.d.ts +14 -0
  201. package/dist/operations/cost.js +37 -0
  202. package/dist/operations/dispatch.d.ts +14 -0
  203. package/dist/operations/dispatch.js +327 -0
  204. package/dist/operations/error-results.d.ts +4 -0
  205. package/dist/operations/error-results.js +25 -0
  206. package/dist/operations/errors/catalog.d.ts +58 -0
  207. package/dist/operations/errors/catalog.js +91 -0
  208. package/dist/operations/errors/classes.d.ts +73 -0
  209. package/dist/operations/errors/classes.js +22 -0
  210. package/dist/operations/errors/errors.d.ts +52 -0
  211. package/dist/operations/errors/errors.js +257 -0
  212. package/dist/operations/generators/capability.d.ts +5 -0
  213. package/dist/operations/generators/capability.js +603 -0
  214. package/dist/operations/generators/compile.d.ts +2 -0
  215. package/dist/operations/generators/compile.js +541 -0
  216. package/dist/operations/generators/grant.d.ts +1 -0
  217. package/dist/operations/generators/grant.js +267 -0
  218. package/dist/operations/generators/operation.d.ts +1 -0
  219. package/dist/operations/generators/operation.js +811 -0
  220. package/dist/operations/generators/permits.d.ts +1 -0
  221. package/dist/operations/generators/permits.js +196 -0
  222. package/dist/operations/generators/seed.d.ts +20 -0
  223. package/dist/operations/generators/seed.js +285 -0
  224. package/dist/operations/generators/trial.d.ts +53 -0
  225. package/dist/operations/generators/trial.js +376 -0
  226. package/dist/operations/http.d.ts +30 -0
  227. package/dist/operations/http.js +121 -0
  228. package/dist/operations/makers/capability.d.ts +1 -0
  229. package/dist/operations/makers/capability.js +117 -0
  230. package/dist/operations/makers/trial.d.ts +1 -0
  231. package/dist/operations/makers/trial.js +413 -0
  232. package/dist/operations/memory.d.ts +104 -0
  233. package/dist/operations/memory.js +365 -0
  234. package/dist/operations/metering.d.ts +55 -0
  235. package/dist/operations/metering.js +247 -0
  236. package/dist/operations/nodes.d.ts +35 -0
  237. package/dist/operations/nodes.js +15 -0
  238. package/dist/operations/operations.d.ts +18 -0
  239. package/dist/operations/operations.js +5 -0
  240. package/dist/operations/queue-policy.d.ts +3 -0
  241. package/dist/operations/queue-policy.js +8 -0
  242. package/dist/operations/result.d.ts +19 -0
  243. package/dist/operations/result.js +10 -0
  244. package/dist/operations/seams.d.ts +239 -0
  245. package/dist/operations/seams.js +45 -0
  246. package/dist/operations/store.d.ts +14 -0
  247. package/dist/operations/store.js +1 -0
  248. package/package.json +16 -0
@@ -0,0 +1,301 @@
1
+ import { dirname, join, normalize } from 'node:path';
2
+ import { parser } from 'typescript-eslint';
3
+ import { descend } from "../../core/descend.js";
4
+ import { here, index, parses, unreadable } from "./sl_unthreaded.js";
5
+ const RELATIVE = /^\.\.?\//;
6
+ const UNDER = 'operations/';
7
+ const NODES = 'nodes';
8
+ /*
9
+ * The one thing an operation may be handed to.
10
+ *
11
+ * It is still `dispatch` and it is still the only one. What moved 2026-08-28 is where it lives:
12
+ * it was imported from this framework, and it is now the app's own `operations/_dispatch.ts`,
13
+ * written by `thread compile`. The name a caller writes did not change, so neither did this.
14
+ */
15
+ const INVOKER = 'dispatch';
16
+ const slash = (one) => one.split('\\').join('/');
17
+ /* Where a name is bound, rather than used. An import is how the operation arrives. */
18
+ const BINDS = new Set(['ImportSpecifier', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier']);
19
+ /*
20
+ * Where a name is a shape, not a thing.
21
+ *
22
+ * `const one: typeof operation` runs nothing and reaches nothing — it borrows the type and is erased
23
+ * before anything executes. Refusing it was a false positive found by reading, not by running: the
24
+ * probe written to test the reassignment form used exactly this line to type its own variable, and
25
+ * the law would have refused the probe for its type annotation rather than for the bypass under it.
26
+ */
27
+ const TYPED = new Set(['TSTypeQuery', 'TSTypeReference', 'TSQualifiedName', 'TSImportType']);
28
+ let bodies;
29
+ /*
30
+ * Which files are operation bodies, asked of the tree rather than guessed from a directory.
31
+ *
32
+ * An operation body is a file a row beneath `operations` DERIVES and that is not a node's body, and
33
+ * the descent already knows every one of them. Reading the directory instead was close enough while
34
+ * an operation was the only thing living there — and stopped being close enough the moment an
35
+ * operation was folded into a body plus the steps it is woven from: the woven body imports its own
36
+ * steps, the steps sat under `operations/`, and this law refused the one file it exists to protect
37
+ * for importing the one file it is made of. A rule that refuses the sanctioned form is deciding
38
+ * nothing, which is the same failure this law was rewritten for on 2026-08-25.
39
+ */
40
+ function operations() {
41
+ if (bodies !== undefined)
42
+ return bodies;
43
+ const pass = descend();
44
+ const under = (id, root, seen) => {
45
+ if (id === root)
46
+ return true;
47
+ if (seen.has(id))
48
+ return false;
49
+ seen.add(id);
50
+ return (pass.coined.get(id)?.from ?? []).some((one) => under(one, root, seen));
51
+ };
52
+ const found = new Set();
53
+ for (const [at, threadings] of pass.threaded) {
54
+ for (const one of threadings) {
55
+ if (one.list !== 'derives')
56
+ continue;
57
+ if (!under(one.by, 'operations', new Set()))
58
+ continue;
59
+ if (under(one.by, NODES, new Set()))
60
+ continue;
61
+ found.add(at);
62
+ }
63
+ }
64
+ bodies = found;
65
+ return found;
66
+ }
67
+ /*
68
+ * Whether a path is an operation body.
69
+ *
70
+ * The tree decides for every file the tree reaches. For a file it does not reach — a probe, a
71
+ * scratch file, anything not yet declared — the shape decides: an operation is seeded as one file
72
+ * directly inside `operations/`, so a single segment is a body and a deeper path is something an
73
+ * operation is built from. Nothing the tree does not reach can be part of an app anyway; that
74
+ * fallback is what makes this law's own proofs mean the same thing in every repository.
75
+ */
76
+ function isBody(path) {
77
+ const known = index().reached;
78
+ if (known.has(path))
79
+ return operations().has(path);
80
+ if (!path.startsWith(UNDER))
81
+ return false;
82
+ return !path.slice(UNDER.length).includes('/');
83
+ }
84
+ const TRIED = ['', '.ts', '.tsx'];
85
+ function reaches(said, from) {
86
+ if (said === '')
87
+ return false;
88
+ const at = slash(normalize(RELATIVE.test(said) ? join(from, said) : said));
89
+ return TRIED.some((one) => isBody(`${at}${one}`));
90
+ }
91
+ /* Whatever a run-time reach spells out, whole or in pieces. */
92
+ function spelled(node, said) {
93
+ if (node === undefined || node === null || typeof node !== 'object')
94
+ return;
95
+ if (node.type === 'Literal' && typeof node.value === 'string')
96
+ said.push(node.value);
97
+ for (const one of node.quasis ?? []) {
98
+ const held = one.value?.cooked ?? one.value?.raw;
99
+ if (typeof held === 'string')
100
+ said.push(held);
101
+ }
102
+ for (const value of Object.values(node)) {
103
+ if (Array.isArray(value))
104
+ for (const one of value)
105
+ spelled(one, said);
106
+ else if (value !== null && typeof value === 'object')
107
+ spelled(value, said);
108
+ }
109
+ }
110
+ const IMPORTS = /import\s+((?:type\s+)?[^'"]*?)\s+from\s+['"]([^'"]+)['"]/g;
111
+ const LOCAL = /(?:^|[,{]|\s)(?:[A-Za-z_$][\w$]*\s+as\s+)?([A-Za-z_$][\w$]*)/g;
112
+ /*
113
+ * `dispatch` IS NOT AN OPERATION, AND IT LIVES WHERE THE OPERATIONS LIVE.
114
+ *
115
+ * `bound` below calls every name imported from under the operations directory an operation, which is
116
+ * right for every file there but one: an app's own dispatch is written to `operations/_dispatch.ts`,
117
+ * so a file importing `dispatch` from beside its operations had `dispatch` itself added to the set —
118
+ * and then this law refused that file for doing something with an operation other than handing it to
119
+ * dispatch. The thing it was accused of mishandling was the handler.
120
+ *
121
+ * It had never surfaced because nothing imported dispatch from inside that directory. Measured
122
+ * 2026-08-30, when the generated client gained a binding that calls the app's own dispatch for an
123
+ * operation needing no server, and the app refused a file its own generator had just written.
124
+ *
125
+ * The other three below are not names at all — they are the words of an import clause that the
126
+ * regex cannot tell from an identifier. This one is a real binding that is really imported and
127
+ * really is not an operation, which is the same answer for a different reason.
128
+ */
129
+ const NOT_A_NAME = new Set(['type', 'as', 'from', 'import', 'dispatch']);
130
+ /*
131
+ * The names this file bound by importing an operation body.
132
+ *
133
+ * A type-only import binds nothing that can be run and is skipped: `import type { Operation }` names
134
+ * a shape, and a shape cannot be dispatched or reached past.
135
+ */
136
+ function bound(text, from) {
137
+ const found = new Set();
138
+ for (const one of text.matchAll(IMPORTS)) {
139
+ const clause = one[1] ?? '';
140
+ if (/^type\s/.test(clause))
141
+ continue;
142
+ if (!reaches(one[2] ?? '', from))
143
+ continue;
144
+ for (const name of clause.matchAll(LOCAL)) {
145
+ const held = name[1];
146
+ if (held !== undefined && !NOT_A_NAME.has(held))
147
+ found.add(held);
148
+ }
149
+ }
150
+ return found;
151
+ }
152
+ /*
153
+ * Parsed here rather than taken from the visitor.
154
+ *
155
+ * `holds` is handed a node when a law runs as an eslint rule and nothing at all when the sweep runs
156
+ * it over a file — measured 2026-08-25, the sweep calls it once per file with `node` undefined, so a
157
+ * reading that only inspected what it was given threw on the first file it saw. What a file DOES
158
+ * with a binding is a question about the whole file anyway, not about one node in it.
159
+ *
160
+ * The parent comes down with the node because the whole rule is about where a name appears.
161
+ */
162
+ function walked(node, up, seen) {
163
+ if (node === null || typeof node !== 'object')
164
+ return undefined;
165
+ if (Array.isArray(node)) {
166
+ for (const one of node) {
167
+ const said = walked(one, up, seen);
168
+ if (said !== undefined)
169
+ return said;
170
+ }
171
+ return undefined;
172
+ }
173
+ const held = node;
174
+ const real = typeof held.type === 'string';
175
+ if (real) {
176
+ const said = seen(held, up);
177
+ if (said !== undefined)
178
+ return said;
179
+ }
180
+ for (const value of Object.values(node)) {
181
+ const said = walked(value, real ? held : up, seen);
182
+ if (said !== undefined)
183
+ return said;
184
+ }
185
+ return undefined;
186
+ }
187
+ export const slopnator = {
188
+ holds: (_node, at) => {
189
+ const path = here(at.filename);
190
+ if (unreadable(path, at.text) !== undefined)
191
+ return false;
192
+ if (!parses(path))
193
+ return false;
194
+ const text = String(at.text);
195
+ if (!text.includes(UNDER))
196
+ return false;
197
+ const from = dirname(path);
198
+ const mine = bound(text, from);
199
+ let ast;
200
+ try {
201
+ ast = parser.parseForESLint(text, {
202
+ range: true,
203
+ ecmaVersion: 'latest',
204
+ sourceType: 'module',
205
+ ecmaFeatures: { jsx: true },
206
+ }).ast;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ /*
212
+ * One rule, stated once.
213
+ *
214
+ * The first reading of this enumerated the SHAPES of a bypass — a member access, a call, an
215
+ * argument — and every shape it did not think of walked through: an alias, a destructure, an
216
+ * array holding the name, a comma operator. Enumerating forms is how a refusal ends up with a
217
+ * list of everything somebody happened to try. The rule is not a list. An operation goes to
218
+ * `dispatch` and nowhere else, so the name may appear where it is bound and as an argument to
219
+ * `dispatch`, and every other appearance is the finding — whatever it happens to look like.
220
+ */
221
+ return (walked(ast, undefined, (held, parent) => {
222
+ /*
223
+ * A body reached at run time never binds a name at all, so the rule above cannot see it.
224
+ *
225
+ * What is read is everything the reach spells out, whole or in pieces: a plain literal, a
226
+ * concatenation, a template. Measured 2026-08-26 against the reading this replaces, which
227
+ * only ever inspected a plain `Literal` — `await import('../operations/' + name + '.ts')`
228
+ * passed it without a word, which is the exact form its own comment said it was written to
229
+ * catch. Nothing legitimate needs any of it: dispatch is handed the operation, and the one
230
+ * file that hands it over imports it statically.
231
+ */
232
+ const dynamic = held.type === 'ImportExpression'
233
+ ? held.source
234
+ : held.type === 'CallExpression' && held.callee?.type === 'Identifier' && held.callee.name === 'require'
235
+ ? held.arguments?.[0]
236
+ : undefined;
237
+ if (dynamic !== undefined) {
238
+ const said = [];
239
+ spelled(dynamic, said);
240
+ const reaching = said.find((one) => one.includes(UNDER) || reaches(one, from));
241
+ if (reaching !== undefined) {
242
+ return (`this reaches \`${reaching}\` at run time, which points at an operation. An operation is handed ` +
243
+ `to \`${INVOKER}\`, and a file that goes looking for one is looking for a second way in`);
244
+ }
245
+ }
246
+ if (held.type !== 'Identifier' || typeof held.name !== 'string' || !mine.has(held.name))
247
+ return undefined;
248
+ const up = parent?.type ?? '';
249
+ if (BINDS.has(up) || TYPED.has(up))
250
+ return undefined;
251
+ if (parent?.type === 'CallExpression' &&
252
+ parent.callee?.type === 'Identifier' &&
253
+ parent.callee.name === INVOKER &&
254
+ (parent.arguments ?? []).includes(held)) {
255
+ return undefined;
256
+ }
257
+ return (`\`${held.name}\` is an operation, and this does something with it other than hand it to ` +
258
+ `\`${INVOKER}\`. Every gate that decides whether it may run is in \`${INVOKER}\` — naming it, ` +
259
+ 'aliasing it, taking it apart or passing it on all reach past every one of them');
260
+ }) ?? false);
261
+ },
262
+ /*
263
+ * These were driven at `holds` directly, because the trial harness cannot run them.
264
+ *
265
+ * A shipped law is meant to be proved by `thread trials`, and today nothing proves one anywhere:
266
+ * the harness cannot run in this repository at all — the framework has no tree, and the command
267
+ * that once tried was removed in 6e98a6d after it was measured exiting 1 — and in an app
268
+ * `thread trials` answers "0 trials from the 0 rule(s) this tree declares",
269
+ * because a shipped constraint is proved in the package that ships it. So every proof below was
270
+ * written to a real probe file and handed to `slopnator.holds` by hand, 2026-08-26: fourteen
271
+ * refused, six accepted, no failures. That is a stand-in for the harness and it is written down
272
+ * here rather than only in a report, because a proof nobody can run is indistinguishable from a
273
+ * proof that passes, and the next person to read this list deserves to know which one it is.
274
+ */
275
+ proves: {
276
+ wrong: [
277
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation.run(input)",
278
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nconst { run } = operation\n\nawait run(hands, held)",
279
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nawait operation(input)",
280
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nawait somewhere(operation, input)",
281
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nconst one = operation\n\nawait one.run(input)",
282
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nconst { requires } = operation",
283
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nlet one\none = operation\nawait one.run(input)",
284
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nconst [one] = [operation]\n\nawait one.run(input)",
285
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nawait (0, operation).run(input)",
286
+ "import { operation } from './operations/wf_send_invoice.ts'\n\nexport { operation }",
287
+ "const held = await import('./operations/wf_send_invoice.ts')",
288
+ "const held = require('../operations/wf_send_invoice.ts')",
289
+ "const held = await import('../operations/' + name + '.ts')",
290
+ 'const held = await import(`../operations/${name}.ts`)',
291
+ ],
292
+ right: [
293
+ "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nawait dispatch(operation, input, actor, seams)",
294
+ "import type { Operation } from '../../operations/operations.ts'\n\nexport const held: Operation = one",
295
+ "import { dispatch } from './operations/_dispatch.ts'\n\nawait dispatch(one, input, actor, seams)",
296
+ "import { dispatch } from './operations/_dispatch.ts'\n\nimport { operation } from './operations/wf_send_invoice.ts'\n\nlet one: typeof operation\n\nawait dispatch(operation, input, actor, seams)",
297
+ "import * as steps from './operations/wf_send_invoice/steps.ts'\n\nexport const held = steps.reads_the_invoice",
298
+ "import * as steps from './steps/wf_send_invoice.ts'\n\nexport const held = steps.reads_the_invoice",
299
+ ],
300
+ },
301
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { findTree } from "../../core/tree.js";
4
+ const ROOT = findTree().at;
5
+ const IMPORTS = /(?:^|\n)\s*import\s[^'"]*['"]([^'"]+)['"]/g;
6
+ const target = (from, spec) => spec.startsWith('.') ? resolve(dirname(from), spec) : undefined;
7
+ export const slopnator = {
8
+ on: ['ImportDeclaration'],
9
+ holds: (node, at) => {
10
+ const spec = node.source?.value;
11
+ if (typeof spec !== 'string')
12
+ return false;
13
+ const here = resolve(ROOT, at.filename);
14
+ const there = target(here, spec);
15
+ if (there === undefined || !existsSync(there))
16
+ return false;
17
+ for (const found of readFileSync(there, 'utf8').matchAll(IMPORTS)) {
18
+ const back = found[1] ?? '';
19
+ if (target(there, back) === here)
20
+ return `${spec} imports \`${back}\` straight back, so the two initialise each other`;
21
+ }
22
+ return false;
23
+ },
24
+ proves: {
25
+ wrong: [
26
+ {
27
+ files: [
28
+ { at: 'threads/_probe_one.ts', source: "import { two } from './_probe_two.ts'\nexport const one = two\n" },
29
+ { at: 'threads/_probe_two.ts', source: "import { one } from './_probe_one.ts'\nexport const two = one\n" },
30
+ ],
31
+ at: 'threads/_probe_one.ts',
32
+ },
33
+ ],
34
+ right: [
35
+ "import type { Law } from '../../core/generators/meta.ts'",
36
+ "import { SLUG, type Shaped } from '../../core/types.ts'",
37
+ "import { existsSync } from 'node:fs'",
38
+ "import type { Rule } from 'eslint'",
39
+ "import { CONSTRAINTS } from '../constraint.ts'",
40
+ ],
41
+ },
42
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,55 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { findTree } from "../../core/tree.js";
4
+ const ROOT = findTree().at;
5
+ const keyOf = (property) => {
6
+ if (property.computed === true)
7
+ return undefined;
8
+ const key = property.key;
9
+ if (key?.type === 'Identifier')
10
+ return key.name;
11
+ if (key?.type === 'Literal' && typeof key.value === 'string')
12
+ return key.value;
13
+ return undefined;
14
+ };
15
+ function scripts() {
16
+ try {
17
+ const held = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8'));
18
+ return new Set(Object.keys(held.scripts ?? {}));
19
+ }
20
+ catch {
21
+ return new Set();
22
+ }
23
+ }
24
+ export const slopnator = {
25
+ on: ['Property'],
26
+ holds: (node) => {
27
+ const property = node;
28
+ const key = keyOf(property);
29
+ if (key !== 'runs' && key !== 'by')
30
+ return false;
31
+ const held = property.value;
32
+ if (held?.type !== 'Literal' || typeof held.value !== 'string')
33
+ return false;
34
+ const spoken = held.value.trim();
35
+ if (!spoken.startsWith('pnpm '))
36
+ return false;
37
+ const named = spoken.replace(/^pnpm (run )?/, '').split(/\s+/)[0] ?? '';
38
+ const known = scripts();
39
+ if (known.size === 0 || known.has(named))
40
+ return false;
41
+ return `\`${key}: ${spoken}\` names the script \`${named}\`, and package.json has no such script`;
42
+ },
43
+ proves: {
44
+ wrong: [
45
+ "const M = {\n runs: 'pnpm nope:check',\n}",
46
+ "const M = {\n by: 'pnpm also-not-a-script',\n}",
47
+ ],
48
+ right: [
49
+ "const M = {\n runs: 'pnpm typecheck',\n}",
50
+ "const M = {\n by: 'pnpm gate:generate',\n}",
51
+ "const M = {\n runs: 'node scripts/_generated/gate.mjs',\n}",
52
+ "const M = {\n describes: 'A sentence mentioning pnpm nope:check without naming it as a command.',\n}",
53
+ ],
54
+ },
55
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,59 @@
1
+ export const slopnator = {
2
+ on: ['CallExpression'],
3
+ holds: (node) => {
4
+ const held = node;
5
+ if (held.callee?.type !== 'Identifier' || held.callee.name !== 'only')
6
+ return false;
7
+ if (held.arguments?.length !== 3)
8
+ return false;
9
+ const keys = held.arguments[1];
10
+ if (keys?.type !== 'ArrayExpression')
11
+ return false;
12
+ const typed = (keys.elements ?? []).filter((one) => one?.type !== 'SpreadElement');
13
+ if (typed.length === 0)
14
+ return false;
15
+ return `it hands \`only\` ${String(typed.length)} key(s) written here, and the receipt prints its questions from somewhere else`;
16
+ },
17
+ proves: {
18
+ wrong: [
19
+ {
20
+ files: [
21
+ {
22
+ at: 'packages/core/src/integrations/generators/probe.ts',
23
+ source: "only(piped, ['name', 'describes'], 'a thing')\n",
24
+ },
25
+ ],
26
+ at: 'packages/core/src/integrations/generators/probe.ts',
27
+ },
28
+ {
29
+ files: [
30
+ {
31
+ at: 'packages/core/src/integrations/generators/probe.ts',
32
+ source: "only(one, ['stop', 'sets'], 'a stop')\n",
33
+ },
34
+ ],
35
+ at: 'packages/core/src/integrations/generators/probe.ts',
36
+ },
37
+ ],
38
+ right: [
39
+ {
40
+ files: [
41
+ {
42
+ at: 'packages/core/src/integrations/generators/probe.ts',
43
+ source: "const TAKES = ASKS.map((one) => one.key)\nonly(piped, TAKES, 'a thing')\n",
44
+ },
45
+ ],
46
+ at: 'packages/core/src/integrations/generators/probe.ts',
47
+ },
48
+ {
49
+ files: [
50
+ {
51
+ at: 'packages/core/src/integrations/generators/probe.ts',
52
+ source: "only(all, [...allowed, ...worded], 'a thing')\n",
53
+ },
54
+ ],
55
+ at: 'packages/core/src/integrations/generators/probe.ts',
56
+ },
57
+ ],
58
+ },
59
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,89 @@
1
+ /*
2
+ * Credentials that announce themselves.
3
+ *
4
+ * A vendor prefix is the one thing here that is certain rather than probable: nothing else in a
5
+ * source file begins `sk_live_` or `AKIA` followed by twenty characters. These are matched on their
6
+ * own, with no need to look at what they were assigned to.
7
+ */
8
+ const ANNOUNCED = [
9
+ { what: 'a Stripe secret key', pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{16,}/ },
10
+ { what: 'a Stripe restricted key', pattern: /\brk_(?:live|test)_[A-Za-z0-9]{16,}/ },
11
+ { what: 'a GitHub token', pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}/ },
12
+ { what: 'a GitHub personal access token', pattern: /\bgithub_pat_[A-Za-z0-9_]{30,}/ },
13
+ { what: 'an AWS access key id', pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/ },
14
+ { what: 'a Slack token', pattern: /\bxox[abposr]-[A-Za-z0-9-]{10,}/ },
15
+ { what: 'an OpenAI key', pattern: /\bsk-[A-Za-z0-9_-]{32,}/ },
16
+ { what: 'an Anthropic key', pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}/ },
17
+ { what: 'a Google API key', pattern: /\bAIza[A-Za-z0-9_-]{35}\b/ },
18
+ { what: 'a JSON web token', pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
19
+ { what: 'a private key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/ },
20
+ { what: 'a password inside a connection string', pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{6,}@/ },
21
+ ];
22
+ /*
23
+ * A value that does not announce itself, read off what it was called.
24
+ *
25
+ * Needs both halves — a name that means credential AND something long enough and mixed enough to be
26
+ * one. Either alone is noise: `const key = 'id'` is a map key, and a long opaque string is as likely
27
+ * to be a hash, a path or a fixture. `process.env.X`, an import, a template hole and a reference to
28
+ * a declared row are all values rather than literals and are not matched at all.
29
+ */
30
+ const NAMED = /\b(?:secret|password|passwd|api_?key|apikey|access_?token|auth_?token|private_?key|client_?secret|credential)s?\b\s*[:=]\s*(['"`])([^'"`\n]{16,})\1/gi;
31
+ const MIXED = /^(?=.*[a-z])(?=.*[A-Z0-9])[A-Za-z0-9_+/=.-]{16,}$/;
32
+ /*
33
+ * What is deliberately NOT a finding.
34
+ *
35
+ * A secret this app declares is a NAME, and naming one is the whole remedy this law recommends — so
36
+ * `sec_stripe_key` sitting in a row, a `requires`, or a call to the vault must never be the thing
37
+ * that trips it. The same for an environment variable, which is a value read at run time rather than
38
+ * a value written down, and for the obvious placeholders people write while wiring something up.
39
+ */
40
+ const NOT = [
41
+ /^(?:sec|pub)_[a-z0-9_]+$/,
42
+ /^\$\{/,
43
+ /^(?:x{3,}|X{3,}|\.{3,}|<[^>]+>|change[-_]?me|your[-_]|example|placeholder|redacted|todo|test|dummy|sample|fake|dGhpcyBpcyBub3QgYSBzZWNyZXQ)/i,
44
+ ];
45
+ const CODE = /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|json|env|ya?ml|toml|sql|md|sh)$/;
46
+ export const slopnator = {
47
+ holds: (_node, at) => {
48
+ if (!CODE.test(at.filename))
49
+ return false;
50
+ /*
51
+ * The sealed store is ciphertext and is meant to be here.
52
+ *
53
+ * It is base64 by construction, so the entropy half below would find every value in it — and
54
+ * the fix it would recommend is the thing the file already is. A law whose refusal has no honest
55
+ * remedy is one people learn to ignore.
56
+ */
57
+ if (/(?:^|\/)\.thread\/vault\.sealed\.json$/.test(at.filename))
58
+ return false;
59
+ const text = String(at.text);
60
+ for (const one of ANNOUNCED) {
61
+ if (!one.pattern.test(text))
62
+ continue;
63
+ return (`this holds ${one.what}, written into the file. It is in every copy of this repository, in its history for ` +
64
+ 'ever, and in whatever bundle reaches it');
65
+ }
66
+ for (const found of text.matchAll(NAMED)) {
67
+ const value = found[2] ?? '';
68
+ if (NOT.some((each) => each.test(value)))
69
+ continue;
70
+ if (!MIXED.test(value))
71
+ continue;
72
+ return (`this writes a ${(found[0].split(/[:=]/)[0] ?? '').trim()} down as a literal ${String(value.length)} characters ` +
73
+ 'long. A credential in a file is in every copy of this repository and in its history for ever');
74
+ }
75
+ return false;
76
+ },
77
+ proves: {
78
+ wrong: [
79
+ "export const stripe = { apiKey: 'sk_live_51H8xQvL2eZvKYlo2C0Nx9K4mQwErTyUiOp' }\n",
80
+ "const config = { password: 'Hunter2Hunter2Hunter2' }\n",
81
+ "export const db = 'postgres://app:s3cr3tpassword@db.example.com:5432/main'\n",
82
+ ],
83
+ right: [
84
+ "export const stripe = { apiKey: 'sec_stripe_key' }\n",
85
+ "const config = { password: process.env['DB_PASSWORD'] }\n",
86
+ "export const node = nd_action(async (hands) => await hands.ht_stripe.charge({ json: { amount: 100 } }))\n",
87
+ ],
88
+ },
89
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,92 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { descend } from "../../core/descend.js";
4
+ import { findTree } from "../../core/tree.js";
5
+ const ROOT = findTree().at;
6
+ const NAMES_A_FILE = /\.[a-z]{1,5}$/;
7
+ /*
8
+ * A FILE A GENERATOR WRITES IS ABSENT UNTIL THE GENERATOR HAS RUN, AND THAT IS NOT A STALE PATH.
9
+ *
10
+ * This law and the descent disagreed about the same file, and the descent was right. A row carrying
11
+ * `by:` names a file a command writes; the descent reports it missing as a WARNING for exactly that
12
+ * reason — "a derived file is absent until it is derived". This read the same path with a bare
13
+ * `existsSync` and refused it, with advice — "fix the path, or delete the sentence naming it" —
14
+ * that would have deleted a correct declaration.
15
+ *
16
+ * Measured 2026-08-29 on a real install: `thread init`, `thread attach @thread/integrations`,
17
+ * `thread gate`, and the sweep was red on `_threads/glossary.ts` — a file `thread glossary` writes,
18
+ * on the first gate run of an app whose author had written nothing. Every other check in that gate
19
+ * answers this state by saying there is nothing to check yet.
20
+ *
21
+ * Read off the tree rather than guessed from the text. The descent already knows which rows carry a
22
+ * `by`, so the two can no longer come apart, and a path nothing derives is refused exactly as before.
23
+ */
24
+ const derived = (() => {
25
+ const found = new Set();
26
+ try {
27
+ const pass = descend();
28
+ for (const [at, held] of pass.threaded) {
29
+ if (held.some((one) => one.row.by !== undefined))
30
+ found.add(at);
31
+ }
32
+ for (const one of pass.derived.values())
33
+ if (one.at !== undefined)
34
+ found.add(one.at);
35
+ }
36
+ catch {
37
+ /* A tree that cannot be descended is reported by the descent's own step, not swallowed here. */
38
+ }
39
+ return found;
40
+ })();
41
+ const ours = () => {
42
+ try {
43
+ return new Set(readdirSync(ROOT).filter((one) => one !== 'node_modules' && statSync(resolve(ROOT, one), { throwIfNoEntry: false })?.isDirectory() === true));
44
+ }
45
+ catch {
46
+ return new Set();
47
+ }
48
+ };
49
+ export const slopnator = {
50
+ on: ['Literal'],
51
+ holds: (node) => {
52
+ const value = node.value;
53
+ if (typeof value !== 'string' || !value.includes('/'))
54
+ return false;
55
+ const tops = ours();
56
+ if (tops.size === 0 || !tops.has(value.split('/')[0] ?? ''))
57
+ return false;
58
+ const brace = value.indexOf('{');
59
+ if (brace !== -1) {
60
+ const cut = value.lastIndexOf('/', brace);
61
+ const dir = cut === -1 ? '.' : value.slice(0, cut);
62
+ if (statSync(resolve(ROOT, dir), { throwIfNoEntry: false })?.isDirectory() === true)
63
+ return false;
64
+ return `\`${value}\` writes into \`${dir}\`, which is not a directory on disk`;
65
+ }
66
+ if (!NAMES_A_FILE.test(value))
67
+ return false;
68
+ if (existsSync(resolve(ROOT, value)))
69
+ return false;
70
+ if (derived.has(value))
71
+ return false;
72
+ return `\`${value}\` is not on disk`;
73
+ },
74
+ proves: {
75
+ wrong: [
76
+ "const at = 'threads/nope.ts'",
77
+ "const at = 'scripts/gone.ts'",
78
+ "const at = 'docs/nowhere/{page}.md'",
79
+ "const at = 'migrations/gone.sql'",
80
+ "const at = 'plans/gone.md'",
81
+ ],
82
+ right: [
83
+ "const at = 'packages/core/integrity/src/constraint.ts'",
84
+ "const at = 'packages/core/integrity/src/lint.ts'",
85
+ "const at = 'docs/reference/{page}.md'",
86
+ "const at = 'threads/'",
87
+ "const said = 'a sentence about packages/core/integrity/src/constraint.ts and nothing else'",
88
+ "const at = 'app/layout.tsx'",
89
+ "const at = 'node_modules/thread/package.json'",
90
+ ],
91
+ },
92
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;