@molecule/api-ai-tools 1.6.4 → 1.6.6
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 +520 -3
- package/dist/command-position.d.ts +27 -1
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +109 -28
- package/dist/cut-run.d.ts +124 -0
- package/dist/cut-run.d.ts.map +1 -0
- package/dist/cut-run.js +267 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/suite-overlap.d.ts +132 -0
- package/dist/suite-overlap.d.ts.map +1 -0
- package/dist/suite-overlap.js +208 -0
- package/dist/tools.d.ts +45 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +333 -27
- package/dist/utilities.d.ts +113 -2
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +171 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51,6 +51,18 @@
|
|
|
51
51
|
* `newlineNote` hint beside an unrelated-looking parse error, which an executor
|
|
52
52
|
* repairs by rewriting the fused line's text and fuses the next one. The
|
|
53
53
|
* `newlineNote` alone is still returned when the file parses.
|
|
54
|
+
* - **A replacement that only changes a line break at its boundary is refused,
|
|
55
|
+
* not applied.** When `new_string` is `old_string` with its trailing newline
|
|
56
|
+
* added or removed and nothing else, the edit can only move text across lines —
|
|
57
|
+
* joining the next line onto the replaced text, or pushing the rest of the line
|
|
58
|
+
* down — which is what a weak model does by accident when it repeats a line it
|
|
59
|
+
* means to keep (flash-m28, m55, m62, m89, five times in the m89 run alone). The
|
|
60
|
+
* replacement is skipped, the rest of the batch is applied, and the result
|
|
61
|
+
* carries `skippedNote`: one line naming the replacement, what applying it would
|
|
62
|
+
* have done, and how to say it on purpose (include the next line in `old_string`).
|
|
63
|
+
* The file's own next character decides: text on the same line is a join or a
|
|
64
|
+
* split and is refused; a blank line after it, or the end of the file, means no
|
|
65
|
+
* text crosses lines and the edit is applied as written.
|
|
54
66
|
* - **A parameter under another name is not a broken tool.** Every tool's input
|
|
55
67
|
* is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
|
|
56
68
|
* snake_case), and a handler that throws returns an error naming the missing
|
|
@@ -121,9 +133,11 @@ export * from './backends/local.js';
|
|
|
121
133
|
export * from './backends/sandbox.js';
|
|
122
134
|
export * from './browser-guard.js';
|
|
123
135
|
export * from './command-position.js';
|
|
136
|
+
export * from './cut-run.js';
|
|
124
137
|
export * from './input-normalizer.js';
|
|
125
138
|
export * from './no-test-run.js';
|
|
126
139
|
export * from './schemas.js';
|
|
140
|
+
export * from './suite-overlap.js';
|
|
127
141
|
export * from './system-prompt.js';
|
|
128
142
|
export * from './tools.js';
|
|
129
143
|
export * from './types.js';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule that decides whether a suite run may start while another is already walking, and
|
|
3
|
+
* the command readings both sides of it need.
|
|
4
|
+
*
|
|
5
|
+
* Two sites ask "may a second run start now?" about the same pair of runs — the mid-run note that
|
|
6
|
+
* tells the executor what to do next while the suite it is waiting on is still going
|
|
7
|
+
* ({@link ./utilities.js} `repeatingFailureNote`), and the hold that refuses the run when it
|
|
8
|
+
* arrives (molecule-dev's `suite-overlap-hold.ts`) — and until xr-molecule-flash-m89 they answered
|
|
9
|
+
* it separately. That run's +19:59 `wait_for_task` handed over a repeating cause with "the test
|
|
10
|
+
* alone reproduces the first one … start on them now, while the run keeps walking"; 23 s later the
|
|
11
|
+
* executor sent exactly that re-run, and the overlap hold refused it — "A playwright suite is
|
|
12
|
+
* already running against this app and database … Wait for it." Two orders, one run, and the round
|
|
13
|
+
* trip between them was the whole cost.
|
|
14
|
+
*
|
|
15
|
+
* So the rule lives here ({@link suiteRunInTheWay}) and both of them call it: a run may start only
|
|
16
|
+
* when nothing in flight is already measuring the same app and database. What "the same app and
|
|
17
|
+
* database" MEANS lives here too ({@link suiteCommandTarget}) — the note and the hold have to key
|
|
18
|
+
* a command the same way before they can agree on whether two of them collide, and the re-run a
|
|
19
|
+
* note hands over is built from the same reading of the running command
|
|
20
|
+
* ({@link scopedRerunCommand}).
|
|
21
|
+
*
|
|
22
|
+
* The same treatment for the other pair that answered one question twice: whether a run walks the
|
|
23
|
+
* WHOLE suite or a part of it. The rewriter that decides whether a background Playwright run gets
|
|
24
|
+
* `--retries=0` ({@link ./utilities.js} `streamingBackgroundCommand`) and the platform's own notes
|
|
25
|
+
* (molecule-dev's `suite-cli-override.ts`) asked it separately — a regex there, a predicate here —
|
|
26
|
+
* until xr-molecule-flash-m89 showed the two answers were never the same question
|
|
27
|
+
* ({@link isDiagnosisRun}).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* A command with its heredoc bodies removed: the runs, kills and hosts it names in a file it
|
|
33
|
+
* WRITES are not what the command itself does.
|
|
34
|
+
*
|
|
35
|
+
* The header is kept (`cat > run.sh <<'EOF' …EOF` leaves `cat > run.sh`), which is what the
|
|
36
|
+
* readers here want: everything before the body is still readable as the command's own words.
|
|
37
|
+
*
|
|
38
|
+
* @param command - The shell command.
|
|
39
|
+
* @returns The command without its heredoc bodies.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withoutHeredocs(command: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* The server a suite command points at, as a comparable key: the host it names (`HOST=`,
|
|
44
|
+
* `BASE_URL=`, `PLAYWRIGHT_BASE_URL=`, `--variable host=`, or a bare URL), or `local` for a run
|
|
45
|
+
* that names none — a suite run in the sandbox goes to the sandbox's own app unless the command
|
|
46
|
+
* says otherwise. Every localhost spelling collapses to `local` (one sandbox, one app, one
|
|
47
|
+
* database).
|
|
48
|
+
*
|
|
49
|
+
* @param command - The suite command.
|
|
50
|
+
* @returns The target key.
|
|
51
|
+
*/
|
|
52
|
+
export declare function suiteCommandTarget(command: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
55
|
+
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
56
|
+
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
57
|
+
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
58
|
+
*
|
|
59
|
+
* @param command - The whole-suite run's command.
|
|
60
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
61
|
+
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
62
|
+
* argument against.
|
|
63
|
+
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
64
|
+
*/
|
|
65
|
+
export declare function scopedRerunCommand(command: string, locations: readonly string[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
68
|
+
*
|
|
69
|
+
* Both callers describe the runs in flight the same way and ask the same question of them; what
|
|
70
|
+
* differs is only which runs each one knows about — the hold reads its ledger of background runs,
|
|
71
|
+
* the note reads the one run it is waiting on.
|
|
72
|
+
*/
|
|
73
|
+
export interface SuiteRunInFlight {
|
|
74
|
+
/** Whether the run invokes a suite at all: a `vitest`/`jest` run is not one, and cannot be. */
|
|
75
|
+
runsSuite: boolean;
|
|
76
|
+
/** The app and database the run measures, as {@link suiteCommandTarget} keys it. */
|
|
77
|
+
target: string;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the command being judged STOPS this run before starting its own — a kill comes first,
|
|
80
|
+
* so a run the command itself stops is not in the way (xr-molecule-flash-m87's
|
|
81
|
+
* `kill 19408 … ; npx playwright test …`).
|
|
82
|
+
*/
|
|
83
|
+
stoppedByCommand?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
87
|
+
*
|
|
88
|
+
* This is the ONE rule the mid-run note and the suite-overlap hold both answer with: a suite run
|
|
89
|
+
* may start only when this returns null, because two suites against one app and database share its
|
|
90
|
+
* rows — the failures one causes the other cannot be told from real ones. A run that invokes no
|
|
91
|
+
* suite is never in the way, and neither is one the candidate command stops first.
|
|
92
|
+
*
|
|
93
|
+
* @param candidate - The run about to start, by the app and database it would measure
|
|
94
|
+
* ({@link suiteCommandTarget}).
|
|
95
|
+
* @param inFlight - The suite runs already going, in the order the caller found them.
|
|
96
|
+
* @returns The first run in the way, or null.
|
|
97
|
+
*/
|
|
98
|
+
export declare function suiteRunInTheWay<T extends SuiteRunInFlight>(candidate: {
|
|
99
|
+
target: string;
|
|
100
|
+
}, inFlight: readonly T[]): T | null;
|
|
101
|
+
/**
|
|
102
|
+
* Whether a command walks a PART of a suite instead of all of it: a cap that stops it at a failure
|
|
103
|
+
* count, the failures the last run printed (`--last-failed`, `--only-changed`), a title filter, or
|
|
104
|
+
* named files/lines.
|
|
105
|
+
*
|
|
106
|
+
* THE one reading of that question, in one place. Two sites act on the answer and must never
|
|
107
|
+
* disagree:
|
|
108
|
+
*
|
|
109
|
+
* - the command rewriter that adds `--retries=0` to a background Playwright run
|
|
110
|
+
* ({@link ./utilities.js} `streamingBackgroundCommand`) — a diagnosis walks each failure once,
|
|
111
|
+
* while the run the suite is judged by keeps the suite's own retries; and
|
|
112
|
+
* - the platform's own notes (molecule-dev's `suite-cli-override.ts`), which must not tell the
|
|
113
|
+
* executor to drop the `--retries=0` the platform itself has just added — the contradiction X1
|
|
114
|
+
* flash-m74 and m75 filed, and m76 filed a third time.
|
|
115
|
+
*
|
|
116
|
+
* Until xr-molecule-flash-m89 they were a regex and a predicate that only LOOKED alike, and the
|
|
117
|
+
* gap was exactly the run the platform had asked for: its `testRunNote` says "run the whole suite
|
|
118
|
+
* once with `--max-failures=0`", and the rewriter read that cap flag as a capped diagnosis and
|
|
119
|
+
* stripped the suite's retries from the uncapped proof run that follows a deploy — the one run the
|
|
120
|
+
* graded run is compared against (+25:03, +31:39, +36:40 and +49:57, the last two against the
|
|
121
|
+
* deployed URL, where a network blip is likeliest and the retries it dropped are the ones that
|
|
122
|
+
* absorb it).
|
|
123
|
+
*
|
|
124
|
+
* `--max-failures=0` is Playwright's own "no cap" ({@link boundsFailures}), so it is not a
|
|
125
|
+
* diagnosis. A command that starts no Playwright run is not one either (false): the question is
|
|
126
|
+
* about a run already read as a Playwright suite.
|
|
127
|
+
*
|
|
128
|
+
* @param command - The command as the executor sent it.
|
|
129
|
+
* @returns True when the run is a diagnosis or a re-run, not the whole suite.
|
|
130
|
+
*/
|
|
131
|
+
export declare function isDiagnosisRun(command: string): boolean;
|
|
132
|
+
//# sourceMappingURL=suite-overlap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexF;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"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule that decides whether a suite run may start while another is already walking, and
|
|
3
|
+
* the command readings both sides of it need.
|
|
4
|
+
*
|
|
5
|
+
* Two sites ask "may a second run start now?" about the same pair of runs — the mid-run note that
|
|
6
|
+
* tells the executor what to do next while the suite it is waiting on is still going
|
|
7
|
+
* ({@link ./utilities.js} `repeatingFailureNote`), and the hold that refuses the run when it
|
|
8
|
+
* arrives (molecule-dev's `suite-overlap-hold.ts`) — and until xr-molecule-flash-m89 they answered
|
|
9
|
+
* it separately. That run's +19:59 `wait_for_task` handed over a repeating cause with "the test
|
|
10
|
+
* alone reproduces the first one … start on them now, while the run keeps walking"; 23 s later the
|
|
11
|
+
* executor sent exactly that re-run, and the overlap hold refused it — "A playwright suite is
|
|
12
|
+
* already running against this app and database … Wait for it." Two orders, one run, and the round
|
|
13
|
+
* trip between them was the whole cost.
|
|
14
|
+
*
|
|
15
|
+
* So the rule lives here ({@link suiteRunInTheWay}) and both of them call it: a run may start only
|
|
16
|
+
* when nothing in flight is already measuring the same app and database. What "the same app and
|
|
17
|
+
* database" MEANS lives here too ({@link suiteCommandTarget}) — the note and the hold have to key
|
|
18
|
+
* a command the same way before they can agree on whether two of them collide, and the re-run a
|
|
19
|
+
* note hands over is built from the same reading of the running command
|
|
20
|
+
* ({@link scopedRerunCommand}).
|
|
21
|
+
*
|
|
22
|
+
* The same treatment for the other pair that answered one question twice: whether a run walks the
|
|
23
|
+
* WHOLE suite or a part of it. The rewriter that decides whether a background Playwright run gets
|
|
24
|
+
* `--retries=0` ({@link ./utilities.js} `streamingBackgroundCommand`) and the platform's own notes
|
|
25
|
+
* (molecule-dev's `suite-cli-override.ts`) asked it separately — a regex there, a predicate here —
|
|
26
|
+
* until xr-molecule-flash-m89 showed the two answers were never the same question
|
|
27
|
+
* ({@link isDiagnosisRun}).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import { commandSegments } from './command-position.js';
|
|
32
|
+
import { commandRunsNoTests } from './no-test-run.js';
|
|
33
|
+
/**
|
|
34
|
+
* A command with its heredoc bodies removed: the runs, kills and hosts it names in a file it
|
|
35
|
+
* WRITES are not what the command itself does.
|
|
36
|
+
*
|
|
37
|
+
* The header is kept (`cat > run.sh <<'EOF' …EOF` leaves `cat > run.sh`), which is what the
|
|
38
|
+
* readers here want: everything before the body is still readable as the command's own words.
|
|
39
|
+
*
|
|
40
|
+
* @param command - The shell command.
|
|
41
|
+
* @returns The command without its heredoc bodies.
|
|
42
|
+
*/
|
|
43
|
+
export function withoutHeredocs(command) {
|
|
44
|
+
return command.replace(/<<-?\s*(['"]?)(\w+)\1[\s\S]*?\n\s*\2\b/g, ' ');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The server a suite command points at, as a comparable key: the host it names (`HOST=`,
|
|
48
|
+
* `BASE_URL=`, `PLAYWRIGHT_BASE_URL=`, `--variable host=`, or a bare URL), or `local` for a run
|
|
49
|
+
* that names none — a suite run in the sandbox goes to the sandbox's own app unless the command
|
|
50
|
+
* says otherwise. Every localhost spelling collapses to `local` (one sandbox, one app, one
|
|
51
|
+
* database).
|
|
52
|
+
*
|
|
53
|
+
* @param command - The suite command.
|
|
54
|
+
* @returns The target key.
|
|
55
|
+
*/
|
|
56
|
+
export function suiteCommandTarget(command) {
|
|
57
|
+
const text = withoutHeredocs(command);
|
|
58
|
+
const named = /(?:\b(?:HOST|BASE_URL|PLAYWRIGHT_BASE_URL|E2E_BASE_URL)|--variable\s+host)\s*=\s*['"]?([^\s'"|;&]+)/i.exec(text)?.[1] ?? /https?:\/\/[^\s'"|;&]+/i.exec(text)?.[0];
|
|
59
|
+
if (!named)
|
|
60
|
+
return 'local';
|
|
61
|
+
const value = named.trim();
|
|
62
|
+
let host;
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `http://${value}`);
|
|
65
|
+
host = url.hostname.toLowerCase();
|
|
66
|
+
if (!/^(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\[::1\])$/.test(host))
|
|
67
|
+
return url.port ? `${host}:${url.port}` : host;
|
|
68
|
+
}
|
|
69
|
+
catch (_error) {
|
|
70
|
+
// Not a URL at all (a shell variable the command does not expand): compare
|
|
71
|
+
// the token itself, so the same spelling still reads as the same target.
|
|
72
|
+
return value.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
return 'local';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
78
|
+
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
79
|
+
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
80
|
+
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
81
|
+
*
|
|
82
|
+
* @param command - The whole-suite run's command.
|
|
83
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
84
|
+
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
85
|
+
* argument against.
|
|
86
|
+
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
87
|
+
*/
|
|
88
|
+
export function scopedRerunCommand(command, locations) {
|
|
89
|
+
const at = command.search(/\bplaywright\s+test\b/);
|
|
90
|
+
if (at === -1)
|
|
91
|
+
return command;
|
|
92
|
+
const head = command.slice(0, at);
|
|
93
|
+
const rest = command.slice(at);
|
|
94
|
+
// Only the first statement runs the suite; the rest (`| tail -50`, `&& echo done`)
|
|
95
|
+
// is left exactly as it was.
|
|
96
|
+
const end = rest.search(/&&|\|\||;|\|/);
|
|
97
|
+
const statement = end === -1 ? rest : rest.slice(0, end);
|
|
98
|
+
const tail = end === -1 ? '' : rest.slice(end);
|
|
99
|
+
const scoped = statement
|
|
100
|
+
.replace(/\s*--last-failed\b/g, '')
|
|
101
|
+
.replace(/\s*--retries(?:[= ]\d+)?/g, '')
|
|
102
|
+
.replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0`);
|
|
103
|
+
return `${head}${scoped}${tail}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
107
|
+
*
|
|
108
|
+
* This is the ONE rule the mid-run note and the suite-overlap hold both answer with: a suite run
|
|
109
|
+
* may start only when this returns null, because two suites against one app and database share its
|
|
110
|
+
* rows — the failures one causes the other cannot be told from real ones. A run that invokes no
|
|
111
|
+
* suite is never in the way, and neither is one the candidate command stops first.
|
|
112
|
+
*
|
|
113
|
+
* @param candidate - The run about to start, by the app and database it would measure
|
|
114
|
+
* ({@link suiteCommandTarget}).
|
|
115
|
+
* @param inFlight - The suite runs already going, in the order the caller found them.
|
|
116
|
+
* @returns The first run in the way, or null.
|
|
117
|
+
*/
|
|
118
|
+
export function suiteRunInTheWay(candidate, inFlight) {
|
|
119
|
+
return (inFlight.find((run) => run.runsSuite && run.target === candidate.target && !run.stoppedByCommand) ?? null);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The Playwright invocation of a command, as its own command-position text (`playwright test …`),
|
|
123
|
+
* or null when the command starts no Playwright run.
|
|
124
|
+
*
|
|
125
|
+
* The runner's own word has to END where the sub-command starts (`(?=\s|$)`, not a `\b`): a
|
|
126
|
+
* segment can be a fragment beginning at a `playwright test` printed INSIDE a string
|
|
127
|
+
* (`echo "then npx playwright test" && npx playwright test`), and a match that ends against the
|
|
128
|
+
* closing quote is that echo. A version-pinned `playwright@1.44.0 test` is the same run.
|
|
129
|
+
*/
|
|
130
|
+
const PLAYWRIGHT_RUN = /^(?:@playwright\/test|playwright)(?:@\S+)?\s+test(?=\s|$)/;
|
|
131
|
+
/**
|
|
132
|
+
* What narrows a Playwright run to a part of the suite: a named file or line, a title filter, or
|
|
133
|
+
* the tests the run before it printed (`--last-failed`, `--only-changed`).
|
|
134
|
+
*/
|
|
135
|
+
const NARROWED_RUN = /\.(?:spec|test)\.[cm]?[jt]sx?\b|\.[cm]?[jt]sx?:\d+|(?:^|\s)(?:-g|--grep(?:-invert)?|--last-failed|--only-changed)\b/;
|
|
136
|
+
/**
|
|
137
|
+
* The Playwright invocation's text when the command starts one, or null.
|
|
138
|
+
*
|
|
139
|
+
* @param command - The command as the executor sent it.
|
|
140
|
+
* @returns The invocation's command-position text, or null.
|
|
141
|
+
*/
|
|
142
|
+
function playwrightRun(command) {
|
|
143
|
+
return (commandSegments(withoutHeredocs(command)).find((segment) => PLAYWRIGHT_RUN.test(segment.text))
|
|
144
|
+
?.text ?? null);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether a `--max-failures` BOUNDS the walk: the flag is given a count above zero.
|
|
148
|
+
*
|
|
149
|
+
* Playwright's own spelling of "no cap" is zero — `maxFailures: 0` "disables the feature" — and
|
|
150
|
+
* that is the flag the platform's `testRunNote` asks for on the uncapped whole-suite run ("run the
|
|
151
|
+
* whole suite once with `--max-failures=0`"), which is why this is not a plain flag test.
|
|
152
|
+
*
|
|
153
|
+
* @param command - The command as the executor sent it.
|
|
154
|
+
* @returns True when a failure cap would stop the walk.
|
|
155
|
+
*/
|
|
156
|
+
function boundsFailures(command) {
|
|
157
|
+
const flag = /(?:^|[\s;&|(])--max-failures(?![-\w])/.exec(command);
|
|
158
|
+
if (!flag)
|
|
159
|
+
return false;
|
|
160
|
+
const value = /(?:^|[\s;&|(])--max-failures(?:=|\s+)(['"]?)(\d[\d_]*)/.exec(command)?.[2];
|
|
161
|
+
// A `--max-failures` with no count at all is not the uncapped spelling of anything: it is read
|
|
162
|
+
// as a cap, the way Playwright's own default reading of the flag is a count of failures.
|
|
163
|
+
if (value === undefined)
|
|
164
|
+
return true;
|
|
165
|
+
return Number(value.replace(/_/g, '')) !== 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Whether a command walks a PART of a suite instead of all of it: a cap that stops it at a failure
|
|
169
|
+
* count, the failures the last run printed (`--last-failed`, `--only-changed`), a title filter, or
|
|
170
|
+
* named files/lines.
|
|
171
|
+
*
|
|
172
|
+
* THE one reading of that question, in one place. Two sites act on the answer and must never
|
|
173
|
+
* disagree:
|
|
174
|
+
*
|
|
175
|
+
* - the command rewriter that adds `--retries=0` to a background Playwright run
|
|
176
|
+
* ({@link ./utilities.js} `streamingBackgroundCommand`) — a diagnosis walks each failure once,
|
|
177
|
+
* while the run the suite is judged by keeps the suite's own retries; and
|
|
178
|
+
* - the platform's own notes (molecule-dev's `suite-cli-override.ts`), which must not tell the
|
|
179
|
+
* executor to drop the `--retries=0` the platform itself has just added — the contradiction X1
|
|
180
|
+
* flash-m74 and m75 filed, and m76 filed a third time.
|
|
181
|
+
*
|
|
182
|
+
* Until xr-molecule-flash-m89 they were a regex and a predicate that only LOOKED alike, and the
|
|
183
|
+
* gap was exactly the run the platform had asked for: its `testRunNote` says "run the whole suite
|
|
184
|
+
* once with `--max-failures=0`", and the rewriter read that cap flag as a capped diagnosis and
|
|
185
|
+
* stripped the suite's retries from the uncapped proof run that follows a deploy — the one run the
|
|
186
|
+
* graded run is compared against (+25:03, +31:39, +36:40 and +49:57, the last two against the
|
|
187
|
+
* deployed URL, where a network blip is likeliest and the retries it dropped are the ones that
|
|
188
|
+
* absorb it).
|
|
189
|
+
*
|
|
190
|
+
* `--max-failures=0` is Playwright's own "no cap" ({@link boundsFailures}), so it is not a
|
|
191
|
+
* diagnosis. A command that starts no Playwright run is not one either (false): the question is
|
|
192
|
+
* about a run already read as a Playwright suite.
|
|
193
|
+
*
|
|
194
|
+
* @param command - The command as the executor sent it.
|
|
195
|
+
* @returns True when the run is a diagnosis or a re-run, not the whole suite.
|
|
196
|
+
*/
|
|
197
|
+
export function isDiagnosisRun(command) {
|
|
198
|
+
if (boundsFailures(command))
|
|
199
|
+
return true;
|
|
200
|
+
const run = playwrightRun(command);
|
|
201
|
+
if (run === null)
|
|
202
|
+
return false;
|
|
203
|
+
// A run that only collects and prints (`playwright test --list`, `--version`) walks no part of
|
|
204
|
+
// the suite either, and is not the whole-suite run the retries belong on.
|
|
205
|
+
if (commandRunsNoTests(run))
|
|
206
|
+
return true;
|
|
207
|
+
return NARROWED_RUN.test(run);
|
|
208
|
+
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -26,6 +26,26 @@ import type { AITool } from '@molecule/api-ai';
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function taskStopCommand(backend: ExecutionBackend, taskId: string): Promise<string>;
|
|
28
28
|
import type { ExecutionBackend, ToolBuildConfig } from './types.js';
|
|
29
|
+
/**
|
|
30
|
+
* The closing advice on an over-budget message for a command that is not a test
|
|
31
|
+
* suite: the work it was chaining is what to split.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SPLIT_WORK_ADVICE: string;
|
|
34
|
+
/**
|
|
35
|
+
* The closing advice on an over-budget message for a command that IS a suite run
|
|
36
|
+
* (Playwright, Vitest, a `hurl` run — {@link isSuiteRunCommand}): its files are not
|
|
37
|
+
* a bigger unit to break up.
|
|
38
|
+
*
|
|
39
|
+
* X1 flash-m36 asked for this half and the message kept saying "run a smaller unit"
|
|
40
|
+
* for two months. It is the wrong direction for a suite, and measurably: a run
|
|
41
|
+
* stopped here reports NO tally, so nothing in the result says which test or request
|
|
42
|
+
* hung — the run has to be sent again in the background (off this budget, its output
|
|
43
|
+
* written to a log that can be read mid-run) or, when a consumer knows the runner's
|
|
44
|
+
* per-request limit, with each request bounded. xr-molecule-flash-m93 (+06:22,
|
|
45
|
+
* 2026-10-16) lost 4.8 minutes to a Hurl run whose ONE unanswered request had hurl's
|
|
46
|
+
* 300 s default per request holding the file, ten seconds past this tool's budget.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SUITE_OVER_BUDGET_ADVICE: string;
|
|
29
49
|
/**
|
|
30
50
|
* The steer attached to a SUCCESSFUL read of a package's built output. The ENOENT
|
|
31
51
|
* redirect below fires only when the read FAILS, so a model that knows the hoisted
|
|
@@ -39,6 +59,31 @@ import type { ExecutionBackend, ToolBuildConfig } from './types.js';
|
|
|
39
59
|
* @returns The note, or null when the path is not inside a package's `dist`.
|
|
40
60
|
*/
|
|
41
61
|
export declare function moleculeDistReadNote(path: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* The refusal for a write whose target is inside an installed package, or null
|
|
64
|
+
* when the path is not under a `node_modules` directory.
|
|
65
|
+
*
|
|
66
|
+
* `node_modules` is not the project's code: nothing written there survives the
|
|
67
|
+
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
68
|
+
* shared (the pre-installed superset) breaks every other run through it. X0
|
|
69
|
+
* xr-molecule-flash-m91 (+04:43) called `write_file` on
|
|
70
|
+
* `node_modules/@molecule/api-resource-comment/dist/numbers.d.ts` while reading
|
|
71
|
+
* the package's declarations and clobbered its runtime module a call later
|
|
72
|
+
* (`module.exports = {}` over the ESM handler its own code imports) — the write
|
|
73
|
+
* answered `ok: true`, and 18 s went to noticing, `npm pack`-ing the package and
|
|
74
|
+
* putting the file back. Reading these files stays allowed (that is how the
|
|
75
|
+
* declarations above are read at all).
|
|
76
|
+
*
|
|
77
|
+
* The restore route is named because the model that has ALREADY clobbered a file
|
|
78
|
+
* (this refusal is too late for it) is the one who needs it, and because the
|
|
79
|
+
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
80
|
+
* sandbox. `npm pack` + a `tar` extraction over the package directory is what
|
|
81
|
+
* m91 used, and what both guards leave alone.
|
|
82
|
+
*
|
|
83
|
+
* @param path - An absolute path a tool is about to write.
|
|
84
|
+
* @returns The one-line refusal naming the package, or null when nothing is installed there.
|
|
85
|
+
*/
|
|
86
|
+
export declare function installedPackageWriteRefusal(path: string): string | null;
|
|
42
87
|
/**
|
|
43
88
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
44
89
|
*
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -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;
|
|
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;AAkF9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAwHD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAgEnE;;;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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBxE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAipExF"}
|