@molecule/api-ai-tools 1.6.2 → 1.6.5
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 +231 -3
- package/dist/command-position.d.ts +1 -1
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +55 -21
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/suite-overlap.d.ts +132 -0
- package/dist/suite-overlap.d.ts.map +1 -0
- package/dist/suite-overlap.js +208 -0
- package/dist/tools.d.ts +43 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +288 -42
- package/dist/utilities.d.ts +38 -2
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +82 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-16T19:48:43.608Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -132,6 +132,29 @@ interface ToolSchema {
|
|
|
132
132
|
}
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
#### `SuiteRunInFlight`
|
|
136
|
+
|
|
137
|
+
One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
138
|
+
|
|
139
|
+
Both callers describe the runs in flight the same way and ask the same question of them; what
|
|
140
|
+
differs is only which runs each one knows about — the hold reads its ledger of background runs,
|
|
141
|
+
the note reads the one run it is waiting on.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
interface SuiteRunInFlight {
|
|
145
|
+
/** Whether the run invokes a suite at all: a `vitest`/`jest` run is not one, and cannot be. */
|
|
146
|
+
runsSuite: boolean
|
|
147
|
+
/** The app and database the run measures, as {@link suiteCommandTarget} keys it. */
|
|
148
|
+
target: string
|
|
149
|
+
/**
|
|
150
|
+
* Whether the command being judged STOPS this run before starting its own — a kill comes first,
|
|
151
|
+
* so a run the command itself stops is not in the way (xr-molecule-flash-m87's
|
|
152
|
+
* `kill 19408 … ; npx playwright test …`).
|
|
153
|
+
*/
|
|
154
|
+
stoppedByCommand?: boolean
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
135
158
|
#### `ExecutionBackend`
|
|
136
159
|
|
|
137
160
|
Abstraction over the execution environment.
|
|
@@ -606,6 +629,108 @@ function commandRunsNoTests(command: string): boolean
|
|
|
606
629
|
|
|
607
630
|
**Returns:** True when the command invokes a runner only to print.
|
|
608
631
|
|
|
632
|
+
#### `withoutHeredocs(command)`
|
|
633
|
+
|
|
634
|
+
A command with its heredoc bodies removed: the runs, kills and hosts it names in a file it
|
|
635
|
+
WRITES are not what the command itself does.
|
|
636
|
+
|
|
637
|
+
The header is kept (`cat > run.sh <<'EOF' …EOF` leaves `cat > run.sh`), which is what the
|
|
638
|
+
readers here want: everything before the body is still readable as the command's own words.
|
|
639
|
+
|
|
640
|
+
```typescript
|
|
641
|
+
function withoutHeredocs(command: string): string
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
- `command` — The shell command.
|
|
645
|
+
|
|
646
|
+
**Returns:** The command without its heredoc bodies.
|
|
647
|
+
|
|
648
|
+
#### `suiteCommandTarget(command)`
|
|
649
|
+
|
|
650
|
+
The server a suite command points at, as a comparable key: the host it names (`HOST=`,
|
|
651
|
+
`BASE_URL=`, `PLAYWRIGHT_BASE_URL=`, `--variable host=`, or a bare URL), or `local` for a run
|
|
652
|
+
that names none — a suite run in the sandbox goes to the sandbox's own app unless the command
|
|
653
|
+
says otherwise. Every localhost spelling collapses to `local` (one sandbox, one app, one
|
|
654
|
+
database).
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
function suiteCommandTarget(command: string): string
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
- `command` — The suite command.
|
|
661
|
+
|
|
662
|
+
**Returns:** The target key.
|
|
663
|
+
|
|
664
|
+
#### `scopedRerunCommand(command, locations)`
|
|
665
|
+
|
|
666
|
+
The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
667
|
+
invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
668
|
+
suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
669
|
+
`--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
function scopedRerunCommand(command: string, locations: readonly string[]): string
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
- `command` — The whole-suite run's command.
|
|
676
|
+
- `locations` — The failing `file:line`s to re-run, as the run's own log printed them — or, for a run that started outside the suite, the absolute form Playwright matches each file argument against.
|
|
677
|
+
|
|
678
|
+
**Returns:** The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
679
|
+
|
|
680
|
+
#### `suiteRunInTheWay(candidate, inFlight)`
|
|
681
|
+
|
|
682
|
+
The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
683
|
+
|
|
684
|
+
This is the ONE rule the mid-run note and the suite-overlap hold both answer with: a suite run
|
|
685
|
+
may start only when this returns null, because two suites against one app and database share its
|
|
686
|
+
rows — the failures one causes the other cannot be told from real ones. A run that invokes no
|
|
687
|
+
suite is never in the way, and neither is one the candidate command stops first.
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
function suiteRunInTheWay(candidate: { target: string }, inFlight: readonly T[]): T | null
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
- `candidate` — The run about to start, by the app and database it would measure ({@link suiteCommandTarget}).
|
|
694
|
+
- `inFlight` — The suite runs already going, in the order the caller found them.
|
|
695
|
+
|
|
696
|
+
**Returns:** The first run in the way, or null.
|
|
697
|
+
|
|
698
|
+
#### `isDiagnosisRun(command)`
|
|
699
|
+
|
|
700
|
+
Whether a command walks a PART of a suite instead of all of it: a cap that stops it at a failure
|
|
701
|
+
count, the failures the last run printed (`--last-failed`, `--only-changed`), a title filter, or
|
|
702
|
+
named files/lines.
|
|
703
|
+
|
|
704
|
+
THE one reading of that question, in one place. Two sites act on the answer and must never
|
|
705
|
+
disagree:
|
|
706
|
+
|
|
707
|
+
- the command rewriter that adds `--retries=0` to a background Playwright run
|
|
708
|
+
({@link ./utilities.js} `streamingBackgroundCommand`) — a diagnosis walks each failure once,
|
|
709
|
+
while the run the suite is judged by keeps the suite's own retries; and
|
|
710
|
+
- the platform's own notes (molecule-dev's `suite-cli-override.ts`), which must not tell the
|
|
711
|
+
executor to drop the `--retries=0` the platform itself has just added — the contradiction X1
|
|
712
|
+
flash-m74 and m75 filed, and m76 filed a third time.
|
|
713
|
+
|
|
714
|
+
Until xr-molecule-flash-m89 they were a regex and a predicate that only LOOKED alike, and the
|
|
715
|
+
gap was exactly the run the platform had asked for: its `testRunNote` says "run the whole suite
|
|
716
|
+
once with `--max-failures=0`", and the rewriter read that cap flag as a capped diagnosis and
|
|
717
|
+
stripped the suite's retries from the uncapped proof run that follows a deploy — the one run the
|
|
718
|
+
graded run is compared against (+25:03, +31:39, +36:40 and +49:57, the last two against the
|
|
719
|
+
deployed URL, where a network blip is likeliest and the retries it dropped are the ones that
|
|
720
|
+
absorb it).
|
|
721
|
+
|
|
722
|
+
`--max-failures=0` is Playwright's own "no cap" ({@link boundsFailures}), so it is not a
|
|
723
|
+
|
|
724
|
+
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/suite-overlap.d.ts`.)_
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
function isDiagnosisRun(command: string): boolean
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
- `command` — The command as the executor sent it.
|
|
731
|
+
|
|
732
|
+
**Returns:** True when the run is a diagnosis or a re-run, not the whole suite.
|
|
733
|
+
|
|
609
734
|
#### `discoverSkills(backend)`
|
|
610
735
|
|
|
611
736
|
Discover skills from a project directory.
|
|
@@ -644,6 +769,27 @@ function buildAgentPrompt(ctx: PromptContext): string
|
|
|
644
769
|
|
|
645
770
|
**Returns:** Fully assembled system prompt text for the coding agent.
|
|
646
771
|
|
|
772
|
+
#### `taskStopCommand(backend, taskId)`
|
|
773
|
+
|
|
774
|
+
The command that stops a running background task, read from the sandbox's own pid files.
|
|
775
|
+
|
|
776
|
+
THE one builder for it, so every note that hands the executor a way to stop a run hands over the
|
|
777
|
+
same pid-only form: a `pkill -f <something>` matches the shell running it (its argv IS the
|
|
778
|
+
command), and `molecule-dev/api/src/ai/pattern-kill-guard.ts` holds exactly that — X1
|
|
779
|
+
xr-molecule-flash-m87's stuck note suggested `kill $(cat /tmp/<id>.pid) 2>/dev/null; pkill -f
|
|
780
|
+
<id>.sh`, which the platform's own guard then refused, and the same shape cost a refused call at
|
|
781
|
+
+21:57. The pid tree is the whole of what the pattern was for: the detached shell and every
|
|
782
|
+
process under it.
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
function taskStopCommand(backend: ExecutionBackend, taskId: string): Promise<string>
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
- `backend` — The execution backend.
|
|
789
|
+
- `taskId` — The task's `mol-bg-…` handle.
|
|
790
|
+
|
|
791
|
+
**Returns:** The command to hand the executor — `kill <pids>` — falling back to the task's own pid file when the walk comes back empty (a sandbox without `pgrep`, or a pid file already gone, in which case the pid-file form is still the one the platform's guard names).
|
|
792
|
+
|
|
647
793
|
#### `moleculeDistReadNote(path)`
|
|
648
794
|
|
|
649
795
|
The steer attached to a SUCCESSFUL read of a package's built output. The ENOENT
|
|
@@ -662,6 +808,36 @@ function moleculeDistReadNote(path: string): string | null
|
|
|
662
808
|
|
|
663
809
|
**Returns:** The note, or null when the path is not inside a package's `dist`.
|
|
664
810
|
|
|
811
|
+
#### `installedPackageWriteRefusal(path)`
|
|
812
|
+
|
|
813
|
+
The refusal for a write whose target is inside an installed package, or null
|
|
814
|
+
when the path is not under a `node_modules` directory.
|
|
815
|
+
|
|
816
|
+
`node_modules` is not the project's code: nothing written there survives the
|
|
817
|
+
next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
818
|
+
shared (the pre-installed superset) breaks every other run through it. X0
|
|
819
|
+
xr-molecule-flash-m91 (+04:43) called `write_file` on
|
|
820
|
+
`node_modules/@molecule/api-resource-comment/dist/numbers.d.ts` while reading
|
|
821
|
+
the package's declarations and clobbered its runtime module a call later
|
|
822
|
+
(`module.exports = {}` over the ESM handler its own code imports) — the write
|
|
823
|
+
answered `ok: true`, and 18 s went to noticing, `npm pack`-ing the package and
|
|
824
|
+
putting the file back. Reading these files stays allowed (that is how the
|
|
825
|
+
declarations above are read at all).
|
|
826
|
+
|
|
827
|
+
The restore route is named because the model that has ALREADY clobbered a file
|
|
828
|
+
(this refusal is too late for it) is the one who needs it, and because the
|
|
829
|
+
obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
830
|
+
sandbox. `npm pack` + a `tar` extraction over the package directory is what
|
|
831
|
+
m91 used, and what both guards leave alone.
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
function installedPackageWriteRefusal(path: string): string | null
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
- `path` — An absolute path a tool is about to write.
|
|
838
|
+
|
|
839
|
+
**Returns:** The one-line refusal naming the package, or null when nothing is installed there.
|
|
840
|
+
|
|
665
841
|
#### `buildTools(backend, config)`
|
|
666
842
|
|
|
667
843
|
Build a complete set of AI agent tools bound to an execution backend.
|
|
@@ -1343,6 +1519,31 @@ function failureCausesNote(log: string): string | null
|
|
|
1343
1519
|
|
|
1344
1520
|
**Returns:** The note, or null.
|
|
1345
1521
|
|
|
1522
|
+
#### `rerunOfRunningSuite(run, locations)`
|
|
1523
|
+
|
|
1524
|
+
The command that re-runs the tests at `locations` the way the run IN FLIGHT ran them: the same
|
|
1525
|
+
invocation, in the same directory, narrowed to those tests — or null when the running command
|
|
1526
|
+
is not a Playwright run this can narrow.
|
|
1527
|
+
|
|
1528
|
+
The `locations` are the paths the run's own log printed, which are relative to the directory
|
|
1529
|
+
that run started in, so a re-run has to resolve them there: the command names that directory
|
|
1530
|
+
unless it already changes into one of its own, because the executor's next command starts
|
|
1531
|
+
wherever it starts. The returned command is for AFTER the run ends — it is a second run against
|
|
1532
|
+
the same app and database, and that is exactly what is refused while the run walks
|
|
1533
|
+
({@link suiteRunInTheWay}).
|
|
1534
|
+
|
|
1535
|
+
```typescript
|
|
1536
|
+
function rerunOfRunningSuite(
|
|
1537
|
+
run: { command: string; cwd?: string },
|
|
1538
|
+
locations: readonly string[],
|
|
1539
|
+
): string | null
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
- `run` — The run in flight: the command it was started with, as it was run, and the directory that command ran in.
|
|
1543
|
+
- `locations` — The failing `file:line`s to re-run, as the run's log printed them.
|
|
1544
|
+
|
|
1545
|
+
**Returns:** The re-run command, or null when there is no suite to narrow.
|
|
1546
|
+
|
|
1346
1547
|
#### `repeatingFailureNote(found, o)`
|
|
1347
1548
|
|
|
1348
1549
|
The sentence that names a cause repeating among a running suite's failures, mid-run.
|
|
@@ -1358,15 +1559,30 @@ cause was certain at the third, 47 s in, and the note answers exactly that; X1 f
|
|
|
1358
1559
|
stopped eight runs at their first repeating cause in 14.5 minutes and never finished one, so
|
|
1359
1560
|
the note must not say to stop when the run still has other causes to name.
|
|
1360
1561
|
|
|
1562
|
+
The run is STILL WALKING while this note is written — both callers produce it mid-run — so a
|
|
1563
|
+
re-run of one of its tests is a second run against the same app and database, and the platform
|
|
1564
|
+
refuses that ({@link suiteRunInTheWay}, the same rule its overlap hold answers with):
|
|
1565
|
+
xr-molecule-flash-m89 (2026-10-16) read _"Start on them now, while the run keeps walking.
|
|
1566
|
+
`null-fields.spec.ts:53` alone reproduces the first one."_ and sent exactly that re-run 23 s
|
|
1567
|
+
later, where the hold answered *"A playwright suite is already running against this app and
|
|
1568
|
+
|
|
1569
|
+
_(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
|
|
1570
|
+
|
|
1361
1571
|
```typescript
|
|
1362
1572
|
function repeatingFailureNote(
|
|
1363
1573
|
found: RepeatingFailure,
|
|
1364
|
-
o: {
|
|
1574
|
+
o: {
|
|
1575
|
+
taskId: string
|
|
1576
|
+
log: string
|
|
1577
|
+
stop: string
|
|
1578
|
+
universal?: UniversalCause | null
|
|
1579
|
+
run?: { command: string; cwd?: string }
|
|
1580
|
+
},
|
|
1365
1581
|
): string
|
|
1366
1582
|
```
|
|
1367
1583
|
|
|
1368
1584
|
- `found` — What {@link repeatingFailure} found.
|
|
1369
|
-
- `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for a {@link UniversalCause} —
|
|
1585
|
+
- `o` — The run's facts: its `taskId`, its `log` path, `stop` — the shell command that stops it, printed only for a {@link UniversalCause} — `universal`, what {@link universalCause} found (absent means the run is not failing whole), and `run`, how the run was started ({@link rerunOfRunningSuite}) when the caller knows it.
|
|
1370
1586
|
|
|
1371
1587
|
**Returns:** The note.
|
|
1372
1588
|
|
|
@@ -1779,6 +1995,18 @@ Peer dependencies:
|
|
|
1779
1995
|
`newlineNote` hint beside an unrelated-looking parse error, which an executor
|
|
1780
1996
|
repairs by rewriting the fused line's text and fuses the next one. The
|
|
1781
1997
|
`newlineNote` alone is still returned when the file parses.
|
|
1998
|
+
- **A replacement that only changes a line break at its boundary is refused,
|
|
1999
|
+
not applied.** When `new_string` is `old_string` with its trailing newline
|
|
2000
|
+
added or removed and nothing else, the edit can only move text across lines —
|
|
2001
|
+
joining the next line onto the replaced text, or pushing the rest of the line
|
|
2002
|
+
down — which is what a weak model does by accident when it repeats a line it
|
|
2003
|
+
means to keep (flash-m28, m55, m62, m89, five times in the m89 run alone). The
|
|
2004
|
+
replacement is skipped, the rest of the batch is applied, and the result
|
|
2005
|
+
carries `skippedNote`: one line naming the replacement, what applying it would
|
|
2006
|
+
have done, and how to say it on purpose (include the next line in `old_string`).
|
|
2007
|
+
The file's own next character decides: text on the same line is a join or a
|
|
2008
|
+
split and is refused; a blank line after it, or the end of the file, means no
|
|
2009
|
+
text crosses lines and the edit is applied as written.
|
|
1782
2010
|
- **A parameter under another name is not a broken tool.** Every tool's input
|
|
1783
2011
|
is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
|
|
1784
2012
|
snake_case), and a handler that throws returns an error naming the missing
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* (`head -30 run-api-tests-hurl.sh`, `grep -n playwright e2e/README.md`,
|
|
23
23
|
* `git show HEAD:run-api-tests.sh`), and a runner named in a heredoc body — a file a command
|
|
24
24
|
* WRITES is the caller's business, so a command with a heredoc goes through the caller's own
|
|
25
|
-
* stripper first (`withoutHeredocs
|
|
25
|
+
* stripper first (`withoutHeredocs`, in `./suite-overlap.js`).
|
|
26
26
|
*
|
|
27
27
|
* @module
|
|
28
28
|
*/
|
|
@@ -1 +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;
|
|
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;AA8TD;;;;;;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"}
|
package/dist/command-position.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* (`head -30 run-api-tests-hurl.sh`, `grep -n playwright e2e/README.md`,
|
|
23
23
|
* `git show HEAD:run-api-tests.sh`), and a runner named in a heredoc body — a file a command
|
|
24
24
|
* WRITES is the caller's business, so a command with a heredoc goes through the caller's own
|
|
25
|
-
* stripper first (`withoutHeredocs
|
|
25
|
+
* stripper first (`withoutHeredocs`, in `./suite-overlap.js`).
|
|
26
26
|
*
|
|
27
27
|
* @module
|
|
28
28
|
*/
|
|
@@ -206,50 +206,84 @@ function commandStart(words) {
|
|
|
206
206
|
* A single `&` separates only when it backgrounds a command: the one in a redirect (`2>&1`,
|
|
207
207
|
* `&>log`) is part of the word it sits in.
|
|
208
208
|
*
|
|
209
|
+
* The nestings are a STACK, not a (quote, depth) pair, because they interleave: a command
|
|
210
|
+
* substitution opened inside a double quote can itself hold double quotes, and each of those can
|
|
211
|
+
* hold another substitution. `DIR="$(cd "$(dirname "$0")" && pwd)"` is exactly that shape — it
|
|
212
|
+
* opens `"`, `$(`, `"`, `$(`, `"` in order — and every runner script of the RealWorld kind has it.
|
|
213
|
+
* A flat walk read that first inner `"` as the CLOSE of the outer one, so the `)` that ends the
|
|
214
|
+
* outer substitution landed inside a quote that was never opened, the depth was left standing at
|
|
215
|
+
* 1, and every newline in the rest of the script was swallowed: the script's own `hurl --test`
|
|
216
|
+
* never reached command position, and X1 xr-molecule-flash-m89 saw seven passing Hurl runs the
|
|
217
|
+
* platform could not see (the runner is read from the tree's own script TEXT —
|
|
218
|
+
* `molecule-dev/api/src/ai/supplied-tree-guard.ts` — so a script that does not classify records
|
|
219
|
+
* no runner, and every run of it reads as nothing).
|
|
220
|
+
*
|
|
209
221
|
* @param command - The shell command.
|
|
210
222
|
* @returns Each segment's text, in order, with empty ones dropped.
|
|
211
223
|
*/
|
|
212
224
|
function splitSegments(command) {
|
|
213
225
|
const parts = [];
|
|
214
226
|
let buffer = '';
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
/** The nestings this position sits inside, outermost first. */
|
|
228
|
+
const stack = [];
|
|
217
229
|
for (let i = 0; i < command.length; i++) {
|
|
218
230
|
const char = command[i] ?? '';
|
|
219
|
-
|
|
231
|
+
const next = command[i + 1];
|
|
232
|
+
const top = stack[stack.length - 1];
|
|
233
|
+
// A single-quoted string expands nothing at all: no escape, no substitution, and no quote
|
|
234
|
+
// but its own ends it.
|
|
235
|
+
if (top === 'single') {
|
|
220
236
|
buffer += char;
|
|
221
|
-
if (char === '
|
|
222
|
-
|
|
223
|
-
i += 1;
|
|
224
|
-
}
|
|
225
|
-
else if (char === quote)
|
|
226
|
-
quote = null;
|
|
237
|
+
if (char === "'")
|
|
238
|
+
stack.pop();
|
|
227
239
|
continue;
|
|
228
240
|
}
|
|
229
|
-
|
|
230
|
-
|
|
241
|
+
// A command substitution, wherever a shell opens one — inside double quotes too, and never
|
|
242
|
+
// inside single quotes (handled above). The `(` is consumed here so the depth is counted once.
|
|
243
|
+
if (char === '$' && next === '(') {
|
|
244
|
+
stack.push('sub');
|
|
245
|
+
buffer += '$(';
|
|
246
|
+
i += 1;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Inside double quotes: a `"` closes them, a backslash escapes the next character (so
|
|
250
|
+
// `"\$("` opens nothing), and everything else is literal — a `'`, a `|`, a newline included.
|
|
251
|
+
if (top === 'double') {
|
|
252
|
+
if (char === '"')
|
|
253
|
+
stack.pop();
|
|
254
|
+
else if (char === '\\' && next !== undefined) {
|
|
255
|
+
buffer += char + next;
|
|
256
|
+
i += 1;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
231
259
|
buffer += char;
|
|
232
260
|
continue;
|
|
233
261
|
}
|
|
234
|
-
if (char === '
|
|
235
|
-
|
|
236
|
-
|
|
262
|
+
if (char === '"') {
|
|
263
|
+
stack.push('double');
|
|
264
|
+
buffer += char;
|
|
237
265
|
continue;
|
|
238
266
|
}
|
|
239
|
-
if (char === '
|
|
240
|
-
|
|
267
|
+
if (char === "'") {
|
|
268
|
+
stack.push('single');
|
|
241
269
|
buffer += char;
|
|
242
270
|
continue;
|
|
243
271
|
}
|
|
244
|
-
|
|
272
|
+
// Inside a substitution or a subshell of one: its own parentheses nest, its `|`/`;`/`&&`
|
|
273
|
+
// belong to the nested command, and its newlines are that command's too.
|
|
274
|
+
if (top === 'sub') {
|
|
245
275
|
if (char === '(')
|
|
246
|
-
|
|
276
|
+
stack.push('sub');
|
|
247
277
|
else if (char === ')')
|
|
248
|
-
|
|
278
|
+
stack.pop();
|
|
249
279
|
buffer += char;
|
|
250
280
|
continue;
|
|
251
281
|
}
|
|
252
|
-
|
|
282
|
+
if (char === '\\' && next !== undefined) {
|
|
283
|
+
buffer += char + next;
|
|
284
|
+
i += 1;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
253
287
|
if ((char === '&' && next === '&') || (char === '|' && next === '|')) {
|
|
254
288
|
parts.push(buffer);
|
|
255
289
|
buffer = '';
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,18 @@
|
|
|
51
51
|
* `newlineNote` hint beside an unrelated-looking parse error, which an executor
|
|
52
52
|
* repairs by rewriting the fused line's text and fuses the next one. The
|
|
53
53
|
* `newlineNote` alone is still returned when the file parses.
|
|
54
|
+
* - **A replacement that only changes a line break at its boundary is refused,
|
|
55
|
+
* not applied.** When `new_string` is `old_string` with its trailing newline
|
|
56
|
+
* added or removed and nothing else, the edit can only move text across lines —
|
|
57
|
+
* joining the next line onto the replaced text, or pushing the rest of the line
|
|
58
|
+
* down — which is what a weak model does by accident when it repeats a line it
|
|
59
|
+
* means to keep (flash-m28, m55, m62, m89, five times in the m89 run alone). The
|
|
60
|
+
* replacement is skipped, the rest of the batch is applied, and the result
|
|
61
|
+
* carries `skippedNote`: one line naming the replacement, what applying it would
|
|
62
|
+
* have done, and how to say it on purpose (include the next line in `old_string`).
|
|
63
|
+
* The file's own next character decides: text on the same line is a join or a
|
|
64
|
+
* split and is refused; a blank line after it, or the end of the file, means no
|
|
65
|
+
* text crosses lines and the edit is applied as written.
|
|
54
66
|
* - **A parameter under another name is not a broken tool.** Every tool's input
|
|
55
67
|
* is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
|
|
56
68
|
* snake_case), and a handler that throws returns an error naming the missing
|
|
@@ -124,6 +136,7 @@ export * from './command-position.js';
|
|
|
124
136
|
export * from './input-normalizer.js';
|
|
125
137
|
export * from './no-test-run.js';
|
|
126
138
|
export * from './schemas.js';
|
|
139
|
+
export * from './suite-overlap.js';
|
|
127
140
|
export * from './system-prompt.js';
|
|
128
141
|
export * from './tools.js';
|
|
129
142
|
export * from './types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,18 @@
|
|
|
51
51
|
* `newlineNote` hint beside an unrelated-looking parse error, which an executor
|
|
52
52
|
* repairs by rewriting the fused line's text and fuses the next one. The
|
|
53
53
|
* `newlineNote` alone is still returned when the file parses.
|
|
54
|
+
* - **A replacement that only changes a line break at its boundary is refused,
|
|
55
|
+
* not applied.** When `new_string` is `old_string` with its trailing newline
|
|
56
|
+
* added or removed and nothing else, the edit can only move text across lines —
|
|
57
|
+
* joining the next line onto the replaced text, or pushing the rest of the line
|
|
58
|
+
* down — which is what a weak model does by accident when it repeats a line it
|
|
59
|
+
* means to keep (flash-m28, m55, m62, m89, five times in the m89 run alone). The
|
|
60
|
+
* replacement is skipped, the rest of the batch is applied, and the result
|
|
61
|
+
* carries `skippedNote`: one line naming the replacement, what applying it would
|
|
62
|
+
* have done, and how to say it on purpose (include the next line in `old_string`).
|
|
63
|
+
* The file's own next character decides: text on the same line is a join or a
|
|
64
|
+
* split and is refused; a blank line after it, or the end of the file, means no
|
|
65
|
+
* text crosses lines and the edit is applied as written.
|
|
54
66
|
* - **A parameter under another name is not a broken tool.** Every tool's input
|
|
55
67
|
* is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
|
|
56
68
|
* snake_case), and a handler that throws returns an error naming the missing
|
|
@@ -124,6 +136,7 @@ export * from './command-position.js';
|
|
|
124
136
|
export * from './input-normalizer.js';
|
|
125
137
|
export * from './no-test-run.js';
|
|
126
138
|
export * from './schemas.js';
|
|
139
|
+
export * from './suite-overlap.js';
|
|
127
140
|
export * from './system-prompt.js';
|
|
128
141
|
export * from './tools.js';
|
|
129
142
|
export * from './types.js';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE rule that decides whether a suite run may start while another is already walking, and
|
|
3
|
+
* the command readings both sides of it need.
|
|
4
|
+
*
|
|
5
|
+
* Two sites ask "may a second run start now?" about the same pair of runs — the mid-run note that
|
|
6
|
+
* tells the executor what to do next while the suite it is waiting on is still going
|
|
7
|
+
* ({@link ./utilities.js} `repeatingFailureNote`), and the hold that refuses the run when it
|
|
8
|
+
* arrives (molecule-dev's `suite-overlap-hold.ts`) — and until xr-molecule-flash-m89 they answered
|
|
9
|
+
* it separately. That run's +19:59 `wait_for_task` handed over a repeating cause with "the test
|
|
10
|
+
* alone reproduces the first one … start on them now, while the run keeps walking"; 23 s later the
|
|
11
|
+
* executor sent exactly that re-run, and the overlap hold refused it — "A playwright suite is
|
|
12
|
+
* already running against this app and database … Wait for it." Two orders, one run, and the round
|
|
13
|
+
* trip between them was the whole cost.
|
|
14
|
+
*
|
|
15
|
+
* So the rule lives here ({@link suiteRunInTheWay}) and both of them call it: a run may start only
|
|
16
|
+
* when nothing in flight is already measuring the same app and database. What "the same app and
|
|
17
|
+
* database" MEANS lives here too ({@link suiteCommandTarget}) — the note and the hold have to key
|
|
18
|
+
* a command the same way before they can agree on whether two of them collide, and the re-run a
|
|
19
|
+
* note hands over is built from the same reading of the running command
|
|
20
|
+
* ({@link scopedRerunCommand}).
|
|
21
|
+
*
|
|
22
|
+
* The same treatment for the other pair that answered one question twice: whether a run walks the
|
|
23
|
+
* WHOLE suite or a part of it. The rewriter that decides whether a background Playwright run gets
|
|
24
|
+
* `--retries=0` ({@link ./utilities.js} `streamingBackgroundCommand`) and the platform's own notes
|
|
25
|
+
* (molecule-dev's `suite-cli-override.ts`) asked it separately — a regex there, a predicate here —
|
|
26
|
+
* until xr-molecule-flash-m89 showed the two answers were never the same question
|
|
27
|
+
* ({@link isDiagnosisRun}).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* A command with its heredoc bodies removed: the runs, kills and hosts it names in a file it
|
|
33
|
+
* WRITES are not what the command itself does.
|
|
34
|
+
*
|
|
35
|
+
* The header is kept (`cat > run.sh <<'EOF' …EOF` leaves `cat > run.sh`), which is what the
|
|
36
|
+
* readers here want: everything before the body is still readable as the command's own words.
|
|
37
|
+
*
|
|
38
|
+
* @param command - The shell command.
|
|
39
|
+
* @returns The command without its heredoc bodies.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withoutHeredocs(command: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* The server a suite command points at, as a comparable key: the host it names (`HOST=`,
|
|
44
|
+
* `BASE_URL=`, `PLAYWRIGHT_BASE_URL=`, `--variable host=`, or a bare URL), or `local` for a run
|
|
45
|
+
* that names none — a suite run in the sandbox goes to the sandbox's own app unless the command
|
|
46
|
+
* says otherwise. Every localhost spelling collapses to `local` (one sandbox, one app, one
|
|
47
|
+
* database).
|
|
48
|
+
*
|
|
49
|
+
* @param command - The suite command.
|
|
50
|
+
* @returns The target key.
|
|
51
|
+
*/
|
|
52
|
+
export declare function suiteCommandTarget(command: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* The whole-suite command made scoped to the tests it should re-run instead: the same
|
|
55
|
+
* invocation — same directory, same settings — with the named `file:line`s in place of the whole
|
|
56
|
+
* suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
|
|
57
|
+
* `--retries=0` set (a re-run of a known failure does not need to pay for retries).
|
|
58
|
+
*
|
|
59
|
+
* @param command - The whole-suite run's command.
|
|
60
|
+
* @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
|
|
61
|
+
* for a run that started outside the suite, the absolute form Playwright matches each file
|
|
62
|
+
* argument against.
|
|
63
|
+
* @returns The scoped command, or the command unchanged when it runs no Playwright suite.
|
|
64
|
+
*/
|
|
65
|
+
export declare function scopedRerunCommand(command: string, locations: readonly string[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* One suite run, as the rule that decides whether a second may start beside it reads it.
|
|
68
|
+
*
|
|
69
|
+
* Both callers describe the runs in flight the same way and ask the same question of them; what
|
|
70
|
+
* differs is only which runs each one knows about — the hold reads its ledger of background runs,
|
|
71
|
+
* the note reads the one run it is waiting on.
|
|
72
|
+
*/
|
|
73
|
+
export interface SuiteRunInFlight {
|
|
74
|
+
/** Whether the run invokes a suite at all: a `vitest`/`jest` run is not one, and cannot be. */
|
|
75
|
+
runsSuite: boolean;
|
|
76
|
+
/** The app and database the run measures, as {@link suiteCommandTarget} keys it. */
|
|
77
|
+
target: string;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the command being judged STOPS this run before starting its own — a kill comes first,
|
|
80
|
+
* so a run the command itself stops is not in the way (xr-molecule-flash-m87's
|
|
81
|
+
* `kill 19408 … ; npx playwright test …`).
|
|
82
|
+
*/
|
|
83
|
+
stoppedByCommand?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The run in flight that makes a second run a hazard, or null when nothing is in the way.
|
|
87
|
+
*
|
|
88
|
+
* This is the ONE rule the mid-run note and the suite-overlap hold both answer with: a suite run
|
|
89
|
+
* may start only when this returns null, because two suites against one app and database share its
|
|
90
|
+
* rows — the failures one causes the other cannot be told from real ones. A run that invokes no
|
|
91
|
+
* suite is never in the way, and neither is one the candidate command stops first.
|
|
92
|
+
*
|
|
93
|
+
* @param candidate - The run about to start, by the app and database it would measure
|
|
94
|
+
* ({@link suiteCommandTarget}).
|
|
95
|
+
* @param inFlight - The suite runs already going, in the order the caller found them.
|
|
96
|
+
* @returns The first run in the way, or null.
|
|
97
|
+
*/
|
|
98
|
+
export declare function suiteRunInTheWay<T extends SuiteRunInFlight>(candidate: {
|
|
99
|
+
target: string;
|
|
100
|
+
}, inFlight: readonly T[]): T | null;
|
|
101
|
+
/**
|
|
102
|
+
* Whether a command walks a PART of a suite instead of all of it: a cap that stops it at a failure
|
|
103
|
+
* count, the failures the last run printed (`--last-failed`, `--only-changed`), a title filter, or
|
|
104
|
+
* named files/lines.
|
|
105
|
+
*
|
|
106
|
+
* THE one reading of that question, in one place. Two sites act on the answer and must never
|
|
107
|
+
* disagree:
|
|
108
|
+
*
|
|
109
|
+
* - the command rewriter that adds `--retries=0` to a background Playwright run
|
|
110
|
+
* ({@link ./utilities.js} `streamingBackgroundCommand`) — a diagnosis walks each failure once,
|
|
111
|
+
* while the run the suite is judged by keeps the suite's own retries; and
|
|
112
|
+
* - the platform's own notes (molecule-dev's `suite-cli-override.ts`), which must not tell the
|
|
113
|
+
* executor to drop the `--retries=0` the platform itself has just added — the contradiction X1
|
|
114
|
+
* flash-m74 and m75 filed, and m76 filed a third time.
|
|
115
|
+
*
|
|
116
|
+
* Until xr-molecule-flash-m89 they were a regex and a predicate that only LOOKED alike, and the
|
|
117
|
+
* gap was exactly the run the platform had asked for: its `testRunNote` says "run the whole suite
|
|
118
|
+
* once with `--max-failures=0`", and the rewriter read that cap flag as a capped diagnosis and
|
|
119
|
+
* stripped the suite's retries from the uncapped proof run that follows a deploy — the one run the
|
|
120
|
+
* graded run is compared against (+25:03, +31:39, +36:40 and +49:57, the last two against the
|
|
121
|
+
* deployed URL, where a network blip is likeliest and the retries it dropped are the ones that
|
|
122
|
+
* absorb it).
|
|
123
|
+
*
|
|
124
|
+
* `--max-failures=0` is Playwright's own "no cap" ({@link boundsFailures}), so it is not a
|
|
125
|
+
* diagnosis. A command that starts no Playwright run is not one either (false): the question is
|
|
126
|
+
* about a run already read as a Playwright suite.
|
|
127
|
+
*
|
|
128
|
+
* @param command - The command as the executor sent it.
|
|
129
|
+
* @returns True when the run is a diagnosis or a re-run, not the whole suite.
|
|
130
|
+
*/
|
|
131
|
+
export declare function isDiagnosisRun(command: string): boolean;
|
|
132
|
+
//# sourceMappingURL=suite-overlap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
|