@molecule/api-ai-tools 1.5.20 → 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 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: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
- /** 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
  ```
@@ -989,6 +1078,38 @@ function sliceLines(
989
1078
 
990
1079
  **Returns:** The window plus its position, or null when neither was given.
991
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
+
992
1113
  #### `looksLikeHtml(text)`
993
1114
 
994
1115
  Whether `text` is an HTML document (a doctype or an `<html` root tag).
@@ -1111,41 +1232,117 @@ The cause repeating among a running suite's failures, or null.
1111
1232
 
1112
1233
  Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
1113
1234
  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.
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.
1118
1239
 
1119
1240
  Either way the point is the same: a suite whose failures repeat one cause spends its whole
1120
1241
  runtime proving that one cause, and the executor watching it can be told so at the third
1121
1242
  failure instead of at the last. X1 flash-m76 walked 15 tests in 4.0 minutes where 15.6 s
1122
1243
  each was the test timeout and one missing `navigate('/')` was the cause of all of them.
1123
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
+
1124
1250
  ```typescript
1125
1251
  function repeatingFailure(log: string): RepeatingFailure | null
1126
1252
  ```
1127
1253
 
1128
1254
  - `log` — The log so far.
1129
1255
 
1130
- **Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} failures behind it.
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.
1131
1321
 
1132
1322
  #### `repeatingFailureNote(found, o)`
1133
1323
 
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.
1324
+ The sentence that names a cause repeating among a running suite's failures, mid-run.
1136
1325
 
1137
1326
  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.
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.
1139
1336
 
1140
1337
  ```typescript
1141
1338
  function repeatingFailureNote(
1142
1339
  found: RepeatingFailure,
1143
- o: { taskId: string; log: string; stop: string },
1340
+ o: { taskId: string; log: string; stop: string; universal?: UniversalCause | null },
1144
1341
  ): string
1145
1342
  ```
1146
1343
 
1147
1344
  - `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).
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).
1149
1346
 
1150
1347
  **Returns:** The note.
1151
1348
 
@@ -1272,34 +1469,67 @@ const MAX_SEARCH_CONTEXT_LINES: 40
1272
1469
  #### `MAX_BATCH_READ_FILES`
1273
1470
 
1274
1471
  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.
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.
1277
1475
 
1278
1476
  ```typescript
1279
1477
  const MAX_BATCH_READ_FILES: 25
1280
1478
  ```
1281
1479
 
1282
- #### `MAX_BATCH_READ_BYTES`
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`
1283
1508
 
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.
1509
+ Room inside one read result for what is not file content: the `path`, the
1510
+ offset/line fields, and the note.
1511
+
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.
1286
1515
 
1287
1516
  ```typescript
1288
- const MAX_BATCH_READ_BYTES: number
1517
+ const READ_RESULT_ENVELOPE_CHARS: 1000
1289
1518
  ```
1290
1519
 
1291
1520
  #### `MAX_READ_RETURN_CHARS`
1292
1521
 
1293
1522
  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.
1523
+ for, in serialized chars: larger files come back as their first window, with
1524
+ a note saying how to read the rest.
1296
1525
 
1297
1526
  MAX_READ_SIZE (5 MB) is a bound on what the tool will open, not on what it
1298
1527
  should hand a model: a single batched read that returned a 1.2 MB README
1299
1528
  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.
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.
1303
1533
 
1304
1534
  ```typescript
1305
1535
  const MAX_READ_RETURN_CHARS: number
@@ -1422,7 +1652,8 @@ const REPEATING_CAUSE_TAIL_BYTES: 40000
1422
1652
 
1423
1653
  #### `REPEATING_FAILURE_MIN`
1424
1654
 
1425
- Failures that must share one signature before a running suite's repeating cause is named.
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.
1426
1657
 
1427
1658
  ```typescript
1428
1659
  const REPEATING_FAILURE_MIN: 3
@@ -1436,6 +1667,35 @@ How many of a repeating cause's failures the note lists before it says "and N mo
1436
1667
  const REPEATING_FAILURE_LISTED: 5
1437
1668
  ```
1438
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
+
1439
1699
  #### `PARSE_CHECKABLE`
1440
1700
 
1441
1701
  Files a parse check can vouch for cheaply right after a write.
@@ -1470,9 +1730,13 @@ Peer dependencies:
1470
1730
  - **`read_file` takes `paths` as well as `path`.** An agent that issues one
1471
1731
  tool call per model round-trip pays a full round-trip per file, so a project
1472
1732
  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.
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.
1476
1740
  - **`edit_file` shows the result, so the model need not re-read.** A
1477
1741
  successful edit returns `excerpt`: each edited region as it now reads, with
1478
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"}