@prestack/thread 0.1.21 → 0.1.23

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 (138) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.js +6 -3
  4. package/dist/core/generators/commit.js +55 -3
  5. package/dist/core/generators/dev.d.ts +1 -0
  6. package/dist/core/generators/dev.js +69 -0
  7. package/dist/derivation/generators/build.d.ts +3 -1
  8. package/dist/derivation/generators/build.js +104 -22
  9. package/dist/derivation/generators/release.js +74 -25
  10. package/dist/derivation/release.js +3 -3
  11. package/dist/integrations/cli.d.ts +0 -1
  12. package/dist/integrations/cli.js +69 -11
  13. package/dist/integrations/declared.d.ts +2 -0
  14. package/dist/integrations/declared.js +6 -1
  15. package/dist/integrations/pglite.js +13 -3
  16. package/dist/integrations/target.d.ts +3 -0
  17. package/dist/integrity/attach.js +1 -5
  18. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  19. package/dist/operations/access.js +3 -2
  20. package/dist/operations/capabilities.d.ts +2 -0
  21. package/dist/operations/capabilities.js +2 -0
  22. package/dist/operations/generators/capability.js +237 -24
  23. package/dist/operations/generators/compile.js +150 -50
  24. package/dist/operations/generators/seed.d.ts +3 -0
  25. package/dist/operations/generators/seed.js +25 -3
  26. package/dist/operations/nodes.d.ts +2 -2
  27. package/package.json +1 -1
  28. package/dist/core/actor.ts +0 -21
  29. package/dist/core/attach.ts +0 -613
  30. package/dist/core/chain.ts +0 -145
  31. package/dist/core/condition.ts +0 -227
  32. package/dist/core/descend.ts +0 -1185
  33. package/dist/core/errors/catalogue.ts +0 -314
  34. package/dist/core/errors/classes.ts +0 -51
  35. package/dist/core/errors/error.ts +0 -152
  36. package/dist/core/errors/namespaces.ts +0 -19
  37. package/dist/core/finding.ts +0 -37
  38. package/dist/core/generators/commit.ts +0 -157
  39. package/dist/core/init.ts +0 -222
  40. package/dist/core/invoked.ts +0 -52
  41. package/dist/core/round.ts +0 -74
  42. package/dist/core/rows.ts +0 -506
  43. package/dist/core/scope.ts +0 -20
  44. package/dist/core/thread.ts +0 -29
  45. package/dist/core/tree.ts +0 -90
  46. package/dist/core/types.ts +0 -351
  47. package/dist/derivation/attach.ts +0 -57
  48. package/dist/derivation/generators/build.ts +0 -371
  49. package/dist/derivation/generators/check.ts +0 -118
  50. package/dist/derivation/generators/determinism.ts +0 -145
  51. package/dist/derivation/generators/release.ts +0 -236
  52. package/dist/derivation/release.ts +0 -43
  53. package/dist/integrations/attach.ts +0 -187
  54. package/dist/integrations/cli.ts +0 -606
  55. package/dist/integrations/declared.ts +0 -34
  56. package/dist/integrations/disk.ts +0 -466
  57. package/dist/integrations/generators/read.ts +0 -127
  58. package/dist/integrations/generators/vault.ts +0 -210
  59. package/dist/integrations/https.ts +0 -216
  60. package/dist/integrations/pglite.ts +0 -206
  61. package/dist/integrations/secret.ts +0 -54
  62. package/dist/integrations/target.ts +0 -672
  63. package/dist/integrations/vault.ts +0 -148
  64. package/dist/integrity/attach.ts +0 -150
  65. package/dist/integrity/check-threads.ts +0 -166
  66. package/dist/integrity/constraint.ts +0 -1246
  67. package/dist/integrity/generators/compiler.ts +0 -138
  68. package/dist/integrity/generators/gate.ts +0 -227
  69. package/dist/integrity/generators/issues.ts +0 -160
  70. package/dist/integrity/generators/publish.ts +0 -233
  71. package/dist/integrity/generators/trial.ts +0 -359
  72. package/dist/integrity/lint.ts +0 -214
  73. package/dist/integrity/lsp/bin.ts +0 -44
  74. package/dist/integrity/lsp/diagnostics.ts +0 -92
  75. package/dist/integrity/lsp/findings.ts +0 -63
  76. package/dist/integrity/lsp/lint.ts +0 -124
  77. package/dist/integrity/lsp/protocol.ts +0 -78
  78. package/dist/integrity/lsp/server.ts +0 -224
  79. package/dist/integrity/slopnator.ts +0 -324
  80. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  81. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  82. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  83. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  84. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  85. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  86. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  87. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  88. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  89. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  90. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  91. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  92. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  93. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  94. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  95. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  96. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  97. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  98. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  99. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  100. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  101. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  102. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  103. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  104. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  105. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  106. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  107. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  108. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  109. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  110. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  111. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  112. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  113. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  114. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  115. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  116. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  117. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  118. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  119. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  120. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  121. package/dist/integrity/sweep.ts +0 -395
  122. package/dist/knowledge/attach.ts +0 -327
  123. package/dist/knowledge/generators/docsgen.ts +0 -186
  124. package/dist/knowledge/generators/glossary.ts +0 -73
  125. package/dist/knowledge/glossary.ts +0 -6
  126. package/dist/operations/access.ts +0 -131
  127. package/dist/operations/attach.ts +0 -408
  128. package/dist/operations/capabilities.ts +0 -88
  129. package/dist/operations/dispatch.ts +0 -294
  130. package/dist/operations/error-results.ts +0 -31
  131. package/dist/operations/generators/capability.ts +0 -1054
  132. package/dist/operations/generators/compile.ts +0 -701
  133. package/dist/operations/generators/permits.ts +0 -229
  134. package/dist/operations/generators/seed.ts +0 -359
  135. package/dist/operations/generators/trial.ts +0 -666
  136. package/dist/operations/nodes.ts +0 -53
  137. package/dist/operations/operations.ts +0 -91
  138. package/dist/operations/result.ts +0 -28
@@ -1,81 +0,0 @@
1
- import { readFileSync } from '../../integrations/disk.ts'
2
- import { resolve } from 'node:path'
3
-
4
- import type { Sourced } from '../constraint.ts'
5
- import type { Slopnator } from '../slopnator.ts'
6
- import { findTree } from '../../core/tree.ts'
7
-
8
- const ROOT = findTree().at
9
-
10
- type Node = {
11
- type?: string
12
- key?: { type?: string; name?: string; value?: unknown }
13
- value?: { type?: string; value?: unknown }
14
- computed?: boolean
15
- }
16
-
17
- const keyOf = (property: Node): string | undefined => {
18
- if (property.computed === true) return undefined
19
- const key = property.key
20
- if (key?.type === 'Identifier') return key.name
21
- if (key?.type === 'Literal' && typeof key.value === 'string') return key.value
22
- return undefined
23
- }
24
-
25
- function scripts(): ReadonlySet<string> {
26
- try {
27
- const held = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')) as { scripts?: Record<string, string> }
28
- return new Set(Object.keys(held.scripts ?? {}))
29
- } catch {
30
- return new Set()
31
- }
32
- }
33
-
34
- const HAS: readonly Sourced[] = [
35
- { at: 'package.json', source: '{\n "scripts": {\n "typecheck": "tsc",\n "gate:generate": "node gate.mjs"\n }\n}\n' },
36
- ]
37
-
38
- export const slopnator: Slopnator = {
39
- on: ['Property'],
40
- holds: (node) => {
41
- const property = node as Node
42
- const key = keyOf(property)
43
- if (key !== 'runs' && key !== 'by') return false
44
-
45
- const held = property.value
46
- if (held?.type !== 'Literal' || typeof held.value !== 'string') return false
47
-
48
- const spoken = held.value.trim()
49
- if (!spoken.startsWith('pnpm ')) return false
50
-
51
- const named = spoken.replace(/^pnpm (run )?/, '').split(/\s+/)[0] ?? ''
52
- const known = scripts()
53
- if (known.size === 0 || known.has(named)) return false
54
-
55
- return `\`${key}: ${spoken}\` names the script \`${named}\`, and package.json has no such script`
56
- },
57
- proves: {
58
- wrong: [
59
- {
60
- files: [...HAS, { at: 'threads/_probe_command.ts', source: "const M = {\n runs: 'pnpm nope:check',\n}\n" }],
61
- at: 'threads/_probe_command.ts',
62
- },
63
- {
64
- files: [...HAS, { at: 'threads/_probe_command.ts', source: "const M = {\n by: 'pnpm also-not-a-script',\n}\n" }],
65
- at: 'threads/_probe_command.ts',
66
- },
67
- ],
68
- right: [
69
- {
70
- files: [...HAS, { at: 'threads/_probe_command.ts', source: "const M = {\n runs: 'pnpm typecheck',\n}\n" }],
71
- at: 'threads/_probe_command.ts',
72
- },
73
- {
74
- files: [...HAS, { at: 'threads/_probe_command.ts', source: "const M = {\n by: 'pnpm gate:generate',\n}\n" }],
75
- at: 'threads/_probe_command.ts',
76
- },
77
- "const M = {\n runs: 'node scripts/_generated/gate.mjs',\n}",
78
- "const M = {\n describes: 'A sentence mentioning pnpm nope:check without naming it as a command.',\n}",
79
- ],
80
- },
81
- }
@@ -1,63 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- interface Node {
4
- readonly type?: string
5
- readonly callee?: { readonly type?: string; readonly name?: string }
6
- readonly arguments?: readonly { readonly type?: string; readonly elements?: readonly { readonly type?: string }[] }[]
7
- }
8
-
9
- export const slopnator: Slopnator = {
10
- on: ['CallExpression'],
11
- holds: (node) => {
12
- const held = node as Node
13
- if (held.callee?.type !== 'Identifier' || held.callee.name !== 'only') return false
14
- if (held.arguments?.length !== 3) return false
15
- const keys = held.arguments[1]
16
- if (keys?.type !== 'ArrayExpression') return false
17
- const typed = (keys.elements ?? []).filter((one) => one?.type !== 'SpreadElement')
18
- if (typed.length === 0) return false
19
- return `it hands \`only\` ${String(typed.length)} key(s) written here, and the receipt prints its questions from somewhere else`
20
- },
21
- proves: {
22
- wrong: [
23
- {
24
- files: [
25
- {
26
- at: 'thread/integrations/generators/probe.ts',
27
- source: "only(piped, ['name', 'describes'], 'a thing')\n",
28
- },
29
- ],
30
- at: 'thread/integrations/generators/probe.ts',
31
- },
32
- {
33
- files: [
34
- {
35
- at: 'thread/integrations/generators/probe.ts',
36
- source: "only(one, ['stop', 'sets'], 'a stop')\n",
37
- },
38
- ],
39
- at: 'thread/integrations/generators/probe.ts',
40
- },
41
- ],
42
- right: [
43
- {
44
- files: [
45
- {
46
- at: 'thread/integrations/generators/probe.ts',
47
- source: "const TAKES = ASKS.map((one) => one.key)\nonly(piped, TAKES, 'a thing')\n",
48
- },
49
- ],
50
- at: 'thread/integrations/generators/probe.ts',
51
- },
52
- {
53
- files: [
54
- {
55
- at: 'thread/integrations/generators/probe.ts',
56
- source: "only(all, [...allowed, ...worded], 'a thing')\n",
57
- },
58
- ],
59
- at: 'thread/integrations/generators/probe.ts',
60
- },
61
- ],
62
- },
63
- }
@@ -1,25 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- import { HANDED_TO, judging } from './sl_undeclared_crossing.ts'
4
-
5
- const SAYS =
6
- 'Each of these uses what this app hands an operation, and an operation is the only thing handed one.'
7
-
8
- export const slopnator: Slopnator = {
9
- holds: (_node, at) => judging(at, HANDED_TO, SAYS),
10
- proves: {
11
- wrong: [
12
- {
13
- files: [
14
- { at: 'thread/operations/operations.ts', source: 'export interface Store {\n insert(row: unknown): Promise<void>\n}\n\nexport type Opened<R extends string = never> = { readonly [K in R]: Store }\n' },
15
- { at: 'threads/_probe_reach.ts', source: "import type { Store } from '../thread/operations/operations.ts'\n\nexport const seeded = async (kept: Store): Promise<void> => {\n await kept['insert']({})\n}\n" },
16
- ],
17
- at: 'threads/_probe_reach.ts',
18
- },
19
- ],
20
- right: [
21
- 'export const began = Date.now()',
22
- "import { join } from 'node:path'\n\nexport const at = join('a', 'b')",
23
- ],
24
- },
25
- }
@@ -1,70 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- import { descend, read, reached } from '../../core/descend.ts'
4
- import { rowsIn } from './sl_unthreaded.ts'
5
-
6
- const WRAPS = 'screen'
7
-
8
- const HANDS = 'dispatch'
9
-
10
- const ELEMENT = 'element'
11
-
12
- const COMPOSITION = 'composition'
13
-
14
- const nameOf = (id: string): string =>
15
- id.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
16
-
17
- function declared(): ReadonlySet<string> {
18
- const found = new Set<string>()
19
-
20
- descend()
21
-
22
- for (const at of reached()) {
23
- const text = read(at)
24
- if (text === undefined) continue
25
-
26
- let waiting: string[] = []
27
- for (const one of rowsIn(text)) {
28
- if (one.id === undefined) continue
29
- if (one.list === 'declares') {
30
- waiting.push(one.id)
31
- continue
32
- }
33
- if (one.list !== undefined) continue
34
- if (one.id === ELEMENT || one.id === COMPOSITION) for (const id of waiting) found.add(nameOf(id))
35
- waiting = []
36
- }
37
- }
38
-
39
- return found
40
- }
41
-
42
- export const slopnator: Slopnator = {
43
- on: ['CallExpression'],
44
- holds: (node) => {
45
- const held = node as { readonly callee?: { readonly type?: string; readonly name?: string } }
46
- const called = held.callee?.type === 'Identifier' ? held.callee.name : undefined
47
- if (called === undefined) return false
48
- if (called === WRAPS || called === HANDS) return false
49
- if (declared().has(called)) return false
50
-
51
- return (
52
- called + ' is called here, and a screen is made of declared elements and compositions and may hand an operation to ' +
53
- HANDS + '. Nothing else has a place in one, so this is work that belongs in an operation'
54
- )
55
- },
56
- proves: {
57
- wrong: ["export const Orders = screen('orders', {}, () => loadOrders())"],
58
- right: [
59
- {
60
- files: [
61
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
62
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_operations.ts',\n },\n ],\n}\n" },
63
- { at: 'threads/_probe_operations.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OPERATIONS: Threaded = {\n id: 'operations',\n declares: [\n {\n id: 'wf_list_orders',\n describes: 'lists the orders',\n derives: [\n {\n id: 'wf_list_orders_body',\n at: 'operations/wf_list_orders.ts',\n by: 'thread compile',\n },\n ],\n },\n ],\n}\n" },
64
- { at: 'screens/probe.tsx', source: "import { dispatch } from '../operations/_dispatch.ts'\n\nimport { operation } from '../operations/wf_list_orders.ts'\n\nexport const Orders = screen('orders', {}, () => dispatch(operation, input, actor, opened))\n" },
65
- ],
66
- at: 'screens/probe.tsx',
67
- },
68
- ],
69
- },
70
- }
@@ -1,68 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
-
3
- const ANNOUNCED: readonly { readonly what: string; readonly pattern: RegExp }[] = [
4
- { what: 'a Stripe secret key', pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{16,}/ },
5
- { what: 'a Stripe restricted key', pattern: /\brk_(?:live|test)_[A-Za-z0-9]{16,}/ },
6
- { what: 'a GitHub token', pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}/ },
7
- { what: 'a GitHub personal access token', pattern: /\bgithub_pat_[A-Za-z0-9_]{30,}/ },
8
- { what: 'an AWS access key id', pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/ },
9
- { what: 'a Slack token', pattern: /\bxox[abposr]-[A-Za-z0-9-]{10,}/ },
10
- { what: 'an OpenAI key', pattern: /\bsk-[A-Za-z0-9_-]{32,}/ },
11
- { what: 'an Anthropic key', pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}/ },
12
- { what: 'a Google API key', pattern: /\bAIza[A-Za-z0-9_-]{35}\b/ },
13
- { what: 'a JSON web token', pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
14
- { what: 'a private key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/ },
15
- { what: 'a password inside a connection string', pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{6,}@/ },
16
- ]
17
-
18
- const NAMED = /\b(?:secret|password|passwd|api_?key|apikey|access_?token|auth_?token|private_?key|client_?secret|credential)s?\b\s*[:=]\s*(['"`])([^'"`\n]{16,})\1/gi
19
-
20
- const MIXED = /^(?=.*[a-z])(?=.*[A-Z0-9])[A-Za-z0-9_+/=.-]{16,}$/
21
-
22
- const NOT = [
23
- /^(?:sec|pub)_[a-z0-9_]+$/,
24
- /^\$\{/,
25
- /^(?:x{3,}|X{3,}|\.{3,}|<[^>]+>|change[-_]?me|your[-_]|example|placeholder|redacted|todo|test|dummy|sample|fake|dGhpcyBpcyBub3QgYSBzZWNyZXQ)/i,
26
- ]
27
-
28
- const CODE = /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|json|env|ya?ml|toml|sql|md|sh)$/
29
-
30
- export const slopnator: Slopnator = {
31
- holds: (_node, at) => {
32
- if (!CODE.test(at.filename)) return false
33
-
34
- if (/(?:^|\/)\.thread\/vault\.sealed\.json$/.test(at.filename)) return false
35
-
36
- const text = String(at.text)
37
-
38
- for (const one of ANNOUNCED) {
39
- if (!one.pattern.test(text)) continue
40
- return (
41
- `this holds ${one.what}, written into the file. It is in every copy of this repository, in its history for ` +
42
- 'ever, and in whatever bundle reaches it'
43
- )
44
- }
45
-
46
- for (const found of text.matchAll(NAMED)) {
47
- const value = found[2] ?? ''
48
- if (NOT.some((each) => each.test(value))) continue
49
- if (!MIXED.test(value)) continue
50
- return (
51
- `this writes a ${(found[0].split(/[:=]/)[0] ?? '').trim()} down as a literal ${String(value.length)} characters ` +
52
- 'long. A credential in a file is in every copy of this repository and in its history for ever'
53
- )
54
- }
55
-
56
- return false
57
- },
58
- proves: {
59
- wrong: [
60
- "const config = { password: 'Hunter2Hunter2Hunter2' }\n",
61
- "export const db = 'postgres://app:s3cr3tpassword@db.example.com:5432/main'\n",
62
- ],
63
- right: [
64
- "export const stripe = { apiKey: 'sec_stripe_key' }\n",
65
- "export const node = nd_action(async (opened) => await opened.ht_stripe.charge({ json: { amount: 100 } }))\n",
66
- ],
67
- },
68
- }
@@ -1,178 +0,0 @@
1
- import { existsSync, readdirSync, statSync } from '../../integrations/disk.ts'
2
- import { resolve } from 'node:path'
3
-
4
- import type { Slopnator } from '../slopnator.ts'
5
- import { descend } from '../../core/descend.ts'
6
- import { findTree } from '../../core/tree.ts'
7
-
8
- const ROOT = findTree().at
9
- const TREE = findTree().name
10
- const canonical = (at: string): string => (at.startsWith('threads/') ? `${TREE}/${at.slice('threads/'.length)}` : at)
11
- const NAMES_A_FILE = /\.[a-z]{1,5}$/
12
-
13
- const known = new WeakMap<object, ReadonlySet<string>>()
14
-
15
- const derived = (): ReadonlySet<string> => {
16
- const found = new Set<string>()
17
- try {
18
- const pass = descend()
19
- const cached = known.get(pass)
20
- if (cached !== undefined) return cached
21
- for (const [at, held] of pass.threaded) {
22
- if (held.some((one) => one.row.by !== undefined)) found.add(at)
23
- }
24
- for (const one of pass.derived.values()) if (one.at !== undefined) found.add(one.at)
25
- known.set(pass, found)
26
- } catch {
27
- }
28
- return found
29
- }
30
-
31
- const ours = (): ReadonlySet<string> => {
32
- try {
33
- return new Set(
34
- readdirSync(ROOT).filter(
35
- (one) => one !== 'node_modules' && statSync(resolve(ROOT, one), { throwIfNoEntry: false })?.isDirectory() === true,
36
- ),
37
- )
38
- } catch {
39
- return new Set()
40
- }
41
- }
42
-
43
- interface Keyed {
44
- readonly type?: string
45
- readonly key?: { readonly name?: string; readonly value?: unknown }
46
- readonly value?: { readonly type?: string }
47
- readonly properties?: readonly Keyed[]
48
- }
49
-
50
- const named = (one: Keyed): string | undefined => one.key?.name ?? (typeof one.key?.value === 'string' ? one.key.value : undefined)
51
-
52
- const sourced = (properties: readonly Keyed[]): boolean => properties.some((one) => named(one) === 'source')
53
-
54
- function proving(ancestors: readonly unknown[]): boolean {
55
- for (let at = ancestors.length - 1; at >= 0; at -= 1) {
56
- const held = ancestors[at] as Keyed
57
- if (held.type !== 'ObjectExpression') continue
58
- const properties = held.properties ?? []
59
- const files = properties.some((one) => named(one) === 'files' && one.value?.type === 'ArrayExpression')
60
- const where = properties.some((one) => named(one) === 'at' && one.value?.type === 'Literal')
61
- if (where && (files || sourced(properties))) return true
62
- }
63
- return false
64
- }
65
-
66
- export const slopnator: Slopnator = {
67
- on: ['Literal'],
68
- holds: (node, held) => {
69
- if (proving(held.ancestors())) return false
70
- const value = (node as { value?: unknown }).value
71
- if (typeof value !== 'string' || !value.includes('/')) return false
72
-
73
- const path = canonical(value)
74
- const tops = ours()
75
- if (tops.size === 0 || !tops.has(path.split('/')[0] ?? '')) return false
76
-
77
- const varies = path.search(/[{*]/)
78
- if (varies !== -1) {
79
- const cut = path.lastIndexOf('/', varies)
80
- const dir = cut === -1 ? '.' : path.slice(0, cut)
81
- if (statSync(resolve(ROOT, dir), { throwIfNoEntry: false })?.isDirectory() === true) return false
82
- return `\`${value}\` ${path[varies] === '*' ? 'names what is in' : 'writes into'} \`${dir}\`, which is not a directory on disk`
83
- }
84
-
85
- if (!NAMES_A_FILE.test(path)) return false
86
- if (existsSync(resolve(ROOT, path))) return false
87
- if (derived().has(path)) return false
88
- return `\`${value}\` is not on disk`
89
- },
90
- proves: {
91
- wrong: [
92
- {
93
- files: [
94
- { at: 'threads/_probe_there.ts', source: 'export const THERE = 1\n' },
95
- { at: 'probe.tsx', source: "const at = 'threads/nope.ts'\n" },
96
- ],
97
- at: 'probe.tsx',
98
- },
99
- {
100
- files: [
101
- { at: 'scripts/_probe_there.ts', source: 'export const THERE = 1\n' },
102
- { at: 'probe.tsx', source: "const at = 'scripts/gone.ts'\n" },
103
- ],
104
- at: 'probe.tsx',
105
- },
106
- {
107
- files: [
108
- { at: 'docs/_probe_there.md', source: 'there\n' },
109
- { at: 'probe.tsx', source: "const at = 'docs/nowhere/{page}.md'\n" },
110
- ],
111
- at: 'probe.tsx',
112
- },
113
- {
114
- files: [
115
- { at: 'docs/_probe_there.md', source: 'there\n' },
116
- { at: 'probe.tsx', source: "const at = 'docs/nowhere/*.md'\n" },
117
- ],
118
- at: 'probe.tsx',
119
- },
120
- {
121
- files: [
122
- { at: 'migrations/_probe_there.sql', source: 'select 1;\n' },
123
- { at: 'probe.tsx', source: "const at = 'migrations/gone.sql'\n" },
124
- ],
125
- at: 'probe.tsx',
126
- },
127
- {
128
- files: [
129
- { at: 'plans/_probe_there.md', source: 'there\n' },
130
- { at: 'probe.tsx', source: "const at = 'plans/gone.md'\n" },
131
- ],
132
- at: 'probe.tsx',
133
- },
134
- ],
135
- right: [
136
- {
137
- files: [
138
- { at: 'packages/core/integrity/src/constraint.ts', source: 'export const CONSTRAINTS = []\n' },
139
- { at: 'probe.tsx', source: "const at = 'packages/core/integrity/src/constraint.ts'\n" },
140
- ],
141
- at: 'probe.tsx',
142
- },
143
- {
144
- files: [
145
- { at: 'packages/core/integrity/src/lint.ts', source: 'export const plugin = {}\n' },
146
- { at: 'probe.tsx', source: "const at = 'packages/core/integrity/src/lint.ts'\n" },
147
- ],
148
- at: 'probe.tsx',
149
- },
150
- {
151
- files: [
152
- { at: 'docs/reference/_probe_there.md', source: 'there\n' },
153
- { at: 'probe.tsx', source: "const at = 'docs/reference/{page}.md'\n" },
154
- ],
155
- at: 'probe.tsx',
156
- },
157
- {
158
- files: [
159
- { at: 'docs/reference/_probe_there.md', source: 'there\n' },
160
- { at: 'probe.tsx', source: "const at = 'docs/reference/*.md'\n" },
161
- ],
162
- at: 'probe.tsx',
163
- },
164
- "const at = 'threads/'",
165
- "const said = 'a sentence about packages/core/integrity/src/constraint.ts and nothing else'",
166
- {
167
- files: [
168
- { 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' },
169
- { 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" },
170
- { at: 'app/_probe_there.tsx', source: 'export const THERE = 1\n' },
171
- { at: 'probe.tsx', source: "const at = 'app/layout.tsx'\n" },
172
- ],
173
- at: 'probe.tsx',
174
- },
175
- "const at = 'node_modules/thread/package.json'",
176
- ],
177
- },
178
- }
@@ -1,57 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
- import { here, index, meaningsIn, parses, unreadable } from './sl_unthreaded.ts'
3
-
4
- export const slopnator: Slopnator = {
5
- holds: (_node, at) => {
6
- const path = here(at.filename)
7
- if (unreadable(path, at.text) !== undefined) return false
8
- const mine = parses(path) ? meaningsIn(at.text) : []
9
- if (mine.length === 0) return false
10
-
11
- const meanings = index().meanings
12
-
13
- for (const one of mine) {
14
- const held = meanings.get(one.id)
15
- if (held === undefined || held.size < 2) continue
16
- return `\`${one.id}\` is coined with ${String(held.size)} different \`describes\`, and the glossary holds one meaning per word`
17
- }
18
- return false
19
- },
20
- proves: {
21
- wrong: [
22
- {
23
- files: [
24
- {
25
- at: 'thread/core/thread.ts',
26
- source:
27
- "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n",
28
- },
29
- {
30
- at: 'threads/thread.ts',
31
- source:
32
- "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_meant.ts',\n },\n ],\n}\n",
33
- },
34
- {
35
- at: 'threads/_probe_meant.ts',
36
- source:
37
- "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'A different thing entirely.',\n },\n ],\n}\n",
38
- },
39
- ],
40
- at: 'threads/_probe_meant.ts',
41
- },
42
- {
43
- files: [
44
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
45
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_meant_two.ts',\n },\n ],\n}\n" },
46
- { at: 'threads/_probe_meant_two.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n id: 'two',\n describes: 'The stated meaning.',\n },\n {\n id: 'two',\n },\n ],\n}\n" },
47
- ],
48
- at: 'threads/_probe_meant_two.ts',
49
- },
50
- ],
51
- right: [
52
- "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'one',\n describes: 'The first thing.',\n },\n ],\n}",
53
- "const M: Threaded = {\n declares: [\n {\n id: 'one',\n describes: 'The first thing.',\n },\n {\n id: 'two',\n describes: 'A different thing entirely.',\n },\n ],\n}",
54
- 'const M = { declares: [] }',
55
- ],
56
- },
57
- }
@@ -1,82 +0,0 @@
1
- import type { Slopnator } from '../slopnator.ts'
2
- import { atsIn, canonical, here, index, parses, unreadable } from './sl_unthreaded.ts'
3
-
4
- const ONLY_DECLARES = new Set(['declares'])
5
-
6
- const declaredIn = (text: string): readonly string[] => atsIn(text, ONLY_DECLARES).map(canonical)
7
-
8
- export const slopnator: Slopnator = {
9
- holds: (_node, at) => {
10
- const path = here(at.filename)
11
- if (unreadable(path, at.text) !== undefined) return false
12
- const mine = parses(path) ? declaredIn(at.text) : []
13
- if (mine.length === 0) return false
14
-
15
- const threaded = index().declared
16
-
17
- for (const claimed of mine) {
18
- const times = threaded.get(claimed) ?? 0
19
- if (times > 1) return `${claimed} is declared ${String(times)} times, and \`declares\` is exactly one parent ever`
20
- }
21
- return false
22
- },
23
- proves: {
24
- wrong: [
25
- {
26
- files: [
27
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
28
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_parented.ts',\n },\n ],\n}\n" },
29
- { at: 'threads/_probe_parented.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/one.ts',\n },\n {\n at: 'threads/one.ts',\n },\n ],\n}\n" },
30
- ],
31
- at: 'threads/_probe_parented.ts',
32
- },
33
- {
34
- files: [
35
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
36
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_parented_2.ts',\n },\n ],\n}\n" },
37
- { at: 'threads/_probe_parented_2.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/two.ts',\n },\n {\n at: 'threads/three.ts',\n },\n {\n at: 'threads/two.ts',\n },\n ],\n}\n" },
38
- ],
39
- at: 'threads/_probe_parented_2.ts',
40
- },
41
- {
42
- files: [
43
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
44
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_parented_hoisted.ts',\n },\n ],\n}\n" },
45
- { at: 'threads/_probe_parented_hoisted.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nconst ROW = {\n at: 'threads/one.ts',\n}\nconst M: Threaded = {\n declares: [ROW, ROW],\n}\n" },
46
- ],
47
- at: 'threads/_probe_parented_hoisted.ts',
48
- },
49
- ],
50
- right: [
51
- {
52
- files: [
53
- { 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" },
54
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_derived.ts',\n },\n ],\n}\n" },
55
- { at: 'threads/_probe_derived.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n derives: [\n {\n at: 'threads/glossary.ts',\n by: 'thread glossary',\n runs: 'thread glossary --check',\n },\n {\n at: 'threads/glossary.ts',\n by: 'thread glossary',\n runs: 'thread glossary --check',\n },\n ],\n}\n" },
56
- { at: 'threads/glossary.ts', source: 'export const GLOSSARY = 1\n' },
57
- ],
58
- at: 'threads/_probe_derived.ts',
59
- },
60
- 'const M = { declares: [] }',
61
- {
62
- files: [
63
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
64
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_hoisted.ts',\n },\n ],\n}\n" },
65
- { at: 'threads/_probe_hoisted.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nconst ROW = {\n at: 'threads/core.ts',\n}\nexport const M: Threaded = {\n declares: [ROW],\n}\n" },
66
- { at: 'threads/core.ts', source: 'export const CORE = 1\n' },
67
- ],
68
- at: 'threads/_probe_hoisted.ts',
69
- },
70
- {
71
- files: [
72
- { at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
73
- { at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_nested.ts',\n },\n ],\n}\n" },
74
- { at: 'threads/_probe_nested.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const M: Threaded = {\n declares: [\n {\n at: 'threads/core.ts',\n declares: [\n {\n at: 'threads/data.ts',\n },\n ],\n },\n ],\n}\n" },
75
- { at: 'threads/core.ts', source: 'export const CORE = 1\n' },
76
- { at: 'threads/data.ts', source: 'export const DATA = 1\n' },
77
- ],
78
- at: 'threads/_probe_nested.ts',
79
- },
80
- ],
81
- },
82
- }