@molecule/api-ai-tools 1.14.3 → 1.14.5
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 +243 -189
- package/dist/browser-probe.d.ts +4 -4
- package/dist/browser-probe.d.ts.map +1 -1
- package/dist/browser-probe.js +5 -5
- package/dist/command-position.d.ts +15 -16
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +21 -23
- package/dist/cut-run.d.ts +9 -9
- package/dist/cut-run.js +6 -6
- package/dist/failure-kind.d.ts +6 -6
- package/dist/failure-kind.js +6 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/inline-script.d.ts +5 -5
- package/dist/inline-script.js +6 -6
- package/dist/line-diff.d.ts +2 -3
- package/dist/line-diff.d.ts.map +1 -1
- package/dist/line-diff.js +2 -3
- package/dist/no-test-run.d.ts +5 -5
- package/dist/no-test-run.d.ts.map +1 -1
- package/dist/no-test-run.js +9 -8
- package/dist/output-filter.d.ts +2 -2
- package/dist/output-filter.js +2 -2
- package/dist/pipeline-exit-note.d.ts +1 -1
- package/dist/pipeline-exit-note.js +1 -1
- package/dist/playwright-report.d.ts +1 -1
- package/dist/playwright-report.js +1 -1
- package/dist/server-command.d.ts +2 -2
- package/dist/server-command.js +2 -2
- package/dist/sleep-peek.d.ts +1 -1
- package/dist/sleep-peek.js +1 -1
- package/dist/suite-overlap.d.ts +24 -25
- package/dist/suite-overlap.d.ts.map +1 -1
- package/dist/suite-overlap.js +41 -43
- package/dist/tools.d.ts +26 -28
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +114 -72
- package/dist/trace-artifacts.d.ts +73 -65
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +76 -77
- package/dist/types.d.ts +5 -5
- package/dist/utilities.d.ts +128 -92
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +298 -143
- package/package.json +2 -2
package/dist/utilities.d.ts
CHANGED
|
@@ -143,7 +143,7 @@ export declare function outputIsWithheldUntilExit(command: string): boolean;
|
|
|
143
143
|
* The file a command sends its own output to, when it redirects it (`> out.txt`,
|
|
144
144
|
* `>> log`, `&> run.log`, `| tee [-a] file`), or null. A background command that does
|
|
145
145
|
* this leaves its task log empty, so the log is the wrong place to look and an empty
|
|
146
|
-
* log proves nothing
|
|
146
|
+
* log proves nothing.
|
|
147
147
|
*
|
|
148
148
|
* Which redirect counts is decided by the command's own shell structure, from the same
|
|
149
149
|
* parse every other command reader uses: a redirect written in a BACKGROUNDED statement
|
|
@@ -151,7 +151,7 @@ export declare function outputIsWithheldUntilExit(command: string): boolean;
|
|
|
151
151
|
* the command's. The platform's own preview launch is exactly that — the preview server is
|
|
152
152
|
* started with `>/tmp/mol-preview-4173.log 2>&1 &` inside the build/serve prelude, and the
|
|
153
153
|
* suite's foreground command follows it — so a reader that took the last `>` in the whole
|
|
154
|
-
* text
|
|
154
|
+
* text tells a run its output went to the PREVIEW's log.
|
|
155
155
|
*
|
|
156
156
|
* The target comes back without its shell quotes: `> '/tmp/x.log'` is `/tmp/x.log`, and
|
|
157
157
|
* the callers resolve a relative one against the command's cwd.
|
|
@@ -170,8 +170,8 @@ export declare function outputRedirectTarget(command: string): string | null;
|
|
|
170
170
|
* Every background command is given this (see {@link streamingBackgroundCommand}), so a suite the
|
|
171
171
|
* executor starts through its own wrapper is covered too — the wrapper passes the environment on
|
|
172
172
|
* to the Playwright process it spawns. Without it the log's only failure lines are `✘` rows:
|
|
173
|
-
*
|
|
174
|
-
*
|
|
173
|
+
* A run told to `grep -n -A 20 '<test name>'` the log got nothing back through four greps,
|
|
174
|
+
* inferred causes from the spec prose instead, and
|
|
175
175
|
* edited `app/src` while the run was still walking — which voids the run.
|
|
176
176
|
*/
|
|
177
177
|
export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE";
|
|
@@ -179,7 +179,7 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
179
179
|
* Rewrite a command about to run in the background so its log grows while it
|
|
180
180
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
|
181
181
|
* `wait_for_task` reads as "stuck", so working suites were reported stuck and
|
|
182
|
-
* killed (
|
|
182
|
+
* killed (one run lost 20 of its 60 minutes this way):
|
|
183
183
|
*
|
|
184
184
|
* - a trailing `| tail -N`, which holds everything back until the command ends.
|
|
185
185
|
* It is dropped: `wait_for_task` already returns the end of the log.
|
|
@@ -187,7 +187,7 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
187
187
|
* failing test's own error and leaves the pipeline's exit status — the filter's — as the run's.
|
|
188
188
|
* Every such stage is dropped from EVERY suite run in the command, not only the last
|
|
189
189
|
* ({@link stripTrailingSuiteFilters}); `wait_for_task` already returns the end of the full log,
|
|
190
|
-
* and a run whose tally shows a failure must never report 0
|
|
190
|
+
* and a run whose tally shows a failure must never report 0.
|
|
191
191
|
* - `playwright test` with no `--reporter`, which runs the config's reporter —
|
|
192
192
|
* often `html`, which prints nothing per test. `--reporter=list` is added
|
|
193
193
|
* when the playwright run is the command's last step, piped or not: it writes each result as
|
|
@@ -197,11 +197,11 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
197
197
|
*
|
|
198
198
|
* The run ends up with exactly ONE reporter that prints each failure — `list`. A run that names
|
|
199
199
|
* any other printing reporter has it REPLACED, never joined: two of them write every test result
|
|
200
|
-
* into the log twice, which
|
|
200
|
+
* into the log twice, which made a `--reporter=list,line` run (its
|
|
201
201
|
* `line` added by this function one run earlier) report two distinct failures as "4 of this run's
|
|
202
202
|
* tests have already failed the same way", and the executor stopped a 139-test walk at test 21
|
|
203
203
|
* with a single cause in hand. `line` alone is no use either: it writes `[n/N] <title>` with no
|
|
204
|
-
* ✓/✘ mark and no duration, so
|
|
204
|
+
* ✓/✘ mark and no duration, so an 8.3-minute walk of one suite could never trip
|
|
205
205
|
* the fail-fast note. A reporter set that writes a machine format to a file (`json`, `html`,
|
|
206
206
|
* `junit`) is the
|
|
207
207
|
* executor's own and is left exactly as it is — so is any reporter this build does not know.
|
|
@@ -210,8 +210,8 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
210
210
|
* {@link PLAYWRIGHT_FAILURES_INLINE_ENV} (inert except for Playwright's `list` reporter), so each
|
|
211
211
|
* failure's own error is in the log as that test fails rather than only at the end: a suite that is
|
|
212
212
|
* still walking is exactly when the executor reads the log, and without it a running log carries
|
|
213
|
-
* `✘` rows and nothing else (
|
|
214
|
-
* guessed;
|
|
213
|
+
* `✘` rows and nothing else (~90 s went to greps of one, then to fixing what was
|
|
214
|
+
* guessed; a wrapper-launched run lost 6.5 minutes to the same shape).
|
|
215
215
|
*
|
|
216
216
|
* Only the step that runs the suite is touched, and only its reporter, and only when that step
|
|
217
217
|
* runs something: `npx playwright test --list` takes two seconds and writes nothing per test, so
|
|
@@ -222,9 +222,8 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
222
222
|
*
|
|
223
223
|
* A command that starts the suite MORE THAN ONCE is not launched at all: every run it carries is
|
|
224
224
|
* a whole walk of the same suite against the same app, and only the last step is rewritten, so the
|
|
225
|
-
* earlier walks run unmeasured for nothing.
|
|
226
|
-
*
|
|
227
|
-
* first had not. The platform refuses a second CONCURRENT run against one app and database by the
|
|
225
|
+
* earlier walks run unmeasured for nothing. Two whole walks of one 138-test suite chained with
|
|
226
|
+
* `;` — 4.5 minutes each — measured nothing the first had not. The platform refuses a second CONCURRENT run against one app and database by the
|
|
228
227
|
* ONE rule ({@link suiteRunInTheWay}) — for every pair, whatever uid a run mints — and this is the
|
|
229
228
|
* same hazard inside one command ({@link suiteRunStatements}). `error` then carries the refusal,
|
|
230
229
|
* naming each suite step and the ONE command to send instead (the last suite step, rewritten as
|
|
@@ -235,8 +234,8 @@ export declare const PLAYWRIGHT_FAILURES_INLINE_ENV = "PLAYWRIGHT_LIST_PRINT_FAI
|
|
|
235
234
|
* excludes by name, and whether this command IS the platform's graded proof
|
|
236
235
|
* ({@link DiagnosisRunContext}). A `--grep-invert` of exactly those exclusions is the
|
|
237
236
|
* request's own scope, not a diagnosis, so it keeps the suite's retries on a deployed proof
|
|
238
|
-
*
|
|
239
|
-
* keeps them whatever its scope, targeted set included
|
|
237
|
+
* — and a proof the platform itself composed and launched
|
|
238
|
+
* keeps them whatever its scope, targeted set included.
|
|
240
239
|
* @param reportPath - Where this run should write Playwright's standard `json` report, when the
|
|
241
240
|
* caller keeps one ({@link ./playwright-report.js}). The reporter is added to the run's own
|
|
242
241
|
* reporter set, never in place of one, and its output goes to that file through
|
|
@@ -305,7 +304,7 @@ export declare function isSuiteRunCommand(command: string): boolean;
|
|
|
305
304
|
* Refuse only a command made entirely of identical suite invocations.
|
|
306
305
|
*
|
|
307
306
|
* Mixed shell commands execute as written. Rebuilding their prefix or suffix can lose writes,
|
|
308
|
-
* break heredoc delimiters or turn an && guard into unconditional work
|
|
307
|
+
* break heredoc delimiters or turn an && guard into unconditional work. Different args
|
|
309
308
|
* or environments may select different suites or targets; they are not known duplicates either.
|
|
310
309
|
* This is an optimization, so uncertainty always allows the original command.
|
|
311
310
|
*
|
|
@@ -318,8 +317,8 @@ export declare function repeatedSuiteRunError(command: string): string | null;
|
|
|
318
317
|
* flags, and the flags that could not be placed.
|
|
319
318
|
*
|
|
320
319
|
* The grader walks the suite under its OWN budget, so a run whose runner config sets a different
|
|
321
|
-
* timeout or worker count measures a run the grader never sees (
|
|
322
|
-
*
|
|
320
|
+
* timeout or worker count measures a run the grader never sees (a 60 s wrapper config produced
|
|
321
|
+
* a green local proof the official 15 s grader then failed). Playwright
|
|
323
322
|
* lets a command-line flag win over every config file, so the suite's values are put on the
|
|
324
323
|
* runner's own arguments — written where {@link statementArgumentEnd} places them, never after a
|
|
325
324
|
* pipe, redirect or group closer. Only the command's LAST suite step is touched: that is the run
|
|
@@ -368,7 +367,7 @@ export declare function suiteRunConfigCommand(command: string, config: string):
|
|
|
368
367
|
* Used where the platform moves a run to the background: the executor wrote the bound to fit this
|
|
369
368
|
* tool's foreground ceiling, and in the background that ceiling no longer applies — the task is
|
|
370
369
|
* bounded by `wait_for_task` and the turn's deadline — so the prefix only kills the walk
|
|
371
|
-
* ({@link withoutTimeoutWrapper}
|
|
370
|
+
* ({@link withoutTimeoutWrapper}).
|
|
372
371
|
*
|
|
373
372
|
* @param command - The shell command.
|
|
374
373
|
* @returns The rewritten command, and the largest lifted bound (null when nothing was lifted).
|
|
@@ -382,7 +381,7 @@ export declare function withoutSuiteTimeout(command: string): {
|
|
|
382
381
|
* that runs something.
|
|
383
382
|
*
|
|
384
383
|
* `npx playwright test --list` matched every `\bplaywright\s+test\b` test in the platform and was
|
|
385
|
-
* then treated as a run: held (
|
|
384
|
+
* then treated as a run: held (a config check), backgrounded for a run that outlasts
|
|
386
385
|
* the tool's ceiling, and rewritten with `--reporter`. A list takes two seconds, prints its lines
|
|
387
386
|
* and exits, so it is none of those; {@link commandRunsNoTests} is what decides.
|
|
388
387
|
*
|
|
@@ -433,8 +432,8 @@ export interface SuiteRunPipelinePlacement {
|
|
|
433
432
|
* code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
|
|
434
433
|
* code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
|
|
435
434
|
* any run whose statements it could not place in the text, which one heredoc body was enough to
|
|
436
|
-
* cause:
|
|
437
|
-
*
|
|
435
|
+
* cause: a config written in the same `exec_command` made the run come back with no exit code
|
|
436
|
+
* at all, and the note under it claimed a stage after the
|
|
438
437
|
* runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
|
|
439
438
|
* "unplaced" is the rare, named case rather than a guess dressed as an answer.
|
|
440
439
|
*
|
|
@@ -456,10 +455,9 @@ export declare function suiteRunPipedAfterRunner(command: string): boolean;
|
|
|
456
455
|
* shares with everything else stays responsive to what the executor does next.
|
|
457
456
|
*
|
|
458
457
|
* A whole-suite Playwright run saturates the one container the app's Vite
|
|
459
|
-
* server, its API process and every other command also live in:
|
|
460
|
-
* the
|
|
461
|
-
*
|
|
462
|
-
* measured on the chat payload and on the live-stream follower independently.
|
|
458
|
+
* server, its API process and every other command also live in: a backgrounded 139-test run
|
|
459
|
+
* has left the two-word shell command sent next — tick its plan file — returning nothing for
|
|
460
|
+
* 2 min 40 s, measured on the chat payload and on the live-stream follower independently.
|
|
463
461
|
* The backgrounding note is right that the executor should go do other work;
|
|
464
462
|
* what it cannot promise is that the sandbox will let it.
|
|
465
463
|
*
|
|
@@ -488,8 +486,8 @@ export declare function suiteRunPriorityLaunch(command: string, launch: string):
|
|
|
488
486
|
* The first check reads the commands the shell actually RUNS — the statements, a `sh -c`
|
|
489
487
|
* string's body, and every command substitution ({@link executedCommands}) — so a `|` inside a
|
|
490
488
|
* quoted grep alternation is data, not a pipeline: `grep -E "USER CALLS|ENV|BUTTON|passed|failed"
|
|
491
|
-
* app.log` prints log lines, and the `ENV` in its pattern
|
|
492
|
-
*
|
|
489
|
+
* app.log` prints log lines, and the `ENV` in its pattern starts nothing — a text
|
|
490
|
+
* match would block exactly that call as an environment dump, twice over. What the
|
|
493
491
|
* shell DOES run stays visible through the same walk: `ls | env`, `bash -c 'env'`,
|
|
494
492
|
* `echo "$(printenv)"` and a nested `sh -c 'x; env'` are all commands, and so is `env` itself
|
|
495
493
|
* when nothing follows it.
|
|
@@ -513,14 +511,36 @@ export declare function resolvePath(path: string, projectRoot: string): string;
|
|
|
513
511
|
* absolute path elsewhere, or a `..` that climbs out). {@link resolvePath}
|
|
514
512
|
* clamps such a path to the root; a file tool must refuse it instead of
|
|
515
513
|
* answering about the root — a `list_files /tmp/x` used to return the
|
|
516
|
-
* workspace listing as if it were `/tmp/x
|
|
517
|
-
* 2026-10-14).
|
|
514
|
+
* workspace listing as if it were `/tmp/x`.
|
|
518
515
|
*
|
|
519
516
|
* @param path - The path the tool was given.
|
|
520
517
|
* @param projectRoot - The project root.
|
|
521
518
|
* @returns True when the path leaves the root.
|
|
522
519
|
*/
|
|
523
520
|
export declare function isOutsideRoot(path: string, projectRoot: string): boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Whether a command's FIRST statement already changes into `directory`.
|
|
523
|
+
*
|
|
524
|
+
* `exec_command` refuses a `cwd` outside the workspace, and that refusal tells
|
|
525
|
+
* the caller to drop `cwd` and start the command with `cd <path> && `. When the
|
|
526
|
+
* command ALREADY starts that way the advice describes what was just sent, so
|
|
527
|
+
* the command runs as written with the backend's working directory at the root
|
|
528
|
+
* instead (`{cwd: "/tmp", command: "cd /tmp && …"}` was refused with "start the command with
|
|
529
|
+
* `cd /tmp && `"; the same command resent with `cwd: "/workspace"` then passes).
|
|
530
|
+
*
|
|
531
|
+
* Only the leading statement counts, only when the shell's own parse puts it in
|
|
532
|
+
* the caller's shell — a `( cd … )` group or a `sh -c '…'` runs in its own
|
|
533
|
+
* directory and moves nothing — and only when it ends the way a following
|
|
534
|
+
* command needs (`&&`, `;`, a newline, or nothing after it). `cd … | cat` is a
|
|
535
|
+
* pipeline, so its `cd` runs in a subshell, and `&` backgrounds it: neither
|
|
536
|
+
* leaves the caller's directory changed.
|
|
537
|
+
*
|
|
538
|
+
* @param command - The shell command as the tool received it.
|
|
539
|
+
* @param directory - The `cwd` the caller asked for.
|
|
540
|
+
* @param projectRoot - The project root a relative path is resolved against.
|
|
541
|
+
* @returns True when the command's first statement `cd`s into `directory`.
|
|
542
|
+
*/
|
|
543
|
+
export declare function commandStartsWithCdInto(command: string, directory: string, projectRoot: string): boolean;
|
|
524
544
|
/**
|
|
525
545
|
* Validate that a glob/include pattern is safe (no shell metacharacters that could
|
|
526
546
|
* inject). Allows alphanumeric, `* ? . _ - /` and the bracket/paren glob chars `[] ()`.
|
|
@@ -557,8 +577,8 @@ export declare const BRACE_GLOB_HINT: string;
|
|
|
557
577
|
* Neither search tool expands braces itself: `grep --include` and `find` take
|
|
558
578
|
* the pattern as a single shell-quoted argument, so no shell ever sees it.
|
|
559
579
|
* `find_files {pattern:"*.{ts,tsx}"}` and `search_files {include:"*.{ts,tsx}"}`
|
|
560
|
-
* were both refused outright
|
|
561
|
-
*
|
|
580
|
+
* were both refused outright, costing a round trip and pushing the agent to a narrower
|
|
581
|
+
* search. The group is expanded here
|
|
562
582
|
* instead — `search_files` passes one `--include` per alternative and
|
|
563
583
|
* `find_files` ORs one `-name`/`-path` predicate per alternative.
|
|
564
584
|
*
|
|
@@ -577,8 +597,8 @@ export declare function expandBraceGlob(pattern: string): string[] | null;
|
|
|
577
597
|
*
|
|
578
598
|
* `find -name` compares a pattern against a file's BASENAME, so a pattern
|
|
579
599
|
* containing a slash (`helpers/*`, `e2e/*.spec.ts`, `src` + `**` + `/App.tsx`)
|
|
580
|
-
* can never match and the tool answers `[]` (
|
|
581
|
-
*
|
|
600
|
+
* can never match and the tool answers `[]` (twice in one run). Such a pattern is matched
|
|
601
|
+
* with `-path` instead: an fnmatch
|
|
582
602
|
* glob over the whole printed path, whose `*` crosses `/` — the rule
|
|
583
603
|
* `.gitignore` uses, and the reason a bare `helpers/*` means "a `helpers/`
|
|
584
604
|
* directory anywhere below the root". The pattern is anchored with a leading
|
|
@@ -621,25 +641,38 @@ export declare function findPathArgs(pattern: string): string;
|
|
|
621
641
|
* search root. Handed `/workspace/node_modules` and
|
|
622
642
|
* `@molecule/api-resource-user/dist/*.d.ts` — a directory named outright —
|
|
623
643
|
* `find` walked the sandbox's entire dependency tree and hit the tool's 10 s
|
|
624
|
-
* deadline without printing a result (
|
|
625
|
-
*
|
|
644
|
+
* deadline without printing a result (the one directory the model meant answered a `list_files`
|
|
645
|
+
* in 0 s). The literal prefix is that
|
|
626
646
|
* directory, and rooting the search at it is what keeps the call instant.
|
|
627
647
|
*
|
|
628
648
|
* A BARE first segment (`helpers/*`, `bonds/*`, `e2e/*.spec.ts`) is NOT one:
|
|
629
649
|
* `helpers/*` means "a `helpers/` directory anywhere below the root", which the
|
|
630
|
-
* tool documents and a live run relied on (
|
|
631
|
-
*
|
|
650
|
+
* tool documents and a live run relied on (a path pattern is not anchored to one depth).
|
|
651
|
+
* Rooting those at
|
|
632
652
|
* `<root>/helpers` would answer with a SUBSET, silently: the nested
|
|
633
653
|
* `api/src/helpers/` matches would disappear, and a missing file reads as
|
|
634
654
|
* "no such file". So a prefix is returned only when it names a path of two or
|
|
635
655
|
* more segments (`@molecule/api-resource-user/dist`), which is the shape that
|
|
636
656
|
* is a directory rather than a directory name.
|
|
637
657
|
*
|
|
658
|
+
* An npm SCOPE as the first segment, under a `node_modules` root, is the one
|
|
659
|
+
* exception that parameter `root` enables: npm installs a scope only directly
|
|
660
|
+
* inside a `node_modules` directory, and the walk already prunes every nested
|
|
661
|
+
* `node_modules` (see `findPruneArgs`), so the whole-tree form can only ever
|
|
662
|
+
* match `<root>/@molecule/…` — the scope directory IS the directory the pattern
|
|
663
|
+
* names. Left un-narrowed, a scoped-package pattern walked the sandbox's entire
|
|
664
|
+
* dependency tree and hit the 10 s deadline with no result at all. A bare NAME stays a
|
|
665
|
+
* directory name at any depth:
|
|
666
|
+
* `helpers/*` is unchanged even under a `node_modules` root.
|
|
667
|
+
*
|
|
638
668
|
* @param pattern - A pattern already accepted by {@link isValidGlob}.
|
|
669
|
+
* @param root - The resolved search root, when one is already known; a
|
|
670
|
+
* scope-first prefix is returned only when this ends in a `node_modules`
|
|
671
|
+
* segment (and the pattern has more path after the scope).
|
|
639
672
|
* @returns The literal directory prefix, without a trailing slash, or null when
|
|
640
673
|
* the pattern has no glob character, no `/`, or only a bare name before one.
|
|
641
674
|
*/
|
|
642
|
-
export declare function literalDirPrefix(pattern: string): string | null;
|
|
675
|
+
export declare function literalDirPrefix(pattern: string, root?: string): string | null;
|
|
643
676
|
/**
|
|
644
677
|
* Whether a backend error is the sandbox's own command deadline.
|
|
645
678
|
*
|
|
@@ -647,7 +680,7 @@ export declare function literalDirPrefix(pattern: string): string | null;
|
|
|
647
680
|
* `[deadline_exceeded] the operation timed out: … It can be modified by passing
|
|
648
681
|
* 'timeoutMs' when making the request. Use '0' to disable the timeout.` — which
|
|
649
682
|
* names an option the executor cannot set and says nothing about the search
|
|
650
|
-
* that died. Handing that sentence to the model is what
|
|
683
|
+
* that died. Handing that sentence to the model is what a run
|
|
651
684
|
* read after `find_files` spent 10 s walking `node_modules`; the tool answers
|
|
652
685
|
* the deadline in its own words instead (see {@link searchTimeoutError}).
|
|
653
686
|
*
|
|
@@ -687,8 +720,8 @@ export declare function pathArgError(path: unknown, tool: string): string | null
|
|
|
687
720
|
*
|
|
688
721
|
* The path message is the wrong reading of it: "requires a non-empty path
|
|
689
722
|
* argument" says the call was understood and the path was left out, so the model
|
|
690
|
-
* retries the call it just made instead of sending the whole call again (
|
|
691
|
-
*
|
|
723
|
+
* retries the call it just made instead of sending the whole call again (`edit_file {}` loses
|
|
724
|
+
* one round). An empty object and a
|
|
692
725
|
* missing `path` are different failures and deserve different sentences.
|
|
693
726
|
*
|
|
694
727
|
* @param input - The raw tool input.
|
|
@@ -783,9 +816,8 @@ export declare const MAX_BATCH_READ_FILES = 25;
|
|
|
783
816
|
* MIDDLE — the cut lands between two `"content"` values, so whole files fell
|
|
784
817
|
* out and the note in their place said only "N characters cut here" — while
|
|
785
818
|
* this tool's own note had promised each of those files that it "fits in one
|
|
786
|
-
* call, so it is not paged":
|
|
787
|
-
*
|
|
788
|
-
* further calls (52,908 chars).
|
|
819
|
+
* call, so it is not paged": one batched read of ten e2e specs (121,467 chars) lost the
|
|
820
|
+
* middle six and bought them again in six further calls (52,908 chars).
|
|
789
821
|
*
|
|
790
822
|
* So the CALL gets the ceiling the request gives it. Whole files are returned
|
|
791
823
|
* up to it; the paths that did not fit are named in `note` and listed in
|
|
@@ -820,7 +852,7 @@ export declare const MAX_READ_RETURN_CHARS: number;
|
|
|
820
852
|
/**
|
|
821
853
|
* A file this small is returned whole on a second window of it (paging it costs a call each).
|
|
822
854
|
* The same bound as {@link MAX_READ_RETURN_CHARS}: the windowed-read note promises that a
|
|
823
|
-
* file under it "fits in ONE call", and a smaller bound here
|
|
855
|
+
* file under it "fits in ONE call", and a smaller bound here breaks that promise.
|
|
824
856
|
*/
|
|
825
857
|
export declare const SMALL_FILE_WHOLE_CHARS: number;
|
|
826
858
|
/**
|
|
@@ -831,11 +863,11 @@ export declare const SMALL_FILE_WHOLE_CHARS: number;
|
|
|
831
863
|
* An entry document is boilerplate the reader rarely needs in full: meta tags,
|
|
832
864
|
* stylesheet and preload links, a `<style>` block, and a handful of `<script>`
|
|
833
865
|
* tags. The parts an executor acts on — the body's structure, where a script is
|
|
834
|
-
* loaded — are all visible in an outline of one line per tag.
|
|
835
|
-
* 63,735 chars
|
|
866
|
+
* loaded — are all visible in an outline of one line per tag. One such read paid
|
|
867
|
+
* 63,735 chars: ~60 KB of it was the IDE's own preview bridges,
|
|
836
868
|
* and the read pushed the transcript past its context cap, so the e2e specs the
|
|
837
869
|
* executor had just read were trimmed to summaries and it read the whole suite
|
|
838
|
-
* again — twice
|
|
870
|
+
* again — twice. The scaffold now keeps those
|
|
839
871
|
* bridges in separate files, but a template's own head is still ~5-12 KB, and a
|
|
840
872
|
* static-site or SSR entry can be far more.
|
|
841
873
|
*
|
|
@@ -923,9 +955,8 @@ export declare const RUNNING_FAILURE_LINE_RE = "^[[:space:]]*(\u2718|\u2717|\u00
|
|
|
923
955
|
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
924
956
|
*
|
|
925
957
|
* A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
|
|
926
|
-
* log are its least informative part —
|
|
927
|
-
*
|
|
928
|
-
* make anyway. A background run also carries each failure's own error as it fails
|
|
958
|
+
* log are its least informative part — the last 3 000 bytes of a 139-test walk are the skip
|
|
959
|
+
* list, and all 61 failures were above them, which costs the run the grep it has to make anyway. A background run also carries each failure's own error as it fails
|
|
929
960
|
* ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
|
|
930
961
|
* those blocks' headers and their `Error:` lines, so this slice shows the causes too.
|
|
931
962
|
*
|
|
@@ -964,11 +995,11 @@ export interface RunningLogRead {
|
|
|
964
995
|
* The shell command whose stdout is a bounded CONTIGUOUS read of a running task's own log: the
|
|
965
996
|
* log's byte size, then its last {@link RUNNING_LOG_READ_BYTES} bytes.
|
|
966
997
|
*
|
|
967
|
-
* {@link runningOutputSliceCommand} is the wrong read for a diagnosis
|
|
998
|
+
* {@link runningOutputSliceCommand} is the wrong read for a diagnosis: it
|
|
968
999
|
* stitches the first 40 grep-selected failure lines to the last 1 000 bytes with a blank line
|
|
969
1000
|
* between, so the tail is DISJOINT from the headers above it. Read as one log, the last numbered
|
|
970
|
-
* header absorbs whatever the tail starts with — a clipped skipped-test title
|
|
971
|
-
* a genuine failure
|
|
1001
|
+
* header absorbs whatever the tail starts with — a clipped skipped-test title becomes the cause
|
|
1002
|
+
* of a genuine failure. A contiguous window can only ever hold real
|
|
972
1003
|
* neighbouring lines of one log.
|
|
973
1004
|
*
|
|
974
1005
|
* The size rides along so a caller can say when the window is only the log's end
|
|
@@ -1031,13 +1062,13 @@ export interface RepeatingFailure {
|
|
|
1031
1062
|
*
|
|
1032
1063
|
* Either way the point is the same: a suite whose failures repeat one cause spends its whole
|
|
1033
1064
|
* runtime proving that one cause, and the executor watching it can be told so at the third
|
|
1034
|
-
* failure instead of at the last.
|
|
1065
|
+
* failure instead of at the last. Fifteen tests walked in 4.0 minutes where 15.6 s
|
|
1035
1066
|
* each was the test timeout and one missing `navigate('/')` was the cause of all of them.
|
|
1036
1067
|
*
|
|
1037
1068
|
* The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
|
|
1038
|
-
* prints each failure twice (a second reporter, a retry)
|
|
1039
|
-
* tests
|
|
1040
|
-
* with one cause in hand
|
|
1069
|
+
* prints each failure twice (a second reporter, a retry) has told a run that four of its
|
|
1070
|
+
* tests failed the same way when two had, so the executor stopped a 139-test walk at test 21
|
|
1071
|
+
* with one cause in hand.
|
|
1041
1072
|
*
|
|
1042
1073
|
* @param log - The log so far.
|
|
1043
1074
|
* @returns The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN}
|
|
@@ -1063,15 +1094,21 @@ export declare function repeatingFailure(log: string): RepeatingFailure | null;
|
|
|
1063
1094
|
* A body for a test the request EXCLUDES is not that evidence: its failure does not count for
|
|
1064
1095
|
* this request ({@link matchingExcludedTitles}), so a wait that handed it over as the diagnostic
|
|
1065
1096
|
* spent a model round trip and 8 k of partial log on a test the same result says to ignore —
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1097
|
+
* which a run has done twice while the platform's proof was still walking.
|
|
1098
|
+
*
|
|
1099
|
+
* With `finalOnly`, only a failure the run has FINISHED with counts; a pending retry waits.
|
|
1068
1100
|
*
|
|
1069
1101
|
* @param log - A log, or the slice of one a wait reads.
|
|
1070
1102
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
|
1103
|
+
* @param options - `finalOnly` reads only a failure the run has finished with; `noRetries` says
|
|
1104
|
+
* the command proves the run cannot retry (`--retries=0`), so its first failure is final.
|
|
1071
1105
|
* @returns True when a `✘` row's own test, outside the request's exclusions, has a failure body
|
|
1072
|
-
* under it.
|
|
1106
|
+
* under it — and, with `finalOnly`, that failure is final in the log read so far.
|
|
1073
1107
|
*/
|
|
1074
|
-
export declare function logShowsInlineFailureBodies(log: string, excluded?: readonly string[]
|
|
1108
|
+
export declare function logShowsInlineFailureBodies(log: string, excluded?: readonly string[], options?: {
|
|
1109
|
+
finalOnly?: boolean;
|
|
1110
|
+
noRetries?: boolean;
|
|
1111
|
+
}): boolean;
|
|
1075
1112
|
/** How many of a repeating cause's failures the note lists before it says "and N more". */
|
|
1076
1113
|
export declare const REPEATING_FAILURE_LISTED = 5;
|
|
1077
1114
|
/**
|
|
@@ -1082,8 +1119,8 @@ export declare const REPEATING_FAILURE_LISTED = 5;
|
|
|
1082
1119
|
*/
|
|
1083
1120
|
export declare const TOOL_RESULT_CHARS = 8000;
|
|
1084
1121
|
/**
|
|
1085
|
-
* The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}.
|
|
1086
|
-
*
|
|
1122
|
+
* The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}. A measured
|
|
1123
|
+
* recap was `12 failures, 8 causes`: one cause line is
|
|
1087
1124
|
* ~200 chars and ~80 more with its two body lines, so 8 causes is ~2.3 k against the 8 k cap —
|
|
1088
1125
|
* the old 5-cause cap withheld 3 causes that the same result had room for.
|
|
1089
1126
|
*
|
|
@@ -1137,7 +1174,7 @@ export interface UniversalCause {
|
|
|
1137
1174
|
* THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
|
|
1138
1175
|
* reading was only offered at a completed run; once it STOPS the walk, every test waited for is
|
|
1139
1176
|
* a test whose result is thrown away, and a blank page costs its per-test budget each time
|
|
1140
|
-
* (
|
|
1177
|
+
* (18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
|
|
1141
1178
|
* `pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
|
|
1142
1179
|
* needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
|
|
1143
1180
|
* no failure showing a locator RESOLVING ({@link ELEMENT_FOUND} — a blank page cannot resolve
|
|
@@ -1158,8 +1195,8 @@ export declare const CONNECTION_CAUSE_TESTS = 3;
|
|
|
1158
1195
|
* whole suite (or its selection cannot be read).
|
|
1159
1196
|
*
|
|
1160
1197
|
* {@link universalCause}'s file threshold is written for a whole-suite run, which a narrowed run
|
|
1161
|
-
* can never reach.
|
|
1162
|
-
*
|
|
1198
|
+
* can never reach. A run that re-ran ONE spec file against a hand-started `vite preview` the
|
|
1199
|
+
* browser could not reach had every test in it fail `page.goto: Timeout` —
|
|
1163
1200
|
* but one file is below {@link UNIVERSAL_CAUSE_FILES}, so the wait sat out its entire 280 s
|
|
1164
1201
|
* budget on a run that had already measured all it could.
|
|
1165
1202
|
*
|
|
@@ -1182,7 +1219,7 @@ export declare function specFilesSelected(command: string): number | null;
|
|
|
1182
1219
|
* threshold, and nothing has passed since its first failure. The threshold is
|
|
1183
1220
|
* {@link UNIVERSAL_CAUSE_FILES} for a whole-suite run but the number of files the command
|
|
1184
1221
|
* SELECTED for a narrowed one ({@link specFilesSelected}): a one-file run can never span three,
|
|
1185
|
-
* and
|
|
1222
|
+
* and a run can wait out its whole budget on exactly that shape. An app
|
|
1186
1223
|
* that is not up, a 5xx on every request, or a selector that is missing on every page all look
|
|
1187
1224
|
* like that, and every remaining test will fail the same way — so the run's remaining minutes
|
|
1188
1225
|
* measure nothing. A CONNECTION-class cause ({@link CONNECTION_CAUSE}) needs no file span at all:
|
|
@@ -1191,8 +1228,8 @@ export declare function specFilesSelected(command: string): number | null;
|
|
|
1191
1228
|
*
|
|
1192
1229
|
* Anything less is NOT this: a cause that stops at one file of a multi-file run, or that leaves
|
|
1193
1230
|
* other tests passing, or that shares the run with failures of another shape, leaves the run with
|
|
1194
|
-
* something to say, and the run is the only thing that will say it.
|
|
1195
|
-
*
|
|
1231
|
+
* something to say, and the run is the only thing that will say it. Stopping runs at their first
|
|
1232
|
+
* repeating cause has burned eight local runs over 14.5 minutes without finishing
|
|
1196
1233
|
* one; three of its deploys each fixed a single cause that a finished run would have listed
|
|
1197
1234
|
* alongside the others.
|
|
1198
1235
|
*
|
|
@@ -1215,14 +1252,14 @@ export declare function universalCause(log: string, found: RepeatingFailure | nu
|
|
|
1215
1252
|
* spec-file threshold — {@link UNIVERSAL_CAUSE_FILES}, or the files a narrowed run's own
|
|
1216
1253
|
* command selects, or {@link CONNECTION_CAUSE_TESTS} tests of an app that never answered — with
|
|
1217
1254
|
* nothing passing since the first. That is the
|
|
1218
|
-
* dont-stop-runs rule.
|
|
1219
|
-
*
|
|
1255
|
+
* dont-stop-runs rule. Stopping runs at their first repeating cause has burned eight local
|
|
1256
|
+
* runs in 14.5 minutes without finishing one, and a run that still has other causes
|
|
1220
1257
|
* left to name is the only thing that will name them.
|
|
1221
1258
|
* - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
|
|
1222
1259
|
* a suite whose config sets a timeout spends that timeout on every failure that waits for
|
|
1223
1260
|
* anything, so three unrelated tests timing out together look exactly like one broken run
|
|
1224
|
-
* (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}).
|
|
1225
|
-
*
|
|
1261
|
+
* (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). A run
|
|
1262
|
+
* told `status: 'repeating_failure'` twice on that
|
|
1226
1263
|
* shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
|
|
1227
1264
|
* — read the early return as "stop waiting, edit now", and voided a run that was still walking;
|
|
1228
1265
|
* the second cost a 224 s wait that ended with no result at all.
|
|
@@ -1247,12 +1284,11 @@ export declare function earlyReturnCause(found: RepeatingFailure | null, univers
|
|
|
1247
1284
|
* extracted by molecule-dev's `excludedTestTitles`) while a run's heading carries more of it
|
|
1248
1285
|
* (`comments.spec.ts › Comments › should require login to post comment`), so containment is the
|
|
1249
1286
|
* comparison. Whitespace is normalised and case is ignored: a brief may wrap the title across a
|
|
1250
|
-
* line
|
|
1287
|
+
* line, and the reporter's own casing never has to match the brief's.
|
|
1251
1288
|
*
|
|
1252
1289
|
* Two readers used to answer this separately — the causes note (which therefore asked the
|
|
1253
1290
|
* executor to fix a test the same result said not to) and molecule-dev's exclusion notes — and
|
|
1254
|
-
*
|
|
1255
|
-
* test).
|
|
1291
|
+
* a run paid for the gap (a turn and an edit spent on the excluded test).
|
|
1256
1292
|
*
|
|
1257
1293
|
* @param heading - The text to match against: a test's title, its whole `file › describe › title`
|
|
1258
1294
|
* path, or (for a note that only has the run's output) the run's text.
|
|
@@ -1278,21 +1314,21 @@ export interface FailureCause {
|
|
|
1278
1314
|
* The suite's own per-test budget in ms, when the cause is the test-budget kill
|
|
1279
1315
|
* (`Test timeout of 15000ms exceeded`). The signature normalizes the number away
|
|
1280
1316
|
* ({@link normalizedSignature}) so equal causes group, and that number is the one fact that
|
|
1281
|
-
* predicts a test spending its budget on a repeated action
|
|
1317
|
+
* predicts a test spending its budget on a repeated action.
|
|
1282
1318
|
*/
|
|
1283
1319
|
budgetMs?: number;
|
|
1284
1320
|
/**
|
|
1285
1321
|
* The layer the failure came from ({@link classifyRunFailure}). A `connection` cause never
|
|
1286
1322
|
* reached the app, so it is NOT a cause the app's code can fix: the note says to re-run it
|
|
1287
1323
|
* once and read the app's log on a repeat, instead of sending a fix at a handler
|
|
1288
|
-
* (
|
|
1324
|
+
* (a CORS middleware was rewritten for a 20 s connection timeout).
|
|
1289
1325
|
*/
|
|
1290
1326
|
kind: RunFailureKind;
|
|
1291
1327
|
/**
|
|
1292
1328
|
* What the page itself showed at the moment of the failure, when the caller read it from
|
|
1293
1329
|
* Playwright's `error-context.md` (the alert / error list). The failure's own error line names
|
|
1294
|
-
* the symptom — `waitForURL` timed out — and never what the page was saying:
|
|
1295
|
-
*
|
|
1330
|
+
* the symptom — `waitForURL` timed out — and never what the page was saying: eight
|
|
1331
|
+
* `waitForURL` timeouts were grouped under one line while the page snapshots named
|
|
1296
1332
|
* the cause (`email can't be blank`, `username can't be blank` — the settings form submitted
|
|
1297
1333
|
* before its values had loaded). Attached to the cause so the fix needs no `cat` of the file.
|
|
1298
1334
|
*/
|
|
@@ -1324,7 +1360,7 @@ export interface CausePageErrors {
|
|
|
1324
1360
|
*
|
|
1325
1361
|
* The point is that ONE run yields EVERY cause. A Playwright report prints its failures one
|
|
1326
1362
|
* after another with no statement of which of them share a cause, so a run of 19 failures is 19
|
|
1327
|
-
* bodies to read;
|
|
1363
|
+
* bodies to read; three deploys (6.7 minutes) each fixed a
|
|
1328
1364
|
* single cause, two of them found by runs stopped before they walked the rest.
|
|
1329
1365
|
*
|
|
1330
1366
|
* The signature is the cause itself where the log carries the failures' bodies (a finished run, a
|
|
@@ -1336,8 +1372,8 @@ export interface CausePageErrors {
|
|
|
1336
1372
|
* A test the request EXCLUDES by name is not a cause to fix: its failure does not count for
|
|
1337
1373
|
* this request ({@link matchingExcludedTitles}), so it is dropped here rather than handed to the
|
|
1338
1374
|
* executor as work — a result that names it beside "it does not count" is the contradiction
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1375
|
+
* a run filed (the model spent a turn and an edit on the excluded test, on a page that was
|
|
1376
|
+
* passing).
|
|
1341
1377
|
*
|
|
1342
1378
|
* @param log - The log of a finished or running run.
|
|
1343
1379
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
|
@@ -1358,7 +1394,7 @@ export declare function requiredLandmark(selector: string): RequiredLandmark | n
|
|
|
1358
1394
|
* The sentence for a run whose navigational elements are all missing from pages that otherwise
|
|
1359
1395
|
* painted — the missing shared header the executor must check BEFORE rewriting auth.
|
|
1360
1396
|
*
|
|
1361
|
-
*
|
|
1397
|
+
* A run had four failures on `/` — `a[href="/profile/…"]`,
|
|
1362
1398
|
* `a.navbar-brand`, `nav a[href="/"]`, `nav .user-pic` — while every `error-context.md` snapshot
|
|
1363
1399
|
* showed the feed, sidebar and footer with no navigation at all. `Home.tsx` imported and
|
|
1364
1400
|
* re-exported `Navbar` but never rendered it, and `App.tsx` mounted `<Home />` in a bare route.
|
|
@@ -1418,7 +1454,7 @@ export declare function failureCausesNote(log: string, excluded?: readonly strin
|
|
|
1418
1454
|
* Spec files whose selector-contract rows an edit broke since the last whole walk, when the
|
|
1419
1455
|
* caller knows them: the narrowed re-run names them beside the failing tests. A green re-run of
|
|
1420
1456
|
* the failure alone would certify a tree it never measured, and the specs selecting the
|
|
1421
|
-
* element the edit removed were never walked (
|
|
1457
|
+
* element the edit removed were never walked (a real tree shipped that).
|
|
1422
1458
|
*/
|
|
1423
1459
|
regressedSpecs?: readonly string[]): string | null;
|
|
1424
1460
|
/**
|
|
@@ -1440,7 +1476,7 @@ regressedSpecs?: readonly string[]): string | null;
|
|
|
1440
1476
|
* @param run - The run in flight: the command it was started with, as it was run, the directory
|
|
1441
1477
|
* that command ran in, the config it loaded when the caller knows it, and the settings the
|
|
1442
1478
|
* request required and the run left out (`env`) — the re-run carries them, so it never
|
|
1443
|
-
* reproduces the configuration the run was measured under by mistake
|
|
1479
|
+
* reproduces the configuration the run was measured under by mistake.
|
|
1444
1480
|
* @param locations - The failing `file:line`s to re-run, as the run's log printed them.
|
|
1445
1481
|
* @param excluded - The test titles the request carves out: the re-run inverts them out
|
|
1446
1482
|
* (`--grep-invert`) so it cannot report them again.
|
|
@@ -1462,18 +1498,18 @@ export declare function rerunOfRunningSuite(run: {
|
|
|
1462
1498
|
* The note names the cause early so the executor can START FIXING while the run keeps walking,
|
|
1463
1499
|
* and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
|
|
1464
1500
|
* predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
|
|
1465
|
-
* on is a finding this note never asks to stop for.
|
|
1501
|
+
* on is a finding this note never asks to stop for. One run spent 4.0 minutes
|
|
1466
1502
|
* walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
|
|
1467
|
-
* exactly that;
|
|
1503
|
+
* exactly that; another stopped eight runs at their first repeating cause in 14.5
|
|
1468
1504
|
* minutes and never finished one, so the note must not say to stop when the run still has other
|
|
1469
|
-
* causes to name.
|
|
1505
|
+
* causes to name. The third shape: a shared duration is
|
|
1470
1506
|
* not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
|
|
1471
1507
|
* for — never as a verdict with a stop command in it.
|
|
1472
1508
|
*
|
|
1473
1509
|
* The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
|
|
1474
1510
|
* re-run of one of its tests is a second run against the same app and database, and the platform
|
|
1475
1511
|
* refuses that ({@link suiteRunInTheWay}, the same rule its overlap hold answers with):
|
|
1476
|
-
*
|
|
1512
|
+
* an executor read *"Start on them now, while the run keeps walking.
|
|
1477
1513
|
* `null-fields.spec.ts:53` alone reproduces the first one."* and sent exactly that re-run 23 s
|
|
1478
1514
|
* later, where the hold answered *"A playwright suite is already running against this app and
|
|
1479
1515
|
* database … Wait for it."* The note therefore hands over the read-and-fix to do NOW and the
|