@molecule/api-ai-tools 1.5.20 → 1.6.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.
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-16T12:46:49.316Z
6
+ Generated: 2026-10-16T15:37:48.365Z
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
- /** How many failures share the signature. */
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 failures that share it, in log order. */
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
@@ -504,6 +584,12 @@ function guardAITool(tool: T, options?: GuardOptions): T
504
584
  Whether a shell command invokes a test runner and EVERY such invocation only prints — so the
505
585
  command runs no test at all.
506
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
+
507
593
  A command that invokes no runner is NOT one of these (false): "the command runs no test" is
508
594
  then true for `ls`, which is not the question any caller is asking. The question is whether
509
595
  the runner invocation each caller read as a run is one.
@@ -753,16 +839,15 @@ killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
753
839
  often `html`, which prints nothing per test. `--reporter=list` is added
754
840
  when the playwright run is the command's last step and nothing is piped or
755
841
  redirected from it: it writes each result as it finishes, and it is the
756
- shape the platform reads a background suite back in (`suite-fail-fast.ts`'s
757
- fail-fast note and the Tests bar both parse `list` lines). A run that names
758
- `--reporter=line` is switched to `list` for the same reason — `line` writes
759
- `[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
760
- 8.3-minute walk of one suite could never trip the fail-fast note.
842
+ shape the platform reads a background suite back in.
761
843
 
762
- A run being ITERATED on (capped, narrowed, or by file) gets `list,line` instead
763
- of `list` alone. `list` holds every failure's error for the summary at the end,
764
- so mid-run its log says only how long each failure took; `line` writes the body
765
- as the failure happens, and a look at a running suite (`wait_for_task`'s
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
766
851
 
767
852
  _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
768
853
 
@@ -793,13 +878,17 @@ function isSuiteRunCommand(command: string): boolean
793
878
 
794
879
  #### `isPlaywrightRunCommand(command)`
795
880
 
796
- Whether a command starts a Playwright run — `playwright test` in a form that runs something.
881
+ Whether a command starts a Playwright run — a `playwright test` in command position, in a form
882
+ that runs something.
797
883
 
798
884
  `npx playwright test --list` matched every `\bplaywright\s+test\b` test in the platform and was
799
885
  then treated as a run: held (flash-m79's config check), backgrounded for a run that outlasts
800
886
  the tool's ceiling, and rewritten with `--reporter`. A list takes two seconds, prints its lines
801
887
  and exits, so it is none of those; {@link commandRunsNoTests} is what decides.
802
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
+
803
892
  ```typescript
804
893
  function isPlaywrightRunCommand(command: string): boolean
805
894
  ```
@@ -951,6 +1040,26 @@ function pathArgError(path: unknown, tool: string): string | null
951
1040
 
952
1041
  **Returns:** An actionable error string, or null when `path` is a non-empty string.
953
1042
 
1043
+ #### `emptyCallError(input, tool)`
1044
+
1045
+ The error for a file tool called with NO arguments at all — `edit_file {}`, the
1046
+ shape a truncated provider stream leaves behind.
1047
+
1048
+ The path message is the wrong reading of it: "requires a non-empty path
1049
+ argument" says the call was understood and the path was left out, so the model
1050
+ retries the call it just made instead of sending the whole call again (x0
1051
+ flash-m83 +29:32: `edit_file {}`, one round lost). An empty object and a
1052
+ missing `path` are different failures and deserve different sentences.
1053
+
1054
+ ```typescript
1055
+ function emptyCallError(input: unknown, tool: string): string | null
1056
+ ```
1057
+
1058
+ - `input` — The raw tool input.
1059
+ - `tool` — The tool name, for the message.
1060
+
1061
+ **Returns:** The truncation message, or null when the call carried any argument.
1062
+
954
1063
  #### `directoryReadHint(message, path)`
955
1064
 
956
1065
  Detect a "read/edit targeted a directory, not a file" failure from a backend
@@ -989,6 +1098,38 @@ function sliceLines(
989
1098
 
990
1099
  **Returns:** The window plus its position, or null when neither was given.
991
1100
 
1101
+ #### `serializedChars(text)`
1102
+
1103
+ A file's text as the request counts it: the length of its JSON string.
1104
+
1105
+ JSON escaping (`\n` → `\\n`, `"` → `\"`) costs characters, and the request
1106
+ side counts the SERIALIZED tool result. A window of 59,000 characters of
1107
+ source — 1,200 lines — arrives as ~60,200 characters of JSON, over the
1108
+ ceiling it was sized for, so a window is measured with this rather than by
1109
+ its raw text length.
1110
+
1111
+ ```typescript
1112
+ function serializedChars(text: string): number
1113
+ ```
1114
+
1115
+ - `text` — The text.
1116
+
1117
+ **Returns:** Its serialized length, without the surrounding quotes.
1118
+
1119
+ #### `linesWithinSerialized(content, chars)`
1120
+
1121
+ How many leading lines of a file fit inside a budget of SERIALIZED chars,
1122
+ the way `read_file` sizes a window (see {@link serializedChars}).
1123
+
1124
+ ```typescript
1125
+ function linesWithinSerialized(content: string, chars: number): number
1126
+ ```
1127
+
1128
+ - `content` — The file's full text.
1129
+ - `chars` — The budget in serialized chars.
1130
+
1131
+ **Returns:** The line count, at least one.
1132
+
992
1133
  #### `looksLikeHtml(text)`
993
1134
 
994
1135
  Whether `text` is an HTML document (a doctype or an `<html` root tag).
@@ -1111,41 +1252,121 @@ The cause repeating among a running suite's failures, or null.
1111
1252
 
1112
1253
  Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
1113
1254
  path and how long it ran — and defers each failure's error to the summary at the end, so
1114
- mid-run the only signature those lines carry is the duration. A `line` reporter prints the
1115
- failure bodies as they happen, and there the repeated error message itself is the signature,
1116
- which is stronger (it needs no assumption about how long a failure should take), so the
1117
- message is looked for first.
1255
+ mid-run the only signature those lines carry is the duration. Where the log carries the
1256
+ failure bodies (a `line` reporter's output, or a finished run's summary) the signature is the
1257
+ error itself — {@link failureMessage}: the error class plus the locator or the values that
1258
+ differ — which is stronger (it needs no assumption about how long a failure should take), so
1259
+ the message is looked for first and the duration is then left with the tests whose bodies the
1260
+ log does NOT carry.
1118
1261
 
1119
1262
  Either way the point is the same: a suite whose failures repeat one cause spends its whole
1120
1263
  runtime proving that one cause, and the executor watching it can be told so at the third
1121
1264
  failure instead of at the last. X1 flash-m76 walked 15 tests in 4.0 minutes where 15.6 s
1122
1265
  each was the test timeout and one missing `navigate('/')` was the cause of all of them.
1123
1266
 
1267
+ The quorum is of DISTINCT tests — {@link REPEATING_FAILURE_MIN} of them — because a log that
1268
+ prints each failure twice (a second reporter, a retry) had X1 flash-m83 told that four of its
1269
+ tests had failed the same way when two had, so the executor stopped a 139-test walk at test 21
1270
+ with one cause in hand (2026-10-16).
1271
+
1124
1272
  ```typescript
1125
1273
  function repeatingFailure(log: string): RepeatingFailure | null
1126
1274
  ```
1127
1275
 
1128
1276
  - `log` — The log so far.
1129
1277
 
1130
- **Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} failures behind it.
1278
+ **Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} distinct tests behind it.
1279
+
1280
+ #### `universalCause(log, found)`
1281
+
1282
+ Whether a repeating cause is failing the whole run — the one case where stopping it is the
1283
+ right call.
1284
+
1285
+ The test is: the cause accounts for every failure the log names, it has hit
1286
+ {@link UNIVERSAL_CAUSE_FILES} spec files or more, and nothing has passed since its first
1287
+ failure. An app that is not up, a 5xx on every request, or a selector that is missing on every
1288
+ page all look like that, and every remaining test will fail the same way — so the run's
1289
+ remaining minutes measure nothing. Anything less is NOT this: a cause that stops at one file,
1290
+ or that leaves other tests passing, or that shares the run with failures of another shape,
1291
+ leaves the run with something to say, and the run is the only thing that will say it. X1
1292
+ flash-m83 (2026-10-16) stopped eight local runs at their first repeating cause over 14.5
1293
+ minutes and never finished one; three of its deploys each fixed a single cause that a finished
1294
+ run would have listed alongside the others.
1295
+
1296
+ ```typescript
1297
+ function universalCause(log: string, found: RepeatingFailure): UniversalCause | null
1298
+ ```
1299
+
1300
+ - `log` — The log so far.
1301
+ - `found` — The repeating cause {@link repeatingFailure} found in it.
1302
+
1303
+ **Returns:** The cause's count and files, or null when the run is not failing whole.
1304
+
1305
+ #### `failuresByCause(log)`
1306
+
1307
+ Every failure a run names, grouped by the cause behind it — one group per cause, with the
1308
+ tests it hit.
1309
+
1310
+ The point is that ONE run yields EVERY cause. A Playwright report prints its failures one
1311
+ after another with no statement of which of them share a cause, so a run of 19 failures is 19
1312
+ bodies to read; X1 flash-m83 (2026-10-16) spent three deploys (6.7 minutes) each fixing a
1313
+ single cause, two of them found by runs stopped before they walked the rest.
1314
+
1315
+ The signature is the cause itself where the log carries the failures' bodies (a finished run,
1316
+ or a `line` reporter) — {@link failureMessage}: the error class plus the locator or the values
1317
+ that differ — and how long they took where it does not (a still-running `list` log, which is
1318
+ why a duration group is labelled as a duration and not as a proven cause). A test the log
1319
+ printed twice, or failed twice, is one test.
1320
+
1321
+ ```typescript
1322
+ function failuresByCause(log: string): FailureCause[]
1323
+ ```
1324
+
1325
+ - `log` — The log of a finished or running run.
1326
+
1327
+ **Returns:** The causes, the one with the most tests first.
1328
+
1329
+ #### `failureCausesNote(log)`
1330
+
1331
+ The one-line-per-cause summary of a finished run's failures, or null when the run named fewer
1332
+ than two of them.
1333
+
1334
+ This is what makes a run worth finishing: every cause the run found, in the result the
1335
+ executor reads, so one round of fixes covers all of them rather than the one cause a run
1336
+ stopped early happened to reach.
1337
+
1338
+ ```typescript
1339
+ function failureCausesNote(log: string): string | null
1340
+ ```
1341
+
1342
+ - `log` — The finished run's output.
1343
+
1344
+ **Returns:** The note, or null.
1131
1345
 
1132
1346
  #### `repeatingFailureNote(found, o)`
1133
1347
 
1134
- The sentence that names a cause repeating among a running suite's failures, and the command
1135
- that stops the run so the cause can be fixed first.
1348
+ The sentence that names a cause repeating among a running suite's failures, mid-run.
1136
1349
 
1137
1350
  Kept here, beside {@link repeatingFailure}, so the two stay in step: every word below is about
1138
- what that function could actually see in the log.
1351
+ what that function could actually see in the log — including the count, which is of DISTINCT
1352
+ tests, so the note never names one or two tests as a cause.
1353
+
1354
+ The note names the cause early so the executor can START FIXING while the run keeps walking,
1355
+ and it asks for the run to be stopped only when {@link universalCause} says the cause is
1356
+ failing the whole run. X1 flash-m76 (2026-10-16) spent 4.0 minutes walking 15 tests whose one
1357
+ cause was certain at the third, 47 s in, and the note answers exactly that; X1 flash-m83 then
1358
+ stopped eight runs at their first repeating cause in 14.5 minutes and never finished one, so
1359
+ the note must not say to stop when the run still has other causes to name.
1139
1360
 
1140
1361
  ```typescript
1141
1362
  function repeatingFailureNote(
1142
1363
  found: RepeatingFailure,
1143
- o: { taskId: string; log: string; stop: string },
1364
+ o: { taskId: string; log: string; stop: string; universal?: UniversalCause | null },
1144
1365
  ): string
1145
1366
  ```
1146
1367
 
1147
1368
  - `found` — What {@link repeatingFailure} found.
1148
- - `o` — The run's facts: its `taskId`, its `log` path, and `stop` — the shell command that stops it (the pids the platform's own pid file leads to).
1369
+ - `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).
1149
1370
 
1150
1371
  **Returns:** The note.
1151
1372
 
@@ -1272,34 +1493,67 @@ const MAX_SEARCH_CONTEXT_LINES: 40
1272
1493
  #### `MAX_BATCH_READ_FILES`
1273
1494
 
1274
1495
  Most files one batched `read_file` call returns. The cap exists so a survey
1275
- of a large tree cannot swallow the turn's context in one result; the tool
1276
- reports where it stopped so the model continues from there.
1496
+ of a large tree cannot swallow the turn's context in one result; every path
1497
+ past it is named in the result's `note` and listed with its own `error`, so
1498
+ the model continues from there.
1277
1499
 
1278
1500
  ```typescript
1279
1501
  const MAX_BATCH_READ_FILES: 25
1280
1502
  ```
1281
1503
 
1282
- #### `MAX_BATCH_READ_BYTES`
1504
+ #### `MAX_READ_CALL_CHARS`
1505
+
1506
+ The most one `read_file` RESULT may carry, in chars of its JSON — every file
1507
+ the call returns, the envelope and the note included. That is how the request
1508
+ side counts a tool result, so it is how this tool counts one too
1509
+ ({@link serializedChars}).
1510
+
1511
+ One tool result is ONE block in the request, and the request side caps a
1512
+ block: molecule-dev cuts any block over `MAX_TAIL_BLOCK_CHARS` (60,000) in
1513
+ place, keeping ~30 KB of each end. A batched read that crossed it lost its
1514
+ MIDDLE — the cut lands between two `"content"` values, so whole files fell
1515
+ out and the note in their place said only "N characters cut here" — while
1516
+ this tool's own note had promised each of those files that it "fits in one
1517
+ call, so it is not paged": X0 flash-m81 (2026-10-16) read ten e2e specs in
1518
+ one call (121,467 chars), lost the middle six, and bought them again in six
1519
+ further calls (52,908 chars).
1283
1520
 
1284
- Total content bytes one batched `read_file` call returns, across all files.
1285
- The per-file {@link MAX_READ_SIZE} still applies to each one.
1521
+ So the CALL gets the ceiling the request gives it. Whole files are returned
1522
+ up to it; the paths that did not fit are named in `note` and listed in
1523
+ `files` with their own `error`, the shape every other entry has. No file is
1524
+ ever cut, and no read result reaches the request-side cut. A test in
1525
+ molecule-dev's `context-fit` asserts the two ceilings still agree.
1286
1526
 
1287
1527
  ```typescript
1288
- const MAX_BATCH_READ_BYTES: number
1528
+ const MAX_READ_CALL_CHARS: 60000
1529
+ ```
1530
+
1531
+ #### `READ_RESULT_ENVELOPE_CHARS`
1532
+
1533
+ Room inside one read result for what is not file content: the `path`, the
1534
+ offset/line fields, and the note.
1535
+
1536
+ A batch sizes each file against what the call has left
1537
+ ({@link MAX_READ_CALL_CHARS} less this), so the entry that does not fit is
1538
+ caught here rather than by the request-side cut.
1539
+
1540
+ ```typescript
1541
+ const READ_RESULT_ENVELOPE_CHARS: 1000
1289
1542
  ```
1290
1543
 
1291
1544
  #### `MAX_READ_RETURN_CHARS`
1292
1545
 
1293
1546
  The most one read_file call returns for ONE file when no window was asked
1294
- for, in chars. Larger files come back as their first window with a note
1295
- saying how to read the rest.
1547
+ for, in serialized chars: larger files come back as their first window, with
1548
+ a note saying how to read the rest.
1296
1549
 
1297
1550
  MAX_READ_SIZE (5 MB) is a bound on what the tool will open, not on what it
1298
1551
  should hand a model: a single batched read that returned a 1.2 MB README
1299
1552
  put the next three provider calls at 366k tokens (~330k of them fresh)
1300
- against a 120k-token context cap (X0 run x5, call 26). ~80 KB is ~20–25k
1301
- tokens — a fifth of that cap, and more than any one file read usually
1302
- needs.
1553
+ against a 120k-token context cap (X0 run x5, call 26). This is the call's
1554
+ own ceiling ({@link MAX_READ_CALL_CHARS}) less the room its envelope takes,
1555
+ so one read result — one file or twenty-five — fits inside what the request
1556
+ side carries without being cut.
1303
1557
 
1304
1558
  ```typescript
1305
1559
  const MAX_READ_RETURN_CHARS: number
@@ -1422,7 +1676,8 @@ const REPEATING_CAUSE_TAIL_BYTES: 40000
1422
1676
 
1423
1677
  #### `REPEATING_FAILURE_MIN`
1424
1678
 
1425
- Failures that must share one signature before a running suite's repeating cause is named.
1679
+ DISTINCT tests that must share one signature before a running suite's repeating cause is named.
1680
+ A test the log prints twice, or a test that failed twice, is still one test.
1426
1681
 
1427
1682
  ```typescript
1428
1683
  const REPEATING_FAILURE_MIN: 3
@@ -1436,6 +1691,35 @@ How many of a repeating cause's failures the note lists before it says "and N mo
1436
1691
  const REPEATING_FAILURE_LISTED: 5
1437
1692
  ```
1438
1693
 
1694
+ #### `FAILURE_CAUSES_LISTED`
1695
+
1696
+ How many causes a finished run's summary lists before it says "and N more".
1697
+
1698
+ ```typescript
1699
+ const FAILURE_CAUSES_LISTED: 5
1700
+ ```
1701
+
1702
+ #### `FAILURE_CAUSE_TESTS_LISTED`
1703
+
1704
+ How many of one cause's tests that summary names before it says "and N more".
1705
+
1706
+ ```typescript
1707
+ const FAILURE_CAUSE_TESTS_LISTED: 6
1708
+ ```
1709
+
1710
+ #### `UNIVERSAL_CAUSE_FILES`
1711
+
1712
+ Distinct spec FILES a repeating cause must span — with every failure sharing it and nothing
1713
+ passing since — before a run is called broken for the whole suite.
1714
+
1715
+ One file's failures can be that file's own setup, so a cause confined to one file is never
1716
+ this. Across {@link UNIVERSAL_CAUSE_FILES} files, on the other hand, whatever it is is a
1717
+ property of the run rather than of a page.
1718
+
1719
+ ```typescript
1720
+ const UNIVERSAL_CAUSE_FILES: 3
1721
+ ```
1722
+
1439
1723
  #### `PARSE_CHECKABLE`
1440
1724
 
1441
1725
  Files a parse check can vouch for cheaply right after a write.
@@ -1470,9 +1754,13 @@ Peer dependencies:
1470
1754
  - **`read_file` takes `paths` as well as `path`.** An agent that issues one
1471
1755
  tool call per model round-trip pays a full round-trip per file, so a project
1472
1756
  survey costs minutes; batched, the same reads cost one call each 25 files
1473
- (`MAX_BATCH_READ_FILES` / `MAX_BATCH_READ_BYTES`, which report where they
1474
- stopped). Each entry carries its own content or its own error, so one bad
1475
- path never fails the batch.
1757
+ (`MAX_BATCH_READ_FILES`). One result is capped at `MAX_READ_CALL_CHARS`, the
1758
+ ceiling the request side puts on a single tool result: whole files are
1759
+ returned up to it and every path past that is named in `note` and listed as
1760
+ `not returned`, so a file is never handed back in part — the shape that lost
1761
+ the middle of a ten-file batch to the request-side cut (X0 flash-m81). Each
1762
+ entry carries its own content or its own error, so one bad path never fails
1763
+ the batch.
1476
1764
  - **`edit_file` shows the result, so the model need not re-read.** A
1477
1765
  successful edit returns `excerpt`: each edited region as it now reads, with
1478
1766
  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"}