@molecule/api-ai-tools 1.8.1 → 1.8.3

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.
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @module
28
28
  */
29
- import { commandSegments } from './command-position.js';
29
+ import { locatedCommandSegments } from './command-position.js';
30
30
  import { isDiagnosisRun } from './suite-overlap.js';
31
31
  import { isPlaywrightRunCommand } from './utilities.js';
32
32
  /** How many traces one read reports, newest first. */
@@ -380,31 +380,36 @@ function namesTraceMode(command) {
380
380
  * shell parse can place.
381
381
  *
382
382
  * The flag has to land INSIDE the Playwright step, not at the end of the whole command: a run
383
- * Flash sends almost every time ends `… 2>&1 | tail -60` or `… > log 2>&1; echo "exit=$?";
384
- * grep -c "" log`, and a flag appended after those is an operand of `tail`/`grep` — GNU `tail`
385
- * refuses it outright (`tail: option used in invalid context -- 6`) and Playwright never sees it
383
+ * Flash sends almost every time ends `… 2>&1 | tail -60` or `… > log 2>&1; echo "exit=$?"; grep -c
384
+ * "" log`, and a flag appended after those is an operand of `tail`/`grep` — GNU `tail` refuses it
385
+ * outright (`tail: option used in invalid context -- 6`) and Playwright never sees it
386
386
  * (xr-molecule-flash-m102 +07:40 → +07:53).
387
387
  *
388
- * The step is found by the package's own shell parse ({@link commandSegments}), the one every
389
- * runner classifier reads: it answers which SEGMENT runs `playwright test` in command position,
390
- * so a `playwright test` named in an argument (`grep -n "playwright test" e2e/README.md`) is not
391
- * a run and is left alone. Each segment's text is its program and arguments re-joined with single
392
- * spaces, so its offset in the command is found by locating that text, walking the segments in
393
- * order. A segment whose text cannot be located verbatim (irregular whitespace) ends the walk —
394
- * null, and the caller adds nothing rather than splicing into a guess.
388
+ * The step is found by the package's own shell parse ({@link locatedCommandSegments}), the one
389
+ * every runner classifier reads: it answers which STATEMENT runs `playwright test` in command
390
+ * position, and where that statement's program word sits in the command. The offset is the parse's
391
+ * own token position, never a search for the re-joined text — that text drops grouping braces and
392
+ * single-space-joins the words, so a command carrying a heredoc body or a `"quoted"` program found
393
+ * nothing at all and the flag was not added.
395
394
  *
396
395
  * @param command - The shell command.
397
396
  * @returns The offset just after `test`, or null.
398
397
  */
399
398
  function playwrightTestWordEnd(command) {
400
- let cursor = 0;
401
- for (const segment of commandSegments(command)) {
402
- const at = command.indexOf(segment.text, cursor);
403
- if (at < 0)
404
- return null;
405
- cursor = at + segment.text.length;
406
- if (/^playwright\s+test\b/.test(segment.text))
407
- return at + 'playwright test'.length;
399
+ for (const located of locatedCommandSegments(command)) {
400
+ const segment = located.segment;
401
+ if (!segment || located.programOffset === null)
402
+ continue;
403
+ if (!/^playwright\s+test\b/.test(segment.text))
404
+ continue;
405
+ // `playwright` and `test` are the statement's own first two words; the flag follows `test`.
406
+ const words = new RegExp(/\S+/.source, 'g');
407
+ words.lastIndex = located.programOffset;
408
+ const program = words.exec(command);
409
+ if (!program)
410
+ continue;
411
+ const second = words.exec(command);
412
+ return second ? second.index + second[0].length : null;
408
413
  }
409
414
  return null;
410
415
  }
@@ -222,18 +222,44 @@ export interface SuiteFilterStrip {
222
222
  * @returns The rewritten command and the stages dropped.
223
223
  */
224
224
  export declare function stripTrailingSuiteFilters(command: string): SuiteFilterStrip;
225
+ /** Whether a finished run's exit code belongs to a stage after the suite runner. */
226
+ export interface SuiteRunPipelinePlacement {
227
+ /**
228
+ * True when a pipe this tool must not drop still follows the suite runner: the code in the
229
+ * result is that stage's, not the run's.
230
+ */
231
+ piped: boolean;
232
+ /**
233
+ * True when the suite (or a stage of its pipeline) sits inside a nested `sh -c '<command>'`
234
+ * string, so the parse cannot place it in the command's own text. The caller then says so
235
+ * plainly instead of implying a stage it can point at — and keeps the command's own code when
236
+ * no pipe follows the runner, because on that command the pipeline's last stage IS the run.
237
+ */
238
+ unplaced: boolean;
239
+ }
240
+ /**
241
+ * Where the suite runner's exit code comes from: the runner itself, or a stage piped after it.
242
+ *
243
+ * {@link suiteRunPipedAfterRunner}'s one question, with the honest third answer kept apart. The
244
+ * code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
245
+ * code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
246
+ * any run whose statements it could not place in the text, which one heredoc body was enough to
247
+ * cause: on X1 xr-molecule-flash-m105 (+08:12) a config written in the same `exec_command` made
248
+ * the run come back with no exit code at all, and the note under it claimed a stage after the
249
+ * runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
250
+ * "unplaced" is the rare, named case rather than a guess dressed as an answer.
251
+ *
252
+ * @param command - The shell command, after the streaming rewrite when one was applied.
253
+ * @returns Whether a stage follows the runner, and whether the run could be placed at all.
254
+ */
255
+ export declare function suiteRunPipelinePlacement(command: string): SuiteRunPipelinePlacement;
225
256
  /**
226
257
  * Whether the command's last step runs a suite whose output still passes through a stage after
227
258
  * the runner — one {@link stripTrailingSuiteFilters} could not prove is output-only (`| tee log`,
228
259
  * `| xargs …`). The pipeline's exit status is that stage's, not the run's.
229
260
  *
230
- * A command whose stages cannot be placed in the text (irregular whitespace inside the run) is
231
- * reported as piped too: whether a pipe follows the runner is then UNKNOWN, and an unknown exit
232
- * code that gets trusted is a failing run read as a pass — the failure this rule exists to stop.
233
- *
234
261
  * @param command - The shell command, after the streaming rewrite when one was applied.
235
- * @returns True when a pipe this tool must not drop still follows the suite runner, or when the
236
- * command runs a suite the parse cannot place.
262
+ * @returns True when a pipe this tool must not drop still follows the suite runner.
237
263
  */
238
264
  export declare function suiteRunPipedAfterRunner(command: string): boolean;
239
265
  /**
@@ -916,9 +942,12 @@ export declare function failuresByCause(log: string, excluded?: readonly string[
916
942
  * stopped early happened to reach.
917
943
  *
918
944
  * A test the request excludes is not one of the causes — it is dropped and named once, at the
919
- * end, so a result never asks to fix a test it also says to ignore. Where the caller knows how
920
- * the run was started, the note also hands over the narrowed re-run of exactly those tests
921
- * ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the whole suite.
945
+ * end, so a result never asks to fix a test it also says to ignore. A test the runner itself
946
+ * declared `flaky` (it failed once and passed on its config retry) is not a cause either: it is
947
+ * named apart from them, once, as what the tally called it ({@link flakyTestsDeclared}). Where
948
+ * the caller knows how the run was started, the note also hands over the narrowed re-run of
949
+ * exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
950
+ * whole suite.
922
951
  *
923
952
  * @param log - The finished run's output.
924
953
  * @param excluded - The test titles the request carves out, if the caller knows them.
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
package/dist/utilities.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @module
6
6
  */
7
7
  import { posix } from 'path';
8
- import { commandSegments } from './command-position.js';
8
+ import { commandSegments, locatedCommandSegments } from './command-position.js';
9
9
  import { commandRunsNoTests } from './no-test-run.js';
10
10
  import { isDiagnosisRun, scopedRerunCommand, suiteCommandTarget, suiteRunInTheWay, withoutHeredocs, } from './suite-overlap.js';
11
11
  /**
@@ -430,16 +430,23 @@ export function streamingBackgroundCommand(command) {
430
430
  // joins stderr to it.
431
431
  const finalPlaywrightRun = isPlaywrightRunCommand(lastStep) && !/>/.test(lastStep.replace(/\d?>&\d/g, ''));
432
432
  /**
433
- * Add a word to the suite runner's own arguments. A pipe this rewrite could not drop still
434
- * follows the runner (`| tee run.log`), so appending at the end of the whole command would make
435
- * the word an operand of the filter — m102's dangling `--trace` flag, in the same shape.
433
+ * Add a word to the suite runner's own arguments; false when the runner could not be placed.
434
+ *
435
+ * A pipe this rewrite could not drop still follows the runner (`| tee run.log`), so appending at
436
+ * the end of the whole command would make the word an operand of the filter — m102's dangling
437
+ * `--trace` flag, in the same shape. A suite inside a nested `sh -c '<command>'` string has no
438
+ * offsets in this command (they are positions in THAT string), so the command is left exactly as
439
+ * written and the caller reports no rewrite: appending at the end would hand the flag to the
440
+ * shell, outside the quotes.
436
441
  */
437
442
  const addToRun = (flag) => {
438
443
  const pipeline = lastSuitePipeline(out);
439
444
  const runner = pipeline ? pipeline.located[pipeline.at] : undefined;
440
- out = runner
441
- ? `${out.slice(0, runner.end)} ${flag}${out.slice(runner.end)}`
442
- : `${out.trimEnd()} ${flag}`;
445
+ if (runner && runner.end === null)
446
+ return false;
447
+ const at = runner?.end ?? out.trimEnd().length;
448
+ out = `${out.slice(0, at)} ${flag}${out.slice(at)}`;
449
+ return true;
443
450
  };
444
451
  // A diagnosis or a re-run (capped, last-failed, grepped, or named files) walks each failure
445
452
  // once: the suite's own `retries` doubled every failure of X1 flash-m71's first whole run,
@@ -493,8 +500,7 @@ export function streamingBackgroundCommand(command) {
493
500
  'write every result into the log twice (X1 flash-m83).');
494
501
  }
495
502
  }
496
- else if (finalPlaywrightRun) {
497
- addToRun('--reporter=list');
503
+ else if (finalPlaywrightRun && addToRun('--reporter=list')) {
498
504
  changes.push('Added `--reporter=list` so each test result is written to the log as it finishes ' +
499
505
  '(the config reporter may print nothing until the end).');
500
506
  }
@@ -513,8 +519,8 @@ export function streamingBackgroundCommand(command) {
513
519
  'and you need not wait for it to end to start on them.');
514
520
  }
515
521
  if (finalPlaywrightRun && diagnosis && !/--retries\b/.test(lastStep)) {
516
- addToRun('--retries=0');
517
- changes.push('Added `--retries=0`: a diagnosis or re-run walks each failure once; keep the suite’s retries for the proof run.');
522
+ if (addToRun('--retries=0'))
523
+ changes.push('Added `--retries=0`: a diagnosis or re-run walks each failure once; keep the suite’s retries for the proof run.');
518
524
  }
519
525
  return { command: out, changes };
520
526
  }
@@ -604,32 +610,34 @@ const OUTPUT_ONLY_FILTERS = new Set([
604
610
  /** A word that sends output to a file (`> f`, `>> f`, `2> f`); `2>&1` only joins stderr, so it is not one. */
605
611
  const OUTPUT_REDIRECT = /^(?:\d*|&)>>?(?!&)/;
606
612
  /**
607
- * The shell parse's segments with their offsets in the command, or null when one cannot be placed.
613
+ * The shell parse's segments with their offsets in the command.
614
+ *
615
+ * {@link commandSegments} answers which program each stage runs; this answers where it is. The
616
+ * offsets come from the parse's own token positions ({@link locatedCommandSegments}), never from
617
+ * searching the command for the re-joined text: the words are joined back with single spaces for
618
+ * matching, so `command.indexOf(segment.text)` comes up empty on irregular spacing, on a grouping
619
+ * brace, or on anything a heredoc body shifts — and the caller then cut at a guess or refused to
620
+ * cut at all (xr-molecule-flash-m105 +08:12, where a heredoc body turned a finished run into
621
+ * "piped" for the rest of the session).
608
622
  *
609
- * {@link commandSegments} answers which program each stage runs, but re-joins the words with single
610
- * spaces — so a segment written with irregular whitespace, or a program spelled with a directory,
611
- * has no offset a caller can cut at. Null is the honest answer there: every caller then leaves the
612
- * command exactly as written rather than cutting into a guess.
623
+ * A statement inside a nested `sh -c '<command>'` string is listed with null offsets: its
624
+ * positions are positions in THAT string, so nothing may be spliced at them — but its separator
625
+ * is known, which is all a pipeline question needs.
613
626
  *
614
627
  * @param command - The shell command.
615
- * @returns The located segments, in order, or null.
628
+ * @returns The located segments, in order.
616
629
  */
617
630
  function locatedSegments(command) {
618
631
  const out = [];
619
- let cursor = 0;
620
- let previousEnd = null;
621
- for (const segment of commandSegments(command)) {
622
- const at = command.indexOf(segment.text, cursor);
623
- if (at < 0)
624
- return null;
632
+ for (const located of locatedCommandSegments(command)) {
633
+ if (located.segment === null)
634
+ continue;
625
635
  out.push({
626
- segment,
627
- start: at,
628
- end: at + segment.text.length,
629
- between: previousEnd === null ? '' : command.slice(previousEnd, at),
636
+ segment: located.segment,
637
+ start: located.start,
638
+ end: located.end,
639
+ between: located.separator,
630
640
  });
631
- cursor = at + segment.text.length;
632
- previousEnd = cursor;
633
641
  }
634
642
  return out;
635
643
  }
@@ -673,8 +681,6 @@ function lastSuitePipeline(command) {
673
681
  if (commandRunsNoTests(command))
674
682
  return null;
675
683
  const located = locatedSegments(command);
676
- if (located === null)
677
- return null;
678
684
  let at = -1;
679
685
  for (let i = 0; i < located.length; i += 1) {
680
686
  const segment = located[i]?.segment;
@@ -724,33 +730,59 @@ export function stripTrailingSuiteFilters(command) {
724
730
  const stage = located[first];
725
731
  if (!previous || !stage)
726
732
  return { command, dropped: '' };
733
+ // A stage inside a nested `sh -c` string has no offsets in this command: nothing can be cut.
734
+ if (previous.end === null || stage.start === null)
735
+ return { command, dropped: '' };
727
736
  const pipe = stage.between.indexOf('|');
728
737
  if (pipe < 0)
729
738
  return { command, dropped: '' };
730
739
  const cut = previous.end + pipe;
731
740
  return { command: command.slice(0, cut).trimEnd(), dropped: command.slice(cut).trim() };
732
741
  }
742
+ /**
743
+ * Where the suite runner's exit code comes from: the runner itself, or a stage piped after it.
744
+ *
745
+ * {@link suiteRunPipedAfterRunner}'s one question, with the honest third answer kept apart. The
746
+ * code a finished run reports is its LAST pipeline stage's, so a stage after the runner makes the
747
+ * code the stage's — but only when such a stage EXISTS. The platform used to report "piped" for
748
+ * any run whose statements it could not place in the text, which one heredoc body was enough to
749
+ * cause: on X1 xr-molecule-flash-m105 (+08:12) a config written in the same `exec_command` made
750
+ * the run come back with no exit code at all, and the note under it claimed a stage after the
751
+ * runner that was not there. The parse now places every statement (`locatedCommandSegments`), so
752
+ * "unplaced" is the rare, named case rather than a guess dressed as an answer.
753
+ *
754
+ * @param command - The shell command, after the streaming rewrite when one was applied.
755
+ * @returns Whether a stage follows the runner, and whether the run could be placed at all.
756
+ */
757
+ export function suiteRunPipelinePlacement(command) {
758
+ const located = locatedSegments(command);
759
+ const pipeline = lastSuitePipeline(command);
760
+ if (pipeline !== null) {
761
+ const stages = pipeline.located.slice(pipeline.at);
762
+ // Every stage of the pipeline has to sit somewhere in THIS command to be read as a pipeline
763
+ // of it: a suite inside a `sh -c '<command>'` string has no offsets here.
764
+ const unplaced = stages.some((stage) => stage.start === null);
765
+ return { piped: pipeline.located.length > pipeline.at + 1, unplaced };
766
+ }
767
+ if (commandRunsNoTests(command))
768
+ return { piped: false, unplaced: false };
769
+ const runner = located.find((stage) => SUITE_RUN_COMMANDS.some((re) => re.test(stage.segment.text)));
770
+ // No suite at all, or a suite that is not the command's last step (a `; echo done` follows it):
771
+ // either way no stage after the RUNNER produces this command's code.
772
+ if (!runner)
773
+ return { piped: false, unplaced: false };
774
+ return { piped: false, unplaced: runner.start === null };
775
+ }
733
776
  /**
734
777
  * Whether the command's last step runs a suite whose output still passes through a stage after
735
778
  * the runner — one {@link stripTrailingSuiteFilters} could not prove is output-only (`| tee log`,
736
779
  * `| xargs …`). The pipeline's exit status is that stage's, not the run's.
737
780
  *
738
- * A command whose stages cannot be placed in the text (irregular whitespace inside the run) is
739
- * reported as piped too: whether a pipe follows the runner is then UNKNOWN, and an unknown exit
740
- * code that gets trusted is a failing run read as a pass — the failure this rule exists to stop.
741
- *
742
781
  * @param command - The shell command, after the streaming rewrite when one was applied.
743
- * @returns True when a pipe this tool must not drop still follows the suite runner, or when the
744
- * command runs a suite the parse cannot place.
782
+ * @returns True when a pipe this tool must not drop still follows the suite runner.
745
783
  */
746
784
  export function suiteRunPipedAfterRunner(command) {
747
- const pipeline = lastSuitePipeline(command);
748
- if (pipeline !== null)
749
- return pipeline.located.length > pipeline.at + 1;
750
- // No placed pipeline: either there is no suite at all, or its stages cannot be located.
751
- if (commandRunsNoTests(command) || locatedSegments(command) !== null)
752
- return false;
753
- return commandSegments(command).some((segment) => SUITE_RUN_COMMANDS.some((re) => re.test(segment.text)));
785
+ return suiteRunPipelinePlacement(command).piped;
754
786
  }
755
787
  /**
756
788
  * Start a background suite run at lower CPU and IO priority, so the sandbox it
@@ -2124,17 +2156,63 @@ export function failuresByCause(log, excluded = []) {
2124
2156
  return groupFailures(log, excluded).causes;
2125
2157
  }
2126
2158
  /**
2127
- * The causes a run's log names, and the failures the request excludes out of them.
2159
+ * A finished run's own count of tests that failed their first attempt and passed on the retry:
2160
+ * Playwright's ` 1 flaky`, printed in its closing tally.
2161
+ */
2162
+ const FLAKY_TALLY_LINE = /^\s*[1-9]\d*\s+flaky\b/;
2163
+ /**
2164
+ * The tests a finished run's tally declares FLAKY (` 1 flaky` followed by their headings), keyed
2165
+ * the way a failure block is.
2166
+ *
2167
+ * Playwright prints a numbered failure block for the FIRST attempt of a test that then passes on
2168
+ * its retry, and also declares the test under `N flaky` — so a reader that only looks at the
2169
+ * blocks counts a flake as a cause to fix. xr-molecule-flash-m105's proof run against the deployed
2170
+ * URL reported ten `waitForURL` timing flakes as "causes" of a red run (+24:54), and the executor
2171
+ * spent its next minutes re-running them one by one until three passed untouched. The
2172
+ * declaration under the count is the runner's own answer, and its heading carries the same words
2173
+ * as the failure block's ({@link splitTitlePath} reads both the same way).
2174
+ *
2175
+ * @param text - The run's output, ANSI already stripped.
2176
+ * @returns Each flaky test, first seen first.
2177
+ */
2178
+ function flakyTestsDeclared(text) {
2179
+ const tests = [];
2180
+ let underFlakyCount = false;
2181
+ for (const line of text.split('\n')) {
2182
+ if (FLAKY_TALLY_LINE.test(line)) {
2183
+ underFlakyCount = true;
2184
+ continue;
2185
+ }
2186
+ if (!underFlakyCount)
2187
+ continue;
2188
+ // The declaration lines are the reporter's own headings (` [chromium] › a.spec.ts:5:1 › …`);
2189
+ // the first line that is not one ends them (the next tally line, or the end of the report).
2190
+ const heading = /^\s+\[[^\]\n]+\]\s*›\s*(.+)$/.exec(line)?.[1];
2191
+ if (heading === undefined) {
2192
+ underFlakyCount = false;
2193
+ continue;
2194
+ }
2195
+ const { where, title } = splitTitlePath(heading.replace(/[\s─]+$/, ''));
2196
+ tests.push({ where, title, seconds: null });
2197
+ }
2198
+ return distinctTests(tests);
2199
+ }
2200
+ /**
2201
+ * The causes a run's log names, the failures the request excludes, and the failures the runner
2202
+ * itself counted as flaky out of them.
2128
2203
  *
2129
2204
  * One grouping pass, because a note that lists the causes must know what it left out to say so
2130
2205
  * ({@link failureCausesNote}).
2131
2206
  *
2132
2207
  * @param log - The log of a finished or running run.
2133
2208
  * @param excluded - The test titles the request carves out.
2134
- * @returns The causes to fix, and the distinct tests dropped as excluded.
2209
+ * @returns The causes to fix, the distinct tests dropped as excluded, and the distinct tests the
2210
+ * runner counted as flaky (a retried pass is not a cause to fix).
2135
2211
  */
2136
2212
  function groupFailures(log, excluded) {
2137
2213
  const text = log.replace(ANSI_SGR, '');
2214
+ const flakyTests = flakyTestsDeclared(text);
2215
+ const flaky = new Set(flakyTests.map(testKey));
2138
2216
  const byMessage = new Map();
2139
2217
  const named = [];
2140
2218
  for (const block of failureBlocks(text)) {
@@ -2144,6 +2222,10 @@ function groupFailures(log, excluded) {
2144
2222
  const m = FAILURE_BLOCK_HEADER.exec(block.split('\n')[0] ?? '');
2145
2223
  const { where, title } = splitTitlePath(m?.[1] ?? '');
2146
2224
  const test = { where, title, seconds: null };
2225
+ // The runner declared this test flaky: the block above is its first attempt, which it
2226
+ // passed on the retry, so it is neither a cause nor a failure to re-run.
2227
+ if (flaky.has(testKey(test)))
2228
+ continue;
2147
2229
  named.push(test);
2148
2230
  if (matchingExcludedTitles(title, excluded).length > 0)
2149
2231
  continue;
@@ -2173,6 +2255,9 @@ function groupFailures(log, excluded) {
2173
2255
  const claimed = new Set(causes.flatMap((c) => c.tests.map(testKey)));
2174
2256
  const unclaimed = distinctTests(listed).filter((t) => t.seconds !== null &&
2175
2257
  !claimed.has(testKey(t)) &&
2258
+ // A flaky test's `✘` row is its first attempt; its `✓` retry follows it in the same log,
2259
+ // and the runner's tally already said the test passed.
2260
+ !flaky.has(testKey(t)) &&
2176
2261
  matchingExcludedTitles(t.title, excluded).length === 0);
2177
2262
  const byDuration = [];
2178
2263
  for (const t of unclaimed) {
@@ -2186,7 +2271,11 @@ function groupFailures(log, excluded) {
2186
2271
  for (const g of byDuration)
2187
2272
  causes.push({ message: null, seconds: g.seconds, tests: g.tests, evidence: [] });
2188
2273
  const ignored = distinctTests(named).filter((t) => matchingExcludedTitles(t.title, excluded).length > 0);
2189
- return { causes: causes.sort((a, b) => b.tests.length - a.tests.length), ignored };
2274
+ return {
2275
+ causes: causes.sort((a, b) => b.tests.length - a.tests.length),
2276
+ ignored,
2277
+ flaky: flakyTests,
2278
+ };
2190
2279
  }
2191
2280
  /**
2192
2281
  * The one-line-per-cause summary of a finished run's failures, or null when the run named fewer
@@ -2197,9 +2286,12 @@ function groupFailures(log, excluded) {
2197
2286
  * stopped early happened to reach.
2198
2287
  *
2199
2288
  * A test the request excludes is not one of the causes — it is dropped and named once, at the
2200
- * end, so a result never asks to fix a test it also says to ignore. Where the caller knows how
2201
- * the run was started, the note also hands over the narrowed re-run of exactly those tests
2202
- * ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the whole suite.
2289
+ * end, so a result never asks to fix a test it also says to ignore. A test the runner itself
2290
+ * declared `flaky` (it failed once and passed on its config retry) is not a cause either: it is
2291
+ * named apart from them, once, as what the tally called it ({@link flakyTestsDeclared}). Where
2292
+ * the caller knows how the run was started, the note also hands over the narrowed re-run of
2293
+ * exactly those tests ({@link rerunOfRunningSuite}) instead of leaving the executor to re-run the
2294
+ * whole suite.
2203
2295
  *
2204
2296
  * @param log - The finished run's output.
2205
2297
  * @param excluded - The test titles the request carves out, if the caller knows them.
@@ -2209,7 +2301,7 @@ function groupFailures(log, excluded) {
2209
2301
  * @returns The note, or null.
2210
2302
  */
2211
2303
  export function failureCausesNote(log, excluded = [], run) {
2212
- const { causes, ignored } = groupFailures(log, excluded);
2304
+ const { causes, ignored, flaky } = groupFailures(log, excluded);
2213
2305
  const total = causes.reduce((n, c) => n + c.tests.length, 0);
2214
2306
  if (total < 2)
2215
2307
  return null;
@@ -2221,9 +2313,16 @@ export function failureCausesNote(log, excluded = [], run) {
2221
2313
  const locations = [...new Set(causes.flatMap((c) => c.tests.map((t) => t.where)).filter(Boolean))];
2222
2314
  const rerun = run ? rerunOfRunningSuite(run, locations, excluded) : null;
2223
2315
  const ignoredTitles = [...new Set(ignored.map((t) => t.title || t.where).filter(Boolean))];
2316
+ const flakyTitles = [...new Set(flaky.map((t) => t.where || t.title).filter(Boolean))];
2224
2317
  const header = `${total} failure${total === 1 ? '' : 's'} in this run, grouped by cause — fix every cause ` +
2225
2318
  `below before the next deploy:\n`;
2319
+ // The flaky sentence sits before the ignored one: the ignored sentence is the LAST thing the
2320
+ // note says, and callers assert on that ending.
2226
2321
  const tail = (rerun ? ` Re-run just these once they are fixed: \`${rerun}\`` : '') +
2322
+ (flakyTitles.length
2323
+ ? ' Flaky (failed once, passed on the config retry — the runner counts these as flaky, not ' +
2324
+ `as failures): ${flakyTitles.join(', ')}.`
2325
+ : '') +
2227
2326
  (ignoredTitles.length ? ` Ignored (excluded by the request): ${ignoredTitles.join(', ')}.` : '');
2228
2327
  // This list is the part of the result that SURVIVES the platform's tool-output cut: the stdout
2229
2328
  // above it is cut in the MIDDLE at {@link TOOL_RESULT_CHARS}, which is why the recap must fit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",