@molecule/api-ai-tools 1.5.18 → 1.5.20

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-16T09:30:45.054Z
6
+ Generated: 2026-10-16T12:46:49.316Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -242,6 +242,23 @@ interface ToolBuildConfig {
242
242
  */
243
243
  searchExcludedDirs?: string[]
244
244
 
245
+ /**
246
+ * Whether a LARGE HTML document should come back as its OUTLINE — the head's
247
+ * tags and the tags that open each body section, one line each, plus a note
248
+ * naming the offset/limit that returns the whole document — instead of its
249
+ * whole text. Over {@link MAX_HTML_INLINE_CHARS}, and only when the read named
250
+ * no line window.
251
+ *
252
+ * A getter, not a boolean, because the answer belongs to the TURN, not to the
253
+ * tool set: a host that plans first and builds after wants an outline while the
254
+ * executor is researching (where a read it cannot act on is pure context cost)
255
+ * and the document itself while it is writing the page. Unset means every big
256
+ * HTML read returns its text, which is what a host with no such phases wants.
257
+ *
258
+ * @returns True when a large HTML read should be outlined rather than returned.
259
+ */
260
+ outlineLargeHtml?: () => boolean
261
+
245
262
  /** Post-write hook (e.g. auto-format via Prettier/ESLint). Called after every write_file/edit_file. */
246
263
  onAfterWrite?: (path: string) => Promise<void>
247
264
 
@@ -320,6 +337,38 @@ interface PromptContext {
320
337
  }
321
338
  ```
322
339
 
340
+ #### `FailedTest`
341
+
342
+ One failed test, as a running suite's log names it.
343
+
344
+ ```typescript
345
+ interface FailedTest {
346
+ /** The location the log writes (`specs/e2e/articles.spec.ts:32`), or '' when it writes none. */
347
+ where: string
348
+ /** The test's own title — the last segment of its title path. */
349
+ title: string
350
+ /** How long it ran before failing, in seconds, or null when the log does not say. */
351
+ seconds: number | null
352
+ }
353
+ ```
354
+
355
+ #### `RepeatingFailure`
356
+
357
+ A cause repeating among a running suite's failures.
358
+
359
+ ```typescript
360
+ interface RepeatingFailure {
361
+ /** How many failures share the signature. */
362
+ count: number
363
+ /** The shared error line, when the log carries the failures' bodies (`line` reporter). */
364
+ message: string | null
365
+ /** The shared duration in seconds, when the failures line up by how long they ran. */
366
+ seconds: number | null
367
+ /** The failures that share it, in log order. */
368
+ tests: FailedTest[]
369
+ }
370
+ ```
371
+
323
372
  ### Types
324
373
 
325
374
  #### `DiscoveredSkill` _(deprecated)_
@@ -450,6 +499,27 @@ function guardAITool(tool: T, options?: GuardOptions): T
450
499
 
451
500
  **Returns:** A copy with a guarded `execute`.
452
501
 
502
+ #### `commandRunsNoTests(command)`
503
+
504
+ Whether a shell command invokes a test runner and EVERY such invocation only prints — so the
505
+ command runs no test at all.
506
+
507
+ A command that invokes no runner is NOT one of these (false): "the command runs no test" is
508
+ then true for `ls`, which is not the question any caller is asking. The question is whether
509
+ the runner invocation each caller read as a run is one.
510
+
511
+ A command that also runs tests is false however it is spelled: `npx playwright install &&
512
+ npx playwright test` installs and runs, so a caller that sees `playwright test` in it is
513
+ reading a real run.
514
+
515
+ ```typescript
516
+ function commandRunsNoTests(command: string): boolean
517
+ ```
518
+
519
+ - `command` — The shell command as the executor sent it.
520
+
521
+ **Returns:** True when the command invokes a runner only to print.
522
+
453
523
  #### `discoverSkills(backend)`
454
524
 
455
525
  Discover skills from a project directory.
@@ -689,7 +759,12 @@ killed (X1 run flash-m15 lost 20 of its 60 minutes this way, 2026-10-15):
689
759
  `[n/N] <title>` with no ✓/✘ mark and no duration, so on X1 flash-m62 an
690
760
  8.3-minute walk of one suite could never trip the fail-fast note.
691
761
 
692
- Only the command's last step is touched, and only its reporter.
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
766
+
767
+ _(description truncated — read the full text in `node_modules/@molecule/api-ai-tools/dist/utilities.d.ts`.)_
693
768
 
694
769
  ```typescript
695
770
  function streamingBackgroundCommand(command: string): { command: string; changes: string[] }
@@ -699,6 +774,73 @@ function streamingBackgroundCommand(command: string): { command: string; changes
699
774
 
700
775
  **Returns:** The command to run and one sentence per change made (empty when none).
701
776
 
777
+ #### `isSuiteRunCommand(command)`
778
+
779
+ Whether a command runs a whole test suite — a Playwright, Vitest or Hurl run
780
+ — rather than a build, an install or a one-off script.
781
+
782
+ A command that invokes a runner only to print is not one of these
783
+ ({@link commandRunsNoTests}): `npx playwright test --list` prints the tests it would run and
784
+ exits, and `npx playwright install`, `vitest list` and `hurl --help` start no test either.
785
+
786
+ ```typescript
787
+ function isSuiteRunCommand(command: string): boolean
788
+ ```
789
+
790
+ - `command` — The shell command.
791
+
792
+ **Returns:** True when the command starts one of those runners.
793
+
794
+ #### `isPlaywrightRunCommand(command)`
795
+
796
+ Whether a command starts a Playwright run — `playwright test` in a form that runs something.
797
+
798
+ `npx playwright test --list` matched every `\bplaywright\s+test\b` test in the platform and was
799
+ then treated as a run: held (flash-m79's config check), backgrounded for a run that outlasts
800
+ the tool's ceiling, and rewritten with `--reporter`. A list takes two seconds, prints its lines
801
+ and exits, so it is none of those; {@link commandRunsNoTests} is what decides.
802
+
803
+ ```typescript
804
+ function isPlaywrightRunCommand(command: string): boolean
805
+ ```
806
+
807
+ - `command` — The shell command.
808
+
809
+ **Returns:** True when the command starts a Playwright run.
810
+
811
+ #### `suiteRunPriorityLaunch(command, launch)`
812
+
813
+ Start a background suite run at lower CPU and IO priority, so the sandbox it
814
+ shares with everything else stays responsive to what the executor does next.
815
+
816
+ A whole-suite Playwright run saturates the one container the app's Vite
817
+ server, its API process and every other command also live in: on X1 flash-m75
818
+ the model backgrounded a 139-test run at +36:23 and the two-word shell
819
+ command it sent next — tick its plan file — returned nothing for 2 min 40 s,
820
+ measured on the chat payload and on the live-stream follower independently.
821
+ The backgrounding note is right that the executor should go do other work;
822
+ what it cannot promise is that the sandbox will let it.
823
+
824
+ `nice -n 10` lowers the run's CPU share and `ionice -c3` its IO class. Both
825
+ are PROBED in the sandbox by RUNNING them, not with `command -v`: a syscall a
826
+ container's seccomp policy refuses leaves `-v` passing while the real run
827
+ fails. Each is applied only if its probe passed, so a sandbox with neither,
828
+ or with only one, runs the suite exactly as it did before. Playwright's own
829
+ workers are children of this shell and inherit both (niceness and IO class
830
+ are inherited across fork/exec).
831
+
832
+ Only a command the caller has already detached reaches this: foreground
833
+ commands are never touched.
834
+
835
+ ```typescript
836
+ function suiteRunPriorityLaunch(command: string, launch: string): { launch: string; note: string }
837
+ ```
838
+
839
+ - `command` — The command as the executor sent it; the suite detection reads this.
840
+ - `launch` — The shell line that would start it, with no prefix of its own.
841
+
842
+ **Returns:** The line to run, and one sentence for the tool note ('' when unchanged).
843
+
702
844
  #### `checkBlockedCommand(command)`
703
845
 
704
846
  Check if a command is blocked for security reasons. Returns error message or null if allowed.
@@ -766,6 +908,32 @@ function isValidGlob(pattern: string): boolean
766
908
 
767
909
  **Returns:** `true` when the pattern contains only allowed characters.
768
910
 
911
+ #### `findPatternArgs(pattern)`
912
+
913
+ The `find` predicate for a `find_files` pattern.
914
+
915
+ `find -name` compares a pattern against a file's BASENAME, so a pattern
916
+ containing a slash (`helpers/*`, `e2e/*.spec.ts`, `src` + `**` + `/App.tsx`)
917
+ can never match and the tool answers `[]` (xr-molecule-flash-m77, on two
918
+ calls in one run). Such a pattern is matched with `-path` instead: an fnmatch
919
+ glob over the whole printed path, whose `*` crosses `/` — the rule
920
+ `.gitignore` uses, and the reason a bare `helpers/*` means "a `helpers/`
921
+ directory anywhere below the root". The pattern is anchored with a leading
922
+ `*` then `/` ("any parent") so the search root — an absolute
923
+ `/workspace/<app>` in a sandbox — need not be spelled out.
924
+
925
+ A doubled-star directory segment keeps its usual meaning of ZERO or more
926
+ directories; `-path` alone would demand at least one, so the pattern is also
927
+ emitted with each such segment removed.
928
+
929
+ ```typescript
930
+ function findPatternArgs(pattern: string): string
931
+ ```
932
+
933
+ - `pattern` — A pattern already accepted by {@link isValidGlob}.
934
+
935
+ **Returns:** A `find` predicate: `-name '…'` for a bare name, else one or more `-path '…'` alternatives, ORed inside parens when there is more than one.
936
+
769
937
  #### `pathArgError(path, tool)`
770
938
 
771
939
  Validate a file tool's `path` argument is a non-empty string. A weak model
@@ -821,6 +989,36 @@ function sliceLines(
821
989
 
822
990
  **Returns:** The window plus its position, or null when neither was given.
823
991
 
992
+ #### `looksLikeHtml(text)`
993
+
994
+ Whether `text` is an HTML document (a doctype or an `<html` root tag).
995
+
996
+ ```typescript
997
+ function looksLikeHtml(text: string): boolean
998
+ ```
999
+
1000
+ #### `htmlOutline(text, label)`
1001
+
1002
+ An OUTLINE of an HTML document: its head's tags and the tags that open each
1003
+ body section, one per line, with line numbers.
1004
+
1005
+ Line-based on purpose — a parser would be a dependency and an attack surface
1006
+ for a read that only has to be _navigable_. A line is shown when it starts with
1007
+ `<` after trimming, which is what keeps the inside of a `<style>` block, a
1008
+ `<script>` body or a prerendered text section out of the outline while keeping
1009
+ every tag the document is built from. Long lines (a minified tag, a data URI)
1010
+ are cut, and the whole outline is capped, so a pathological document cannot
1011
+ turn one read into another wall of text.
1012
+
1013
+ ```typescript
1014
+ function htmlOutline(text: string, label?: string): string | null
1015
+ ```
1016
+
1017
+ - `text` — The document's full text.
1018
+ - `label` — What to call the document in the outline's header (its path).
1019
+
1020
+ **Returns:** The outline — or null when `text` is not an HTML document.
1021
+
824
1022
  #### `truncate(s, maxLength)`
825
1023
 
826
1024
  Truncate a string to a max length with a truncation notice.
@@ -887,6 +1085,70 @@ function runningOutputSliceCommand(log: string, own?: string): string
887
1085
 
888
1086
  **Returns:** The shell command, safe to hand to the execution backend as-is.
889
1087
 
1088
+ #### `runningCauseCommand(log, own)`
1089
+
1090
+ The shell command whose stdout is enough of a running log to tell whether one cause is
1091
+ repeating: its tail, {@link REPEATING_CAUSE_TAIL_BYTES} of it.
1092
+
1093
+ {@link runningOutputSliceCommand} is the wrong read for this: it keeps only the FIRST 40
1094
+ failure lines plus 1 000 bytes, so on a run whose failures print a body each — which is what
1095
+ a `line` reporter does, and the only form that names the cause as an error message rather than
1096
+ a duration — three bodies do not fit, and the repeat is invisible. This read is not shown to
1097
+ anyone; it is parsed.
1098
+
1099
+ ```typescript
1100
+ function runningCauseCommand(log: string, own?: string): string
1101
+ ```
1102
+
1103
+ - `log` — The task log path.
1104
+ - `own` — A file the command redirects its own output to, if any.
1105
+
1106
+ **Returns:** The shell command, safe to hand to the execution backend as-is.
1107
+
1108
+ #### `repeatingFailure(log)`
1109
+
1110
+ The cause repeating among a running suite's failures, or null.
1111
+
1112
+ Playwright's `list` reporter prints one line per test as it finishes — its mark, its title
1113
+ 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.
1118
+
1119
+ Either way the point is the same: a suite whose failures repeat one cause spends its whole
1120
+ runtime proving that one cause, and the executor watching it can be told so at the third
1121
+ failure instead of at the last. X1 flash-m76 walked 15 tests in 4.0 minutes where 15.6 s
1122
+ each was the test timeout and one missing `navigate('/')` was the cause of all of them.
1123
+
1124
+ ```typescript
1125
+ function repeatingFailure(log: string): RepeatingFailure | null
1126
+ ```
1127
+
1128
+ - `log` — The log so far.
1129
+
1130
+ **Returns:** The repeating cause, or null when no signature has {@link REPEATING_FAILURE_MIN} failures behind it.
1131
+
1132
+ #### `repeatingFailureNote(found, o)`
1133
+
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.
1136
+
1137
+ 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.
1139
+
1140
+ ```typescript
1141
+ function repeatingFailureNote(
1142
+ found: RepeatingFailure,
1143
+ o: { taskId: string; log: string; stop: string },
1144
+ ): string
1145
+ ```
1146
+
1147
+ - `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).
1149
+
1150
+ **Returns:** The note.
1151
+
890
1152
  #### `whitespaceTolerantReplace(content, oldString, newString)`
891
1153
 
892
1154
  Attempt a whitespace-tolerant replacement when an exact `old_string` match
@@ -1053,6 +1315,38 @@ file under it "fits in ONE call", and a smaller bound here broke that promise (X
1053
1315
  const SMALL_FILE_WHOLE_CHARS: number
1054
1316
  ```
1055
1317
 
1318
+ #### `MAX_HTML_INLINE_CHARS`
1319
+
1320
+ How large an HTML document must be before a whole-file read is replaced by its
1321
+ outLINE — much smaller than {@link MAX_READ_RETURN_CHARS}, because the cost of
1322
+ an HTML read is almost never worth it.
1323
+
1324
+ An entry document is boilerplate the reader rarely needs in full: meta tags,
1325
+ stylesheet and preload links, a `<style>` block, and a handful of `<script>`
1326
+ tags. The parts an executor acts on — the body's structure, where a script is
1327
+ loaded — are all visible in an outline of one line per tag. X0 flash-m77 paid
1328
+ 63,735 chars for one such read: ~60 KB of it was the IDE's own preview bridges,
1329
+ and the read pushed the transcript past its context cap, so the e2e specs the
1330
+ executor had just read were trimmed to summaries and it read the whole suite
1331
+ again — twice (findings, +01:23 and +02:56). The scaffold now keeps those
1332
+ bridges in separate files, but a template's own head is still ~5-12 KB, and a
1333
+ static-site or SSR entry can be far more.
1334
+
1335
+ The outline is not a truncation: the note beside it names the exact
1336
+ offset/limit that returns the whole document in one call.
1337
+
1338
+ ```typescript
1339
+ const MAX_HTML_INLINE_CHARS: number
1340
+ ```
1341
+
1342
+ #### `MAX_HTML_OUTLINE_LINES`
1343
+
1344
+ Most lines an HTML outline prints before it stops and says so.
1345
+
1346
+ ```typescript
1347
+ const MAX_HTML_OUTLINE_LINES: 200
1348
+ ```
1349
+
1056
1350
  #### `MAX_SEARCH_RESULTS`
1057
1351
 
1058
1352
  Max search results.
@@ -1118,6 +1412,30 @@ numbered block header, and a runner's own error lines. Matched with `grep -E`.
1118
1412
  const RUNNING_FAILURE_LINE_RE: '^[[:space:]]*(✘|✗|×|✖|✕|[0-9]+\\)|[0-9]+ (passed|failed|flaky|skipped|did not run)|FAIL|FAILED|ERROR|Error:|error:|Traceback|AssertionError)'
1119
1413
  ```
1120
1414
 
1415
+ #### `REPEATING_CAUSE_TAIL_BYTES`
1416
+
1417
+ Bytes of a running log a repeating-cause look reads back.
1418
+
1419
+ ```typescript
1420
+ const REPEATING_CAUSE_TAIL_BYTES: 40000
1421
+ ```
1422
+
1423
+ #### `REPEATING_FAILURE_MIN`
1424
+
1425
+ Failures that must share one signature before a running suite's repeating cause is named.
1426
+
1427
+ ```typescript
1428
+ const REPEATING_FAILURE_MIN: 3
1429
+ ```
1430
+
1431
+ #### `REPEATING_FAILURE_LISTED`
1432
+
1433
+ How many of a repeating cause's failures the note lists before it says "and N more".
1434
+
1435
+ ```typescript
1436
+ const REPEATING_FAILURE_LISTED: 5
1437
+ ```
1438
+
1121
1439
  #### `PARSE_CHECKABLE`
1122
1440
 
1123
1441
  Files a parse check can vouch for cheaply right after a write.
package/dist/index.d.ts CHANGED
@@ -117,6 +117,7 @@ export * from './backends/local.js';
117
117
  export * from './backends/sandbox.js';
118
118
  export * from './browser-guard.js';
119
119
  export * from './input-normalizer.js';
120
+ export * from './no-test-run.js';
120
121
  export * from './schemas.js';
121
122
  export * from './system-prompt.js';
122
123
  export * from './tools.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkHG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkHG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -117,6 +117,7 @@ export * from './backends/local.js';
117
117
  export * from './backends/sandbox.js';
118
118
  export * from './browser-guard.js';
119
119
  export * from './input-normalizer.js';
120
+ export * from './no-test-run.js';
120
121
  export * from './schemas.js';
121
122
  export * from './system-prompt.js';
122
123
  export * from './tools.js';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The runner invocations that print information and run no test — the one classifier every
3
+ * site asks before it treats a command as a test run.
4
+ *
5
+ * A Playwright CLI has subcommands and flags that never start a test: `playwright test --list`
6
+ * prints the tests it would run and exits, `show-report`/`show-trace` open a saved report,
7
+ * `codegen` records one, `install` fetches browsers, `merge-reports` joins blobs, and
8
+ * `--help`/`--version` print and exit. Vitest lists with `vitest list` / `--list` and hurl
9
+ * prints with `--help`/`--version`, the same way.
10
+ *
11
+ * Every check in the platform read such a command as a whole-suite run, because each one
12
+ * matched the runner's NAME. X1 xr-molecule-flash-m79's first Playwright command was a config
13
+ * check — a config written to `/tmp`, `cp`'d beside the suite and used with `--list` — and it
14
+ * came back HELD, `status: not_run`, with a ~4 KB `suiteNote` telling it to send the same
15
+ * command with `--max-failures=15` ("uncapped it walks every test … 8–13 minutes"), beside an
16
+ * environment block saying the run "ran on their defaults" and an import note about a path the
17
+ * `cp` had already fixed. Nothing ran. The model gave up on the approach, wrote a second
18
+ * config, and its next command was held again (flash-m74's "same command three times" shape):
19
+ * two holds, zero tests, and the config check it asked for never answered.
20
+ *
21
+ * So the classifiers live here, in one place, and callers ask them instead of their own
22
+ * regexes: {@link commandRunsNoTests} answers for a whole command, and the runner-specific
23
+ * sites (molecule-dev's suite checks, `streamingBackgroundCommand`, `isSuiteRunCommand`) route
24
+ * through it. A command that runs no test is never held, never noted, and never rewritten
25
+ * (`--retries`, `--reporter`) as if it were a run.
26
+ *
27
+ * @module
28
+ */
29
+ /**
30
+ * Whether a shell command invokes a test runner and EVERY such invocation only prints — so the
31
+ * command runs no test at all.
32
+ *
33
+ * A command that invokes no runner is NOT one of these (false): "the command runs no test" is
34
+ * then true for `ls`, which is not the question any caller is asking. The question is whether
35
+ * the runner invocation each caller read as a run is one.
36
+ *
37
+ * A command that also runs tests is false however it is spelled: `npx playwright install &&
38
+ * npx playwright test` installs and runs, so a caller that sees `playwright test` in it is
39
+ * reading a real run.
40
+ *
41
+ * @param command - The shell command as the executor sent it.
42
+ * @returns True when the command invokes a runner only to print.
43
+ */
44
+ export declare function commandRunsNoTests(command: string): boolean;
45
+ //# sourceMappingURL=no-test-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-test-run.d.ts","sourceRoot":"","sources":["../src/no-test-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA6EH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAY3D"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The runner invocations that print information and run no test — the one classifier every
3
+ * site asks before it treats a command as a test run.
4
+ *
5
+ * A Playwright CLI has subcommands and flags that never start a test: `playwright test --list`
6
+ * prints the tests it would run and exits, `show-report`/`show-trace` open a saved report,
7
+ * `codegen` records one, `install` fetches browsers, `merge-reports` joins blobs, and
8
+ * `--help`/`--version` print and exit. Vitest lists with `vitest list` / `--list` and hurl
9
+ * prints with `--help`/`--version`, the same way.
10
+ *
11
+ * Every check in the platform read such a command as a whole-suite run, because each one
12
+ * matched the runner's NAME. X1 xr-molecule-flash-m79's first Playwright command was a config
13
+ * check — a config written to `/tmp`, `cp`'d beside the suite and used with `--list` — and it
14
+ * came back HELD, `status: not_run`, with a ~4 KB `suiteNote` telling it to send the same
15
+ * command with `--max-failures=15` ("uncapped it walks every test … 8–13 minutes"), beside an
16
+ * environment block saying the run "ran on their defaults" and an import note about a path the
17
+ * `cp` had already fixed. Nothing ran. The model gave up on the approach, wrote a second
18
+ * config, and its next command was held again (flash-m74's "same command three times" shape):
19
+ * two holds, zero tests, and the config check it asked for never answered.
20
+ *
21
+ * So the classifiers live here, in one place, and callers ask them instead of their own
22
+ * regexes: {@link commandRunsNoTests} answers for a whole command, and the runner-specific
23
+ * sites (molecule-dev's suite checks, `streamingBackgroundCommand`, `isSuiteRunCommand`) route
24
+ * through it. A command that runs no test is never held, never noted, and never rewritten
25
+ * (`--retries`, `--reporter`) as if it were a run.
26
+ *
27
+ * @module
28
+ */
29
+ /**
30
+ * A runner's name as a statement invokes it, from the start of its own arguments. The trailing
31
+ * guard is what separates the runner from a FILE named after it — `-c playwright.local.config.ts`
32
+ * names a config, not a second invocation, and reading it as one would give that "invocation" no
33
+ * `--list` of its own and un-do the answer for the real one.
34
+ */
35
+ const INVOCATIONS = [
36
+ ['playwright', /(?:^|[\s;&|(])(?:npx\s+(?:--no-install\s+)?)?(?:\S*\/)?playwright(?![\w.-])/g],
37
+ ['vitest', /(?:^|[\s;&|(])(?:npx\s+(?:--no-install\s+)?)?(?:\S*\/)?vitest(?![\w.-])/g],
38
+ ['hurl', /(?:^|[\s;&|(])(?:\S*\/)?hurl(?![\w.-])/g],
39
+ ];
40
+ /** Playwright's subcommands that start no test. */
41
+ const PLAYWRIGHT_INFO_SUBCOMMANDS = [
42
+ 'show-report',
43
+ 'show-trace',
44
+ 'codegen',
45
+ 'install',
46
+ 'merge-reports',
47
+ ];
48
+ /** Vitest's subcommand that prints what a run would collect. */
49
+ const VITEST_LIST_SUBCOMMAND = 'list';
50
+ /**
51
+ * The flags that put a runner in an information mode: it prints and exits. `-h` is `--help`
52
+ * for all three (hurl's manual documents `-h, --help`; Playwright's and Vitest's CLIs follow
53
+ * the same convention), and the value-taking flags beside them are why each is matched as a
54
+ * whole token — hurl's `-H` is a header, not help.
55
+ */
56
+ const NO_RUN_FLAGS = {
57
+ playwright: [
58
+ /(?:^|\s)--list(?=\s|$)/,
59
+ /(?:^|\s)--dry-run(?=\s|$)/,
60
+ /(?:^|\s)--help(?=\s|$)/,
61
+ /(?:^|\s)-h(?=\s|$)/,
62
+ /(?:^|\s)--version(?=\s|$)/,
63
+ ],
64
+ vitest: [
65
+ /(?:^|\s)--list(?=\s|$)/,
66
+ /(?:^|\s)--help(?=\s|$)/,
67
+ /(?:^|\s)-h(?=\s|$)/,
68
+ /(?:^|\s)--version(?=\s|$)/,
69
+ ],
70
+ hurl: [/(?:^|\s)--help(?=\s|$)/, /(?:^|\s)-h(?=\s|$)/, /(?:^|\s)--version(?=\s|$)/],
71
+ };
72
+ /** Statements of a shell command, the way the suite checks split them. */
73
+ const STATEMENT_SEP = /\s*(?:&&|\|\||;|\||\n)\s*/;
74
+ /**
75
+ * A heredoc body is data a command writes, not what it runs: a `playwright test --list` inside
76
+ * the config a command writes is not a run of this command either way. The pattern consumes the
77
+ * writer's own line prefix, the same shape `outputRedirectTarget` reads.
78
+ */
79
+ const HEREDOC = /[^\n;&|]*<<-?\s*(['"]?)(\w+)\1[\s\S]*?\n\s*\2\b/g;
80
+ /**
81
+ * Whether this runner's invocation — the text from just after its name to the end of its
82
+ * statement — only prints.
83
+ *
84
+ * @param runner - The runner the statement invokes.
85
+ * @param after - Everything after the runner's name in that statement.
86
+ * @returns True when the invocation starts no test.
87
+ */
88
+ function informational(runner, after) {
89
+ const first = after.trimStart().split(/\s+/)[0] ?? '';
90
+ if (runner === 'playwright' && PLAYWRIGHT_INFO_SUBCOMMANDS.includes(first))
91
+ return true;
92
+ if (runner === 'vitest' && first === VITEST_LIST_SUBCOMMAND)
93
+ return true;
94
+ return NO_RUN_FLAGS[runner].some((re) => re.test(after));
95
+ }
96
+ /**
97
+ * Whether a shell command invokes a test runner and EVERY such invocation only prints — so the
98
+ * command runs no test at all.
99
+ *
100
+ * A command that invokes no runner is NOT one of these (false): "the command runs no test" is
101
+ * then true for `ls`, which is not the question any caller is asking. The question is whether
102
+ * the runner invocation each caller read as a run is one.
103
+ *
104
+ * A command that also runs tests is false however it is spelled: `npx playwright install &&
105
+ * npx playwright test` installs and runs, so a caller that sees `playwright test` in it is
106
+ * reading a real run.
107
+ *
108
+ * @param command - The shell command as the executor sent it.
109
+ * @returns True when the command invokes a runner only to print.
110
+ */
111
+ export function commandRunsNoTests(command) {
112
+ if (typeof command !== 'string' || !command.trim())
113
+ return false;
114
+ let invocations = 0;
115
+ for (const statement of command.replace(HEREDOC, ' ').split(STATEMENT_SEP)) {
116
+ for (const [runner, re] of INVOCATIONS) {
117
+ for (const m of statement.matchAll(re)) {
118
+ invocations += 1;
119
+ if (!informational(runner, statement.slice((m.index ?? 0) + m[0].length)))
120
+ return false;
121
+ }
122
+ }
123
+ }
124
+ return invocations > 0;
125
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAgU1C,CAAA"}
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAmU1C,CAAA"}
package/dist/schemas.js CHANGED
@@ -143,13 +143,13 @@ export const TOOL_SCHEMAS = {
143
143
  },
144
144
  find_files: {
145
145
  name: 'find_files',
146
- description: "Find files by name or glob pattern recursively. Excludes node_modules and .git. Use for discovering files by name (e.g. '*.tsx', 'Dashboard*', 'index.*').",
146
+ description: "Find files by name or glob pattern recursively. Excludes node_modules and .git. Use for discovering files by name (e.g. '*.tsx', 'Dashboard*', 'index.*'). A pattern containing '/' matches the path instead: 'e2e/*.spec.ts', 'src/**/App.tsx', '**/SELECTORS.md'.",
147
147
  parameters: {
148
148
  type: 'object',
149
149
  properties: {
150
150
  pattern: {
151
151
  type: 'string',
152
- description: "File name or glob pattern (e.g. '*.tsx', 'Dashboard*').",
152
+ description: "File name or glob pattern (e.g. '*.tsx', 'Dashboard*'). A pattern containing '/' is matched against the file's path, so 'src/**/App.tsx' and 'e2e/*.spec.ts' work.",
153
153
  },
154
154
  path: {
155
155
  type: 'string',
@@ -247,7 +247,9 @@ export const TOOL_SCHEMAS = {
247
247
  'code. Blocks until it finishes or the time budget runs out, then returns — one call, ' +
248
248
  'no polling. Use it the moment you need that result to continue; never `sleep` and ' +
249
249
  'read the log by hand. If it is still running when the budget ends, the result says so ' +
250
- 'and you can call it again.',
250
+ 'and you can call it again. A suite whose failures keep repeating one cause comes back ' +
251
+ 'EARLY with that cause named, the tests it hit and the command that stops the run — act ' +
252
+ 'on it instead of waiting for the rest.',
251
253
  parameters: {
252
254
  type: 'object',
253
255
  properties: {
@@ -15,7 +15,7 @@ const TOOL_DESCRIPTIONS = {
15
15
  write_file: 'Create or overwrite files (full content)',
16
16
  edit_file: 'Make targeted search-and-replace edits (preferred for small changes)',
17
17
  search_files: 'Search for text patterns across files (grep)',
18
- find_files: 'Find files by name/glob pattern recursively',
18
+ find_files: 'Find files by name/glob pattern recursively (a pattern with "/" matches the path)',
19
19
  create_directory: 'Create directories (with parents)',
20
20
  rename_file: 'Rename or move files and directories',
21
21
  delete_file: 'Remove files',
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AA0H9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6CnE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAutDxF"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAkL9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAqDnE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA0zDxF"}