@molecule/api-ai-tools 1.5.19 → 1.6.0
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 +343 -37
- package/dist/command-position.d.ts +62 -0
- package/dist/command-position.d.ts.map +1 -0
- package/dist/command-position.js +352 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/no-test-run.d.ts +58 -0
- package/dist/no-test-run.d.ts.map +1 -0
- package/dist/no-test-run.js +134 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +10 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +118 -51
- package/dist/utilities.d.ts +208 -39
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +495 -91
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-16T15:16:34.345Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -58,6 +58,24 @@ npm install @molecule/api-ai-tools @molecule/api-ai
|
|
|
58
58
|
|
|
59
59
|
### Interfaces
|
|
60
60
|
|
|
61
|
+
#### `CommandSegment`
|
|
62
|
+
|
|
63
|
+
One statement or pipeline stage of a shell command: what it runs, and with what.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
interface CommandSegment {
|
|
67
|
+
/**
|
|
68
|
+
* The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
|
|
69
|
+
* the shell would run once whatever wrappers stood in front of it are taken away.
|
|
70
|
+
*/
|
|
71
|
+
program: string
|
|
72
|
+
/** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
|
|
73
|
+
args: readonly string[]
|
|
74
|
+
/** The program and its arguments as one line — what a runner pattern is matched against. */
|
|
75
|
+
text: string
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
61
79
|
#### `NormalizableSchema`
|
|
62
80
|
|
|
63
81
|
The JSON-Schema-ish shape every tool in this package declares.
|
|
@@ -358,13 +376,44 @@ A cause repeating among a running suite's failures.
|
|
|
358
376
|
|
|
359
377
|
```typescript
|
|
360
378
|
interface RepeatingFailure {
|
|
361
|
-
/**
|
|
379
|
+
/**
|
|
380
|
+
* How many DISTINCT tests share the signature — a test printed twice (two reporters in the
|
|
381
|
+
* log) or failed twice (a retry) is one test.
|
|
382
|
+
*/
|
|
362
383
|
count: number
|
|
363
384
|
/** The shared error line, when the log carries the failures' bodies (`line` reporter). */
|
|
364
385
|
message: string | null
|
|
365
386
|
/** The shared duration in seconds, when the failures line up by how long they ran. */
|
|
366
387
|
seconds: number | null
|
|
367
|
-
/** The
|
|
388
|
+
/** The distinct tests that share it, in log order. */
|
|
389
|
+
tests: FailedTest[]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### `UniversalCause`
|
|
394
|
+
|
|
395
|
+
A repeating cause that is failing the WHOLE run — see {@link universalCause}.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
interface UniversalCause {
|
|
399
|
+
/** How many distinct tests share it. */
|
|
400
|
+
count: number
|
|
401
|
+
/** The distinct spec files they live in, in the order the log named them. */
|
|
402
|
+
files: string[]
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### `FailureCause`
|
|
407
|
+
|
|
408
|
+
One cause of failure in a run, and the tests it hit.
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
interface FailureCause {
|
|
412
|
+
/** The error line its tests all reported, or null when only the duration ties them together. */
|
|
413
|
+
message: string | null
|
|
414
|
+
/** The duration they shared in seconds, or null when the error line is the signature. */
|
|
415
|
+
seconds: number | null
|
|
416
|
+
/** The distinct tests it hit, in the order the log named them. */
|
|
368
417
|
tests: FailedTest[]
|
|
369
418
|
}
|
|
370
419
|
```
|
|
@@ -419,6 +468,37 @@ function createSandboxBackend(sandbox: SandboxLike, projectRoot?: string): Execu
|
|
|
419
468
|
|
|
420
469
|
**Returns:** A backend that proxies all filesystem calls into the sandbox.
|
|
421
470
|
|
|
471
|
+
#### `commandSegments(command)`
|
|
472
|
+
|
|
473
|
+
The segments of a command, each with its program and arguments — the answer every classifier
|
|
474
|
+
that reads a command as "this runs the suite" is asking for.
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
function commandSegments(command: string): CommandSegment[]
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
- `command` — The shell command.
|
|
481
|
+
|
|
482
|
+
**Returns:** One entry per statement or pipeline stage that runs something, in order.
|
|
483
|
+
|
|
484
|
+
#### `runsInCommandPosition(command, pattern)`
|
|
485
|
+
|
|
486
|
+
Whether any segment of the command runs a program whose command-position text matches
|
|
487
|
+
`pattern` — the one question a runner-kind table asks.
|
|
488
|
+
|
|
489
|
+
The pattern is matched against `program` and its arguments as one line, so it must be
|
|
490
|
+
anchored at `^`; it must not carry the `g` flag (a pattern shared between calls would then
|
|
491
|
+
carry its own match position from one segment to the next).
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
function runsInCommandPosition(command: string, pattern: RegExp): boolean
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
- `command` — The shell command.
|
|
498
|
+
- `pattern` — An anchored pattern for the runner's invocation.
|
|
499
|
+
|
|
500
|
+
**Returns:** True when a segment in command position matches.
|
|
501
|
+
|
|
422
502
|
#### `normalizeToolInput(schema, raw)`
|
|
423
503
|
|
|
424
504
|
Move known aliases onto the declared parameter names and report what is still
|
|
@@ -499,6 +579,33 @@ function guardAITool(tool: T, options?: GuardOptions): T
|
|
|
499
579
|
|
|
500
580
|
**Returns:** A copy with a guarded `execute`.
|
|
501
581
|
|
|
582
|
+
#### `commandRunsNoTests(command)`
|
|
583
|
+
|
|
584
|
+
Whether a shell command invokes a test runner and EVERY such invocation only prints — so the
|
|
585
|
+
command runs no test at all.
|
|
586
|
+
|
|
587
|
+
A runner counts only where a shell would RUN it ({@link commandSegments}): the word in command
|
|
588
|
+
position of a segment, after its wrapper prefixes. A runner named in an argument is not an
|
|
589
|
+
invocation — `head -30 run-api-tests-hurl.sh` and `grep -n playwright e2e/README.md` name a
|
|
590
|
+
file to read, and a `--list` written inside a file the command writes is not a flag anyone runs
|
|
591
|
+
with (flash-m81).
|
|
592
|
+
|
|
593
|
+
A command that invokes no runner is NOT one of these (false): "the command runs no test" is
|
|
594
|
+
then true for `ls`, which is not the question any caller is asking. The question is whether
|
|
595
|
+
the runner invocation each caller read as a run is one.
|
|
596
|
+
|
|
597
|
+
A command that also runs tests is false however it is spelled: `npx playwright install &&
|
|
598
|
+
npx playwright test` installs and runs, so a caller that sees `playwright test` in it is
|
|
599
|
+
reading a real run.
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
function commandRunsNoTests(command: string): boolean
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
- `command` — The shell command as the executor sent it.
|
|
606
|
+
|
|
607
|
+
**Returns:** True when the command invokes a runner only to print.
|
|
608
|
+
|
|
502
609
|
#### `discoverSkills(backend)`
|
|
503
610
|
|
|
504
611
|
Discover skills from a project directory.
|
|
@@ -732,16 +839,15 @@ killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
|
|
|
732
839
|
often `html`, which prints nothing per test. `--reporter=list` is added
|
|
733
840
|
when the playwright run is the command's last step and nothing is piped or
|
|
734
841
|
redirected from it: it writes each result as it finishes, and it is the
|
|
735
|
-
shape the platform reads a background suite back in
|
|
736
|
-
fail-fast note and the Tests bar both parse `list` lines). A run that names
|
|
737
|
-
`--reporter=line` is switched to `list` for the same reason — `line` writes
|
|
738
|
-
`[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
|
|
739
|
-
8.3-minute walk of one suite could never trip the fail-fast note.
|
|
842
|
+
shape the platform reads a background suite back in.
|
|
740
843
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
844
|
+
The run ends up with exactly ONE reporter that prints each failure — `list`. A run that names
|
|
845
|
+
any other printing reporter has it REPLACED, never joined: two of them write every test result
|
|
846
|
+
into the log twice, which on X1 flash-m83 (2026-10-16) made a `--reporter=list,line` run (its
|
|
847
|
+
`line` added by this function one run earlier) report two distinct failures as "4 of this run's
|
|
848
|
+
tests have already failed the same way", and the executor stopped a 139-test walk at test 21
|
|
849
|
+
with a single cause in hand. `line` alone is no use either: it writes `[n/N] <title>` with no
|
|
850
|
+
✓/✘ mark and no duration, so on X1 flash-m62 an 8.3-minute walk of one suite could never trip
|
|
745
851
|
|
|
746
852
|
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
|
|
747
853
|
|
|
@@ -758,6 +864,10 @@ function streamingBackgroundCommand(command: string): { command: string; changes
|
|
|
758
864
|
Whether a command runs a whole test suite — a Playwright, Vitest or Hurl run
|
|
759
865
|
— rather than a build, an install or a one-off script.
|
|
760
866
|
|
|
867
|
+
A command that invokes a runner only to print is not one of these
|
|
868
|
+
({@link commandRunsNoTests}): `npx playwright test --list` prints the tests it would run and
|
|
869
|
+
exits, and `npx playwright install`, `vitest list` and `hurl --help` start no test either.
|
|
870
|
+
|
|
761
871
|
```typescript
|
|
762
872
|
function isSuiteRunCommand(command: string): boolean
|
|
763
873
|
```
|
|
@@ -766,6 +876,27 @@ function isSuiteRunCommand(command: string): boolean
|
|
|
766
876
|
|
|
767
877
|
**Returns:** True when the command starts one of those runners.
|
|
768
878
|
|
|
879
|
+
#### `isPlaywrightRunCommand(command)`
|
|
880
|
+
|
|
881
|
+
Whether a command starts a Playwright run — a `playwright test` in command position, in a form
|
|
882
|
+
that runs something.
|
|
883
|
+
|
|
884
|
+
`npx playwright test --list` matched every `\bplaywright\s+test\b` test in the platform and was
|
|
885
|
+
then treated as a run: held (flash-m79's config check), backgrounded for a run that outlasts
|
|
886
|
+
the tool's ceiling, and rewritten with `--reporter`. A list takes two seconds, prints its lines
|
|
887
|
+
and exits, so it is none of those; {@link commandRunsNoTests} is what decides.
|
|
888
|
+
|
|
889
|
+
A `playwright test` named as an argument is not a run either (`head -30 run-tests.sh`,
|
|
890
|
+
`grep -n "playwright test" e2e/README.md`): the command that runs is the reader.
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
function isPlaywrightRunCommand(command: string): boolean
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
- `command` — The shell command.
|
|
897
|
+
|
|
898
|
+
**Returns:** True when the command starts a Playwright run.
|
|
899
|
+
|
|
769
900
|
#### `suiteRunPriorityLaunch(command, launch)`
|
|
770
901
|
|
|
771
902
|
Start a background suite run at lower CPU and IO priority, so the sandbox it
|
|
@@ -947,6 +1078,38 @@ function sliceLines(
|
|
|
947
1078
|
|
|
948
1079
|
**Returns:** The window plus its position, or null when neither was given.
|
|
949
1080
|
|
|
1081
|
+
#### `serializedChars(text)`
|
|
1082
|
+
|
|
1083
|
+
A file's text as the request counts it: the length of its JSON string.
|
|
1084
|
+
|
|
1085
|
+
JSON escaping (`\n` → `\\n`, `"` → `\"`) costs characters, and the request
|
|
1086
|
+
side counts the SERIALIZED tool result. A window of 59,000 characters of
|
|
1087
|
+
source — 1,200 lines — arrives as ~60,200 characters of JSON, over the
|
|
1088
|
+
ceiling it was sized for, so a window is measured with this rather than by
|
|
1089
|
+
its raw text length.
|
|
1090
|
+
|
|
1091
|
+
```typescript
|
|
1092
|
+
function serializedChars(text: string): number
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
- `text` — The text.
|
|
1096
|
+
|
|
1097
|
+
**Returns:** Its serialized length, without the surrounding quotes.
|
|
1098
|
+
|
|
1099
|
+
#### `linesWithinSerialized(content, chars)`
|
|
1100
|
+
|
|
1101
|
+
How many leading lines of a file fit inside a budget of SERIALIZED chars,
|
|
1102
|
+
the way `read_file` sizes a window (see {@link serializedChars}).
|
|
1103
|
+
|
|
1104
|
+
```typescript
|
|
1105
|
+
function linesWithinSerialized(content: string, chars: number): number
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
- `content` — The file's full text.
|
|
1109
|
+
- `chars` — The budget in serialized chars.
|
|
1110
|
+
|
|
1111
|
+
**Returns:** The line count, at least one.
|
|
1112
|
+
|
|
950
1113
|
#### `looksLikeHtml(text)`
|
|
951
1114
|
|
|
952
1115
|
Whether `text` is an HTML document (a doctype or an `<html` root tag).
|
|
@@ -1069,41 +1232,117 @@ The cause repeating among a running suite's failures, or null.
|
|
|
1069
1232
|
|
|
1070
1233
|
Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
|
|
1071
1234
|
path and how long it ran — and defers each failure's error to the summary at the end, so
|
|
1072
|
-
mid-run the only signature those lines carry is the duration.
|
|
1073
|
-
failure bodies
|
|
1074
|
-
which is stronger (it needs no assumption about how long a
|
|
1075
|
-
message is looked for first.
|
|
1235
|
+
mid-run the only signature those lines carry is the duration. Where the log carries the
|
|
1236
|
+
failure bodies (a `line` reporter's output, or a finished run's summary) the repeated error
|
|
1237
|
+
message itself is the signature, which is stronger (it needs no assumption about how long a
|
|
1238
|
+
failure should take), so the message is looked for first.
|
|
1076
1239
|
|
|
1077
1240
|
Either way the point is the same: a suite whose failures repeat one cause spends its whole
|
|
1078
1241
|
runtime proving that one cause, and the executor watching it can be told so at the third
|
|
1079
1242
|
failure instead of at the last. X1 flash-m76 walked 15 tests in 4.0 minutes where 15.6 s
|
|
1080
1243
|
each was the test timeout and one missing `navigate('/')` was the cause of all of them.
|
|
1081
1244
|
|
|
1245
|
+
The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
|
|
1246
|
+
prints each failure twice (a second reporter, a retry) had X1 flash-m83 told that four of its
|
|
1247
|
+
tests had failed the same way when two had, so the executor stopped a 139-test walk at test 21
|
|
1248
|
+
with one cause in hand (2026-10-16).
|
|
1249
|
+
|
|
1082
1250
|
```typescript
|
|
1083
1251
|
function repeatingFailure(log: string): RepeatingFailure | null
|
|
1084
1252
|
```
|
|
1085
1253
|
|
|
1086
1254
|
- `log` — The log so far.
|
|
1087
1255
|
|
|
1088
|
-
**Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN}
|
|
1256
|
+
**Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} distinct tests behind it.
|
|
1257
|
+
|
|
1258
|
+
#### `universalCause(log, found)`
|
|
1259
|
+
|
|
1260
|
+
Whether a repeating cause is failing the whole run — the one case where stopping it is the
|
|
1261
|
+
right call.
|
|
1262
|
+
|
|
1263
|
+
The test is: the cause accounts for every failure the log names, it has hit
|
|
1264
|
+
{@link UNIVERSAL_CAUSE_FILES} spec files or more, and nothing has passed since its first
|
|
1265
|
+
failure. An app that is not up, a 5xx on every request, or a selector that is missing on every
|
|
1266
|
+
page all look like that, and every remaining test will fail the same way — so the run's
|
|
1267
|
+
remaining minutes measure nothing. Anything less is NOT this: a cause that stops at one file,
|
|
1268
|
+
or that leaves other tests passing, or that shares the run with failures of another shape,
|
|
1269
|
+
leaves the run with something to say, and the run is the only thing that will say it. X1
|
|
1270
|
+
flash-m83 (2026-10-16) stopped eight local runs at their first repeating cause over 14.5
|
|
1271
|
+
minutes and never finished one; three of its deploys each fixed a single cause that a finished
|
|
1272
|
+
run would have listed alongside the others.
|
|
1273
|
+
|
|
1274
|
+
```typescript
|
|
1275
|
+
function universalCause(log: string, found: RepeatingFailure): UniversalCause | null
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
- `log` — The log so far.
|
|
1279
|
+
- `found` — The repeating cause {@link repeatingFailure} found in it.
|
|
1280
|
+
|
|
1281
|
+
**Returns:** The cause's count and files, or null when the run is not failing whole.
|
|
1282
|
+
|
|
1283
|
+
#### `failuresByCause(log)`
|
|
1284
|
+
|
|
1285
|
+
Every failure a run names, grouped by the cause behind it — one group per cause, with the
|
|
1286
|
+
tests it hit.
|
|
1287
|
+
|
|
1288
|
+
The point is that ONE run yields EVERY cause. A Playwright report prints its failures one
|
|
1289
|
+
after another with no statement of which of them share a cause, so a run of 19 failures is 19
|
|
1290
|
+
bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
|
|
1291
|
+
single cause, two of them found by runs stopped before they walked the rest.
|
|
1292
|
+
|
|
1293
|
+
The signature is the failures' own error line where the log carries their bodies (a finished
|
|
1294
|
+
run, or a `line` reporter), and how long they took where it does not (a still-running `list`
|
|
1295
|
+
log). A test the log printed twice, or failed twice, is one test.
|
|
1296
|
+
|
|
1297
|
+
```typescript
|
|
1298
|
+
function failuresByCause(log: string): FailureCause[]
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
- `log` — The log of a finished or running run.
|
|
1302
|
+
|
|
1303
|
+
**Returns:** The causes, the one with the most tests first.
|
|
1304
|
+
|
|
1305
|
+
#### `failureCausesNote(log)`
|
|
1306
|
+
|
|
1307
|
+
The one-line-per-cause summary of a finished run's failures, or null when the run named fewer
|
|
1308
|
+
than two of them.
|
|
1309
|
+
|
|
1310
|
+
This is what makes a run worth finishing: every cause the run found, in the result the
|
|
1311
|
+
executor reads, so one round of fixes covers all of them rather than the one cause a run
|
|
1312
|
+
stopped early happened to reach.
|
|
1313
|
+
|
|
1314
|
+
```typescript
|
|
1315
|
+
function failureCausesNote(log: string): string | null
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
- `log` — The finished run's output.
|
|
1319
|
+
|
|
1320
|
+
**Returns:** The note, or null.
|
|
1089
1321
|
|
|
1090
1322
|
#### `repeatingFailureNote(found, o)`
|
|
1091
1323
|
|
|
1092
|
-
The sentence that names a cause repeating among a running suite's failures,
|
|
1093
|
-
that stops the run so the cause can be fixed first.
|
|
1324
|
+
The sentence that names a cause repeating among a running suite's failures, mid-run.
|
|
1094
1325
|
|
|
1095
1326
|
Kept here, beside {@link repeatingFailure}, so the two stay in step: every word below is about
|
|
1096
|
-
what that function could actually see in the log
|
|
1327
|
+
what that function could actually see in the log — including the count, which is of DISTINCT
|
|
1328
|
+
tests, so the note never names one or two tests as a cause.
|
|
1329
|
+
|
|
1330
|
+
The note names the cause early so the executor can START FIXING while the run keeps walking,
|
|
1331
|
+
and it asks for the run to be stopped only when {@link universalCause} says the cause is
|
|
1332
|
+
failing the whole run. X1 flash-m76 (2026-10-16) spent 4.0 minutes walking 15 tests whose one
|
|
1333
|
+
cause was certain at the third, 47 s in, and the note answers exactly that; X1 flash-m83 then
|
|
1334
|
+
stopped eight runs at their first repeating cause in 14.5 minutes and never finished one, so
|
|
1335
|
+
the note must not say to stop when the run still has other causes to name.
|
|
1097
1336
|
|
|
1098
1337
|
```typescript
|
|
1099
1338
|
function repeatingFailureNote(
|
|
1100
1339
|
found: RepeatingFailure,
|
|
1101
|
-
o: { taskId: string; log: string; stop: string },
|
|
1340
|
+
o: { taskId: string; log: string; stop: string; universal?: UniversalCause | null },
|
|
1102
1341
|
): string
|
|
1103
1342
|
```
|
|
1104
1343
|
|
|
1105
1344
|
- `found` — What {@link repeatingFailure} found.
|
|
1106
|
-
- `o` — The run's facts: its `taskId`, its `log` path,
|
|
1345
|
+
- `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for a {@link UniversalCause} — and `universal`, what {@link universalCause} found (absent means the run is not failing whole).
|
|
1107
1346
|
|
|
1108
1347
|
**Returns:** The note.
|
|
1109
1348
|
|
|
@@ -1230,34 +1469,67 @@ const MAX_SEARCH_CONTEXT_LINES: 40
|
|
|
1230
1469
|
#### `MAX_BATCH_READ_FILES`
|
|
1231
1470
|
|
|
1232
1471
|
Most files one batched `read_file` call returns. The cap exists so a survey
|
|
1233
|
-
of a large tree cannot swallow the turn's context in one result;
|
|
1234
|
-
|
|
1472
|
+
of a large tree cannot swallow the turn's context in one result; every path
|
|
1473
|
+
past it is named in the result's `note` and listed with its own `error`, so
|
|
1474
|
+
the model continues from there.
|
|
1235
1475
|
|
|
1236
1476
|
```typescript
|
|
1237
1477
|
const MAX_BATCH_READ_FILES: 25
|
|
1238
1478
|
```
|
|
1239
1479
|
|
|
1240
|
-
#### `
|
|
1480
|
+
#### `MAX_READ_CALL_CHARS`
|
|
1481
|
+
|
|
1482
|
+
The most one `read_file` RESULT may carry, in chars of its JSON — every file
|
|
1483
|
+
the call returns, the envelope and the note included. That is how the request
|
|
1484
|
+
side counts a tool result, so it is how this tool counts one too
|
|
1485
|
+
({@link serializedChars}).
|
|
1486
|
+
|
|
1487
|
+
One tool result is ONE block in the request, and the request side caps a
|
|
1488
|
+
block: molecule-dev cuts any block over `MAX_TAIL_BLOCK_CHARS` (60,000) in
|
|
1489
|
+
place, keeping ~30 KB of each end. A batched read that crossed it lost its
|
|
1490
|
+
MIDDLE — the cut lands between two `"content"` values, so whole files fell
|
|
1491
|
+
out and the note in their place said only "N characters cut here" — while
|
|
1492
|
+
this tool's own note had promised each of those files that it "fits in one
|
|
1493
|
+
call, so it is not paged": X0 flash-m81 (2026-10-16) read ten e2e specs in
|
|
1494
|
+
one call (121,467 chars), lost the middle six, and bought them again in six
|
|
1495
|
+
further calls (52,908 chars).
|
|
1496
|
+
|
|
1497
|
+
So the CALL gets the ceiling the request gives it. Whole files are returned
|
|
1498
|
+
up to it; the paths that did not fit are named in `note` and listed in
|
|
1499
|
+
`files` with their own `error`, the shape every other entry has. No file is
|
|
1500
|
+
ever cut, and no read result reaches the request-side cut. A test in
|
|
1501
|
+
molecule-dev's `context-fit` asserts the two ceilings still agree.
|
|
1502
|
+
|
|
1503
|
+
```typescript
|
|
1504
|
+
const MAX_READ_CALL_CHARS: 60000
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
#### `READ_RESULT_ENVELOPE_CHARS`
|
|
1508
|
+
|
|
1509
|
+
Room inside one read result for what is not file content: the `path`, the
|
|
1510
|
+
offset/line fields, and the note.
|
|
1241
1511
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1512
|
+
A batch sizes each file against what the call has left
|
|
1513
|
+
({@link MAX_READ_CALL_CHARS} less this), so the entry that does not fit is
|
|
1514
|
+
caught here rather than by the request-side cut.
|
|
1244
1515
|
|
|
1245
1516
|
```typescript
|
|
1246
|
-
const
|
|
1517
|
+
const READ_RESULT_ENVELOPE_CHARS: 1000
|
|
1247
1518
|
```
|
|
1248
1519
|
|
|
1249
1520
|
#### `MAX_READ_RETURN_CHARS`
|
|
1250
1521
|
|
|
1251
1522
|
The most one read_file call returns for ONE file when no window was asked
|
|
1252
|
-
for, in chars
|
|
1253
|
-
saying how to read the rest.
|
|
1523
|
+
for, in serialized chars: larger files come back as their first window, with
|
|
1524
|
+
a note saying how to read the rest.
|
|
1254
1525
|
|
|
1255
1526
|
MAX_READ_SIZE (5 MB) is a bound on what the tool will open, not on what it
|
|
1256
1527
|
should hand a model: a single batched read that returned a 1.2 MB README
|
|
1257
1528
|
put the next three provider calls at 366k tokens (~330k of them fresh)
|
|
1258
|
-
against a 120k-token context cap (X0 run x5, call 26).
|
|
1259
|
-
|
|
1260
|
-
|
|
1529
|
+
against a 120k-token context cap (X0 run x5, call 26). This is the call's
|
|
1530
|
+
own ceiling ({@link MAX_READ_CALL_CHARS}) less the room its envelope takes,
|
|
1531
|
+
so one read result — one file or twenty-five — fits inside what the request
|
|
1532
|
+
side carries without being cut.
|
|
1261
1533
|
|
|
1262
1534
|
```typescript
|
|
1263
1535
|
const MAX_READ_RETURN_CHARS: number
|
|
@@ -1380,7 +1652,8 @@ const REPEATING_CAUSE_TAIL_BYTES: 40000
|
|
|
1380
1652
|
|
|
1381
1653
|
#### `REPEATING_FAILURE_MIN`
|
|
1382
1654
|
|
|
1383
|
-
|
|
1655
|
+
DISTINCT tests that must share one signature before a running suite's repeating cause is named.
|
|
1656
|
+
A test the log prints twice, or a test that failed twice, is still one test.
|
|
1384
1657
|
|
|
1385
1658
|
```typescript
|
|
1386
1659
|
const REPEATING_FAILURE_MIN: 3
|
|
@@ -1394,6 +1667,35 @@ How many of a repeating cause's failures the note lists before it says "and N mo
|
|
|
1394
1667
|
const REPEATING_FAILURE_LISTED: 5
|
|
1395
1668
|
```
|
|
1396
1669
|
|
|
1670
|
+
#### `FAILURE_CAUSES_LISTED`
|
|
1671
|
+
|
|
1672
|
+
How many causes a finished run's summary lists before it says "and N more".
|
|
1673
|
+
|
|
1674
|
+
```typescript
|
|
1675
|
+
const FAILURE_CAUSES_LISTED: 5
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
#### `FAILURE_CAUSE_TESTS_LISTED`
|
|
1679
|
+
|
|
1680
|
+
How many of one cause's tests that summary names before it says "and N more".
|
|
1681
|
+
|
|
1682
|
+
```typescript
|
|
1683
|
+
const FAILURE_CAUSE_TESTS_LISTED: 6
|
|
1684
|
+
```
|
|
1685
|
+
|
|
1686
|
+
#### `UNIVERSAL_CAUSE_FILES`
|
|
1687
|
+
|
|
1688
|
+
Distinct spec FILES a repeating cause must span — with every failure sharing it and nothing
|
|
1689
|
+
passing since — before a run is called broken for the whole suite.
|
|
1690
|
+
|
|
1691
|
+
One file's failures can be that file's own setup, so a cause confined to one file is never
|
|
1692
|
+
this. Across {@link UNIVERSAL_CAUSE_FILES} files, on the other hand, whatever it is is a
|
|
1693
|
+
property of the run rather than of a page.
|
|
1694
|
+
|
|
1695
|
+
```typescript
|
|
1696
|
+
const UNIVERSAL_CAUSE_FILES: 3
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1397
1699
|
#### `PARSE_CHECKABLE`
|
|
1398
1700
|
|
|
1399
1701
|
Files a parse check can vouch for cheaply right after a write.
|
|
@@ -1428,9 +1730,13 @@ Peer dependencies:
|
|
|
1428
1730
|
- **`read_file` takes `paths` as well as `path`.** An agent that issues one
|
|
1429
1731
|
tool call per model round-trip pays a full round-trip per file, so a project
|
|
1430
1732
|
survey costs minutes; batched, the same reads cost one call each 25 files
|
|
1431
|
-
(`MAX_BATCH_READ_FILES`
|
|
1432
|
-
|
|
1433
|
-
path
|
|
1733
|
+
(`MAX_BATCH_READ_FILES`). One result is capped at `MAX_READ_CALL_CHARS`, the
|
|
1734
|
+
ceiling the request side puts on a single tool result: whole files are
|
|
1735
|
+
returned up to it and every path past that is named in `note` and listed as
|
|
1736
|
+
`not returned`, so a file is never handed back in part — the shape that lost
|
|
1737
|
+
the middle of a ten-file batch to the request-side cut (X0 flash-m81). Each
|
|
1738
|
+
entry carries its own content or its own error, so one bad path never fails
|
|
1739
|
+
the batch.
|
|
1434
1740
|
- **`edit_file` shows the result, so the model need not re-read.** A
|
|
1435
1741
|
successful edit returns `excerpt`: each edited region as it now reads, with
|
|
1436
1742
|
1-based line numbers (`N: text`) and a few lines of context, capped at
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command position: which program a shell segment actually RUNS, and which words are only
|
|
3
|
+
* arguments to something else.
|
|
4
|
+
*
|
|
5
|
+
* Every classifier that read a command as "this runs the suite" matched the runner's NAME
|
|
6
|
+
* anywhere in the text, so a runner named in an argument was read as an invocation: X1
|
|
7
|
+
* xr-molecule-flash-m81 (+20:03) sent
|
|
8
|
+
* `cd /workspace/fixtures/realworld/specs/api && ls && head -30 run-api-tests-hurl.sh` — two
|
|
9
|
+
* reads of a file that was already there — and the suite-overlap hold answered "a Playwright
|
|
10
|
+
* suite is already running against this app and database … wait for it", costing a minute of
|
|
11
|
+
* forced idling on a command that writes nothing and runs nothing. `cat`, `grep`, `sed -n`,
|
|
12
|
+
* `wc`, `ls` and `git show` of a runner script are the same shape.
|
|
13
|
+
*
|
|
14
|
+
* So this parser answers it in one place, for every caller: split the command at its pipeline
|
|
15
|
+
* and statement separators, take each segment's word in command position — after the wrapper
|
|
16
|
+
* prefixes a shell puts in front of it (`HOST=x sudo nice -n 10 npx`, `bash`, `timeout 280`) —
|
|
17
|
+
* and let the caller match its runner against THAT segment's text, never against the raw
|
|
18
|
+
* command. `{@link commandSegments}` returns the segments, and
|
|
19
|
+
* {@link runsInCommandPosition} asks one anchored pattern of them all.
|
|
20
|
+
*
|
|
21
|
+
* Not command position, and therefore not an invocation: a runner named as an argument
|
|
22
|
+
* (`head -30 run-api-tests-hurl.sh`, `grep -n playwright e2e/README.md`,
|
|
23
|
+
* `git show HEAD:run-api-tests.sh`), and a runner named in a heredoc body — a file a command
|
|
24
|
+
* WRITES is the caller's business, so a command with a heredoc goes through the caller's own
|
|
25
|
+
* stripper first (`withoutHeredocs` in molecule-dev's suite checks).
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
/** One statement or pipeline stage of a shell command: what it runs, and with what. */
|
|
30
|
+
export interface CommandSegment {
|
|
31
|
+
/**
|
|
32
|
+
* The word in command position, its directory removed (`/usr/bin/hurl` → `hurl`): the program
|
|
33
|
+
* the shell would run once whatever wrappers stood in front of it are taken away.
|
|
34
|
+
*/
|
|
35
|
+
program: string;
|
|
36
|
+
/** The words after it, as written — quotes kept, so a quoted `"a|b"` is one word. */
|
|
37
|
+
args: readonly string[];
|
|
38
|
+
/** The program and its arguments as one line — what a runner pattern is matched against. */
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The segments of a command, each with its program and arguments — the answer every classifier
|
|
43
|
+
* that reads a command as "this runs the suite" is asking for.
|
|
44
|
+
*
|
|
45
|
+
* @param command - The shell command.
|
|
46
|
+
* @returns One entry per statement or pipeline stage that runs something, in order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function commandSegments(command: string): CommandSegment[];
|
|
49
|
+
/**
|
|
50
|
+
* Whether any segment of the command runs a program whose command-position text matches
|
|
51
|
+
* `pattern` — the one question a runner-kind table asks.
|
|
52
|
+
*
|
|
53
|
+
* The pattern is matched against `program` and its arguments as one line, so it must be
|
|
54
|
+
* anchored at `^`; it must not carry the `g` flag (a pattern shared between calls would then
|
|
55
|
+
* carry its own match position from one segment to the next).
|
|
56
|
+
*
|
|
57
|
+
* @param command - The shell command.
|
|
58
|
+
* @param pattern - An anchored pattern for the runner's invocation.
|
|
59
|
+
* @returns True when a segment in command position matches.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runsInCommandPosition(command: string, pattern: RegExp): boolean;
|
|
62
|
+
//# sourceMappingURL=command-position.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;CACb;AAqRD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAEjE;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
|