@prestack/thread 0.0.1 → 0.0.2

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 (150) hide show
  1. package/dist/core/actor.ts +20 -0
  2. package/dist/core/asking.d.ts +0 -1
  3. package/dist/core/asking.js +0 -1
  4. package/dist/core/asking.ts +1342 -0
  5. package/dist/core/attach.js +2 -2
  6. package/dist/core/attach.ts +585 -0
  7. package/dist/core/chain.js +8 -1
  8. package/dist/core/chain.ts +148 -0
  9. package/dist/core/cli.js +97 -18
  10. package/dist/core/cli.ts +2004 -0
  11. package/dist/core/condition.ts +227 -0
  12. package/dist/core/descend.d.ts +1 -0
  13. package/dist/core/descend.js +36 -0
  14. package/dist/core/descend.ts +1001 -0
  15. package/dist/core/finding.ts +38 -0
  16. package/dist/core/generators/meta.js +6 -0
  17. package/dist/core/generators/meta.ts +1006 -0
  18. package/dist/core/init.ts +170 -0
  19. package/dist/core/invoked.ts +47 -0
  20. package/dist/core/round.ts +57 -0
  21. package/dist/core/rows.ts +369 -0
  22. package/dist/core/scope.ts +18 -0
  23. package/dist/core/thread.ts +29 -0
  24. package/dist/core/tree.ts +67 -0
  25. package/dist/core/types.ts +350 -0
  26. package/dist/derivation/attach.js +1 -1
  27. package/dist/derivation/attach.ts +68 -0
  28. package/dist/derivation/generators/build.ts +232 -0
  29. package/dist/derivation/generators/check.ts +118 -0
  30. package/dist/derivation/generators/derivation.ts +347 -0
  31. package/dist/derivation/generators/determinism.ts +145 -0
  32. package/dist/derivation/generators/release.js +19 -9
  33. package/dist/derivation/generators/release.ts +213 -0
  34. package/dist/derivation/makers/release.ts +135 -0
  35. package/dist/derivation/release.ts +23 -0
  36. package/dist/integrations/attach.js +2 -2
  37. package/dist/integrations/attach.ts +207 -0
  38. package/dist/integrations/generators/read.ts +127 -0
  39. package/dist/integrations/generators/secret.ts +289 -0
  40. package/dist/integrations/generators/target.js +16 -6
  41. package/dist/integrations/generators/target.ts +944 -0
  42. package/dist/integrations/generators/vault.js +46 -14
  43. package/dist/integrations/generators/vault.ts +212 -0
  44. package/dist/integrations/{sdk.js → module.js} +4 -4
  45. package/dist/integrations/module.ts +189 -0
  46. package/dist/integrations/secret.ts +50 -0
  47. package/dist/integrations/target.d.ts +3 -1
  48. package/dist/integrations/target.js +14 -11
  49. package/dist/integrations/target.ts +340 -0
  50. package/dist/integrations/vault.d.ts +2 -0
  51. package/dist/integrations/vault.js +4 -2
  52. package/dist/integrations/vault.ts +136 -0
  53. package/dist/integrity/attach.ts +234 -0
  54. package/dist/integrity/check-threads.js +2 -26
  55. package/dist/integrity/check-threads.ts +166 -0
  56. package/dist/integrity/constraint.ts +1202 -0
  57. package/dist/integrity/generators/compiler.ts +161 -0
  58. package/dist/integrity/generators/gate.ts +201 -0
  59. package/dist/integrity/generators/issues.ts +160 -0
  60. package/dist/integrity/generators/publish.js +14 -5
  61. package/dist/integrity/generators/publish.ts +247 -0
  62. package/dist/integrity/generators/rule.ts +271 -0
  63. package/dist/integrity/generators/seal.ts +117 -0
  64. package/dist/integrity/generators/trial.ts +320 -0
  65. package/dist/integrity/lint.ts +227 -0
  66. package/dist/integrity/lsp/bin.ts +42 -0
  67. package/dist/integrity/lsp/diagnostics.ts +92 -0
  68. package/dist/integrity/lsp/findings.ts +63 -0
  69. package/dist/integrity/lsp/lint.ts +140 -0
  70. package/dist/integrity/lsp/protocol.ts +78 -0
  71. package/dist/integrity/lsp/server.ts +224 -0
  72. package/dist/integrity/sealed.js +2 -2
  73. package/dist/integrity/sealed.ts +56 -0
  74. package/dist/integrity/slopnator.ts +350 -0
  75. package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
  76. package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
  77. package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
  78. package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
  79. package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
  80. package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
  81. package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
  82. package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
  83. package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
  84. package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
  85. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
  86. package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
  87. package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
  88. package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
  89. package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
  90. package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
  91. package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
  92. package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
  93. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
  94. package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
  95. package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
  96. package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
  97. package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
  98. package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
  99. package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
  100. package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
  101. package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
  102. package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
  103. package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
  104. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
  105. package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
  106. package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
  107. package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
  108. package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
  109. package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
  110. package/dist/integrity/sweep.ts +307 -0
  111. package/dist/knowledge/attach.ts +350 -0
  112. package/dist/knowledge/generators/docsgen.ts +174 -0
  113. package/dist/knowledge/generators/glossary.ts +73 -0
  114. package/dist/knowledge/generators/skills.ts +263 -0
  115. package/dist/knowledge/glossary.ts +6 -0
  116. package/dist/operations/access.ts +88 -0
  117. package/dist/operations/attach.ts +475 -0
  118. package/dist/operations/capabilities.ts +65 -0
  119. package/dist/operations/cost.ts +58 -0
  120. package/dist/operations/dispatch.ts +348 -0
  121. package/dist/operations/error-results.ts +31 -0
  122. package/dist/operations/errors/catalog.ts +168 -0
  123. package/dist/operations/errors/classes.ts +51 -0
  124. package/dist/operations/errors/errors.ts +314 -0
  125. package/dist/operations/generators/capability.js +29 -19
  126. package/dist/operations/generators/capability.ts +704 -0
  127. package/dist/operations/generators/compile.js +20 -1
  128. package/dist/operations/generators/compile.ts +643 -0
  129. package/dist/operations/generators/grant.ts +336 -0
  130. package/dist/operations/generators/operation.ts +972 -0
  131. package/dist/operations/generators/permits.ts +223 -0
  132. package/dist/operations/generators/seed.ts +347 -0
  133. package/dist/operations/generators/trial.js +4 -1
  134. package/dist/operations/generators/trial.ts +529 -0
  135. package/dist/operations/http.ts +164 -0
  136. package/dist/operations/makers/capability.ts +166 -0
  137. package/dist/operations/makers/trial.ts +520 -0
  138. package/dist/operations/memory.ts +470 -0
  139. package/dist/operations/metering.ts +328 -0
  140. package/dist/operations/nodes.ts +54 -0
  141. package/dist/operations/operations.ts +32 -0
  142. package/dist/operations/queue-policy.ts +10 -0
  143. package/dist/operations/result.ts +28 -0
  144. package/dist/operations/seams.d.ts +4 -4
  145. package/dist/operations/seams.ts +364 -0
  146. package/dist/operations/store.ts +29 -0
  147. package/package.json +8 -1
  148. package/dist/integrations/generators/sdk.d.ts +0 -1
  149. package/dist/integrations/generators/sdk.js +0 -148
  150. /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
@@ -0,0 +1,227 @@
1
+ import { ENVIRONMENT, type Actor } from './actor.ts'
2
+
3
+ export const COMPARE_OPS = ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'in', 'has'] as const
4
+ export type CompareOp = (typeof COMPARE_OPS)[number]
5
+
6
+ export const AGGREGATIONS = ['count', 'sum', 'min', 'max', 'avg'] as const
7
+
8
+ export const ACTOR_FACTS = ['id', 'roles', 'entitlements', ...ENVIRONMENT.map(([fact]) => fact)] as const satisfies readonly (keyof Actor)[]
9
+
10
+ export const AGO = /^[1-9][0-9]*[smhd]$/
11
+
12
+ export type Operand =
13
+ | { readonly value: string | number | boolean | null }
14
+ | { readonly field: string }
15
+ | { readonly was: string }
16
+ | { readonly outer: string }
17
+ | { readonly actor: (typeof ACTOR_FACTS)[number] }
18
+ | { readonly now: true }
19
+ | { readonly ago: string }
20
+ | {
21
+ readonly agg: {
22
+ readonly of: string
23
+ readonly how: (typeof AGGREGATIONS)[number]
24
+ readonly field?: string
25
+ readonly where?: Condition
26
+ }
27
+ }
28
+
29
+ export type Condition =
30
+ | { readonly all: readonly Condition[] }
31
+ | { readonly any: readonly Condition[] }
32
+ | { readonly not: Condition }
33
+ | { readonly is: Operand; readonly op: CompareOp; readonly than: Operand }
34
+
35
+ export function walkOperands(condition: Condition, take: (one: Operand) => void): void {
36
+ if ('all' in condition) {
37
+ for (const one of condition.all) walkOperands(one, take)
38
+ return
39
+ }
40
+ if ('any' in condition) {
41
+ for (const one of condition.any) walkOperands(one, take)
42
+ return
43
+ }
44
+ if ('not' in condition) {
45
+ walkOperands(condition.not, take)
46
+ return
47
+ }
48
+ for (const side of [condition.is, condition.than]) {
49
+ take(side)
50
+ if ('agg' in side && side.agg.where !== undefined) walkOperands(side.agg.where, take)
51
+ }
52
+ }
53
+
54
+ export function mentionsActor(condition: Condition): boolean {
55
+ let found = false
56
+ walkOperands(condition, (one) => {
57
+ if ('actor' in one) found = true
58
+ })
59
+ return found
60
+ }
61
+
62
+ export function agoSeconds(ago: string): number | undefined {
63
+ if (!AGO.test(ago)) return undefined
64
+ const size = Number(ago.slice(0, -1))
65
+ const unit = ago.slice(-1)
66
+ return size * (unit === 's' ? 1 : unit === 'm' ? 60 : unit === 'h' ? 3_600 : 86_400)
67
+ }
68
+
69
+ export function satisfies(condition: Condition, actor: Actor): boolean {
70
+ if ('all' in condition) return condition.all.every((one) => satisfies(one, actor))
71
+ if ('any' in condition) return condition.any.some((one) => satisfies(one, actor))
72
+ if ('not' in condition) return !satisfies(condition.not, actor)
73
+
74
+ const reads = (one: Operand): unknown => {
75
+ if ('value' in one) return one.value
76
+ if ('actor' in one) return actor[one.actor]
77
+ return undefined
78
+ }
79
+
80
+ const left = reads(condition.is)
81
+ const right = reads(condition.than)
82
+ if (left === undefined || right === undefined) return false
83
+
84
+ const op = condition.op
85
+ if (op === 'in') return Array.isArray(right) && right.includes(left)
86
+ if (op === 'has') return Array.isArray(left) && left.includes(right)
87
+ if (op === 'eq') return left === right
88
+ if (op === 'neq') return left !== right
89
+ if (typeof left !== typeof right) return false
90
+ if (typeof left !== 'number' && typeof left !== 'string') return false
91
+ const other = right as number | string
92
+ if (op === 'lt') return left < other
93
+ if (op === 'lte') return left <= other
94
+ if (op === 'gt') return left > other
95
+ return left >= other
96
+ }
97
+
98
+ type Asked = {
99
+ readonly id: string
100
+ readonly fields: readonly string[]
101
+ }
102
+
103
+ export const CONDITION_ANSWERS = ['is', 'than', 'field', 'value', 'actor', 'ago', 'of', 'how', 'over', 'op', 'when', 'joins'] as const
104
+
105
+ const OPERAND_WORDS = ['field', 'value', 'actor', 'now', 'ago', 'agg'] as const
106
+
107
+ async function sided(
108
+ ask: (key: string, question: string) => Promise<string>,
109
+ say: (line: string) => void,
110
+ subject: Asked,
111
+ all: readonly Asked[],
112
+ which: string,
113
+ ): Promise<Operand> {
114
+ for (;;) {
115
+ const kind = await ask(which, ` ${which} — ${OPERAND_WORDS.join(', ')}: `)
116
+ if (kind === 'field') {
117
+ const id = await ask('field', ` which field — ${subject.fields.join(', ')}: `)
118
+ if (!subject.fields.includes(id)) {
119
+ say(` "${subject.id}" declares no \`${id}\`.\n`)
120
+ continue
121
+ }
122
+ return { field: id }
123
+ }
124
+ if (kind === 'value') {
125
+ const raw = await ask('value', ' the value (text, a number, true, false, null): ')
126
+ if (raw === 'null') return { value: null }
127
+ if (raw === 'true' || raw === 'false') return { value: raw === 'true' }
128
+ const asNumber = Number(raw)
129
+ return { value: raw !== '' && Number.isFinite(asNumber) ? asNumber : raw }
130
+ }
131
+ if (kind === 'actor') {
132
+ const fact = await ask('actor', ` which — ${ACTOR_FACTS.join(', ')}: `)
133
+ if (!(ACTOR_FACTS as readonly string[]).includes(fact)) {
134
+ say(` an actor has ${ACTOR_FACTS.join(', ')} and nothing else.\n`)
135
+ continue
136
+ }
137
+ return { actor: fact as (typeof ACTOR_FACTS)[number] }
138
+ }
139
+ if (kind === 'now') return { now: true }
140
+ if (kind === 'ago') {
141
+ const span = await ask('ago', ' how long — e.g. 30d, 12h, 15m, 45s: ')
142
+ if (!AGO.test(span)) {
143
+ say(' a span is a number and one of s, m, h, d.\n')
144
+ continue
145
+ }
146
+ return { ago: span }
147
+ }
148
+ if (kind === 'agg') {
149
+ const of = await ask('of', ` over which entity — ${all.map((one) => one.id).join(', ')}: `)
150
+ const target = all.find((one) => one.id === of)
151
+ if (target === undefined) {
152
+ say(` nothing declares \`${of}\`.\n`)
153
+ continue
154
+ }
155
+ const how = await ask('how', ` how — ${AGGREGATIONS.join(', ')}: `)
156
+ if (!(AGGREGATIONS as readonly string[]).includes(how)) {
157
+ say(` an aggregation is ${AGGREGATIONS.join(', ')}.\n`)
158
+ continue
159
+ }
160
+ if (how === 'count') return { agg: { of, how: 'count' } }
161
+ const over = await ask('over', ` over which field of ${of} — ${target.fields.join(', ')}: `)
162
+ if (!target.fields.includes(over)) {
163
+ say(` "${of}" declares no \`${over}\`.\n`)
164
+ continue
165
+ }
166
+ return { agg: { of, how: how as 'sum' | 'min' | 'max' | 'avg', field: over } }
167
+ }
168
+ say(` there is no side called "${kind}".\n`)
169
+ }
170
+ }
171
+
172
+ async function compared(
173
+ ask: (key: string, question: string) => Promise<string>,
174
+ say: (line: string) => void,
175
+ subject: Asked,
176
+ all: readonly Asked[],
177
+ ): Promise<Condition> {
178
+ for (;;) {
179
+ const is = await sided(ask, say, subject, all, 'is')
180
+ const op = await ask('op', ` op — ${COMPARE_OPS.join(', ')}: `)
181
+ if (!(COMPARE_OPS as readonly string[]).includes(op)) {
182
+ say(` \`${op}\` is not a comparison this provider carries.\n`)
183
+ continue
184
+ }
185
+ const than = await sided(ask, say, subject, all, 'than')
186
+ return { is, op: op as (typeof COMPARE_OPS)[number], than }
187
+ }
188
+ }
189
+
190
+ export async function conditioned(
191
+ ask: (key: string, question: string) => Promise<string>,
192
+ say: (line: string) => void,
193
+ subject: { readonly id: string; readonly fields: readonly string[] },
194
+ all: readonly { readonly id: string; readonly fields: readonly string[] }[],
195
+ ): Promise<Condition | undefined> {
196
+ const held: Condition[] = []
197
+ let joiner: 'all' | 'any' = 'all'
198
+ for (;;) {
199
+ if (held.length === 0) {
200
+ const wanted = await ask('when', ' state a condition — y/n: ')
201
+ if (!wanted.toLowerCase().startsWith('y')) return undefined
202
+ }
203
+ held.push(await compared(ask, say, subject, all))
204
+ const next = await ask('joins', ' and, or, done: ')
205
+ if (next === 'done' || next === '') break
206
+ if (next === 'and') joiner = 'all'
207
+ else if (next === 'or') joiner = 'any'
208
+ else {
209
+ say(' say and, or, or done.\n')
210
+ break
211
+ }
212
+ }
213
+ if (held.length === 0) return undefined
214
+ const first = held[0] as Condition
215
+ return held.length === 1 ? first : joiner === 'all' ? { all: held } : { any: held }
216
+ }
217
+
218
+ import { quoted } from './rows.ts'
219
+
220
+ export function said(one: unknown): string {
221
+ if (typeof one === 'string') return quoted(one)
222
+ if (one === null) return 'null'
223
+ if (typeof one === 'boolean' || typeof one === 'number') return String(one)
224
+ if (Array.isArray(one)) return `[${one.map((each) => said(each)).join(', ')}]`
225
+ if (typeof one === 'object') return `{ ${Object.entries(one).map(([key, held]) => `${key}: ${said(held)}`).join(', ')} }`
226
+ return String(one)
227
+ }
@@ -3,6 +3,7 @@ import type { Finding } from './finding.ts';
3
3
  export declare const ROOT: string;
4
4
  export declare const TREE: string;
5
5
  export declare const THREAD_AT: string;
6
+ export declare function unkept(at: string): boolean;
6
7
  export declare const LISTS: readonly ["declares", "derives", "seeds"];
7
8
  export interface Row {
8
9
  readonly at?: string;
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from 'node:child_process';
1
2
  import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
3
  import { dirname, join, relative, resolve } from 'node:path';
3
4
  import ts from 'typescript';
@@ -25,6 +26,33 @@ function rooted() {
25
26
  export const ROOT = TREE_AT?.at ?? NOWHERE;
26
27
  export const TREE = `${TREE_AT?.name ?? NOWHERE}/`;
27
28
  export const THREAD_AT = `${TREE_AT?.name ?? NOWHERE}/thread.ts`;
29
+ const OWN_IGNORES = (() => {
30
+ if (spawnSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: ROOT, stdio: 'ignore' }).status === 0) {
31
+ return undefined;
32
+ }
33
+ try {
34
+ return readFileSync(join(ROOT, '.gitignore'), 'utf8')
35
+ .split('\n')
36
+ .map((one) => one.trim())
37
+ .filter((one) => one !== '' && !one.startsWith('#') && !one.startsWith('!'))
38
+ .map((one) => one.replace(/^\/+|\/+$/g, ''))
39
+ .filter((one) => one !== '' && !one.includes('*'));
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ })();
45
+ const asked = new Map();
46
+ export function unkept(at) {
47
+ const held = asked.get(at);
48
+ if (held !== undefined)
49
+ return held;
50
+ const found = OWN_IGNORES === undefined
51
+ ? spawnSync('git', ['check-ignore', '-q', '--', at], { cwd: ROOT, stdio: 'ignore' }).status === 0
52
+ : OWN_IGNORES.some((one) => at === one || at.startsWith(`${one}/`) || at.split('/').includes(one));
53
+ asked.set(at, found);
54
+ return found;
55
+ }
28
56
  export const LISTS = ['declares', 'derives', 'seeds'];
29
57
  const SOURCE = /\.(ts|tsx|mts|cts)$/;
30
58
  const RUNNER = /^(?:pnpm|npm|yarn) (?:run )?([\w:.-]+)/;
@@ -663,6 +691,14 @@ export function descend() {
663
691
  return;
664
692
  }
665
693
  const target = resolve(ROOT, row.at);
694
+ if (unkept(row.at)) {
695
+ findings.push({
696
+ file: at,
697
+ detail: `\`${names(row)}\` names \`${row.at}\`, which git ignores, so it is on this machine and in no clone of it`,
698
+ instead: 'name a file this repository carries, or stop ignoring it. A row naming an ignored path is answered here and nowhere else, so the descent passes on the disk that wrote it and breaks everywhere the work actually runs',
699
+ });
700
+ return;
701
+ }
666
702
  if (!existsSync(target)) {
667
703
  const derived = list === 'derives' || row.by !== undefined;
668
704
  const said = absent.get(row.at);