@prestack/thread 0.0.1 → 0.0.3

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 (150) hide show
  1. package/dist/core/actor.ts +20 -0
  2. package/dist/core/asking.d.ts +0 -1
  3. package/dist/core/asking.js +0 -1
  4. package/dist/core/asking.ts +1342 -0
  5. package/dist/core/attach.js +2 -2
  6. package/dist/core/attach.ts +585 -0
  7. package/dist/core/chain.js +8 -1
  8. package/dist/core/chain.ts +148 -0
  9. package/dist/core/cli.js +97 -18
  10. package/dist/core/cli.ts +2004 -0
  11. package/dist/core/condition.ts +227 -0
  12. package/dist/core/descend.d.ts +1 -0
  13. package/dist/core/descend.js +36 -0
  14. package/dist/core/descend.ts +1001 -0
  15. package/dist/core/finding.ts +38 -0
  16. package/dist/core/generators/meta.js +6 -0
  17. package/dist/core/generators/meta.ts +1006 -0
  18. package/dist/core/init.ts +170 -0
  19. package/dist/core/invoked.ts +47 -0
  20. package/dist/core/round.ts +57 -0
  21. package/dist/core/rows.ts +369 -0
  22. package/dist/core/scope.ts +18 -0
  23. package/dist/core/thread.ts +29 -0
  24. package/dist/core/tree.ts +67 -0
  25. package/dist/core/types.ts +350 -0
  26. package/dist/derivation/attach.js +1 -1
  27. package/dist/derivation/attach.ts +68 -0
  28. package/dist/derivation/generators/build.ts +232 -0
  29. package/dist/derivation/generators/check.ts +118 -0
  30. package/dist/derivation/generators/derivation.ts +347 -0
  31. package/dist/derivation/generators/determinism.ts +145 -0
  32. package/dist/derivation/generators/release.js +19 -9
  33. package/dist/derivation/generators/release.ts +213 -0
  34. package/dist/derivation/makers/release.ts +135 -0
  35. package/dist/derivation/release.ts +23 -0
  36. package/dist/integrations/attach.js +2 -2
  37. package/dist/integrations/attach.ts +207 -0
  38. package/dist/integrations/generators/read.ts +127 -0
  39. package/dist/integrations/generators/secret.ts +289 -0
  40. package/dist/integrations/generators/target.js +16 -6
  41. package/dist/integrations/generators/target.ts +944 -0
  42. package/dist/integrations/generators/vault.js +46 -14
  43. package/dist/integrations/generators/vault.ts +212 -0
  44. package/dist/integrations/{sdk.js → module.js} +4 -4
  45. package/dist/integrations/module.ts +189 -0
  46. package/dist/integrations/secret.ts +50 -0
  47. package/dist/integrations/target.d.ts +3 -1
  48. package/dist/integrations/target.js +14 -11
  49. package/dist/integrations/target.ts +340 -0
  50. package/dist/integrations/vault.d.ts +2 -0
  51. package/dist/integrations/vault.js +4 -2
  52. package/dist/integrations/vault.ts +136 -0
  53. package/dist/integrity/attach.ts +234 -0
  54. package/dist/integrity/check-threads.js +2 -26
  55. package/dist/integrity/check-threads.ts +166 -0
  56. package/dist/integrity/constraint.ts +1202 -0
  57. package/dist/integrity/generators/compiler.ts +161 -0
  58. package/dist/integrity/generators/gate.ts +201 -0
  59. package/dist/integrity/generators/issues.ts +160 -0
  60. package/dist/integrity/generators/publish.js +14 -5
  61. package/dist/integrity/generators/publish.ts +247 -0
  62. package/dist/integrity/generators/rule.ts +271 -0
  63. package/dist/integrity/generators/seal.ts +117 -0
  64. package/dist/integrity/generators/trial.ts +320 -0
  65. package/dist/integrity/lint.ts +227 -0
  66. package/dist/integrity/lsp/bin.ts +42 -0
  67. package/dist/integrity/lsp/diagnostics.ts +92 -0
  68. package/dist/integrity/lsp/findings.ts +63 -0
  69. package/dist/integrity/lsp/lint.ts +140 -0
  70. package/dist/integrity/lsp/protocol.ts +78 -0
  71. package/dist/integrity/lsp/server.ts +224 -0
  72. package/dist/integrity/sealed.js +2 -2
  73. package/dist/integrity/sealed.ts +56 -0
  74. package/dist/integrity/slopnator.ts +350 -0
  75. package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
  76. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
  77. package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
  78. package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
  79. package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
  80. package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
  81. package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
  82. package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
  83. package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
  84. package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
  85. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
  86. package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
  87. package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
  88. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
  89. package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
  90. package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
  91. package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
  92. package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
  93. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
  94. package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
  95. package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
  96. package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
  97. package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
  98. package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
  99. package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
  100. package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
  101. package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
  102. package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
  103. package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
  104. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
  105. package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
  106. package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
  107. package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
  108. package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
  109. package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
  110. package/dist/integrity/sweep.ts +307 -0
  111. package/dist/knowledge/attach.ts +350 -0
  112. package/dist/knowledge/generators/docsgen.ts +174 -0
  113. package/dist/knowledge/generators/glossary.ts +73 -0
  114. package/dist/knowledge/generators/skills.ts +263 -0
  115. package/dist/knowledge/glossary.ts +6 -0
  116. package/dist/operations/access.ts +88 -0
  117. package/dist/operations/attach.ts +475 -0
  118. package/dist/operations/capabilities.ts +65 -0
  119. package/dist/operations/cost.ts +58 -0
  120. package/dist/operations/dispatch.ts +348 -0
  121. package/dist/operations/error-results.ts +31 -0
  122. package/dist/operations/errors/catalog.ts +168 -0
  123. package/dist/operations/errors/classes.ts +51 -0
  124. package/dist/operations/errors/errors.ts +314 -0
  125. package/dist/operations/generators/capability.js +29 -19
  126. package/dist/operations/generators/capability.ts +704 -0
  127. package/dist/operations/generators/compile.js +20 -1
  128. package/dist/operations/generators/compile.ts +643 -0
  129. package/dist/operations/generators/grant.ts +336 -0
  130. package/dist/operations/generators/operation.ts +972 -0
  131. package/dist/operations/generators/permits.ts +223 -0
  132. package/dist/operations/generators/seed.ts +347 -0
  133. package/dist/operations/generators/trial.js +4 -1
  134. package/dist/operations/generators/trial.ts +529 -0
  135. package/dist/operations/http.ts +164 -0
  136. package/dist/operations/makers/capability.ts +166 -0
  137. package/dist/operations/makers/trial.ts +520 -0
  138. package/dist/operations/memory.ts +470 -0
  139. package/dist/operations/metering.ts +328 -0
  140. package/dist/operations/nodes.ts +54 -0
  141. package/dist/operations/operations.ts +32 -0
  142. package/dist/operations/queue-policy.ts +10 -0
  143. package/dist/operations/result.ts +28 -0
  144. package/dist/operations/seams.d.ts +4 -4
  145. package/dist/operations/seams.ts +364 -0
  146. package/dist/operations/store.ts +29 -0
  147. package/package.json +8 -1
  148. package/dist/integrations/generators/sdk.d.ts +0 -1
  149. package/dist/integrations/generators/sdk.js +0 -148
  150. /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
@@ -0,0 +1,234 @@
1
+ import type { Attaching } from '../core/attach.ts'
2
+ import { named } from '../core/scope.ts'
3
+
4
+ import { quoted } from '../core/rows.ts'
5
+
6
+ const DESCRIBES =
7
+ 'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.'
8
+
9
+ const CONSTRAINT =
10
+ 'The rules the framework enforces on every app that installs it. They are read from the installed package rather than from this tree, so there is nothing here to weaken.'
11
+
12
+ const RULES =
13
+ 'What this app refuses on top of what the framework already does. `thread create rule` asks what to ban, where it applies and what to do instead, then writes the row and the check beside it.'
14
+
15
+ const HOME = `node_modules/${named('integrity')}/dist`
16
+
17
+ const GATE =
18
+ 'Every surface that can refuse, each naming where it lives and the command that runs it, so the gate is derived from what this system declares rather than kept as a string beside it.'
19
+
20
+ const COMPILER =
21
+ 'The compiler run over the files this app declares and no others. A type error is the plainest kind of broken build, so it is refused here rather than found by whoever runs the app next.'
22
+
23
+ const INTEGRITY_SOURCE = `import type { Threaded } from '../core/thread.ts'
24
+
25
+ export const INTEGRITY: Threaded = {
26
+ id: 'integrity',
27
+ at: '_threads/integrity.ts',
28
+ describes: ${quoted(DESCRIBES)},
29
+ version: 1,
30
+ declares: [
31
+ {
32
+ id: 'compiler',
33
+ at: 'tsconfig.json',
34
+ describes: ${quoted(COMPILER)},
35
+ version: 1,
36
+ by: 'thread compiler',
37
+ },
38
+ {
39
+ id: 'constraint',
40
+ at: '${HOME}/constraint.js',
41
+ describes: ${quoted(CONSTRAINT)},
42
+ version: 1,
43
+ },
44
+ {
45
+ id: 'rules',
46
+ at: '_threads/rules.ts',
47
+ describes: ${quoted(RULES)},
48
+ version: 1,
49
+ },
50
+ {
51
+ id: 'gate',
52
+ at: '_threads/gate.ts',
53
+ describes: ${quoted(GATE)},
54
+ version: 1,
55
+ },
56
+ ],
57
+ }
58
+ `
59
+
60
+ const RULES_SOURCE = `import type { Constraint } from './constraint.ts'
61
+
62
+ export const RULES: readonly Constraint[] = []
63
+ `
64
+
65
+ const GATE_SOURCE = `import type { Threaded } from '../core/thread.ts'
66
+
67
+ export const GATE: Threaded = {
68
+ id: 'gate',
69
+ at: '_threads/gate.ts',
70
+ describes: ${quoted(GATE)},
71
+ version: 1,
72
+ declares: [],
73
+ }
74
+ `
75
+
76
+ const SEEDED = {
77
+ attaches: ['@prestack/thread/derivation/attach'] as const,
78
+ id: 'integrity',
79
+ declares: [
80
+ ' {',
81
+ ' id: \'integrity\',',
82
+ ' at: \'_threads/integrity.ts\',',
83
+ ' describes: \'What this app refuses, and the checks that make each refusal actually happen. Every check has one owner, so none of them can be switched off somewhere nobody is looking.\',',
84
+ ' version: 1,',
85
+ ' },',
86
+ ].join('\n'),
87
+ }
88
+
89
+ const SHIPPED: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
90
+ {
91
+ into: 'derivation.ts',
92
+ list: 'GENERATORS',
93
+ id: 'gate-generator',
94
+ source: " {\n id: 'gate-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/gate.js',\n describes: 'The generator of the gate chain — it reads every row that names a command and refuses one naming none, rather than writing a shorter gate.',\n version: 1,\n by: 'thread gate',\n },",
95
+ },
96
+ {
97
+ into: 'derivation.ts',
98
+ list: 'GENERATORS',
99
+ id: 'compiler-generator',
100
+ source: " {\n id: 'compiler-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/compiler.js',\n describes: 'The generator of the compiler config — it walks this tree, leaves out every directory with nothing declared beneath it, and writes that list into the config as what the compiler may not read. It checks the same list before letting the compiler run, because a compile that was pointed at the wrong code is not a compile that passed.',\n version: 1,\n by: 'thread compiler',\n runs: 'thread compiler --check',\n },",
101
+ },
102
+ {
103
+ into: 'derivation.ts',
104
+ list: 'GENERATORS',
105
+ id: 'descent-runner',
106
+ source: " {\n id: 'descent-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/check-threads.js',\n describes: 'The walk over everything this app tracks, refusing any file no row names. It runs before anything is built, so a file nothing declares stops the build rather than reaching a person.',\n version: 1,\n by: 'thread descent',\n runs: 'thread descent',\n },",
107
+ },
108
+ {
109
+ into: 'derivation.ts',
110
+ list: 'GENERATORS',
111
+ id: 'sweep-runner',
112
+ source: " {\n id: 'sweep-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/sweep.js',\n describes: 'Every rule — the framework\\'s and this app\\'s own — run over the files it governs. A rule that could not run is named rather than counted as one that passed.',\n version: 1,\n by: 'thread sweep',\n runs: 'thread sweep',\n },",
113
+ },
114
+ {
115
+ into: 'derivation.ts',
116
+ list: 'GENERATORS',
117
+ id: 'issues-reader',
118
+ source: " {\n id: 'issues-reader',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/issues.js',\n describes: 'Everything wrong with this tree in one listing: files no row declares, files that will not compile, and what the rules refuse. It only reports, so it answers even when every other check has stopped.',\n version: 1,\n by: 'thread issues',\n runs: 'thread issues --check',\n },",
119
+ },
120
+ {
121
+ into: 'derivation.ts',
122
+ list: 'GENERATORS',
123
+ id: 'publish-runner',
124
+ source: " {\n id: 'publish-runner',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/publish.js',\n describes: 'Sends this repository\\'s packages to a registry. It derives what each package says about itself first, then builds, then refuses to send anything until the gate, the typecheck, the lint and the seal have each passed, and commits what it derived once the packages are out. The seal is checked rather than regenerated, because a release that rewrites its own seal can never fail it.',\n version: 1,\n by: 'thread publish',\n },",
125
+ },
126
+ {
127
+ into: 'derivation.ts',
128
+ list: 'GENERATORS',
129
+ id: 'rule-noun',
130
+ source: " {\n id: 'rule-noun',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/rule.js',\n describes: 'Declares a rule this app holds itself to. `thread create rule` writes the row and seeds the check beside it, so a new rule goes through the same wizard as everything else rather than being written by hand.',\n version: 1,\n by: 'thread create rule',\n },",
131
+ },
132
+ {
133
+ into: 'derivation.ts',
134
+ list: 'GENERATORS',
135
+ id: 'seal-generator',
136
+ source: " {\n id: 'seal-generator',\n at: 'node_modules/@prestack/thread/dist/integrity/generators/seal.js',\n describes: 'Writes the seal: a hash of every built file the integrity package ships, so an installed copy can prove it is the one that went out. With --check it writes nothing and refuses a seal that no longer matches the built files.',\n version: 1,\n by: 'thread seal',\n runs: 'thread seal --check',\n },",
137
+ },
138
+ ]
139
+ export const ATTACH: Attaching = {
140
+ attaches: SEEDED.attaches,
141
+ id: SEEDED.id,
142
+ declares: SEEDED.declares,
143
+ writes: [
144
+ { file: 'integrity.ts', source: INTEGRITY_SOURCE },
145
+ { file: 'rules.ts', source: RULES_SOURCE },
146
+ { file: 'gate.ts', source: GATE_SOURCE },
147
+ ],
148
+
149
+ retires: [
150
+ {
151
+ into: 'gate.ts',
152
+ list: 'declares',
153
+ id: 'compiler',
154
+ why: 'moved into `GENERATORS` beside the generator that writes it, which is what makes `tsconfig.json` settle when declarations move. Left here it would be a second gate step running the same command, which the gate refuses.',
155
+ },
156
+ {
157
+ into: 'gate.ts',
158
+ list: 'declares',
159
+ id: 'descent',
160
+ why: 'its step now comes from the `descent-runner` row this package ships into `GENERATORS`, which states `runs` beside `by`. Left here it would be a second gate step running the same command, which the gate refuses.',
161
+ },
162
+ {
163
+ into: 'gate.ts',
164
+ list: 'declares',
165
+ id: 'sweep',
166
+ why: 'its step now comes from the `sweep-runner` row this package ships into `GENERATORS`, which states `runs` beside `by`. Left here it would be a second gate step running the same command, which the gate refuses.',
167
+ },
168
+ {
169
+ into: 'constraint.ts',
170
+ list: 'GENERATORS',
171
+ id: 'sql-duplicate-id',
172
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
173
+ },
174
+ {
175
+ into: 'constraint.ts',
176
+ list: 'GENERATORS',
177
+ id: 'sql-name-collision',
178
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
179
+ },
180
+ {
181
+ into: 'constraint.ts',
182
+ list: 'GENERATORS',
183
+ id: 'sql-duplicate-parameter',
184
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
185
+ },
186
+ {
187
+ into: 'constraint.ts',
188
+ list: 'GENERATORS',
189
+ id: 'sql-unwritable-parameter-type',
190
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
191
+ },
192
+ {
193
+ into: 'constraint.ts',
194
+ list: 'GENERATORS',
195
+ id: 'sql-duplicate-returned-column',
196
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
197
+ },
198
+ {
199
+ into: 'constraint.ts',
200
+ list: 'GENERATORS',
201
+ id: 'sql-unwritable-return-type',
202
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
203
+ },
204
+ {
205
+ into: 'constraint.ts',
206
+ list: 'GENERATORS',
207
+ id: 'sql-unknown-write-target',
208
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
209
+ },
210
+ {
211
+ into: 'constraint.ts',
212
+ list: 'GENERATORS',
213
+ id: 'sql-multi-statement',
214
+ why: 'its check imported a package this framework no longer ships and read a folder no app has, so it could never refuse anything. A rule that cannot fire reads as coverage while enforcing nothing.',
215
+ },
216
+ ],
217
+
218
+ ignores: ['.thread/', '.next/', 'node_modules/', 'tsconfig.tsbuildinfo'],
219
+ scripts: {
220
+ gate: 'thread gate',
221
+ predev: 'thread gate',
222
+ dev: 'next dev',
223
+ prebuild: 'thread gate',
224
+ build: 'next build',
225
+ prestart: 'thread gate',
226
+ start: 'next start',
227
+ },
228
+
229
+ needs: {
230
+ '@types/node': '^24.0.0',
231
+ typescript: '^6.0.0',
232
+ },
233
+ rows: SHIPPED,
234
+ }
@@ -1,35 +1,11 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { readFileSync, readdirSync } from 'node:fs';
1
+ import { readdirSync } from 'node:fs';
3
2
  import { dirname, join } from 'node:path';
4
3
  import { argv, stdout } from 'node:process';
5
4
  import { exit, invoked } from "../core/invoked.js";
6
- import { ROOT, THREAD_AT, TREE, configured, descend } from "../core/descend.js";
5
+ import { ROOT, THREAD_AT, TREE, configured, descend, unkept } from "../core/descend.js";
7
6
  import { runChecker } from "../core/finding.js";
8
7
  export { ROOT, THREAD_AT, TREE, descend };
9
8
  const ELSEWHERE = new Set(['node_modules', '.git']);
10
- const OWN_IGNORES = (() => {
11
- if (spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
12
- return undefined;
13
- }
14
- try {
15
- return readFileSync(join(ROOT, '.gitignore'), 'utf8')
16
- .split('\n')
17
- .map((one) => one.trim())
18
- .filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
19
- .map((one) => one.replace(/^\/+|\/+$/g, ''))
20
- .filter((one) => one !== '' && !one.includes('*'));
21
- }
22
- catch {
23
- return [];
24
- }
25
- })();
26
- function unkept(at) {
27
- if (OWN_IGNORES === undefined) {
28
- const out = spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' });
29
- return out.status === 0;
30
- }
31
- return OWN_IGNORES.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
32
- }
33
9
  export function trackedFiles() {
34
10
  const found = [];
35
11
  const walk = (at, under) => {
@@ -0,0 +1,166 @@
1
+ import { readdirSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { argv, stdout } from 'node:process'
4
+
5
+ import { exit, invoked } from '../core/invoked.ts'
6
+
7
+ import { ROOT, THREAD_AT, TREE, configured, descend, unkept, type Finding, type Pass } from '../core/descend.ts'
8
+ import { runChecker } from '../core/finding.ts'
9
+
10
+ export { ROOT, THREAD_AT, TREE, descend }
11
+ export type { Finding, Pass }
12
+
13
+ const ELSEWHERE = new Set(['node_modules', '.git'])
14
+
15
+ export function trackedFiles(): readonly string[] {
16
+ const found: string[] = []
17
+
18
+ const walk = (at: string, under: string): void => {
19
+ for (const one of readdirSync(at, { withFileTypes: true })) {
20
+ if (ELSEWHERE.has(one.name)) continue
21
+ const here = under === '' ? one.name : `${under}/${one.name}`
22
+ if (one.isDirectory()) {
23
+ if (unkept(here)) continue
24
+ walk(join(at, one.name), here)
25
+ continue
26
+ }
27
+ if (unkept(here)) continue
28
+ found.push(here)
29
+ }
30
+ }
31
+
32
+ walk(ROOT, '')
33
+ return found.sort()
34
+ }
35
+
36
+ export function unthreaded(pass: Pass, files: readonly string[]): readonly string[] {
37
+ return files.filter((one) => one !== THREAD_AT && !pass.threaded.has(one))
38
+ }
39
+
40
+ export function twiceParented(pass: Pass): readonly { readonly at: string; readonly parents: readonly string[] }[] {
41
+ const found: { at: string; parents: readonly string[] }[] = []
42
+ for (const [at, held] of pass.threaded) {
43
+ const parents = held.filter((one) => one.list === 'declares').map((one) => one.by)
44
+ if (parents.length > 1) found.push({ at, parents })
45
+ }
46
+ return found
47
+ }
48
+
49
+ export function refusals(pass: Pass, files: readonly string[]): readonly Finding[] {
50
+ const found: Finding[] = [...pass.broken, ...pass.findings]
51
+
52
+ const held = configured()
53
+ if (held.trouble !== undefined) {
54
+ found.push({
55
+ file: held.trouble.split(' ')[0] ?? 'tsconfig.json',
56
+ detail: `the gate fell back to its own compiler options because ${held.trouble}`,
57
+ instead:
58
+ 'fix the config, or point the gate at one it can read. Falling back silently means declarations are judged by options nobody chose, and the run says none was found when one was',
59
+ })
60
+ }
61
+
62
+ return withFiles(found, pass, files)
63
+ }
64
+
65
+ function withFiles(found: Finding[], pass: Pass, files: readonly string[]): readonly Finding[] {
66
+
67
+ for (const one of unthreaded(pass, files)) {
68
+ const by = pass.mentioned.get(one) ?? []
69
+ if (by.length === 0) {
70
+ found.push({
71
+ file: one,
72
+ detail: 'nothing declares this — the descent from the root never reaches it, so it is not wired',
73
+ instead:
74
+ 'add a row naming this path to the node that owns it, with all four fields, or delete the file. A file that nothing names is either somebody\'s child nobody wrote down, or it is nobody\'s',
75
+ })
76
+ continue
77
+ }
78
+ const cut = by.filter((one) => one !== THREAD_AT && !pass.threaded.has(one))
79
+ if (cut.length > 0) {
80
+ found.push({
81
+ file: one,
82
+ detail: `broken thread — ${cut.join(' and ')} names this, and nothing declares ${cut.length === 1 ? 'it' : 'them'}, so the descent never arrives`,
83
+ instead:
84
+ 'thread the parent. The row is written and the file is where it says, but the line back to the root is cut above it, and nothing below the cut is reached',
85
+ })
86
+ continue
87
+ }
88
+ found.push({
89
+ file: one,
90
+ detail: `broken thread — ${by.join(' and ')} names this and is itself declared, and the descent still never arrived, so the row was read and never walked`,
91
+ instead:
92
+ 'the cut is inside the parent, not above it. Its node answered its own parent with a shape that row does not state, so the descent refused it and stopped there — fix the shape the parent states, and everything under it joins up',
93
+ })
94
+ }
95
+
96
+ for (const one of twiceParented(pass)) {
97
+ found.push({
98
+ file: one.at,
99
+ detail: `declared by ${one.parents.join(' and ')} — declares is exactly one parent ever, so its owner, its shape and its origin each have two answers and the tree gives none`,
100
+ instead:
101
+ 'keep the row that owns it and delete the other. If both are true, one of them is a `derives` or a `seeds`, which may have many parents because neither of them owns the child',
102
+ })
103
+ }
104
+
105
+ return found
106
+ }
107
+
108
+ if (invoked(import.meta.url)) {
109
+ const pass = await descend()
110
+ const tracked = trackedFiles()
111
+ const findings = refusals(pass, tracked)
112
+
113
+ if (argv.includes('--json')) {
114
+ stdout.write(`${JSON.stringify(findings)}\n`)
115
+ exit(0)
116
+ }
117
+
118
+ const fixed = [...pass.threaded.keys()]
119
+ const reach = new Map<string, number>()
120
+ for (const one of fixed) reach.set(dirname(one), (reach.get(dirname(one)) ?? 0) + 1)
121
+
122
+ const held = configured()
123
+ stdout.write(
124
+ `threads: descended ${String(pass.declarations)} declaration(s) over ${String(pass.rows)} row(s), naming ${String(fixed.length)} file(s) and coining ${String(pass.coined.size)} word(s).\n`,
125
+ )
126
+ stdout.write(
127
+ held.trouble !== undefined
128
+ ? `threads: compiler options fallen back to — ${held.trouble}\n`
129
+ : held.from === ''
130
+ ? 'threads: compiler options fallen back to — nothing at the root states a tsconfig, so what the build compiles with may differ from what judged this\n'
131
+ : `threads: compiler options taken from ${held.from} at the root — what a package builds with may still differ from this\n`,
132
+ )
133
+ const here = new Set(tracked)
134
+ for (const [dir, held] of [...reach].sort()) {
135
+ const present = tracked.filter((one) => dirname(one) === dir).length
136
+ const mine = [...pass.threaded.keys()].filter((one) => dirname(one) === dir && here.has(one)).length
137
+ stdout.write(
138
+ present === 0
139
+ ? `threads: ${dir} — ${String(held)} file(s) named, none of them tracked here\n`
140
+ : `threads: ${dir} — ${String(mine)} of ${String(present)} tracked file(s) named\n`,
141
+ )
142
+ }
143
+
144
+ const named = [...pass.threaded.keys()].filter((one) => here.has(one)).length
145
+
146
+ runChecker(
147
+ 'threads',
148
+ findings,
149
+ `${String(pass.declarations)} declaration(s), ${String(pass.rows)} row(s), ${String(named)} of ${String(tracked.length)} tracked file(s) named`,
150
+ )
151
+
152
+ const warned = findings.filter((one) => one.severity === 'warning')
153
+ const refused = findings.filter((one) => one.severity !== 'warning')
154
+
155
+ if (warned.length > 0) {
156
+ stdout.write(
157
+ `threads: ${String(warned.length)} of these is a derivation nothing has produced yet — the row states what a ` +
158
+ 'generator would write and no file is there. A generator that refuses to write an empty file is doing its ' +
159
+ 'job, so this is said rather than refused. A derived file that is there and differs from what its generator ' +
160
+ 'writes is the other case, and that one fails.\n',
161
+ )
162
+ }
163
+
164
+ if (refused.length > 0) exit(1)
165
+ exit(0)
166
+ }