@molecule/api-ai-tools 1.8.3 → 1.9.2

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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The exit code a hand-run check's result carries is the pipeline's LAST stage's, and a weak
3
+ * executor reads the number as the check's verdict.
4
+ *
5
+ * `npx tsc --noEmit 2>&1 | head -40` reports `head`'s 0 while the output above it carries a type
6
+ * error, and `… | head -40 && tail -30 missing.log` reports `tail`'s 1 while the checker was clean —
7
+ * both measured in xr-molecule-flash-m117 (+04:06, +08:05). A model that trusts the code concludes
8
+ * "clean" and ships a build that does not compile, or hunts an error that is not there. This module
9
+ * says which stage produced the code, and what the output actually carries, in one sentence beside
10
+ * `exitCode`.
11
+ *
12
+ * The parse is the package's own shell reader ({@link locatedCommandSegments}), so a `|` inside
13
+ * quotes or a heredoc is not a stage, and `set -o pipefail` (where it really runs) turns the note
14
+ * off: then the code IS the check's.
15
+ *
16
+ * @module
17
+ */
18
+ /**
19
+ * The sentence an `exec_command` result carries beside `exitCode` when the code belongs to a
20
+ * pipeline's last stage rather than to the check that printed the output.
21
+ *
22
+ * Two shapes, both from a hand-run check with an unguarded pipe:
23
+ *
24
+ * - the code says 0 and the output carries failures (`tsc | head` reports `head`'s 0);
25
+ * - the code is non-zero and the checker printed no failures (`tsc | head && tail missing.log`
26
+ * reports `tail`'s 1, and the checker was clean).
27
+ *
28
+ * A command with no pipe, a pipe guarded by `pipefail`, a pipeline where no checker runs, or a
29
+ * code and an output that agree all return null.
30
+ *
31
+ * @param command - The command as the executor sent it.
32
+ * @param exitCode - The code the shell reported (the pipeline's last stage's).
33
+ * @param output - The command's raw stdout and stderr, for the checker's own text.
34
+ * @returns One sentence naming the stage the code came from, or null.
35
+ */
36
+ export declare function pipelineExitCodeNote(command: string, exitCode: number, output: string): string | null;
37
+ //# sourceMappingURL=pipeline-exit-note.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline-exit-note.d.ts","sourceRoot":"","sources":["../src/pipeline-exit-note.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA2GH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,IAAI,CAsCf"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The exit code a hand-run check's result carries is the pipeline's LAST stage's, and a weak
3
+ * executor reads the number as the check's verdict.
4
+ *
5
+ * `npx tsc --noEmit 2>&1 | head -40` reports `head`'s 0 while the output above it carries a type
6
+ * error, and `… | head -40 && tail -30 missing.log` reports `tail`'s 1 while the checker was clean —
7
+ * both measured in xr-molecule-flash-m117 (+04:06, +08:05). A model that trusts the code concludes
8
+ * "clean" and ships a build that does not compile, or hunts an error that is not there. This module
9
+ * says which stage produced the code, and what the output actually carries, in one sentence beside
10
+ * `exitCode`.
11
+ *
12
+ * The parse is the package's own shell reader ({@link locatedCommandSegments}), so a `|` inside
13
+ * quotes or a heredoc is not a stage, and `set -o pipefail` (where it really runs) turns the note
14
+ * off: then the code IS the check's.
15
+ *
16
+ * @module
17
+ */
18
+ import { locatedCommandSegments } from './command-position.js';
19
+ /** The shells whose `-o pipefail` makes a pipeline report its first failing stage. */
20
+ const PIPEFAIL_SHELLS = new Set(['ash', 'bash', 'dash', 'ksh', 'sh', 'zsh']);
21
+ /**
22
+ * The programs a pipeline may end with when all they do is read what was printed and print it
23
+ * again — the stages that hand back their own status in place of the check's.
24
+ */
25
+ const OUTPUT_ONLY_FILTERS = new Set([
26
+ 'awk',
27
+ 'cat',
28
+ 'column',
29
+ 'cut',
30
+ 'egrep',
31
+ 'fgrep',
32
+ 'fmt',
33
+ 'fold',
34
+ 'grep',
35
+ 'head',
36
+ 'jq',
37
+ 'nl',
38
+ 'rg',
39
+ 'sed',
40
+ 'sort',
41
+ 'tail',
42
+ 'tr',
43
+ 'uniq',
44
+ 'wc',
45
+ ]);
46
+ /** `1 type error` / `2 type errors`. */
47
+ const plural = (count, one, many) => `${count} ${count === 1 ? one : many}`;
48
+ /**
49
+ * The failure count a runner's tally names. Vitest prints two tallies — `Test Files 1 failed`
50
+ * above `Tests 2 failed` — and the last one is the tests, which is the number the note means.
51
+ *
52
+ * @param output - The runner's output.
53
+ * @returns The count in the last `N failed` tally, or 0.
54
+ */
55
+ const failedTally = (output) => {
56
+ const tallies = [...output.matchAll(/\b(\d+) failed\b/g)];
57
+ return Number(tallies.at(-1)?.[1] ?? 0);
58
+ };
59
+ /** The checkers whose output says pass or fail in text of its own. */
60
+ const CHECKERS = [
61
+ {
62
+ name: 'tsc',
63
+ runs: (program) => program === 'tsc' || program === 'vue-tsc',
64
+ failures: (output) => {
65
+ const errors = output.match(/\berror TS\d+/g)?.length ?? 0;
66
+ if (errors > 0)
67
+ return errors;
68
+ const found = /\bFound (\d+) errors?/.exec(output);
69
+ return found ? Number(found[1]) : 0;
70
+ },
71
+ label: (count) => plural(count, 'type error', 'type errors'),
72
+ },
73
+ {
74
+ name: 'eslint',
75
+ runs: (program) => program === 'eslint',
76
+ failures: (output) => {
77
+ const problems = /✖\s*(\d+)\s+problems?/.exec(output);
78
+ return problems ? Number(problems[1]) : 0;
79
+ },
80
+ label: (count) => plural(count, 'lint problem', 'lint problems'),
81
+ },
82
+ {
83
+ name: 'vitest',
84
+ runs: (program) => program === 'vitest',
85
+ failures: failedTally,
86
+ label: (count) => plural(count, 'failing test', 'failing tests'),
87
+ },
88
+ {
89
+ name: 'playwright',
90
+ runs: (_program, text) => /^playwright\s+test\b/.test(text),
91
+ failures: failedTally,
92
+ label: (count) => plural(count, 'failing test', 'failing tests'),
93
+ },
94
+ ];
95
+ /** Whether the command turns on `pipefail`, so a pipeline reports the check's own status. */
96
+ function guardedByPipefail(segments) {
97
+ return segments.some(({ program, args }) => (program === 'set' || PIPEFAIL_SHELLS.has(program)) && args.includes('pipefail'));
98
+ }
99
+ /**
100
+ * The sentence an `exec_command` result carries beside `exitCode` when the code belongs to a
101
+ * pipeline's last stage rather than to the check that printed the output.
102
+ *
103
+ * Two shapes, both from a hand-run check with an unguarded pipe:
104
+ *
105
+ * - the code says 0 and the output carries failures (`tsc | head` reports `head`'s 0);
106
+ * - the code is non-zero and the checker printed no failures (`tsc | head && tail missing.log`
107
+ * reports `tail`'s 1, and the checker was clean).
108
+ *
109
+ * A command with no pipe, a pipe guarded by `pipefail`, a pipeline where no checker runs, or a
110
+ * code and an output that agree all return null.
111
+ *
112
+ * @param command - The command as the executor sent it.
113
+ * @param exitCode - The code the shell reported (the pipeline's last stage's).
114
+ * @param output - The command's raw stdout and stderr, for the checker's own text.
115
+ * @returns One sentence naming the stage the code came from, or null.
116
+ */
117
+ export function pipelineExitCodeNote(command, exitCode, output) {
118
+ const running = locatedCommandSegments(command).filter((entry) => entry.segment !== null);
119
+ if (running.length < 2)
120
+ return null;
121
+ if (!running.some((entry) => entry.separator.trim() === '|'))
122
+ return null;
123
+ const segments = running.map((entry) => entry.segment);
124
+ if (guardedByPipefail(segments))
125
+ return null;
126
+ let checkerAt = -1;
127
+ for (let i = segments.length - 1; i >= 0; i -= 1) {
128
+ const segment = segments[i];
129
+ if (CHECKERS.some((checker) => checker.runs(segment.program, segment.text))) {
130
+ checkerAt = i;
131
+ break;
132
+ }
133
+ }
134
+ if (checkerAt < 0)
135
+ return null;
136
+ // The check's own status is lost only when a stage of the SAME pipeline follows it; a `&&` after
137
+ // the check already reports a real failure of its own, and a check followed by nothing reports
138
+ // its own code.
139
+ if (running[checkerAt + 1]?.separator.trim() !== '|')
140
+ return null;
141
+ const checkerSegment = segments[checkerAt];
142
+ const checker = CHECKERS.find((candidate) => candidate.runs(checkerSegment.program, checkerSegment.text));
143
+ const last = segments[segments.length - 1];
144
+ const failures = checker.failures(output);
145
+ if (exitCode === 0 && failures > 0) {
146
+ return (`the exit code 0 is the last stage's (${last.program}), not ${checker.name}'s — ` +
147
+ `the output carries ${checker.label(failures)}`);
148
+ }
149
+ if (exitCode !== 0 && failures === 0 && OUTPUT_ONLY_FILTERS.has(last.program)) {
150
+ return (`the exit code ${exitCode} is the last stage's (${last.program}), not ${checker.name}'s — ` +
151
+ 'the checker printed no errors');
152
+ }
153
+ return null;
154
+ }
@@ -50,12 +50,44 @@ export declare function withoutHeredocs(command: string): string;
50
50
  * @returns The target key.
51
51
  */
52
52
  export declare function suiteCommandTarget(command: string): string;
53
+ /**
54
+ * Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
55
+ * {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
56
+ *
57
+ * @param command - A command, or the Playwright statement inside it.
58
+ * @returns True when the statement loads a config of its own.
59
+ */
60
+ export declare function namesSuiteConfig(command: string): boolean;
61
+ /**
62
+ * The command with a `--config` added to the Playwright invocation it starts — or unchanged when
63
+ * it already names one, or starts no Playwright run.
64
+ *
65
+ * The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
66
+ * it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
67
+ *
68
+ * xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
69
+ * `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
70
+ * NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
71
+ * that says nothing about the app. The platform composes commands back to the executor too (the
72
+ * narrowed re-run of a finished run's failures), and a command it hands back has to carry the
73
+ * config the run was measured under, never the shape that caused that finding.
74
+ *
75
+ * @param command - The command as the executor sent it.
76
+ * @param config - The config the run loads, when the caller knows it.
77
+ * @returns The command with the flag, or the command unchanged.
78
+ */
79
+ export declare function withSuiteConfig(command: string, config: string | null | undefined): string;
53
80
  /**
54
81
  * The whole-suite command made scoped to the tests it should re-run instead: the same
55
82
  * invocation — same directory, same settings — with the named `file:line`s in place of the whole
56
83
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
57
84
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
58
85
  *
86
+ * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
87
+ * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
88
+ * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
89
+ * `Cannot navigate to invalid URL` because the re-run loaded no base URL).
90
+ *
59
91
  * @param command - The whole-suite run's command.
60
92
  * @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
61
93
  * for a run that started outside the suite, the absolute form Playwright matches each file
@@ -64,9 +96,11 @@ export declare function suiteCommandTarget(command: string): string;
64
96
  * (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
65
97
  * xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
66
98
  * a test the request had already said does not count.
99
+ * @param config - The config the run loaded, when the caller knows it; added when the command
100
+ * does not name one.
67
101
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
68
102
  */
69
- export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[]): string;
103
+ export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null): string;
70
104
  /**
71
105
  * One suite run, as the rule that decides whether a second may start beside it reads it.
72
106
  *
@@ -1 +1 @@
1
- {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,MAAM,CA2BR;AAED;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
1
+ {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;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;;;;;;;;;;;;;;;;;;;;;;GAsBG;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,GACrB,MAAM,CA8BR;AAED;;;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
@@ -74,12 +74,62 @@ export function suiteCommandTarget(command) {
74
74
  }
75
75
  return 'local';
76
76
  }
77
+ /** Whether a Playwright statement already names its own config (`-c`, `--config`, `--config=`). */
78
+ const NAMES_CONFIG = /(?:^|\s)(?:-c|--config)(?:\s+|=)/;
79
+ /**
80
+ * Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
81
+ * {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
82
+ *
83
+ * @param command - A command, or the Playwright statement inside it.
84
+ * @returns True when the statement loads a config of its own.
85
+ */
86
+ export function namesSuiteConfig(command) {
87
+ return NAMES_CONFIG.test(command);
88
+ }
89
+ /**
90
+ * The command with a `--config` added to the Playwright invocation it starts — or unchanged when
91
+ * it already names one, or starts no Playwright run.
92
+ *
93
+ * The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
94
+ * it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
95
+ *
96
+ * xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
97
+ * `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
98
+ * NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
99
+ * that says nothing about the app. The platform composes commands back to the executor too (the
100
+ * narrowed re-run of a finished run's failures), and a command it hands back has to carry the
101
+ * config the run was measured under, never the shape that caused that finding.
102
+ *
103
+ * @param command - The command as the executor sent it.
104
+ * @param config - The config the run loads, when the caller knows it.
105
+ * @returns The command with the flag, or the command unchanged.
106
+ */
107
+ export function withSuiteConfig(command, config) {
108
+ if (!config)
109
+ return command;
110
+ const at = command.search(/\bplaywright\s+test\b/);
111
+ if (at === -1)
112
+ return command;
113
+ const head = command.slice(0, at);
114
+ const rest = command.slice(at);
115
+ const end = rest.search(/&&|\|\||;|\|/);
116
+ const statement = end === -1 ? rest : rest.slice(0, end);
117
+ const tail = end === -1 ? '' : rest.slice(end);
118
+ if (namesSuiteConfig(statement))
119
+ return command;
120
+ return `${head}${statement.replace(/^(playwright\s+test)\b/, `$1 --config ${shellQuote(config)}`)}${tail}`;
121
+ }
77
122
  /**
78
123
  * The whole-suite command made scoped to the tests it should re-run instead: the same
79
124
  * invocation — same directory, same settings — with the named `file:line`s in place of the whole
80
125
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
81
126
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
82
127
  *
128
+ * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
129
+ * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
130
+ * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
131
+ * `Cannot navigate to invalid URL` because the re-run loaded no base URL).
132
+ *
83
133
  * @param command - The whole-suite run's command.
84
134
  * @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
85
135
  * for a run that started outside the suite, the absolute form Playwright matches each file
@@ -88,9 +138,11 @@ export function suiteCommandTarget(command) {
88
138
  * (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
89
139
  * xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
90
140
  * a test the request had already said does not count.
141
+ * @param config - The config the run loaded, when the caller knows it; added when the command
142
+ * does not name one.
91
143
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
92
144
  */
93
- export function scopedRerunCommand(command, locations, excluded = []) {
145
+ export function scopedRerunCommand(command, locations, excluded = [], config) {
94
146
  const at = command.search(/\bplaywright\s+test\b/);
95
147
  if (at === -1)
96
148
  return command;
@@ -110,10 +162,13 @@ export function scopedRerunCommand(command, locations, excluded = []) {
110
162
  .map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
111
163
  .join('|'))}`
112
164
  : '';
165
+ // The run's own config: the command's `--config` when it has one, else the config the caller
166
+ // recorded for the run. A re-run without it measures nothing (m113).
167
+ const carry = config && !namesSuiteConfig(statement) ? ` --config ${shellQuote(config)}` : '';
113
168
  const scoped = statement
114
169
  .replace(/\s*--last-failed\b/g, '')
115
170
  .replace(/\s*--retries(?:[= ]\d+)?/g, '')
116
- .replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${invert}`);
171
+ .replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${carry}${invert}`);
117
172
  return `${head}${scoped}${tail}`;
118
173
  }
119
174
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAgJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAiOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AAgDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAu2ExF"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAiJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAiOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AAgDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAy3ExF"}
package/dist/tools.js CHANGED
@@ -9,6 +9,7 @@
9
9
  import { commandCutRun, CUT_AT_BUDGET, cutRunSentence, suiteRunExitCode } from './cut-run.js';
10
10
  import { addChangeSpan, changedSpan, countLines, renderEditExcerpt, renderSmallFile, } from './edit-excerpt.js';
11
11
  import { guardToolExecute, missingParamError, ToolInputError } from './input-normalizer.js';
12
+ import { pipelineExitCodeNote } from './pipeline-exit-note.js';
12
13
  import { TOOL_SCHEMAS } from './schemas.js';
13
14
  import { leadingSleepPeek } from './sleep-peek.js';
14
15
  /**
@@ -2172,6 +2173,12 @@ export function buildTools(backend, config) {
2172
2173
  const placement = suiteRunPipelinePlacement(streaming.command);
2173
2174
  const reported = suiteRunExitCode(code, output, placement.piped);
2174
2175
  const exitNote = suiteExitCodeNote(placement, reported);
2176
+ // The same question the foreground path asks: when the code is a filter's, say
2177
+ // which stage produced it (and what the output carries) rather than leaving a
2178
+ // 0 beside a printed error.
2179
+ const pipeNote = reported === null
2180
+ ? null
2181
+ : pipelineExitCodeNote(streaming.command, reported, output);
2175
2182
  const notes = [
2176
2183
  ...(sleepNote ? [sleepNote] : []),
2177
2184
  ...streaming.changes,
@@ -2181,6 +2188,7 @@ export function buildTools(backend, config) {
2181
2188
  stdout,
2182
2189
  stderr: '',
2183
2190
  exitCode: reported,
2191
+ ...(pipeNote === null ? {} : { exitCodeNote: pipeNote }),
2184
2192
  ...(output.length > MAX_OUTPUT_SIZE ? { truncated: true, fullOutput: log } : {}),
2185
2193
  ...(notes.length ? { note: notes.join(' ') } : {}),
2186
2194
  };
@@ -2305,10 +2313,16 @@ export function buildTools(backend, config) {
2305
2313
  (isSuiteRunCommand(command) ? SUITE_OVER_BUDGET_ADVICE : SPLIT_WORK_ADVICE),
2306
2314
  };
2307
2315
  }
2316
+ // A pipeline reports its LAST stage's code: `tsc | head` says 0 beside a printed type
2317
+ // error, and `tsc | head && tail missing.log` says 1 beside a clean check. Name the
2318
+ // stage the number came from (pipeline-exit-note.ts) — the field the executor trusts
2319
+ // most must not contradict the text it can read.
2320
+ const exitCodeNote = pipelineExitCodeNote(command, result.exitCode, `${result.stdout}\n${result.stderr}`);
2308
2321
  return {
2309
2322
  stdout,
2310
2323
  stderr,
2311
2324
  exitCode: result.exitCode,
2325
+ ...(exitCodeNote === null ? {} : { exitCodeNote }),
2312
2326
  ...(peekedTask?.slice.trim() ? { outputSoFar: sanitizeOutput(peekedTask.slice) } : {}),
2313
2327
  ...(sleepNote || ceilingNote || traced.change
2314
2328
  ? {
@@ -32,8 +32,37 @@ export interface TraceRequest {
32
32
  method: string;
33
33
  /** The request's path (`/api/articles/x/favorite`), or the whole URL when it is cross-origin. */
34
34
  path: string;
35
- /** The response status, or 0 when the request never got a response. */
35
+ /**
36
+ * The response status as the trace recorded it. Playwright writes `-1` when a request was never
37
+ * answered (its HAR entry starts at `-1` and only a response event overwrites it); `0` is the
38
+ * same absence when the snapshot carried no response at all. Neither is a server failure.
39
+ */
36
40
  status: number;
41
+ /**
42
+ * Playwright's own error text for a request that failed WITHOUT a response
43
+ * (`net::ERR_CONNECTION_REFUSED`) — a real failure. A request that got no response and recorded
44
+ * no text was aborted or still in flight when the test ended (the page closed under it), so it
45
+ * says nothing about the server (xr-molecule-flash-m112 +30:45).
46
+ */
47
+ failure?: string;
48
+ /**
49
+ * What the page SENT, for a 4xx answered by the app's own API — bounded (see
50
+ * {@link MAX_TRACED_BODY_CHARS}). A body wrapped as `{"data":{…}}` is visible here in one line:
51
+ * app-http's verbs take the body as their second argument, so a helper that passes
52
+ * `{ data: body }` there ships the wrapper (xr-molecule-flash-m111 +11:36).
53
+ */
54
+ sent?: string;
55
+ /** What came back for that 4xx, bounded the same way — the API's own error body. */
56
+ got?: string;
57
+ }
58
+ /** One action the traced page performed, with the trace's own timing for it. */
59
+ export interface TraceAction {
60
+ /** The Playwright API the entry names: `Frame.goto`, `Locator.fill`, `Locator.click`. */
61
+ api: string;
62
+ /** When the action started, on the trace's own monotonic clock (ms). */
63
+ start: number;
64
+ /** When it finished, or null when the run ended before it did. */
65
+ end: number | null;
37
66
  }
38
67
  /** What one trace says: the test it belongs to, the requests it made, and its console errors. */
39
68
  export interface TraceArtifact {
@@ -45,6 +74,12 @@ export interface TraceArtifact {
45
74
  requests: TraceRequest[];
46
75
  /** The console errors and page errors the page raised. */
47
76
  console: string[];
77
+ /**
78
+ * The page's own actions in the order the trace recorded them, bounded — what a test that ran
79
+ * out of a per-test budget spends its time on. A test that repeats one action prints it once per
80
+ * repeat, so the gap between two starts is one repeat's measured cost.
81
+ */
82
+ actions: TraceAction[];
48
83
  }
49
84
  /** How many traces one read reports, newest first. */
50
85
  export declare const MAX_TRACE_ARTIFACTS = 12;
@@ -52,12 +87,20 @@ export declare const MAX_TRACE_ARTIFACTS = 12;
52
87
  export declare const MAX_TRACED_REQUESTS = 6;
53
88
  /** How many console errors one test's trace contributes. */
54
89
  export declare const MAX_TRACED_CONSOLE = 4;
90
+ /** How many of the page's own actions one test's trace contributes, the LAST ones. */
91
+ export declare const MAX_TRACED_ACTIONS = 40;
92
+ /** How much of a 4xx request's sent body (or its response body) the trace read keeps. */
93
+ export declare const MAX_TRACED_BODY_CHARS = 300;
55
94
  /** How many tests the note in a run's result names. */
56
95
  export declare const MAX_TRACE_NOTE_TESTS = 5;
57
96
  /** How many requests the note names per test. */
58
97
  export declare const MAX_TRACE_NOTE_REQUESTS = 4;
59
98
  /** How long a URL the note prints, before the middle is cut. */
60
99
  export declare const TRACE_NOTE_URL_CHARS = 96;
100
+ /** How much of the sent body the note prints — enough to see a `{"data":…}` wrapper. */
101
+ export declare const TRACE_NOTE_SENT_CHARS = 160;
102
+ /** How much of the response body the note prints — the API's first error line. */
103
+ export declare const TRACE_NOTE_GOT_CHARS = 120;
61
104
  /**
62
105
  * The Node program the sandbox runs to read a failed run's traces: `node <script> <dir>` prints one
63
106
  * `TRACE <json>` line per `trace.zip` under `<dir>` (newest first, {@link MAX_TRACE_ARTIFACTS} of
@@ -69,10 +112,17 @@ export declare const TRACE_NOTE_URL_CHARS = 96;
69
112
  * central directory and `zlib.inflateRawSync` — about forty lines, and the only thing that has to
70
113
  * be true of a `trace.zip` is that it IS one.
71
114
  *
72
- * A request is kept over a static asset when it failed (`status >= 400`) or never answered
73
- * (`status === 0`), and over anything else when it is the page talking to the app
74
- * (`fetch`/`xhr`/`document`): a failing test's trace holds the whole page load, and the answer is
75
- * always at the end of it.
115
+ * A request is kept over a static asset when it failed — a `status >= 400`, or no response at all
116
+ * together with Playwright's own error text (`-1` plus `_failureText`, a refused connection) — and
117
+ * over anything else when it is the page talking to the app (`fetch`/`xhr`/`document`): a failing
118
+ * test's trace holds the whole page load. A request with no response and NO error text was aborted
119
+ * or still in flight when the test ended; it is kept too, but as what it is, not as a failure.
120
+ *
121
+ * A request answered 4xx by the app's OWN API also keeps what the page SENT and the first of what
122
+ * came back: Playwright stores either inline (`text`) or in a resource entry (`_file`), and both
123
+ * are read. That is where a wrapped body shows: xr-molecule-flash-m111's helper passed
124
+ * `{ data: body }` as app-http's second argument, so every POST/PUT was `{"data":{…}}` and the API
125
+ * validated `{}`. Status alone said only "422".
76
126
  *
77
127
  * @returns The program's source, to write to a file and run.
78
128
  */
@@ -97,11 +147,31 @@ export declare function parseTraceArtifacts(stdout: string): TraceArtifact[];
97
147
  * {@link MAX_TRACE_NOTE_TESTS} tests with {@link MAX_TRACE_NOTE_REQUESTS} requests each, because it
98
148
  * rides in a result the model pays for.
99
149
  *
150
+ * The three kinds are kept apart, because they mean different things. A FAILED request (4xx/5xx, or
151
+ * a network error with Playwright's own text) is a cause. A request with NO RESPONSE RECORDED —
152
+ * Playwright's `-1`, no error text — was still in flight when the test ended (the page closed under
153
+ * it); listing it as a failure sent a run hunting an API that never answered anything wrong
154
+ * (xr-molecule-flash-m112 +30:45). The rest are the page's last answered requests.
155
+ *
156
+ * A 4xx request carries what the page SENT beside its status, so a wrapped body is visible in one
157
+ * line rather than inferred from the status; when one of those bodies is an object whose only key
158
+ * is `data`, the note names the call shape that produces it (xr-molecule-flash-m111 +11:36).
159
+ *
160
+ * WHICH rows it spends is evidence, not artifact order (xr-molecule-flash-m112 +53:46): a row
161
+ * needs a recorded response, a real failure or a console error ({@link traceRowEvidence}), rows are
162
+ * deduplicated by the test's location, and they are ordered real failure → repeated from an
163
+ * earlier walk → the rest ({@link traceRowTier}). Without that the note spent all five rows on four
164
+ * traces of one `afterEach` line whose every request was `-1`, and left the favorite cause — the
165
+ * one failure that repeated in every walk — in its "… and N more".
166
+ *
100
167
  * @param items - The traces {@link parseTraceArtifacts} read.
101
168
  * @param target - Where the run went — the deployed host, as the note names it.
102
- * @returns The note, or null when no trace held a request or a console error.
169
+ * @param earlierFailures - The failing `file:line`s earlier walks of this suite left, so a repeat
170
+ * is ranked above a one-off. Empty when the caller knows of no earlier walk.
171
+ * @returns The note, or null when no trace held a recorded response, a real failure or a console
172
+ * error.
103
173
  */
104
- export declare function failureTraceNote(items: readonly TraceArtifact[], target: string): string | null;
174
+ export declare function failureTraceNote(items: readonly TraceArtifact[], target: string, earlierFailures?: readonly string[]): string | null;
105
175
  /**
106
176
  * Whether a Playwright config records traces for the tests that fail.
107
177
  *
@@ -1 +1 @@
1
- {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;CACf;AAED,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,QAAO,MA+LvC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAyBnE;AAiBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B/F;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,8BAA8B,CAAA;AA2G/D,oGAAoG;AACpG,MAAM,WAAW,qBAAqB;IACpC,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyBjC"}
1
+ {"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACnB;AAED,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB;;;;OAIG;IACH,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,yFAAyF;AACzF,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,wFAAwF;AACxF,eAAO,MAAM,qBAAqB,MAAM,CAAA;AAExC,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAKvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAiQvC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAqCnE;AAwJD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,MAAM,EAAE,MAAM,EACd,eAAe,GAAE,SAAS,MAAM,EAAO,GACtC,MAAM,GAAG,IAAI,CAmFf;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,8BAA8B,CAAA;AA2G/D,oGAAoG;AACpG,MAAM,WAAW,qBAAqB;IACpC,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyBjC"}