@molecule/api-ai-tools 1.14.4 → 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 +211 -184
- 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 +98 -69
- package/dist/trace-artifacts.d.ts +64 -65
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +73 -75
- package/dist/types.d.ts +5 -5
- package/dist/utilities.d.ts +107 -91
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +191 -143
- 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
|
|
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
|
|
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
|
-
*
|
|
535
|
-
*
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
631
|
-
* guessed;
|
|
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.
|
|
643
|
-
*
|
|
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
|
-
*
|
|
656
|
-
* keeps them whatever its scope, targeted set included
|
|
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
|
|
759
|
-
*
|
|
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
|
|
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
|
|
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 (
|
|
1201
|
-
*
|
|
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}
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
* (
|
|
1477
|
-
*
|
|
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`
|
|
1570
|
-
* `exitCode: 0` for a run with a failure in it
|
|
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
|
|
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 (
|
|
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 `( …`
|
|
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:
|
|
1634
|
-
*
|
|
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:
|
|
1677
|
-
* the
|
|
1678
|
-
*
|
|
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
|
|
1828
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1948
|
-
*
|
|
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 `[]` (
|
|
1989
|
-
*
|
|
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,14 +2090,14 @@ 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 (
|
|
2040
|
-
*
|
|
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 (
|
|
2046
|
-
*
|
|
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
|
|
@@ -2056,8 +2110,8 @@ export function findPathArgs(pattern) {
|
|
|
2056
2110
|
* `node_modules` (see `findPruneArgs`), so the whole-tree form can only ever
|
|
2057
2111
|
* match `<root>/@molecule/…` — the scope directory IS the directory the pattern
|
|
2058
2112
|
* names. Left un-narrowed, a scoped-package pattern walked the sandbox's entire
|
|
2059
|
-
* dependency tree and hit the 10 s deadline with no result at all
|
|
2060
|
-
*
|
|
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:
|
|
2061
2115
|
* `helpers/*` is unchanged even under a `node_modules` root.
|
|
2062
2116
|
*
|
|
2063
2117
|
* @param pattern - A pattern already accepted by {@link isValidGlob}.
|
|
@@ -2094,7 +2148,7 @@ export function literalDirPrefix(pattern, root) {
|
|
|
2094
2148
|
* `[deadline_exceeded] the operation timed out: … It can be modified by passing
|
|
2095
2149
|
* 'timeoutMs' when making the request. Use '0' to disable the timeout.` — which
|
|
2096
2150
|
* names an option the executor cannot set and says nothing about the search
|
|
2097
|
-
* that died. Handing that sentence to the model is what
|
|
2151
|
+
* that died. Handing that sentence to the model is what a run
|
|
2098
2152
|
* read after `find_files` spent 10 s walking `node_modules`; the tool answers
|
|
2099
2153
|
* the deadline in its own words instead (see {@link searchTimeoutError}).
|
|
2100
2154
|
*
|
|
@@ -2175,8 +2229,8 @@ export function pathArgError(path, tool) {
|
|
|
2175
2229
|
*
|
|
2176
2230
|
* The path message is the wrong reading of it: "requires a non-empty path
|
|
2177
2231
|
* argument" says the call was understood and the path was left out, so the model
|
|
2178
|
-
* retries the call it just made instead of sending the whole call again (
|
|
2179
|
-
*
|
|
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
|
|
2180
2234
|
* missing `path` are different failures and deserve different sentences.
|
|
2181
2235
|
*
|
|
2182
2236
|
* @param input - The raw tool input.
|
|
@@ -2320,9 +2374,8 @@ export const MAX_BATCH_READ_FILES = 25;
|
|
|
2320
2374
|
* MIDDLE — the cut lands between two `"content"` values, so whole files fell
|
|
2321
2375
|
* out and the note in their place said only "N characters cut here" — while
|
|
2322
2376
|
* this tool's own note had promised each of those files that it "fits in one
|
|
2323
|
-
* call, so it is not paged":
|
|
2324
|
-
*
|
|
2325
|
-
* 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).
|
|
2326
2379
|
*
|
|
2327
2380
|
* So the CALL gets the ceiling the request gives it. Whole files are returned
|
|
2328
2381
|
* up to it; the paths that did not fit are named in `note` and listed in
|
|
@@ -2357,7 +2410,7 @@ export const MAX_READ_RETURN_CHARS = MAX_READ_CALL_CHARS - READ_RESULT_ENVELOPE_
|
|
|
2357
2410
|
/**
|
|
2358
2411
|
* A file this small is returned whole on a second window of it (paging it costs a call each).
|
|
2359
2412
|
* The same bound as {@link MAX_READ_RETURN_CHARS}: the windowed-read note promises that a
|
|
2360
|
-
* file under it "fits in ONE call", and a smaller bound here
|
|
2413
|
+
* file under it "fits in ONE call", and a smaller bound here breaks that promise.
|
|
2361
2414
|
*/
|
|
2362
2415
|
export const SMALL_FILE_WHOLE_CHARS = MAX_READ_RETURN_CHARS;
|
|
2363
2416
|
/**
|
|
@@ -2368,11 +2421,11 @@ export const SMALL_FILE_WHOLE_CHARS = MAX_READ_RETURN_CHARS;
|
|
|
2368
2421
|
* An entry document is boilerplate the reader rarely needs in full: meta tags,
|
|
2369
2422
|
* stylesheet and preload links, a `<style>` block, and a handful of `<script>`
|
|
2370
2423
|
* tags. The parts an executor acts on — the body's structure, where a script is
|
|
2371
|
-
* loaded — are all visible in an outline of one line per tag.
|
|
2372
|
-
* 63,735 chars
|
|
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,
|
|
2373
2426
|
* and the read pushed the transcript past its context cap, so the e2e specs the
|
|
2374
2427
|
* executor had just read were trimmed to summaries and it read the whole suite
|
|
2375
|
-
* again — twice
|
|
2428
|
+
* again — twice. The scaffold now keeps those
|
|
2376
2429
|
* bridges in separate files, but a template's own head is still ~5-12 KB, and a
|
|
2377
2430
|
* static-site or SSR entry can be far more.
|
|
2378
2431
|
*
|
|
@@ -2506,7 +2559,7 @@ export function truncateMiddle(s, maxLength) {
|
|
|
2506
2559
|
/**
|
|
2507
2560
|
* A test run's tally lines (` 3 failed`, ` 71 passed (4.6m)`) the cut text lost, put back at
|
|
2508
2561
|
* its end: Playwright lists every failed test after its "N failed" line, so a long list pushed
|
|
2509
|
-
* the line itself into the cut middle and the run
|
|
2562
|
+
* the line itself into the cut middle and the run reads as having no failures.
|
|
2510
2563
|
*
|
|
2511
2564
|
* @param original - The whole output.
|
|
2512
2565
|
* @param cut - The truncated output.
|
|
@@ -2621,9 +2674,8 @@ export const RUNNING_FAILURE_LINE_RE = '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\
|
|
|
2621
2674
|
* the failure lines so far (capped at {@link RUNNING_FAILURE_LINES}), then the end of the log.
|
|
2622
2675
|
*
|
|
2623
2676
|
* A Playwright `list` run prints one line per test as it finishes, so the LAST bytes of a running
|
|
2624
|
-
* log are its least informative part —
|
|
2625
|
-
*
|
|
2626
|
-
* 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
|
|
2627
2679
|
* ({@link PLAYWRIGHT_FAILURES_INLINE_ENV}), and {@link RUNNING_FAILURE_LINE_RE} already matches
|
|
2628
2680
|
* those blocks' headers and their `Error:` lines, so this slice shows the causes too.
|
|
2629
2681
|
*
|
|
@@ -2662,11 +2714,11 @@ export const RUNNING_LOG_READ_BYTES = REPEATING_CAUSE_TAIL_BYTES;
|
|
|
2662
2714
|
* The shell command whose stdout is a bounded CONTIGUOUS read of a running task's own log: the
|
|
2663
2715
|
* log's byte size, then its last {@link RUNNING_LOG_READ_BYTES} bytes.
|
|
2664
2716
|
*
|
|
2665
|
-
* {@link runningOutputSliceCommand} is the wrong read for a diagnosis
|
|
2717
|
+
* {@link runningOutputSliceCommand} is the wrong read for a diagnosis: it
|
|
2666
2718
|
* stitches the first 40 grep-selected failure lines to the last 1 000 bytes with a blank line
|
|
2667
2719
|
* between, so the tail is DISJOINT from the headers above it. Read as one log, the last numbered
|
|
2668
|
-
* header absorbs whatever the tail starts with — a clipped skipped-test title
|
|
2669
|
-
* a genuine failure
|
|
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
|
|
2670
2722
|
* neighbouring lines of one log.
|
|
2671
2723
|
*
|
|
2672
2724
|
* The size rides along so a caller can say when the window is only the log's end
|
|
@@ -2798,8 +2850,8 @@ function failureBody(block) {
|
|
|
2798
2850
|
* pair of a failed assertion.
|
|
2799
2851
|
*
|
|
2800
2852
|
* Without this, every `TimeoutError: page.press: Timeout 5000ms exceeded.` in a log is one
|
|
2801
|
-
* message, because that line names the ACTION (`page.press`) and never its subject.
|
|
2802
|
-
*
|
|
2853
|
+
* message, because that line names the ACTION (`page.press`) and never its subject. Four such
|
|
2854
|
+
* failures in one run — one waiting on
|
|
2803
2855
|
* `button:has-text("Publish Article")`, one on `.article-preview h1`, one on
|
|
2804
2856
|
* `.article-preview').first().locator('.author')`, one on a generated `h1:has-text("Test
|
|
2805
2857
|
* Article …")` — and the run's summary called them two causes while its mid-run note called them
|
|
@@ -2835,8 +2887,8 @@ const FAILURE_EVIDENCE_CHARS = 200;
|
|
|
2835
2887
|
* `Expected: <n>`, and a signature built from a `waiting for` line never carries the assertion
|
|
2836
2888
|
* values at all. Those values are what a fix round needs, and the run's stdout they came from is
|
|
2837
2889
|
* cut in the MIDDLE by the tool-result cap, so a listed cause that does not carry them sends the
|
|
2838
|
-
* executor back to the log —
|
|
2839
|
-
*
|
|
2890
|
+
* executor back to the log — four `grep -A` round trips (~25 s) for the bodies of causes the
|
|
2891
|
+
* note had already listed.
|
|
2840
2892
|
*
|
|
2841
2893
|
* @param body - The block's lines, from {@link failureBody}.
|
|
2842
2894
|
* @param message - The cause's signature: a line it already carries is not repeated.
|
|
@@ -2880,7 +2932,7 @@ function failureEvidence(body, message) {
|
|
|
2880
2932
|
* The duration is the one that matters. A suite that sets `actionTimeout: 5_000` — the X1
|
|
2881
2933
|
* realworld suite does — spends `5 s` plus teardown on every "the locator never appeared"
|
|
2882
2934
|
* failure, so `~7 s` is a property of the CONFIG, not of the cause, and four different missing
|
|
2883
|
-
* elements print the same number
|
|
2935
|
+
* elements print the same number. Two tests waiting on the same
|
|
2884
2936
|
* locator for 5 s and for 10 s wait for the same thing; two waiting on different locators do not
|
|
2885
2937
|
* become one cause by timing out together.
|
|
2886
2938
|
*
|
|
@@ -2914,7 +2966,7 @@ function failureBudgetMs(block) {
|
|
|
2914
2966
|
* The cause signature with the budget it normalized away put back: `Test timeout of <n> exceeded`
|
|
2915
2967
|
* becomes `Test timeout of 15000ms exceeded`. Every other `<n>` stays — those are the values that
|
|
2916
2968
|
* differ between tests of one cause, while the budget is the one that does not, and it is what
|
|
2917
|
-
* turns "a timeout" into "15 creates in 15 s"
|
|
2969
|
+
* turns "a timeout" into "15 creates in 15 s".
|
|
2918
2970
|
*
|
|
2919
2971
|
* @param message - The normalized signature.
|
|
2920
2972
|
* @param budgetMs - The budget the cause's first block printed.
|
|
@@ -2976,9 +3028,8 @@ function testKey(t) {
|
|
|
2976
3028
|
* A retried failure prints twice in a `list` log — its first attempt, then the same test with
|
|
2977
3029
|
* `(retry #1)` after the title — while its failure block names it without the marker. Keyed
|
|
2978
3030
|
* literally, the retry row is a test no cause claims, so it fell through to the duration arm and
|
|
2979
|
-
* was listed as a "shared duration" cause beside the proven cause it repeated:
|
|
2980
|
-
*
|
|
2981
|
-
* 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.
|
|
2982
3033
|
*
|
|
2983
3034
|
* @param t - A failed test, as a result line or a failure block spelled it.
|
|
2984
3035
|
* @returns The key a test's retry row shares with its failure block.
|
|
@@ -3018,13 +3069,13 @@ function distinctTests(tests) {
|
|
|
3018
3069
|
*
|
|
3019
3070
|
* Either way the point is the same: a suite whose failures repeat one cause spends its whole
|
|
3020
3071
|
* runtime proving that one cause, and the executor watching it can be told so at the third
|
|
3021
|
-
* failure instead of at the last.
|
|
3072
|
+
* failure instead of at the last. Fifteen tests walked in 4.0 minutes where 15.6 s
|
|
3022
3073
|
* each was the test timeout and one missing `navigate('/')` was the cause of all of them.
|
|
3023
3074
|
*
|
|
3024
3075
|
* The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
|
|
3025
|
-
* prints each failure twice (a second reporter, a retry)
|
|
3026
|
-
* tests
|
|
3027
|
-
* with one cause in hand
|
|
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.
|
|
3028
3079
|
*
|
|
3029
3080
|
* @param log - The log so far.
|
|
3030
3081
|
* @returns The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN}
|
|
@@ -3103,8 +3154,7 @@ export function repeatingFailure(log) {
|
|
|
3103
3154
|
* A body for a test the request EXCLUDES is not that evidence: its failure does not count for
|
|
3104
3155
|
* this request ({@link matchingExcludedTitles}), so a wait that handed it over as the diagnostic
|
|
3105
3156
|
* spent a model round trip and 8 k of partial log on a test the same result says to ignore —
|
|
3106
|
-
*
|
|
3107
|
-
* walking.
|
|
3157
|
+
* which a run has done twice while the platform's proof was still walking.
|
|
3108
3158
|
*
|
|
3109
3159
|
* With `finalOnly`, only a failure the run has FINISHED with counts; a pending retry waits.
|
|
3110
3160
|
*
|
|
@@ -3192,10 +3242,9 @@ function failureBlockKey(block) {
|
|
|
3192
3242
|
* retry may not have printed yet — so it waits; a command that pins `--retries=0` proves there is
|
|
3193
3243
|
* no retry to wait for.
|
|
3194
3244
|
*
|
|
3195
|
-
*
|
|
3196
|
-
*
|
|
3197
|
-
*
|
|
3198
|
-
* first walk, null-fields:71) paid off — but to yield only on this one.
|
|
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.
|
|
3199
3248
|
*
|
|
3200
3249
|
* @param args - The log's rows and blocks, the failing test's key, and whether the command
|
|
3201
3250
|
* proves the run cannot retry.
|
|
@@ -3226,8 +3275,8 @@ export const REPEATING_FAILURE_LISTED = 5;
|
|
|
3226
3275
|
*/
|
|
3227
3276
|
export const TOOL_RESULT_CHARS = 8000;
|
|
3228
3277
|
/**
|
|
3229
|
-
* The characters a finished run's cause list may use of {@link TOOL_RESULT_CHARS}.
|
|
3230
|
-
*
|
|
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
|
|
3231
3280
|
* ~200 chars and ~80 more with its two body lines, so 8 causes is ~2.3 k against the 8 k cap —
|
|
3232
3281
|
* the old 5-cause cap withheld 3 causes that the same result had room for.
|
|
3233
3282
|
*
|
|
@@ -3251,8 +3300,8 @@ export const FAILURE_CAUSE_TESTS_LISTED = 6;
|
|
|
3251
3300
|
*
|
|
3252
3301
|
* Used only where a COUNT of files is what the sentence needs; every note that names a
|
|
3253
3302
|
* test to the executor prints the path the log wrote, because that is the path its own
|
|
3254
|
-
* re-run hint names and the one the executor can paste back into a command (
|
|
3255
|
-
*
|
|
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
|
|
3256
3305
|
* over a suite path that was already right).
|
|
3257
3306
|
*
|
|
3258
3307
|
* @param file - A path as {@link specFile} returns it.
|
|
@@ -3318,7 +3367,7 @@ export const UNIVERSAL_CAUSE_FILES = 3;
|
|
|
3318
3367
|
* THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
|
|
3319
3368
|
* reading was only offered at a completed run; once it STOPS the walk, every test waited for is
|
|
3320
3369
|
* a test whose result is thrown away, and a blank page costs its per-test budget each time
|
|
3321
|
-
* (
|
|
3370
|
+
* (18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
|
|
3322
3371
|
* `pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
|
|
3323
3372
|
* needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
|
|
3324
3373
|
* no failure showing a locator RESOLVING ({@link ELEMENT_FOUND} — a blank page cannot resolve
|
|
@@ -3343,8 +3392,8 @@ const LOCATOR_WAIT = /waiting for locator\(|element\(s\) not found|toBeVisible\(
|
|
|
3343
3392
|
* of the viewport`). A blank page cannot resolve a locator, so one of these anywhere in a run
|
|
3344
3393
|
* disproves {@link emptyPageCause}.
|
|
3345
3394
|
*
|
|
3346
|
-
*
|
|
3347
|
-
*
|
|
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
|
|
3348
3397
|
* carried `locator resolved to <i class="ion-close-circled"></i>` followed by `element is not
|
|
3349
3398
|
* visible` — the page was drawn, and only an icon had no box.
|
|
3350
3399
|
*/
|
|
@@ -3391,10 +3440,10 @@ function pageErrorInLog(text) {
|
|
|
3391
3440
|
*
|
|
3392
3441
|
* This is ONE cause even though the signatures differ — each spec asks for its own first element
|
|
3393
3442
|
* (`input[name="username"]`, `a.navbar-brand`, `h1`), so {@link repeatingFailure} cannot group
|
|
3394
|
-
* them and {@link universalCause}'s signature test cannot see the run is over.
|
|
3395
|
-
*
|
|
3396
|
-
* first locator, and the note told the run to keep walking for another ~9 minutes.
|
|
3397
|
-
*
|
|
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
|
|
3398
3447
|
* `PAGEERROR No 'auth-client' provider bonded` was in the log from the first one.
|
|
3399
3448
|
*
|
|
3400
3449
|
* @param text - The log (ANSI already stripped).
|
|
@@ -3498,8 +3547,8 @@ const PLAYWRIGHT_VALUE_FLAGS = new Set([
|
|
|
3498
3547
|
* whole suite (or its selection cannot be read).
|
|
3499
3548
|
*
|
|
3500
3549
|
* {@link universalCause}'s file threshold is written for a whole-suite run, which a narrowed run
|
|
3501
|
-
* can never reach.
|
|
3502
|
-
*
|
|
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` —
|
|
3503
3552
|
* but one file is below {@link UNIVERSAL_CAUSE_FILES}, so the wait sat out its entire 280 s
|
|
3504
3553
|
* budget on a run that had already measured all it could.
|
|
3505
3554
|
*
|
|
@@ -3548,7 +3597,7 @@ export function specFilesSelected(command) {
|
|
|
3548
3597
|
*
|
|
3549
3598
|
* A command that names `file:line` tests is a re-run of tests that already failed — the shape
|
|
3550
3599
|
* {@link emptyPageCause} must not read as "the page renders nothing", because the executor is
|
|
3551
|
-
* verifying fixes and the stop throws that verdict away (
|
|
3600
|
+
* verifying fixes and the stop throws that verdict away (twice).
|
|
3552
3601
|
*
|
|
3553
3602
|
* @param command - The run's command.
|
|
3554
3603
|
* @returns The selected test references, or null when the selection is not per-test.
|
|
@@ -3606,7 +3655,7 @@ function sameTestRef(ref, where) {
|
|
|
3606
3655
|
* threshold, and nothing has passed since its first failure. The threshold is
|
|
3607
3656
|
* {@link UNIVERSAL_CAUSE_FILES} for a whole-suite run but the number of files the command
|
|
3608
3657
|
* SELECTED for a narrowed one ({@link specFilesSelected}): a one-file run can never span three,
|
|
3609
|
-
* and
|
|
3658
|
+
* and a run can wait out its whole budget on exactly that shape. An app
|
|
3610
3659
|
* that is not up, a 5xx on every request, or a selector that is missing on every page all look
|
|
3611
3660
|
* like that, and every remaining test will fail the same way — so the run's remaining minutes
|
|
3612
3661
|
* measure nothing. A CONNECTION-class cause ({@link CONNECTION_CAUSE}) needs no file span at all:
|
|
@@ -3615,8 +3664,8 @@ function sameTestRef(ref, where) {
|
|
|
3615
3664
|
*
|
|
3616
3665
|
* Anything less is NOT this: a cause that stops at one file of a multi-file run, or that leaves
|
|
3617
3666
|
* other tests passing, or that shares the run with failures of another shape, leaves the run with
|
|
3618
|
-
* something to say, and the run is the only thing that will say it.
|
|
3619
|
-
*
|
|
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
|
|
3620
3669
|
* one; three of its deploys each fixed a single cause that a finished run would have listed
|
|
3621
3670
|
* alongside the others.
|
|
3622
3671
|
*
|
|
@@ -3672,14 +3721,14 @@ export function universalCause(log, found, command) {
|
|
|
3672
3721
|
* spec-file threshold — {@link UNIVERSAL_CAUSE_FILES}, or the files a narrowed run's own
|
|
3673
3722
|
* command selects, or {@link CONNECTION_CAUSE_TESTS} tests of an app that never answered — with
|
|
3674
3723
|
* nothing passing since the first. That is the
|
|
3675
|
-
* dont-stop-runs rule.
|
|
3676
|
-
*
|
|
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
|
|
3677
3726
|
* left to name is the only thing that will name them.
|
|
3678
3727
|
* - **A PROVEN cause** is an error repeated VERBATIM. A shared DURATION is a LEAD, never a cause:
|
|
3679
3728
|
* a suite whose config sets a timeout spends that timeout on every failure that waits for
|
|
3680
3729
|
* anything, so three unrelated tests timing out together look exactly like one broken run
|
|
3681
|
-
* (`~7 s` is a property of the CONFIG, not of the cause — {@link normalizedSignature}).
|
|
3682
|
-
*
|
|
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
|
|
3683
3732
|
* shape — three `url-navigation.spec.ts` tests at ~16.2 s, one `timeout: 15_000` plus teardown
|
|
3684
3733
|
* — read the early return as "stop waiting, edit now", and voided a run that was still walking;
|
|
3685
3734
|
* the second cost a 224 s wait that ended with no result at all.
|
|
@@ -3713,12 +3762,11 @@ export function earlyReturnCause(found, universal) {
|
|
|
3713
3762
|
* extracted by molecule-dev's `excludedTestTitles`) while a run's heading carries more of it
|
|
3714
3763
|
* (`comments.spec.ts › Comments › should require login to post comment`), so containment is the
|
|
3715
3764
|
* comparison. Whitespace is normalised and case is ignored: a brief may wrap the title across a
|
|
3716
|
-
* line
|
|
3765
|
+
* line, and the reporter's own casing never has to match the brief's.
|
|
3717
3766
|
*
|
|
3718
3767
|
* Two readers used to answer this separately — the causes note (which therefore asked the
|
|
3719
3768
|
* executor to fix a test the same result said not to) and molecule-dev's exclusion notes — and
|
|
3720
|
-
*
|
|
3721
|
-
* test).
|
|
3769
|
+
* a run paid for the gap (a turn and an edit spent on the excluded test).
|
|
3722
3770
|
*
|
|
3723
3771
|
* @param heading - The text to match against: a test's title, its whole `file › describe › title`
|
|
3724
3772
|
* path, or (for a note that only has the run's output) the run's text.
|
|
@@ -3741,7 +3789,7 @@ export function matchingExcludedTitles(heading, excluded) {
|
|
|
3741
3789
|
*
|
|
3742
3790
|
* The point is that ONE run yields EVERY cause. A Playwright report prints its failures one
|
|
3743
3791
|
* after another with no statement of which of them share a cause, so a run of 19 failures is 19
|
|
3744
|
-
* bodies to read;
|
|
3792
|
+
* bodies to read; three deploys (6.7 minutes) each fixed a
|
|
3745
3793
|
* single cause, two of them found by runs stopped before they walked the rest.
|
|
3746
3794
|
*
|
|
3747
3795
|
* The signature is the cause itself where the log carries the failures' bodies (a finished run, a
|
|
@@ -3753,8 +3801,8 @@ export function matchingExcludedTitles(heading, excluded) {
|
|
|
3753
3801
|
* A test the request EXCLUDES by name is not a cause to fix: its failure does not count for
|
|
3754
3802
|
* this request ({@link matchingExcludedTitles}), so it is dropped here rather than handed to the
|
|
3755
3803
|
* executor as work — a result that names it beside "it does not count" is the contradiction
|
|
3756
|
-
*
|
|
3757
|
-
*
|
|
3804
|
+
* a run filed (the model spent a turn and an edit on the excluded test, on a page that was
|
|
3805
|
+
* passing).
|
|
3758
3806
|
*
|
|
3759
3807
|
* @param log - The log of a finished or running run.
|
|
3760
3808
|
* @param excluded - The test titles the request carves out, if the caller knows them.
|
|
@@ -3774,8 +3822,8 @@ const FLAKY_TALLY_LINE = /^\s*[1-9]\d*\s+flaky\b/;
|
|
|
3774
3822
|
*
|
|
3775
3823
|
* Playwright prints a numbered failure block for the FIRST attempt of a test that then passes on
|
|
3776
3824
|
* its retry, and also declares the test under `N flaky` — so a reader that only looks at the
|
|
3777
|
-
* blocks counts a flake as a cause to fix.
|
|
3778
|
-
*
|
|
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
|
|
3779
3827
|
* spent its next minutes re-running them one by one until three passed untouched. The
|
|
3780
3828
|
* declaration under the count is the runner's own answer, and its heading carries the same words
|
|
3781
3829
|
* as the failure block's ({@link splitTitlePath} reads both the same way).
|
|
@@ -3932,7 +3980,7 @@ function groupFailures(log, excluded, pageErrors = []) {
|
|
|
3932
3980
|
/**
|
|
3933
3981
|
* The error every test of a Playwright run reports when the run loaded no base URL: `page.goto('/')`
|
|
3934
3982
|
* has nothing to resolve against, so the browser refuses the navigation before any page loads.
|
|
3935
|
-
*
|
|
3983
|
+
* One run got ten of these and a list that called them ten causes.
|
|
3936
3984
|
*/
|
|
3937
3985
|
const NO_BASE_URL_ERROR = /Cannot navigate to invalid URL/i;
|
|
3938
3986
|
/** The browser's own `net::ERR_*`, as Playwright prints it for a document that never loaded. */
|
|
@@ -3957,7 +4005,7 @@ function everyCauseIsNavigationTimeout(causes) {
|
|
|
3957
4005
|
* command loaded no base URL. The line names the config the run should have loaded (the caller
|
|
3958
4006
|
* passes the one the platform recorded for this suite) and hands over the exact narrowed command
|
|
3959
4007
|
* with it, so the next run measures the app instead of spending another six minutes on the same
|
|
3960
|
-
* configuration mistake (
|
|
4008
|
+
* configuration mistake (one run sent four such runs in six minutes).
|
|
3961
4009
|
*
|
|
3962
4010
|
* @param causes - The run's failures, all carrying the same error.
|
|
3963
4011
|
* @param run - How the run was started, when the caller knows it.
|
|
@@ -4093,7 +4141,7 @@ const LANDMARK_WORD = {
|
|
|
4093
4141
|
* The sentence for a run whose navigational elements are all missing from pages that otherwise
|
|
4094
4142
|
* painted — the missing shared header the executor must check BEFORE rewriting auth.
|
|
4095
4143
|
*
|
|
4096
|
-
*
|
|
4144
|
+
* A run had four failures on `/` — `a[href="/profile/…"]`,
|
|
4097
4145
|
* `a.navbar-brand`, `nav a[href="/"]`, `nav .user-pic` — while every `error-context.md` snapshot
|
|
4098
4146
|
* showed the feed, sidebar and footer with no navigation at all. `Home.tsx` imported and
|
|
4099
4147
|
* re-exported `Navbar` but never rendered it, and `App.tsx` mounted `<Home />` in a bare route.
|
|
@@ -4187,7 +4235,7 @@ export function failureCausesNote(log, excluded = [], run, pageErrors = [],
|
|
|
4187
4235
|
* Spec files whose selector-contract rows an edit broke since the last whole walk, when the
|
|
4188
4236
|
* caller knows them: the narrowed re-run names them beside the failing tests. A green re-run of
|
|
4189
4237
|
* the failure alone would certify a tree it never measured, and the specs selecting the
|
|
4190
|
-
* element the edit removed were never walked (
|
|
4238
|
+
* element the edit removed were never walked (a real tree shipped that).
|
|
4191
4239
|
*/
|
|
4192
4240
|
regressedSpecs = []) {
|
|
4193
4241
|
const { causes, ignored, flaky } = groupFailures(log, excluded, pageErrors);
|
|
@@ -4303,9 +4351,9 @@ regressedSpecs = []) {
|
|
|
4303
4351
|
* failure's two most useful body lines underneath.
|
|
4304
4352
|
*
|
|
4305
4353
|
* The path is the one the log itself wrote, never a shortened one: it is relative to the
|
|
4306
|
-
* command's own cwd, so it is the form the executor can paste back (
|
|
4307
|
-
* `e2e/auth.spec.ts:19` as the suite's own stale starter spec,
|
|
4308
|
-
* it
|
|
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).
|
|
4309
4357
|
*
|
|
4310
4358
|
* @param cause - The cause to write.
|
|
4311
4359
|
* @param index - Its position in the recap (0-based; printed 1-based).
|
|
@@ -4343,8 +4391,8 @@ function failureCauseLine(cause, index) {
|
|
|
4343
4391
|
* found at, and the ONE command that prints their bodies.
|
|
4344
4392
|
*
|
|
4345
4393
|
* Without it the old note said "… and 3 more causes" and left the executor to find them in a
|
|
4346
|
-
* stdout whose middle the tool-result cut had already removed —
|
|
4347
|
-
*
|
|
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
|
|
4348
4396
|
* weaker turn reads the listed ones and skips the rest.
|
|
4349
4397
|
*
|
|
4350
4398
|
* @param omitted - The causes past the recap's budget.
|
|
@@ -4405,7 +4453,7 @@ function omittedCausesRecap(omitted, run, excluded) {
|
|
|
4405
4453
|
* @param run - The run in flight: the command it was started with, as it was run, the directory
|
|
4406
4454
|
* that command ran in, the config it loaded when the caller knows it, and the settings the
|
|
4407
4455
|
* request required and the run left out (`env`) — the re-run carries them, so it never
|
|
4408
|
-
* reproduces the configuration the run was measured under by mistake
|
|
4456
|
+
* reproduces the configuration the run was measured under by mistake.
|
|
4409
4457
|
* @param locations - The failing `file:line`s to re-run, as the run's log printed them.
|
|
4410
4458
|
* @param excluded - The test titles the request carves out: the re-run inverts them out
|
|
4411
4459
|
* (`--grep-invert`) so it cannot report them again.
|
|
@@ -4439,18 +4487,18 @@ export function rerunOfRunningSuite(run, locations, excluded = []) {
|
|
|
4439
4487
|
* The note names the cause early so the executor can START FIXING while the run keeps walking,
|
|
4440
4488
|
* and it hands a stop command over only when {@link earlyReturnCause} says so — the SAME
|
|
4441
4489
|
* predicate the wait uses to decide whether to return early, so a finding the wait keeps waiting
|
|
4442
|
-
* on is a finding this note never asks to stop for.
|
|
4490
|
+
* on is a finding this note never asks to stop for. One run spent 4.0 minutes
|
|
4443
4491
|
* walking 15 tests whose one cause was certain at the third, 47 s in, and the note answers
|
|
4444
|
-
* exactly that;
|
|
4492
|
+
* exactly that; another stopped eight runs at their first repeating cause in 14.5
|
|
4445
4493
|
* minutes and never finished one, so the note must not say to stop when the run still has other
|
|
4446
|
-
* causes to name.
|
|
4494
|
+
* causes to name. The third shape: a shared duration is
|
|
4447
4495
|
* not a cause at all, so a lead reaches the executor as a note on a result it is still waiting
|
|
4448
4496
|
* for — never as a verdict with a stop command in it.
|
|
4449
4497
|
*
|
|
4450
4498
|
* The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
|
|
4451
4499
|
* re-run of one of its tests is a second run against the same app and database, and the platform
|
|
4452
4500
|
* refuses that ({@link suiteRunInTheWay}, the same rule its overlap hold answers with):
|
|
4453
|
-
*
|
|
4501
|
+
* an executor read *"Start on them now, while the run keeps walking.
|
|
4454
4502
|
* `null-fields.spec.ts:53` alone reproduces the first one."* and sent exactly that re-run 23 s
|
|
4455
4503
|
* later, where the hold answered *"A playwright suite is already running against this app and
|
|
4456
4504
|
* database … Wait for it."* The note therefore hands over the read-and-fix to do NOW and the
|
|
@@ -4644,7 +4692,7 @@ export const PARSE_CHECKABLE = /\.(m?js|cjs|jsx|ts|tsx|mts|cts|json)$/;
|
|
|
4644
4692
|
* Basenames TypeScript reads as JSONC: `tsconfig.json`, `tsconfig.*.json` and
|
|
4645
4693
|
* the `jsconfig` counterparts. Comments and trailing commas are legal there —
|
|
4646
4694
|
* `tsc` accepts them — so a strict `JSON.parse` reports a valid project config
|
|
4647
|
-
* as broken (
|
|
4695
|
+
* as broken (a commented `tsconfig.conduit-suite.json` was
|
|
4648
4696
|
* answered with "every build and test fails until it does").
|
|
4649
4697
|
*/
|
|
4650
4698
|
const TYPESCRIPT_CONFIG = /^(?:ts|js)config(?:\.[^/\\]+)?\.json$/;
|