@prestack/thread 0.1.6 → 0.1.8
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.
- package/CHANGELOG.md +174 -0
- package/dist/core/attach.js +40 -6
- package/dist/core/attach.ts +38 -6
- package/dist/core/descend.js +13 -17
- package/dist/core/descend.ts +13 -15
- package/dist/integrations/attach.js +5 -3
- package/dist/integrations/attach.ts +8 -4
- package/dist/integrations/cli.js +18 -2
- package/dist/integrations/cli.ts +19 -2
- package/dist/integrations/disk.js +4 -3
- package/dist/integrations/disk.ts +4 -3
- package/dist/integrations/generators/vault.ts +0 -1
- package/dist/integrations/target.d.ts +1 -1
- package/dist/integrations/target.ts +1 -1
- package/dist/integrity/generators/gate.ts +1 -2
- package/dist/integrity/generators/publish.ts +0 -1
- package/dist/integrity/lint.js +0 -6
- package/dist/integrity/lint.ts +0 -10
- package/dist/integrity/slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +0 -2
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -3
- package/dist/integrity/slopnators/sl_stale_path.js +10 -3
- package/dist/integrity/slopnators/sl_stale_path.ts +10 -3
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +8 -5
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +12 -5
- package/dist/integrity/slopnators/sl_unlisted_program.js +7 -1
- package/dist/integrity/slopnators/sl_unlisted_program.ts +7 -1
- package/dist/integrity/slopnators/sl_unthreaded.js +0 -2
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -5
- package/dist/integrity/sweep.ts +0 -2
- package/dist/knowledge/attach.js +1 -18
- package/dist/knowledge/attach.ts +1 -19
- package/dist/knowledge/generators/docsgen.ts +1 -1
- package/dist/operations/generators/capability.js +20 -9
- package/dist/operations/generators/capability.ts +19 -12
- package/dist/operations/generators/compile.js +41 -28
- package/dist/operations/generators/compile.ts +48 -33
- package/dist/operations/generators/permits.js +6 -3
- package/dist/operations/generators/permits.ts +7 -5
- package/dist/operations/generators/trial.ts +1 -1
- 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
|
-
|
|
40
|
-
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
package/dist/integrations/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ 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';
|
|
9
|
+
import { CALL_PREFIX, LOCKFILES, ORDERS, PACKAGER, REACH, managerOf, programOf, unlisted } from '../integrations/target.js';
|
|
10
10
|
import { CLI } from '../core/invoked.js';
|
|
11
11
|
import { valuesIn } from '../core/rows.js';
|
|
12
12
|
import { THING } from '../core/scope.js';
|
|
@@ -201,6 +201,11 @@ const still = (one, asks) => ({ there: true, ok: false, code: -1, out: '', err:
|
|
|
201
201
|
const nowhere = (why) => ({ there: false, ok: false, code: 1, out: '', err: '', killed: null, why });
|
|
202
202
|
const lost = (of) => ({ there: true, ok: false, code: -1, out: '', err: '', killed: null, why: '', of, lost: of });
|
|
203
203
|
const told = (one) => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null]);
|
|
204
|
+
const READS = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order));
|
|
205
|
+
function kept(one, asked, held) {
|
|
206
|
+
if (READS.has(asked.order) && held.answered !== 'refused')
|
|
207
|
+
one.known.set(told(asked), held);
|
|
208
|
+
}
|
|
204
209
|
const plainly = (one) => one.order + ' ' + (one.at === '' ? '.' : one.at);
|
|
205
210
|
const outside = (root, at) => {
|
|
206
211
|
const step = relative(root, at);
|
|
@@ -248,10 +253,18 @@ function watching(one) {
|
|
|
248
253
|
continue;
|
|
249
254
|
}
|
|
250
255
|
const asked = { ...order, at: relative(one.root, order.at) };
|
|
256
|
+
const known = one.known.get(told(asked));
|
|
257
|
+
if (known !== undefined) {
|
|
258
|
+
reply(one.dir, short, known);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!READS.has(asked.order))
|
|
262
|
+
one.known.clear();
|
|
251
263
|
const due = one.replaying.shift();
|
|
252
264
|
if (due !== undefined) {
|
|
253
265
|
if (told(due.order) === told(asked)) {
|
|
254
266
|
reply(one.dir, short, due.answered);
|
|
267
|
+
kept(one, asked, due.answered);
|
|
255
268
|
continue;
|
|
256
269
|
}
|
|
257
270
|
one.replaying = [];
|
|
@@ -263,6 +276,7 @@ function watching(one) {
|
|
|
263
276
|
continue;
|
|
264
277
|
}
|
|
265
278
|
one.pending = short;
|
|
279
|
+
one.asking = asked;
|
|
266
280
|
hand(one, still(one, [asked]));
|
|
267
281
|
}
|
|
268
282
|
}, TICK);
|
|
@@ -295,7 +309,7 @@ function begun(word, args, root, hears, called, recording = []) {
|
|
|
295
309
|
const of = called ?? randomBytes(6).toString('hex');
|
|
296
310
|
const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'));
|
|
297
311
|
writeFileSync(join(dir, 'channel'), '', 'utf8');
|
|
298
|
-
const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length };
|
|
312
|
+
const one = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() };
|
|
299
313
|
const child = spawn(execPath, [...CLI, word, ...args], {
|
|
300
314
|
cwd: OWN,
|
|
301
315
|
shell: false,
|
|
@@ -346,6 +360,8 @@ async function served(word, args, within, how) {
|
|
|
346
360
|
const given = (how?.answered ?? [])[0];
|
|
347
361
|
if (one.pending !== '' && given !== undefined) {
|
|
348
362
|
reply(one.dir, one.pending, given);
|
|
363
|
+
if (one.asking !== undefined)
|
|
364
|
+
kept(one, one.asking, given);
|
|
349
365
|
one.pending = '';
|
|
350
366
|
}
|
|
351
367
|
return counted(one, await next(one, within));
|
package/dist/integrations/cli.ts
CHANGED
|
@@ -8,7 +8,7 @@ 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'
|
|
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
12
|
import { CLI } from '../core/invoked.ts'
|
|
13
13
|
import { valuesIn } from '../core/rows.ts'
|
|
14
14
|
import { THING } from '../core/scope.ts'
|
|
@@ -267,6 +267,8 @@ interface Serving {
|
|
|
267
267
|
parked: Ran[]
|
|
268
268
|
replaying: Recorded[]
|
|
269
269
|
handed: number
|
|
270
|
+
readonly known: Map<string, Answer>
|
|
271
|
+
asking?: Order
|
|
270
272
|
last?: Ran
|
|
271
273
|
stop?: () => void
|
|
272
274
|
watch?: ReturnType<typeof setInterval>
|
|
@@ -282,6 +284,12 @@ const lost = (of: string): Ran => ({ there: true, ok: false, code: -1, out: '',
|
|
|
282
284
|
|
|
283
285
|
const told = (one: Order): string => JSON.stringify([one.order, one.at, one.said ?? null, one.mode ?? null, one.words ?? null])
|
|
284
286
|
|
|
287
|
+
const READS: ReadonlySet<string> = new Set(Object.entries(ORDERS).filter(([order, answer]) => order === answer).map(([order]) => order))
|
|
288
|
+
|
|
289
|
+
function kept(one: Serving, asked: Order, held: Answer): void {
|
|
290
|
+
if (READS.has(asked.order) && held.answered !== 'refused') one.known.set(told(asked), held)
|
|
291
|
+
}
|
|
292
|
+
|
|
285
293
|
const plainly = (one: Order): string => one.order + ' ' + (one.at === '' ? '.' : one.at)
|
|
286
294
|
|
|
287
295
|
const outside = (root: string, at: string): boolean => {
|
|
@@ -328,10 +336,17 @@ function watching(one: Serving): void {
|
|
|
328
336
|
continue
|
|
329
337
|
}
|
|
330
338
|
const asked: Order = { ...order, at: relative(one.root, order.at) }
|
|
339
|
+
const known = one.known.get(told(asked))
|
|
340
|
+
if (known !== undefined) {
|
|
341
|
+
reply(one.dir, short, known)
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
if (!READS.has(asked.order)) one.known.clear()
|
|
331
345
|
const due = one.replaying.shift()
|
|
332
346
|
if (due !== undefined) {
|
|
333
347
|
if (told(due.order) === told(asked)) {
|
|
334
348
|
reply(one.dir, short, due.answered)
|
|
349
|
+
kept(one, asked, due.answered)
|
|
335
350
|
continue
|
|
336
351
|
}
|
|
337
352
|
one.replaying = []
|
|
@@ -343,6 +358,7 @@ function watching(one: Serving): void {
|
|
|
343
358
|
continue
|
|
344
359
|
}
|
|
345
360
|
one.pending = short
|
|
361
|
+
one.asking = asked
|
|
346
362
|
hand(one, still(one, [asked]))
|
|
347
363
|
}
|
|
348
364
|
}, TICK)
|
|
@@ -373,7 +389,7 @@ function begun(word: string, args: readonly string[], root: string, hears?: stri
|
|
|
373
389
|
const of = called ?? randomBytes(6).toString('hex')
|
|
374
390
|
const dir = join(tmpdir(), SERVED, randomBytes(6).toString('hex'))
|
|
375
391
|
writeFileSync(join(dir, 'channel'), '', 'utf8')
|
|
376
|
-
const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length }
|
|
392
|
+
const one: Serving = { of, dir, root, out: '', err: '', why: '', pending: '', waiting: [], parked: [], replaying: [...recording], handed: recording.length, known: new Map() }
|
|
377
393
|
const child = spawn(execPath, [...CLI, word, ...args], {
|
|
378
394
|
cwd: OWN,
|
|
379
395
|
shell: false,
|
|
@@ -422,6 +438,7 @@ async function served(word: string, args: readonly string[], within?: number, ho
|
|
|
422
438
|
const given = (how?.answered ?? [])[0]
|
|
423
439
|
if (one.pending !== '' && given !== undefined) {
|
|
424
440
|
reply(one.dir, one.pending, given)
|
|
441
|
+
if (one.asking !== undefined) kept(one, one.asking, given)
|
|
425
442
|
one.pending = ''
|
|
426
443
|
}
|
|
427
444
|
return counted(one, await next(one, within))
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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'
|
package/dist/integrity/lint.js
CHANGED
|
@@ -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})`;
|
package/dist/integrity/lint.ts
CHANGED
|
@@ -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
|
|
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
|
|
@@ -6,10 +6,14 @@ const ROOT = findTree().at;
|
|
|
6
6
|
const TREE = findTree().name;
|
|
7
7
|
const canonical = (at) => (at.startsWith('threads/') ? `${TREE}/${at.slice('threads/'.length)}` : at);
|
|
8
8
|
const NAMES_A_FILE = /\.[a-z]{1,5}$/;
|
|
9
|
-
const
|
|
9
|
+
const known = new WeakMap();
|
|
10
|
+
const derived = () => {
|
|
10
11
|
const found = new Set();
|
|
11
12
|
try {
|
|
12
13
|
const pass = descend();
|
|
14
|
+
const cached = known.get(pass);
|
|
15
|
+
if (cached !== undefined)
|
|
16
|
+
return cached;
|
|
13
17
|
for (const [at, held] of pass.threaded) {
|
|
14
18
|
if (held.some((one) => one.row.by !== undefined))
|
|
15
19
|
found.add(at);
|
|
@@ -17,11 +21,12 @@ const derived = (() => {
|
|
|
17
21
|
for (const one of pass.derived.values())
|
|
18
22
|
if (one.at !== undefined)
|
|
19
23
|
found.add(one.at);
|
|
24
|
+
known.set(pass, found);
|
|
20
25
|
}
|
|
21
26
|
catch {
|
|
22
27
|
}
|
|
23
28
|
return found;
|
|
24
|
-
}
|
|
29
|
+
};
|
|
25
30
|
const ours = () => {
|
|
26
31
|
try {
|
|
27
32
|
return new Set(readdirSync(ROOT).filter((one) => one !== 'node_modules' && statSync(resolve(ROOT, one), { throwIfNoEntry: false })?.isDirectory() === true));
|
|
@@ -69,7 +74,7 @@ export const slopnator = {
|
|
|
69
74
|
return false;
|
|
70
75
|
if (existsSync(resolve(ROOT, path)))
|
|
71
76
|
return false;
|
|
72
|
-
if (derived.has(path))
|
|
77
|
+
if (derived().has(path))
|
|
73
78
|
return false;
|
|
74
79
|
return `\`${value}\` is not on disk`;
|
|
75
80
|
},
|
|
@@ -137,6 +142,8 @@ export const slopnator = {
|
|
|
137
142
|
"const said = 'a sentence about packages/core/integrity/src/constraint.ts and nothing else'",
|
|
138
143
|
{
|
|
139
144
|
files: [
|
|
145
|
+
{ at: 'thread/core/thread.ts', source: 'export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly by?: string\n readonly runs?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n' },
|
|
146
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n derives: [\n {\n id: 'layout',\n at: 'app/layout.tsx',\n by: 'thread routes',\n runs: 'thread routes --check',\n },\n ],\n}\n" },
|
|
140
147
|
{ at: 'app/_probe_there.tsx', source: 'export const THERE = 1\n' },
|
|
141
148
|
{ at: 'probe.tsx', source: "const at = 'app/layout.tsx'\n" },
|
|
142
149
|
],
|
|
@@ -10,18 +10,23 @@ const TREE = findTree().name
|
|
|
10
10
|
const canonical = (at: string): string => (at.startsWith('threads/') ? `${TREE}/${at.slice('threads/'.length)}` : at)
|
|
11
11
|
const NAMES_A_FILE = /\.[a-z]{1,5}$/
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const known = new WeakMap<object, ReadonlySet<string>>()
|
|
14
|
+
|
|
15
|
+
const derived = (): ReadonlySet<string> => {
|
|
14
16
|
const found = new Set<string>()
|
|
15
17
|
try {
|
|
16
18
|
const pass = descend()
|
|
19
|
+
const cached = known.get(pass)
|
|
20
|
+
if (cached !== undefined) return cached
|
|
17
21
|
for (const [at, held] of pass.threaded) {
|
|
18
22
|
if (held.some((one) => one.row.by !== undefined)) found.add(at)
|
|
19
23
|
}
|
|
20
24
|
for (const one of pass.derived.values()) if (one.at !== undefined) found.add(one.at)
|
|
25
|
+
known.set(pass, found)
|
|
21
26
|
} catch {
|
|
22
27
|
}
|
|
23
28
|
return found
|
|
24
|
-
}
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
const ours = (): ReadonlySet<string> => {
|
|
27
32
|
try {
|
|
@@ -79,7 +84,7 @@ export const slopnator: Slopnator = {
|
|
|
79
84
|
|
|
80
85
|
if (!NAMES_A_FILE.test(path)) return false
|
|
81
86
|
if (existsSync(resolve(ROOT, path))) return false
|
|
82
|
-
if (derived.has(path)) return false
|
|
87
|
+
if (derived().has(path)) return false
|
|
83
88
|
return `\`${value}\` is not on disk`
|
|
84
89
|
},
|
|
85
90
|
proves: {
|
|
@@ -146,6 +151,8 @@ export const slopnator: Slopnator = {
|
|
|
146
151
|
"const said = 'a sentence about packages/core/integrity/src/constraint.ts and nothing else'",
|
|
147
152
|
{
|
|
148
153
|
files: [
|
|
154
|
+
{ at: 'thread/core/thread.ts', source: 'export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly by?: string\n readonly runs?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n' },
|
|
155
|
+
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n derives: [\n {\n id: 'layout',\n at: 'app/layout.tsx',\n by: 'thread routes',\n runs: 'thread routes --check',\n },\n ],\n}\n" },
|
|
149
156
|
{ at: 'app/_probe_there.tsx', source: 'export const THERE = 1\n' },
|
|
150
157
|
{ at: 'probe.tsx', source: "const at = 'app/layout.tsx'\n" },
|
|
151
158
|
],
|
|
@@ -6,7 +6,11 @@ import { packageOf } from '../../core/attach.js';
|
|
|
6
6
|
import { PREFIXES } from '../../core/thread.js';
|
|
7
7
|
import { HOLDS, OPENS } from '../../integrations/target.js';
|
|
8
8
|
const FROM_SOURCE = import.meta.url.endsWith('.ts');
|
|
9
|
-
const
|
|
9
|
+
const SHAPE = 'operations/operations';
|
|
10
|
+
const OPENED_AT = fileURLToPath(new URL('../../' + SHAPE + (FROM_SOURCE ? '.ts' : '.d.ts'), import.meta.url));
|
|
11
|
+
const FRAMEWORK = 'thread';
|
|
12
|
+
const STATED_AT = resolve(ROOT, FRAMEWORK, SHAPE + '.ts');
|
|
13
|
+
const SHAPES = [...new Set([STATED_AT, OPENED_AT])];
|
|
10
14
|
const HANDED = 'Opened';
|
|
11
15
|
const EVERY = '';
|
|
12
16
|
const IMPORTED = 'import ';
|
|
@@ -147,15 +151,14 @@ function compiled() {
|
|
|
147
151
|
return built;
|
|
148
152
|
const options = configured().options;
|
|
149
153
|
const roots = [THREAD_AT, ...sources()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one));
|
|
150
|
-
const program = ts.createProgram([...new Set([
|
|
154
|
+
const program = ts.createProgram([...new Set([...SHAPES, ...roots])], options, hosting(options));
|
|
151
155
|
const checker = program.getTypeChecker();
|
|
152
156
|
const hands = new Map();
|
|
153
157
|
const own = (symbol) => (symbol.getDeclarations() ?? []).some((one) => {
|
|
154
158
|
const from = one.getSourceFile();
|
|
155
159
|
return !program.isSourceFileDefaultLibrary(from) && !program.isSourceFileFromExternalLibrary(from);
|
|
156
160
|
});
|
|
157
|
-
const
|
|
158
|
-
for (const statement of shape?.statements ?? []) {
|
|
161
|
+
for (const statement of SHAPES.flatMap((one) => program.getSourceFile(one)?.statements ?? [])) {
|
|
159
162
|
if (!ts.isTypeAliasDeclaration(statement) || statement.name.text !== HANDED)
|
|
160
163
|
continue;
|
|
161
164
|
const alias = checker.getSymbolAtLocation(statement.name);
|
|
@@ -376,7 +379,7 @@ export function judging(at, wanted, closing) {
|
|
|
376
379
|
if (!SOURCE.test(path))
|
|
377
380
|
return false;
|
|
378
381
|
const held = compiled();
|
|
379
|
-
if (held.hands.size === 0 && resolve(ROOT, path)
|
|
382
|
+
if (held.hands.size === 0 && SHAPES.includes(resolve(ROOT, path))) {
|
|
380
383
|
return 'no type called ' + HANDED + ' can be read here, so nothing can tell what an operation was opened with from any other value, and a call on it outside an operation goes unmeasured';
|
|
381
384
|
}
|
|
382
385
|
const found = judged(path, held, operational(held.of, path), wanted);
|
|
@@ -12,7 +12,15 @@ import type { Slopnator, SlopnatorContext } from '../slopnator.ts'
|
|
|
12
12
|
|
|
13
13
|
const FROM_SOURCE = import.meta.url.endsWith('.ts')
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const SHAPE = 'operations/operations'
|
|
16
|
+
|
|
17
|
+
const OPENED_AT = fileURLToPath(new URL('../../' + SHAPE + (FROM_SOURCE ? '.ts' : '.d.ts'), import.meta.url))
|
|
18
|
+
|
|
19
|
+
const FRAMEWORK = 'thread'
|
|
20
|
+
|
|
21
|
+
const STATED_AT = resolve(ROOT, FRAMEWORK, SHAPE + '.ts')
|
|
22
|
+
|
|
23
|
+
const SHAPES = [...new Set([STATED_AT, OPENED_AT])]
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
const HANDED = 'Opened'
|
|
@@ -184,7 +192,7 @@ function compiled(): Built {
|
|
|
184
192
|
if (built !== undefined && built.of === pass) return built
|
|
185
193
|
const options = configured().options
|
|
186
194
|
const roots = [THREAD_AT, ...sources()].filter((one) => SOURCE.test(one)).map((one) => resolve(ROOT, one))
|
|
187
|
-
const program = ts.createProgram([...new Set([
|
|
195
|
+
const program = ts.createProgram([...new Set([...SHAPES, ...roots])], options, hosting(options))
|
|
188
196
|
const checker = program.getTypeChecker()
|
|
189
197
|
const hands = new Map<ts.Symbol, string>()
|
|
190
198
|
const own = (symbol: ts.Symbol): boolean =>
|
|
@@ -192,8 +200,7 @@ function compiled(): Built {
|
|
|
192
200
|
const from = one.getSourceFile()
|
|
193
201
|
return !program.isSourceFileDefaultLibrary(from) && !program.isSourceFileFromExternalLibrary(from)
|
|
194
202
|
})
|
|
195
|
-
const
|
|
196
|
-
for (const statement of shape?.statements ?? []) {
|
|
203
|
+
for (const statement of SHAPES.flatMap((one) => program.getSourceFile(one)?.statements ?? [])) {
|
|
197
204
|
if (!ts.isTypeAliasDeclaration(statement) || statement.name.text !== HANDED) continue
|
|
198
205
|
const alias = checker.getSymbolAtLocation(statement.name)
|
|
199
206
|
if (alias !== undefined) hands.set(alias, EVERY)
|
|
@@ -400,7 +407,7 @@ export function judging(at: SlopnatorContext, wanted: string, closing: string):
|
|
|
400
407
|
const path = here(at.filename)
|
|
401
408
|
if (!SOURCE.test(path)) return false
|
|
402
409
|
const held = compiled()
|
|
403
|
-
if (held.hands.size === 0 && resolve(ROOT, path)
|
|
410
|
+
if (held.hands.size === 0 && SHAPES.includes(resolve(ROOT, path))) {
|
|
404
411
|
return 'no type called ' + HANDED + ' can be read here, so nothing can tell what an operation was opened with from any other value, and a call on it outside an operation goes unmeasured'
|
|
405
412
|
}
|
|
406
413
|
const found = judged(path, held, operational(held.of, path), wanted)
|
|
@@ -30,7 +30,13 @@ export const slopnator = {
|
|
|
30
30
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_bash', of: 'in_shell', describes: 'The shell on the machine a command runs on.', version: 1, takes: [], requires: [] }]",
|
|
31
31
|
],
|
|
32
32
|
right: [
|
|
33
|
-
|
|
33
|
+
{
|
|
34
|
+
files: [
|
|
35
|
+
{ at: 'node_modules/.bin/tsc', source: '' },
|
|
36
|
+
{ at: 'threads/_probe_targets.ts', source: "export const TARGETS: readonly Target[] = [{ id: 'cli_tsc', of: 'in_typescript', describes: 'The compiler this tree is checked and built with.', version: 1, takes: [], requires: [] }]\n" },
|
|
37
|
+
],
|
|
38
|
+
at: 'threads/_probe_targets.ts',
|
|
39
|
+
},
|
|
34
40
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_packager', of: 'in_packager', describes: 'The package manager the lockfile beside this tree picks.', version: 1, takes: [], requires: [] }]",
|
|
35
41
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_node', of: 'in_node', describes: 'The runtime this framework runs on.', version: 1, takes: [], requires: [] }]",
|
|
36
42
|
"export const TARGETS: readonly Target[] = [{ id: 'ht_registry', of: 'in_registry', describes: 'The registry every package goes to.', version: 1, takes: [], requires: [], address: 'https://registry.npmjs.org' }]",
|
|
@@ -31,7 +31,13 @@ export const slopnator: Slopnator = {
|
|
|
31
31
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_bash', of: 'in_shell', describes: 'The shell on the machine a command runs on.', version: 1, takes: [], requires: [] }]",
|
|
32
32
|
],
|
|
33
33
|
right: [
|
|
34
|
-
|
|
34
|
+
{
|
|
35
|
+
files: [
|
|
36
|
+
{ at: 'node_modules/.bin/tsc', source: '' },
|
|
37
|
+
{ at: 'threads/_probe_targets.ts', source: "export const TARGETS: readonly Target[] = [{ id: 'cli_tsc', of: 'in_typescript', describes: 'The compiler this tree is checked and built with.', version: 1, takes: [], requires: [] }]\n" },
|
|
38
|
+
],
|
|
39
|
+
at: 'threads/_probe_targets.ts',
|
|
40
|
+
},
|
|
35
41
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_packager', of: 'in_packager', describes: 'The package manager the lockfile beside this tree picks.', version: 1, takes: [], requires: [] }]",
|
|
36
42
|
"export const TARGETS: readonly Target[] = [{ id: 'cli_node', of: 'in_node', describes: 'The runtime this framework runs on.', version: 1, takes: [], requires: [] }]",
|
|
37
43
|
"export const TARGETS: readonly Target[] = [{ id: 'ht_registry', of: 'in_registry', describes: 'The registry every package goes to.', version: 1, takes: [], requires: [], address: 'https://registry.npmjs.org' }]",
|
|
@@ -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
|
}
|
package/dist/integrity/sweep.ts
CHANGED
|
@@ -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)$/
|