@prestack/thread 0.0.1 → 0.0.3

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,148 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync, readdirSync } from 'node:fs'
3
+ import { join } from 'node:path'
4
+ import { execPath } from 'node:process'
5
+ import { fileURLToPath, pathToFileURL } from 'node:url'
6
+
7
+ import type { Row } from './descend.ts'
8
+ import { findTree, type Tree } from './tree.ts'
9
+
10
+ const STATING = 'derivation.ts'
11
+
12
+ export async function generators(tree: Tree = findTree()): Promise<readonly Row[]> {
13
+ if (!existsSync(tree.dir)) return []
14
+
15
+ const sheets = readdirSync(tree.dir)
16
+ .filter((one) => one.endsWith('.ts'))
17
+ .sort((one, two) => (one === STATING ? -1 : two === STATING ? 1 : one < two ? -1 : one > two ? 1 : 0))
18
+
19
+ const found: Row[] = []
20
+ for (const name of sheets) {
21
+ const sheet = join(tree.dir, name)
22
+ try {
23
+ const held = (await import(pathToFileURL(sheet).href)) as { GENERATORS?: readonly Row[] }
24
+ found.push(...(held.GENERATORS ?? []))
25
+ } catch (thrown) {
26
+ throw new Error(
27
+ `${sheet} is one of the sheets where this tree states the commands it offers, and reading it threw: ` +
28
+ `${thrown instanceof Error ? thrown.message : String(thrown)}. A tree whose generators cannot be read ` +
29
+ 'offers exactly what a tree declaring none offers, so everything derived from your declarations would be ' +
30
+ 'left stale by a command reporting that it had finished. Fix that file.',
31
+ )
32
+ }
33
+ }
34
+ return found
35
+ }
36
+
37
+ export const offers = (all: readonly Row[]): ReadonlySet<string> =>
38
+ new Set(all.map((one) => (typeof one.by === 'string' ? one.by : '')).filter((one) => one !== ''))
39
+
40
+ const ROUNDS = 4
41
+
42
+ const word = (held: unknown): string => (typeof held === 'string' ? held.trim() : '')
43
+
44
+ const FROM_SOURCE = import.meta.url.endsWith('.ts')
45
+
46
+ const RUNNER = fileURLToPath(new URL(FROM_SOURCE ? './round.ts' : './round.js', import.meta.url))
47
+
48
+ const LOADER: readonly string[] = FROM_SOURCE ? ['--import', 'tsx'] : []
49
+
50
+ export const RAN = 'round: ran'
51
+ export const REFUSED_BY = 'round: refused'
52
+
53
+ export function batched(
54
+ at: string,
55
+ commands: readonly string[],
56
+ within?: number,
57
+ ): readonly { readonly command: string; readonly ok: boolean; readonly said: string }[] {
58
+ if (commands.length === 0) return []
59
+
60
+ const out = spawnSync(execPath, [...LOADER, RUNNER, ...new Set(commands)], {
61
+ cwd: at,
62
+ encoding: 'utf8',
63
+ ...(within === undefined ? {} : { timeout: within, killSignal: 'SIGKILL' as const }),
64
+ })
65
+ const lines = `${out.stdout ?? ''}${out.stderr ?? ''}`.split('\n')
66
+
67
+ const found = new Map<string, { readonly command: string; readonly ok: boolean; readonly said: string }>()
68
+ let held: string[] = []
69
+ for (const line of lines) {
70
+ const mark = line.startsWith(RAN) ? RAN : line.startsWith(REFUSED_BY) ? REFUSED_BY : undefined
71
+ if (mark === undefined) {
72
+ held.push(line)
73
+ continue
74
+ }
75
+ const command = line.slice(mark.length).trim()
76
+ found.set(command, { command, ok: mark === RAN, said: held.join('\n').trim() })
77
+ held = []
78
+ }
79
+
80
+ const orphaned = held.join('\n').trim()
81
+ const died =
82
+ out.error !== undefined
83
+ ? ` The runner itself failed to start or was stopped: ${out.error.message}.`
84
+ : out.signal !== null && out.signal !== undefined
85
+ ? ` The runner was killed by ${out.signal}, so everything after the command it was running is unreported rather than refused.`
86
+ : found.size === 0 && out.status !== 0
87
+ ? ` The runner exited ${String(out.status)} having reported nothing, so it died before the first command finished.`
88
+ : ''
89
+ let firstUnreached = true
90
+ return commands.map((one) => {
91
+ const has = found.get(one)
92
+ if (has !== undefined) return has
93
+ const why = `settle: the runner never reported on \`${one}\`, so it was not run and is not settled.${died}`
94
+ const said = firstUnreached && orphaned !== '' ? `${orphaned}\n${why}` : why
95
+ firstUnreached = false
96
+ return { command: one, ok: false, said }
97
+ })
98
+ }
99
+
100
+ const WITHIN = 300_000
101
+
102
+ function batch(
103
+ at: string,
104
+ commands: readonly string[],
105
+ ): { readonly said: readonly string[]; readonly refused: readonly string[] } {
106
+ const all = batched(at, commands, WITHIN)
107
+ return {
108
+ said: all.map((one) => one.said).filter((one) => one !== ''),
109
+ refused: all.filter((one) => !one.ok).map((one) => one.command),
110
+ }
111
+ }
112
+
113
+ export function settling(
114
+ at: string,
115
+ all: readonly Row[],
116
+ skip: readonly string[] = [],
117
+ ): { readonly ran: readonly string[]; readonly unsettled: readonly string[]; readonly rounds: number } {
118
+ const mine = all
119
+ .filter((one) => word(one.by) !== '' && word(one.runs) !== '' && word(one.by) !== word(one.runs) && !skip.includes(word(one.by)))
120
+ .filter((one, order, every) => every.findIndex((each) => word(each.by) === word(one.by)) === order)
121
+
122
+ const ran: string[] = []
123
+ let round = 0
124
+ let before: readonly string[] | undefined
125
+
126
+ while (round < ROUNDS) {
127
+ round += 1
128
+
129
+ const asking = round === 1 ? mine.map((one) => word(one.runs)) : batch(at, mine.map((one) => word(one.runs))).refused
130
+ const unhappy = mine.filter((one) => asking.includes(word(one.runs)))
131
+ if (unhappy.length === 0) return { ran, unsettled: [], rounds: round }
132
+
133
+ const still = unhappy.map((one) => word(one.runs))
134
+ const seen = before
135
+ if (seen !== undefined && still.length === seen.length && still.every((one) => seen.includes(one))) {
136
+ return { ran, unsettled: still, rounds: round }
137
+ }
138
+ before = still
139
+
140
+ const writing = batch(at, unhappy.map((one) => word(one.by)))
141
+ for (const said of writing.said) ran.push(said)
142
+ const refused = unhappy.filter((one) => writing.refused.includes(word(one.by))).map((one) => word(one.runs))
143
+
144
+ if (refused.length === unhappy.length) return { ran, unsettled: refused, rounds: round }
145
+ }
146
+
147
+ return { ran, unsettled: batch(at, mine.map((one) => word(one.runs))).refused, rounds: round }
148
+ }
package/dist/core/cli.js CHANGED
@@ -59,13 +59,48 @@ const commanded = (word, one) => {
59
59
  const opens = `Declares ${/^[aeiou]/.test(word) ? 'an' : 'a'} ${word}`;
60
60
  return meaning === '' ? `${opens}.` : `${opens} — ${meaning.slice(0, 1).toLowerCase()}${meaning.slice(1)}.`;
61
61
  };
62
+ const fielded = (word, one) => {
63
+ if (one === undefined || one.fields.length === 0)
64
+ return [];
65
+ const room = Math.max(...one.fields.map((each) => each.name.length));
66
+ const kind = (each) => each.list ? 'a list' : each.map ? 'a table' : each.boolean ? 'yes or no' : each.worded ? 'a word' : 'a value';
67
+ const base = ['id', 'at', 'describes', 'version'];
68
+ const order = (each) => {
69
+ const held = base.indexOf(each.name);
70
+ return held === -1 ? (each.required ? 100 : 200) : held;
71
+ };
72
+ return [
73
+ '',
74
+ `every ${word} row states these:`,
75
+ '',
76
+ ...[...one.fields].sort((a, b) => order(a) - order(b) || (a.name < b.name ? -1 : 1)).flatMap((each) => columned(each.name, `${each.required ? '' : 'optional, '}${kind(each)}${each.guide === '' ? '' : `. ${each.guide}`}`, room + 2)),
77
+ ];
78
+ };
79
+ const doing = (word, holds, answers, at) => {
80
+ const held = [
81
+ ...(holds === undefined
82
+ ? []
83
+ : [
84
+ ...columned(`${TYPED} create ${word}`, 'What it asks for, and everything already declared.', 34),
85
+ ...columned(`${TYPED} create ${word} read <mark>`, 'One of them in full. It writes nothing.', 34),
86
+ ]),
87
+ ...(answers?.by === undefined
88
+ ? []
89
+ : [
90
+ ...columned(answers.by, first(answers.describes ?? ''), 34),
91
+ ...(answers.runs === undefined ? [] : columned(answers.runs, 'Proves it is still what its command writes.', 34)),
92
+ ]),
93
+ ...(at === undefined ? [] : columned(`${TYPED} read ${at}`, 'Prints the file itself.', 34)),
94
+ ];
95
+ return held.length === 0 ? [] : ['', 'to do something with it:', ...held];
96
+ };
62
97
  const OWN = [
63
98
  {
64
99
  said: 'read <path>',
65
100
  describes: 'Print any file with line numbers, or list a directory. `--find <text>` searches every file in this tree. Reach for this first; `inspect <word>` reads rows instead, which is a different question.',
66
101
  },
67
- { said: 'init', describes: 'Write the tree this app is declared in, and its manifest.' },
68
- { said: 'attach <package>', describes: 'Take on what a package declares, and settle what that derives.' },
102
+ { said: 'init' },
103
+ { said: 'attach <package>' },
69
104
  { said: 'detach <package>', describes: 'Give a package back, withdrawing every row it put here.' },
70
105
  { said: 'create <noun>', describes: 'Declare a thing. Run it with no noun to see what this tree makes.' },
71
106
  { said: 'run <noun>', describes: 'Run a thing that was declared.' },
@@ -86,6 +121,54 @@ const OWN = [
86
121
  describes: 'Writes every derived file again from the rows, running each generator until a full pass changes nothing, and touches nothing you wrote. Run it when the package manifests fell out of step with the rows and pnpm refuses to start: it needs no pnpm in front of it, so `npx thread repair` in an app and `node --import tsx thread/core/cli.ts repair` in the framework repository both reach it.',
87
122
  },
88
123
  ];
124
+ let COINED;
125
+ const coining = () => {
126
+ const found = new Map();
127
+ try {
128
+ const pass = descend();
129
+ for (const [, threadings] of pass.threaded) {
130
+ for (const one of threadings) {
131
+ const id = one.row.id;
132
+ const says = one.row.describes;
133
+ if (typeof id === 'string' && typeof says === 'string' && says !== '' && !found.has(id))
134
+ found.set(id, says);
135
+ }
136
+ }
137
+ for (const [id, row] of pass.stated) {
138
+ const says = row.describes;
139
+ if (typeof says === 'string' && says !== '' && !found.has(id))
140
+ found.set(id, says);
141
+ }
142
+ }
143
+ catch {
144
+ return found;
145
+ }
146
+ return found;
147
+ };
148
+ const coined = () => (COINED ??= coining());
149
+ const verb = (one) => one.said.split(' ')[0] ?? one.said;
150
+ const framework = () => OWN.map((one) => ({ said: one.said, describes: coined().get(verb(one)) ?? one.describes ?? '' }));
151
+ const WORKS = [
152
+ 'Everything this app is, it DECLARES — in `_threads/`, one row per thing. You never write those rows',
153
+ 'by hand: `npx thread create <noun>` asks what it needs and writes one. Everything else is DERIVED',
154
+ 'from them — the schema, the migrations, the routes, the docs — by generators that re-run themselves',
155
+ 'whenever a row changes, so nothing has to be kept in step. `npx thread gate` refuses the tree if any',
156
+ 'of it has drifted, and a build reads that refusal.',
157
+ ];
158
+ const heading = (short) => [
159
+ ...framework().flatMap((one) => columned(one.said, short ? opening(one.describes) : one.describes)),
160
+ ...untold(),
161
+ ];
162
+ const untold = () => {
163
+ const held = OWN.map(verb).filter((one) => !coined().has(one));
164
+ if (held.length === 0)
165
+ return [];
166
+ return [
167
+ '',
168
+ ...over(`${held.join(', ')} — no row coins these, so the sentence beside each is typed into this command rather ` +
169
+ `than read from the tree. \`${TYPED} create concept\` declares one, and this page reads it from there.`, '', 88),
170
+ ];
171
+ };
89
172
  const WITHIN = '--in';
90
173
  function within(root, path) {
91
174
  const held = resolve(root, path);
@@ -464,9 +547,11 @@ async function listing() {
464
547
  stdout.write([
465
548
  `${TYPED} list`,
466
549
  '',
550
+ ...WORKS,
551
+ '',
467
552
  `every command this tree answers. These ${String(OWN.length)} are this framework's own and are here in every tree:`,
468
553
  '',
469
- ...OWN.flatMap((one) => columned(one.said, one.describes)),
554
+ ...heading(false),
470
555
  ...(broke === ''
471
556
  ? []
472
557
  : [
@@ -540,7 +625,7 @@ async function helping(word) {
540
625
  outright = undefined;
541
626
  unread = thrown instanceof Error ? thrown.message : String(thrown);
542
627
  }
543
- const own = OWN.find((one) => (one.said.split(' ')[0] ?? '') === word);
628
+ const own = framework().find((one) => verb(one) === word);
544
629
  const holds = told.get(word);
545
630
  const named = every.filter((one) => one.row.id === word);
546
631
  const answers = rows.find((one) => one.by === `thread ${word}` || one.by === `thread create ${word}` || one.by === `thread run ${word}`);
@@ -577,17 +662,9 @@ async function helping(word) {
577
662
  holds.holds === 0
578
663
  ? ` nothing is declared as one yet. They are listed in ${holds.at}`
579
664
  : ` ${String(holds.holds)} ${holds.holds === 1 ? 'is' : 'are'} declared, listed in ${holds.at}`,
580
- ...(holds.shape === undefined ? [] : [` every ${word} states the same things, and this asks for each of them`]),
581
665
  ]),
582
- '',
583
- 'to do something with it:',
584
- ...(holds === undefined
585
- ? []
586
- : columned(`${TYPED} create ${word}`, 'What it asks for, and everything already declared.', 34).concat(columned(`${TYPED} create ${word} read <mark>`, 'One of them in full. It writes nothing.', 34))),
587
- ...(answers?.by === undefined
588
- ? []
589
- : columned(answers.by, 'Runs it.', 34).concat(answers.runs === undefined ? [] : columned(answers.runs, 'Proves it is still what its command writes.', 34))),
590
- ...(named.length === 0 ? [] : columned(`${TYPED} read ${named[0]?.at ?? ''}`, 'Prints the file itself.', 34)),
666
+ ...fielded(word, holds),
667
+ ...doing(word, holds, answers, named[0]?.at),
591
668
  '',
592
669
  ].join('\n'));
593
670
  return 0;
@@ -678,7 +755,7 @@ async function offered() {
678
755
  exit(1);
679
756
  }
680
757
  }
681
- const MINE = 'packages';
758
+ const MINE = 'thread';
682
759
  const UNDER = 'generators';
683
760
  const MAKING = 'makers';
684
761
  function own(name, under = UNDER) {
@@ -687,7 +764,7 @@ function own(name, under = UNDER) {
687
764
  for (const entry of readdirSync(at, { withFileTypes: true })) {
688
765
  if (!entry.isDirectory() || entry.name === 'node_modules')
689
766
  continue;
690
- const made = join(at, entry.name, 'src', under, `${name}.ts`);
767
+ const made = join(at, entry.name, under, `${name}.ts`);
691
768
  try {
692
769
  readFileSync(made);
693
770
  found = made;
@@ -1345,7 +1422,7 @@ async function attachable(verb) {
1345
1422
  return verb === 'attach'
1346
1423
  ? [
1347
1424
  `nothing to attach — no \`${named('*')}\` package is installed here beyond the CLI itself.`,
1348
- `Install one with \`npm install ${named('<name>')}\` and run this again to see it listed.`,
1425
+ `Install one with \`npm install -D ${named('<name>')}\` — this framework is scaffolding, so it goes in \`devDependencies\` and a host installs none of it — then run this again to see it listed.`,
1349
1426
  ]
1350
1427
  : [
1351
1428
  'nothing to detach — this tree has taken on no package yet.',
@@ -1453,9 +1530,11 @@ async function run(name) {
1453
1530
  stderr.write([
1454
1531
  `${TYPED} <command>`,
1455
1532
  '',
1533
+ ...WORKS,
1534
+ '',
1456
1535
  `a tree is made, attached to, and then declared into. These ${String(OWN.length)} are this framework's own:`,
1457
1536
  '',
1458
- ...OWN.flatMap((one) => columned(one.said, opening(one.describes))),
1537
+ ...heading(true),
1459
1538
  ...(unread === ''
1460
1539
  ? []
1461
1540
  : [