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