@molecule/api-ai-tools 1.12.0 → 1.13.1

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-21T20:10:49.315Z
6
+ Generated: 2026-10-22T07:11:20.054Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -360,6 +360,101 @@ interface RunnerOutputFilter {
360
360
  }
361
361
  ```
362
362
 
363
+ #### `PlaywrightReportAttachment`
364
+
365
+ One artifact the runner saved for one test, as the report records it.
366
+
367
+ ```typescript
368
+ interface PlaywrightReportAttachment {
369
+ /** The attachment's name (`trace`, `screenshot`, …). */
370
+ name: string
371
+ /** Its media type, when the runner recorded one. */
372
+ contentType: string | null
373
+ /** Where the runner saved it, as the report wrote the path. */
374
+ path: string
375
+ }
376
+ ```
377
+
378
+ #### `PlaywrightReportTest`
379
+
380
+ One failing (or flaky) test's identity and evidence, as the runner recorded them.
381
+
382
+ The identity fields are Playwright's own — never reconstructed from printed headings — so a
383
+ reader can tell exactly which test an attachment belongs to.
384
+
385
+ ```typescript
386
+ interface PlaywrightReportTest {
387
+ /** The spec's stable id, when the report carries one. */
388
+ id: string | null
389
+ /** The Playwright project the test ran in. */
390
+ project: string | null
391
+ /** The spec file, relative to the config's root directory as the report writes it. */
392
+ file: string
393
+ /** The test's declaration line. */
394
+ line: number
395
+ /** The test's declaration column. */
396
+ column: number
397
+ /** The full title path (`['describe', …, 'test title']`), empty when none is readable. */
398
+ titlePath: string[]
399
+ /** The test's own title (the last entry of {@link titlePath}). */
400
+ title: string
401
+ /** Playwright's outcome for the test: `unexpected`, `flaky`, `skipped` or `expected`. */
402
+ status: string
403
+ /** How many attempts the runner made (1 when it was not retried). */
404
+ attempts: number
405
+ /** The retry index of the attempt whose evidence is reported (0 when not retried). */
406
+ retry: number
407
+ /** That attempt's own duration, in milliseconds. */
408
+ durationMs: number
409
+ /** That attempt's error message, bounded and redacted, or null. */
410
+ error: string | null
411
+ /** The artifacts the runner saved for THAT attempt — `trace` first. */
412
+ attachments: PlaywrightReportAttachment[]
413
+ }
414
+ ```
415
+
416
+ #### `PlaywrightReportStats`
417
+
418
+ The runner's own tally, read straight from the report. Never a verdict this module derives.
419
+
420
+ ```typescript
421
+ interface PlaywrightReportStats {
422
+ /** Tests that ended as expected. */
423
+ expected: number
424
+ /** Tests the runner skipped. */
425
+ skipped: number
426
+ /** Tests that did not end as expected. */
427
+ unexpected: number
428
+ /** Tests that failed and then passed on a retry. */
429
+ flaky: number
430
+ /** The run's own duration, in milliseconds. */
431
+ durationMs: number
432
+ }
433
+ ```
434
+
435
+ #### `PlaywrightRunReport`
436
+
437
+ What one run's standard machine report says, bounded for a tool result.
438
+
439
+ A reader that needs evidence this record does not carry reads {@link path} in the sandbox.
440
+
441
+ ```typescript
442
+ interface PlaywrightRunReport {
443
+ /** How much of the report was read — see {@link PlaywrightReportStatus}. */
444
+ status: PlaywrightReportStatus
445
+ /** Where the report file is, for the full evidence. Always the runner's own file. */
446
+ path: string
447
+ /** One short phrase saying why a record is not `complete`, when it is not. */
448
+ reason?: string
449
+ /** The runner's tally, or null when the report did not carry one. */
450
+ stats: PlaywrightReportStats | null
451
+ /** The failing and flaky tests, in report order, bounded by {@link MAX_REPORTED_FAILURES}. */
452
+ failures: PlaywrightReportTest[]
453
+ /** How many failing tests did not fit the bound above. */
454
+ omittedFailures: number
455
+ }
456
+ ```
457
+
363
458
  #### `ToolSchema`
364
459
 
365
460
  JSON-schema-backed definition for a single agent tool.
@@ -943,6 +1038,18 @@ interface TraceArtifact {
943
1038
  * Absent when no group reached the threshold, and on traces read before this field existed.
944
1039
  */
945
1040
  repeated?: TraceRequestRepeat[]
1041
+ /**
1042
+ * The directory the trace was read from, relative to the root the reader was given.
1043
+ *
1044
+ * Playwright saves one directory per test attempt under the run's `outputDir`, and every artifact
1045
+ * of that attempt — `trace.zip` included — sits in it. The run's own machine report records each
1046
+ * test that wrote a trace with its attachment path, so this relative directory is the exact mark
1047
+ * binding THIS artifact to the test that wrote it, with no join by spec line needed
1048
+ * ({@link ./playwright-report.js}).
1049
+ *
1050
+ * Absent on traces read before this field existed.
1051
+ */
1052
+ dir?: string
946
1053
  }
947
1054
  ```
948
1055
 
@@ -1562,6 +1669,23 @@ What the transport did when a request never got an answer.
1562
1669
  type ConnectionFailureKind = 'timeout' | 'refused' | 'reset' | 'dns' | 'tls' | 'empty'
1563
1670
  ```
1564
1671
 
1672
+ #### `PlaywrightReportStatus`
1673
+
1674
+ How much of a report this module could account for.
1675
+
1676
+ - `complete` — the runner's stats and its suite tree both parsed: every test entry is present.
1677
+ - `partial` — a report was written but is not a whole run (Playwright's own `errors` are present,
1678
+ the suite tree is missing, or the file is past {@link MAX_REPORT_PARSE_CHARS}). What is listed
1679
+ is real; what is missing must be read at {@link PlaywrightRunReport.path}.
1680
+ - `malformed` — the file exists but is not a Playwright JSON report (truncated, wrong shape).
1681
+ Nothing in it may be treated as runner data.
1682
+ - `missing` — the run left no report (killed before `onEnd`, not a run this wrapper rewrote, or
1683
+ the file was removed). Nothing may be inferred from its absence.
1684
+
1685
+ ```typescript
1686
+ type PlaywrightReportStatus = 'complete' | 'partial' | 'malformed' | 'missing'
1687
+ ```
1688
+
1565
1689
  #### `DiscoveredSkill` _(deprecated)_
1566
1690
 
1567
1691
  Re-export SkillEntry as DiscoveredSkill for backwards compatibility.
@@ -1792,6 +1916,43 @@ function statementArgumentEnd(
1792
1916
 
1793
1917
  **Returns:** The offset to write the option at, or null when the statement cannot be placed here.
1794
1918
 
1919
+ #### `statementBoundary(command, start, end)`
1920
+
1921
+ Where a statement's own text ends, and whether it leaves a group open.
1922
+
1923
+ A statement can be written inside a subshell or a brace group, and the tokenizer attaches an
1924
+ adjacent closer to its last word because no separator comes between them: `( … | tail -25)`
1925
+ parses its last stage as `tail` with the argument `-25)`. {@link LocatedCommandSegment.end}
1926
+ therefore says one past the `)`, and a caller cutting that stage out of the command takes the
1927
+ group's closer with it — the rewritten command is then `( … 2>&1`, which no shell parses
1928
+ (xr-molecule-flash-m418 +23:31: a heredoc config plus a parenthesized Playwright pipeline came
1929
+ back as `end of file unexpected` from the background runner).
1930
+
1931
+ The parse's own group depths answer which closers belong to an enclosing group: the depth a
1932
+ statement was written at is its first character's depth (an opener is recorded at the depth
1933
+ BEFORE it opens), and every character of that statement's own words is at that depth or deeper.
1934
+ A closer at a LOWER depth closes a group opened before the statement, so it is not the
1935
+ statement's text. A character inside a quote (`-1`) is literal text and stops the walk: `grep
1936
+ 'a)b'` ends on its own quote, not on a closer.
1937
+
1938
+ The same depths answer the other dangerous cut: a stage that OPENS a group it does not close
1939
+ (`| { cat` when the `}` follows a `;`) has its closer outside the stage, so dropping the stage
1940
+ alone leaves that closer dangling.
1941
+
1942
+ ```typescript
1943
+ function statementBoundary(
1944
+ command: string,
1945
+ start: number | null,
1946
+ end: number | null,
1947
+ ): { textEnd: number; leavesGroupOpen: boolean } | null
1948
+ ```
1949
+
1950
+ - `command` — The shell command.
1951
+ - `start` — The statement's first character ({@link LocatedCommandSegment.start}).
1952
+ - `end` — One past the statement's last character ({@link LocatedCommandSegment.end}).
1953
+
1954
+ **Returns:** The offset of the first character that is not the statement's own text, and whether the statement leaves a group it opened still open; null when it cannot be placed in this command.
1955
+
1795
1956
  #### `commandTimeoutSeconds(command)`
1796
1957
 
1797
1958
  The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
@@ -2255,6 +2416,105 @@ function pipelineExitCodeNote(command: string, exitCode: number, output: string)
2255
2416
 
2256
2417
  **Returns:** One sentence naming the stage the code came from, or null.
2257
2418
 
2419
+ #### `reportCarriesIdentities(status)`
2420
+
2421
+ The report statuses for which the report's own per-test identities and attachments may be used.
2422
+
2423
+ `partial` is included because a partial report's suite tree, where it parsed, is still the
2424
+ runner's own record of which test owns which attachment; `malformed` and `missing` are not,
2425
+ because nothing about them is runner data.
2426
+
2427
+ ```typescript
2428
+ function reportCarriesIdentities(status: PlaywrightReportStatus): boolean
2429
+ ```
2430
+
2431
+ - `status` — One record's {@link PlaywrightReportStatus}.
2432
+
2433
+ **Returns:** True when the record's tests and attachments are the runner's own.
2434
+
2435
+ #### `reportTestAttachmentPaths(test)`
2436
+
2437
+ The attachment paths a stored test record carries, `trace` first.
2438
+
2439
+ ```typescript
2440
+ function reportTestAttachmentPaths(test: PlaywrightReportTest): string[]
2441
+ ```
2442
+
2443
+ - `test` — One entry of {@link PlaywrightRunReport.failures}, as stored.
2444
+
2445
+ **Returns:** Every saved path the record can prove, in its own order.
2446
+
2447
+ #### `reportTestArtifactDir(test)`
2448
+
2449
+ The directory a test result's own artifacts were saved in, or null when the report names none.
2450
+
2451
+ Playwright writes one directory per test attempt under the run's `outputDir`, and every artifact
2452
+ of that attempt — `trace.zip` included — sits in it. The directory is therefore the exact
2453
+ ownership mark for a trace: two tests' traces are never in one another's directory. A damaged
2454
+ record whose attachment entries carry no path answers null, so nothing is claimed from it.
2455
+
2456
+ ```typescript
2457
+ function reportTestArtifactDir(test: PlaywrightReportTest): string | null
2458
+ ```
2459
+
2460
+ - `test` — One entry of {@link PlaywrightRunReport.failures}, as stored.
2461
+
2462
+ **Returns:** The posix-normalized directory, or null.
2463
+
2464
+ #### `summarizePlaywrightReport(text, options)`
2465
+
2466
+ Summarize one Playwright `json` report into the bounded record a tool result carries.
2467
+
2468
+ Pure text in, record out: the caller reads the file (and decides whether it may), so this is
2469
+ testable against genuine reports and against malformed controls alike.
2470
+
2471
+ ```typescript
2472
+ function summarizePlaywrightReport(
2473
+ text: string,
2474
+ options: { path: string; sanitize?: (text: string) => string },
2475
+ ): PlaywrightRunReport
2476
+ ```
2477
+
2478
+ - `text` — The report file's text, exactly as written.
2479
+ - `options` — Where the file is, and the consumer's redactor for error text.
2480
+ - `options.path` — The report file's path, carried on the record for full evidence.
2481
+ - `options.sanitize` — Redactor applied to error text (defaults to the identity).
2482
+
2483
+ **Returns:** The bounded record; never a verdict.
2484
+
2485
+ #### `commandWritesReportTo(command, expectedPath)`
2486
+
2487
+ The report path a command was launched with, when it is the one this platform sets.
2488
+
2489
+ Read back from the launched script (and from a completed task's own command) so a record written
2490
+ before an API restart is still found: the path is the task's own id derived by
2491
+ {@link playwrightReportPathFor}, never a path parsed out of the executor's command.
2492
+
2493
+ ```typescript
2494
+ function commandWritesReportTo(command: string, expectedPath: string): boolean
2495
+ ```
2496
+
2497
+ - `command` — The command as it was launched.
2498
+ - `expectedPath` — The one path this task's report may be at.
2499
+
2500
+ **Returns:** True when the command sets {@link PLAYWRIGHT_REPORT_ENV} to that exact path.
2501
+
2502
+ #### `playwrightReportPathFor(taskId)`
2503
+
2504
+ The report file a background task's own scratch files live beside.
2505
+
2506
+ Derived from the task id, never from the command: the id is unique per launch, so two runs can
2507
+ never read one another's report, and the path survives a process restart the same way the task's
2508
+ log and exit file do.
2509
+
2510
+ ```typescript
2511
+ function playwrightReportPathFor(taskId: string): string
2512
+ ```
2513
+
2514
+ - `taskId` — The `mol-bg-…` handle the launch returned.
2515
+
2516
+ **Returns:** The report file's absolute path.
2517
+
2258
2518
  #### `startsServerProgram(command)`
2259
2519
 
2260
2520
  Whether a command, read from its TEXT alone, starts a server that stays up in the foreground.
@@ -2423,13 +2683,15 @@ function withSuiteConfig(command: string, config: string | null | undefined): st
2423
2683
  #### `scopedRerunCommand(command, locations, excluded, config, cwd, env)`
2424
2684
 
2425
2685
  The whole-suite command made scoped to the tests it should re-run instead: the same
2426
- invocation — same directory, same settings — with the named `file:line`s in place of the whole
2427
- suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
2686
+ invocation — same launcher, same settings — with the named `file:line`s in place of every
2687
+ positional selection the run carried (a spec file, a suite directory or a regex filter),
2688
+ `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
2428
2689
  `--retries=0` set (a re-run of a known failure does not need to pay for retries).
2429
2690
 
2430
2691
  The re-run keeps the run's `> <file>` redirect only in the sense of keeping its OUTPUT there:
2431
- the target is rewritten to a fresh sibling ({@link redirectToFreshFile}), so a re-run never
2432
- overwrites the log the run itself is the evidence for (xr-molecule-flash-m173).
2692
+ the target is rewritten to a fresh sibling ({@link redirectRunnerOutputsToFreshFiles}), so a
2693
+ re-run never overwrites the log the run itself is the evidence for (xr-molecule-flash-m173) —
2694
+ and the setup around the run keeps its own file writes untouched (xr-molecule-flash-m429).
2433
2695
 
2434
2696
  `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
2435
2697
  carries it whenever the command itself does not, so a run that dropped `--config` cannot be
@@ -3184,7 +3446,7 @@ function outputRedirectTarget(command: string): string | null
3184
3446
 
3185
3447
  **Returns:** The target, or null (`/dev/null` and `&1`/`&2` are not files).
3186
3448
 
3187
- #### `streamingBackgroundCommand(command, context)`
3449
+ #### `streamingBackgroundCommand(command, context, reportPath)`
3188
3450
 
3189
3451
  Rewrite a command about to run in the background so its log grows while it
3190
3452
  runs. Two shapes left the log empty until exit, and an empty log is what
@@ -3213,13 +3475,44 @@ _(description truncated — read the full text in `node_modules/@molecule/api-ai
3213
3475
  function streamingBackgroundCommand(
3214
3476
  command: string,
3215
3477
  context?: DiagnosisRunContext,
3216
- ): { command: string; changes: string[]; error?: string }
3478
+ reportPath?: string,
3479
+ ): { command: string; changes: string[]; reportPath?: string; error?: string }
3217
3480
  ```
3218
3481
 
3219
3482
  - `command` — The command as the executor sent it.
3220
3483
  - `context` — What the caller knows beyond the command: the tests the person's request excludes by name ({@link DiagnosisRunContext}). A `--grep-invert` of exactly those is the request's own scope, not a diagnosis, so it keeps the suite's retries on a deployed proof (xr-molecule-flash-m170 +33:16).
3484
+ - `reportPath` — Where this run should write Playwright's standard `json` report, when the caller keeps one ({@link ./playwright-report.js}). The reporter is added to the run's own reporter set, never in place of one, and its output goes to that file through `PLAYWRIGHT_JSON_OUTPUT_FILE` — so the log, the executors' own reporters and every verdict stay exactly as they are. Omitted, or a run whose reporter set is not this function's to place (already names `json`, sets the variable itself, or whose runner cannot be located), leaves the command untouched and the result's `reportPath` unset.
3485
+
3486
+ **Returns:** The command to run, one sentence per change made (empty when none), and the report path when one was actually wired — or `error` when the command starts the suite more than once.
3221
3487
 
3222
- **Returns:** The command to run and one sentence per change made (empty when none), or `error` when the command starts the suite more than once.
3488
+ #### `withPlaywrightRunReport(command, reportPath)`
3489
+
3490
+ Put Playwright's standard `json` report on a FOREGROUND run that already names its own reporters.
3491
+
3492
+ A background run is rewritten onto the platform's reporter anyway ({@link
3493
+ streamingBackgroundCommand}), so adding the machine report there costs nothing. A foreground run
3494
+ is not: its reporters are either the command's own flags or the suite's config, and replacing a
3495
+ config reporter would stop writing the artifact the executor configured. So this only ever
3496
+ APPENDS `json` to a `--reporter` set the command already wrote — the log keeps every printing
3497
+ reporter it had, the executor's file reporters keep their place, and the standard report goes to
3498
+ {@link PLAYWRIGHT_REPORT_ENV}'s file.
3499
+
3500
+ A command that names no reporter at all is left exactly as written: its reporters are the suite
3501
+ config's, and this tool does not rewrite those. A command that already names `json`, or sets
3502
+ `PLAYWRIGHT_JSON_OUTPUT_FILE` itself, already has a report of its own — a path the executor chose
3503
+ is never overwritten.
3504
+
3505
+ ```typescript
3506
+ function withPlaywrightRunReport(
3507
+ command: string,
3508
+ reportPath: string,
3509
+ ): { command: string; reportPath: string | null }
3510
+ ```
3511
+
3512
+ - `command` — The command the executor sent, after every other rewrite.
3513
+ - `reportPath` — The file this run's machine report should be written to.
3514
+
3515
+ **Returns:** The command to run and the report path when one was wired; `reportPath` is null when the run was left untouched and no report may be expected.
3223
3516
 
3224
3517
  #### `suiteRunStatements(command)`
3225
3518
 
@@ -3388,6 +3681,10 @@ run writes still holds every failure (xr-molecule-flash-m121 — a `| tail -5; e
3388
3681
  tail, and a 95-second walk's log held only the tally while the platform's own hold had handed
3389
3682
  that command back).
3390
3683
 
3684
+ The command around the dropped stages is left as written: a group they sit in keeps its closer
3685
+ (`( … | tail -25)` → `( … )`, never `( …` — xr-molecule-flash-m418 +23:31), and a stage the cut
3686
+ would leave a closer dangling inside is not dropped at all.
3687
+
3391
3688
  ```typescript
3392
3689
  function stripTrailingSuiteFilters(command: string): SuiteFilterStrip
3393
3690
  ```
@@ -4319,7 +4616,9 @@ function whitespaceTolerantReplace(
4319
4616
 
4320
4617
  The shell command that parses one file and exits non-zero with the error
4321
4618
  when it does not parse: `node --check` for JavaScript, `JSON.parse` for
4322
- JSON, esbuild's transform (present wherever Vite is) for TypeScript.
4619
+ ordinary JSON, TypeScript's config parser for `tsconfig`/`jsconfig` files
4620
+ (which are JSONC), esbuild's transform (present wherever Vite is) for
4621
+ TypeScript.
4323
4622
 
4324
4623
  ```typescript
4325
4624
  function parseCheckCommand(path: string): string | null
@@ -4385,6 +4684,59 @@ is a runaway detector, not a performance budget.
4385
4684
  const INLINE_SCRIPT_BUDGET_MS: 20000
4386
4685
  ```
4387
4686
 
4687
+ #### `PLAYWRIGHT_REPORT_ENV`
4688
+
4689
+ The environment variable Playwright's built-in `json` reporter takes its output path from
4690
+ (`resolveOutputFile("JSON", …)`), resolved against the runner's own working directory. An
4691
+ absolute path is used here, so the file lands in the run's scratch area whatever directory the
4692
+ suite runs from.
4693
+
4694
+ ```typescript
4695
+ const PLAYWRIGHT_REPORT_ENV: 'PLAYWRIGHT_JSON_OUTPUT_FILE'
4696
+ ```
4697
+
4698
+ #### `PLAYWRIGHT_REPORT_SUFFIX`
4699
+
4700
+ The file name a background task's report is written to, beside its own log.
4701
+
4702
+ ```typescript
4703
+ const PLAYWRIGHT_REPORT_SUFFIX: '.playwright.json'
4704
+ ```
4705
+
4706
+ #### `MAX_REPORT_PARSE_CHARS`
4707
+
4708
+ How big a report may be before this module refuses to summarize it. The file stays readable at
4709
+ its path; the record only says so. A whole 139-test suite's report is well under a megabyte; a
4710
+ report past this is not one a tool result should carry.
4711
+
4712
+ ```typescript
4713
+ const MAX_REPORT_PARSE_CHARS: 8000000
4714
+ ```
4715
+
4716
+ #### `MAX_REPORTED_FAILURES`
4717
+
4718
+ How many failing tests a record lists before the rest are counted in `omittedFailures`.
4719
+
4720
+ ```typescript
4721
+ const MAX_REPORTED_FAILURES: 12
4722
+ ```
4723
+
4724
+ #### `MAX_REPORTED_ATTACHMENTS`
4725
+
4726
+ How many attachments one failing test keeps in the record (`trace` first).
4727
+
4728
+ ```typescript
4729
+ const MAX_REPORTED_ATTACHMENTS: 4
4730
+ ```
4731
+
4732
+ #### `MAX_REPORTED_ERROR_CHARS`
4733
+
4734
+ How much of one failure's own error text the record keeps.
4735
+
4736
+ ```typescript
4737
+ const MAX_REPORTED_ERROR_CHARS: 400
4738
+ ```
4739
+
4388
4740
  #### `TOOL_SCHEMAS`
4389
4741
 
4390
4742
  Canonical tool schemas shared by the agent runtime and documentation.
@@ -205,6 +205,39 @@ export declare function locatedCommandSegments(command: string): LocatedCommandS
205
205
  * @returns The offset to write the option at, or null when the statement cannot be placed here.
206
206
  */
207
207
  export declare function statementArgumentEnd(command: string, start: number | null, end: number | null): number | null;
208
+ /**
209
+ * Where a statement's own text ends, and whether it leaves a group open.
210
+ *
211
+ * A statement can be written inside a subshell or a brace group, and the tokenizer attaches an
212
+ * adjacent closer to its last word because no separator comes between them: `( … | tail -25)`
213
+ * parses its last stage as `tail` with the argument `-25)`. {@link LocatedCommandSegment.end}
214
+ * therefore says one past the `)`, and a caller cutting that stage out of the command takes the
215
+ * group's closer with it — the rewritten command is then `( … 2>&1`, which no shell parses
216
+ * (xr-molecule-flash-m418 +23:31: a heredoc config plus a parenthesized Playwright pipeline came
217
+ * back as `end of file unexpected` from the background runner).
218
+ *
219
+ * The parse's own group depths answer which closers belong to an enclosing group: the depth a
220
+ * statement was written at is its first character's depth (an opener is recorded at the depth
221
+ * BEFORE it opens), and every character of that statement's own words is at that depth or deeper.
222
+ * A closer at a LOWER depth closes a group opened before the statement, so it is not the
223
+ * statement's text. A character inside a quote (`-1`) is literal text and stops the walk: `grep
224
+ * 'a)b'` ends on its own quote, not on a closer.
225
+ *
226
+ * The same depths answer the other dangerous cut: a stage that OPENS a group it does not close
227
+ * (`| { cat` when the `}` follows a `;`) has its closer outside the stage, so dropping the stage
228
+ * alone leaves that closer dangling.
229
+ *
230
+ * @param command - The shell command.
231
+ * @param start - The statement's first character ({@link LocatedCommandSegment.start}).
232
+ * @param end - One past the statement's last character ({@link LocatedCommandSegment.end}).
233
+ * @returns The offset of the first character that is not the statement's own text, and whether
234
+ * the statement leaves a group it opened still open; null when it cannot be placed in this
235
+ * command.
236
+ */
237
+ export declare function statementBoundary(command: string, start: number | null, end: number | null): {
238
+ textEnd: number;
239
+ leavesGroupOpen: boolean;
240
+ } | null;
208
241
  /**
209
242
  * The bound a command puts on its own run: the largest `timeout <N>` any of its segments runs
210
243
  * under (`timeout 280 hurl --test …` → 280), or null when it carries none.
@@ -1 +1 @@
1
- {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAgSD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM3D;AAynBD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AA8GD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAIjE;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IACvB,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;CACb;AAmFD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAGnE;AAmCD;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,0FAA0F;IAC1F,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9B;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,EAAE,CAE/E;AAmDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,MAAM,GAAG,IAAI,CAyDf;AA4GD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,GAC1C;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAoD7C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAK/E"}
1
+ {"version":3,"file":"command-position.d.ts","sourceRoot":"","sources":["../src/command-position.ts"],"names":[],"mappings":"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"}
@@ -1325,6 +1325,72 @@ export function statementArgumentEnd(command, start, end) {
1325
1325
  at -= 1;
1326
1326
  return at;
1327
1327
  }
1328
+ /**
1329
+ * Where a statement's own text ends, and whether it leaves a group open.
1330
+ *
1331
+ * A statement can be written inside a subshell or a brace group, and the tokenizer attaches an
1332
+ * adjacent closer to its last word because no separator comes between them: `( … | tail -25)`
1333
+ * parses its last stage as `tail` with the argument `-25)`. {@link LocatedCommandSegment.end}
1334
+ * therefore says one past the `)`, and a caller cutting that stage out of the command takes the
1335
+ * group's closer with it — the rewritten command is then `( … 2>&1`, which no shell parses
1336
+ * (xr-molecule-flash-m418 +23:31: a heredoc config plus a parenthesized Playwright pipeline came
1337
+ * back as `end of file unexpected` from the background runner).
1338
+ *
1339
+ * The parse's own group depths answer which closers belong to an enclosing group: the depth a
1340
+ * statement was written at is its first character's depth (an opener is recorded at the depth
1341
+ * BEFORE it opens), and every character of that statement's own words is at that depth or deeper.
1342
+ * A closer at a LOWER depth closes a group opened before the statement, so it is not the
1343
+ * statement's text. A character inside a quote (`-1`) is literal text and stops the walk: `grep
1344
+ * 'a)b'` ends on its own quote, not on a closer.
1345
+ *
1346
+ * The same depths answer the other dangerous cut: a stage that OPENS a group it does not close
1347
+ * (`| { cat` when the `}` follows a `;`) has its closer outside the stage, so dropping the stage
1348
+ * alone leaves that closer dangling.
1349
+ *
1350
+ * @param command - The shell command.
1351
+ * @param start - The statement's first character ({@link LocatedCommandSegment.start}).
1352
+ * @param end - One past the statement's last character ({@link LocatedCommandSegment.end}).
1353
+ * @returns The offset of the first character that is not the statement's own text, and whether
1354
+ * the statement leaves a group it opened still open; null when it cannot be placed in this
1355
+ * command.
1356
+ */
1357
+ export function statementBoundary(command, start, end) {
1358
+ if (typeof command !== 'string')
1359
+ return null;
1360
+ if (start === null || end === null)
1361
+ return null;
1362
+ if (!Number.isInteger(start) || !Number.isInteger(end))
1363
+ return null;
1364
+ if (start < 0 || end > command.length || end < start)
1365
+ return null;
1366
+ const part = splitSegments(command).find((candidate) => candidate.start === start && candidate.start + candidate.text.length === end);
1367
+ const enclosing = part?.depths[0];
1368
+ if (part === undefined || enclosing === undefined)
1369
+ return null;
1370
+ let at = part.text.length;
1371
+ while (at > 0) {
1372
+ const depth = part.depths[at - 1];
1373
+ if (depth === undefined || depth < 0 || depth >= enclosing)
1374
+ break;
1375
+ at -= 1;
1376
+ }
1377
+ // Whitespace before the closer is not part of the statement's last word either.
1378
+ while (at > 0 && /\s/.test(part.text[at - 1] ?? ''))
1379
+ at -= 1;
1380
+ // The group state where the statement's own text stops: a group opened here and never closed
1381
+ // puts its closer after the statement, in a later stage or statement.
1382
+ let trailing = enclosing;
1383
+ let deepest = enclosing;
1384
+ for (let i = 0; i < at; i += 1) {
1385
+ const depth = part.depths[i];
1386
+ if (depth === undefined || depth < 0)
1387
+ continue;
1388
+ trailing = depth;
1389
+ if (depth > deepest)
1390
+ deepest = depth;
1391
+ }
1392
+ return { textEnd: start + at, leavesGroupOpen: deepest > enclosing && trailing > enclosing };
1393
+ }
1328
1394
  /**
1329
1395
  * {@link locatedCommandSegments}'s recursion, so a `sh -c '<command>'` is opened as far as
1330
1396
  * {@link MAX_NESTING} allows. A nested statement's offsets are cleared: they are positions in
package/dist/index.d.ts CHANGED
@@ -147,6 +147,7 @@ export * from './line-diff.js';
147
147
  export * from './no-test-run.js';
148
148
  export * from './output-filter.js';
149
149
  export * from './pipeline-exit-note.js';
150
+ export * from './playwright-report.js';
150
151
  export * from './schemas.js';
151
152
  export * from './server-command.js';
152
153
  export * from './sleep-peek.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuIG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AACvC,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuIG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AACvC,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -147,6 +147,7 @@ export * from './line-diff.js';
147
147
  export * from './no-test-run.js';
148
148
  export * from './output-filter.js';
149
149
  export * from './pipeline-exit-note.js';
150
+ export * from './playwright-report.js';
150
151
  export * from './schemas.js';
151
152
  export * from './server-command.js';
152
153
  export * from './sleep-peek.js';