@molecule/api-ai-tools 1.6.6 → 1.8.1

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.
package/README.md CHANGED
@@ -3,15 +3,11 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-16T21:18:13.332Z
6
+ Generated: 2026-10-17T02:50:55.123Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
10
10
 
11
- > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
- > It is written to be read by coding agents as much as by people, and is generated from this
13
- > package's source — edit `src/index.ts` JSDoc, not this file.
14
-
15
11
  Shared AI agent tool set for molecule.dev — filesystem, search, and shell
16
12
  tools an LLM agent can call, with a swappable execution backend.
17
13
 
@@ -24,6 +20,10 @@ WHERE they act: `createLocalBackend(projectRoot)` (host filesystem) or
24
20
  container). `buildAgentPrompt(ctx)` composes a matching system prompt
25
21
  (identity, tool listing, project docs, discovered skills).
26
22
 
23
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
24
+ > It is written to be read by coding agents as much as by people, and is generated from this
25
+ > package's source — edit `src/index.ts` JSDoc, not this file.
26
+
27
27
  ## Quick Start
28
28
 
29
29
  ```typescript
@@ -81,6 +81,72 @@ interface CommandSegment {
81
81
  }
82
82
  ```
83
83
 
84
+ #### `LocatedCommandSegment`
85
+
86
+ One statement of a shell command, with where it sits and the variables it sets for itself.
87
+
88
+ `commandSegments` answers WHAT runs; this answers where, and what the shell binds to it — the
89
+ two things a caller that WRITES into a command needs. The platform composes a run from the
90
+ executor's own command in more than one place, and a value written for the runner belongs in
91
+ the runner's own statement: `APP_BASE=https://… cd app && npx playwright test` sets the variable
92
+ for `cd` alone, so the Playwright config falls back to the local app while the command line
93
+ still names the deployed host (xr-molecule-flash-m103 +28:08).
94
+
95
+ ```typescript
96
+ interface LocatedCommandSegment {
97
+ /**
98
+ * What the statement runs, or null when it only sets variables (`BASE=http://…` on its own):
99
+ * that shape persists in the shell, so a caller following assignments across `;`/`&&` has to
100
+ * see it.
101
+ */
102
+ segment: CommandSegment | null
103
+ /** The statement's own leading `NAME=value` words, as written (`TEST_MODE=fullstack`). */
104
+ assignments: readonly string[]
105
+ /**
106
+ * The offset in the command of the statement's first word, or null for a segment inside a
107
+ * nested `sh -c '<command>'` string — its positions are offsets in THAT string, not in this
108
+ * command, so nothing may be spliced at them.
109
+ */
110
+ start: number | null
111
+ /**
112
+ * The offset in the command just before the word the statement runs — after its own leading
113
+ * `NAME=value` words and their whitespace. A `NAME=value` written here is in the environment of
114
+ * the program the statement runs (`… TEST_MODE=fullstack <here>npx playwright test`). Null
115
+ * inside a nested `sh -c` string, for the same reason as {@link start}.
116
+ */
117
+ assignmentOffset: number | null
118
+ }
119
+ ```
120
+
121
+ #### `SuiteSummary`
122
+
123
+ What a runner's own closing summary reports, and whether it accounts for the whole run.
124
+
125
+ A summary is not by itself proof that the run finished. Playwright prints one when its process
126
+ is stopped too (SIGTERM/SIGINT), listing only the tests it reached — so a run killed at 36 of
127
+ 139 tests ends with the same `36 passed (2.5m)` line as a run that walked all 139. The only
128
+ thing that separates them is the total the runner announced before it started
129
+ (`Running 139 tests using 1 worker`), which {@link suiteSummary} reconciles the summary against.
130
+
131
+ ```typescript
132
+ interface SuiteSummary {
133
+ /**
134
+ * The total the runner announced before its first test, or null when it announced none (Hurl
135
+ * announces nothing; Vitest's own summary line carries its total in parentheses).
136
+ */
137
+ announced: number | null
138
+ /**
139
+ * Every test the summary accounts for — the ones it passed, failed, marked flaky, timed out,
140
+ * interrupted, skipped or left "did not run". This is how far the walk got, which is what a
141
+ * stopped run's sentence says; a summary that accounts for fewer than {@link announced} was
142
+ * printed by a process that stopped early.
143
+ */
144
+ accounted: number
145
+ /** Whether the summary accounts for every test the runner announced. */
146
+ finished: boolean
147
+ }
148
+ ```
149
+
84
150
  #### `CutRun`
85
151
 
86
152
  A run a budget stopped, as its result reports it.
@@ -96,9 +162,15 @@ interface CutRun {
96
162
  * the pipeline's filter (`… | tail`) rather than to the run — the case that read as success.
97
163
  */
98
164
  exitCode: number | null
99
- /** How many files or tests the runner's own output shows finishing, or null when it shows none. */
165
+ /**
166
+ * How far the run got, as its own output counts it — the files that finished, or the tests its
167
+ * summary accounts for — or null when the output says nothing.
168
+ */
100
169
  ran: number | null
101
- /** How many the run was walking, when the runner printed it (Playwright's `[n/K]`), or null. */
170
+ /**
171
+ * How many the run was walking: the total its runner announced (`Running 139 tests`), or
172
+ * Playwright's `[n/K]`, whichever the output carries; null when neither does.
173
+ */
102
174
  total: number | null
103
175
  /** The noun the runner counts in. */
104
176
  noun: 'files' | 'tests'
@@ -186,6 +258,51 @@ interface SuiteRunInFlight {
186
258
  }
187
259
  ```
188
260
 
261
+ #### `TraceRequest`
262
+
263
+ One request a traced page made, as `*.network` records it.
264
+
265
+ ```typescript
266
+ interface TraceRequest {
267
+ /** The HTTP method (`POST`). */
268
+ method: string
269
+ /** The request's path (`/api/articles/x/favorite`), or the whole URL when it is cross-origin. */
270
+ path: string
271
+ /** The response status, or 0 when the request never got a response. */
272
+ status: number
273
+ }
274
+ ```
275
+
276
+ #### `TraceArtifact`
277
+
278
+ What one trace says: the test it belongs to, the requests it made, and its console errors.
279
+
280
+ ```typescript
281
+ interface TraceArtifact {
282
+ /** The failure's spec file, relative to the run's directory, when the trace names one. */
283
+ spec: string | null
284
+ /** The line of the test's last traced step, 0 when the trace names no spec. */
285
+ line: number
286
+ /** The requests the page made, the interesting ones first ({@link traceArtifactsScript}). */
287
+ requests: TraceRequest[]
288
+ /** The console errors and page errors the page raised. */
289
+ console: string[]
290
+ }
291
+ ```
292
+
293
+ #### `DiagnosisTraceRewrite`
294
+
295
+ A command with the trace flag this platform decided to add, and the sentence to report it in.
296
+
297
+ ```typescript
298
+ interface DiagnosisTraceRewrite {
299
+ /** The command to run. */
300
+ command: string
301
+ /** One sentence naming the flag and why, or null when nothing was added. */
302
+ change: string | null
303
+ }
304
+ ```
305
+
189
306
  #### `ExecutionBackend`
190
307
 
191
308
  Abstraction over the execution environment.
@@ -331,6 +448,20 @@ interface ToolBuildConfig {
331
448
  */
332
449
  outlineLargeHtml?: () => boolean
333
450
 
451
+ /**
452
+ * The test titles the person's request EXCLUDES by name (molecule-dev's `excludedTestTitles`),
453
+ * asked when a finished suite run's cause note is built. Their failures are not causes to fix:
454
+ * the note drops them, says so at the end, and inverts them out of the narrowed re-run it hands
455
+ * over — a result must never name a test beside "it does not count" (xr-molecule-flash-m101
456
+ * +12:00, where the executor spent a turn and an edit on the excluded test).
457
+ *
458
+ * A getter, not an array, because the request is read per turn while the tool set is built
459
+ * once. Unset means no test is excluded.
460
+ *
461
+ * @returns The quoted titles the request carves out.
462
+ */
463
+ excludedTestTitles?: () => readonly string[]
464
+
334
465
  /** Post-write hook (e.g. auto-format via Prettier/ESLint). Called after every write_file/edit_file. */
335
466
  onAfterWrite?: (path: string) => Promise<void>
336
467
 
@@ -409,6 +540,19 @@ interface PromptContext {
409
540
  }
410
541
  ```
411
542
 
543
+ #### `SuiteFilterStrip`
544
+
545
+ A command with the output filters after its suite runner removed.
546
+
547
+ ```typescript
548
+ interface SuiteFilterStrip {
549
+ /** The command to run. */
550
+ command: string
551
+ /** The pipeline stages dropped, as written (`| grep … | tail -20`), or '' when none were. */
552
+ dropped: string
553
+ }
554
+ ```
555
+
412
556
  #### `FailedTest`
413
557
 
414
558
  One failed test, as a running suite's log names it.
@@ -469,6 +613,12 @@ interface FailureCause {
469
613
  seconds: number | null
470
614
  /** The distinct tests it hit, in the order the log named them. */
471
615
  tests: FailedTest[]
616
+ /**
617
+ * The FIRST failure's two most useful body lines — the values that differ, or the locator from
618
+ * its `Call log` ({@link failureEvidence}) — so a fix round needs no re-read of the log. Empty
619
+ * for a duration group, whose blocks carried no bodies.
620
+ */
621
+ evidence: string[]
472
622
  }
473
623
  ```
474
624
 
@@ -487,7 +637,17 @@ type CommandRunState = 'ran' | typeof CUT_AT_BUDGET
487
637
  What stopped a run.
488
638
 
489
639
  ```typescript
490
- type CutBy = 'tool-budget' | 'command-timeout'
640
+ type CutBy =
641
+ /** This tool's own budget (the backend's `timeout`, just past it). */
642
+ | 'tool-budget'
643
+ /** The `timeout <N>` the command itself carried. */
644
+ | 'command-timeout'
645
+ /**
646
+ * Something outside the run: the runner's summary does not account for the total it announced,
647
+ * and neither of the two bounds above had fired — the process was killed by something this
648
+ * platform does not own (the sandbox's own process supervisor, the box going away, a signal).
649
+ */
650
+ | 'process-stop'
491
651
  ```
492
652
 
493
653
  #### `DiscoveredSkill` _(deprecated)_
@@ -563,6 +723,20 @@ function commandSegments(command: string): CommandSegment[]
563
723
 
564
724
  **Returns:** One entry per statement or pipeline stage that runs something, in order.
565
725
 
726
+ #### `locatedCommandSegments(command)`
727
+
728
+ Every statement of a command, with its own assignments and where it sits — assignment-only
729
+ statements included, because the variables those persist are exactly what a later statement of
730
+ the same command reads.
731
+
732
+ ```typescript
733
+ function locatedCommandSegments(command: string): LocatedCommandSegment[]
734
+ ```
735
+
736
+ - `command` — The shell command.
737
+
738
+ **Returns:** One entry per statement or pipeline stage, in order.
739
+
566
740
  #### `commandTimeoutSeconds(command)`
567
741
 
568
742
  The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
@@ -617,9 +791,29 @@ function commandRunState(result: unknown): CommandRunState
617
791
 
618
792
  **Returns:** The state the result records.
619
793
 
794
+ #### `suiteSummary(text)`
795
+
796
+ What the runner's closing summary accounts for, or null when it printed none.
797
+
798
+ Three runners' summaries are read, each by its own wording: Playwright's per-status lines
799
+ (reconciled against its `Running N tests` announcement), Vitest's single `Tests …` line (its own
800
+ parenthesised total), and Hurl's `Executed files:` block (which it prints only at the end, and
801
+ which announces no total — so it is a finish by construction).
802
+
803
+ ```typescript
804
+ function suiteSummary(text: string): SuiteSummary | null
805
+ ```
806
+
807
+ - `text` — The run's stdout and stderr, RAW (a truncated copy can lose the announcement).
808
+
809
+ **Returns:** The summary, or null when the text carries none.
810
+
620
811
  #### `suiteTally(text)`
621
812
 
622
- Whether text carries a runner's closing summary ({@link SUITE_TALLY}).
813
+ Whether text carries a runner's closing summary.
814
+
815
+ A summary is NOT a finish on its own — a stopped Playwright run prints one too. Use
816
+ {@link suiteSummary} and its `finished` for that.
623
817
 
624
818
  ```typescript
625
819
  function suiteTally(text: string): boolean
@@ -629,13 +823,39 @@ function suiteTally(text: string): boolean
629
823
 
630
824
  **Returns:** True when the runner's own end-of-run summary is there.
631
825
 
632
- #### `commandCutRun(command, result, elapsedMs, budgetSeconds)`
826
+ #### `suiteRunExitCode(exitCode, output, piped)`
827
+
828
+ The exit code a finished suite run may report.
829
+
830
+ The code in a background result is the code of the command's LAST pipeline stage. When a stage
831
+ after the suite runner still consumes its output (`| tee run.log`, or a filter the platform could
832
+ not prove output-only), that stage produced the code, not the run — and `… | grep … | tail -20`
833
+ exited 0 for a run with a failure in it (xr-molecule-flash-m101 +25:54). No code is reported
834
+ then: the log's own tally decides.
835
+
836
+ The same reading covers a code of 0 beside a tally that names a failure: whatever ended the
837
+ pipeline, a run whose log counts a failure did not pass.
838
+
839
+ ```typescript
840
+ function suiteRunExitCode(exitCode: number | null, output: string, piped: boolean): number | null
841
+ ```
842
+
843
+ - `exitCode` — The code the pipeline reported (null when it has none).
844
+ - `output` — The run's full log.
845
+ - `piped` — Whether a pipeline stage after the suite runner still consumes its output.
846
+
847
+ **Returns:** The code the run may report, or null when the code is the filter's or contradicts the tally.
848
+
849
+ #### `commandCutRun(command, result, elapsedMs, budgetSeconds, rawOutput)`
633
850
 
634
851
  The run a command's result records as stopped by a budget, or null when the result is a run that
635
852
  ended (or says nothing about a run).
636
853
 
637
854
  Evidence, in order — never a guess:
638
855
 
856
+ - the runner's own summary, when it printed one: it is a finish only if it accounts for the total
857
+ the runner announced. A summary that accounts for fewer is a walk a process stopped early, and
858
+ falls through to the branches below with its own counts ({@link suiteSummary}).
639
859
  - `timeout`'s own 124 (the code the backend's wrapper returns whenever the TOOL's budget fired,
640
860
  whatever the pipeline ended with) — and, when the clock says the command's own smaller bound
641
861
  ran out first and less than the budget, that bound instead.
@@ -644,6 +864,8 @@ Evidence, in order — never a guess:
644
864
  killed at its own bound. Its exit code, when the pipeline handed one back, is the filter's.
645
865
  - the run used this tool's whole budget behind a filter that withheld its output, and printed no
646
866
  closing summary: the budget stopped it, and no code in the result is the run's.
867
+ - a summary that does not account for its announced total, with neither bound fired: something
868
+ outside this platform stopped the process ({@link CutBy} `process-stop`).
647
869
 
648
870
  ```typescript
649
871
  function commandCutRun(
@@ -651,6 +873,7 @@ function commandCutRun(
651
873
  result: unknown,
652
874
  elapsedMs: number,
653
875
  budgetSeconds?: number,
876
+ rawOutput?: string,
654
877
  ): CutRun | null
655
878
  ```
656
879
 
@@ -658,6 +881,7 @@ function commandCutRun(
658
881
  - `result` — Its result.
659
882
  - `elapsedMs` — How long the command ran, in ms.
660
883
  - `budgetSeconds` — This tool's foreground budget, in seconds (0 when it has none).
884
+ - `rawOutput` — The run's output BEFORE truncation, when the caller has it: the announcement a summary is reconciled against is the first line a long run prints, which is exactly what a size cap drops. Detection reads this; the sentence's last line still comes from `result`.
661
885
 
662
886
  **Returns:** The stopped run, or null.
663
887
 
@@ -681,12 +905,14 @@ function cutRunFacts(result: unknown): CutRun | null
681
905
 
682
906
  #### `cutRunSentence(run)`
683
907
 
684
- The first line of a stopped run's result: how long it was allowed, what stopped it, how far it
685
- got, and the last thing it printed.
908
+ The first line of a stopped run's result: how far it got, what stopped it, the last thing it
909
+ printed, and what the reader must do next.
686
910
 
687
911
  Written to be quotable by the model in one sentence and to leave no room for "nearly passing":
688
912
  the counts come from the runner's own output ({@link runProgress}), and a `total` the caller
689
- read elsewhere (the suite's own file count) fills in the "M of K" it can then state.
913
+ read elsewhere (the suite's own file count) fills in the "of N" it can then state. The sentence
914
+ says how many tests the stop left unrun — a number a reader cannot round up to "the suite
915
+ passed" — and hands over the one move that survives a long run: run it in the background.
690
916
 
691
917
  ```typescript
692
918
  function cutRunSentence(run: CutRun): string
@@ -835,7 +1061,7 @@ function suiteCommandTarget(command: string): string
835
1061
 
836
1062
  **Returns:** The target key.
837
1063
 
838
- #### `scopedRerunCommand(command, locations)`
1064
+ #### `scopedRerunCommand(command, locations, excluded)`
839
1065
 
840
1066
  The whole-suite command made scoped to the tests it should re-run instead: the same
841
1067
  invocation — same directory, same settings — with the named `file:line`s in place of the whole
@@ -843,11 +1069,16 @@ suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flas
843
1069
  `--retries=0` set (a re-run of a known failure does not need to pay for retries).
844
1070
 
845
1071
  ```typescript
846
- function scopedRerunCommand(command: string, locations: readonly string[]): string
1072
+ function scopedRerunCommand(
1073
+ command: string,
1074
+ locations: readonly string[],
1075
+ excluded?: readonly string[],
1076
+ ): string
847
1077
  ```
848
1078
 
849
1079
  - `command` — The whole-suite run's command.
850
1080
  - `locations` — The failing `file:line`s to re-run, as the run's own log printed them — or, for a run that started outside the suite, the absolute form Playwright matches each file argument against.
1081
+ - `excluded` — The test titles the request carves out. They are inverted out (`--grep-invert`) so a re-run of the failing locations cannot report one of them again — xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about a test the request had already said does not count.
851
1082
 
852
1083
  **Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
853
1084
 
@@ -1025,6 +1256,107 @@ function buildTools(backend: ExecutionBackend, config?: ToolBuildConfig): AITool
1025
1256
 
1026
1257
  **Returns:** Array of AITool objects ready to pass to an AI provider
1027
1258
 
1259
+ #### `traceArtifactsScript()`
1260
+
1261
+ The Node program the sandbox runs to read a failed run's traces: `node <script> <dir>` prints one
1262
+ `TRACE <json>` line per `trace.zip` under `<dir>` (newest first, {@link MAX_TRACE_ARTIFACTS} of
1263
+ them), each with the test it belongs to, the requests its page made and its console errors.
1264
+
1265
+ It is a standalone program, not an import, for the reason `@molecule/api-code-sandbox` runs it:
1266
+ Playwright's own zip reader is not reachable from a project's `node_modules`, and a dependency
1267
+ on `unzip` would make the read fail on every sandbox that has none. The zip is read with the
1268
+ central directory and `zlib.inflateRawSync` — about forty lines, and the only thing that has to
1269
+ be true of a `trace.zip` is that it IS one.
1270
+
1271
+ A request is kept over a static asset when it failed (`status >= 400`) or never answered
1272
+ (`status === 0`), and over anything else when it is the page talking to the app
1273
+ (`fetch`/`xhr`/`document`): a failing test's trace holds the whole page load, and the answer is
1274
+ always at the end of it.
1275
+
1276
+ ```typescript
1277
+ function traceArtifactsScript(): string
1278
+ ```
1279
+
1280
+ **Returns:** The program's source, to write to a file and run.
1281
+
1282
+ #### `parseTraceArtifacts(stdout)`
1283
+
1284
+ The traces {@link traceArtifactsScript} printed, parsed.
1285
+
1286
+ A line that is not a complete `TRACE <json>` (a truncated read, a program that died mid-write) is
1287
+ dropped: a partial trace is not evidence about the test it came from.
1288
+
1289
+ ```typescript
1290
+ function parseTraceArtifacts(stdout: string): TraceArtifact[]
1291
+ ```
1292
+
1293
+ - `stdout` — The program's output.
1294
+
1295
+ **Returns:** One entry per trace it printed.
1296
+
1297
+ #### `failureTraceNote(items, target)`
1298
+
1299
+ The note a failed suite run's result carries: what each failing test's page actually sent, and
1300
+ what came back.
1301
+
1302
+ This is the answer to the question a swallowed error hides — a click that changed nothing is
1303
+ usually a request that failed — so the note leads with the failing requests and says what to do
1304
+ with them: read the handler the route reaches, not the selector. It is BOUNDED to
1305
+ {@link MAX_TRACE_NOTE_TESTS} tests with {@link MAX_TRACE_NOTE_REQUESTS} requests each, because it
1306
+ rides in a result the model pays for.
1307
+
1308
+ ```typescript
1309
+ function failureTraceNote(items: readonly TraceArtifact[], target: string): string | null
1310
+ ```
1311
+
1312
+ - `items` — The traces {@link parseTraceArtifacts} read.
1313
+ - `target` — Where the run went — the deployed host, as the note names it.
1314
+
1315
+ **Returns:** The note, or null when no trace held a request or a console error.
1316
+
1317
+ #### `suiteRecordsTraces(configText)`
1318
+
1319
+ Whether a Playwright config records traces for the tests that fail.
1320
+
1321
+ Only `on` and `retain-on-failure` keep a `trace.zip` for a failure whatever the retries do: the
1322
+ two retry modes need a retry to happen at all, and a diagnosis run is given `--retries=0`
1323
+ (`streamingBackgroundCommand`), so on X1 xr-molecule-flash-m100 the run's own
1324
+ `trace: 'on-first-retry'` recorded nothing — which is what left the failing tests' network log
1325
+ unreadable. A config that sets no `trace:` at all records nothing either (Playwright's default
1326
+ is off); so does one that cannot be read.
1327
+
1328
+ ```typescript
1329
+ function suiteRecordsTraces(configText: string): boolean
1330
+ ```
1331
+
1332
+ - `configText` — The config's source, or '' when none could be read.
1333
+
1334
+ **Returns:** True when the config records traces for failures.
1335
+
1336
+ #### `diagnosisTraceCommand(args)`
1337
+
1338
+ The command a DIAGNOSIS run needs to leave a readable trace behind, when the suite's config
1339
+ would record none.
1340
+
1341
+ The failing tests' requests are only readable if the run wrote them down (see the module doc),
1342
+ and the config is where the decision about that lives — the platform does not edit the suite's
1343
+ config, so it passes the flag instead, and only where it can do no harm: on a run narrowed to a
1344
+ part of the suite or capped ({@link isDiagnosisRun}), never on the whole-suite proof run, and
1345
+ never over a `--trace` the executor wrote itself. A capped walk is exactly the run whose
1346
+ failures someone is about to work on; the proof run is graded as it is.
1347
+
1348
+ ```typescript
1349
+ function diagnosisTraceCommand(args: {
1350
+ command: string
1351
+ cwd: string
1352
+ readFile: (path: string) => Promise<string>
1353
+ }): Promise<DiagnosisTraceRewrite>
1354
+ ```
1355
+
1356
+ - `args` — The command, the directory it starts in, and the backend's file read.
1357
+
1358
+ **Returns:** The command to run and the sentence to report, or the command unchanged.
1359
+
1028
1360
  #### `shellQuote(s)`
1029
1361
 
1030
1362
  Shell-safe quoting using single quotes. Unlike JSON.stringify (double quotes),
@@ -1185,19 +1517,19 @@ killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
1185
1517
 
1186
1518
  - a trailing `| tail -N`, which holds everything back until the command ends.
1187
1519
  It is dropped: `wait_for_task` already returns the end of the log.
1520
+ - any output-only filter piped after a SUITE runner (`| grep … | tail -20`), which hides the
1521
+ failing test's own error and leaves the pipeline's exit status — the filter's — as the run's.
1522
+ Every such stage is dropped ({@link stripTrailingSuiteFilters}); `wait_for_task` already
1523
+ returns the end of the full log, and a run whose tally shows a failure must never report 0
1524
+ (xr-molecule-flash-m101 +25:54).
1188
1525
  - `playwright test` with no `--reporter`, which runs the config's reporter —
1189
1526
  often `html`, which prints nothing per test. `--reporter=list` is added
1190
- when the playwright run is the command's last step and nothing is piped or
1191
- redirected from it: it writes each result as it finishes, and it is the
1192
- shape the platform reads a background suite back in.
1527
+ when the playwright run is the command's last step, piped or not: it writes each result as
1528
+ it finishes, and it is the shape the platform reads a background suite back in.
1193
1529
 
1194
1530
  The run ends up with exactly ONE reporter that prints each failure — `list`. A run that names
1195
1531
  any other printing reporter has it REPLACED, never joined: two of them write every test result
1196
1532
  into the log twice, which on X1 flash-m83 (2026-10-16) made a `--reporter=list,line` run (its
1197
- `line` added by this function one run earlier) report two distinct failures as "4 of this run's
1198
- tests have already failed the same way", and the executor stopped a 139-test walk at test 21
1199
- with a single cause in hand. `line` alone is no use either: it writes `[n/N] <title>` with no
1200
- ✓/✘ mark and no duration, so on X1 flash-m62 an 8.3-minute walk of one suite could never trip
1201
1533
 
1202
1534
  _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
1203
1535
 
@@ -1247,6 +1579,47 @@ function isPlaywrightRunCommand(command: string): boolean
1247
1579
 
1248
1580
  **Returns:** True when the command starts a Playwright run.
1249
1581
 
1582
+ #### `stripTrailingSuiteFilters(command)`
1583
+
1584
+ Drop the output-only filters piped after the command's suite runner.
1585
+
1586
+ A filter after the runner is not "the end of the log" the executor asked for: it hides the
1587
+ failing test's own error (it usually matches none of `✘|passed|failed`), and the pipeline's exit
1588
+ status becomes the FILTER's, so `… 2>&1 | grep -E "✘|passed|failed|skipped" | tail -20` came back
1589
+ `exitCode: 0` for a run with a failure in it (xr-molecule-flash-m101 +25:54). The tool returns
1590
+ the end of the full log anyway, so the filter only costs diagnosability.
1591
+
1592
+ Only a contiguous suffix of the stages after the runner is cut, and only stages the parse proves
1593
+ are output-only ({@link isOutputOnlyFilter}): a `| tee` or `| xargs` keeps its place, and a
1594
+ caller that reports the run's exit code must then treat it as unknown
1595
+ ({@link suiteRunPipedAfterRunner}).
1596
+
1597
+ ```typescript
1598
+ function stripTrailingSuiteFilters(command: string): SuiteFilterStrip
1599
+ ```
1600
+
1601
+ - `command` — The shell command.
1602
+
1603
+ **Returns:** The rewritten command and the stages dropped.
1604
+
1605
+ #### `suiteRunPipedAfterRunner(command)`
1606
+
1607
+ Whether the command's last step runs a suite whose output still passes through a stage after
1608
+ the runner — one {@link stripTrailingSuiteFilters} could not prove is output-only (`| tee log`,
1609
+ `| xargs …`). The pipeline's exit status is that stage's, not the run's.
1610
+
1611
+ A command whose stages cannot be placed in the text (irregular whitespace inside the run) is
1612
+ reported as piped too: whether a pipe follows the runner is then UNKNOWN, and an unknown exit
1613
+ code that gets trusted is a failing run read as a pass — the failure this rule exists to stop.
1614
+
1615
+ ```typescript
1616
+ function suiteRunPipedAfterRunner(command: string): boolean
1617
+ ```
1618
+
1619
+ - `command` — The shell command, after the streaming rewrite when one was applied.
1620
+
1621
+ **Returns:** True when a pipe this tool must not drop still follows the suite runner, or when the command runs a suite the parse cannot place.
1622
+
1250
1623
  #### `suiteRunPriorityLaunch(command, launch)`
1251
1624
 
1252
1625
  Start a background suite run at lower CPU and IO priority, so the sandbox it
@@ -1661,10 +2034,12 @@ function failureBlocks(s: string): string[]
1661
2034
  The shell command whose stdout is the interesting part of a log that is still being written:
1662
2035
  the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
1663
2036
 
1664
- A Playwright `list` run prints one line per test as it finishes and its error blocks only at
1665
- the end, so the LAST bytes of a running log are its least informative part — in X1 flash-m61
1666
- the last 3 000 bytes of a 139-test walk were the skip list, and all 61 failures were above
1667
- them, which cost the run the grep it had to make anyway.
2037
+ A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
2038
+ log are its least informative part — in X1 flash-m61 the last 3 000 bytes of a 139-test walk
2039
+ were the skip list, and all 61 failures were above them, which cost the run the grep it had to
2040
+ make anyway. A background run also carries each failure's own error as it fails
2041
+ ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
2042
+ those blocks' headers and their `Error:` lines, so this slice shows the causes too.
1668
2043
 
1669
2044
  ```typescript
1670
2045
  function runningOutputSliceCommand(log: string, own?: string): string
@@ -1700,9 +2075,11 @@ function runningCauseCommand(log: string, own?: string): string
1700
2075
  The cause repeating among a running suite's failures, or null.
1701
2076
 
1702
2077
  Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
1703
- path and how long it ran — and defers each failure's error to the summary at the end, so
1704
- mid-run the only signature those lines carry is the duration. Where the log carries the
1705
- failure bodies (a `line` reporter's output, or a finished run's summary) the signature is the
2078
+ path and how long it ran — and, on a run the platform started in the background, each failure's
2079
+ own error with it ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}); a `list` run with no error bodies in
2080
+ its log (an older Playwright, a log the bodies have been cut from) carries only the duration as
2081
+ a signature. Where the log carries the failure bodies (either inline, or in a finished run's
2082
+ summary) the signature is the
1706
2083
  error itself — {@link failureMessage}: the error class plus the locator or the values that
1707
2084
  differ — which is stronger (it needs no assumption about how long a failure should take), so
1708
2085
  the message is looked for first and the duration is then left with the tests whose bodies the
@@ -1715,8 +2092,8 @@ each was the test timeout and one missing `navigate('/')` was the cause of all o
1715
2092
 
1716
2093
  The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
1717
2094
  prints each failure twice (a second reporter, a retry) had X1 flash-m83 told that four of its
1718
- tests had failed the same way when two had, so the executor stopped a 139-test walk at test 21
1719
- with one cause in hand (2026-10-16).
2095
+
2096
+ _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
1720
2097
 
1721
2098
  ```typescript
1722
2099
  function repeatingFailure(log: string): RepeatingFailure | null
@@ -1751,7 +2128,69 @@ function universalCause(log: string, found: RepeatingFailure): UniversalCause |
1751
2128
 
1752
2129
  **Returns:** The cause's count and files, or null when the run is not failing whole.
1753
2130
 
1754
- #### `failuresByCause(log)`
2131
+ #### `earlyReturnCause(found, universal)`
2132
+
2133
+ The ONE finding that ends a wait early, and the only one a note may hand a stop command over
2134
+ for: a PROVEN cause that is failing the whole run.
2135
+
2136
+ Both halves are load-bearing, each of them learned from a run that lost minutes to the other.
2137
+
2138
+ - **Failing the WHOLE run** is {@link universalCause}: every failure the log names, across
2139
+ {@link UNIVERSAL_CAUSE_FILES} spec files or more, with nothing passing since the first — the
2140
+ dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
2141
+ repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
2142
+ left to name is the only thing that will name them.
2143
+ - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
2144
+ a suite whose config sets a timeout spends that timeout on every failure that waits for
2145
+ anything, so three unrelated tests timing out together look exactly like one broken run
2146
+ (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). X1
2147
+ xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
2148
+ shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
2149
+ — read the early return as "stop waiting, edit now", and voided a run that was still walking;
2150
+ the second cost a 224 s wait that ended with no result at all.
2151
+
2152
+ Neither half alone is enough, and {@link repeatingFailure} finding a signature is not either:
2153
+
2154
+ _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
2155
+
2156
+ ```typescript
2157
+ function earlyReturnCause(
2158
+ found: RepeatingFailure,
2159
+ universal: UniversalCause | null | undefined,
2160
+ ): UniversalCause | null
2161
+ ```
2162
+
2163
+ - `found` — What {@link repeatingFailure} found mid-run.
2164
+ - `universal` — What {@link universalCause} found for it, if anything.
2165
+
2166
+ **Returns:** The cause to stop the run over, or null — in which case nothing may be stopped, the wait keeps going, and the finding rides the run's result as a note.
2167
+
2168
+ #### `matchingExcludedTitles(heading, excluded)`
2169
+
2170
+ The titles a request excludes that a test's heading names — the ONE matcher for "is this
2171
+ failure one the request does not count?".
2172
+
2173
+ The request quotes a test's own title (`… except "should require login to post comment"`,
2174
+ extracted by molecule-dev's `excludedTestTitles`) while a run's heading carries more of it
2175
+ (`comments.spec.ts › Comments › should require login to post comment`), so containment is the
2176
+ comparison. Whitespace is normalised and case is ignored: a brief may wrap the title across a
2177
+ line (`xr-molecule-flash-m44`), and the reporter's own casing never has to match the brief's.
2178
+
2179
+ Two readers used to answer this separately — the causes note (which therefore asked the
2180
+ executor to fix a test the same result said not to) and molecule-dev's exclusion notes — and
2181
+ xr-molecule-flash-m101 paid for the gap (+12:00: a turn and an edit spent on the excluded
2182
+ test).
2183
+
2184
+ ```typescript
2185
+ function matchingExcludedTitles(heading: string, excluded: readonly string[]): string[]
2186
+ ```
2187
+
2188
+ - `heading` — The text to match against: a test's title, its whole `file › describe › title` path, or (for a note that only has the run's output) the run's text.
2189
+ - `excluded` — The titles the request carves out, if the caller knows them.
2190
+
2191
+ **Returns:** The excluded titles this heading names, in the request's order.
2192
+
2193
+ #### `failuresByCause(log, excluded)`
1755
2194
 
1756
2195
  Every failure a run names, grouped by the cause behind it — one group per cause, with the
1757
2196
  tests it hit.
@@ -1761,21 +2200,28 @@ after another with no statement of which of them share a cause, so a run of 19 f
1761
2200
  bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
1762
2201
  single cause, two of them found by runs stopped before they walked the rest.
1763
2202
 
1764
- The signature is the cause itself where the log carries the failures' bodies (a finished run,
1765
- or a `line` reporter) — {@link failureMessage}: the error class plus the locator or the values
1766
- that differ — and how long they took where it does not (a still-running `list` log, which is
1767
- why a duration group is labelled as a duration and not as a proven cause). A test the log
1768
- printed twice, or failed twice, is one test.
2203
+ The signature is the cause itself where the log carries the failures' bodies (a finished run, a
2204
+ background `list` run — whose failures print as they fail — or a `line` reporter) —
2205
+ {@link failureMessage}: the error class plus the locator or the values that differ — and how long
2206
+ they took where it does not, which is why a duration group is labelled as a duration and not as a
2207
+ proven cause. A test the log printed twice, or failed twice, is one test.
2208
+
2209
+ A test the request EXCLUDES by name is not a cause to fix: its failure does not count for
2210
+ this request ({@link matchingExcludedTitles}), so it is dropped here rather than handed to the
2211
+ executor as work — a result that names it beside "it does not count" is the contradiction
2212
+ xr-molecule-flash-m101 filed (+12:00: the model spent a turn and an edit on the excluded test,
2213
+ on a page that was passing).
1769
2214
 
1770
2215
  ```typescript
1771
- function failuresByCause(log: string): FailureCause[]
2216
+ function failuresByCause(log: string, excluded?: readonly string[]): FailureCause[]
1772
2217
  ```
1773
2218
 
1774
2219
  - `log` — The log of a finished or running run.
2220
+ - `excluded` — The test titles the request carves out, if the caller knows them.
1775
2221
 
1776
2222
  **Returns:** The causes, the one with the most tests first.
1777
2223
 
1778
- #### `failureCausesNote(log)`
2224
+ #### `failureCausesNote(log, excluded, run)`
1779
2225
 
1780
2226
  The one-line-per-cause summary of a finished run's failures, or null when the run named fewer
1781
2227
  than two of them.
@@ -1784,15 +2230,26 @@ This is what makes a run worth finishing: every cause the run found, in the resu
1784
2230
  executor reads, so one round of fixes covers all of them rather than the one cause a run
1785
2231
  stopped early happened to reach.
1786
2232
 
2233
+ A test the request excludes is not one of the causes — it is dropped and named once, at the
2234
+ end, so a result never asks to fix a test it also says to ignore. Where the caller knows how
2235
+ the run was started, the note also hands over the narrowed re-run of exactly those tests
2236
+ ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the whole suite.
2237
+
1787
2238
  ```typescript
1788
- function failureCausesNote(log: string): string | null
2239
+ function failureCausesNote(
2240
+ log: string,
2241
+ excluded?: readonly string[],
2242
+ run?: { command: string; cwd?: string; log?: string },
2243
+ ): string | null
1789
2244
  ```
1790
2245
 
1791
2246
  - `log` — The finished run's output.
2247
+ - `excluded` — The test titles the request carves out, if the caller knows them.
2248
+ - `run` — How the run was started, when the caller knows it: the narrowed re-run is built from `command`/`cwd`, and `log` (the path the output was written to) is what the "left out" sentence greps when more causes exist than the result has room for.
1792
2249
 
1793
2250
  **Returns:** The note, or null.
1794
2251
 
1795
- #### `rerunOfRunningSuite(run, locations)`
2252
+ #### `rerunOfRunningSuite(run, locations, excluded)`
1796
2253
 
1797
2254
  The command that re-runs the tests at `locations` the way the run IN FLIGHT ran them: the same
1798
2255
  invocation, in the same directory, narrowed to those tests — or null when the running command
@@ -1809,11 +2266,13 @@ the same app and database, and that is exactly what is refused while the run wal
1809
2266
  function rerunOfRunningSuite(
1810
2267
  run: { command: string; cwd?: string },
1811
2268
  locations: readonly string[],
2269
+ excluded?: readonly string[],
1812
2270
  ): string | null
1813
2271
  ```
1814
2272
 
1815
2273
  - `run` — The run in flight: the command it was started with, as it was run, and the directory that command ran in.
1816
2274
  - `locations` — The failing `file:line`s to re-run, as the run's log printed them.
2275
+ - `excluded` — The test titles the request carves out: the re-run inverts them out (`--grep-invert`) so it cannot report them again.
1817
2276
 
1818
2277
  **Returns:** The re-run command, or null when there is no suite to narrow.
1819
2278
 
@@ -1826,18 +2285,19 @@ what that function could actually see in the log — including the count, which
1826
2285
  tests, so the note never names one or two tests as a cause.
1827
2286
 
1828
2287
  The note names the cause early so the executor can START FIXING while the run keeps walking,
1829
- and it asks for the run to be stopped only when {@link universalCause} says the cause is
1830
- failing the whole run. X1 flash-m76 (2026-10-16) spent 4.0 minutes walking 15 tests whose one
1831
- cause was certain at the third, 47 s in, and the note answers exactly that; X1 flash-m83 then
1832
- stopped eight runs at their first repeating cause in 14.5 minutes and never finished one, so
1833
- the note must not say to stop when the run still has other causes to name.
2288
+ and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
2289
+ predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
2290
+ on is a finding this note never asks to stop for. X1 flash-m76 (2026-10-16) spent 4.0 minutes
2291
+ walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
2292
+ exactly that; X1 flash-m83 then stopped eight runs at their first repeating cause in 14.5
2293
+ minutes and never finished one, so the note must not say to stop when the run still has other
2294
+ causes to name. X1 xr-molecule-flash-m99 (2026-10-16) is the third shape: a shared duration is
2295
+ not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
2296
+ for — never as a verdict with a stop command in it.
1834
2297
 
1835
2298
  The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
1836
2299
  re-run of one of its tests is a second run against the same app and database, and the platform
1837
2300
  refuses that ({@link suiteRunInTheWay}, the same rule its overlap hold answers with):
1838
- xr-molecule-flash-m89 (2026-10-16) read _"Start on them now, while the run keeps walking.
1839
- `null-fields.spec.ts:53` alone reproduces the first one."_ and sent exactly that re-run 23 s
1840
- later, where the hold answered *"A playwright suite is already running against this app and
1841
2301
 
1842
2302
  _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
1843
2303
 
@@ -1855,7 +2315,7 @@ function repeatingFailureNote(
1855
2315
  ```
1856
2316
 
1857
2317
  - `found` — What {@link repeatingFailure} found.
1858
- - `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for a {@link UniversalCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), and `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it.
2318
+ - `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for an {@link earlyReturnCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), and `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it.
1859
2319
 
1860
2320
  **Returns:** The note.
1861
2321
 
@@ -1982,6 +2442,80 @@ per-request limit, with each request bounded. xr-molecule-flash-m93 (+06:22,
1982
2442
  const SUITE_OVER_BUDGET_ADVICE: string
1983
2443
  ```
1984
2444
 
2445
+ #### `MAX_TRACE_ARTIFACTS`
2446
+
2447
+ How many traces one read reports, newest first.
2448
+
2449
+ ```typescript
2450
+ const MAX_TRACE_ARTIFACTS: 12
2451
+ ```
2452
+
2453
+ #### `MAX_TRACED_REQUESTS`
2454
+
2455
+ How many requests one test's trace contributes.
2456
+
2457
+ ```typescript
2458
+ const MAX_TRACED_REQUESTS: 6
2459
+ ```
2460
+
2461
+ #### `MAX_TRACED_CONSOLE`
2462
+
2463
+ How many console errors one test's trace contributes.
2464
+
2465
+ ```typescript
2466
+ const MAX_TRACED_CONSOLE: 4
2467
+ ```
2468
+
2469
+ #### `MAX_TRACE_NOTE_TESTS`
2470
+
2471
+ How many tests the note in a run's result names.
2472
+
2473
+ ```typescript
2474
+ const MAX_TRACE_NOTE_TESTS: 5
2475
+ ```
2476
+
2477
+ #### `MAX_TRACE_NOTE_REQUESTS`
2478
+
2479
+ How many requests the note names per test.
2480
+
2481
+ ```typescript
2482
+ const MAX_TRACE_NOTE_REQUESTS: 4
2483
+ ```
2484
+
2485
+ #### `TRACE_NOTE_URL_CHARS`
2486
+
2487
+ How long a URL the note prints, before the middle is cut.
2488
+
2489
+ ```typescript
2490
+ const TRACE_NOTE_URL_CHARS: 96
2491
+ ```
2492
+
2493
+ #### `DIAGNOSIS_TRACE_FLAG`
2494
+
2495
+ The `--trace` flag this platform adds, and the only value it adds it with.
2496
+
2497
+ ```typescript
2498
+ const DIAGNOSIS_TRACE_FLAG: '--trace=retain-on-failure'
2499
+ ```
2500
+
2501
+ #### `PLAYWRIGHT_FAILURES_INLINE_ENV`
2502
+
2503
+ Playwright's switch for printing each failure's own error as that test fails, instead of only in
2504
+ the summary at the end: `list` reads it in its constructor (`ListReporterOptions.printFailuresInline`,
2505
+ `playwright/lib/reporters/list.js` — 1.63, the version the toolchain pins), and when it prints
2506
+ inline the end-of-run repeat is suppressed with it (`epilogue(!printFailuresInline)`), so one
2507
+ reporter still prints each failure exactly once.
2508
+
2509
+ A background suite is put on `list` (see {@link streamingBackgroundCommand}) and this with it, so
2510
+ the log names its causes WHILE it walks. Without it the log's only failure lines are `✘` rows:
2511
+ X1 xr-molecule-flash-m99 (+19:27 → +21:00, 2026-10-16) was told to `grep -n -A 20 '<test name>'`
2512
+ the log, got nothing back through four greps, inferred causes from the spec prose instead, and
2513
+ edited `app/src` while the run was still walking — which voids the run.
2514
+
2515
+ ```typescript
2516
+ const PLAYWRIGHT_FAILURES_INLINE_ENV: 'PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE'
2517
+ ```
2518
+
1985
2519
  #### `MAX_READ_SIZE`
1986
2520
 
1987
2521
  Max file size for read_file (5MB).
@@ -2217,12 +2751,39 @@ How many of a repeating cause's failures the note lists before it says "and N mo
2217
2751
  const REPEATING_FAILURE_LISTED: 5
2218
2752
  ```
2219
2753
 
2754
+ #### `TOOL_RESULT_CHARS`
2755
+
2756
+ The platform's cap on one tool result's text: `MAX_TOOL_OUTPUT_CHARS` in molecule-dev's
2757
+ `api/src/ai/chat-handler.ts`, which cuts a longer string field's MIDDLE. A finished run's
2758
+ stdout is cut to it, so the cause list must not depend on reading the log back — and must
2759
+ itself fit inside it, or the cut lands in the list.
2760
+
2761
+ ```typescript
2762
+ const TOOL_RESULT_CHARS: 8000
2763
+ ```
2764
+
2765
+ #### `FAILURE_CAUSES_CHARS`
2766
+
2767
+ The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}. The recap
2768
+ measured by xr-molecule-flash-m102 (+12:43) was `12 failures, 8 causes`: one cause line is
2769
+ ~200 chars and ~80 more with its two body lines, so 8 causes is ~2.3 k against the 8 k cap —
2770
+ the old 5-cause cap withheld 3 causes that the same result had room for.
2771
+
2772
+ The rest of the cap is left to the header, the narrowed re-run command and the exclusion
2773
+ line, which ride the same note.
2774
+
2775
+ ```typescript
2776
+ const FAILURE_CAUSES_CHARS: number
2777
+ ```
2778
+
2220
2779
  #### `FAILURE_CAUSES_LISTED`
2221
2780
 
2222
- How many causes a finished run's summary lists before it says "and N more".
2781
+ How many causes a finished run's summary lists at most. The character budget above is the
2782
+ bound that must hold (it is what keeps the note on the surviving side of the tool-result
2783
+ cut); this is the bound past which a list stops being read at all.
2223
2784
 
2224
2785
  ```typescript
2225
- const FAILURE_CAUSES_LISTED: 5
2786
+ const FAILURE_CAUSES_LISTED: 12
2226
2787
  ```
2227
2788
 
2228
2789
  #### `FAILURE_CAUSE_TESTS_LISTED`