@prestack/thread 0.1.20 → 0.1.22

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 (148) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/core/attach.js +21 -0
  3. package/dist/core/descend.d.ts +2 -0
  4. package/dist/core/descend.js +18 -3
  5. package/dist/core/generators/commit.js +55 -3
  6. package/dist/core/generators/dev.d.ts +1 -0
  7. package/dist/core/generators/dev.js +68 -0
  8. package/dist/core/rows.d.ts +5 -0
  9. package/dist/core/rows.js +45 -0
  10. package/dist/core/tree.d.ts +1 -0
  11. package/dist/core/tree.js +7 -1
  12. package/dist/derivation/generators/build.d.ts +3 -1
  13. package/dist/derivation/generators/build.js +103 -22
  14. package/dist/derivation/generators/release.js +74 -26
  15. package/dist/derivation/release.js +3 -3
  16. package/dist/integrations/cli.d.ts +3 -9
  17. package/dist/integrations/cli.js +96 -36
  18. package/dist/integrations/declared.d.ts +8 -0
  19. package/dist/integrations/declared.js +22 -0
  20. package/dist/integrations/pglite.js +13 -3
  21. package/dist/integrations/target.d.ts +4 -1
  22. package/dist/integrations/target.js +20 -6
  23. package/dist/integrity/attach.js +1 -5
  24. package/dist/integrity/generators/publish.js +2 -1
  25. package/dist/integrity/slopnators/sl_stale_path.js +10 -0
  26. package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
  27. package/dist/operations/access.js +3 -2
  28. package/dist/operations/capabilities.d.ts +2 -0
  29. package/dist/operations/capabilities.js +2 -0
  30. package/dist/operations/generators/capability.js +234 -24
  31. package/dist/operations/generators/compile.d.ts +21 -0
  32. package/dist/operations/generators/compile.js +151 -51
  33. package/dist/operations/generators/permits.d.ts +2 -1
  34. package/dist/operations/generators/permits.js +52 -47
  35. package/dist/operations/generators/seed.d.ts +3 -0
  36. package/dist/operations/generators/seed.js +25 -3
  37. package/dist/operations/nodes.d.ts +2 -2
  38. package/package.json +1 -1
  39. package/dist/core/actor.ts +0 -21
  40. package/dist/core/attach.ts +0 -613
  41. package/dist/core/chain.ts +0 -145
  42. package/dist/core/condition.ts +0 -227
  43. package/dist/core/descend.ts +0 -1173
  44. package/dist/core/errors/catalogue.ts +0 -314
  45. package/dist/core/errors/classes.ts +0 -51
  46. package/dist/core/errors/error.ts +0 -152
  47. package/dist/core/errors/namespaces.ts +0 -19
  48. package/dist/core/finding.ts +0 -37
  49. package/dist/core/generators/commit.ts +0 -157
  50. package/dist/core/init.ts +0 -222
  51. package/dist/core/invoked.ts +0 -52
  52. package/dist/core/round.ts +0 -74
  53. package/dist/core/rows.ts +0 -459
  54. package/dist/core/scope.ts +0 -20
  55. package/dist/core/thread.ts +0 -29
  56. package/dist/core/tree.ts +0 -90
  57. package/dist/core/types.ts +0 -351
  58. package/dist/derivation/attach.ts +0 -57
  59. package/dist/derivation/generators/build.ts +0 -371
  60. package/dist/derivation/generators/check.ts +0 -118
  61. package/dist/derivation/generators/determinism.ts +0 -145
  62. package/dist/derivation/generators/release.ts +0 -236
  63. package/dist/derivation/release.ts +0 -43
  64. package/dist/integrations/attach.ts +0 -187
  65. package/dist/integrations/cli.ts +0 -615
  66. package/dist/integrations/disk.ts +0 -466
  67. package/dist/integrations/generators/read.ts +0 -127
  68. package/dist/integrations/generators/vault.ts +0 -210
  69. package/dist/integrations/https.ts +0 -216
  70. package/dist/integrations/pglite.ts +0 -206
  71. package/dist/integrations/secret.ts +0 -54
  72. package/dist/integrations/target.ts +0 -656
  73. package/dist/integrations/vault.ts +0 -148
  74. package/dist/integrity/attach.ts +0 -150
  75. package/dist/integrity/check-threads.ts +0 -166
  76. package/dist/integrity/constraint.ts +0 -1246
  77. package/dist/integrity/generators/compiler.ts +0 -138
  78. package/dist/integrity/generators/gate.ts +0 -227
  79. package/dist/integrity/generators/issues.ts +0 -160
  80. package/dist/integrity/generators/publish.ts +0 -232
  81. package/dist/integrity/generators/trial.ts +0 -359
  82. package/dist/integrity/lint.ts +0 -214
  83. package/dist/integrity/lsp/bin.ts +0 -44
  84. package/dist/integrity/lsp/diagnostics.ts +0 -92
  85. package/dist/integrity/lsp/findings.ts +0 -63
  86. package/dist/integrity/lsp/lint.ts +0 -124
  87. package/dist/integrity/lsp/protocol.ts +0 -78
  88. package/dist/integrity/lsp/server.ts +0 -224
  89. package/dist/integrity/slopnator.ts +0 -324
  90. package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
  91. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
  92. package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
  93. package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
  94. package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
  95. package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
  96. package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
  97. package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
  98. package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
  99. package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
  100. package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
  101. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
  102. package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
  103. package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
  104. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
  105. package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
  106. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
  107. package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
  108. package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
  109. package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
  110. package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
  111. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
  112. package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
  113. package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
  114. package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
  115. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
  116. package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
  117. package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
  118. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
  119. package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
  120. package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
  121. package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
  122. package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
  123. package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
  124. package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
  125. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
  126. package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
  127. package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
  128. package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
  129. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
  130. package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
  131. package/dist/integrity/sweep.ts +0 -395
  132. package/dist/knowledge/attach.ts +0 -327
  133. package/dist/knowledge/generators/docsgen.ts +0 -186
  134. package/dist/knowledge/generators/glossary.ts +0 -73
  135. package/dist/knowledge/glossary.ts +0 -6
  136. package/dist/operations/access.ts +0 -131
  137. package/dist/operations/attach.ts +0 -408
  138. package/dist/operations/capabilities.ts +0 -88
  139. package/dist/operations/dispatch.ts +0 -294
  140. package/dist/operations/error-results.ts +0 -31
  141. package/dist/operations/generators/capability.ts +0 -1054
  142. package/dist/operations/generators/compile.ts +0 -701
  143. package/dist/operations/generators/permits.ts +0 -224
  144. package/dist/operations/generators/seed.ts +0 -359
  145. package/dist/operations/generators/trial.ts +0 -666
  146. package/dist/operations/nodes.ts +0 -53
  147. package/dist/operations/operations.ts +0 -91
  148. 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
- }