@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,30 @@
1
+ import { here, index, meaningsIn, parses, unreadable } from "./sl_unthreaded.js";
2
+ export const slopnator = {
3
+ holds: (_node, at) => {
4
+ const path = here(at.filename);
5
+ if (unreadable(path, at.text) !== undefined)
6
+ return false;
7
+ const mine = parses(path) ? meaningsIn(at.text) : [];
8
+ if (mine.length === 0)
9
+ return false;
10
+ const meanings = index().meanings;
11
+ for (const one of mine) {
12
+ const held = meanings.get(one.id);
13
+ if (held === undefined || held.size < 2)
14
+ continue;
15
+ return `\`${one.id}\` is coined with ${String(held.size)} different \`describes\`, and the glossary holds one meaning per word`;
16
+ }
17
+ return false;
18
+ },
19
+ proves: {
20
+ wrong: [
21
+ "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'A different thing entirely.',\n },\n ],\n}",
22
+ "const M: Threaded = {\n declares: [\n {\n id: 'two',\n describes: 'The stated meaning.',\n },\n {\n id: 'two',\n },\n ],\n}",
23
+ ],
24
+ right: [
25
+ "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'The first thing.',\n },\n ],\n}",
26
+ "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'two',\n describes: 'A different thing entirely.',\n },\n ],\n}",
27
+ 'const M = { declares: [] }',
28
+ ],
29
+ },
30
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,34 @@
1
+ import { atsIn, here, index, parses, unreadable } from "./sl_unthreaded.js";
2
+ const ONLY_DECLARES = new Set(['declares']);
3
+ const declaredIn = (text) => atsIn(text, ONLY_DECLARES);
4
+ export const slopnator = {
5
+ holds: (_node, at) => {
6
+ const path = here(at.filename);
7
+ if (unreadable(path, at.text) !== undefined)
8
+ return false;
9
+ const mine = parses(path) ? declaredIn(at.text) : [];
10
+ if (mine.length === 0)
11
+ return false;
12
+ const threaded = index().declared;
13
+ for (const claimed of mine) {
14
+ const times = threaded.get(claimed) ?? 0;
15
+ if (times > 1)
16
+ return `${claimed} is declared ${String(times)} times, and \`declares\` is exactly one parent ever`;
17
+ }
18
+ return false;
19
+ },
20
+ proves: {
21
+ wrong: [
22
+ "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}",
23
+ "const M: Threaded = {\n declares: [\n {\n at: 'threads/two.ts',\n },\n {\n at: 'threads/three.ts',\n },\n {\n at: 'threads/two.ts',\n },\n ],\n}",
24
+ "const ROW = {\n at: 'threads/one.ts',\n}\nconst M: Threaded = {\n declares: [ROW, ROW],\n}",
25
+ ],
26
+ right: [
27
+ "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/two.ts',\n },\n ],\n}",
28
+ "const M: Threaded = {\n derives: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}",
29
+ 'const M = { declares: [] }',
30
+ "const ROW = {\n at: 'threads/one.ts',\n}\nconst M: Threaded = {\n declares: [ROW],\n}",
31
+ "const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n declares: [\n {\n at: 'threads/two.ts',\n },\n ],\n },\n ],\n}",
32
+ ],
33
+ },
34
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,78 @@
1
+ import { descend, under } from "../../core/descend.js";
2
+ import { here } from "./sl_unthreaded.js";
3
+ const CROSSINGS = [
4
+ { what: 'the network', finds: /(?<![.\w])fetch\s*\(/ },
5
+ { what: 'the network', finds: /(?<![.\w])XMLHttpRequest\b/ },
6
+ { what: 'the clock', finds: /\bDate\s*\.\s*now\s*\(/ },
7
+ { what: 'the clock', finds: /\bperformance\s*\.\s*now\s*\(/ },
8
+ { what: 'the clock', finds: /\bsetInterval\s*\(/ },
9
+ { what: 'the clock', finds: /\bsetTimeout\s*\(/ },
10
+ { what: 'the clock', finds: /\brequestAnimationFrame\s*\(/ },
11
+ { what: 'randomness', finds: /\bMath\s*\.\s*random\s*\(/ },
12
+ { what: 'randomness', finds: /\bcrypto\s*\.\s*randomUUID\s*\(/ },
13
+ { what: 'the queue', finds: /\bqueue\s*\.\s*enqueue\s*\(/ },
14
+ { what: 'a seam', finds: /\b(?:files|llm|http)\s*\.\s*(?:call|run|complete|put|signedUrl|remove|get)\s*\(/ },
15
+ ];
16
+ const OPERATIONS_ID = 'operations';
17
+ const NODES_ID = 'nodes';
18
+ let bodies;
19
+ /*
20
+ * The files this law is about, read off the tree rather than off a path.
21
+ *
22
+ * A crossing belongs in a declared node and nowhere else, so what the law has to know is which
23
+ * files ARE declared nodes — every row in an operation's `seeds` that requires a node type. It
24
+ * cannot be a path rule: an operation seeds its node bodies and its input file into the same
25
+ * directory, so excepting the directory would except the input file too and leave a hole exactly
26
+ * one file wide per operation. Measured 2026-08-27: with the directory excepted instead, this law
27
+ * fired on `operations/wf_asks/asks_it.ts` — a node body calling the model its operation had
28
+ * declared, which is the one place the law's own `instead` sends you.
29
+ */
30
+ function declared() {
31
+ if (bodies !== undefined)
32
+ return bodies;
33
+ const pass = descend();
34
+ const found = new Set();
35
+ for (const [at, threadings] of pass.threaded) {
36
+ for (const one of threadings) {
37
+ if (one.list !== 'seeds')
38
+ continue;
39
+ if (!under(pass, one.by, OPERATIONS_ID) || under(pass, one.by, NODES_ID))
40
+ continue;
41
+ if ((one.row.requires ?? []).length === 0)
42
+ continue;
43
+ found.add(at);
44
+ }
45
+ }
46
+ bodies = found;
47
+ return found;
48
+ }
49
+ export const slopnator = {
50
+ holds: (_node, at) => {
51
+ if (declared().has(here(at.filename)))
52
+ return false;
53
+ for (const crossing of CROSSINGS) {
54
+ if (!crossing.finds.test(at.text))
55
+ continue;
56
+ return (`${crossing.what} reached outside a declared node — nothing recorded this crossing, so it ` +
57
+ 'cannot be replayed, retried, priced or unwound, and the page renders exactly the same either way');
58
+ }
59
+ return false;
60
+ },
61
+ proves: {
62
+ wrong: [
63
+ "const res = await fetch('https://api.stripe.com/v1/charges')",
64
+ 'const at = Date.now()',
65
+ 'const id = crypto.randomUUID()',
66
+ "await queue.enqueue('job', payload)",
67
+ "await files.put('receipt.pdf', bytes)",
68
+ 'const t = setInterval(() => refetch(), 5000)',
69
+ 'const t = setTimeout(function again() { refetch(); setTimeout(again, 5000) }, 5000)',
70
+ 'window.requestAnimationFrame(step)',
71
+ ],
72
+ right: [
73
+ "const node = { id: 'st_charge', kind: 'action', action: 'payment.charge', declares: { seams: ['http'] } }",
74
+ 'const rows = useRead(recentOrders)',
75
+ "const { data } = useRead(recentOrders, { refresh: 'every-5s' })",
76
+ ],
77
+ },
78
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,56 @@
1
+ const HREF = 'href';
2
+ const GLOBALS = new Set(['window', 'globalThis', 'self']);
3
+ const HISTORIES = new Set(['router', 'navigation', 'history']);
4
+ const MOVES = new Set(['push', 'replace', 'prefetch']);
5
+ const named = (held) => {
6
+ if (held?.type === 'Identifier' || held?.type === 'JSXIdentifier')
7
+ return typeof held.name === 'string' ? held.name : undefined;
8
+ if (held?.type === 'Literal' && typeof held.value === 'string')
9
+ return held.value;
10
+ return undefined;
11
+ };
12
+ const spoken = (held) => {
13
+ if (held.type === 'JSXAttribute')
14
+ return named(held.name);
15
+ if (held.type === 'Property')
16
+ return held.computed === true ? undefined : named(held.key);
17
+ return undefined;
18
+ };
19
+ function moving(held) {
20
+ if (held.type !== 'MemberExpression' || held.computed === true)
21
+ return undefined;
22
+ const on = named(held.object);
23
+ const reached = named(held.property);
24
+ if (on === undefined || reached === undefined)
25
+ return undefined;
26
+ if (GLOBALS.has(on) && reached === 'location')
27
+ return `${on}.location`;
28
+ if (HISTORIES.has(on) && MOVES.has(reached))
29
+ return `${on}.${reached}()`;
30
+ return undefined;
31
+ }
32
+ export const slopnator = {
33
+ on: ['JSXAttribute', 'Property', 'MemberExpression'],
34
+ holds: (node) => {
35
+ const held = node;
36
+ const written = spoken(held) === HREF ? '`href`' : moving(held);
37
+ if (written === undefined)
38
+ return false;
39
+ return (`${written} moves somebody without naming a screen, so the map holds no link for it — ` +
40
+ 'the destination reads as unreachable to the orphan pass, the app reads as having no path to it, and it navigates correctly');
41
+ },
42
+ proves: {
43
+ wrong: [
44
+ "const link = { href: '/orders/42' }",
45
+ "window.location = '/orders/42'",
46
+ "router.push('/orders/42')",
47
+ 'history.replace(next)',
48
+ 'navigation.prefetch(next)',
49
+ ],
50
+ right: [
51
+ '<Link.label navigateTo="Home" labelKey="home.title" />',
52
+ "const to = { navigateTo: 'Order', labelKey: 'order.title' }",
53
+ 'const rows = orders.push(one)',
54
+ ],
55
+ },
56
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,34 @@
1
+ const HOST = /^[a-z]/;
2
+ function tagOf(node) {
3
+ const name = node?.name;
4
+ if (name?.type === 'JSXNamespacedName') {
5
+ const space = typeof name.namespace?.name === 'string' ? name.namespace.name : '';
6
+ const local = typeof name.name?.name === 'string' ? String(name.name.name) : '';
7
+ return `${space}:${local}`;
8
+ }
9
+ if (name?.type !== 'JSXIdentifier')
10
+ return undefined;
11
+ return typeof name.name === 'string' ? name.name : undefined;
12
+ }
13
+ export const slopnator = {
14
+ on: ['JSXOpeningElement'],
15
+ holds: (node) => {
16
+ const tag = tagOf(node);
17
+ if (tag === undefined || !HOST.test(tag))
18
+ return false;
19
+ return (`<${tag}> is a host element, so it wears no declared style, sits in no catalogue, and the style guide, ` +
20
+ 'a rebrand and every overlay read past it — it renders and nothing downstream knows it is there');
21
+ },
22
+ proves: {
23
+ wrong: [
24
+ '<div className="row" />',
25
+ '<button onClick={save}>save</button>',
26
+ '<svg:path d="M0 0" />',
27
+ ],
28
+ right: [
29
+ '<Box.card />',
30
+ '<Column>{rows}</Column>',
31
+ '<Button.buttonPrimary label="save" />',
32
+ ],
33
+ },
34
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,59 @@
1
+ import { dirname, join, normalize } from 'node:path';
2
+ import { here, index, parses, unreadable } from "./sl_unthreaded.js";
3
+ /*
4
+ * Every string this file writes, whatever it writes it for.
5
+ *
6
+ * The first reading matched `node: '<name>'`, which was the shape when an operation named its node
7
+ * in a string beside a step of its own; a node builds its own span now, so that matched nothing on
8
+ * any real file. The second matched `import … from '<path>'`, which was the one route anybody had
9
+ * thought of — and a dynamic `import()`, a `require()` and an `export … from` each walked straight
10
+ * through it, measured on a real tree. Enumerating the ways in is how a refusal ends up as a list of
11
+ * whatever somebody happened to try. What matters is the path, not the syntax carrying it.
12
+ */
13
+ const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g;
14
+ const RELATIVE = /^\.\.?\//;
15
+ const UNDER = 'nodes/';
16
+ const slash = (one) => one.split('\\').join('/');
17
+ function nodeAt(said, from) {
18
+ if (said === '')
19
+ return undefined;
20
+ const path = slash(normalize(RELATIVE.test(said) ? join(from, said) : said));
21
+ return path.startsWith(UNDER) ? path : undefined;
22
+ }
23
+ export const slopnator = {
24
+ holds: (_node, at) => {
25
+ const path = here(at.filename);
26
+ if (unreadable(path, at.text) !== undefined)
27
+ return false;
28
+ if (!parses(path))
29
+ return false;
30
+ const declared = index().reached;
31
+ const from = dirname(path);
32
+ for (const found of String(at.text).matchAll(SAID)) {
33
+ const named = nodeAt(found[1] ?? found[2] ?? found[3] ?? '', from);
34
+ if (named === undefined || declared.has(named))
35
+ continue;
36
+ return (`this reaches \`${named}\`, and the descent from the tree root never arrives there — so one of ` +
37
+ 'its steps is a node nobody declared. An operation that takes a step nothing agreed to is doing ' +
38
+ 'something this app never said it could do, and the flow reads as agreed the whole way down');
39
+ }
40
+ return false;
41
+ },
42
+ proves: {
43
+ wrong: [
44
+ "import { node as ghost } from '../nodes/nd_ghost.ts'",
45
+ "const held = await import('../nodes/nd_ghost.ts')",
46
+ "const held = require('../nodes/nd_ghost.ts')",
47
+ "export { node as ghost } from '../nodes/nd_ghost.ts'",
48
+ "const AT = '../nodes/nd_ghost.ts'",
49
+ "import { node as invented } from 'nodes/nd_nowhere.ts'",
50
+ ],
51
+ right: [
52
+ "import { node as read } from '../nodes/nd_read.ts'",
53
+ "import { node as action } from '../nodes/nd_action.ts'\nimport { node as read } from '../nodes/nd_read.ts'",
54
+ "import { z } from 'zod'",
55
+ "import type { Operation } from '../../operations/operations.ts'",
56
+ "import type { Held } from '../../operations/nodes.ts'",
57
+ ],
58
+ },
59
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,120 @@
1
+ import { descend, read, reached } from "../../core/descend.js";
2
+ import { rowsIn } from "./sl_unthreaded.js";
3
+ const UNDER = 'operations/';
4
+ const DISPATCH = 'dispatch';
5
+ function declared() {
6
+ const found = new Set();
7
+ descend();
8
+ for (const at of reached()) {
9
+ const text = read(at);
10
+ if (text === undefined)
11
+ continue;
12
+ for (const one of rowsIn(text)) {
13
+ const where = one.at?.split('\\').join('/');
14
+ if (where === undefined || !where.includes(UNDER))
15
+ continue;
16
+ if (one.id !== undefined)
17
+ found.add(one.id);
18
+ const named = where.split('/').at(-1)?.replace(/\.[^.]+$/, '');
19
+ if (named !== undefined)
20
+ found.add(named);
21
+ }
22
+ }
23
+ return found;
24
+ }
25
+ export const slopnator = {
26
+ on: ['CallExpression'],
27
+ holds: (node) => {
28
+ const call = node;
29
+ const callee = call?.callee;
30
+ const named = callee?.type === 'Identifier'
31
+ ? callee.name
32
+ : callee?.property?.type === 'Identifier'
33
+ ? callee.property.name
34
+ : undefined;
35
+ if (named !== DISPATCH)
36
+ return false;
37
+ const first = call?.arguments?.[0];
38
+ if (first === undefined) {
39
+ return ('dispatch is called with nothing to dispatch, so no operation is named and none can be found — the name ' +
40
+ 'is the whole of what makes an operation reachable');
41
+ }
42
+ /*
43
+ * THE OPERATION ITSELF IS A BETTER ANSWER THAN ITS NAME, AND THIS USED TO REFUSE IT.
44
+ *
45
+ * This asked for a string literal and refused everything else, which was right when the only
46
+ * caller was a UI seam handed a bare name. It has been wrong since dispatch stopped taking one:
47
+ * the note above says so in as many words — "`dispatch` in packages/core/operations/src/dispatch.ts
48
+ * takes an Operation object rather than a name". What ships now imports the compiled operation
49
+ * and hands over the binding, and a binding cannot be assembled at run time: if it named nothing,
50
+ * the file would not compile.
51
+ *
52
+ * Measured 2026-08-31 on a registry install: an operations-only app, everything made by the CLI,
53
+ * failed its own sweep on `operations/_client.ts` — a file its own generator had just written,
54
+ * naming an operation the same tree declares. The gate refuses the build now, so a law that
55
+ * refuses a generated file is an app that cannot ship.
56
+ *
57
+ * Narrow on purpose. An identifier is accepted only when it IS one of the declared operations,
58
+ * which is the shape the generators emit — `import { operation as wf_charge }` and then
59
+ * `dispatch(wf_charge, …)`. A variable holding a computed name is still refused, because it does
60
+ * not match a declaration, and that is the hole this law was written for.
61
+ */
62
+ const all = declared();
63
+ if (first.type === 'Identifier' && typeof first.name === 'string') {
64
+ if (all.has(first.name))
65
+ return false;
66
+ return (`dispatch is handed \`${first.name}\`, and nothing declares an operation by that name. A binding is only ` +
67
+ 'checkable when it is the operation — import the compiled operation and hand that over, so what runs and ' +
68
+ 'what was declared are the same thing rather than two things that agree today');
69
+ }
70
+ if (first.type !== 'Literal' || typeof first.value !== 'string') {
71
+ return ('dispatch is handed a name this file does not write down, so nothing can hold it against what is declared. ' +
72
+ 'A name assembled at runtime reads from every check exactly like a name that was checked and found — the ' +
73
+ 'operation is reachable and no declaration was ever consulted');
74
+ }
75
+ if (all.has(first.value))
76
+ return false;
77
+ return (`dispatch names \`${String(first.value)}\` and nothing declares an operation by that name, so this reaches for ` +
78
+ 'something that does not exist and fails where a person is waiting rather than where it was written. ' +
79
+ (all.size === 0
80
+ ? 'Nothing declares an operation at all, so no name would have been accepted — the declaration is what has to change'
81
+ : `Declared: ${[...all].sort().join(', ')}`));
82
+ },
83
+ proves: {
84
+ wrong: [
85
+ {
86
+ files: [
87
+ {
88
+ at: 'threads/_probe_operations.ts',
89
+ source: "export const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
90
+ },
91
+ { at: 'app/_probe_unknown.tsx', source: "export const HELD = dispatch('wf_not_declared')\n" },
92
+ ],
93
+ at: 'app/_probe_unknown.tsx',
94
+ },
95
+ {
96
+ files: [
97
+ {
98
+ at: 'threads/_probe_operations.ts',
99
+ source: "export const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
100
+ },
101
+ { at: 'app/_probe_built.tsx', source: "export const HELD = dispatch(`wf_${name}`)\n" },
102
+ ],
103
+ at: 'app/_probe_built.tsx',
104
+ },
105
+ ],
106
+ right: [
107
+ {
108
+ files: [
109
+ {
110
+ at: 'threads/_probe_operations.ts',
111
+ source: "export const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_send_invoice',\n at: 'operations/wf_send_invoice.ts',\n },\n ],\n}\n",
112
+ },
113
+ { at: 'app/_probe_known.tsx', source: "export const HELD = dispatch('wf_send_invoice')\n" },
114
+ ],
115
+ at: 'app/_probe_known.tsx',
116
+ },
117
+ "export const HELD = translate('order.place')",
118
+ ],
119
+ },
120
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,117 @@
1
+ import { descend, read, reached } from "../../core/descend.js";
2
+ import { rowsIn } from "./sl_unthreaded.js";
3
+ const ELEMENT = 'element';
4
+ const VARIANT = 'variant';
5
+ function nameOf(id) {
6
+ return id
7
+ .split('-')
8
+ .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
9
+ .join('');
10
+ }
11
+ function idOf(name) {
12
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
13
+ }
14
+ function memberOf(id) {
15
+ const [first = '', ...rest] = id.split('-');
16
+ return first + rest.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join('');
17
+ }
18
+ function declared() {
19
+ const elements = new Set();
20
+ const variants = new Map();
21
+ descend();
22
+ for (const at of reached()) {
23
+ const text = read(at);
24
+ if (text === undefined)
25
+ continue;
26
+ let waiting = [];
27
+ for (const one of rowsIn(text)) {
28
+ if (one.id === undefined)
29
+ continue;
30
+ if (one.list === 'declares') {
31
+ waiting.push(one.wears === undefined ? { id: one.id } : { id: one.id, wears: one.wears });
32
+ continue;
33
+ }
34
+ if (one.list !== undefined)
35
+ continue;
36
+ if (one.id === ELEMENT)
37
+ for (const row of waiting)
38
+ elements.add(nameOf(row.id));
39
+ if (one.id === VARIANT)
40
+ for (const row of waiting)
41
+ variants.set(row.id, row.wears ?? []);
42
+ waiting = [];
43
+ }
44
+ }
45
+ return { elements, variants };
46
+ }
47
+ export const slopnator = {
48
+ on: ['JSXOpeningElement'],
49
+ holds: (node) => {
50
+ const name = node?.name;
51
+ if (name?.type !== 'JSXMemberExpression')
52
+ return false;
53
+ const element = name.object?.type === 'JSXIdentifier' ? name.object.name : undefined;
54
+ const member = name.property?.type === 'JSXIdentifier' ? name.property.name : undefined;
55
+ if (element === undefined || member === undefined)
56
+ return false;
57
+ const { elements, variants } = declared();
58
+ if (!elements.has(element))
59
+ return false;
60
+ const id = idOf(member);
61
+ const wears = variants.get(id);
62
+ const worn = [...variants]
63
+ .filter(([, all]) => all.includes(element))
64
+ .map(([one]) => `${element}.${memberOf(one)}`)
65
+ .sort();
66
+ const offered = worn.length === 0
67
+ ? `nothing declares itself worn by \`${element}\`, so no member of it would have been accepted — a variant row has to name \`${element}\` first`
68
+ : `\`${element}\` wears ${worn.join(', ')}`;
69
+ if (wears === undefined) {
70
+ return (`\`${element}.${member}\` names the variant \`${id}\` and nothing declares it — so it renders carrying a class ` +
71
+ 'the stylesheet never writes, which reads on screen as an element that lost its styling and is reported ' +
72
+ `nowhere. ${offered}`);
73
+ }
74
+ if (!wears.includes(element)) {
75
+ return (`\`${element}.${member}\` pairs \`${element}\` with the variant \`${id}\`, and that variant's row does not name ` +
76
+ `\`${element}\` among the elements that wear it — so the pairing is nobody's declaration. ${offered}`);
77
+ }
78
+ return false;
79
+ },
80
+ proves: {
81
+ wrong: [
82
+ {
83
+ files: [
84
+ {
85
+ at: 'threads/_probe_worn.ts',
86
+ source: "export const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n",
87
+ },
88
+ { at: 'app/_probe_undeclared.tsx', source: 'export const HELD = <Button.primary />\n' },
89
+ ],
90
+ at: 'app/_probe_undeclared.tsx',
91
+ },
92
+ {
93
+ files: [
94
+ {
95
+ at: 'threads/_probe_worn.ts',
96
+ source: "export const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n {\n id: 'badge',\n at: 'app/_probe_badge.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'chip',\n at: 'app/_probe_chip.ts',\n wears: ['Badge'],\n },\n ],\n}\n",
97
+ },
98
+ { at: 'app/_probe_mispaired.tsx', source: 'export const HELD = <Button.chip />\n' },
99
+ ],
100
+ at: 'app/_probe_mispaired.tsx',
101
+ },
102
+ ],
103
+ right: [
104
+ {
105
+ files: [
106
+ {
107
+ at: 'threads/_probe_worn.ts',
108
+ source: "export const ELEMENTS: Threaded = {\n id: 'element',\n declares: [\n {\n id: 'button',\n at: 'app/_probe_button.tsx',\n },\n ],\n}\n\nexport const VARIANTS: Threaded = {\n id: 'variant',\n declares: [\n {\n id: 'button-primary',\n at: 'app/_probe_variant.ts',\n wears: ['Button'],\n },\n ],\n}\n",
109
+ },
110
+ { at: 'app/_probe_declared.tsx', source: 'export const HELD = <Button.buttonPrimary />\n' },
111
+ ],
112
+ at: 'app/_probe_declared.tsx',
113
+ },
114
+ '<Unknown.anything />',
115
+ ],
116
+ },
117
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;
@@ -0,0 +1,54 @@
1
+ const COMMON = new Set([
2
+ 'a', 'an', 'the', 'of', 'for', 'to', 'and', 'or', 'is', 'it', 'its', 'this', 'that',
3
+ 'in', 'on', 'at', 'as', 'by', 'with', 'be', 'are', 'was', 'one', 'every', 'each',
4
+ ]);
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
+ const said = (property) => {
16
+ const held = property?.value;
17
+ return held?.type === 'Literal' && typeof held.value === 'string' ? held.value : undefined;
18
+ };
19
+ const words = (text) => text.toLowerCase().split(/[^a-z]+/).filter((one) => one !== '' && !COMMON.has(one));
20
+ export const slopnator = {
21
+ on: ['Property'],
22
+ holds: (node, at) => {
23
+ const property = node;
24
+ const key = keyOf(property);
25
+ if (key !== 'describes' && key !== 'means')
26
+ return false;
27
+ const sentence = said(property);
28
+ if (sentence === undefined)
29
+ return false;
30
+ if (sentence.trim() === '')
31
+ return `\`${key}\` is empty, so the word arrived and the meaning never did`;
32
+ const owner = at.ancestors().at(-1);
33
+ const id = said((owner?.properties ?? []).find((one) => keyOf(one) === 'id' || keyOf(one) === 'name'));
34
+ if (id === undefined)
35
+ return false;
36
+ const own = new Set(words(id));
37
+ if (words(sentence).some((word) => !own.has(word)))
38
+ return false;
39
+ return `\`${key}\` says "${sentence}", which adds no word its id \`${id}\` does not already carry`;
40
+ },
41
+ proves: {
42
+ wrong: [
43
+ "const M = { id: 'law', describes: 'The law.' }",
44
+ "const M = { id: 'law', describes: '' }",
45
+ "const M = { id: 'term', means: '' }",
46
+ "const M = { id: 'thread-check', describes: 'The thread check.' }",
47
+ ],
48
+ right: [
49
+ "const M = { id: 'law', describes: 'A rule that must be guaranteed to be enforced.' }",
50
+ "const M = { id: 'term', means: 'A word this repository coins, and what somebody reading it should understand.' }",
51
+ "const M = { id: 'law', at: 'packages/core/integrity/src/constraint.ts' }",
52
+ ],
53
+ },
54
+ };
@@ -0,0 +1,2 @@
1
+ import type { Slopnator } from '../slopnator.ts';
2
+ export declare const slopnator: Slopnator;