@prestack/thread 0.1.7 → 0.1.9

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 (34) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/dist/core/attach.js +40 -6
  3. package/dist/core/attach.ts +38 -6
  4. package/dist/core/descend.js +13 -17
  5. package/dist/core/descend.ts +13 -15
  6. package/dist/integrations/attach.js +5 -3
  7. package/dist/integrations/attach.ts +8 -4
  8. package/dist/integrations/cli.d.ts +2 -0
  9. package/dist/integrations/cli.js +55 -11
  10. package/dist/integrations/cli.ts +62 -11
  11. package/dist/integrations/disk.js +4 -3
  12. package/dist/integrations/disk.ts +4 -3
  13. package/dist/integrations/generators/vault.ts +0 -1
  14. package/dist/integrations/target.d.ts +1 -1
  15. package/dist/integrations/target.ts +1 -1
  16. package/dist/integrity/generators/gate.ts +1 -2
  17. package/dist/integrity/generators/publish.ts +0 -1
  18. package/dist/integrity/lint.js +0 -6
  19. package/dist/integrity/lint.ts +0 -10
  20. package/dist/integrity/slopnator.ts +1 -1
  21. package/dist/integrity/slopnators/sl_derived_drift.js +0 -2
  22. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -3
  23. package/dist/integrity/slopnators/sl_unthreaded.js +0 -2
  24. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -5
  25. package/dist/integrity/sweep.ts +0 -2
  26. package/dist/knowledge/generators/docsgen.ts +1 -1
  27. package/dist/operations/generators/capability.js +20 -9
  28. package/dist/operations/generators/capability.ts +19 -12
  29. package/dist/operations/generators/compile.js +41 -28
  30. package/dist/operations/generators/compile.ts +48 -33
  31. package/dist/operations/generators/permits.js +6 -3
  32. package/dist/operations/generators/permits.ts +7 -5
  33. package/dist/operations/generators/trial.ts +1 -1
  34. package/package.json +1 -1
@@ -15,6 +15,7 @@ const ARRIVES = 'Which header a value comes in on. A webhook says which one carr
15
15
  const SIGNS = 'What a secret does to a body on its way out: its value is the key a signature is computed with, and the digest goes on the header named here. It is `sends` turned around — `sends` puts the value itself on a header, and this puts the value nowhere at all.';
16
16
  const ADDRESS = 'Where a target reached over https is: the one absolute url every call on it is written from, stated on the row because a place is a fact anybody may read. Only the address of a database is sealed instead, because a connection string carries a password.';
17
17
  const HEADERS = 'The headers every call on a target carries, stated on its row as name and value: the version of an API this app is written against, or a fixed way of asking. A credential never goes here, because anybody may read a row; a value that has to be withheld is a secret with a sends.';
18
+ const NEEDS = 'The packages that must be installed for this to work, each named with the versions it works with: a range from npm, a git link or a tarball. Attaching a package, or creating any row that states them, adds the ones the app does not already name to what it depends on and fetches them. A name the app already carries is left alone, so nothing overwrites a choice somebody made.';
18
19
  const SSOT = { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' };
19
20
  const RULED = { on: '2026-09-22', said: 'remove pub then as an acceptable thing, that\'s definitely slop and it\'s not the first time such a fucking thing has surfaced. Delete and redo it properly.' };
20
21
  export const CONCEPTS = [
@@ -28,6 +29,7 @@ export const CONCEPTS = [
28
29
  { id: 'call', describes: CALL, cites: [SSOT] },
29
30
  { id: 'reach', describes: REACH, cites: [SSOT] },
30
31
  { id: 'integration', describes: INTEGRATION, cites: [SSOT] },
32
+ { id: 'needs', describes: NEEDS, cites: [] },
31
33
  ];
32
34
  import { quoted } from '../core/rows.js';
33
35
  const concept = (one) => [
@@ -36,9 +38,9 @@ const concept = (one) => [
36
38
  " at: '_threads/concepts.ts',",
37
39
  ` describes: ${quoted(one.describes)},`,
38
40
  ` version: ${String(VERSION)},`,
39
- ' cites: [',
40
- ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'),
41
- ' ],',
41
+ ...(one.cites.length === 0
42
+ ? [' cites: [],']
43
+ : [' cites: [', ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'), ' ],']),
42
44
  ' },',
43
45
  ].join('\n');
44
46
  const INTEGRATIONS_SOURCE = `import type { Threaded } from '${SHAPED}'
@@ -1,6 +1,6 @@
1
1
  import type { Attaching } from '../core/attach.ts'
2
2
  import { SHAPED } from '../core/init.ts'
3
- import { NAMED, named } from '../core/scope.ts'
3
+ import { NAMED } from '../core/scope.ts'
4
4
 
5
5
  const VERSION = 1
6
6
 
@@ -46,6 +46,9 @@ const ADDRESS =
46
46
  const HEADERS =
47
47
  'The headers every call on a target carries, stated on its row as name and value: the version of an API this app is written against, or a fixed way of asking. A credential never goes here, because anybody may read a row; a value that has to be withheld is a secret with a sends.'
48
48
 
49
+ const NEEDS =
50
+ 'The packages that must be installed for this to work, each named with the versions it works with: a range from npm, a git link or a tarball. Attaching a package, or creating any row that states them, adds the ones the app does not already name to what it depends on and fetches them. A name the app already carries is left alone, so nothing overwrites a choice somebody made.'
51
+
49
52
  const SSOT = { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' }
50
53
 
51
54
  const RULED = { on: '2026-09-22', said: 'remove pub then as an acceptable thing, that\'s definitely slop and it\'s not the first time such a fucking thing has surfaced. Delete and redo it properly.' }
@@ -61,6 +64,7 @@ export const CONCEPTS: readonly { readonly id: string; readonly describes: strin
61
64
  { id: 'call', describes: CALL, cites: [SSOT] },
62
65
  { id: 'reach', describes: REACH, cites: [SSOT] },
63
66
  { id: 'integration', describes: INTEGRATION, cites: [SSOT] },
67
+ { id: 'needs', describes: NEEDS, cites: [] },
64
68
  ]
65
69
 
66
70
  import { quoted } from '../core/rows.ts'
@@ -72,9 +76,9 @@ const concept = (one: (typeof CONCEPTS)[number]): string =>
72
76
  " at: '_threads/concepts.ts',",
73
77
  ` describes: ${quoted(one.describes)},`,
74
78
  ` version: ${String(VERSION)},`,
75
- ' cites: [',
76
- ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'),
77
- ' ],',
79
+ ...(one.cites.length === 0
80
+ ? [' cites: [],']
81
+ : [' cites: [', ...one.cites.map((each) => " { on: '" + each.on + "', said: " + quoted(each.said) + ' },'), ' ],']),
78
82
  ' },',
79
83
  ].join('\n')
80
84
 
@@ -22,6 +22,8 @@ export declare function claims(root: string, runs: string): Held;
22
22
  export declare function programFor(name: string, at: string): string;
23
23
  export declare const SIGNING = "THREAD_RECEIPTS_KEY";
24
24
  export declare function signing(): string;
25
+ export declare const PROGRAM: string;
26
+ export declare const COPIES: string;
25
27
  export declare function running(name: string, subcommands: Readonly<Record<string, string>>, at: string): Running;
26
28
  export interface Expansion {
27
29
  readonly program: string;
@@ -6,8 +6,8 @@ import { allowedNodeEnvironmentFlags, env, execPath, kill, pid, stderr, stdout }
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { CHANNEL, FOLDER, across, attached, createFileSync, existsSync, here, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from '../integrations/disk.js';
8
8
  import { VAULT_DIR } from '../integrations/vault.js';
9
- import { CALL_PREFIX, LOCKFILES, PACKAGER, REACH, managerOf, programOf, unlisted } from '../integrations/target.js';
10
- import { CLI } from '../core/invoked.js';
9
+ import { CALL_PREFIX, LOCKFILES, ORDERS, PACKAGER, REACH, managerOf, programOf, unlisted } from '../integrations/target.js';
10
+ import { BESIDE, CLI } from '../core/invoked.js';
11
11
  import { valuesIn } from '../core/rows.js';
12
12
  import { THING } from '../core/scope.js';
13
13
  import { findTree, inTree } from '../core/tree.js';
@@ -168,11 +168,33 @@ export function signing() {
168
168
  return signed;
169
169
  }
170
170
  const OWN = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
171
+ export const PROGRAM = REACH.cli + THING;
172
+ export const COPIES = THING + ' create copy';
173
+ const UNCOPIED = 'the thread command sits beside none of the framework code this server runs, because no package ships it, and the ' +
174
+ 'folder this server was started from keeps no compiled copy of it. Run `' + COPIES + '` in that folder, and the next ' +
175
+ 'command starts that copy.';
176
+ function startable() {
177
+ if (existsSync(CLI.at(-1) ?? ''))
178
+ return { words: CLI, own: OWN, reads: [OWN] };
179
+ let root;
180
+ try {
181
+ root = findTree().at;
182
+ }
183
+ catch {
184
+ return UNCOPIED;
185
+ }
186
+ const copied = ((sheet(root).TARGETS ?? []).find((one) => one.id === PROGRAM)?.derives ?? []).find((one) => one.by === COPIES)?.at;
187
+ if (copied === undefined)
188
+ return UNCOPIED;
189
+ const own = resolve(root, copied);
190
+ const entry = join(own, 'cli', BESIDE('cli'));
191
+ return existsSync(entry) ? { words: [entry], own, reads: [own, join(root, 'node_modules')] } : UNCOPIED;
192
+ }
171
193
  const GUARDS = ['--permission', '--experimental-permission'];
172
194
  const HUSHES = '--disable-warning';
173
195
  const swapped = (path) => [...path].map((one) => (one === one.toUpperCase() ? one.toLowerCase() : one.toUpperCase())).join('');
174
196
  let unguarded = false;
175
- function guarded() {
197
+ function guarded(reads) {
176
198
  const before = env.NODE_OPTIONS ?? '';
177
199
  const on = GUARDS.find((one) => allowedNodeEnvironmentFlags.has(one));
178
200
  if (on === undefined) {
@@ -186,7 +208,7 @@ function guarded() {
186
208
  const kept = [...new Set([tmpdir(), realpathSync(tmpdir())])];
187
209
  const allowed = (flag, paths) => paths.map((one) => flag + '="' + one + '"');
188
210
  const quiet = allowedNodeEnvironmentFlags.has(HUSHES) ? [HUSHES + '=SecurityWarning'] : [];
189
- return [before, on, ...allowed('--allow-fs-read', [OWN, swapped(OWN), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
211
+ return [before, on, ...allowed('--allow-fs-read', [...reads.flatMap((one) => [one, swapped(one)]), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
190
212
  .filter((one) => one !== '')
191
213
  .join(' ');
192
214
  }
@@ -201,6 +223,11 @@ const still = (one, asks) => ({ there: true, ok: false, code: -1, out: '', err:
201
223
  const nowhere = (why) => ({ there: false, ok: false, code: 1, out: '', err: '', killed: null, why });
202
224
  const lost = (of) => ({ there: true, ok: false, code: -1, out: '', err: '', killed: null, why: '', of, lost: of });
203
225
  const told = (one) => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null]);
226
+ const READS = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order));
227
+ function kept(one, asked, held) {
228
+ if (READS.has(asked.order) && held.answered !== 'refused')
229
+ one.known.set(told(asked), held);
230
+ }
204
231
  const plainly = (one) => one.order + ' ' + (one.at === '' ? '.' : one.at);
205
232
  const outside = (root, at) => {
206
233
  const step = relative(root, at);
@@ -248,10 +275,18 @@ function watching(one) {
248
275
  continue;
249
276
  }
250
277
  const asked = { ...order, at: relative(one.root, order.at) };
278
+ const known = one.known.get(told(asked));
279
+ if (known !== undefined) {
280
+ reply(one.dir, short, known);
281
+ continue;
282
+ }
283
+ if (!READS.has(asked.order))
284
+ one.known.clear();
251
285
  const due = one.replaying.shift();
252
286
  if (due !== undefined) {
253
287
  if (told(due.order) === told(asked)) {
254
288
  reply(one.dir, short, due.answered);
289
+ kept(one, asked, due.answered);
255
290
  continue;
256
291
  }
257
292
  one.replaying = [];
@@ -263,6 +298,7 @@ function watching(one) {
263
298
  continue;
264
299
  }
265
300
  one.pending = short;
301
+ one.asking = asked;
266
302
  hand(one, still(one, [asked]));
267
303
  }
268
304
  }, TICK);
@@ -291,15 +327,15 @@ function next(one, within) {
291
327
  }, Math.max(1_000, within - MARGIN));
292
328
  });
293
329
  }
294
- function begun(word, args, root, hears, called, recording = []) {
330
+ function begun(from, word, args, root, hears, called, recording = []) {
295
331
  const of = called ?? randomBytes(6).toString('hex');
296
332
  const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'));
297
333
  writeFileSync(join(dir, 'channel'), '', 'utf8');
298
- const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length };
299
- const child = spawn(execPath, [...CLI, word, ...args], {
300
- cwd: OWN,
334
+ const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() };
335
+ const child = spawn(execPath, [...from.words, word, ...args], {
336
+ cwd: from.own,
301
337
  shell: false,
302
- env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded() },
338
+ env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded(from.reads) },
303
339
  });
304
340
  one.stop = () => child.kill();
305
341
  child.stdout.on('data', (chunk) => (one.out += chunk.toString()));
@@ -337,7 +373,10 @@ async function served(word, args, within, how) {
337
373
  const root = how?.at ?? '';
338
374
  if (root === '')
339
375
  return nowhere('nothing said which folder to run thread ' + word + UNPLACED);
340
- const again = begun(word, args, root, how?.hears, of, recording);
376
+ const from = startable();
377
+ if (typeof from === 'string')
378
+ return nowhere(from);
379
+ const again = begun(from, word, args, root, how?.hears, of, recording);
341
380
  return counted(again, await next(again, within));
342
381
  }
343
382
  const answering = how?.answering;
@@ -346,6 +385,8 @@ async function served(word, args, within, how) {
346
385
  const given = (how?.answered ?? [])[0];
347
386
  if (one.pending !== '' && given !== undefined) {
348
387
  reply(one.dir, one.pending, given);
388
+ if (one.asking !== undefined)
389
+ kept(one, one.asking, given);
349
390
  one.pending = '';
350
391
  }
351
392
  return counted(one, await next(one, within));
@@ -353,7 +394,10 @@ async function served(word, args, within, how) {
353
394
  const root = how?.at ?? '';
354
395
  if (root === '')
355
396
  return nowhere('nothing said which folder to run thread ' + word + UNPLACED);
356
- const fresh = begun(word, args, root, how?.hears);
397
+ const from = startable();
398
+ if (typeof from === 'string')
399
+ return nowhere(from);
400
+ const fresh = begun(from, word, args, root, how?.hears);
357
401
  return counted(fresh, await next(fresh, within));
358
402
  }
359
403
  export function running(name, subcommands, at) {
@@ -8,8 +8,8 @@ import { fileURLToPath } from 'node:url'
8
8
  import { CHANNEL, FOLDER, across, attached, createFileSync, existsSync, here, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from '../integrations/disk.ts'
9
9
  import { VAULT_DIR } from '../integrations/vault.ts'
10
10
 
11
- import { CALL_PREFIX, LOCKFILES, PACKAGER, REACH, managerOf, programOf, unlisted, type Answer, type Call, type How, type Order, type Ran, type Recorded, type Running, type Target } from '../integrations/target.ts'
12
- import { CLI } from '../core/invoked.ts'
11
+ import { CALL_PREFIX, LOCKFILES, ORDERS, PACKAGER, REACH, managerOf, programOf, unlisted, type Answer, type Call, type How, type Order, type Ran, type Recorded, type Running, type Target } from '../integrations/target.ts'
12
+ import { BESIDE, CLI } from '../core/invoked.ts'
13
13
  import { valuesIn } from '../core/rows.ts'
14
14
  import { THING } from '../core/scope.ts'
15
15
  import { findTree, inTree } from '../core/tree.ts'
@@ -213,6 +213,36 @@ export function signing(): string {
213
213
 
214
214
  const OWN = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
215
215
 
216
+ export const PROGRAM = REACH.cli + THING
217
+
218
+ export const COPIES = THING + ' create copy'
219
+
220
+ interface Started {
221
+ readonly words: readonly string[]
222
+ readonly own: string
223
+ readonly reads: readonly string[]
224
+ }
225
+
226
+ const UNCOPIED =
227
+ 'the thread command sits beside none of the framework code this server runs, because no package ships it, and the ' +
228
+ 'folder this server was started from keeps no compiled copy of it. Run `' + COPIES + '` in that folder, and the next ' +
229
+ 'command starts that copy.'
230
+
231
+ function startable(): Started | string {
232
+ if (existsSync(CLI.at(-1) ?? '')) return { words: CLI, own: OWN, reads: [OWN] }
233
+ let root: string
234
+ try {
235
+ root = findTree().at
236
+ } catch {
237
+ return UNCOPIED
238
+ }
239
+ const copied = ((sheet(root).TARGETS ?? []).find((one) => one.id === PROGRAM)?.derives ?? []).find((one) => one.by === COPIES)?.at
240
+ if (copied === undefined) return UNCOPIED
241
+ const own = resolve(root, copied)
242
+ const entry = join(own, 'cli', BESIDE('cli'))
243
+ return existsSync(entry) ? { words: [entry], own, reads: [own, join(root, 'node_modules')] } : UNCOPIED
244
+ }
245
+
216
246
  const GUARDS = ['--permission', '--experimental-permission']
217
247
 
218
248
  const HUSHES = '--disable-warning'
@@ -222,7 +252,7 @@ const swapped = (path: string): string =>
222
252
 
223
253
  let unguarded = false
224
254
 
225
- function guarded(): string {
255
+ function guarded(reads: readonly string[]): string {
226
256
  const before = env.NODE_OPTIONS ?? ''
227
257
  const on = GUARDS.find((one) => allowedNodeEnvironmentFlags.has(one))
228
258
  if (on === undefined) {
@@ -238,7 +268,7 @@ function guarded(): string {
238
268
  const kept = [...new Set([tmpdir(), realpathSync(tmpdir())])]
239
269
  const allowed = (flag: string, paths: readonly string[]): readonly string[] => paths.map((one) => flag + '="' + one + '"')
240
270
  const quiet = allowedNodeEnvironmentFlags.has(HUSHES) ? [HUSHES + '=SecurityWarning'] : []
241
- return [before, on, ...allowed('--allow-fs-read', [OWN, swapped(OWN), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
271
+ return [before, on, ...allowed('--allow-fs-read', [...reads.flatMap((one) => [one, swapped(one)]), ...kept]), ...allowed('--allow-fs-write', kept), '--allow-child-process', '--allow-worker', ...quiet]
242
272
  .filter((one) => one !== '')
243
273
  .join(' ')
244
274
  }
@@ -267,6 +297,8 @@ interface Serving {
267
297
  parked: Ran[]
268
298
  replaying: Recorded[]
269
299
  handed: number
300
+ readonly known: Map<string, Answer>
301
+ asking?: Order
270
302
  last?: Ran
271
303
  stop?: () => void
272
304
  watch?: ReturnType<typeof setInterval>
@@ -282,6 +314,12 @@ const lost = (of: string): Ran => ({ there: true, ok: false, code: -1, out: '',
282
314
 
283
315
  const told = (one: Order): string => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null])
284
316
 
317
+ const READS: ReadonlySet<string> = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order))
318
+
319
+ function kept(one: Serving, asked: Order, held: Answer): void {
320
+ if (READS.has(asked.order) && held.answered !== 'refused') one.known.set(told(asked), held)
321
+ }
322
+
285
323
  const plainly = (one: Order): string => one.order + ' ' + (one.at === '' ? '.' : one.at)
286
324
 
287
325
  const outside = (root: string, at: string): boolean => {
@@ -328,10 +366,17 @@ function watching(one: Serving): void {
328
366
  continue
329
367
  }
330
368
  const asked: Order = { ...order, at: relative(one.root, order.at) }
369
+ const known = one.known.get(told(asked))
370
+ if (known !== undefined) {
371
+ reply(one.dir, short, known)
372
+ continue
373
+ }
374
+ if (!READS.has(asked.order)) one.known.clear()
331
375
  const due = one.replaying.shift()
332
376
  if (due !== undefined) {
333
377
  if (told(due.order) === told(asked)) {
334
378
  reply(one.dir, short, due.answered)
379
+ kept(one, asked, due.answered)
335
380
  continue
336
381
  }
337
382
  one.replaying = []
@@ -343,6 +388,7 @@ function watching(one: Serving): void {
343
388
  continue
344
389
  }
345
390
  one.pending = short
391
+ one.asking = asked
346
392
  hand(one, still(one, [asked]))
347
393
  }
348
394
  }, TICK)
@@ -369,15 +415,15 @@ function next(one: Serving, within?: number): Promise<Ran> {
369
415
  })
370
416
  }
371
417
 
372
- function begun(word: string, args: readonly string[], root: string, hears?: string, called?: string, recording: readonly Recorded[] = []): Serving {
418
+ function begun(from: Started, word: string, args: readonly string[], root: string, hears?: string, called?: string, recording: readonly Recorded[] = []): Serving {
373
419
  const of = called ?? randomBytes(6).toString('hex')
374
420
  const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'))
375
421
  writeFileSync(join(dir, 'channel'), '', 'utf8')
376
- const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length }
377
- const child = spawn(execPath, [...CLI, word, ...args], {
378
- cwd: OWN,
422
+ const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() }
423
+ const child = spawn(execPath, [...from.words, word, ...args], {
424
+ cwd: from.own,
379
425
  shell: false,
380
- env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded() },
426
+ env: { ...env, [CHANNEL]: dir, [FOLDER]: root, [SIGNING]: signing(), NODE_OPTIONS: guarded(from.reads) },
381
427
  })
382
428
  one.stop = () => child.kill()
383
429
  child.stdout.on('data', (chunk: Buffer) => (one.out += chunk.toString()))
@@ -414,7 +460,9 @@ async function served(word: string, args: readonly string[], within?: number, ho
414
460
  if (recording === undefined) return lost(of)
415
461
  const root = how?.at ?? ''
416
462
  if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
417
- const again = begun(word, args, root, how?.hears, of, recording)
463
+ const from = startable()
464
+ if (typeof from === 'string') return nowhere(from)
465
+ const again = begun(from, word, args, root, how?.hears, of, recording)
418
466
  return counted(again, await next(again, within))
419
467
  }
420
468
  const answering = how?.answering
@@ -422,13 +470,16 @@ async function served(word: string, args: readonly string[], within?: number, ho
422
470
  const given = (how?.answered ?? [])[0]
423
471
  if (one.pending !== '' && given !== undefined) {
424
472
  reply(one.dir, one.pending, given)
473
+ if (one.asking !== undefined) kept(one, one.asking, given)
425
474
  one.pending = ''
426
475
  }
427
476
  return counted(one, await next(one, within))
428
477
  }
429
478
  const root = how?.at ?? ''
430
479
  if (root === '') return nowhere('nothing said which folder to run thread ' + word + UNPLACED)
431
- const fresh = begun(word, args, root, how?.hears)
480
+ const from = startable()
481
+ if (typeof from === 'string') return nowhere(from)
482
+ const fresh = begun(from, word, args, root, how?.hears)
432
483
  return counted(fresh, await next(fresh, within))
433
484
  }
434
485
 
@@ -132,9 +132,9 @@ function overFiles(dir) {
132
132
  reals.set(at, found);
133
133
  return found;
134
134
  },
135
- fetch: (at) => {
135
+ fetch: (at, words) => {
136
136
  try {
137
- return String(took({ order: 'fetch', at }).said);
137
+ return String(took(words === undefined ? { order: 'fetch', at } : { order: 'fetch', at, words }).said);
138
138
  }
139
139
  finally {
140
140
  unknown();
@@ -308,7 +308,8 @@ export const HERE = {
308
308
  held = disk.statSync(at, { throwIfNoEntry: false });
309
309
  }
310
310
  catch (thrown) {
311
- if (thrown.code === 'ERR_ACCESS_DENIED')
311
+ const code = thrown.code;
312
+ if (code === 'ERR_ACCESS_DENIED' || code === 'ENAMETOOLONG')
312
313
  return 'none';
313
314
  throw thrown;
314
315
  }
@@ -146,9 +146,9 @@ function overFiles(dir: string): Attached {
146
146
  reals.set(at, found)
147
147
  return found
148
148
  },
149
- fetch: (at) => {
149
+ fetch: (at, words) => {
150
150
  try {
151
- return String(took({ order: 'fetch', at }).said)
151
+ return String(took(words === undefined ? { order: 'fetch', at } : { order: 'fetch', at, words }).said)
152
152
  } finally {
153
153
  unknown()
154
154
  }
@@ -349,7 +349,8 @@ export const HERE: Attached = {
349
349
  try {
350
350
  held = disk.statSync(at, { throwIfNoEntry: false })
351
351
  } catch (thrown) {
352
- if ((thrown as NodeJS.ErrnoException).code === 'ERR_ACCESS_DENIED') return 'none'
352
+ const code = (thrown as NodeJS.ErrnoException).code
353
+ if (code === 'ERR_ACCESS_DENIED' || code === 'ENAMETOOLONG') return 'none'
353
354
  throw thrown
354
355
  }
355
356
  if (held === undefined) return 'none'
@@ -2,7 +2,6 @@ import { existsSync, readFileSync, writeFileSync } from '../disk.ts'
2
2
  import { valuesIn } from '../../core/rows.ts'
3
3
  import { dirname, join, relative } from 'node:path'
4
4
  import { argv, stderr, stdout } from 'node:process'
5
- import { fileURLToPath } from 'node:url'
6
5
 
7
6
  import ts from 'typescript'
8
7
 
@@ -48,7 +48,7 @@ export interface Answering {
48
48
  readonly real: (at: string) => string;
49
49
  }
50
50
  export interface Attached extends Answering {
51
- readonly fetch: (at: string) => string;
51
+ readonly fetch: (at: string, words?: readonly string[]) => string;
52
52
  readonly run: (at: string, words: readonly string[]) => Ran;
53
53
  }
54
54
  export declare const ORDERS: {
@@ -127,7 +127,7 @@ export interface Answering {
127
127
 
128
128
  export interface Attached extends Answering {
129
129
 
130
- readonly fetch: (at: string) => string
130
+ readonly fetch: (at: string, words?: readonly string[]) => string
131
131
 
132
132
  readonly run: (at: string, words: readonly string[]) => Ran
133
133
  }
@@ -1,8 +1,7 @@
1
1
  import { starts } from '../../integrations/cli.ts'
2
2
  import { createHash } from 'node:crypto'
3
- import { dirname, join } from 'node:path'
3
+ import { join } from 'node:path'
4
4
  import { argv, execPath, stderr, stdout } from 'node:process'
5
- import { fileURLToPath } from 'node:url'
6
5
  import { descend } from '../../core/descend.ts'
7
6
  import { attached, existsSync, here, readdirSync, readFileSync, rmSync, writeFileSync } from '../../integrations/disk.ts'
8
7
  import { LOADER, exit } from '../../core/invoked.ts'
@@ -3,7 +3,6 @@ import { existsSync, readFileSync } from '../../integrations/disk.ts'
3
3
  import { valuesIn } from '../../core/rows.ts'
4
4
  import { join } from 'node:path'
5
5
  import { argv, env, execPath, stderr, stdout } from 'node:process'
6
- import { pathToFileURL } from 'node:url'
7
6
  import { CLI, exit } from '../../core/invoked.ts'
8
7
  import { THING as TYPED, named } from '../../core/scope.ts'
9
8
  import { findTree, inTree } from '../../core/tree.ts'
@@ -4,12 +4,6 @@ import tseslint from 'typescript-eslint';
4
4
  import { THING } from '../core/scope.js';
5
5
  import { CONSTRAINTS, shown } from './constraint.js';
6
6
  import { NOT_WRITTEN, governs, subject, reads, visits, load, ROOT, rules as appRules, slopnatorName, } from './slopnator.js';
7
- const PROBE = {
8
- filename: 'probe.ts',
9
- text: '',
10
- ancestors: () => [],
11
- comments: () => [],
12
- };
13
7
  export function ruleMessage(rule) {
14
8
  const cites = (rule.cites ?? []).map((cite) => `${cite.on} — ${cite.said}`).join(' · ');
15
9
  return cites === '' ? `${rule.bans} — ${rule.because}. Instead: ${rule.instead}.` : `${rule.bans} — ${rule.because}. Instead: ${rule.instead}. (${cites})`;
@@ -1,5 +1,4 @@
1
1
  import { starts } from '../integrations/cli.ts'
2
- import { fileURLToPath } from 'node:url'
3
2
 
4
3
  import { Linter, type Rule } from 'eslint'
5
4
  import tseslint from 'typescript-eslint'
@@ -20,15 +19,6 @@ import {
20
19
  type SlopnatorContext,
21
20
  } from './slopnator.ts'
22
21
 
23
-
24
-
25
- const PROBE: SlopnatorContext = {
26
- filename: 'probe.ts',
27
- text: '',
28
- ancestors: () => [],
29
- comments: () => [],
30
- }
31
-
32
22
  export function ruleMessage(rule: Constraint): string {
33
23
  const cites = (rule.cites ?? []).map((cite) => `${cite.on} — ${cite.said}`).join(' · ')
34
24
  return cites === '' ? `${rule.bans} — ${rule.because}. Instead: ${rule.instead}.` : `${rule.bans} — ${rule.because}. Instead: ${rule.instead}. (${cites})`
@@ -8,7 +8,7 @@ import { invoked } from '../core/invoked.ts'
8
8
 
9
9
  import { listBounds, quoted, withSeed } from '../core/rows.ts'
10
10
 
11
- import { CONSTRAINTS, type Constraint, type Proof, type Sourced } from './constraint.ts'
11
+ import { CONSTRAINTS, type Constraint, type Proof } from './constraint.ts'
12
12
  import { findTree, inTree } from '../core/tree.ts'
13
13
 
14
14
  export const NOT_WRITTEN = Symbol.for('thread.slopnator.not_written')
@@ -2,8 +2,6 @@ import { readFileSync } from '../../integrations/disk.js';
2
2
  import { resolve } from 'node:path';
3
3
  import { atsIn } from './sl_unthreaded.js';
4
4
  import { reached } from '../../core/descend.js';
5
- import { findTree } from '../../core/tree.js';
6
- const ROOT = `${findTree().at}/`;
7
5
  export function derivations(root) {
8
6
  const found = [];
9
7
  for (const at of reached()) {
@@ -4,9 +4,6 @@ import { resolve } from 'node:path'
4
4
  import type { Slopnator } from '../slopnator.ts'
5
5
  import { atsIn } from './sl_unthreaded.ts'
6
6
  import { reached } from '../../core/descend.ts'
7
- import { findTree } from '../../core/tree.ts'
8
-
9
- const ROOT = `${findTree().at}/`
10
7
 
11
8
  export interface Derivation {
12
9
  readonly at: string
@@ -7,7 +7,6 @@ const TREE = TREE_AT.name;
7
7
  const THREAD_AT = `${TREE_AT.name}/thread.ts`;
8
8
  const FOLLOWABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/;
9
9
  const LISTS = new Set(LINEAGE);
10
- let threaders;
11
10
  let tree;
12
11
  const parsed = new Map();
13
12
  const broke = new Map();
@@ -233,7 +232,6 @@ export function index() {
233
232
  export function forget() {
234
233
  parsed.clear();
235
234
  broke.clear();
236
- threaders = undefined;
237
235
  tree = undefined;
238
236
  forgetDescent();
239
237
  }
@@ -1,6 +1,3 @@
1
- import { readFileSync } from '../../integrations/disk.ts'
2
- import { resolve } from 'node:path'
3
-
4
1
  import { parser } from 'typescript-eslint'
5
2
 
6
3
  import type { Slopnator } from '../slopnator.ts'
@@ -15,7 +12,6 @@ const THREAD_AT = `${TREE_AT.name}/thread.ts`
15
12
  const FOLLOWABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/
16
13
  const LISTS: ReadonlySet<string> = new Set(LINEAGE)
17
14
 
18
- let threaders: ReadonlySet<string> | undefined
19
15
  let tree: Index | undefined
20
16
  const parsed = new Map<string, readonly Reach[]>()
21
17
  const broke = new Map<string, string>()
@@ -295,7 +291,6 @@ export function index(): Index {
295
291
  export function forget(): void {
296
292
  parsed.clear()
297
293
  broke.clear()
298
- threaders = undefined
299
294
  tree = undefined
300
295
  forgetDescent()
301
296
  }
@@ -2,7 +2,6 @@ import { starts } from '../integrations/cli.ts'
2
2
  import { attached, existsSync, readFileSync, readdirSync } from '../integrations/disk.ts'
3
3
  import { join } from 'node:path'
4
4
  import { argv, stdout } from 'node:process'
5
- import { pathToFileURL } from 'node:url'
6
5
 
7
6
  import { runChecker, type Finding } from '../core/finding.ts'
8
7
  import { exit, invoked } from '../core/invoked.ts'
@@ -11,7 +10,6 @@ import { parser } from 'typescript-eslint'
11
10
 
12
11
  import { CONSTRAINTS, type Constraint, type Proof } from './constraint.ts'
13
12
  import { NOT_WRITTEN, ROOT, derivedHere, governs, load, proofsOf, reads, rules, slopnatorName, subject, sweeps, visits, type Reading, type Slopnator, type Spoken, type Visiting } from './slopnator.ts'
14
- import { ruleMessage } from './lint.ts'
15
13
  import { crossed, deriveTrials, runTrials } from './generators/trial.ts'
16
14
 
17
15
  const PARSEABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js|jsx)$/
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
2
- import { dirname, resolve } from 'node:path'
2
+ import { resolve } from 'node:path'
3
3
  import { argv, stderr, stdout } from 'node:process'
4
4
 
5
5
  import { exit } from '../../core/invoked.ts'