@molecule/api-ai-tools 1.9.18 → 1.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-18T15:48:52.035Z
6
+ Generated: 2026-10-18T20:46:28.562Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -66,7 +66,9 @@ One statement or pipeline stage of a shell command: what it runs, and with what.
66
66
  interface CommandSegment {
67
67
  /**
68
68
  * The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
69
- * the shell would run once whatever wrappers stood in front of it are taken away.
69
+ * the shell would run once whatever wrappers stood in front of it are taken away. When a
70
+ * wrapper runs a tool's own CLI by file, this is the tool's name — `node
71
+ * <path>/node_modules/.bin/playwright test` is a `playwright` invocation, not a `node` one.
70
72
  */
71
73
  program: string
72
74
  /** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
@@ -284,9 +286,13 @@ A cutting stage that follows the runner, and the command with the whole pipe rem
284
286
 
285
287
  ```typescript
286
288
  interface RunnerOutputFilter {
287
- /** The stage exactly as the runner's command wrote it — `tail -6`. */
289
+ /**
290
+ * The stage exactly as the runner's command wrote it — `tail -6`. When several cutting filters
291
+ * follow the runner, this is the whole trailing chain as written, pipes included:
292
+ * `grep -E "Failure|actual" | head -40`.
293
+ */
288
294
  stage: string
289
- /** The pipe as a sentence names it — `| tail -6`. */
295
+ /** The pipe as a sentence names it — `| tail -6`, or the whole chain `| grep … | head -40`. */
290
296
  pipe: string
291
297
  /**
292
298
  * The command with the pipe and the stage removed — `hurl --variable uid=… --test /tmp/t.hurl
@@ -511,6 +517,17 @@ interface TraceAction {
511
517
  pageId?: string
512
518
  /** The frame the action ran in, from the same snapshot. */
513
519
  frameId?: string
520
+ /**
521
+ * The selector a `fill`/`type`/`selectOption` acted on (`textarea[name="bio"]`), from the
522
+ * trace's own params. Kept so the note can compare what the test typed with what the page
523
+ * submitted (m173: the field it typed into was sent empty).
524
+ */
525
+ selector?: string
526
+ /**
527
+ * The value a `fill`/`type`/`selectOption` typed or chose, from the trace's own params —
528
+ * absent when the action carried none (a `selectOption` by label only).
529
+ */
530
+ value?: string
514
531
  }
515
532
  ```
516
533
 
@@ -1699,10 +1716,13 @@ function commandRunsNoTests(command: string): boolean
1699
1716
 
1700
1717
  The output filter that follows the runner in `command`, or null when there is none.
1701
1718
 
1702
- Only a stage of the runner's OWN pipeline counts (its separator is `|`), and only when it is the
1703
- LAST stage of that pipeline: a `| grep … | wc -l` chain would leave a dangling pipe if the
1704
- middle stage were cut out, so a chain is not answered here at all. A filter before the runner
1705
- (`cat run.log | tail -20 && hurl …`) reads something else's output and is not this.
1719
+ Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
1720
+ one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
1721
+ the run piped into the second. Every stage after the runner must be a cutting filter and the
1722
+ chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
1723
+ here, because removing the cutting stages alone would still leave the failure hidden. A filter
1724
+ before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
1725
+ this.
1706
1726
 
1707
1727
  ```typescript
1708
1728
  function outputFilterAfterRunner(
@@ -1888,6 +1908,10 @@ invocation — same directory, same settings — with the named `file:line`s in
1888
1908
  suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
1889
1909
  `--retries=0` set (a re-run of a known failure does not need to pay for retries).
1890
1910
 
1911
+ The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
1912
+ the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
1913
+ overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
1914
+
1891
1915
  `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
1892
1916
  carries it whenever the command itself does not, so a run that dropped `--config` cannot be
1893
1917
  handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
@@ -2226,6 +2250,25 @@ function stalledDocumentSubresource(request: TraceRequest): boolean
2226
2250
 
2227
2251
  **Returns:** True when the request is a document subresource that never finished.
2228
2252
 
2253
+ #### `isCredentialExchangePath(path, authEndpoints)`
2254
+
2255
+ Whether a request PATH asks the app to mint a credential — a login, a registration, a token
2256
+ endpoint — as opposed to a protected resource that presents one.
2257
+
2258
+ This is the path half of {@link isCredentialExchange}, exported so a caller holding only a
2259
+ `METHOD path` reads the same classification: a Hurl failing entry says
2260
+ `POST /api/users/login` and nothing about the body, and the whole-suite note must not explain
2261
+ a wrong-password login that answered 200 as a cookie-jar effect (xr-molecule-flash-m176).
2262
+
2263
+ ```typescript
2264
+ function isCredentialExchangePath(path: string, authEndpoints?: readonly string[]): boolean
2265
+ ```
2266
+
2267
+ - `path` — The request path (`/api/users/login`); a query or fragment is ignored.
2268
+ - `authEndpoints` — The endpoints the app's auth config names (`/users/login`).
2269
+
2270
+ **Returns:** True when the path names a credential exchange.
2271
+
2229
2272
  #### `failureTraceNote(items, target, earlierFailures, apiPrefixes, report)`
2230
2273
 
2231
2274
  The note a failed suite run's result carries: what each failing test's page actually sent, and
@@ -4026,11 +4069,22 @@ const UNIVERSAL_CAUSE_FILES: 3
4026
4069
  #### `EMPTY_PAGE_CAUSE_TESTS`
4027
4070
 
4028
4071
  How many tests a run must have walked — with nothing passing since its first failure — before
4029
- "the page renders nothing" can be its one cause. A handful of failures is a handful of bugs;
4030
- a page that never painted fails every spec that reaches it, and m141's walk had 36 by +20:30.
4031
-
4032
- ```typescript
4033
- const EMPTY_PAGE_CAUSE_TESTS: 15
4072
+ "the page renders nothing" can be its one cause.
4073
+
4074
+ THREE, and the number is the cost of the alternative. The old floor of 15 was chosen when the
4075
+ reading was only offered at a completed run; once it STOPS the walk, every test waited for is
4076
+ a test whose result is thrown away, and a blank page costs its per-test budget each time
4077
+ (m177: 18 tests, ~4.2 minutes, on a page whose thrown error was visible in the first
4078
+ `pageerror`). Three is the smallest count that is not one page's own bug: the cause additionally
4079
+ needs three DISTINCT spec files (`UNIVERSAL_CAUSE_FILES`, or the files a narrowed run selected),
4080
+ each spec waiting for its OWN first element, and no test passing after the first failure. A page
4081
+ that renders misses elements, but three unrelated specs in three files failing at three
4082
+ different first locators with nothing passing in between is the page, not three bugs. At the
4083
+ suite's own 5 s action timeout that verdict lands ~20 s into the walk, so the early stop costs
4084
+ about one test's worth of time instead of fifteen.
4085
+
4086
+ ```typescript
4087
+ const EMPTY_PAGE_CAUSE_TESTS: 3
4034
4088
  ```
4035
4089
 
4036
4090
  #### `CONNECTION_CAUSE_TESTS`
@@ -36,7 +36,9 @@
36
36
  export interface CommandSegment {
37
37
  /**
38
38
  * The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
39
- * the shell would run once whatever wrappers stood in front of it are taken away.
39
+ * the shell would run once whatever wrappers stood in front of it are taken away. When a
40
+ * wrapper runs a tool's own CLI by file, this is the tool's name — `node
41
+ * <path>/node_modules/.bin/playwright test` is a `playwright` invocation, not a `node` one.
40
42
  */
41
43
  program: string;
42
44
  /** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
@@ -1 +1 @@
1
- {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAqND;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAmjBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AAwFD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
1
+ {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AA8RD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAwkBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA2FD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
@@ -68,6 +68,75 @@ const LAUNCHERS = new Set([
68
68
  ]);
69
69
  /** The shells whose `-c '<command>'` argument is a command of its own. */
70
70
  const SHELLS = new Set(['ash', 'bash', 'dash', 'ksh', 'sh', 'zsh']);
71
+ /**
72
+ * Node, which is a wrapper only when the script it runs IS a tool's own CLI — never in general:
73
+ * `node script.mjs` RUNS that script, so the parser's answer for it stays `node`.
74
+ *
75
+ * The Playwright runner is spelled this way whenever a project reaches it by file instead of by
76
+ * the `.bin` shim: xr-molecule-flash-m179 (+17:51) ran
77
+ * `node /workspace/node_modules/.bin/playwright test` after `npx playwright test` in every
78
+ * earlier call, and no reader recognised it — the run got no `--trace`, no suite-budget pin and
79
+ * no proof credit, and the platform spent the rest of the turn re-walking a suite it had already
80
+ * walked green.
81
+ */
82
+ const NODE = new Set(['node']);
83
+ /**
84
+ * The Playwright CLI, as the path operand of a `node` invocation: the package's `.bin` shim, or
85
+ * the `cli.js` a package installs (`playwright/cli.js`, `@playwright/test/cli.js`).
86
+ */
87
+ const PLAYWRIGHT_CLI = /(?:^|\/)node_modules\/(?:\.bin\/playwright|(?:playwright|@playwright\/test)\/cli\.(?:js|mjs|cjs))$/;
88
+ /**
89
+ * The runner a `node` script operand IS, or null when the operand is any other script.
90
+ *
91
+ * @param word - The script operand, as written.
92
+ * @returns The runner's name (`playwright`), or null.
93
+ */
94
+ function nodeScriptRunner(word) {
95
+ return PLAYWRIGHT_CLI.test(unquoted(word)) ? 'playwright' : null;
96
+ }
97
+ /**
98
+ * Node's own value-taking options: the ones that consume the word after them, so the script
99
+ * operand is not mistaken for a value (`node -r tsx/cli.js …`). Node also accepts `--flag=value`.
100
+ */
101
+ const NODE_VALUE_FLAGS = new Set([
102
+ '-e',
103
+ '--eval',
104
+ '-p',
105
+ '--print',
106
+ '-r',
107
+ '--require',
108
+ '--import',
109
+ '--loader',
110
+ '--experimental-loader',
111
+ '--input-type',
112
+ '--conditions',
113
+ '--title',
114
+ ]);
115
+ /**
116
+ * The word a `node` invocation runs as its script: the first operand after node's own options,
117
+ * with `--` ending the options. Null when node runs no file (`node --test`, `node -e '<code>'`).
118
+ *
119
+ * A wrong guess here is harmless: the operand is then checked against {@link PLAYWRIGHT_CLI}, so
120
+ * an ordinary script (`node probe.mjs`) is never read as a runner.
121
+ *
122
+ * @param words - The segment's words.
123
+ * @param from - The index just after `node`.
124
+ * @returns The script operand's index, or null.
125
+ */
126
+ function nodeScriptIndex(words, from) {
127
+ let at = from;
128
+ for (let guard = 0; guard < MAX_WRAPPERS && at < words.length; guard++) {
129
+ const word = words[at] ?? '';
130
+ if (!word.startsWith('-'))
131
+ return at;
132
+ const flag = unquoted(word);
133
+ // `--` ends node's options; the script is the next word.
134
+ if (flag === '--')
135
+ return at + 1 < words.length ? at + 1 : null;
136
+ at += flag.includes('=') || !NODE_VALUE_FLAGS.has(flag) ? 1 : 2;
137
+ }
138
+ return null;
139
+ }
71
140
  /** Package runners that run the program named after them (`npx playwright test`). */
72
141
  const PACKAGE_RUNNERS = new Set(['bunx', 'npx', 'pnpx']);
73
142
  /** Package managers whose sub-command runs a package's binary (`bun x`, `pnpm dlx`, `npm exec`). */
@@ -345,6 +414,8 @@ function nestedCommand(words, from) {
345
414
  * @returns The index of the word in command position (one past the end when the segment runs
346
415
  * nothing), the command string of a `sh -c '<command>'` this segment turns out to be, and the
347
416
  * seconds its own `timeout` wrapper allows that command (`timeout 280 hurl …` → 280) or null.
417
+ * `program` names what runs when that is not the word itself's name — `node <path>/playwright`
418
+ * is a `playwright` invocation, not a `node` one ({@link nodeScriptRunner}).
348
419
  */
349
420
  function commandStart(words) {
350
421
  let at = 0;
@@ -364,14 +435,14 @@ function commandStart(words) {
364
435
  if (SHELLS.has(name)) {
365
436
  const nested = nestedCommand(words, at + 1);
366
437
  if (nested !== null)
367
- return { at, nested, timeoutSeconds };
438
+ return { at, nested, timeoutSeconds, program: null };
368
439
  at += 1 + flagWords(words, at + 1, name);
369
440
  continue;
370
441
  }
371
442
  if (PACKAGE_RUNNERS.has(name)) {
372
443
  const runner = packageRunnerOptions(words, at + 1, name);
373
444
  if (runner.nested !== null)
374
- return { at, nested: runner.nested, timeoutSeconds };
445
+ return { at, nested: runner.nested, timeoutSeconds, program: null };
375
446
  at += 1 + runner.count;
376
447
  continue;
377
448
  }
@@ -380,19 +451,35 @@ function commandStart(words) {
380
451
  const subcommand = unquoted(words[at + 1] ?? '');
381
452
  const runner = packageRunnerOptions(words, at + 2, `${name} ${subcommand}`);
382
453
  if (runner.nested !== null)
383
- return { at, nested: runner.nested, timeoutSeconds };
454
+ return { at, nested: runner.nested, timeoutSeconds, program: null };
384
455
  at += 2 + runner.count;
385
456
  continue;
386
457
  }
458
+ if (name === 'yarn' && unquoted(words[at + 1] ?? '') === 'playwright') {
459
+ // Yarn classic runs the binary named after it (`yarn playwright test`); Yarn 2+ spells the
460
+ // same thing `yarn exec`/`yarn dlx` ({@link PACKAGE_RUNNER_SUBCOMMANDS}). Only the runner's
461
+ // own name is read through it — `yarn test` is the project's own script, not a binary.
462
+ at += 1;
463
+ continue;
464
+ }
465
+ if (NODE.has(name)) {
466
+ // `node <path>/playwright test` is a Playwright invocation; `node probe.mjs` runs the
467
+ // project's own script and stays a `node` statement (`program: null`).
468
+ const script = nodeScriptIndex(words, at + 1);
469
+ const runner = script === null ? null : nodeScriptRunner(words[script] ?? '');
470
+ return runner === null
471
+ ? { at, nested: null, timeoutSeconds, program: null }
472
+ : { at: script ?? at, nested: null, timeoutSeconds, program: runner };
473
+ }
387
474
  if (!LAUNCHERS.has(name))
388
- return { at, nested: null, timeoutSeconds };
475
+ return { at, nested: null, timeoutSeconds, program: null };
389
476
  at += 1 + flagWords(words, at + 1, name);
390
477
  if (name === 'timeout' && DURATION.test(words[at] ?? '')) {
391
478
  timeoutSeconds = durationSeconds(words[at] ?? '');
392
479
  at += 1;
393
480
  }
394
481
  }
395
- return { at, nested: null, timeoutSeconds };
482
+ return { at, nested: null, timeoutSeconds, program: null };
396
483
  }
397
484
  /**
398
485
  * A command with every heredoc BODY blanked to spaces, so its length and every offset in it are
@@ -993,7 +1080,9 @@ function locatedSegmentsOf(command, depth) {
993
1080
  assignmentOffset: part.start + leading.end,
994
1081
  programOffset: words[start.at] === undefined ? null : part.start + (wordOffset(part.text, start.at) ?? 0),
995
1082
  };
996
- const program = words[start.at] === undefined ? '' : programName(words[start.at] ?? '');
1083
+ // The runner the word RUNS names, when that is not the word's own name (`node <path>/playwright`
1084
+ // is a `playwright` invocation, xr-molecule-flash-m179).
1085
+ const program = words[start.at] === undefined ? '' : (start.program ?? programName(words[start.at] ?? ''));
997
1086
  const args = words.slice(start.at + 1);
998
1087
  const redirectTarget = redirectTargetOf(part.text, part.depths, program, args);
999
1088
  if (start.nested !== null) {
@@ -19,9 +19,13 @@
19
19
  import { type CommandSegment } from './command-position.js';
20
20
  /** A cutting stage that follows the runner, and the command with the whole pipe removed. */
21
21
  export interface RunnerOutputFilter {
22
- /** The stage exactly as the runner's command wrote it — `tail -6`. */
22
+ /**
23
+ * The stage exactly as the runner's command wrote it — `tail -6`. When several cutting filters
24
+ * follow the runner, this is the whole trailing chain as written, pipes included:
25
+ * `grep -E "Failure|actual" | head -40`.
26
+ */
23
27
  stage: string;
24
- /** The pipe as a sentence names it — `| tail -6`. */
28
+ /** The pipe as a sentence names it — `| tail -6`, or the whole chain `| grep … | head -40`. */
25
29
  pipe: string;
26
30
  /**
27
31
  * The command with the pipe and the stage removed — `hurl --variable uid=… --test /tmp/t.hurl
@@ -33,10 +37,13 @@ export interface RunnerOutputFilter {
33
37
  /**
34
38
  * The output filter that follows the runner in `command`, or null when there is none.
35
39
  *
36
- * Only a stage of the runner's OWN pipeline counts (its separator is `|`), and only when it is the
37
- * LAST stage of that pipeline: a `| grep … | wc -l` chain would leave a dangling pipe if the
38
- * middle stage were cut out, so a chain is not answered here at all. A filter before the runner
39
- * (`cat run.log | tail -20 && hurl …`) reads something else's output and is not this.
40
+ * Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
41
+ * one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
42
+ * the run piped into the second. Every stage after the runner must be a cutting filter and the
43
+ * chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
44
+ * here, because removing the cutting stages alone would still leave the failure hidden. A filter
45
+ * before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
46
+ * this.
40
47
  *
41
48
  * @param command - The command as the executor sent it.
42
49
  * @param runs - Whether a segment is the runner this run is about (the caller knows its own kind:
@@ -1 +1 @@
1
- {"version":3,"file":"output-filter.d.ts","sourceRoot":"","sources":["../src/output-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,uBAAuB,CAAA;AASnF,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,EAC1C,UAAU,SAAI,GACb,kBAAkB,GAAG,IAAI,CAsB3B"}
1
+ {"version":3,"file":"output-filter.d.ts","sourceRoot":"","sources":["../src/output-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAA;AAS9B,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,+FAA+F;IAC/F,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,EAC1C,UAAU,SAAI,GACb,kBAAkB,GAAG,IAAI,CAmC3B"}
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * @module
18
18
  */
19
- import { locatedCommandSegments } from './command-position.js';
19
+ import { locatedCommandSegments, } from './command-position.js';
20
20
  /**
21
21
  * The programs whose job is to re-shape what an earlier stage printed and that can DROP lines —
22
22
  * the reader's own cut, never part of the run. `tee`, `sort` and `wc` are output plumbing too, but
@@ -26,10 +26,13 @@ const CUTTING_FILTERS = new Set(['awk', 'cut', 'grep', 'head', 'sed', 'tail']);
26
26
  /**
27
27
  * The output filter that follows the runner in `command`, or null when there is none.
28
28
  *
29
- * Only a stage of the runner's OWN pipeline counts (its separator is `|`), and only when it is the
30
- * LAST stage of that pipeline: a `| grep … | wc -l` chain would leave a dangling pipe if the
31
- * middle stage were cut out, so a chain is not answered here at all. A filter before the runner
32
- * (`cat run.log | tail -20 && hurl …`) reads something else's output and is not this.
29
+ * Only stages of the runner's OWN pipeline count (their separator is `|`), and they are read as
30
+ * one trailing CHAIN: `| grep … | head -40` is two cuts, and removing only the first would leave
31
+ * the run piped into the second. Every stage after the runner must be a cutting filter and the
32
+ * chain must run to the pipeline's end — a pipeline that continues into `wc -l` is not answered
33
+ * here, because removing the cutting stages alone would still leave the failure hidden. A filter
34
+ * before the runner (`cat run.log | tail -20 && hurl …`) reads something else's output and is not
35
+ * this.
33
36
  *
34
37
  * @param command - The command as the executor sent it.
35
38
  * @param runs - Whether a segment is the runner this run is about (the caller knows its own kind:
@@ -51,23 +54,39 @@ export function outputFilterAfterRunner(command, runs, occurrence = 1) {
51
54
  runners.push(index);
52
55
  });
53
56
  const runnerAt = runners[occurrence - 1];
54
- if (runnerAt === undefined || runnerAt + 1 >= entries.length)
57
+ if (runnerAt === undefined)
55
58
  return null;
56
59
  const runner = entries[runnerAt];
57
- const filter = entries[runnerAt + 1];
58
- // The stage must be fed by the runner's own pipe, and nothing may be piped out of the filter:
59
- // those are the two positions where cutting it out leaves a command that still runs the run.
60
- if (!runner || !filter || filter.separator.trim() !== '|')
60
+ if (!runner || runner.end === null)
61
61
  return null;
62
- if (!filter.segment || !CUTTING_FILTERS.has(filter.segment.program))
62
+ // The chain is every stage fed by the runner's own pipe, until the pipeline ends. Each one has
63
+ // to be a cutting filter: a stage that does not drop lines ends the chain, and the chain then
64
+ // stops before the pipeline's end — which is not answerable (`| grep … | wc -l` would still
65
+ // hide the assertion in a count).
66
+ const chain = [];
67
+ for (let index = runnerAt + 1; index < entries.length; index++) {
68
+ const entry = entries[index];
69
+ if (!entry || entry.separator.trim() !== '|')
70
+ break;
71
+ if (!entry.segment || !CUTTING_FILTERS.has(entry.segment.program))
72
+ break;
73
+ chain.push(entry);
74
+ if (entry.terminator.trim() !== '|')
75
+ break;
76
+ }
77
+ const first = chain[0];
78
+ const last = chain[chain.length - 1];
79
+ if (!first || !last)
63
80
  return null;
64
- if (filter.terminator.trim() === '|')
81
+ // A pipeline that continues past the chain means a non-cutting stage sits after it.
82
+ if (last.terminator.trim() === '|')
65
83
  return null;
66
- if (runner.end === null || filter.start === null || filter.end === null)
84
+ if (first.start === null || last.end === null)
67
85
  return null;
86
+ const stage = command.slice(first.start, last.end);
68
87
  return {
69
- stage: command.slice(filter.start, filter.end),
70
- pipe: `| ${command.slice(filter.start, filter.end)}`,
71
- command: `${command.slice(0, runner.end)}${command.slice(filter.end)}`,
88
+ stage,
89
+ pipe: `| ${stage}`,
90
+ command: `${command.slice(0, runner.end)}${command.slice(last.end)}`,
72
91
  };
73
92
  }
@@ -83,6 +83,10 @@ export declare function withSuiteConfig(command: string, config: string | null |
83
83
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
84
84
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
85
85
  *
86
+ * The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
87
+ * the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
88
+ * overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
89
+ *
86
90
  * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
87
91
  * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
88
92
  * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
@@ -1 +1 @@
1
- {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,CA0CR;AAgHD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAYtF"}
1
+ {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS1F;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAClB,MAAM,CA8CR;AAkHD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AA2FD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAmCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAYtF"}