@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,145 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { argv, stderr, stdout } from 'node:process';
4
+ import { exit } from "../../core/invoked.js";
5
+ import { ROOT, descend } from "../../core/descend.js";
6
+ import { forbids, runChecker } from "../../core/finding.js";
7
+ const AT = 'docs.md';
8
+ const CHECKING = argv.includes('--check');
9
+ const SHOWING = argv.includes('--show');
10
+ const TRIALS_AT = 'operations/_trials.json';
11
+ function refuse(why) {
12
+ stderr.write(`docs: ${why}\n`);
13
+ exit(1);
14
+ }
15
+ const text = (value) => (typeof value === 'string' && value !== '' ? value : undefined);
16
+ const said = (one) => text(one.describes) ?? '_no `describes` — this row says nothing about itself._';
17
+ const pass = await descend();
18
+ const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0];
19
+ if (stopped !== undefined) {
20
+ refuse('the descent is broken, so this would state a tree that is only partly known and read afterwards as the whole of it — ' +
21
+ `${stopped.detail} (${stopped.file ?? ''})`);
22
+ }
23
+ const PATH = resolve(ROOT, AT);
24
+ const proven = () => {
25
+ const found = new Map();
26
+ const at = resolve(ROOT, TRIALS_AT);
27
+ if (!existsSync(at))
28
+ return found;
29
+ try {
30
+ const held = JSON.parse(readFileSync(at, 'utf8'));
31
+ for (const one of held) {
32
+ const which = text(one.operation);
33
+ if (which === undefined)
34
+ continue;
35
+ found.set(which, (found.get(which) ?? 0) + 1);
36
+ }
37
+ }
38
+ catch {
39
+ return found;
40
+ }
41
+ return found;
42
+ };
43
+ function grouped(held) {
44
+ const found = new Map();
45
+ for (const threadings of held.threaded.values()) {
46
+ for (const one of threadings) {
47
+ const kept = found.get(one.by) ?? [];
48
+ kept.push({ list: one.list, row: one.row });
49
+ found.set(one.by, kept);
50
+ }
51
+ }
52
+ return found;
53
+ }
54
+ const under = grouped(pass);
55
+ const trials = proven();
56
+ const named = (one) => text(one.id) ?? text(one.at) ?? '(unnamed)';
57
+ const line = (one) => {
58
+ const row = one.row;
59
+ const where = text(row.at);
60
+ const by = text(row.by);
61
+ const runs = text(row.runs);
62
+ const ran = trials.get(named(row));
63
+ return [
64
+ `- **${named(row)}**${where === undefined ? '' : ` — \`${where}\``}`,
65
+ ` ${said(row)}`,
66
+ ...(by === undefined ? [] : [` Written by \`${by}\`${runs === undefined ? '' : `, checked by \`${runs}\``}.`]),
67
+ ...((row.requires ?? []).length === 0 ? [] : [` Requires ${(row.requires ?? []).map((each) => `\`${each}\``).join(', ')}.`]),
68
+ ...((row.takes ?? []).length === 0 ? [] : [` Takes ${(row.takes ?? []).map((each) => `\`${each}\``).join(', ')}.`]),
69
+ ...(ran === undefined ? [] : [` Proved by ${String(ran)} recorded run(s).`]),
70
+ ].join('\n');
71
+ };
72
+ const words = [...pass.coined].sort(([one], [two]) => (one < two ? -1 : 1));
73
+ const commands = [...pass.derived.values(), ...[...under.values()].flat().map((one) => one.row)]
74
+ .filter((one) => text(one.by)?.startsWith('thread ') === true)
75
+ .reduce((found, one) => found.set(text(one.by), one), new Map());
76
+ const sections = [...under.keys()].sort();
77
+ const page = [
78
+ '<!-- GENERATED by `thread docs` from what this app declares — do not edit by hand.',
79
+ ' Every sentence here is the `describes` on a row. To change one, change the row. -->',
80
+ '',
81
+ '# What this app declares',
82
+ '',
83
+ `${String(pass.declarations)} declaration(s) over ${String(pass.rows)} row(s), read from the tree by the same descent`,
84
+ 'every generator here reads. This page is rewritten whenever a package is attached and whenever a',
85
+ 'command writes a row, so a section that is out of date is a bug rather than a chore.',
86
+ '',
87
+ '## Commands',
88
+ '',
89
+ ...(commands.size === 0
90
+ ? ['_nothing here declares a command yet._']
91
+ : [...commands.entries()]
92
+ .sort(([one], [two]) => (one < two ? -1 : 1))
93
+ .map(([word, one]) => `- \`${word}\` — ${said(one)}`)),
94
+ '',
95
+ ...sections.flatMap((node) => {
96
+ const own = pass.stated.get(node) ?? pass.derived.get(node);
97
+ const kept = under.get(node) ?? [];
98
+ const declares = kept.filter((one) => one.list === 'declares');
99
+ const derives = kept.filter((one) => one.list === 'derives');
100
+ const seeds = kept.filter((one) => one.list === 'seeds');
101
+ return [
102
+ `## ${node}`,
103
+ '',
104
+ ...(own === undefined ? [] : [said(own), '']),
105
+ ...(declares.length === 0 ? [] : ['### What it declares', '', ...declares.map(line), '']),
106
+ ...(derives.length === 0 ? [] : ['### What it derives', '', ...derives.map(line), '']),
107
+ ...(seeds.length === 0 ? [] : ['### What it seeds', '', ...seeds.map(line), '']),
108
+ ];
109
+ }),
110
+ '## Every word this app coins',
111
+ '',
112
+ ...(words.length === 0
113
+ ? ['_nothing here coins a word yet._']
114
+ : words.map(([word, held]) => `- **${word}** — ${held.said[0] ?? '_no meaning stated_'}`)),
115
+ '',
116
+ ].join('\n');
117
+ if (SHOWING) {
118
+ stdout.write(page);
119
+ exit(0);
120
+ }
121
+ if (CHECKING) {
122
+ const findings = [];
123
+ const held = existsSync(PATH) ? readFileSync(PATH, 'utf8') : undefined;
124
+ if (held === undefined) {
125
+ findings.push({
126
+ detail: `${AT} is not there, and this app declares it`,
127
+ instead: `Run \`thread docs\`. Nothing writes this page by hand — it is what the tree says, read back.`,
128
+ file: AT,
129
+ severity: 'forbidden',
130
+ });
131
+ }
132
+ else if (held !== page) {
133
+ findings.push({
134
+ detail: `${AT} states something other than what this tree declares`,
135
+ instead: `Run \`thread docs\`. A sentence here is the \`describes\` on a row — to change one, change the row.`,
136
+ file: AT,
137
+ severity: 'forbidden',
138
+ });
139
+ }
140
+ runChecker('docs', findings, `${AT} states every row this app declares, ${String(pass.rows)} of them`);
141
+ exit(findings.length === 0 ? 0 : 1);
142
+ }
143
+ mkdirSync(dirname(PATH), { recursive: true });
144
+ writeFileSync(PATH, page, 'utf8');
145
+ stdout.write(`docs: wrote ${AT} — ${String(sections.length)} node(s), ${String(commands.size)} command(s), ${String(words.length)} word(s).\n`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, relative, resolve } from 'node:path';
3
+ import { argv, stderr, stdout } from 'node:process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { exit } from "../../core/invoked.js";
6
+ import { ROOT, descend } from "../../core/descend.js";
7
+ import { forbids, runChecker } from "../../core/finding.js";
8
+ function refuse(why) {
9
+ stderr.write(`glossary: ${why}\n`);
10
+ exit(1);
11
+ }
12
+ function text(value) {
13
+ return typeof value === 'string' && value !== '' ? value : undefined;
14
+ }
15
+ function quote(value) {
16
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n')}'`;
17
+ }
18
+ const pass = await descend();
19
+ const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0];
20
+ if (stopped !== undefined) {
21
+ refuse(`the descent is broken, so the harvest is short and writing it would delete words that exist — ${stopped.detail} (${stopped.file ?? ''})`);
22
+ }
23
+ const SELF = pass.derived.get('glossary');
24
+ if (SELF === undefined) {
25
+ refuse('nothing in the tree derives `glossary`, so this script has no declaration saying what it writes or where');
26
+ }
27
+ const AT = text(SELF.at) ?? refuse('the `glossary` row names no `at`, so where the glossary lands is unstated');
28
+ const PATH = resolve(ROOT, AT);
29
+ const toward = relative(dirname(PATH), fileURLToPath(new URL('../glossary.ts', import.meta.url))).split('\\').join('/');
30
+ const shape = toward.startsWith('.') ? toward : `./${toward}`;
31
+ const words = [...pass.coined];
32
+ function row([word, held]) {
33
+ const from = held.from.map(quote).join(', ');
34
+ return ` { id: ${quote(word)}, under: ${quote(held.under)}, from: [${from}], means: ${quote(held.said[0] ?? '')} },`;
35
+ }
36
+ const content = `import type { Glossary } from '${shape}'
37
+
38
+ export const GLOSSARY: Glossary[] = [
39
+ ${words.map(row).join('\n')}
40
+ ]
41
+ `;
42
+ if (argv.includes('--check')) {
43
+ const findings = [];
44
+ const current = existsSync(PATH) ? readFileSync(PATH, 'utf8') : '';
45
+ if (current !== content) {
46
+ findings.push({
47
+ file: AT,
48
+ detail: 'is not what the slopnator from the root derives',
49
+ instead: `run \`${text(SELF.by) ?? 'thread glossary'}\` and commit what it writes. Never edit this file — a meaning lives on the row that coins the word, and an edit here is a change to that row somebody skipped`,
50
+ });
51
+ }
52
+ runChecker('glossary', findings, `${String(words.length)} word(s) coined over ${String(pass.declarations)} declaration(s)`);
53
+ exit(findings.length === 0 ? 0 : 1);
54
+ }
55
+ writeFileSync(PATH, content);
56
+ stdout.write(`glossary: wrote ${AT} — ${String(words.length)} word(s).\n`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,222 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { argv, cwd, env, execPath, stdout } from 'node:process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { SKILLS_AT, declared, nouns, skillOf } from "../../core/asking.js";
7
+ import { generators } from "../../core/chain.js";
8
+ import { TREE } from "../../core/descend.js";
9
+ import { LOADER, exit, invoked } from "../../core/invoked.js";
10
+ import { ORGANISATION, named } from "../../core/scope.js";
11
+ import { findTree } from "../../core/tree.js";
12
+ const WHERE = SKILLS_AT;
13
+ const RECEIPT_LINE = /^[a-z]+: receipt .*$/gm;
14
+ const NONCE = /--receipt [0-9a-f]{6,}/g;
15
+ const CRLF = /\r\n/g;
16
+ const META = fileURLToPath(new URL('../../core/generators/meta.ts', import.meta.url));
17
+ const fileFor = (noun) => skillOf(noun);
18
+ async function offering(tree) {
19
+ const answers = relative(tree.at, META).split('\\').join('/');
20
+ const found = new Map(nouns(await generators(tree)));
21
+ found.set('meta', answers);
22
+ for (const noun of (await declared(tree)).keys())
23
+ if (!found.has(noun))
24
+ found.set(noun, answers);
25
+ return found;
26
+ }
27
+ function printed(root, at, noun) {
28
+ if (at === '')
29
+ return { refused: `nothing in this tree is written by \`thread create ${noun}\``, body: '' };
30
+ const ran = spawnSync(execPath, [...LOADER, join(root, at)], {
31
+ cwd: root,
32
+ encoding: 'utf8',
33
+ env: { ...env, THREAD_NOUN: noun },
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ });
36
+ const body = String(ran.stdout ?? '').replace(CRLF, '\n').replace(RECEIPT_LINE, '').trimEnd();
37
+ const said = String(ran.stderr ?? '').replace(CRLF, '\n').trim().split('\n')[0] ?? '';
38
+ return { refused: body === '' ? said || 'it printed nothing' : '', body };
39
+ }
40
+ function skill(noun, body) {
41
+ return [
42
+ `# ${noun}`,
43
+ '',
44
+ `Everything \`npx thread create ${noun}\` knows, written out by \`npx thread skills\`.`,
45
+ '',
46
+ '**Do not edit this file.** It is derived from the tree, so an edit here is reverted the next',
47
+ 'time the command runs, and `npx thread skills --check` fails until it is. If something below is',
48
+ 'wrong or missing, the command that printed it is what to change.',
49
+ '',
50
+ `**Read this before you declare a ${noun}.** What follows is what already exists. The reason it`,
51
+ 'exists is that things get built twice by people who did not look — thirteen elements were once',
52
+ 'declared in one app that every one of them already shipped, and the cost was two days of fixes',
53
+ 'landing on a page nobody was looking at. Open what it names before you add to it.',
54
+ '',
55
+ 'When you are ready to write, run the command yourself for a live receipt. The one printed here',
56
+ 'has been removed on purpose: a receipt is spent by one run and goes stale the moment its sheet',
57
+ 'changes, so a copy kept in a file is always the wrong one.',
58
+ '',
59
+ '```',
60
+ body.replace(NONCE, '--receipt <receipt>'),
61
+ '```',
62
+ '',
63
+ ].join('\n');
64
+ }
65
+ const SHEET = `${TREE}_generated/skills.ts`;
66
+ function sheet(files) {
67
+ const rows = files.map((at) => {
68
+ const noun = at.slice(WHERE.length + 1, -'.md'.length);
69
+ return [
70
+ ' {',
71
+ ` id: 'skill-${noun}',`,
72
+ ` at: '${at}',`,
73
+ ` describes: 'What \`thread create ${noun}\` teaches: the ${noun}s this tree already has, with the sentence each one states, and the rules for declaring another. Written from what that command prints about itself, so the skill and the command cannot disagree.',`,
74
+ ' version: 1,',
75
+ " by: 'thread skills',",
76
+ " runs: 'thread skills --check',",
77
+ ' },',
78
+ ].join('\n');
79
+ });
80
+ return [
81
+ "import type { Threaded } from '../../thread/core/thread.ts'",
82
+ '',
83
+ 'export const SKILLS: Threaded = {',
84
+ " id: 'skills',",
85
+ ` at: '${SHEET}',`,
86
+ " describes: 'One row per skill `thread skills` writes, so every `skills/<noun>.md` is declared by the command that wrote it instead of being refused as a file nothing named. It sits beside the files it names because the nouns are not known until the tree is read, and a list somebody keeps by hand would go stale the first time a package brings a new maker.',",
87
+ ' version: 1,',
88
+ ' derives: [',
89
+ ...rows,
90
+ ' ],',
91
+ '}',
92
+ '',
93
+ ].join('\n');
94
+ }
95
+ const HERE = () => cwd();
96
+ const COINS = /by:\s*'thread create ([a-z][a-z0-9-]*)'/g;
97
+ const ANSWERS_AT = new RegExp(`at:\\s*'node_modules/${ORGANISATION}/[^/]+/(dist/[^']+)'`, 'g');
98
+ function ownMakers(root) {
99
+ const found = new Map();
100
+ let names;
101
+ try {
102
+ names = readdirSync(join(root, 'src'), { recursive: true });
103
+ }
104
+ catch {
105
+ return found;
106
+ }
107
+ for (const one of names) {
108
+ const path = String(one).split('\\').join('/');
109
+ if (!path.endsWith('attach.ts'))
110
+ continue;
111
+ let text;
112
+ try {
113
+ text = readFileSync(join(root, 'src', String(one)), 'utf8');
114
+ }
115
+ catch {
116
+ continue;
117
+ }
118
+ for (const row of text.split(/\{\s*$/m)) {
119
+ const noun = [...row.matchAll(COINS)].at(0)?.[1];
120
+ const at = [...row.matchAll(ANSWERS_AT)].at(-1)?.[1];
121
+ if (noun === undefined || at === undefined || found.has(noun))
122
+ continue;
123
+ found.set(noun, join(root, at));
124
+ }
125
+ }
126
+ return found;
127
+ }
128
+ function printedOwn(at) {
129
+ const ran = spawnSync(execPath, ['--experimental-strip-types', at, '--own'], {
130
+ encoding: 'utf8',
131
+ stdio: ['ignore', 'pipe', 'pipe'],
132
+ });
133
+ const body = String(ran.stdout ?? '').replace(CRLF, '\n').replace(RECEIPT_LINE, '').trimEnd();
134
+ const said = String(ran.stderr ?? '').replace(CRLF, '\n').trim().split('\n')[0] ?? '';
135
+ return { refused: ran.status === 0 && body !== '' ? '' : said || 'it printed nothing', body };
136
+ }
137
+ async function run() {
138
+ const own = argv.includes('--own');
139
+ const checking = argv.includes('--check');
140
+ const root = own ? HERE() : findTree().at;
141
+ const mine = own ? ownMakers(root) : new Map();
142
+ const theirs = own ? new Map() : await offering(findTree());
143
+ const known = [...(own ? mine : theirs).keys()].sort();
144
+ if (known.length === 0) {
145
+ stdout.write(own
146
+ ? 'skills: this package ships no maker, so it coins no noun and has no skill to write.\n'
147
+ : 'skills: this tree declares no `create` command yet, so there is no method to write down. ' +
148
+ `Attach a package that brings one — \`npx thread attach ${named('ui')}\` — and run this again.\n`);
149
+ return 0;
150
+ }
151
+ const wanted = new Map();
152
+ const skipped = [];
153
+ for (const noun of known) {
154
+ const { refused, body } = own ? printedOwn(mine.get(noun) ?? '') : printed(root, theirs.get(noun) ?? '', noun);
155
+ if (refused !== '') {
156
+ skipped.push(` ${noun} — ${refused}`);
157
+ continue;
158
+ }
159
+ wanted.set(fileFor(noun), skill(noun, body));
160
+ }
161
+ if (wanted.size === 0) {
162
+ stdout.write(`skills: every \`create\` this tree declares refused, so there is no method to write down yet:\n${skipped.join('\n')}\n`);
163
+ return 1;
164
+ }
165
+ const made = wanted.size;
166
+ const sheetAt = own ? '' : SHEET;
167
+ if (sheetAt !== '')
168
+ wanted.set(sheetAt, sheet([...wanted.keys()].sort()));
169
+ const held = new Map();
170
+ try {
171
+ for (const name of readdirSync(join(root, WHERE))) {
172
+ if (!name.endsWith('.md'))
173
+ continue;
174
+ held.set(join(WHERE, name), readFileSync(join(root, WHERE, name), 'utf8').replace(CRLF, '\n'));
175
+ }
176
+ }
177
+ catch {
178
+ }
179
+ if (sheetAt !== '') {
180
+ try {
181
+ held.set(sheetAt, readFileSync(join(root, sheetAt), 'utf8').replace(CRLF, '\n'));
182
+ }
183
+ catch {
184
+ }
185
+ }
186
+ const stale = [...held.keys()].filter((one) => !wanted.has(one)).sort();
187
+ const differs = [...wanted.entries()].filter(([one, text]) => held.get(one) !== text).map(([one]) => one).sort();
188
+ if (checking) {
189
+ if (stale.length === 0 && differs.length === 0) {
190
+ stdout.write(`skills: ${String(made)} skill(s), each matching what its own command prints` +
191
+ (sheetAt === '' ? '' : `, and each declared by a row in ${sheetAt}`) +
192
+ '.\n');
193
+ return 0;
194
+ }
195
+ for (const one of differs) {
196
+ stdout.write(`skills: ${one} is not what \`npx thread skills\` writes. A skill is derived from the command ` +
197
+ 'it describes, so an edit here is a second statement of the method and will drift from the ' +
198
+ 'first. Change the command, then run `npx thread skills`.\n');
199
+ }
200
+ for (const one of stale) {
201
+ stdout.write(`skills: ${one} names a \`create\` this tree no longer declares, so it teaches a command that ` +
202
+ 'will refuse. Run `npx thread skills`.\n');
203
+ }
204
+ return 1;
205
+ }
206
+ for (const [one, text] of wanted) {
207
+ mkdirSync(dirname(join(root, one)), { recursive: true });
208
+ writeFileSync(join(root, one), text, 'utf8');
209
+ }
210
+ for (const one of stale)
211
+ rmSync(join(root, one), { force: true });
212
+ stdout.write(`skills: wrote ${String(made)} skill(s) into ${WHERE}/` +
213
+ (sheetAt === '' ? '' : `, and the ${String(made)} row(s) declaring them into ${sheetAt}`) +
214
+ (stale.length === 0
215
+ ? ''
216
+ : `, and removed ${String(stale.length)} naming a command this tree no longer has`) +
217
+ '.\n' +
218
+ (skipped.length === 0 ? '' : `skills: no method to keep yet for:\n${skipped.join('\n')}\n`));
219
+ return 0;
220
+ }
221
+ if (invoked(import.meta.url))
222
+ exit(await run());
@@ -0,0 +1,6 @@
1
+ export interface Glossary {
2
+ readonly id: string;
3
+ readonly under: string;
4
+ readonly from: readonly string[];
5
+ readonly means: string;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { Actor } from '../core/actor.ts';
2
+ import { type Condition } from '../core/condition.ts';
3
+ import type { Member } from '../core/thread.ts';
4
+ export declare const PERMITS_AT = "src/_generated/permits.ts";
5
+ export declare function declaredWords(entities: readonly {
6
+ readonly holds: readonly {
7
+ readonly id: string;
8
+ readonly options?: readonly string[];
9
+ }[];
10
+ readonly actor?: {
11
+ readonly roles: string;
12
+ readonly entitlements?: string;
13
+ };
14
+ }[], which: 'roles' | 'entitlements', everybody: string): readonly string[];
15
+ export declare function undeclared(answered: string, offers: readonly string[], which: 'roles' | 'entitlements'): string | undefined;
16
+ export interface Grant extends Member {
17
+ readonly may: readonly string[];
18
+ readonly on?: string;
19
+ readonly to?: {
20
+ readonly roles?: Actor['roles'];
21
+ readonly entitlements?: Actor['entitlements'];
22
+ };
23
+ readonly when?: Condition;
24
+ }
25
+ export declare function opensToEverybody(grants: readonly Grant[]): readonly Grant[];
26
+ export declare function grantedTwice(grants: readonly Grant[]): readonly string[];
27
+ export declare function unanswerable(grants: readonly Grant[]): readonly string[];
@@ -0,0 +1,61 @@
1
+ import { mentionsActor, walkOperands } from "../core/condition.js";
2
+ export const PERMITS_AT = 'src/_generated/permits.ts';
3
+ export function declaredWords(entities, which, everybody) {
4
+ const found = entities.flatMap((one) => {
5
+ const at = one.actor?.[which];
6
+ return at === undefined ? [] : (one.holds.find((each) => each.id === at)?.options ?? []);
7
+ });
8
+ return [...new Set(which === 'roles' ? [everybody, ...found] : found)];
9
+ }
10
+ export function undeclared(answered, offers, which) {
11
+ const stray = answered
12
+ .split(',')
13
+ .map((one) => one.trim())
14
+ .filter((one) => one !== '' && !offers.includes(one));
15
+ if (stray.length === 0)
16
+ return undefined;
17
+ return (`this app declares no ${which === 'roles' ? 'role' : 'entitlement'} called ${stray.map((one) => `\`${one}\``).join(', ')} — ` +
18
+ (offers.length === 0
19
+ ? `it declares none at all. A ${which === 'roles' ? 'role' : 'entitlement'} is one of the words the value an ` +
20
+ 'entity names as saying what a caller may do is held to. Declare them with `thread create entity edit` — ' +
21
+ 'name that value, give it the `choice` format, and state its options'
22
+ : `it declares ${offers.map((one) => `\`${one}\``).join(', ')}`));
23
+ }
24
+ export function opensToEverybody(grants) {
25
+ return grants.filter((one) => one.to === undefined && (one.when === undefined || !mentionsActor(one.when)));
26
+ }
27
+ export function grantedTwice(grants) {
28
+ const seen = new Set();
29
+ const twice = new Set();
30
+ for (const one of grants) {
31
+ if (seen.has(one.id))
32
+ twice.add(one.id);
33
+ seen.add(one.id);
34
+ }
35
+ return [...twice].sort();
36
+ }
37
+ function operandsOf(one) {
38
+ if (one.when === undefined)
39
+ return [];
40
+ const found = [];
41
+ walkOperands(one.when, (each) => {
42
+ if ('agg' in each)
43
+ found.push('agg');
44
+ if ('was' in each)
45
+ found.push('was');
46
+ if ('outer' in each)
47
+ found.push('outer');
48
+ });
49
+ return found;
50
+ }
51
+ export function unanswerable(grants) {
52
+ return grants.flatMap((one) => {
53
+ const held = [...new Set(operandsOf(one))];
54
+ if (held.length === 0)
55
+ return [];
56
+ const why = held.includes('agg')
57
+ ? 'it counts other rows, and a gate is asked about the one in front of it. Put the aggregate in an invariant on the entity, where it runs against the rows themselves'
58
+ : 'it reads a value as it was before the write, and at the moment this is asked the write has not happened. Only an invariant, which runs on the write itself, can see a before';
59
+ return [`\`${one.id}\` states a condition this gate cannot answer — ${why}`];
60
+ });
61
+ }
@@ -0,0 +1,6 @@
1
+ import type { Attaching } from '../core/attach.ts';
2
+ import type { Threaded } from '../core/thread.ts';
3
+ export declare const TAKES = "What one operation needs before it will run: the shape of its input, what it is expected to cost, and who is allowed to ask for it. Written once when the operation is created, then yours to change.";
4
+ export declare const WOVEN = "The operation written out as one function. `thread compile` builds it from the steps declared for it, so nothing here is edited by hand \u2014 anything added here is gone the next time it is compiled.";
5
+ export declare const said: (one: Threaded, pad: string) => string;
6
+ export declare const ATTACH: Attaching;