@orkestrel/scaffold 0.0.103 → 0.0.104

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 (88) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
  3. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
  4. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
  5. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
  6. package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
  7. package/dist/bin/main.js +16 -9
  8. package/dist/bin/main.js.map +1 -1
  9. package/dist/host/agents/orchestration.md +3 -3
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  14. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
  15. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
  16. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  17. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
  20. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
  21. package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
  22. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
  23. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
  24. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
  25. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
  26. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
  27. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
  28. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
  29. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
  30. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
  31. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
  32. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
  33. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
  34. package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
  35. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
  36. package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
  37. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
  38. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
  39. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
  40. package/dist/host/agents/transports/codex.md +1 -1
  41. package/dist/host/claude/agents/orkestrel.md +54 -53
  42. package/dist/host/claude/agents/reviewer.md +4 -4
  43. package/dist/host/claude/rules/documentation.md +2 -1
  44. package/dist/host/claude/rules/patterns.md +1 -1
  45. package/dist/host/claude/rules/quality.md +2 -2
  46. package/dist/host/claude/rules/styles.md +1 -1
  47. package/dist/host/claude/rules/tests.md +34 -8
  48. package/dist/host/claude/rules/typescript.md +29 -0
  49. package/dist/host/claude/rules/workspace.md +32 -22
  50. package/dist/host/claude/rules/writing.md +34 -26
  51. package/dist/host/claude/settings.json +2 -0
  52. package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
  53. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
  54. package/dist/host/codex/agents/analyst.toml +3 -3
  55. package/dist/host/codex/agents/reviewer.toml +4 -4
  56. package/dist/host/configs/policy.ts +13 -2
  57. package/dist/host/dotfiles/editorconfig +0 -1
  58. package/dist/host/dotfiles/gitattributes +0 -1
  59. package/dist/host/dotfiles/gitignore +0 -1
  60. package/dist/host/dotfiles/oxlintrc.json +24 -0
  61. package/dist/host/guides/agent.md +1638 -598
  62. package/dist/host/guides/budget.md +89 -29
  63. package/dist/host/guides/form.md +477 -91
  64. package/dist/host/guides/mcp.md +14 -2
  65. package/dist/host/guides/ollama.md +97 -73
  66. package/dist/host/guides/probe.md +1 -1
  67. package/dist/host/guides/scaffold.md +158 -101
  68. package/dist/host/guides/study.md +2635 -0
  69. package/dist/host/guides/table.md +225 -15
  70. package/dist/host/guides/terminal.md +22 -20
  71. package/dist/host/guides/test.md +689 -255
  72. package/dist/host/manifest.json +87 -63
  73. package/dist/host/tests/config.test.ts +136 -82
  74. package/dist/host/tests/policy.test.ts +39 -1
  75. package/dist/host/tests/setupPolicy.ts +86 -28
  76. package/dist/src/core/index.cjs +1387 -611
  77. package/dist/src/core/index.cjs.map +1 -1
  78. package/dist/src/core/index.d.cts +35 -20
  79. package/dist/src/core/index.d.ts +35 -20
  80. package/dist/src/core/index.js +1385 -612
  81. package/dist/src/core/index.js.map +1 -1
  82. package/dist/src/server/index.cjs +12 -12
  83. package/dist/src/server/index.cjs.map +1 -1
  84. package/dist/src/server/index.d.cts +12 -12
  85. package/dist/src/server/index.d.ts +12 -12
  86. package/dist/src/server/index.js +12 -12
  87. package/dist/src/server/index.js.map +1 -1
  88. package/package.json +4 -4
@@ -0,0 +1,1700 @@
1
+ # Bindings census
2
+
3
+ Run this census before a sweep under `.claude/rules/typescript.md` § Bindings, and before a review
4
+ rules that code conforms to that section or to `.claude/rules/architecture.md` § Middleware. Read
5
+ each item's heading as the keys of the rule sentences it covers and the rule bullet that holds them.
6
+ Find candidates with the item's signatures, and rule each candidate by the item's reading.
7
+
8
+ ## Scope
9
+
10
+ - **Population.** Apply every item to the TypeScript and Vue sources of the scanned checkout:
11
+ `src/`, `app/`, `tests/`, `configs/`, `studies/`, the skill scripts the checkout carries,
12
+ `vite.config.ts`, and the code a template literal emits. Skip `node_modules/`, `dist/`, `tmp/`,
13
+ and ambient `.d.ts` files. In a scaffold consumer, also skip every file scaffold vendors: the
14
+ vendored test set and the configuration leaves.
15
+ - **Tool.** Run each signature with ripgrep's default engine, `rg -n`, and add `-U` where the item
16
+ says a pattern spans lines.
17
+ - **Ruling.** Treat a signature hit as a candidate, and rule it by reading its source. A text search
18
+ reports on text (`.claude/rules/quality.md` § Instruments); an AST pass over the TypeScript parser
19
+ is the exact instrument for `variables.assignments`.
20
+ - **Report.** Record each ruled candidate as `path:line`, the item's key, and the ruling.
21
+
22
+ ## Run the controls
23
+
24
+ Run an item's controls before trusting its clean result:
25
+
26
+ 1. Write each fixture of the item to a scratch file under `tmp/probes/census/`, named as the
27
+ fixture's label gives.
28
+ 2. Run the item's signatures over the scratch files, and record the hit lines of each file.
29
+ 3. Require every hit fixture to yield a candidate the reading keeps, and every clear fixture to yield
30
+ no candidate or only candidates the reading drops.
31
+ 4. Delete `tmp/probes/census/`.
32
+
33
+ When a hit fixture yields no kept candidate, or a clear fixture yields a kept one, repair the
34
+ signatures or the reading before you run the item over the checkout.
35
+
36
+ ## Keys outside the census
37
+
38
+ - Run no item for these keys: `variables.declarations`, `derivation.expression`,
39
+ `iteration.counter`, `try.helper`, `try.log`, `running.definition`, `lists.derived`, `logs.loop`,
40
+ `logs.scan`, `exemptions.reach`, `percall.entity`, `percall.lifetime`, `percall.relative`,
41
+ `entity.home`, `entity.pointers`, `slots.route`, `slots.exclusions`, `resets.home`, `bound.read`,
42
+ `bound.several`, `bound.selection`, `bound.latch`, `registry.clear`, `registry.scope`,
43
+ `immutability.registry`, and `immutability.helper`.
44
+ - Read `logs.forms` through `logs.final`, `logs.last`, and `percall.concurrent`, which it widens to
45
+ logs a `reduce` call returns. Read `bound.unit` through `bound.field`. Read `running.position`
46
+ through `running.cell`, `bound.field`, and `registry.reads`.
47
+
48
+ ## `variables.assignments` (**Variables**)
49
+
50
+ - **Precheck.** Run `\b(?:let|var)\s+[A-Za-z_$\[{]` first, and rule each hit in code, outside
51
+ comments and strings, under `variables.declarations`. Hold a parameter-only reading of this item
52
+ until every such declaration is ruled: after the precheck, a bare-identifier assignment can only
53
+ target a parameter, a `catch` binding, a declaration the precheck found, or a name TypeScript
54
+ refuses.
55
+ - **Signatures.**
56
+ - Assignment statements: `(?:^|[;{}]\s*)\s*[A-Za-z_$][\w$]*\s*(?:[-+*/%&|^]|\*\*|<<|>>>?|\?\?|\|\||&&)?=[^=>]`.
57
+ - Guarded assignments: `(?:\)|\belse|=>)\s*[A-Za-z_$][\w$]*\s*(?:[-+*/%&|^]|\*\*|<<|>>>?|\?\?|\|\||&&)?=[^=>]`.
58
+ - Updates: `(?:^|[^.#\w$])[A-Za-z_$][\w$]*\s*(?:\+\+|--)` and `(?:\+\+|--)\s*[A-Za-z_$][\w$]*\b`.
59
+ - Destructuring targets: `^\s*\[[^\]]*\]\s*=[^=]` and `^\s*\(\s*\{[^}]*\}\s*=[^=]`.
60
+ - Declaration-free loop heads: `for\s*(?:await\s*)?\(\s*[A-Za-z_$][\w$]*\s+(?:of|in)\b`.
61
+ - **Reading.** Keep a hit whose target resolves to a parameter of the enclosing function or to a
62
+ `catch` binding (**Variables**). Drop a parameter default on its own line, a public class field,
63
+ an enum member, a member update such as `++this.#count`, and a target the precheck found.
64
+
65
+ Hit fixture, `variables.assignments.hit.ts`:
66
+
67
+ ```ts
68
+ function normalizeName(name: string): string {
69
+ name = name.trim()
70
+ return name
71
+ }
72
+
73
+ function labelRow(label: string): string {
74
+ if (label === '') label = 'untitled'
75
+ return label
76
+ }
77
+
78
+ function mergeOverride(base: ProjectConfig, override?: ProjectConfig): ProjectConfig {
79
+ if (override !== undefined && 'command' in override && 'mode' in override) {
80
+ if (typeof override.mode !== 'string') {
81
+ throw new Error('The project invocation carries no string mode')
82
+ }
83
+ override = { mode: override.mode }
84
+ }
85
+ return override === undefined ? { ...base } : mergeConfig(base, override)
86
+ }
87
+ ```
88
+
89
+ Clear fixture, `variables.assignments.clear.ts`:
90
+
91
+ ```ts
92
+ class Transport {
93
+ #count = 0
94
+
95
+ async readWithRetries(
96
+ url: string,
97
+ timeout: number,
98
+ spent: number[],
99
+ accept?: string,
100
+ binary = false,
101
+ ): Promise<TextReadResult | BytesReadResult> {
102
+ const id = ++this.#count
103
+ return binary ? readBytes(url, timeout, spent, id) : readText(url, timeout, spent, id, accept)
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## `running.carriers` (**Running values**)
109
+
110
+ - **Signatures.**
111
+ - Mutating calls: `\b[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])*\.(?:push|unshift|splice|set|add|delete|clear|fill|sort|reverse|copyWithin)\(`.
112
+ - Property and index writes: `\b[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])+\s*(?:[-+*/%&|^]|\?\?|\|\||&&)?=[^=>]`.
113
+ - Property updates: `\b[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\[[^\]]+\])+\s*(?:\+\+|--)`.
114
+ - Route a recorder to `logs.reads`: a `createRecorder` result, or any object whose `handler`
115
+ appends to its `calls`. Its writes run through `handler`, which no signature here matches.
116
+ - **Reading.**
117
+ - Resolve each hit's root identifier. Keep it when the root is a parameter, a `const`, or the
118
+ accumulator, the first parameter, of a `reduce` or `reduceRight` callback.
119
+ - Drop the roots `this`, `globalThis`, `process`, and host or DOM objects.
120
+ - Rule a kept hit banned under **Running values** when the change carries a flag, a counter, a
121
+ sum, a latest value, a one-slot cell, or appended text from one step or call to a later one, and
122
+ the collection is neither a log under **Lists and logs** nor an item of **Exemptions**. Drop a
123
+ helper's append to its owning operation's log, which **Immutability exceptions** admits.
124
+
125
+ Hit fixture, `running.carriers.hit.ts`:
126
+
127
+ ```ts
128
+ async function readBody(
129
+ reader: ReadableStreamDefaultReader<Uint8Array>,
130
+ spent: number[],
131
+ allowance: number,
132
+ ): Promise<boolean> {
133
+ for (;;) {
134
+ const chunk = await reader.read()
135
+ if (chunk.done) return true
136
+ spent.push(chunk.value.byteLength)
137
+ if (allowance - computeTotal(spent) < 0) {
138
+ await reader.cancel()
139
+ return false
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ Clear fixture, `running.carriers.clear.ts`:
146
+
147
+ ```ts
148
+ interface Step {
149
+ readonly step: string
150
+ readonly exit: number | undefined
151
+ }
152
+
153
+ interface Runner {
154
+ readonly dry: boolean
155
+ readonly steps: Step[]
156
+ }
157
+
158
+ interface Edit {
159
+ readonly start: number
160
+ readonly text: string
161
+ }
162
+
163
+ function runStep(runner: Runner, step: string, execute: () => number): void {
164
+ if (runner.dry) {
165
+ runner.steps.push({ step, exit: undefined })
166
+ return
167
+ }
168
+ runner.steps.push({ step, exit: execute() })
169
+ }
170
+
171
+ function describeRun(runner: Runner): string {
172
+ return runner.steps.map(({ step, exit }) => `${step}: ${String(exit)}`).join('\n')
173
+ }
174
+
175
+ function collectEdits(tokens: readonly Token[]): readonly Edit[] {
176
+ return tokens.reduce<readonly Edit[]>(
177
+ (log, token) =>
178
+ token.value === undefined ? log : [...log, { start: token.index, text: token.value }],
179
+ [],
180
+ )
181
+ }
182
+ ```
183
+
184
+ ## `running.cell` and `running.position` (**Running values**)
185
+
186
+ - **Signatures.** `\[0\]`, `\.at\(\s*-1\s*\)`, `\[\s*[\w.$]+\.length\s*-\s*1\s*\]`,
187
+ `const\s*\[\s*[A-Za-z_$][\w$]*\s*\]\s*=\s*[A-Za-z_$][\w$.]*\s*$`,
188
+ `const\s*\[\s*[A-Za-z_$][\w$]*\s*\]\s*=\s*[A-Za-z_$][\w$.]*\.(?:keys|values|entries)\(\)`, and
189
+ `\.(?:values|keys|entries)\(\)\.next\(\)`.
190
+ - **Reading.** Keep a hit whose collection a variable, a parameter, or an accumulator holds, and that
191
+ holds at most one entry at every read (**Running values**). The signs are a reset before each add
192
+ (`length = 0`, `splice(0`, `clear()`), a cap, or a single write. Drop a read of a source list that
193
+ nothing carries, and a `#` registry read, which `registry.reads` rules.
194
+
195
+ Hit fixture, `running.cell.hit.ts`:
196
+
197
+ ```ts
198
+ function readLatest(
199
+ readings: readonly Reading[],
200
+ admit: (reading: Reading) => boolean,
201
+ ): Reading | undefined {
202
+ const latest: Reading[] = []
203
+ for (const reading of readings) {
204
+ if (admit(reading)) latest.splice(0, latest.length, reading)
205
+ }
206
+ return latest[0]
207
+ }
208
+ ```
209
+
210
+ Clear fixture, `running.cell.clear.ts`:
211
+
212
+ ```ts
213
+ function readLast(readings: readonly Reading[]): Reading | undefined {
214
+ return readings.at(-1)
215
+ }
216
+ ```
217
+
218
+ ## `logs.final` (**Lists and logs**)
219
+
220
+ - **Signatures.**
221
+ - Replacement: `\.with\(`, `\.toSpliced\(`, `\.slice\(\s*0\s*,\s*-1\s*\)`, `\.splice\(`, and
222
+ `\[\s*[\w.$]+\.length\s*-\s*1\s*\]\s*=[^=]`.
223
+ - Text growth: `\$\{\s*[\w.$]+\.text\s*\}` and `\.text\s*\+`.
224
+ - **Reading.** Keep a hit whose list is a log, meaning each step appends its own result, whether the
225
+ loop pushes it or a `reduce` returns it, and whose step replaces, rewrites, or removes a record, or
226
+ builds a record's text from an earlier record's text (**Lists and logs**). Drop a fold's working
227
+ stack or frontier, which its steps pop.
228
+
229
+ Hit fixture, `logs.final.hit.ts`:
230
+
231
+ ```ts
232
+ interface Row {
233
+ readonly line: number
234
+ readonly text: string
235
+ }
236
+
237
+ function stepRows(rows: readonly Row[], text: string, line: number): readonly Row[] {
238
+ const content = text.trim()
239
+ const last = rows.at(-1)
240
+ if (content.startsWith('- ') && last !== undefined) {
241
+ return rows.with(-1, { ...last, text: `${last.text} ${content}`.trimEnd() })
242
+ }
243
+ return [...rows, { line, text: content }]
244
+ }
245
+
246
+ function readRows(lines: readonly string[]): readonly Row[] {
247
+ return lines.reduce<readonly Row[]>((rows, text, index) => stepRows(rows, text, index + 1), [])
248
+ }
249
+ ```
250
+
251
+ Clear fixture, `logs.final.clear.ts`:
252
+
253
+ ```ts
254
+ interface Edit {
255
+ readonly start: number
256
+ readonly text: string
257
+ }
258
+
259
+ interface Frame {
260
+ readonly indent: number
261
+ readonly key: string
262
+ }
263
+
264
+ function collectEdits(tokens: readonly Token[]): readonly Edit[] {
265
+ return tokens.reduce<readonly Edit[]>((log, token) => {
266
+ if (log.at(-1)?.start === token.index) return log
267
+ return [...log, { start: token.index, text: token.text }]
268
+ }, [])
269
+ }
270
+
271
+ function stepStack(stack: readonly Frame[], indent: number, key: string): readonly Frame[] {
272
+ const open = stack.filter((frame) => frame.indent < indent)
273
+ return [...open, { indent, key }]
274
+ }
275
+ ```
276
+
277
+ ## `derivation.text` (**Derivation**)
278
+
279
+ - **Signatures**, each with `-U`:
280
+ - ``\.reduce(?:<[^>]*>)?\(\s*\(\s*[A-Za-z_$][\w$]*\s*,[^)]*\)\s*=>\s*(?:[A-Za-z_$][\w$]*\s*\+|`\$\{)``;
281
+ - `\.reduce(?:<[^>]*>)?\([\s\S]{0,400}?,\s*(?:''|"")\s*\)`;
282
+ - `\.reduce<\s*string\b`, a fold typed to carry text;
283
+ - `\.reduce(?:<[^>]*>)?\(\s*\(\s*[A-Za-z_$][\w$]*\s*,[^)]*\)\s*=>[\s\S]{0,400}?\b[A-Za-z_$][\w$]*\.text\s*\+`,
284
+ a fold whose record carries text.
285
+ - **Reading.** Keep a fold whose step returns its carried text with a part appended (**Derivation**):
286
+ `text + part`, a template that opens with the carried text, a helper that appends to it such as
287
+ `joinThinking(joined, part)`, or a returned record whose text field extends the carried one. Drop a
288
+ fold whose step replaces a range of a fixed source text.
289
+
290
+ Hit fixture, `derivation.text.hit.ts`:
291
+
292
+ ```ts
293
+ function foldBlock(lines: readonly string[]): string {
294
+ return lines.reduce(
295
+ (running, line) =>
296
+ line === ''
297
+ ? { text: running.text, blanks: running.blanks + 1 }
298
+ : {
299
+ text:
300
+ running.text +
301
+ (running.text === ''
302
+ ? ''
303
+ : running.blanks === 0
304
+ ? ' '
305
+ : '\n'.repeat(running.blanks)) +
306
+ line,
307
+ blanks: 0,
308
+ },
309
+ { text: '', blanks: 0 },
310
+ ).text
311
+ }
312
+
313
+ function renderLines(lines: readonly string[]): string {
314
+ return lines.reduce((text, line) => `${text}${line}\n`, '')
315
+ }
316
+
317
+ function joinPasses(passes: readonly Pass[]): string | undefined {
318
+ return passes.reduce<string | undefined>(
319
+ (joined, pass) => (pass.thinking === undefined ? joined : joinThinking(joined, pass.thinking)),
320
+ undefined,
321
+ )
322
+ }
323
+ ```
324
+
325
+ Clear fixture, `derivation.text.clear.ts`:
326
+
327
+ ```ts
328
+ function applyEdits(source: string, edits: readonly Edit[]): string {
329
+ return [...edits]
330
+ .sort((left, right) => right.start - left.start)
331
+ .reduce((text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), source)
332
+ }
333
+ ```
334
+
335
+ ## `logs.last` and `logs.reads` (**Lists and logs**)
336
+
337
+ This item also rules the step-keyed maps `exemptions.memo` routes here.
338
+
339
+ - **Signatures.**
340
+ - Last-entry and length reads: `\.get\(\s*this\.#retries\s*\)`, `\.get\([^)]*length\s*-\s*1\s*\)`,
341
+ `\.at\(\s*-1\s*\)`, `\[\s*[\w.$]+\.length\s*-\s*1\s*\]`, `\.length\s*(?:[<>]=?|===?|!==?)\s*\d`,
342
+ and `toHaveLength\(`.
343
+ - Log, map, and recorder declarations:
344
+ `const\s+[A-Za-z_$][\w$]*\s*(?::[^=]+)?=\s*(?:\[\]|new Map\b|createRecorder\b)`. For each, list
345
+ the name's reads, following it through its aliases, the calls it is passed to, and the callers it
346
+ is returned to.
347
+ - Recorder reads: `\.count\b`, `\.calls\.length\b`, `\.calls\s*\)\s*\.toHaveLength\(`,
348
+ `\.calls\.at\(\s*-1\s*\)`, and `\.calls\[\s*[\w.$]+\.(?:length|count)\s*-\s*1\s*\]`. Resolve each
349
+ hit's root to its declaration, which can be a `createRecorder` call, a setup-module factory, or a
350
+ class whose `calls` the `handler` appends to.
351
+ - **Reading.**
352
+ - Keep a log, or a `Map` keyed by loop index or attempt, whose every read along those paths is its
353
+ last entry, its length, or `get` of its last key (**Lists and logs**). Count passing or returning
354
+ the log as no read. Drop a log that some read consumes whole (a spread, `map`, `filter`, `some`,
355
+ `join`, iteration, or a whole-list comparison), even when a step also looks back at its last
356
+ record.
357
+ - Treat a recorder's `calls` as its log and its `count` as that log's length, and follow it through
358
+ aliases, the calls it is passed to, and the callers it is returned to. Keep a recorder whose every
359
+ read is its `count`, its `calls` length, `toHaveLength` on its `calls`, or its last call. Drop a
360
+ recorder that some read consumes whole, such as a whole-history comparison of its `calls`, a `map`
361
+ or iteration over them, or a spread. Drop a `count` that belongs to an entity tally rather than a
362
+ recorder.
363
+
364
+ Hit fixture, `logs.last.hit.ts`:
365
+
366
+ ```ts
367
+ class Reader {
368
+ readonly #retries: number
369
+
370
+ constructor(retries: number) {
371
+ this.#retries = retries
372
+ }
373
+
374
+ async read(url: string): Promise<string> {
375
+ const notes = new Map<number, string>()
376
+ for (const round of Array.from({ length: this.#retries + 1 }).keys()) {
377
+ const outcome = await fetchText(url)
378
+ if (outcome.ok) return outcome.content
379
+ notes.set(round, outcome.note)
380
+ }
381
+ throw new Error(`The read at ${url} produced no answer: ${notes.get(this.#retries) ?? ''}`)
382
+ }
383
+ }
384
+
385
+ function createJudge(asked: string[], answer: (count: number) => Promise<Verdict>): Judge {
386
+ return {
387
+ ask: (request) => {
388
+ asked.push(...Object.keys(request.questions))
389
+ return answer(asked.length)
390
+ },
391
+ }
392
+ }
393
+
394
+ it('asks the following question after the judge aborts', async () => {
395
+ const asked: string[] = []
396
+ await classify(createJudge(asked, () => Promise.reject(new Error('The judge aborted'))))
397
+ expect(asked).toHaveLength(2)
398
+ })
399
+ ```
400
+
401
+ Clear fixture, `logs.last.clear.ts`:
402
+
403
+ ```ts
404
+ function collectEdits(tokens: readonly Token[]): readonly Edit[] {
405
+ return tokens.reduce<readonly Edit[]>((log, token) => {
406
+ if (log.at(-1)?.start === token.index) return log
407
+ return [...log, { name: token.name, start: token.index }]
408
+ }, [])
409
+ }
410
+
411
+ function declares(tokens: readonly Token[], name: string): boolean {
412
+ return collectEdits(tokens).some((edit) => edit.name === name)
413
+ }
414
+
415
+ function establish(missing: readonly string[], located: Anchor): Established {
416
+ const created: Anchor[] = []
417
+ for (const segment of [...missing].reverse()) {
418
+ const anchor = created.at(-1) ?? located
419
+ if (!matchesAnchor(anchor)) {
420
+ throw new Error(`The directory at ${anchor.path} changed while writing.`)
421
+ }
422
+ mkdirSync(segment)
423
+ created.push(readAnchor(segment))
424
+ }
425
+ return { anchor: created.at(-1) ?? located, created }
426
+ }
427
+
428
+ it('establishes a nested directory segment by segment', () => {
429
+ const result = establish(['/workspace/project/.claude', '/workspace/project'], ROOT_ANCHOR)
430
+ expect(result.created.map((anchor) => anchor.path)).toEqual([
431
+ '/workspace/project',
432
+ '/workspace/project/.claude',
433
+ ])
434
+ })
435
+ ```
436
+
437
+ Hit fixture, `logs.reads.hit.ts`:
438
+
439
+ ```ts
440
+ it('emits a change for each write', () => {
441
+ const seen = createRecorder<[string]>()
442
+ const emitter = createEmitter()
443
+ emitter.on('change', seen.handler)
444
+ emitter.emit('change', 'first')
445
+ emitter.emit('change', 'second')
446
+ expect(seen.count).toBe(2)
447
+ })
448
+
449
+ function expectOneSave(store: DraftStore): void {
450
+ const saved = createRecorder<[string]>()
451
+ store.on('save', saved.handler)
452
+ store.save('draft')
453
+ expect(saved.calls).toHaveLength(1)
454
+ }
455
+
456
+ it('saves a draft once', () => {
457
+ expectOneSave(createDraftStore())
458
+ })
459
+ ```
460
+
461
+ Clear fixture, `logs.reads.clear.ts`:
462
+
463
+ ```ts
464
+ it('emits each write in order', () => {
465
+ const seen = createRecorder<[string]>()
466
+ const emitter = createEmitter()
467
+ emitter.on('change', seen.handler)
468
+ emitter.emit('change', 'first')
469
+ emitter.emit('change', 'second')
470
+ expect(seen.count).toBe(2)
471
+ expect(seen.calls).toStrictEqual([['first'], ['second']])
472
+ })
473
+ ```
474
+
475
+ ## `percall.concurrent` and `logs.writers` (**Per-call state**, **Lists and logs**)
476
+
477
+ - **Signatures.**
478
+ - Promise combinators: `Promise\.(?:all|allSettled|any|race)\(`. Inside the `async` callbacks
479
+ passed to one, look for `\.(?:push|unshift|set|add)\(` on a name the callback does not declare.
480
+ - Worker pools: `Array\.from\(\s*\{\s*length:`, with each collection parameter traced from the
481
+ pool's caller into the workers.
482
+ - Several event sources: `\.on\(\s*['"](?:data|message|chunk|error)['"]`, where more than one
483
+ source pushes into one list.
484
+ - **Reading.** Keep a list that more than one concurrently pending async path of one call appends
485
+ to, or that the callbacks of more than one event source append to (**Per-call state**,
486
+ **Lists and logs**). Drop a list one loop owns, one worker's own log, and the callbacks of one
487
+ event source in emission order.
488
+
489
+ Hit fixture, `percall.concurrent.hit.ts`:
490
+
491
+ ```ts
492
+ class Upstream {
493
+ readonly #concurrency: number
494
+
495
+ constructor(concurrency: number) {
496
+ this.#concurrency = concurrency
497
+ }
498
+
499
+ async lookup(names: readonly string[]): Promise<readonly Release[]> {
500
+ const spent: number[] = []
501
+ return this.#gather(names, (name) => this.#release(name, spent))
502
+ }
503
+
504
+ async #release(name: string, spent: number[]): Promise<Release> {
505
+ const body = await fetchBytes(name)
506
+ spent.push(body.byteLength)
507
+ return { name, bytes: body.byteLength }
508
+ }
509
+
510
+ async #gather<T, R>(items: readonly T[], run: (item: T) => Promise<R>): Promise<readonly R[]> {
511
+ const entries = items.entries()
512
+ const workers = Array.from({ length: Math.min(this.#concurrency, items.length) }, () =>
513
+ this.#drain(entries, run),
514
+ )
515
+ const settled = await Promise.allSettled(workers)
516
+ return settled.flatMap((worker) => (worker.status === 'fulfilled' ? worker.value : []))
517
+ }
518
+
519
+ async #drain<T, R>(
520
+ entries: IterableIterator<[number, T]>,
521
+ run: (item: T) => Promise<R>,
522
+ ): Promise<readonly R[]> {
523
+ const produced: R[] = []
524
+ for (const [, item] of entries) produced.push(await run(item))
525
+ return produced
526
+ }
527
+ }
528
+ ```
529
+
530
+ Clear fixture, `percall.concurrent.clear.ts`:
531
+
532
+ ```ts
533
+ class Upstream {
534
+ readonly #concurrency: number
535
+ readonly #retries: number
536
+
537
+ constructor(concurrency: number, retries: number) {
538
+ this.#concurrency = concurrency
539
+ this.#retries = retries
540
+ }
541
+
542
+ async lookup(names: readonly string[]): Promise<readonly Release[]> {
543
+ return this.#gather(names, (name) => this.#release(name))
544
+ }
545
+
546
+ async #release(name: string): Promise<Release> {
547
+ for (const round of Array.from({ length: this.#retries + 1 }).keys()) {
548
+ const outcome = await fetchBytes(name, round)
549
+ if (outcome.ok) return { name, bytes: outcome.body.byteLength }
550
+ }
551
+ return { name, bytes: 0 }
552
+ }
553
+
554
+ async #gather<T, R>(items: readonly T[], run: (item: T) => Promise<R>): Promise<readonly R[]> {
555
+ const entries = items.entries()
556
+ const workers = Array.from({ length: Math.min(this.#concurrency, items.length) }, () =>
557
+ this.#drain(entries, run),
558
+ )
559
+ const settled = await Promise.allSettled(workers)
560
+ return settled.flatMap((worker) => (worker.status === 'fulfilled' ? worker.value : []))
561
+ }
562
+
563
+ async #drain<T, R>(
564
+ entries: IterableIterator<[number, T]>,
565
+ run: (item: T) => Promise<R>,
566
+ ): Promise<readonly R[]> {
567
+ const produced: R[] = []
568
+ for (const [, item] of entries) produced.push(await run(item))
569
+ return produced
570
+ }
571
+ }
572
+ ```
573
+
574
+ ## `exemptions.traversal`, `exemptions.step`, and `exemptions.cursor` (**Exemptions**)
575
+
576
+ - **Signatures.**
577
+ `for\s*\(\s*const\s+(?:\[[^\]]*\]|[A-Za-z_$][\w$]*)\s+of\s+[A-Za-z_$][\w$]*(?:\.entries\(\)|\.keys\(\)|\.values\(\))?\s*\)`
578
+ and `while\s*\(\s*[A-Za-z_$][\w$]*\.length\b`. Keep the loops whose body adds to the collection the
579
+ loop iterates or tests, through `.push(`, `.unshift(`, `.set(`, or `.add(`.
580
+ - **Reading.** Count as one step a complete iteration that removes or visits an entry of the
581
+ worklist, its nested loops included, and count the entries each control path through that step can
582
+ add (**Exemptions**). Rule a cursor when no path adds more than one entry and none spreads a list
583
+ (`push(...`). Drop a loop whose step can add several entries.
584
+
585
+ Hit fixture, `exemptions.traversal.hit.ts`:
586
+
587
+ ```ts
588
+ function resolveRealPath(path: string): string | undefined {
589
+ const trail = [resolve(path)]
590
+ const pending: string[] = []
591
+ for (const [depth, current] of trail.entries()) {
592
+ if (depth > MAX_PATH_DEPTH) break
593
+ const real = attempt(() => realpathSync(current))
594
+ if (real.success) {
595
+ return pending.reduce((physical, segment) => join(physical, segment), real.value)
596
+ }
597
+ const status = attempt(() => lstatSync(current))
598
+ if (status.success) {
599
+ if (!status.value.isSymbolicLink()) return undefined
600
+ trail.push(resolve(dirname(current), readlinkSync(current)))
601
+ continue
602
+ }
603
+ const parent = dirname(current)
604
+ if (parent === current) return undefined
605
+ pending.unshift(relative(parent, current))
606
+ trail.push(parent)
607
+ }
608
+ return undefined
609
+ }
610
+
611
+ function renderCauseChain(error: unknown): string {
612
+ const chain: unknown[] = error === undefined ? [] : [error]
613
+ while (chain.length < 4) {
614
+ const last = chain.at(-1)
615
+ const cause = isError(last) ? last.cause : undefined
616
+ if (cause === undefined) break
617
+ chain.push(cause)
618
+ }
619
+ return chain
620
+ .map((one) => (isError(one) ? `${one.name}: ${one.message}` : String(one)))
621
+ .join(' <- ')
622
+ }
623
+ ```
624
+
625
+ Clear fixture, `exemptions.traversal.clear.ts`:
626
+
627
+ ```ts
628
+ function listFiles(root: string): readonly string[] {
629
+ const pending = [{ full: root, path: '' }]
630
+ const entries: Array<{ readonly path: string; readonly file: boolean }> = []
631
+ while (pending.length > 0) {
632
+ const current = pending.pop()
633
+ if (current === undefined) break
634
+ const handle = opendirSync(current.full)
635
+ for (;;) {
636
+ const entry = handle.readSync()
637
+ if (entry === null) break
638
+ const path = current.path === '' ? entry.name : `${current.path}/${entry.name}`
639
+ const file = !entry.isDirectory() || entry.isSymbolicLink()
640
+ entries.push({ path, file })
641
+ if (!file) pending.push({ full: join(current.full, entry.name), path })
642
+ }
643
+ handle.closeSync()
644
+ }
645
+ return entries.filter(({ file }) => file).map(({ path }) => path)
646
+ }
647
+
648
+ function scanAmendments(
649
+ amendments: ReadonlyMap<string, readonly string[]>,
650
+ start: string,
651
+ admit: (id: string) => boolean,
652
+ ): readonly string[] {
653
+ const queue = [start]
654
+ const seen = new Set<string>()
655
+ const admitted: string[] = []
656
+ for (const id of queue) {
657
+ if (seen.has(id)) continue
658
+ seen.add(id)
659
+ if (!admit(id)) continue
660
+ admitted.push(id)
661
+ queue.push(...(amendments.get(id) ?? []))
662
+ }
663
+ return admitted
664
+ }
665
+
666
+ function validateRuntime(instrument: string, roots: readonly string[]): void {
667
+ const pending = [instrument]
668
+ const visited = new Set<string>()
669
+ while (pending.length > 0) {
670
+ const root = pending.pop()
671
+ if (root === undefined || visited.has(root)) continue
672
+ visited.add(root)
673
+ if (!roots.includes(root)) throw new Error(`Profile roots omit a runtime dependency: ${root}`)
674
+ const manifest = join(root, 'package.json')
675
+ const value: unknown = JSON.parse(readFileSync(manifest, 'utf8'))
676
+ if (!isRecord(value) || !isRecord(value.dependencies)) continue
677
+ const resolver = createRequire(manifest)
678
+ for (const dependency of Object.keys(value.dependencies))
679
+ pending.push(dirname(realpathSync(resolver.resolve(`${dependency}/package.json`))))
680
+ }
681
+ }
682
+
683
+ function capturePackages(selections: ReadonlyMap<string, string>): readonly InstallPackage[] {
684
+ const sources = new Map(selections)
685
+ const packages: InstallPackage[] = []
686
+ for (const [name, source] of sources) {
687
+ const manifest = readManifest(source)
688
+ packages.push(Object.freeze({ name, source }))
689
+ const require = createRequire(join(source, 'package.json'))
690
+ for (const dependency of Object.keys(manifest.dependencies ?? {}))
691
+ if (!sources.has(dependency))
692
+ sources.set(dependency, dirname(require.resolve(`${dependency}/package.json`)))
693
+ }
694
+ return Object.freeze(packages)
695
+ }
696
+ ```
697
+
698
+ ## `exemptions.memo` and `exemptions.position` (**Exemptions**)
699
+
700
+ - **Signatures.** Every `new Map\b` that a variable or a `#` field holds, and every parameter typed
701
+ as a map, `[A-Za-z_$][\w$]*\??\s*:\s*Map<`. Then the map's `\.set\(` and `\.delete\(` sites, and
702
+ `\.get\([^)]*\)\s*[!?]?\.(?:push|set|add|delete|splice|unshift)\(`.
703
+ - **Reading.** Keep a map treated as a memo that does any of these (**Exemptions**):
704
+ - writes a key twice, through a `set` path that no `has` or `get` miss for that key dominates;
705
+ - deletes a key;
706
+ - changes a stored value;
707
+ - caches no computation's result under its input or identity key for reuse within one owning
708
+ operation, in which case **Lists and logs** rules it.
709
+
710
+ Route a map keyed by loop step or attempt to `logs.last`. Drop a map that meets the memo item, and
711
+ a map that **Lists and logs** or **Registries** governs.
712
+
713
+ Hit fixture, `exemptions.memo.hit.ts`:
714
+
715
+ ```ts
716
+ function readLines(paths: readonly string[], cache: Map<string, string[]>): readonly string[] {
717
+ return paths.flatMap((path) => {
718
+ const cached = cache.get(path)
719
+ if (cached !== undefined) return cached
720
+ const lines = readFileSync(path, 'utf8').split('\n')
721
+ cache.set(path, lines)
722
+ return lines
723
+ })
724
+ }
725
+
726
+ function appendLine(cache: Map<string, string[]>, path: string, line: string): void {
727
+ cache.get(path)?.push(line)
728
+ }
729
+ ```
730
+
731
+ Clear fixture, `exemptions.memo.clear.ts`:
732
+
733
+ ```ts
734
+ function placeMember(
735
+ input: ProjectionInput,
736
+ id: string,
737
+ seen: ReadonlySet<string>,
738
+ settled?: Map<string, ReadonlySet<string>>,
739
+ ): ReadonlySet<string> {
740
+ const known = settled?.get(id)
741
+ if (known !== undefined) return known
742
+ const sides = (input.amending.get(id) ?? []).filter((side) => side !== id && !seen.has(side))
743
+ const keys = new Set(
744
+ sides.flatMap((side) => [...placeMember(input, side, new Set([...seen, id]), settled)]),
745
+ )
746
+ if (settled !== undefined && sides.every((side) => settled.has(side))) settled.set(id, keys)
747
+ return keys
748
+ }
749
+
750
+ function listCollected(
751
+ args: readonly string[],
752
+ cache: Map<string, readonly Collected[]>,
753
+ ): readonly Collected[] | undefined {
754
+ const key = JSON.stringify(args)
755
+ const cached = cache.get(key)
756
+ if (cached !== undefined) return cached
757
+ const collected = runListing(args)
758
+ if (collected === undefined) return undefined
759
+ cache.set(key, collected)
760
+ return collected
761
+ }
762
+
763
+ function mapGateFiles(gates: ReadonlyMap<string, readonly string[]>): ReadonlyMap<string, string> {
764
+ const files = new Map<string, string>()
765
+ for (const [gate, command] of gates) {
766
+ for (const argument of command) {
767
+ if (/^tests\/[A-Za-z0-9_./-]+\.test\.ts$/u.test(argument) && !files.has(argument))
768
+ files.set(argument, gate)
769
+ }
770
+ }
771
+ return files
772
+ }
773
+ ```
774
+
775
+ ## `exemptions.branches` and `exemptions.platform` (**Exemptions**)
776
+
777
+ - **Signatures.** Per function body, list `\b[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*\s*=[^=]`,
778
+ `\b[A-Za-z_$][\w$]*\[['"][^'"]+['"]\]\s*=[^=]`, and `\.set\(\s*['"][^'"]+['"]`. Then group the
779
+ hits by object and key.
780
+ - **Reading.** Keep a key that straight-line branches write twice, or that one fixture writes twice
781
+ (**Exemptions**). Drop a write a platform API requires, such as a `Headers` method or a DOM
782
+ property.
783
+
784
+ Hit fixture, `exemptions.branches.hit.ts`:
785
+
786
+ ```ts
787
+ function buildOptions(strict: boolean, legacy: boolean): BuildOptions {
788
+ const options: { mode?: string } = {}
789
+ if (strict) options.mode = 'strict'
790
+ if (legacy) options.mode = 'legacy'
791
+ return options
792
+ }
793
+ ```
794
+
795
+ Clear fixture, `exemptions.branches.clear.ts`:
796
+
797
+ ```ts
798
+ function collectQuestions(target: string, writing: boolean): readonly Question[] {
799
+ const questions: Question[] = []
800
+ if (hasVueComponents(target)) {
801
+ questions.push({
802
+ field: 'extensions',
803
+ message:
804
+ 'Move Vue components from app/browser to app/vue before regenerating this workspace.',
805
+ blocking: writing,
806
+ })
807
+ }
808
+ if (!writing) {
809
+ const script = readScriptQuestion(target)
810
+ if (script !== undefined) questions.push(script)
811
+ }
812
+ const project = readProjectQuestion(target, writing)
813
+ if (project !== undefined) questions.push(project)
814
+ return questions
815
+ }
816
+ ```
817
+
818
+ ## `percall.builtins` (**Per-call state**)
819
+
820
+ - **Signatures.** In an `async` function, or one that registers callbacks, find every local
821
+ `const\s+[A-Za-z_$][\w$]*\s*(?::[^=]+)?=\s*(?:\{|\[|new (?:Map|Set)\b)`. Keep the ones whose name a
822
+ `running.carriers` write signature hits inside a callback (`=>`) or after an `await`.
823
+ - **Reading.** Keep a plain array, record, `Map`, or `Set` that the awaits or callbacks of one call
824
+ change to carry state other than a log (**Per-call state**). Drop a log, an **Exemptions** item,
825
+ an iterator, and an `AbortController` instance.
826
+
827
+ Hit fixture, `percall.builtins.hit.ts`:
828
+
829
+ ```ts
830
+ async function readAll(urls: readonly string[]): Promise<readonly string[]> {
831
+ const budget = { remaining: BUDGET_BYTES }
832
+ return Promise.all(
833
+ urls.map(async (url) => {
834
+ const body = await fetchText(url)
835
+ budget.remaining -= body.length
836
+ if (budget.remaining < 0) throw new Error(`The read of ${url} spent the byte allowance`)
837
+ return body
838
+ }),
839
+ )
840
+ }
841
+ ```
842
+
843
+ Clear fixture, `percall.builtins.clear.ts`:
844
+
845
+ ```ts
846
+ class Pool {
847
+ readonly #concurrency: number
848
+
849
+ constructor(concurrency: number) {
850
+ this.#concurrency = concurrency
851
+ }
852
+
853
+ async gather<T, R>(items: readonly T[], run: (item: T) => Promise<R>): Promise<readonly R[]> {
854
+ const entries = items.entries()
855
+ const failed = new AbortController()
856
+ const workers = Array.from({ length: Math.min(this.#concurrency, items.length) }, () =>
857
+ this.#drain(entries, run, failed),
858
+ )
859
+ const settled = await Promise.allSettled(workers)
860
+ if (failed.signal.aborted) throw failed.signal.reason
861
+ return settled.flatMap((worker) => (worker.status === 'fulfilled' ? worker.value : []))
862
+ }
863
+
864
+ async #drain<T, R>(
865
+ entries: IterableIterator<[number, T]>,
866
+ run: (item: T) => Promise<R>,
867
+ failed: AbortController,
868
+ ): Promise<readonly R[]> {
869
+ const produced: R[] = []
870
+ for (const [, item] of entries) {
871
+ if (failed.signal.aborted) break
872
+ try {
873
+ produced.push(await run(item))
874
+ } catch (error) {
875
+ failed.abort(error)
876
+ break
877
+ }
878
+ }
879
+ return produced
880
+ }
881
+ }
882
+ ```
883
+
884
+ ## `entity.closure` (**Entity state**)
885
+
886
+ - **Signatures**, with `-U`. A function body that declares
887
+ `const\s+[A-Za-z_$][\w$]*\s*(?::[^=]+)?=\s*(?:new (?:Map|Set|WeakMap|WeakSet|Array)\b|\[\]|\{\})`.
888
+ The body then returns a function (`return\s+(?:async\s+)?(?:function\b|\()`) or registers one
889
+ (`\.(?:on|once|addEventListener|use)\(`). Inside that function, a `running.carriers` write
890
+ signature hits the declared name. Include `describe(` bodies whose collections `it(` callbacks
891
+ change.
892
+ - **Reading.** Keep an array, record, `Map`, or `Set` that the returned or registered function
893
+ changes when it runs after the creating call returns (**Entity state**). Drop a collection that
894
+ function only reads, an entity instance, and a composable's reactive state.
895
+
896
+ Hit fixture, `entity.closure.hit.ts`:
897
+
898
+ ```ts
899
+ function createLimiter(options: LimiterOptions): MiddlewareHandler {
900
+ const capacity = options.capacity ?? DEFAULT_LIMITER_CAPACITY
901
+ const clock = options.clock ?? Date.now
902
+ const buckets = new Map<string, { budget: BudgetInterface<number>; resetAt: number }>()
903
+ return async (_request, context, next) => {
904
+ const key = resolveKey(context.state)
905
+ const now = clock()
906
+ const found = buckets.get(key)
907
+ if (found === undefined && buckets.size >= capacity) {
908
+ const oldest = buckets.keys().next().value
909
+ if (oldest !== undefined) buckets.delete(oldest)
910
+ }
911
+ const bucket = found ?? {
912
+ budget: createBudget<number>({ max: options.max, consumer: Number }),
913
+ resetAt: now + options.window,
914
+ }
915
+ buckets.delete(key)
916
+ buckets.set(key, bucket)
917
+ if (bucket.budget.exhausted) return new Response(options.message, { status: 429 })
918
+ bucket.budget.consume(1)
919
+ return next()
920
+ }
921
+ }
922
+ ```
923
+
924
+ Clear fixture, `entity.closure.clear.ts`:
925
+
926
+ ```ts
927
+ function only<TState>(
928
+ paths: string | readonly string[],
929
+ handler: MiddlewareHandler<TState>,
930
+ ): MiddlewareHandler<TState> {
931
+ const matches = new Set(isString(paths) ? [paths] : paths)
932
+ return async (request, context, next) => {
933
+ if (matches.has(context.url.pathname)) return handler(request, context, next)
934
+ return next()
935
+ }
936
+ }
937
+ ```
938
+
939
+ ## `middleware.fixed` and `middleware.changing` (`.claude/rules/architecture.md` § Middleware)
940
+
941
+ - **Signatures.** Apply the `entity.closure` signatures to every `middlewares.ts` file and to every
942
+ function that returns a middleware.
943
+ - **Reading.** Keep closure state that a middleware changes across requests
944
+ (`.claude/rules/architecture.md` § Middleware). Drop a value the factory derives and never
945
+ changes, and a per-request local, which `.claude/rules/typescript.md` § Bindings rules.
946
+
947
+ Hit fixture, `middleware.fixed.hit.ts`:
948
+
949
+ ```ts
950
+ function createSession(options?: SessionMiddlewareOptions): MiddlewareHandler {
951
+ const path = options?.path ?? DEFAULT_SESSION_PATH
952
+ const ttl = options?.ttl
953
+ const clock = options?.clock ?? Date.now
954
+ const store = new Map<string, SessionEntry>()
955
+ return async (request, context, next) => {
956
+ if (context.url.pathname !== path) return next()
957
+ if (ttl !== undefined) {
958
+ const cutoff = clock() - ttl
959
+ for (const [id, entry] of store) {
960
+ if (entry.touched <= cutoff) store.delete(id)
961
+ }
962
+ }
963
+ if (context.method === 'DELETE') {
964
+ const id = readSessionHeader(request)
965
+ if (id === undefined || !store.delete(id)) return rejectUnknownSession()
966
+ return new Response(null, { status: 204 })
967
+ }
968
+ const session = readSessionHeader(request) ?? crypto.randomUUID()
969
+ store.set(session, { touched: clock() })
970
+ return next()
971
+ }
972
+ }
973
+ ```
974
+
975
+ Clear fixture, `middleware.fixed.clear.ts`:
976
+
977
+ ```ts
978
+ function only<TState>(
979
+ paths: string | readonly string[],
980
+ handler: MiddlewareHandler<TState>,
981
+ ): MiddlewareHandler<TState> {
982
+ const matches = new Set(isString(paths) ? [paths] : paths)
983
+ return async (request, context, next) => {
984
+ if (matches.has(context.url.pathname)) return handler(request, context, next)
985
+ return next()
986
+ }
987
+ }
988
+ ```
989
+
990
+ ## `slots.ban` (**Call slots**)
991
+
992
+ - **Signatures.** `this\.#[A-Za-z_$][\w$]*\s*=\s*undefined\b`, plus every
993
+ `this\.#[A-Za-z_$][\w$]*\s*=\s*` in the opening statements of a method. Then group each field's
994
+ assignment sites by method, and list the settle callbacks (`.then(`, `.finally(`) each method
995
+ registers.
996
+ - **Reading.** Keep a `#` field of a long-lived entity that a call sets when it starts to its own
997
+ entity, context, promise, or scratch value, or clears when it settles (**Call slots**). The clear
998
+ can sit in a `finally` or in a settle callback the call registers. Judge long-lived by
999
+ **Per-call state**: an owner that can serve another call like the one whose value the field holds
1000
+ is long-lived for it. Keep the hit when the call's steps, its callbacks, an outside handler, or a
1001
+ later call reach the value while the call runs, even when a refusal keeps the calls from
1002
+ overlapping. Drop a bound value, which `bound.field`, `bound.bind`, and `bound.release` rule; a
1003
+ latch and a selection under **Bound values**; and a field a call writes mid-call and never clears,
1004
+ such as `#previous`.
1005
+
1006
+ Hit fixture, `slots.ban.hit.ts`:
1007
+
1008
+ ```ts
1009
+ class Worker {
1010
+ readonly #session: Session
1011
+ #meter: RemoteMeter | undefined
1012
+
1013
+ constructor(session: Session) {
1014
+ this.#session = session
1015
+ }
1016
+
1017
+ async goal(value: Goal): Promise<Product> {
1018
+ if (this.#meter !== undefined) throw new Error('Worker goal is out of order')
1019
+ const meter = new RemoteMeter()
1020
+ this.#meter = meter
1021
+ try {
1022
+ return await this.#session.execute(value, meter)
1023
+ } finally {
1024
+ this.#meter = undefined
1025
+ }
1026
+ }
1027
+
1028
+ append(value: Evidence): Promise<void> {
1029
+ const meter = this.#meter
1030
+ if (meter === undefined) return Promise.reject(new Error('Worker evidence is outside a goal'))
1031
+ return meter.record(value)
1032
+ }
1033
+ }
1034
+
1035
+ class Conversation {
1036
+ #pending: Promise<unknown> | undefined
1037
+
1038
+ compact(summarize: Summarizer): Promise<Section | undefined> {
1039
+ const previous = this.#pending
1040
+ const fold = this.#fold(previous, summarize)
1041
+ const pending = Promise.allSettled([previous, fold])
1042
+ this.#pending = pending
1043
+ void pending.then(() => {
1044
+ if (this.#pending === pending) this.#pending = undefined
1045
+ })
1046
+ return fold
1047
+ }
1048
+
1049
+ async #fold(
1050
+ previous: Promise<unknown> | undefined,
1051
+ summarize: Summarizer,
1052
+ ): Promise<Section | undefined> {
1053
+ await previous
1054
+ return summarize()
1055
+ }
1056
+ }
1057
+ ```
1058
+
1059
+ Clear fixture, `slots.ban.clear.ts`:
1060
+
1061
+ ```ts
1062
+ class Worker {
1063
+ readonly #meters = new Set<RemoteMeter>()
1064
+
1065
+ async goal(value: Goal): Promise<Product> {
1066
+ if (this.#meters.size !== 0) throw new Error('Worker goal is out of order')
1067
+ const meter = new RemoteMeter()
1068
+ this.#meters.add(meter)
1069
+ try {
1070
+ return await execute(value, meter)
1071
+ } finally {
1072
+ this.#meters.delete(meter)
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ class ConversationManager {
1078
+ readonly #conversations = new Map<string, Conversation>()
1079
+ #active: string | undefined
1080
+
1081
+ switch(id: string): Conversation | undefined {
1082
+ const conversation = this.#conversations.get(id)
1083
+ if (conversation === undefined) return undefined
1084
+ this.#active = id
1085
+ return conversation
1086
+ }
1087
+ }
1088
+
1089
+ class Transport {
1090
+ #fault: { readonly error: unknown } | undefined
1091
+
1092
+ fail(error: unknown): void {
1093
+ this.#fault ??= { error }
1094
+ }
1095
+ }
1096
+ ```
1097
+
1098
+ ## `resets.ban` and `resets.assignment`, with `bound.state` (**Unit resets**, **Bound values**)
1099
+
1100
+ - **Signatures.**
1101
+ - Fresh initializers: `this\.#[A-Za-z_$][\w$]*\s*=\s*(?:(?:0|-1|false|true|undefined)\b|''|""|\[\]|\{\}|new\s)`.
1102
+ - Emptying: `this\.#[A-Za-z_$][\w$]*\.(?:clear\(\)|length\s*=\s*0|splice\(\s*0)`.
1103
+ - Any assignment inside a method that binds or releases a unit:
1104
+ `this\.#[A-Za-z_$][\w$]*\s*(?:[-+*/%&|^]|\*\*|\?\?|\|\||&&)?=[^=]`.
1105
+ - **Reading.** Keep a hit inside a method that binds or releases a unit of work that later calls
1106
+ continue when it discards the preceding unit's state and initializes state for another unit,
1107
+ whatever its expression reads (**Unit resets**). Read the method: it begins or ends a goal, a
1108
+ session, or a lease. Require that the field is not the bound value's own field and that another
1109
+ method changes it afterwards (`++`, `--`, `+=`, `.push(`, `.set(`, `.add(`, or a reassignment).
1110
+ Keep a field whose lifetime is the unit that sits on the owner (**Bound values**). Drop a field
1111
+ whose state continues across units, such as a cross-goal ordinal or request count; a copy-on-write
1112
+ update that keeps the entity's state; a constructor; teardown; and a reset in a method that
1113
+ neither binds nor releases a unit.
1114
+
1115
+ Hit fixture, `resets.ban.hit.ts`:
1116
+
1117
+ ```ts
1118
+ class AgentScript {
1119
+ #plan: Plan | undefined
1120
+ #cursor = 0
1121
+
1122
+ prepare(plan: Plan): void {
1123
+ this.#plan = plan
1124
+ this.#cursor = 0
1125
+ }
1126
+
1127
+ take(): ScriptedResponse | undefined {
1128
+ const response = this.#plan?.responses[this.#cursor]
1129
+ this.#cursor++
1130
+ return response
1131
+ }
1132
+ }
1133
+
1134
+ class AgentTrace {
1135
+ #rows: Evidence[] = []
1136
+ #goal: string | undefined
1137
+
1138
+ start(goal: string): void {
1139
+ if (this.#goal !== undefined) throw new Error('Trace goal is already active')
1140
+ this.#rows = []
1141
+ this.#goal = goal
1142
+ }
1143
+
1144
+ append(event: string, value: JSONValue): void {
1145
+ const goal = this.#goal
1146
+ if (goal === undefined) return
1147
+ this.#rows.push({ event, goal, value })
1148
+ }
1149
+ }
1150
+
1151
+ class ReplayScript {
1152
+ #plan: Plan | undefined
1153
+ #cursor = 0
1154
+
1155
+ prepare(plan: Plan): void {
1156
+ this.#plan = plan
1157
+ this.#cursor = this.#cursor - this.#cursor
1158
+ }
1159
+
1160
+ take(): ScriptedResponse | undefined {
1161
+ const response = this.#plan?.responses[this.#cursor]
1162
+ this.#cursor += 1
1163
+ return response
1164
+ }
1165
+ }
1166
+ ```
1167
+
1168
+ Clear fixture, `resets.ban.clear.ts`:
1169
+
1170
+ ```ts
1171
+ class Channel {
1172
+ #waiting: Array<() => void> = []
1173
+
1174
+ #signal(): void {
1175
+ const waiting = this.#waiting
1176
+ this.#waiting = []
1177
+ for (const wake of waiting) wake()
1178
+ }
1179
+
1180
+ #park(): Promise<void> {
1181
+ return new Promise<void>((resolve) => {
1182
+ this.#waiting = [...this.#waiting, resolve]
1183
+ })
1184
+ }
1185
+ }
1186
+
1187
+ class ConversationManager {
1188
+ readonly #conversations = new Map<string, Conversation>()
1189
+ #active: string | undefined
1190
+
1191
+ clear(): void {
1192
+ this.#conversations.clear()
1193
+ this.#active = undefined
1194
+ }
1195
+ }
1196
+
1197
+ class Conversation {
1198
+ readonly #rolling: boolean
1199
+ #sections: readonly Section[] = []
1200
+
1201
+ constructor(rolling: boolean) {
1202
+ this.#rolling = rolling
1203
+ }
1204
+
1205
+ async #fold(summarize: Summarizer, slice: readonly Message[]): Promise<Section> {
1206
+ const section: Section = { summary: await summarize(slice), messages: slice }
1207
+ this.#sections = this.#rolling ? [section] : [...this.#sections, section]
1208
+ return section
1209
+ }
1210
+ }
1211
+
1212
+ class AgentTrace {
1213
+ readonly #goals = new Set<TraceGoal>()
1214
+ #ordinal = 0
1215
+ #dispatches = 0
1216
+
1217
+ start(goal: string): void {
1218
+ if (this.#goals.size !== 0) throw new Error('Trace goal is already active')
1219
+ this.#goals.add(new TraceGoal(goal))
1220
+ }
1221
+
1222
+ admit(): number {
1223
+ return ++this.#ordinal
1224
+ }
1225
+
1226
+ dispatch(): number {
1227
+ return ++this.#dispatches
1228
+ }
1229
+ }
1230
+ ```
1231
+
1232
+ ## `bound.field`, with `bound.unit` and `running.position` (**Bound values**)
1233
+
1234
+ - **Signatures.** `const\s*\[\s*[A-Za-z_$][\w$]*\s*\]\s*=\s*this\.#[A-Za-z_$][\w$]*`,
1235
+ `this\.#[A-Za-z_$][\w$]*\.(?:values|keys|entries)\(\)\.next\(\)`, and
1236
+ `\[\s*\.\.\.this\.#[A-Za-z_$][\w$]*\s*\]\s*(?:\[0\]|\.at\()`. For each field, find the methods that
1237
+ add and delete its entry. Also list each explicit bind-to-release pair of methods: a method that
1238
+ sets a `#` field or adds to a `#` collection, and a different later method that releases it.
1239
+ - **Reading.** Keep a `#` collection that holds one bound unit at a time behind a refusal on add,
1240
+ whose entry a later call deletes (`clear`, `close`, or another method's `finally`), and whose unit
1241
+ the entity holds nowhere else (**Bound values**). Drop a registry whose adding call deletes the
1242
+ entry, which `registry.reads` rules; a population, a cache, or a persisted record, which
1243
+ **Registries** excludes; and a selection.
1244
+
1245
+ Hit fixture, `bound.field.hit.ts`:
1246
+
1247
+ ```ts
1248
+ class AgentScript {
1249
+ readonly #goals = new Set<ScriptGoal>()
1250
+ readonly #serving = new Set<ScriptedResponse>()
1251
+
1252
+ prepare(plan: Plan): void {
1253
+ if (this.#serving.size !== 0) throw new Error('Cannot replace a running scripted workload')
1254
+ if (this.#goals.size !== 0) throw new Error('Scripted goal is already prepared')
1255
+ this.#goals.add(new ScriptGoal(plan))
1256
+ }
1257
+
1258
+ clear(): void {
1259
+ this.#goals.clear()
1260
+ }
1261
+
1262
+ async serve(messages: readonly Message[]): Promise<ProviderResult> {
1263
+ const [goal] = this.#goals
1264
+ if (goal === undefined) throw new Error('Scripted provider is unavailable')
1265
+ const response = goal.take()
1266
+ this.#serving.add(response)
1267
+ try {
1268
+ return await respond(messages, response)
1269
+ } finally {
1270
+ this.#serving.delete(response)
1271
+ }
1272
+ }
1273
+ }
1274
+
1275
+ class AgentTrace {
1276
+ readonly #goals = new Set<TraceGoal>()
1277
+
1278
+ get goal(): string | undefined {
1279
+ const [running] = this.#goals
1280
+ return running?.goal
1281
+ }
1282
+
1283
+ start(goal: string): void {
1284
+ if (this.#goals.size !== 0) throw new Error('Trace goal is already active')
1285
+ this.#goals.add(new TraceGoal(goal))
1286
+ }
1287
+
1288
+ async flush(evidence: EvidenceSink): Promise<void> {
1289
+ const [running] = this.#goals
1290
+ if (running === undefined) return
1291
+ try {
1292
+ await running.flush(evidence)
1293
+ } finally {
1294
+ this.#goals.delete(running)
1295
+ }
1296
+ }
1297
+ }
1298
+ ```
1299
+
1300
+ Clear fixture, `bound.field.clear.ts`:
1301
+
1302
+ ```ts
1303
+ class ProcessSession {
1304
+ readonly #goals = new Set<ProcessGoal>()
1305
+
1306
+ async execute(goal: Goal, context: GoalContext): Promise<Product> {
1307
+ if (this.#goals.size !== 0) throw new Error('The process cannot execute in its current state')
1308
+ const running = new ProcessGoal(context)
1309
+ this.#goals.add(running)
1310
+ try {
1311
+ return await request('goal', goal)
1312
+ } finally {
1313
+ this.#goals.delete(running)
1314
+ }
1315
+ }
1316
+
1317
+ async #handle(operation: Operation): Promise<Reply> {
1318
+ const [running] = this.#goals
1319
+ if (running === undefined) throw new Error('The worker requested work outside a goal')
1320
+ return running.context.meter.admit(operation)
1321
+ }
1322
+ }
1323
+
1324
+ class MemorySessionStore {
1325
+ readonly #entries = new Map<string, SessionEntry>()
1326
+ readonly #capacity: number
1327
+
1328
+ constructor(capacity: number) {
1329
+ this.#capacity = capacity
1330
+ }
1331
+
1332
+ #reserve(): void {
1333
+ if (this.#entries.size < this.#capacity) return
1334
+ const oldest = this.#entries.keys().next().value
1335
+ if (oldest !== undefined) this.#entries.delete(oldest)
1336
+ }
1337
+ }
1338
+ ```
1339
+
1340
+ ## `bound.bind` and `bound.restart` (**Bound values**)
1341
+
1342
+ - **Signatures.** In bind methods (`prepare`, `start`, `begin`, `open`, `bind`, `hold`, `use`), find
1343
+ `this\.#[A-Za-z_$][\w$]*\s*(?:\?\?)?=\s*` and `\.add\(`. Then read the method's guard and its
1344
+ suspensions (`await`, `yield`).
1345
+ - **Reading.** Keep a bind with no throw-while-bound check (`!== undefined` or `size !== 0`, then
1346
+ `throw`), a bind whose check or whose publication of the incoming value comes after the method's
1347
+ first suspension, and a bind that keeps the first value through `??=` instead of throwing
1348
+ (**Bound values**). For a `start` that restarts, keep one that suspends between calling the
1349
+ previous value's end method and publishing the incoming value. Drop a bind that publishes a value
1350
+ owning its asynchronous initialization before its first suspension, and a latch under
1351
+ **Bound values**.
1352
+
1353
+ Hit fixture, `bound.bind.hit.ts`:
1354
+
1355
+ ```ts
1356
+ class AuditRegistry {
1357
+ #server: UpstreamServer | undefined
1358
+
1359
+ async start(): Promise<void> {
1360
+ this.#server ??= await createUpstreamServer(AUDIT_REPLIES)
1361
+ }
1362
+ }
1363
+
1364
+ class GoalRunner {
1365
+ #goal: Goal | undefined
1366
+
1367
+ async begin(plan: Plan): Promise<void> {
1368
+ if (this.#goal !== undefined) throw new Error('A goal is already bound')
1369
+ const goal = await createGoal(plan)
1370
+ this.#goal = goal
1371
+ }
1372
+ }
1373
+ ```
1374
+
1375
+ Clear fixture, `bound.bind.clear.ts`:
1376
+
1377
+ ```ts
1378
+ class Holder<TValue> {
1379
+ #value: TValue | undefined
1380
+
1381
+ hold(value: TValue): TValue {
1382
+ if (this.#value !== undefined) throw new Error('The holder already holds a value')
1383
+ this.#value = value
1384
+ return value
1385
+ }
1386
+ }
1387
+
1388
+ class AgentTrace {
1389
+ readonly #goals = new Set<TraceGoal>()
1390
+
1391
+ start(goal: string): void {
1392
+ if (this.#goals.size !== 0) throw new Error('Trace goal is already active')
1393
+ this.#goals.add(new TraceGoal(goal))
1394
+ }
1395
+ }
1396
+ ```
1397
+
1398
+ ## `bound.release` and `bound.later` (**Bound values**)
1399
+
1400
+ - **Signatures.** For each bound field that `slots.ban`, `bound.field`, or `bound.bind` found, list
1401
+ its `this\.#[A-Za-z_$][\w$]*\s*=\s*undefined`, `\.clear\(\)`, and `\.delete\(` sites by method, and
1402
+ the callbacks and helpers each binding invocation registers or calls.
1403
+ - **Reading.** Keep a binding that only the binding invocation's own settle path, or cleanup that
1404
+ invocation registers or calls, releases, and rule it a call slot under **Call slots**. Keep a
1405
+ binding that no later independent invocation releases while the entity lives on and that is no
1406
+ latch (**Bound values**). Drop a release in `clear`, in teardown, and in a later independent
1407
+ invocation's settle path.
1408
+
1409
+ Hit fixture, `bound.release.hit.ts`:
1410
+
1411
+ ```ts
1412
+ class ProcessSession {
1413
+ readonly #channel: Channel
1414
+ #context: GoalContext | undefined
1415
+
1416
+ constructor(channel: Channel) {
1417
+ this.#channel = channel
1418
+ }
1419
+
1420
+ async execute(goal: Goal, context: GoalContext): Promise<Product> {
1421
+ if (this.#context !== undefined)
1422
+ throw new Error('The process cannot execute in its current state')
1423
+ this.#context = context
1424
+ try {
1425
+ return await this.#channel.request('goal', goal)
1426
+ } finally {
1427
+ this.#context = undefined
1428
+ }
1429
+ }
1430
+
1431
+ async handle(operation: Operation): Promise<Reply> {
1432
+ const context = this.#context
1433
+ if (context === undefined) throw new Error('The worker requested work outside a goal')
1434
+ return context.meter.admit(operation)
1435
+ }
1436
+ }
1437
+
1438
+ class Conversation {
1439
+ #pending: Promise<unknown> | undefined
1440
+
1441
+ compact(summarize: Summarizer): Promise<Section | undefined> {
1442
+ const previous = this.#pending
1443
+ const fold = this.#fold(previous, summarize)
1444
+ const pending = Promise.allSettled([previous, fold])
1445
+ this.#pending = pending
1446
+ void pending.then(() => {
1447
+ if (this.#pending === pending) this.#pending = undefined
1448
+ })
1449
+ return fold
1450
+ }
1451
+
1452
+ async #fold(
1453
+ previous: Promise<unknown> | undefined,
1454
+ summarize: Summarizer,
1455
+ ): Promise<Section | undefined> {
1456
+ await previous
1457
+ return summarize()
1458
+ }
1459
+ }
1460
+ ```
1461
+
1462
+ Clear fixture, `bound.release.clear.ts`:
1463
+
1464
+ ```ts
1465
+ class AgentTrace {
1466
+ readonly #goals = new Set<TraceGoal>()
1467
+ readonly #flushes = new Set<Promise<void>>()
1468
+
1469
+ start(goal: string): void {
1470
+ if (this.#goals.size !== 0) throw new Error('Trace goal is already active')
1471
+ this.#goals.add(new TraceGoal(goal))
1472
+ }
1473
+
1474
+ async flush(evidence: EvidenceSink): Promise<void> {
1475
+ const [running] = this.#goals
1476
+ if (running === undefined) return
1477
+ if (this.#flushes.size !== 0) throw new Error('Trace flush is already pending')
1478
+ const flushing = running.flush(evidence)
1479
+ this.#flushes.add(flushing)
1480
+ try {
1481
+ await flushing
1482
+ } finally {
1483
+ this.#goals.delete(running)
1484
+ this.#flushes.delete(flushing)
1485
+ }
1486
+ }
1487
+ }
1488
+ ```
1489
+
1490
+ ## `registry.form` (**Registries**)
1491
+
1492
+ - **Signatures.**
1493
+ - Bulk clears: `this\.#[A-Za-z_$][\w$]*\.clear\(\)` in methods other than `destroy`, `close`,
1494
+ `stop`, and the private teardown they call.
1495
+ - Clear-then-add, with `-U`: `\.clear\(\)\s*\n?\s*this\.#[A-Za-z_$][\w$]*\.add\(`.
1496
+ - Deletes: every `this\.#[A-Za-z_$][\w$]*\.delete\(` on a registry field.
1497
+ - **Reading.** Keep a registry of in-flight entities that a non-teardown method clears in bulk, that
1498
+ a call clears before adding its own entry, or whose entry a path other than that entry's own end
1499
+ deletes (**Registries**). Drop an operation's delete of its own entry, even when it empties the
1500
+ registry, and drop a population, a subscription, a cache, and a persisted record.
1501
+
1502
+ Hit fixture, `registry.form.hit.ts`:
1503
+
1504
+ ```ts
1505
+ class Dispatcher {
1506
+ readonly #live = new Set<Call>()
1507
+
1508
+ async dispatch(call: Call): Promise<Reply> {
1509
+ this.#live.clear()
1510
+ this.#live.add(call)
1511
+ try {
1512
+ return await call.run()
1513
+ } finally {
1514
+ this.#live.delete(call)
1515
+ }
1516
+ }
1517
+ }
1518
+ ```
1519
+
1520
+ Clear fixture, `registry.form.clear.ts`:
1521
+
1522
+ ```ts
1523
+ class ProcessSession {
1524
+ readonly #goals = new Set<ProcessGoal>()
1525
+ readonly #tickets = new Map<number, ParentTicket>()
1526
+ #closing: Promise<void> | undefined
1527
+
1528
+ async execute(goal: Goal, context: GoalContext): Promise<Product> {
1529
+ if (this.#goals.size !== 0) throw new Error('The process cannot execute in its current state')
1530
+ const running = new ProcessGoal(context)
1531
+ this.#goals.add(running)
1532
+ try {
1533
+ return await request('goal', goal)
1534
+ } finally {
1535
+ this.#goals.delete(running)
1536
+ }
1537
+ }
1538
+
1539
+ close(): Promise<void> {
1540
+ this.#closing ??= this.#close()
1541
+ return this.#closing
1542
+ }
1543
+
1544
+ async #close(): Promise<void> {
1545
+ for (const entry of this.#tickets.values()) entry.listeners.abort()
1546
+ this.#tickets.clear()
1547
+ await terminate()
1548
+ }
1549
+ }
1550
+ ```
1551
+
1552
+ ## `registry.reads` and `registry.position`, with `running.position` (**Registries**)
1553
+
1554
+ - **Signatures.** The `bound.field` position-read signatures, `keys()` and `entries()` iterator reads
1555
+ and a key obtained that way included, restricted to registry fields.
1556
+ - **Reading.** Keep every position read unless all of these hold (**Registries**):
1557
+ - a `size !== 0` throw precedes every add to that registry;
1558
+ - the call that added the entry deletes it on every path that settles that call;
1559
+ - the read handles the empty registry.
1560
+
1561
+ Drop a `size` read, a whole read, and a keyed read.
1562
+
1563
+ Hit fixture, `registry.reads.hit.ts`:
1564
+
1565
+ ```ts
1566
+ class Supervisor {
1567
+ readonly #children = new Set<ChildProcess>()
1568
+
1569
+ spawn(command: string): ChildProcess {
1570
+ const child = spawnChild(command)
1571
+ this.#children.add(child)
1572
+ child.once('exit', () => this.#children.delete(child))
1573
+ return child
1574
+ }
1575
+
1576
+ first(): ChildProcess | undefined {
1577
+ const [child] = this.#children
1578
+ return child
1579
+ }
1580
+
1581
+ oldest(): ChildProcess | undefined {
1582
+ return this.#children.values().next().value
1583
+ }
1584
+ }
1585
+ ```
1586
+
1587
+ Clear fixture, `registry.reads.clear.ts`:
1588
+
1589
+ ```ts
1590
+ class Worker {
1591
+ readonly #meters = new Set<RemoteMeter>()
1592
+
1593
+ async goal(value: Goal): Promise<Product> {
1594
+ if (this.#meters.size !== 0) throw new Error('Worker goal is invalid or out of order')
1595
+ const meter = new RemoteMeter()
1596
+ this.#meters.add(meter)
1597
+ try {
1598
+ return await execute(value, meter)
1599
+ } finally {
1600
+ this.#meters.delete(meter)
1601
+ }
1602
+ }
1603
+
1604
+ abort(value: Cancellation): void {
1605
+ const [meter] = this.#meters
1606
+ if (meter === undefined) throw new Error('Worker ticket cancellation is foreign')
1607
+ meter.abort(value.id, value.failure)
1608
+ }
1609
+ }
1610
+
1611
+ class ProcessSession {
1612
+ readonly #goals = new Set<ProcessGoal>()
1613
+
1614
+ async execute(goal: Goal, context: GoalContext): Promise<Product> {
1615
+ if (this.#goals.size !== 0) throw new Error('The process cannot execute in its current state')
1616
+ const running = new ProcessGoal(context)
1617
+ this.#goals.add(running)
1618
+ try {
1619
+ return await request('goal', goal)
1620
+ } finally {
1621
+ this.#goals.delete(running)
1622
+ }
1623
+ }
1624
+
1625
+ async #handle(operation: Operation): Promise<Reply> {
1626
+ const [running] = this.#goals
1627
+ if (running === undefined) throw new Error('The worker requested work outside a goal')
1628
+ return running.context.meter.admit(operation)
1629
+ }
1630
+ }
1631
+ ```
1632
+
1633
+ ## `exemptions.membership` and `exemptions.walk` (**Exemptions**)
1634
+
1635
+ - **Signatures.** `const\s+[A-Za-z_$][\w$]*\s*(?::[^=]+)?=\s*new (?:Set|Map)\b`. For each, list the
1636
+ name's `\.has\(` reads and its `\.(?:add|set)\(` writes inside the loops of its function.
1637
+ - **Reading.** Keep a set or map that a loop reads through `has` and writes through `add` or `set`
1638
+ when the loop is no traversal: it reaches no node as a successor of a node it has already reached
1639
+ (**Exemptions**). Drop a traversal's membership set or map, whether or not the traversal iterates
1640
+ it, and a map that `exemptions.memo` rules as a memo.
1641
+
1642
+ Hit fixture, `exemptions.membership.hit.ts`:
1643
+
1644
+ ```ts
1645
+ function selectPlugins(
1646
+ base: readonly PluginOption[],
1647
+ candidates: readonly PluginOption[],
1648
+ ): readonly PluginOption[] {
1649
+ const taken = new Set<number>()
1650
+ const selected: PluginOption[] = []
1651
+ for (const plugin of base) {
1652
+ const index = candidates.findIndex(
1653
+ (candidate, position) =>
1654
+ !taken.has(position) && isNamedPlugin(candidate) && candidate.name === readName(plugin),
1655
+ )
1656
+ const replacement = candidates[index]
1657
+ if (replacement === undefined) {
1658
+ selected.push(plugin)
1659
+ } else {
1660
+ selected.push(replacement)
1661
+ taken.add(index)
1662
+ }
1663
+ }
1664
+ for (const [index, plugin] of candidates.entries()) {
1665
+ if (!taken.has(index)) selected.push(plugin)
1666
+ }
1667
+ return selected
1668
+ }
1669
+ ```
1670
+
1671
+ Clear fixture, `exemptions.membership.clear.ts`:
1672
+
1673
+ ```ts
1674
+ function collectDescendants(rows: readonly (readonly number[])[], root: number): readonly number[] {
1675
+ const descendants = new Set([root])
1676
+ rows.forEach(() => {
1677
+ for (const [pid, parent] of rows)
1678
+ if (pid !== undefined && parent !== undefined && descendants.has(parent)) descendants.add(pid)
1679
+ })
1680
+ return [...descendants].reverse()
1681
+ }
1682
+
1683
+ function scanAmendments(
1684
+ amendments: ReadonlyMap<string, readonly string[]>,
1685
+ start: string,
1686
+ admit: (id: string) => boolean,
1687
+ ): readonly string[] {
1688
+ const queue = [start]
1689
+ const seen = new Set<string>()
1690
+ const admitted: string[] = []
1691
+ for (const id of queue) {
1692
+ if (seen.has(id)) continue
1693
+ seen.add(id)
1694
+ if (!admit(id)) continue
1695
+ admitted.push(id)
1696
+ queue.push(...(amendments.get(id) ?? []))
1697
+ }
1698
+ return admitted
1699
+ }
1700
+ ```