@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.
Files changed (45) hide show
  1. package/README.md +243 -189
  2. package/dist/browser-probe.d.ts +4 -4
  3. package/dist/browser-probe.d.ts.map +1 -1
  4. package/dist/browser-probe.js +5 -5
  5. package/dist/command-position.d.ts +15 -16
  6. package/dist/command-position.d.ts.map +1 -1
  7. package/dist/command-position.js +21 -23
  8. package/dist/cut-run.d.ts +9 -9
  9. package/dist/cut-run.js +6 -6
  10. package/dist/failure-kind.d.ts +6 -6
  11. package/dist/failure-kind.js +6 -6
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +2 -2
  14. package/dist/inline-script.d.ts +5 -5
  15. package/dist/inline-script.js +6 -6
  16. package/dist/line-diff.d.ts +2 -3
  17. package/dist/line-diff.d.ts.map +1 -1
  18. package/dist/line-diff.js +2 -3
  19. package/dist/no-test-run.d.ts +5 -5
  20. package/dist/no-test-run.d.ts.map +1 -1
  21. package/dist/no-test-run.js +9 -8
  22. package/dist/output-filter.d.ts +2 -2
  23. package/dist/output-filter.js +2 -2
  24. package/dist/pipeline-exit-note.d.ts +1 -1
  25. package/dist/pipeline-exit-note.js +1 -1
  26. package/dist/playwright-report.d.ts +1 -1
  27. package/dist/playwright-report.js +1 -1
  28. package/dist/server-command.d.ts +2 -2
  29. package/dist/server-command.js +2 -2
  30. package/dist/sleep-peek.d.ts +1 -1
  31. package/dist/sleep-peek.js +1 -1
  32. package/dist/suite-overlap.d.ts +24 -25
  33. package/dist/suite-overlap.d.ts.map +1 -1
  34. package/dist/suite-overlap.js +41 -43
  35. package/dist/tools.d.ts +26 -28
  36. package/dist/tools.d.ts.map +1 -1
  37. package/dist/tools.js +114 -72
  38. package/dist/trace-artifacts.d.ts +73 -65
  39. package/dist/trace-artifacts.d.ts.map +1 -1
  40. package/dist/trace-artifacts.js +76 -77
  41. package/dist/types.d.ts +5 -5
  42. package/dist/utilities.d.ts +128 -92
  43. package/dist/utilities.d.ts.map +1 -1
  44. package/dist/utilities.js +298 -143
  45. package/package.json +2 -2
package/dist/utilities.js CHANGED
@@ -455,7 +455,7 @@ export function outputIsWithheldUntilExit(command) {
455
455
  * The file a command sends its own output to, when it redirects it (`> out.txt`,
456
456
  * `>> log`, `&> run.log`, `| tee [-a] file`), or null. A background command that does
457
457
  * this leaves its task log empty, so the log is the wrong place to look and an empty
458
- * log proves nothing (X1 flash-m29).
458
+ * log proves nothing.
459
459
  *
460
460
  * Which redirect counts is decided by the command's own shell structure, from the same
461
461
  * parse every other command reader uses: a redirect written in a BACKGROUNDED statement
@@ -463,7 +463,7 @@ export function outputIsWithheldUntilExit(command) {
463
463
  * the command's. The platform's own preview launch is exactly that — the preview server is
464
464
  * started with `>/tmp/mol-preview-4173.log 2>&1 &` inside the build/serve prelude, and the
465
465
  * suite's foreground command follows it — so a reader that took the last `>` in the whole
466
- * text told a run its output went to the PREVIEW's log (X1 flash-m136 +18:31).
466
+ * text tells a run its output went to the PREVIEW's log.
467
467
  *
468
468
  * The target comes back without its shell quotes: `> '/tmp/x.log'` is `/tmp/x.log`, and
469
469
  * the callers resolve a relative one against the command's cwd.
@@ -531,8 +531,8 @@ const PRINTING_REPORTERS = new Set(['list', 'line', 'dot', 'basic', 'spec']);
531
531
  * Every background command is given this (see {@link streamingBackgroundCommand}), so a suite the
532
532
  * executor starts through its own wrapper is covered too — the wrapper passes the environment on
533
533
  * to the Playwright process it spawns. Without it the log's only failure lines are `✘` rows:
534
- * X1 xr-molecule-flash-m99 (+19:27 → +21:00, 2026-10-16) was told to `grep -n -A 20 '<test name>'`
535
- * the log, got nothing back through four greps, inferred causes from the spec prose instead, and
534
+ * A run told to `grep -n -A 20 '<test name>'` the log got nothing back through four greps,
535
+ * inferred causes from the spec prose instead, and
536
536
  * edited `app/src` while the run was still walking — which voids the run.
537
537
  */
538
538
  export const PLAYWRIGHT_FAILURES_INLINE_ENV = 'PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE';
@@ -560,7 +560,7 @@ const STATUS_EXIT_ARITHMETIC = /^[A-Za-z0-9_|&+\-*/%^<>=!~() \t]*$/;
560
560
  * `npx playwright test … > /tmp/pw.log 2>&1; echo EXIT $?` — and reading the split's last part as
561
561
  * the run withheld the reporter, the inline-failure switch and `--retries=0` from every one of
562
562
  * them: the log then carried `✘` rows and nothing else while the wait note promised each failure's
563
- * error under them (xr-molecule-flash-m173 +17:07, +39:14). Whitespace and a group's closing
563
+ * error under them. Whitespace and a group's closing
564
564
  * punctuation are not part of the step; {@link streamingBackgroundCommand} strips those before
565
565
  * asking, and strips them here too for the split it is given.
566
566
  *
@@ -570,7 +570,7 @@ const STATUS_EXIT_ARITHMETIC = /^[A-Za-z0-9_|&+\-*/%^<>=!~() \t]*$/;
570
570
  * browser half as a status step is what lets the run keep its reporter, its machine report and the
571
571
  * inline-failure switch — without it, every platform proof ran its browser suite on the request's
572
572
  * own `line` reporter, which writes no ✓/✘ mark and no per-failure error into the log the wait
573
- * reads (xr-molecule-flash-x03 +27:44).
573
+ * reads.
574
574
  *
575
575
  * Only the two shapes whose whole value is the shell's own status are read this way; anything that
576
576
  * could run — a command substitution, a backticked word, a suffix after the status — stays a step.
@@ -596,7 +596,7 @@ function isStatusOnlyStep(step) {
596
596
  * Rewrite a command about to run in the background so its log grows while it
597
597
  * runs. Two shapes left the log empty until exit, and an empty log is what
598
598
  * `wait_for_task` reads as "stuck", so working suites were reported stuck and
599
- * killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
599
+ * killed (one run lost 20 of its 60 minutes this way):
600
600
  *
601
601
  * - a trailing `| tail -N`, which holds everything back until the command ends.
602
602
  * It is dropped: `wait_for_task` already returns the end of the log.
@@ -604,7 +604,7 @@ function isStatusOnlyStep(step) {
604
604
  * failing test's own error and leaves the pipeline's exit status — the filter's — as the run's.
605
605
  * Every such stage is dropped from EVERY suite run in the command, not only the last
606
606
  * ({@link stripTrailingSuiteFilters}); `wait_for_task` already returns the end of the full log,
607
- * and a run whose tally shows a failure must never report 0 (xr-molecule-flash-m101 +25:54).
607
+ * and a run whose tally shows a failure must never report 0.
608
608
  * - `playwright test` with no `--reporter`, which runs the config's reporter —
609
609
  * often `html`, which prints nothing per test. `--reporter=list` is added
610
610
  * when the playwright run is the command's last step, piped or not: it writes each result as
@@ -614,11 +614,11 @@ function isStatusOnlyStep(step) {
614
614
  *
615
615
  * The run ends up with exactly ONE reporter that prints each failure — `list`. A run that names
616
616
  * any other printing reporter has it REPLACED, never joined: two of them write every test result
617
- * into the log twice, which on X1 flash-m83 (2026-10-16) made a `--reporter=list,line` run (its
617
+ * into the log twice, which made a `--reporter=list,line` run (its
618
618
  * `line` added by this function one run earlier) report two distinct failures as "4 of this run's
619
619
  * tests have already failed the same way", and the executor stopped a 139-test walk at test 21
620
620
  * with a single cause in hand. `line` alone is no use either: it writes `[n/N] <title>` with no
621
- * ✓/✘ mark and no duration, so on X1 flash-m62 an 8.3-minute walk of one suite could never trip
621
+ * ✓/✘ mark and no duration, so an 8.3-minute walk of one suite could never trip
622
622
  * the fail-fast note. A reporter set that writes a machine format to a file (`json`, `html`,
623
623
  * `junit`) is the
624
624
  * executor's own and is left exactly as it is — so is any reporter this build does not know.
@@ -627,8 +627,8 @@ function isStatusOnlyStep(step) {
627
627
  * {@link PLAYWRIGHT_FAILURES_INLINE_ENV} (inert except for Playwright's `list` reporter), so each
628
628
  * failure's own error is in the log as that test fails rather than only at the end: a suite that is
629
629
  * still walking is exactly when the executor reads the log, and without it a running log carries
630
- * `✘` rows and nothing else (xr-molecule-flash-m99 spent ~90 s on greps of one, then fixed what it
631
- * guessed; m191's wrapper-launched run lost 6.5 minutes to the same shape).
630
+ * `✘` rows and nothing else (~90 s went to greps of one, then to fixing what was
631
+ * guessed; a wrapper-launched run lost 6.5 minutes to the same shape).
632
632
  *
633
633
  * Only the step that runs the suite is touched, and only its reporter, and only when that step
634
634
  * runs something: `npx playwright test --list` takes two seconds and writes nothing per test, so
@@ -639,9 +639,8 @@ function isStatusOnlyStep(step) {
639
639
  *
640
640
  * A command that starts the suite MORE THAN ONCE is not launched at all: every run it carries is
641
641
  * a whole walk of the same suite against the same app, and only the last step is rewritten, so the
642
- * earlier walks run unmeasured for nothing. X1 xr-molecule-flash-m157 (+14:02) chained two whole
643
- * walks of one 138-test suite with `;` — 4.5 minutes each — and the second measured nothing the
644
- * first had not. The platform refuses a second CONCURRENT run against one app and database by the
642
+ * earlier walks run unmeasured for nothing. Two whole walks of one 138-test suite chained with
643
+ * `;` — 4.5 minutes each — measured nothing the first had not. The platform refuses a second CONCURRENT run against one app and database by the
645
644
  * ONE rule ({@link suiteRunInTheWay}) — for every pair, whatever uid a run mints — and this is the
646
645
  * same hazard inside one command ({@link suiteRunStatements}). `error` then carries the refusal,
647
646
  * naming each suite step and the ONE command to send instead (the last suite step, rewritten as
@@ -652,8 +651,8 @@ function isStatusOnlyStep(step) {
652
651
  * excludes by name, and whether this command IS the platform's graded proof
653
652
  * ({@link DiagnosisRunContext}). A `--grep-invert` of exactly those exclusions is the
654
653
  * request's own scope, not a diagnosis, so it keeps the suite's retries on a deployed proof
655
- * (xr-molecule-flash-m170 +33:16) — and a proof the platform itself composed and launched
656
- * keeps them whatever its scope, targeted set included (xr-molecule-flash-l48 +26:35).
654
+ * — and a proof the platform itself composed and launched
655
+ * keeps them whatever its scope, targeted set included.
657
656
  * @param reportPath - Where this run should write Playwright's standard `json` report, when the
658
657
  * caller keeps one ({@link ./playwright-report.js}). The reporter is added to the run's own
659
658
  * reporter set, never in place of one, and its output goes to that file through
@@ -755,8 +754,8 @@ export function streamingBackgroundCommand(command, context, reportPath) {
755
754
  * (`| tee run.log`), and a statement can end on a group closer (`( … )`, `{ …; }`) or a
756
755
  * redirection (`… 2>&1`). Appending after the `)` of a subshell was a shell syntax error that
757
756
  * killed the run at once (`sh: Syntax error: word unexpected`) and pushed the model off the
758
- * platform's runner shape (xr-molecule-flash-m129 +34:29); appending after a pipe hands the
759
- * word to the filter (m102's dangling `--trace` flag, in the same shape).
757
+ * platform's runner shape; appending after a pipe hands the word to the filter (a dangling
758
+ * `--trace` flag, in the same shape).
760
759
  *
761
760
  * The suite's own segment is found wherever it sits ({@link lastSuiteRun}), because a trailing
762
761
  * status step is not another command's output: in `… > /tmp/pw.log 2>&1; echo EXIT $?` the run
@@ -928,7 +927,7 @@ export function streamingBackgroundCommand(command, context, reportPath) {
928
927
  * reporter.
929
928
  *
930
929
  * A compound command's earlier walk is not a preview: it writes the same log the executor reads,
931
- * and on X1 xr-molecule-flash-l21 (+21:27) the first 3.6-minute walk kept its own
930
+ * and there the first 3.6-minute walk keeps its own
932
931
  * `--reporter=line`, which prints `[n/N] <title>` rows with no ✓/✘ mark and no per-failure error.
933
932
  * What came back was `72 passed` and two failure NAMES; the error bodies existed only in the walk's
934
933
  * discarded log, and diagnosing them needed another run.
@@ -1137,7 +1136,7 @@ export function isSuiteRunCommand(command) {
1137
1136
  * Refuse only a command made entirely of identical suite invocations.
1138
1137
  *
1139
1138
  * Mixed shell commands execute as written. Rebuilding their prefix or suffix can lose writes,
1140
- * break heredoc delimiters or turn an && guard into unconditional work (m277). Different args
1139
+ * break heredoc delimiters or turn an && guard into unconditional work. Different args
1141
1140
  * or environments may select different suites or targets; they are not known duplicates either.
1142
1141
  * This is an optimization, so uncertainty always allows the original command.
1143
1142
  *
@@ -1197,8 +1196,8 @@ function suiteRunnerArguments(command) {
1197
1196
  * flags, and the flags that could not be placed.
1198
1197
  *
1199
1198
  * The grader walks the suite under its OWN budget, so a run whose runner config sets a different
1200
- * timeout or worker count measures a run the grader never sees (xr-molecule-flash-m169: a 60 s
1201
- * wrapper config produced a green local proof the official 15 s grader then failed). Playwright
1199
+ * timeout or worker count measures a run the grader never sees (a 60 s wrapper config produced
1200
+ * a green local proof the official 15 s grader then failed). Playwright
1202
1201
  * lets a command-line flag win over every config file, so the suite's values are put on the
1203
1202
  * runner's own arguments — written where {@link statementArgumentEnd} places them, never after a
1204
1203
  * pipe, redirect or group closer. Only the command's LAST suite step is touched: that is the run
@@ -1312,7 +1311,7 @@ export function suiteRunConfigCommand(command, config) {
1312
1311
  * Used where the platform moves a run to the background: the executor wrote the bound to fit this
1313
1312
  * tool's foreground ceiling, and in the background that ceiling no longer applies — the task is
1314
1313
  * bounded by `wait_for_task` and the turn's deadline — so the prefix only kills the walk
1315
- * ({@link withoutTimeoutWrapper}; X1 xr-molecule-flash-m143 +24:45).
1314
+ * ({@link withoutTimeoutWrapper}).
1316
1315
  *
1317
1316
  * @param command - The shell command.
1318
1317
  * @returns The rewritten command, and the largest lifted bound (null when nothing was lifted).
@@ -1325,7 +1324,7 @@ export function withoutSuiteTimeout(command) {
1325
1324
  * that runs something.
1326
1325
  *
1327
1326
  * `npx playwright test --list` matched every `\bplaywright\s+test\b` test in the platform and was
1328
- * then treated as a run: held (flash-m79's config check), backgrounded for a run that outlasts
1327
+ * then treated as a run: held (a config check), backgrounded for a run that outlasts
1329
1328
  * the tool's ceiling, and rewritten with `--reporter`. A list takes two seconds, prints its lines
1330
1329
  * and exits, so it is none of those; {@link commandRunsNoTests} is what decides.
1331
1330
  *
@@ -1378,8 +1377,7 @@ const OUTPUT_REDIRECT = /^(?:\d*|&)>>?(?!&)/;
1378
1377
  * searching the command for the re-joined text: the words are joined back with single spaces for
1379
1378
  * matching, so `command.indexOf(segment.text)` comes up empty on irregular spacing, on a grouping
1380
1379
  * brace, or on anything a heredoc body shifts — and the caller then cut at a guess or refused to
1381
- * cut at all (xr-molecule-flash-m105 +08:12, where a heredoc body turned a finished run into
1382
- * "piped" for the rest of the session).
1380
+ * cut at all (a heredoc body turned a finished run into "piped" for the rest of the session).
1383
1381
  *
1384
1382
  * A statement inside a nested `sh -c '<command>'` string is listed with null offsets: its
1385
1383
  * positions are positions in THAT string, so nothing may be spliced at them — but its separator
@@ -1456,7 +1454,7 @@ function lastSuitePipeline(command) {
1456
1454
  * the command exits with is then the following statement's. That is the right answer to "whose
1457
1455
  * exit code is this", and the wrong one to "which stages filter this run's output": a
1458
1456
  * `… --reporter=list 2>&1 | tail -5; echo done` still pipes its log through a tail, and the tail
1459
- * still throws away every failure line the run printed (xr-molecule-flash-m121 +09:14, where the
1457
+ * still throws away every failure line the run printed (where the
1460
1458
  * platform's own hold handed that shape back and the first 95-second walk's log held only the
1461
1459
  * tally).
1462
1460
  *
@@ -1473,8 +1471,8 @@ function lastSuiteRun(command) {
1473
1471
  * spec, or a Hurl run followed by a browser walk — and every one of them writes the log the
1474
1472
  * executor reads. Reading only the last one left the earlier walk's failing test's own error
1475
1473
  * behind a `| tail -5` while the LAST invocation was rewritten normally
1476
- * (xr-molecule-flash-l21 +21:27: a 3.6-minute walk returned `72 passed` with no error body when
1477
- * the driver ran `… specs 2>&1 | tail -5; echo START; … auth.spec.ts`).
1474
+ * (a 3.6-minute walk returned `72 passed` with no error body when the driver ran
1475
+ * `… specs 2>&1 | tail -5; echo START; … auth.spec.ts`).
1478
1476
  *
1479
1477
  * A command that runs no test at all is not a set of runs ({@link commandRunsNoTests}); nor is a
1480
1478
  * statement that only lists tests, which the reporter pass asks about per statement
@@ -1566,13 +1564,13 @@ function suiteFilterCut(command, located, at) {
1566
1564
  *
1567
1565
  * A filter after a runner is not "the end of the log" the executor asked for: it hides the
1568
1566
  * failing test's own error (it usually matches none of `✘|passed|failed`), and the pipeline's exit
1569
- * status becomes the FILTER's, so `… 2>&1 | grep -E "✘|passed|failed|skipped" | tail -20` came back
1570
- * `exitCode: 0` for a run with a failure in it (xr-molecule-flash-m101 +25:54). The tool returns
1567
+ * status becomes the FILTER's, so `… 2>&1 | grep -E "✘|passed|failed|skipped" | tail -20` comes
1568
+ * back `exitCode: 0` for a run with a failure in it. The tool returns
1571
1569
  * the end of the full log anyway, so the filter only costs diagnosability.
1572
1570
  *
1573
1571
  * EVERY run is stripped, not only the command's last: a compound command's earlier walk writes
1574
1572
  * the same log, and its `| tail -5` threw away the error body of a 3.6-minute Playwright walk
1575
- * whose two failure names were all the executor got back (xr-molecule-flash-l21 +21:27). Only a
1573
+ * whose two failure names were all the executor got back. Only a
1576
1574
  * run the parse places is touched, and only its own pipeline — a later statement's filters belong
1577
1575
  * to it.
1578
1576
  *
@@ -1583,12 +1581,12 @@ function suiteFilterCut(command, located, at) {
1583
1581
  *
1584
1582
  * A statement after the run's own pipeline (`; echo done`, `&& echo ok`) does not protect the
1585
1583
  * filters before it: the filter is thrown away and the trailing statement is kept, so the log the
1586
- * run writes still holds every failure (xr-molecule-flash-m121 — a `| tail -5; echo done` kept its
1584
+ * run writes still holds every failure (a `| tail -5; echo done` keeps its
1587
1585
  * tail, and a 95-second walk's log held only the tally while the platform's own hold had handed
1588
1586
  * that command back).
1589
1587
  *
1590
1588
  * The command around the dropped stages is left as written: a group they sit in keeps its closer
1591
- * (`( … | tail -25)` → `( … )`, never `( …` — xr-molecule-flash-m418 +23:31), and a stage the cut
1589
+ * (`( … | tail -25)` → `( … )`, never `( …`), and a stage the cut
1592
1590
  * would leave a closer dangling inside is not dropped at all.
1593
1591
  *
1594
1592
  * @param command - The shell command.
@@ -1630,8 +1628,8 @@ export function stripTrailingSuiteFilters(command) {
1630
1628
  * code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
1631
1629
  * code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
1632
1630
  * any run whose statements it could not place in the text, which one heredoc body was enough to
1633
- * cause: on X1 xr-molecule-flash-m105 (+08:12) a config written in the same `exec_command` made
1634
- * the run come back with no exit code at all, and the note under it claimed a stage after the
1631
+ * cause: a config written in the same `exec_command` made the run come back with no exit code
1632
+ * at all, and the note under it claimed a stage after the
1635
1633
  * runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
1636
1634
  * "unplaced" is the rare, named case rather than a guess dressed as an answer.
1637
1635
  *
@@ -1673,10 +1671,9 @@ export function suiteRunPipedAfterRunner(command) {
1673
1671
  * shares with everything else stays responsive to what the executor does next.
1674
1672
  *
1675
1673
  * A whole-suite Playwright run saturates the one container the app's Vite
1676
- * server, its API process and every other command also live in: on X1 flash-m75
1677
- * the model backgrounded a 139-test run at +36:23 and the two-word shell
1678
- * command it sent next — tick its plan file — returned nothing for 2 min 40 s,
1679
- * measured on the chat payload and on the live-stream follower independently.
1674
+ * server, its API process and every other command also live in: a backgrounded 139-test run
1675
+ * has left the two-word shell command sent next — tick its plan file — returning nothing for
1676
+ * 2 min 40 s, measured on the chat payload and on the live-stream follower independently.
1680
1677
  * The backgrounding note is right that the executor should go do other work;
1681
1678
  * what it cannot promise is that the sandbox will let it.
1682
1679
  *
@@ -1824,8 +1821,8 @@ const BLOCKED_MOL_REDIRECT = /(?:<\s*\/etc\/mol)/i;
1824
1821
  * The first check reads the commands the shell actually RUNS — the statements, a `sh -c`
1825
1822
  * string's body, and every command substitution ({@link executedCommands}) — so a `|` inside a
1826
1823
  * quoted grep alternation is data, not a pipeline: `grep -E "USER CALLS|ENV|BUTTON|passed|failed"
1827
- * app.log` prints log lines, and the `ENV` in its pattern started nothing (X0
1828
- * xr-molecule-flash-m331 blocked exactly that call as an environment dump, twice). What the
1824
+ * app.log` prints log lines, and the `ENV` in its pattern starts nothing — a text
1825
+ * match would block exactly that call as an environment dump, twice over. What the
1829
1826
  * shell DOES run stays visible through the same walk: `ls | env`, `bash -c 'env'`,
1830
1827
  * `echo "$(printenv)"` and a nested `sh -c 'x; env'` are all commands, and so is `env` itself
1831
1828
  * when nothing follows it.
@@ -1870,21 +1867,30 @@ export function checkBlockedCommand(command) {
1870
1867
  export function resolvePath(path, projectRoot) {
1871
1868
  if (path === '' || path === '/')
1872
1869
  return projectRoot;
1873
- const clean = path.replace(/\0/g, '');
1874
- const resolved = clean.startsWith('/')
1875
- ? posix.normalize(clean)
1876
- : posix.normalize(`${projectRoot}/${clean}`);
1870
+ const resolved = rootedPath(path, projectRoot);
1877
1871
  if (resolved !== projectRoot && !resolved.startsWith(projectRoot + '/'))
1878
1872
  return projectRoot;
1879
1873
  return resolved;
1880
1874
  }
1875
+ /**
1876
+ * A tool path as an absolute posix path: an absolute path normalized, a relative
1877
+ * one resolved against the project root — the one rule {@link resolvePath},
1878
+ * {@link isOutsideRoot} and {@link commandStartsWithCdInto} all compare against.
1879
+ *
1880
+ * @param path - The path the tool was given.
1881
+ * @param projectRoot - The project root.
1882
+ * @returns The normalized absolute path.
1883
+ */
1884
+ function rootedPath(path, projectRoot) {
1885
+ const clean = path.replace(/\0/g, '');
1886
+ return clean.startsWith('/') ? posix.normalize(clean) : posix.normalize(`${projectRoot}/${clean}`);
1887
+ }
1881
1888
  /**
1882
1889
  * Whether a tool path points outside the project root once normalized (an
1883
1890
  * absolute path elsewhere, or a `..` that climbs out). {@link resolvePath}
1884
1891
  * clamps such a path to the root; a file tool must refuse it instead of
1885
1892
  * answering about the root — a `list_files /tmp/x` used to return the
1886
- * workspace listing as if it were `/tmp/x` (X1 runs sonnet-m17 and sol-m05,
1887
- * 2026-10-14).
1893
+ * workspace listing as if it were `/tmp/x`.
1888
1894
  *
1889
1895
  * @param path - The path the tool was given.
1890
1896
  * @param projectRoot - The project root.
@@ -1893,12 +1899,60 @@ export function resolvePath(path, projectRoot) {
1893
1899
  export function isOutsideRoot(path, projectRoot) {
1894
1900
  if (path === '' || path === '/')
1895
1901
  return false;
1896
- const clean = path.replace(/\0/g, '');
1897
- const resolved = clean.startsWith('/')
1898
- ? posix.normalize(clean)
1899
- : posix.normalize(`${projectRoot}/${clean}`);
1902
+ const resolved = rootedPath(path, projectRoot);
1900
1903
  return resolved !== projectRoot && !resolved.startsWith(projectRoot + '/');
1901
1904
  }
1905
+ /**
1906
+ * Whether a command's FIRST statement already changes into `directory`.
1907
+ *
1908
+ * `exec_command` refuses a `cwd` outside the workspace, and that refusal tells
1909
+ * the caller to drop `cwd` and start the command with `cd <path> && `. When the
1910
+ * command ALREADY starts that way the advice describes what was just sent, so
1911
+ * the command runs as written with the backend's working directory at the root
1912
+ * instead (`{cwd: "/tmp", command: "cd /tmp && …"}` was refused with "start the command with
1913
+ * `cd /tmp && `"; the same command resent with `cwd: "/workspace"` then passes).
1914
+ *
1915
+ * Only the leading statement counts, only when the shell's own parse puts it in
1916
+ * the caller's shell — a `( cd … )` group or a `sh -c '…'` runs in its own
1917
+ * directory and moves nothing — and only when it ends the way a following
1918
+ * command needs (`&&`, `;`, a newline, or nothing after it). `cd … | cat` is a
1919
+ * pipeline, so its `cd` runs in a subshell, and `&` backgrounds it: neither
1920
+ * leaves the caller's directory changed.
1921
+ *
1922
+ * @param command - The shell command as the tool received it.
1923
+ * @param directory - The `cwd` the caller asked for.
1924
+ * @param projectRoot - The project root a relative path is resolved against.
1925
+ * @returns True when the command's first statement `cd`s into `directory`.
1926
+ */
1927
+ export function commandStartsWithCdInto(command, directory, projectRoot) {
1928
+ const first = locatedCommandSegments(command)[0];
1929
+ if (!first || first.segment === null || first.segment.program !== 'cd')
1930
+ return false;
1931
+ if ((first.shellScope?.length ?? 0) > 0)
1932
+ return false;
1933
+ const terminator = first.terminator;
1934
+ if (terminator !== '&&' && terminator !== ';' && terminator !== '\n' && terminator !== '') {
1935
+ return false;
1936
+ }
1937
+ if (first.segment.args.length !== 1)
1938
+ return false;
1939
+ const target = withoutQuotes(first.segment.args[0] ?? '');
1940
+ return rootedPath(target, projectRoot) === rootedPath(directory, projectRoot);
1941
+ }
1942
+ /**
1943
+ * A shell word with one surrounding quote pair removed (`'/tmp'` → `/tmp`,
1944
+ * `"/tmp"` → `/tmp`). A word without a matching pair is returned as written.
1945
+ *
1946
+ * @param word - One word of a parsed statement, as written.
1947
+ * @returns The word's text.
1948
+ */
1949
+ function withoutQuotes(word) {
1950
+ const quote = word[0];
1951
+ if ((quote === "'" || quote === '"') && word.length >= 2 && word.endsWith(quote)) {
1952
+ return word.slice(1, -1);
1953
+ }
1954
+ return word;
1955
+ }
1902
1956
  /**
1903
1957
  * Validate that a glob/include pattern is safe (no shell metacharacters that could
1904
1958
  * inject). Allows alphanumeric, `* ? . _ - /` and the bracket/paren glob chars `[] ()`.
@@ -1944,8 +1998,8 @@ export const BRACE_GLOB_HINT = `or one brace group of up to ${MAX_BRACE_GLOB_ALT
1944
1998
  * Neither search tool expands braces itself: `grep --include` and `find` take
1945
1999
  * the pattern as a single shell-quoted argument, so no shell ever sees it.
1946
2000
  * `find_files {pattern:"*.{ts,tsx}"}` and `search_files {include:"*.{ts,tsx}"}`
1947
- * were both refused outright (xr-molecule-flash-m199 and m189), costing a round
1948
- * trip and pushing the agent to a narrower search. The group is expanded here
2001
+ * were both refused outright, costing a round trip and pushing the agent to a narrower
2002
+ * search. The group is expanded here
1949
2003
  * instead — `search_files` passes one `--include` per alternative and
1950
2004
  * `find_files` ORs one `-name`/`-path` predicate per alternative.
1951
2005
  *
@@ -1985,8 +2039,8 @@ export function expandBraceGlob(pattern) {
1985
2039
  *
1986
2040
  * `find -name` compares a pattern against a file's BASENAME, so a pattern
1987
2041
  * containing a slash (`helpers/*`, `e2e/*.spec.ts`, `src` + `**` + `/App.tsx`)
1988
- * can never match and the tool answers `[]` (xr-molecule-flash-m77, on two
1989
- * calls in one run). Such a pattern is matched with `-path` instead: an fnmatch
2042
+ * can never match and the tool answers `[]` (twice in one run). Such a pattern is matched
2043
+ * with `-path` instead: an fnmatch
1990
2044
  * glob over the whole printed path, whose `*` crosses `/` — the rule
1991
2045
  * `.gitignore` uses, and the reason a bare `helpers/*` means "a `helpers/`
1992
2046
  * directory anywhere below the root". The pattern is anchored with a leading
@@ -2036,25 +2090,38 @@ export function findPathArgs(pattern) {
2036
2090
  * search root. Handed `/workspace/node_modules` and
2037
2091
  * `@molecule/api-resource-user/dist/*.d.ts` — a directory named outright —
2038
2092
  * `find` walked the sandbox's entire dependency tree and hit the tool's 10 s
2039
- * deadline without printing a result (xr-molecule-flash-m94: the one directory
2040
- * the model meant answered a `list_files` in 0 s). The literal prefix is that
2093
+ * deadline without printing a result (the one directory the model meant answered a `list_files`
2094
+ * in 0 s). The literal prefix is that
2041
2095
  * directory, and rooting the search at it is what keeps the call instant.
2042
2096
  *
2043
2097
  * A BARE first segment (`helpers/*`, `bonds/*`, `e2e/*.spec.ts`) is NOT one:
2044
2098
  * `helpers/*` means "a `helpers/` directory anywhere below the root", which the
2045
- * tool documents and a live run relied on (xr-molecule-flash-m77 — a path
2046
- * pattern is not anchored to one depth). Rooting those at
2099
+ * tool documents and a live run relied on (a path pattern is not anchored to one depth).
2100
+ * Rooting those at
2047
2101
  * `<root>/helpers` would answer with a SUBSET, silently: the nested
2048
2102
  * `api/src/helpers/` matches would disappear, and a missing file reads as
2049
2103
  * "no such file". So a prefix is returned only when it names a path of two or
2050
2104
  * more segments (`@molecule/api-resource-user/dist`), which is the shape that
2051
2105
  * is a directory rather than a directory name.
2052
2106
  *
2107
+ * An npm SCOPE as the first segment, under a `node_modules` root, is the one
2108
+ * exception that parameter `root` enables: npm installs a scope only directly
2109
+ * inside a `node_modules` directory, and the walk already prunes every nested
2110
+ * `node_modules` (see `findPruneArgs`), so the whole-tree form can only ever
2111
+ * match `<root>/@molecule/…` — the scope directory IS the directory the pattern
2112
+ * names. Left un-narrowed, a scoped-package pattern walked the sandbox's entire
2113
+ * dependency tree and hit the 10 s deadline with no result at all. A bare NAME stays a
2114
+ * directory name at any depth:
2115
+ * `helpers/*` is unchanged even under a `node_modules` root.
2116
+ *
2053
2117
  * @param pattern - A pattern already accepted by {@link isValidGlob}.
2118
+ * @param root - The resolved search root, when one is already known; a
2119
+ * scope-first prefix is returned only when this ends in a `node_modules`
2120
+ * segment (and the pattern has more path after the scope).
2054
2121
  * @returns The literal directory prefix, without a trailing slash, or null when
2055
2122
  * the pattern has no glob character, no `/`, or only a bare name before one.
2056
2123
  */
2057
- export function literalDirPrefix(pattern) {
2124
+ export function literalDirPrefix(pattern, root) {
2058
2125
  const stripped = pattern.replace(/^(?:\.?\/)+/, '');
2059
2126
  const firstGlob = stripped.search(/[?*[]/);
2060
2127
  // No glob at all: the pattern IS a path, and the whole-tree form already
@@ -2065,7 +2132,14 @@ export function literalDirPrefix(pattern) {
2065
2132
  if (at <= 0)
2066
2133
  return null;
2067
2134
  const prefix = stripped.slice(0, at);
2068
- return prefix.includes('/') ? prefix : null;
2135
+ if (prefix.includes('/'))
2136
+ return prefix;
2137
+ // One segment: a bare directory NAME ("any depth", flash-m77) — except an npm
2138
+ // scope directly inside the node_modules root, which is one directory (above).
2139
+ if (root === undefined || !/^@[^/*?[]+$/.test(prefix))
2140
+ return null;
2141
+ const rootName = root.replace(/\/+$/, '').split('/').pop();
2142
+ return rootName === 'node_modules' ? prefix : null;
2069
2143
  }
2070
2144
  /**
2071
2145
  * Whether a backend error is the sandbox's own command deadline.
@@ -2074,7 +2148,7 @@ export function literalDirPrefix(pattern) {
2074
2148
  * `[deadline_exceeded] the operation timed out: … It can be modified by passing
2075
2149
  * 'timeoutMs' when making the request. Use '0' to disable the timeout.` — which
2076
2150
  * names an option the executor cannot set and says nothing about the search
2077
- * that died. Handing that sentence to the model is what xr-molecule-flash-m94
2151
+ * that died. Handing that sentence to the model is what a run
2078
2152
  * read after `find_files` spent 10 s walking `node_modules`; the tool answers
2079
2153
  * the deadline in its own words instead (see {@link searchTimeoutError}).
2080
2154
  *
@@ -2155,8 +2229,8 @@ export function pathArgError(path, tool) {
2155
2229
  *
2156
2230
  * The path message is the wrong reading of it: "requires a non-empty path
2157
2231
  * argument" says the call was understood and the path was left out, so the model
2158
- * retries the call it just made instead of sending the whole call again (x0
2159
- * flash-m83 +29:32: `edit_file {}`, one round lost). An empty object and a
2232
+ * retries the call it just made instead of sending the whole call again (`edit_file {}` loses
2233
+ * one round). An empty object and a
2160
2234
  * missing `path` are different failures and deserve different sentences.
2161
2235
  *
2162
2236
  * @param input - The raw tool input.
@@ -2300,9 +2374,8 @@ export const MAX_BATCH_READ_FILES = 25;
2300
2374
  * MIDDLE — the cut lands between two `"content"` values, so whole files fell
2301
2375
  * out and the note in their place said only "N characters cut here" — while
2302
2376
  * this tool's own note had promised each of those files that it "fits in one
2303
- * call, so it is not paged": X0 flash-m81 (2026-10-16) read ten e2e specs in
2304
- * one call (121,467 chars), lost the middle six, and bought them again in six
2305
- * further calls (52,908 chars).
2377
+ * call, so it is not paged": one batched read of ten e2e specs (121,467 chars) lost the
2378
+ * middle six and bought them again in six further calls (52,908 chars).
2306
2379
  *
2307
2380
  * So the CALL gets the ceiling the request gives it. Whole files are returned
2308
2381
  * up to it; the paths that did not fit are named in `note` and listed in
@@ -2337,7 +2410,7 @@ export const MAX_READ_RETURN_CHARS = MAX_READ_CALL_CHARS - READ_RESULT_ENVELOPE_
2337
2410
  /**
2338
2411
  * A file this small is returned whole on a second window of it (paging it costs a call each).
2339
2412
  * The same bound as {@link MAX_READ_RETURN_CHARS}: the windowed-read note promises that a
2340
- * file under it "fits in ONE call", and a smaller bound here broke that promise (X1 flash-m67).
2413
+ * file under it "fits in ONE call", and a smaller bound here breaks that promise.
2341
2414
  */
2342
2415
  export const SMALL_FILE_WHOLE_CHARS = MAX_READ_RETURN_CHARS;
2343
2416
  /**
@@ -2348,11 +2421,11 @@ export const SMALL_FILE_WHOLE_CHARS = MAX_READ_RETURN_CHARS;
2348
2421
  * An entry document is boilerplate the reader rarely needs in full: meta tags,
2349
2422
  * stylesheet and preload links, a `<style>` block, and a handful of `<script>`
2350
2423
  * tags. The parts an executor acts on — the body's structure, where a script is
2351
- * loaded — are all visible in an outline of one line per tag. X0 flash-m77 paid
2352
- * 63,735 chars for one such read: ~60 KB of it was the IDE's own preview bridges,
2424
+ * loaded — are all visible in an outline of one line per tag. One such read paid
2425
+ * 63,735 chars: ~60 KB of it was the IDE's own preview bridges,
2353
2426
  * and the read pushed the transcript past its context cap, so the e2e specs the
2354
2427
  * executor had just read were trimmed to summaries and it read the whole suite
2355
- * again — twice (findings, +01:23 and +02:56). The scaffold now keeps those
2428
+ * again — twice. The scaffold now keeps those
2356
2429
  * bridges in separate files, but a template's own head is still ~5-12 KB, and a
2357
2430
  * static-site or SSR entry can be far more.
2358
2431
  *
@@ -2486,7 +2559,7 @@ export function truncateMiddle(s, maxLength) {
2486
2559
  /**
2487
2560
  * A test run's tally lines (` 3 failed`, ` 71 passed (4.6m)`) the cut text lost, put back at
2488
2561
  * its end: Playwright lists every failed test after its "N failed" line, so a long list pushed
2489
- * the line itself into the cut middle and the run read as having no failures (X1 flash-m60).
2562
+ * the line itself into the cut middle and the run reads as having no failures.
2490
2563
  *
2491
2564
  * @param original - The whole output.
2492
2565
  * @param cut - The truncated output.
@@ -2601,9 +2674,8 @@ export const RUNNING_FAILURE_LINE_RE = '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\
2601
2674
  * the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
2602
2675
  *
2603
2676
  * A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
2604
- * log are its least informative part — in X1 flash-m61 the last 3 000 bytes of a 139-test walk
2605
- * were the skip list, and all 61 failures were above them, which cost the run the grep it had to
2606
- * make anyway. A background run also carries each failure's own error as it fails
2677
+ * log are its least informative part — the last 3 000 bytes of a 139-test walk are the skip
2678
+ * 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
2607
2679
  * ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
2608
2680
  * those blocks' headers and their `Error:` lines, so this slice shows the causes too.
2609
2681
  *
@@ -2642,11 +2714,11 @@ export const RUNNING_LOG_READ_BYTES = REPEATING_CAUSE_TAIL_BYTES;
2642
2714
  * The shell command whose stdout is a bounded CONTIGUOUS read of a running task's own log: the
2643
2715
  * log's byte size, then its last {@link RUNNING_LOG_READ_BYTES} bytes.
2644
2716
  *
2645
- * {@link runningOutputSliceCommand} is the wrong read for a diagnosis, and l07 is the finding: it
2717
+ * {@link runningOutputSliceCommand} is the wrong read for a diagnosis: it
2646
2718
  * stitches the first 40 grep-selected failure lines to the last 1 000 bytes with a blank line
2647
2719
  * between, so the tail is DISJOINT from the headers above it. Read as one log, the last numbered
2648
- * header absorbs whatever the tail starts with — a clipped skipped-test title became the cause of
2649
- * a genuine failure (xr-molecule-flash-l07). A contiguous window can only ever hold real
2720
+ * header absorbs whatever the tail starts with — a clipped skipped-test title becomes the cause
2721
+ * of a genuine failure. A contiguous window can only ever hold real
2650
2722
  * neighbouring lines of one log.
2651
2723
  *
2652
2724
  * The size rides along so a caller can say when the window is only the log's end
@@ -2778,8 +2850,8 @@ function failureBody(block) {
2778
2850
  * pair of a failed assertion.
2779
2851
  *
2780
2852
  * Without this, every `TimeoutError: page.press: Timeout 5000ms exceeded.` in a log is one
2781
- * message, because that line names the ACTION (`page.press`) and never its subject. X1 flash-m84
2782
- * (2026-10-16) had four such failures in one run — one waiting on
2853
+ * message, because that line names the ACTION (`page.press`) and never its subject. Four such
2854
+ * failures in one run — one waiting on
2783
2855
  * `button:has-text("Publish Article")`, one on `.article-preview h1`, one on
2784
2856
  * `.article-preview').first().locator('.author')`, one on a generated `h1:has-text("Test
2785
2857
  * Article …")` — and the run's summary called them two causes while its mid-run note called them
@@ -2815,8 +2887,8 @@ const FAILURE_EVIDENCE_CHARS = 200;
2815
2887
  * `Expected: <n>`, and a signature built from a `waiting for` line never carries the assertion
2816
2888
  * values at all. Those values are what a fix round needs, and the run's stdout they came from is
2817
2889
  * cut in the MIDDLE by the tool-result cap, so a listed cause that does not carry them sends the
2818
- * executor back to the log — xr-molecule-flash-m102 (+12:43) made four `grep -A` round trips
2819
- * (~25 s) for the bodies of causes the note had already listed.
2890
+ * executor back to the log — four `grep -A` round trips (~25 s) for the bodies of causes the
2891
+ * note had already listed.
2820
2892
  *
2821
2893
  * @param body - The block's lines, from {@link failureBody}.
2822
2894
  * @param message - The cause's signature: a line it already carries is not repeated.
@@ -2860,7 +2932,7 @@ function failureEvidence(body, message) {
2860
2932
  * The duration is the one that matters. A suite that sets `actionTimeout: 5_000` — the X1
2861
2933
  * realworld suite does — spends `5 s` plus teardown on every "the locator never appeared"
2862
2934
  * failure, so `~7 s` is a property of the CONFIG, not of the cause, and four different missing
2863
- * elements print the same number (X1 flash-m84, 2026-10-16). Two tests waiting on the same
2935
+ * elements print the same number. Two tests waiting on the same
2864
2936
  * locator for 5 s and for 10 s wait for the same thing; two waiting on different locators do not
2865
2937
  * become one cause by timing out together.
2866
2938
  *
@@ -2894,7 +2966,7 @@ function failureBudgetMs(block) {
2894
2966
  * The cause signature with the budget it normalized away put back: `Test timeout of <n> exceeded`
2895
2967
  * becomes `Test timeout of 15000ms exceeded`. Every other `<n>` stays — those are the values that
2896
2968
  * differ between tests of one cause, while the budget is the one that does not, and it is what
2897
- * turns "a timeout" into "15 creates in 15 s" (xr-molecule-flash-m110 +20:44).
2969
+ * turns "a timeout" into "15 creates in 15 s".
2898
2970
  *
2899
2971
  * @param message - The normalized signature.
2900
2972
  * @param budgetMs - The budget the cause's first block printed.
@@ -2956,9 +3028,8 @@ function testKey(t) {
2956
3028
  * A retried failure prints twice in a `list` log — its first attempt, then the same test with
2957
3029
  * `(retry #1)` after the title — while its failure block names it without the marker. Keyed
2958
3030
  * literally, the retry row is a test no cause claims, so it fell through to the duration arm and
2959
- * was listed as a "shared duration" cause beside the proven cause it repeated: on
2960
- * xr-molecule-flash-m155 (+21:50) every retry row of the run became one of six extra causes
2961
- * duplicating the causes above them.
3031
+ * was listed as a "shared duration" cause beside the proven cause it repeated: every retry row
3032
+ * of a run became one of six extra causes duplicating the causes above them.
2962
3033
  *
2963
3034
  * @param t - A failed test, as a result line or a failure block spelled it.
2964
3035
  * @returns The key a test's retry row shares with its failure block.
@@ -2998,13 +3069,13 @@ function distinctTests(tests) {
2998
3069
  *
2999
3070
  * Either way the point is the same: a suite whose failures repeat one cause spends its whole
3000
3071
  * runtime proving that one cause, and the executor watching it can be told so at the third
3001
- * failure instead of at the last. X1 flash-m76 walked 15 tests in 4.0 minutes where 15.6 s
3072
+ * failure instead of at the last. Fifteen tests walked in 4.0 minutes where 15.6 s
3002
3073
  * each was the test timeout and one missing `navigate('/')` was the cause of all of them.
3003
3074
  *
3004
3075
  * The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
3005
- * prints each failure twice (a second reporter, a retry) had X1 flash-m83 told that four of its
3006
- * tests had failed the same way when two had, so the executor stopped a 139-test walk at test 21
3007
- * with one cause in hand (2026-10-16).
3076
+ * prints each failure twice (a second reporter, a retry) has told a run that four of its
3077
+ * tests failed the same way when two had, so the executor stopped a 139-test walk at test 21
3078
+ * with one cause in hand.
3008
3079
  *
3009
3080
  * @param log - The log so far.
3010
3081
  * @returns The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN}
@@ -3083,20 +3154,25 @@ export function repeatingFailure(log) {
3083
3154
  * A body for a test the request EXCLUDES is not that evidence: its failure does not count for
3084
3155
  * this request ({@link matchingExcludedTitles}), so a wait that handed it over as the diagnostic
3085
3156
  * spent a model round trip and 8 k of partial log on a test the same result says to ignore —
3086
- * exactly what xr-molecule-flash-h13 (+19:06) did twice while the platform's proof was still
3087
- * walking.
3157
+ * which a run has done twice while the platform's proof was still walking.
3158
+ *
3159
+ * With `finalOnly`, only a failure the run has FINISHED with counts; a pending retry waits.
3088
3160
  *
3089
3161
  * @param log - A log, or the slice of one a wait reads.
3090
3162
  * @param excluded - The test titles the request carves out, if the caller knows them.
3163
+ * @param options - `finalOnly` reads only a failure the run has finished with; `noRetries` says
3164
+ * the command proves the run cannot retry (`--retries=0`), so its first failure is final.
3091
3165
  * @returns True when a `✘` row's own test, outside the request's exclusions, has a failure body
3092
- * under it.
3166
+ * under it — and, with `finalOnly`, that failure is final in the log read so far.
3093
3167
  */
3094
- export function logShowsInlineFailureBodies(log, excluded = []) {
3168
+ export function logShowsInlineFailureBodies(log, excluded = [], options = {}) {
3095
3169
  const text = log.replace(ANSI_SGR, '');
3096
3170
  const failed = new Set(distinctTests(failedTests(text)).map(retrylessTestKey));
3097
3171
  if (failed.size === 0)
3098
3172
  return false;
3099
- for (const block of failureBlocks(text)) {
3173
+ const blocks = failureBlocks(text);
3174
+ const rows = options.finalOnly ? resultRows(text) : [];
3175
+ for (const block of blocks) {
3100
3176
  const m = FAILURE_BLOCK_HEADER.exec(block.split('\n')[0] ?? '');
3101
3177
  if (m === null)
3102
3178
  continue;
@@ -3104,11 +3180,91 @@ export function logShowsInlineFailureBodies(log, excluded = []) {
3104
3180
  const heading = where === '' ? title : `${where} › ${title}`;
3105
3181
  if (matchingExcludedTitles(heading, excluded).length > 0)
3106
3182
  continue;
3107
- if (failed.has(retrylessTestKey({ where, title, seconds: null })))
3183
+ const key = retrylessTestKey({ where, title, seconds: null });
3184
+ if (!failed.has(key))
3185
+ continue;
3186
+ if (!options.finalOnly)
3187
+ return true;
3188
+ if (failureIsFinal({ rows, blocks, key, noRetries: options.noRetries === true }))
3108
3189
  return true;
3109
3190
  }
3110
3191
  return false;
3111
3192
  }
3193
+ /** The runner's retry marker on a `list`-reporter row (` (retry #1)`, `_retrySuffix`). */
3194
+ const RETRY_ROW_SUFFIX = /\s*\(retry #\d+\)$/;
3195
+ /** The separator a retried failure's body opens its own attempt with (`formatFailure`). */
3196
+ const RETRY_BODY_SEPARATOR = /^\s*Retry #\d+\b/m;
3197
+ /**
3198
+ * Every result row a log carries, in the order the runner printed them.
3199
+ *
3200
+ * @param log - The log (ANSI already stripped).
3201
+ * @returns The rows, each with where it sits, what it is called, and which attempt it is.
3202
+ */
3203
+ function resultRows(log) {
3204
+ const rows = [];
3205
+ for (const line of log.split('\n')) {
3206
+ const m = LIST_RESULT_LINE.exec(line);
3207
+ if (!m)
3208
+ continue;
3209
+ const { where, title } = splitTitlePath(m[2]);
3210
+ rows.push({
3211
+ mark: m[1],
3212
+ where,
3213
+ title,
3214
+ seconds: durationSeconds(m[3], m[4]),
3215
+ retry: RETRY_ROW_SUFFIX.test(title),
3216
+ });
3217
+ }
3218
+ return rows;
3219
+ }
3220
+ /**
3221
+ * A failure block's test identity, keyed the way a result row is.
3222
+ *
3223
+ * @param block - One block from {@link failureBlocks}.
3224
+ * @returns The test key, or null when the block's header names no test.
3225
+ */
3226
+ function failureBlockKey(block) {
3227
+ const m = FAILURE_BLOCK_HEADER.exec(block.split('\n')[0] ?? '');
3228
+ if (m === null)
3229
+ return null;
3230
+ const { where, title } = splitTitlePath(m[1] ?? '');
3231
+ return retrylessTestKey({ where, title, seconds: null });
3232
+ }
3233
+ /**
3234
+ * Whether a test's failure is FINAL in the log read so far — one the run is done with, not a
3235
+ * first attempt whose retry is still to come.
3236
+ *
3237
+ * A `list` run retries a failed test immediately, so the test's own next row says which attempt
3238
+ * the log has printed last: the same test's ` (retry #N)` row (final when that retry failed; a
3239
+ * flake the run absorbed when it passed), or a different test's row (no retry ran, so the failure
3240
+ * was final). A `Retry #N` separator in the test's body is the runner's own record that a retry
3241
+ * attempt ran and failed. A first attempt followed by nothing is only the last row READ — its
3242
+ * retry may not have printed yet — so it waits; a command that pins `--retries=0` proves there is
3243
+ * no retry to wait for.
3244
+ *
3245
+ * A deployed proof's yield handed back a first attempt (no `Retry #` separator) that then
3246
+ * passed on its retry, and the edit it prompted cost a redeploy plus a whole walk. The fix is
3247
+ * not to stop yielding — the retry-failed shape paid off — but to yield only on this one.
3248
+ *
3249
+ * @param args - The log's rows and blocks, the failing test's key, and whether the command
3250
+ * proves the run cannot retry.
3251
+ * @returns True when that test's failure is final in the log read so far.
3252
+ */
3253
+ function failureIsFinal(args) {
3254
+ if (args.noRetries)
3255
+ return true;
3256
+ // The runner's own record that an attempt after the first ran and failed.
3257
+ if (args.blocks.some((block) => failureBlockKey(block) === args.key && RETRY_BODY_SEPARATOR.test(block)))
3258
+ return true;
3259
+ const own = args.rows.filter((row) => retrylessTestKey(row) === args.key);
3260
+ const last = own.at(-1);
3261
+ if (last === undefined)
3262
+ return false;
3263
+ if (last.retry)
3264
+ return last.mark === '✘';
3265
+ const at = args.rows.indexOf(last);
3266
+ return args.rows.slice(at + 1).some((row) => retrylessTestKey(row) !== args.key);
3267
+ }
3112
3268
  /** How many of a repeating cause's failures the note lists before it says "and N more". */
3113
3269
  export const REPEATING_FAILURE_LISTED = 5;
3114
3270
  /**
@@ -3119,8 +3275,8 @@ export const REPEATING_FAILURE_LISTED = 5;
3119
3275
  */
3120
3276
  export const TOOL_RESULT_CHARS = 8000;
3121
3277
  /**
3122
- * The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}. The recap
3123
- * measured by xr-molecule-flash-m102 (+12:43) was `12 failures, 8 causes`: one cause line is
3278
+ * The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}. A measured
3279
+ * recap was `12 failures, 8 causes`: one cause line is
3124
3280
  * ~200 chars and ~80 more with its two body lines, so 8 causes is ~2.3 k against the 8 k cap —
3125
3281
  * the old 5-cause cap withheld 3 causes that the same result had room for.
3126
3282
  *
@@ -3144,8 +3300,8 @@ export const FAILURE_CAUSE_TESTS_LISTED = 6;
3144
3300
  *
3145
3301
  * Used only where a COUNT of files is what the sentence needs; every note that names a
3146
3302
  * test to the executor prints the path the log wrote, because that is the path its own
3147
- * re-run hint names and the one the executor can paste back into a command (flash-m82
3148
- * read a shortened `e2e/auth.spec.ts:19` as a file the app owned, and rewrote its config
3303
+ * re-run hint names and the one the executor can paste back into a command (a shortened
3304
+ * `e2e/auth.spec.ts:19` reads as a file the app owned, and rewrites its config
3149
3305
  * over a suite path that was already right).
3150
3306
  *
3151
3307
  * @param file - A path as {@link specFile} returns it.
@@ -3211,7 +3367,7 @@ export const UNIVERSAL_CAUSE_FILES = 3;
3211
3367
  * THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
3212
3368
  * reading was only offered at a completed run; once it STOPS the walk, every test waited for is
3213
3369
  * a test whose result is thrown away, and a blank page costs its per-test budget each time
3214
- * (m177: 18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
3370
+ * (18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
3215
3371
  * `pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
3216
3372
  * needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
3217
3373
  * no failure showing a locator RESOLVING ({@link ELEMENT_FOUND} — a blank page cannot resolve
@@ -3236,8 +3392,8 @@ const LOCATOR_WAIT = /waiting for locator\(|element\(s\) not found|toBeVisible\(
3236
3392
  * of the viewport`). A blank page cannot resolve a locator, so one of these anywhere in a run
3237
3393
  * disproves {@link emptyPageCause}.
3238
3394
  *
3239
- * xr-molecule-flash-m187 (+15:39 and +18:54) narrowed two re-runs to the tests that had just
3240
- * failed, and both were stopped as "the app is rendering NOTHING" while every block in the log
3395
+ * Two re-runs narrowed to the tests that had just failed were stopped as "the app is rendering
3396
+ * NOTHING" while every block in the log
3241
3397
  * carried `locator resolved to <i class="ion-close-circled"></i>` followed by `element is not
3242
3398
  * visible` — the page was drawn, and only an icon had no box.
3243
3399
  */
@@ -3284,10 +3440,10 @@ function pageErrorInLog(text) {
3284
3440
  *
3285
3441
  * This is ONE cause even though the signatures differ — each spec asks for its own first element
3286
3442
  * (`input[name="username"]`, `a.navbar-brand`, `h1`), so {@link repeatingFailure} cannot group
3287
- * them and {@link universalCause}'s signature test cannot see the run is over. xr-molecule-flash-
3288
- * m141 (+20:30) walked a built app that rendered nothing: 36 tests had failed, every one at its
3289
- * first locator, and the note told the run to keep walking for another ~9 minutes. xr-molecule-
3290
- * flash-m177 (+08:38 → +12:52) repeated it: 18 tests, one whole walk, on a page whose
3443
+ * them and {@link universalCause}'s signature test cannot see the run is over. One run
3444
+ * walked a built app that rendered nothing: 36 tests had failed, every one at its
3445
+ * first locator, and the note told the run to keep walking for another ~9 minutes. Another
3446
+ * repeated it: 18 tests, one whole walk, on a page whose
3291
3447
  * `PAGEERROR No 'auth-client' provider bonded` was in the log from the first one.
3292
3448
  *
3293
3449
  * @param text - The log (ANSI already stripped).
@@ -3391,8 +3547,8 @@ const PLAYWRIGHT_VALUE_FLAGS = new Set([
3391
3547
  * whole suite (or its selection cannot be read).
3392
3548
  *
3393
3549
  * {@link universalCause}'s file threshold is written for a whole-suite run, which a narrowed run
3394
- * can never reach. xr-molecule-flash-m119 (+42:08) re-ran ONE spec file against a hand-started
3395
- * `vite preview` the browser could not reach, and every test in it failed `page.goto: Timeout` —
3550
+ * can never reach. A run that re-ran ONE spec file against a hand-started `vite preview` the
3551
+ * browser could not reach had every test in it fail `page.goto: Timeout` —
3396
3552
  * but one file is below {@link UNIVERSAL_CAUSE_FILES}, so the wait sat out its entire 280 s
3397
3553
  * budget on a run that had already measured all it could.
3398
3554
  *
@@ -3441,7 +3597,7 @@ export function specFilesSelected(command) {
3441
3597
  *
3442
3598
  * A command that names `file:line` tests is a re-run of tests that already failed — the shape
3443
3599
  * {@link emptyPageCause} must not read as "the page renders nothing", because the executor is
3444
- * verifying fixes and the stop throws that verdict away (xr-molecule-flash-m187, twice).
3600
+ * verifying fixes and the stop throws that verdict away (twice).
3445
3601
  *
3446
3602
  * @param command - The run's command.
3447
3603
  * @returns The selected test references, or null when the selection is not per-test.
@@ -3499,7 +3655,7 @@ function sameTestRef(ref, where) {
3499
3655
  * threshold, and nothing has passed since its first failure. The threshold is
3500
3656
  * {@link UNIVERSAL_CAUSE_FILES} for a whole-suite run but the number of files the command
3501
3657
  * SELECTED for a narrowed one ({@link specFilesSelected}): a one-file run can never span three,
3502
- * and xr-molecule-flash-m119 (+42:08) waited out its whole budget on exactly that shape. An app
3658
+ * and a run can wait out its whole budget on exactly that shape. An app
3503
3659
  * that is not up, a 5xx on every request, or a selector that is missing on every page all look
3504
3660
  * like that, and every remaining test will fail the same way — so the run's remaining minutes
3505
3661
  * measure nothing. A CONNECTION-class cause ({@link CONNECTION_CAUSE}) needs no file span at all:
@@ -3508,8 +3664,8 @@ function sameTestRef(ref, where) {
3508
3664
  *
3509
3665
  * Anything less is NOT this: a cause that stops at one file of a multi-file run, or that leaves
3510
3666
  * other tests passing, or that shares the run with failures of another shape, leaves the run with
3511
- * something to say, and the run is the only thing that will say it. X1 flash-m83 (2026-10-16)
3512
- * stopped eight local runs at their first repeating cause over 14.5 minutes and never finished
3667
+ * something to say, and the run is the only thing that will say it. Stopping runs at their first
3668
+ * repeating cause has burned eight local runs over 14.5 minutes without finishing
3513
3669
  * one; three of its deploys each fixed a single cause that a finished run would have listed
3514
3670
  * alongside the others.
3515
3671
  *
@@ -3565,14 +3721,14 @@ export function universalCause(log, found, command) {
3565
3721
  * spec-file threshold — {@link UNIVERSAL_CAUSE_FILES}, or the files a narrowed run's own
3566
3722
  * command selects, or {@link CONNECTION_CAUSE_TESTS} tests of an app that never answered — with
3567
3723
  * nothing passing since the first. That is the
3568
- * dont-stop-runs rule. X1 flash-m83 (2026-10-16) stopped eight local runs at their first
3569
- * repeating cause in 14.5 minutes and never finished one, and a run that still has other causes
3724
+ * dont-stop-runs rule. Stopping runs at their first repeating cause has burned eight local
3725
+ * runs in 14.5 minutes without finishing one, and a run that still has other causes
3570
3726
  * left to name is the only thing that will name them.
3571
3727
  * - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
3572
3728
  * a suite whose config sets a timeout spends that timeout on every failure that waits for
3573
3729
  * anything, so three unrelated tests timing out together look exactly like one broken run
3574
- * (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). X1
3575
- * xr-molecule-flash-m99 (2026-10-16) was told `status: 'repeating_failure'` twice on that
3730
+ * (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}). A run
3731
+ * told `status: 'repeating_failure'` twice on that
3576
3732
  * shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
3577
3733
  * — read the early return as "stop waiting, edit now", and voided a run that was still walking;
3578
3734
  * the second cost a 224 s wait that ended with no result at all.
@@ -3606,12 +3762,11 @@ export function earlyReturnCause(found, universal) {
3606
3762
  * extracted by molecule-dev's `excludedTestTitles`) while a run's heading carries more of it
3607
3763
  * (`comments.spec.ts › Comments › should require login to post comment`), so containment is the
3608
3764
  * comparison. Whitespace is normalised and case is ignored: a brief may wrap the title across a
3609
- * line (`xr-molecule-flash-m44`), and the reporter's own casing never has to match the brief's.
3765
+ * line, and the reporter's own casing never has to match the brief's.
3610
3766
  *
3611
3767
  * Two readers used to answer this separately — the causes note (which therefore asked the
3612
3768
  * executor to fix a test the same result said not to) and molecule-dev's exclusion notes — and
3613
- * xr-molecule-flash-m101 paid for the gap (+12:00: a turn and an edit spent on the excluded
3614
- * test).
3769
+ * a run paid for the gap (a turn and an edit spent on the excluded test).
3615
3770
  *
3616
3771
  * @param heading - The text to match against: a test's title, its whole `file › describe › title`
3617
3772
  * path, or (for a note that only has the run's output) the run's text.
@@ -3634,7 +3789,7 @@ export function matchingExcludedTitles(heading, excluded) {
3634
3789
  *
3635
3790
  * The point is that ONE run yields EVERY cause. A Playwright report prints its failures one
3636
3791
  * after another with no statement of which of them share a cause, so a run of 19 failures is 19
3637
- * bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
3792
+ * bodies to read; three deploys (6.7 minutes) each fixed a
3638
3793
  * single cause, two of them found by runs stopped before they walked the rest.
3639
3794
  *
3640
3795
  * The signature is the cause itself where the log carries the failures' bodies (a finished run, a
@@ -3646,8 +3801,8 @@ export function matchingExcludedTitles(heading, excluded) {
3646
3801
  * A test the request EXCLUDES by name is not a cause to fix: its failure does not count for
3647
3802
  * this request ({@link matchingExcludedTitles}), so it is dropped here rather than handed to the
3648
3803
  * executor as work — a result that names it beside "it does not count" is the contradiction
3649
- * xr-molecule-flash-m101 filed (+12:00: the model spent a turn and an edit on the excluded test,
3650
- * on a page that was passing).
3804
+ * a run filed (the model spent a turn and an edit on the excluded test, on a page that was
3805
+ * passing).
3651
3806
  *
3652
3807
  * @param log - The log of a finished or running run.
3653
3808
  * @param excluded - The test titles the request carves out, if the caller knows them.
@@ -3667,8 +3822,8 @@ const FLAKY_TALLY_LINE = /^\s*[1-9]\d*\s+flaky\b/;
3667
3822
  *
3668
3823
  * Playwright prints a numbered failure block for the FIRST attempt of a test that then passes on
3669
3824
  * its retry, and also declares the test under `N flaky` — so a reader that only looks at the
3670
- * blocks counts a flake as a cause to fix. xr-molecule-flash-m105's proof run against the deployed
3671
- * URL reported ten `waitForURL` timing flakes as "causes" of a red run (+24:54), and the executor
3825
+ * blocks counts a flake as a cause to fix. A proof run against the deployed URL reported ten
3826
+ * `waitForURL` timing flakes as "causes" of a red run, and the executor
3672
3827
  * spent its next minutes re-running them one by one until three passed untouched. The
3673
3828
  * declaration under the count is the runner's own answer, and its heading carries the same words
3674
3829
  * as the failure block's ({@link splitTitlePath} reads both the same way).
@@ -3825,7 +3980,7 @@ function groupFailures(log, excluded, pageErrors = []) {
3825
3980
  /**
3826
3981
  * The error every test of a Playwright run reports when the run loaded no base URL: `page.goto('/')`
3827
3982
  * has nothing to resolve against, so the browser refuses the navigation before any page loads.
3828
- * xr-molecule-flash-m113 (+20:34) got ten of these and a list that called them ten causes.
3983
+ * One run got ten of these and a list that called them ten causes.
3829
3984
  */
3830
3985
  const NO_BASE_URL_ERROR = /Cannot navigate to invalid URL/i;
3831
3986
  /** The browser's own `net::ERR_*`, as Playwright prints it for a document that never loaded. */
@@ -3850,7 +4005,7 @@ function everyCauseIsNavigationTimeout(causes) {
3850
4005
  * command loaded no base URL. The line names the config the run should have loaded (the caller
3851
4006
  * passes the one the platform recorded for this suite) and hands over the exact narrowed command
3852
4007
  * with it, so the next run measures the app instead of spending another six minutes on the same
3853
- * configuration mistake (xr-molecule-flash-m113 ran four such runs, 07:06:17 → 07:12:03).
4008
+ * configuration mistake (one run sent four such runs in six minutes).
3854
4009
  *
3855
4010
  * @param causes - The run's failures, all carrying the same error.
3856
4011
  * @param run - How the run was started, when the caller knows it.
@@ -3986,7 +4141,7 @@ const LANDMARK_WORD = {
3986
4141
  * The sentence for a run whose navigational elements are all missing from pages that otherwise
3987
4142
  * painted — the missing shared header the executor must check BEFORE rewriting auth.
3988
4143
  *
3989
- * xr-molecule-flash-m193 (+13:54 → +20:00): four failures on `/` — `a[href="/profile/…"]`,
4144
+ * A run had four failures on `/` — `a[href="/profile/…"]`,
3990
4145
  * `a.navbar-brand`, `nav a[href="/"]`, `nav .user-pic` — while every `error-context.md` snapshot
3991
4146
  * showed the feed, sidebar and footer with no navigation at all. `Home.tsx` imported and
3992
4147
  * re-exported `Navbar` but never rendered it, and `App.tsx` mounted `<Home />` in a bare route.
@@ -4080,7 +4235,7 @@ export function failureCausesNote(log, excluded = [], run, pageErrors = [],
4080
4235
  * Spec files whose selector-contract rows an edit broke since the last whole walk, when the
4081
4236
  * caller knows them: the narrowed re-run names them beside the failing tests. A green re-run of
4082
4237
  * the failure alone would certify a tree it never measured, and the specs selecting the
4083
- * element the edit removed were never walked (xr-molecule-flash-m187 shipped that).
4238
+ * element the edit removed were never walked (a real tree shipped that).
4084
4239
  */
4085
4240
  regressedSpecs = []) {
4086
4241
  const { causes, ignored, flaky } = groupFailures(log, excluded, pageErrors);
@@ -4196,9 +4351,9 @@ regressedSpecs = []) {
4196
4351
  * failure's two most useful body lines underneath.
4197
4352
  *
4198
4353
  * The path is the one the log itself wrote, never a shortened one: it is relative to the
4199
- * command's own cwd, so it is the form the executor can paste back (X1 flash-m82 read a shortened
4200
- * `e2e/auth.spec.ts:19` as the suite's own stale starter spec, rewrote its Playwright config over
4201
- * it and deleted the app's tests — 4 minutes of a 60-minute run on a working config).
4354
+ * command's own cwd, so it is the form the executor can paste back (a shortened
4355
+ * `e2e/auth.spec.ts:19` read as the suite's own stale starter spec, and a Playwright config
4356
+ * rewritten over it deleted the app's tests — 4 minutes of a 60-minute run on a working config).
4202
4357
  *
4203
4358
  * @param cause - The cause to write.
4204
4359
  * @param index - Its position in the recap (0-based; printed 1-based).
@@ -4236,8 +4391,8 @@ function failureCauseLine(cause, index) {
4236
4391
  * found at, and the ONE command that prints their bodies.
4237
4392
  *
4238
4393
  * Without it the old note said "… and 3 more causes" and left the executor to find them in a
4239
- * stdout whose middle the tool-result cut had already removed — xr-molecule-flash-m102 (+12:43)
4240
- * spent four `grep -A` round trips (~25 s) naming three causes the platform had withheld, and a
4394
+ * stdout whose middle the tool-result cut had already removed — four `grep -A` round trips
4395
+ * (~25 s) naming three causes the platform had withheld, and a
4241
4396
  * weaker turn reads the listed ones and skips the rest.
4242
4397
  *
4243
4398
  * @param omitted - The causes past the recap's budget.
@@ -4298,7 +4453,7 @@ function omittedCausesRecap(omitted, run, excluded) {
4298
4453
  * @param run - The run in flight: the command it was started with, as it was run, the directory
4299
4454
  * that command ran in, the config it loaded when the caller knows it, and the settings the
4300
4455
  * request required and the run left out (`env`) — the re-run carries them, so it never
4301
- * reproduces the configuration the run was measured under by mistake (m182).
4456
+ * reproduces the configuration the run was measured under by mistake.
4302
4457
  * @param locations - The failing `file:line`s to re-run, as the run's log printed them.
4303
4458
  * @param excluded - The test titles the request carves out: the re-run inverts them out
4304
4459
  * (`--grep-invert`) so it cannot report them again.
@@ -4332,18 +4487,18 @@ export function rerunOfRunningSuite(run, locations, excluded = []) {
4332
4487
  * The note names the cause early so the executor can START FIXING while the run keeps walking,
4333
4488
  * and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
4334
4489
  * predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
4335
- * on is a finding this note never asks to stop for. X1 flash-m76 (2026-10-16) spent 4.0 minutes
4490
+ * on is a finding this note never asks to stop for. One run spent 4.0 minutes
4336
4491
  * walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
4337
- * exactly that; X1 flash-m83 then stopped eight runs at their first repeating cause in 14.5
4492
+ * exactly that; another stopped eight runs at their first repeating cause in 14.5
4338
4493
  * minutes and never finished one, so the note must not say to stop when the run still has other
4339
- * causes to name. X1 xr-molecule-flash-m99 (2026-10-16) is the third shape: a shared duration is
4494
+ * causes to name. The third shape: a shared duration is
4340
4495
  * not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
4341
4496
  * for — never as a verdict with a stop command in it.
4342
4497
  *
4343
4498
  * The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
4344
4499
  * re-run of one of its tests is a second run against the same app and database, and the platform
4345
4500
  * refuses that ({@link suiteRunInTheWay}, the same rule its overlap hold answers with):
4346
- * xr-molecule-flash-m89 (2026-10-16) read *"Start on them now, while the run keeps walking.
4501
+ * an executor read *"Start on them now, while the run keeps walking.
4347
4502
  * `null-fields.spec.ts:53` alone reproduces the first one."* and sent exactly that re-run 23 s
4348
4503
  * later, where the hold answered *"A playwright suite is already running against this app and
4349
4504
  * database … Wait for it."* The note therefore hands over the read-and-fix to do NOW and the
@@ -4537,7 +4692,7 @@ export const PARSE_CHECKABLE = /\.(m?js|cjs|jsx|ts|tsx|mts|cts|json)$/;
4537
4692
  * Basenames TypeScript reads as JSONC: `tsconfig.json`, `tsconfig.*.json` and
4538
4693
  * the `jsconfig` counterparts. Comments and trailing commas are legal there —
4539
4694
  * `tsc` accepts them — so a strict `JSON.parse` reports a valid project config
4540
- * as broken (X0 flash-m436: a commented `tsconfig.conduit-suite.json` was
4695
+ * as broken (a commented `tsconfig.conduit-suite.json` was
4541
4696
  * answered with "every build and test fails until it does").
4542
4697
  */
4543
4698
  const TYPESCRIPT_CONFIG = /^(?:ts|js)config(?:\.[^/\\]+)?\.json$/;