supercov 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -344,6 +344,29 @@ npx supercov report
344
344
 
345
345
  Collections accept `--limit` and `--offset` and print a copyable next-page command. Machine-readable output is available with `--json` when an integration needs it.
346
346
 
347
+ ## Comment on pull requests
348
+
349
+ In GitHub Actions, one step runs your tests and tells the pull request which
350
+ changed lines no test ran:
351
+
352
+ ```yaml
353
+ permissions:
354
+ contents: read
355
+ pull-requests: write
356
+
357
+ steps:
358
+ - uses: actions/checkout@v6
359
+ - run: npm ci
360
+ - uses: supercorp-ai/supercov@v4
361
+ with:
362
+ command: npm test
363
+ ```
364
+
365
+ It works with every supported language, keeps one comment up to date, marks the
366
+ lines in the diff, and can fail the job below a floor (`min-changed-lines: 90`).
367
+ No account, no upload token and no server: the report never leaves the runner.
368
+ See [Comment on a pull request](docs/cli.md#comment-on-a-pull-request).
369
+
347
370
  ## Local, private, and zero-edit
348
371
 
349
372
  Everything Supercov writes lives under one hidden `.supercov/` directory: run evidence in `.supercov/runs/<run-id>/` and the isolated build cache in `.supercov/workspaces/`. It ignores itself in Git, so there is nothing to add to your `.gitignore`.
package/docs/cli.md CHANGED
@@ -279,6 +279,62 @@ code nothing ran.
279
279
  adjacent misses into one range and capping the total (`--max-annotations`). It
280
280
  needs no token and posts no comment.
281
281
 
282
+ ## Comment on a pull request
283
+
284
+ In GitHub Actions, one step runs the tests, says in a comment which changed
285
+ lines no test ran, and marks them in the diff:
286
+
287
+ ```yaml
288
+ on: pull_request
289
+
290
+ permissions:
291
+ contents: read
292
+ pull-requests: write
293
+
294
+ jobs:
295
+ test:
296
+ runs-on: ubuntu-latest
297
+ steps:
298
+ - uses: actions/checkout@v6
299
+ - run: npm ci
300
+ - uses: supercorp-ai/supercov@v4
301
+ with:
302
+ command: npm test
303
+ ```
304
+
305
+ `command` is the test command in any supported language: `pytest`,
306
+ `cargo test`, `go test ./...`, `bundle exec rspec`, `mvn test`. The step takes
307
+ the place of the one that ran the tests, and fails when they fail. It needs no
308
+ account and no server: the comment is written with the workflow's own token,
309
+ and everything else stays on the runner.
310
+
311
+ The comment gives the coverage of the changed lines, the run's line, branch and
312
+ MC/DC coverage, and each changed file with the lines no test ran, linked to the
313
+ commit. A later push rewrites the same comment. The job summary holds the same
314
+ report, which is what a pull request from a fork gets: its token cannot write a
315
+ comment.
316
+
317
+ | Input | Default | |
318
+ | --- | --- | --- |
319
+ | `command` | required | The test command. One command; put several in a script. |
320
+ | `working-directory` | `.` | Where to run it. |
321
+ | `min-changed-lines` | none | Fail below this percentage of changed lines covered. |
322
+ | `min-lines`, `min-branches`, `min-mcdc` | none | Fail below this percentage for the whole run. |
323
+ | `title` | `Coverage` | The comment's heading. Give each job its own when several comment on one pull request. |
324
+ | `base` | the pull request's base | What the change is compared with. |
325
+ | `comment`, `annotate` | `true` | Turn the comment or the marks in the diff off. |
326
+ | `version` | the action's release | The Supercov release to run. |
327
+ | `binary` | none | A `supercov` already on the runner, used instead of downloading one. |
328
+
329
+ The default checkout fetches one commit; the step fetches the history between
330
+ it and the base itself. It puts `supercov` on the `PATH` and sets the outputs
331
+ `run-id`, `lines`, `branches` and `changed-lines`, so later steps can read the
332
+ same run:
333
+
334
+ ```yaml
335
+ - run: supercov runs report --format html --output coverage/report
336
+ ```
337
+
282
338
  ## Export for other tools
283
339
 
284
340
  ```sh supercov-example
@@ -300,7 +356,13 @@ one to fill a field.
300
356
 
301
357
  MC/DC conditions are not exported as ordinary branches. A consumer would then
302
358
  show condition obligations as branch coverage, which is a different
303
- measurement; that evidence stays in the JSON view and the HTML report. A report
359
+ measurement. The LCOV export writes them as LCOV 2's own records, `MCDC:` for
360
+ each condition with `MCF:` and `MCH:` for a file, so `lcov --mcdc-coverage` and
361
+ `genhtml --mcdc-coverage` show a per-file MC/DC figure from the export alone.
362
+ LCOV counts a condition twice, once for each way it can turn the outcome, and
363
+ Supercov's covered condition is both: hit over found is the run's MC/DC. A
364
+ reader of the older format passes the records over. Cobertura has no such
365
+ record; there the evidence stays in the JSON view and the HTML report. A report
304
366
  from a failed or stale run is still written, with a warning on stderr — only
305
367
  `check` refuses to pass on one.
306
368
 
@@ -449,8 +511,12 @@ pass in the run is listed regardless.
449
511
  per line, for a runner's filter. A Vitest, Playwright or node:test test inside
450
512
  `describe` blocks is printed as the blocks and the test joined by spaces
451
513
  (`small charges`), which is what `vitest -t`, `--grep` and
452
- `--test-name-pattern` match. A dependency, lockfile, configuration or
453
- toolchain change affects every test and is reported as such. A source file
514
+ `--test-name-pattern` match. A helper beside the tests that changed affects
515
+ the tests whose files import it (a helper that only gained functions affects
516
+ none), and every test where Supercov cannot read the imports. A dependency,
517
+ lockfile, configuration or toolchain change affects every test and is reported
518
+ as such; a test runner's settings affect only the runs that used that runner.
519
+ A source file
454
520
  added since the run is outside every test's record; the working-tree check
455
521
  says so, and the suite should run in full.
456
522
 
@@ -516,12 +582,71 @@ assessment, each commit cost under a cent.
516
582
 
517
583
  ```sh supercov
518
584
  npx supercov merge <shard-a> <shard-b> <shard-c>
585
+ npx supercov merge
519
586
  ```
520
587
 
521
588
  Merge creates a new run. The inputs must describe the same source,
522
589
  configuration, toolchain, schema, and coverage denominator. Supercov rejects an
523
590
  incompatible merge rather than publishing a misleading aggregate.
524
591
 
592
+ With no run named, `merge` takes the runs recorded on the source as it is now:
593
+ the newest of each command, leaving out runs that are themselves a merge. It
594
+ prints the runs it took. This is what a script uses after measuring its
595
+ suites, one after another or side by side, without knowing what each run was
596
+ called. Asked again with nothing new, it names the merge already made.
597
+
598
+ A test that is in two of the runs is one test in the merge. If it failed in
599
+ either, it is failed, which is why a command run again replaces its earlier
600
+ run and is not merged with it.
601
+
602
+ The runs may differ in their tests, the helpers beside them, and the test
603
+ runners' settings, as long as nothing that changed reaches what an earlier
604
+ run's tests did. Measuring a new test alone and adding it to the runs before it
605
+ is then the whole suite's figure, without the whole suite's time:
606
+
607
+ ```sh supercov-example
608
+ npx supercov -- npm test # the suite, once
609
+ npx supercov -- <the new test alone> # a test added since
610
+ npx supercov merge
611
+ ```
612
+
613
+ Each file that differs from what an earlier run was recorded with is judged by
614
+ what that run's tests did with it:
615
+
616
+ - Another run's test file: it was not in this run. A file of the run's own
617
+ tests: what it recorded is of other tests, and the run is left out.
618
+ - A helper, fixture or support file: left out of a run only when that run's
619
+ test files import it, directly or through other files. For JavaScript and
620
+ TypeScript tests Supercov reads the imports; a computed import path, or tests
621
+ in another language, count every helper as loaded. A helper that only gained
622
+ functions, which code written before cannot have called, leaves every run
623
+ in.
624
+ - A test runner's settings: `playwright.config.*`, `vitest.config.*`,
625
+ `jest.config.*` and `ava.config.*` belong to their runner, and a run that
626
+ did not use that runner keeps. Of Playwright's settings, only those that
627
+ decide what a test does count: `use`, `testDir`, `webServer`,
628
+ `globalSetup`, `globalTeardown`, and a project's `dependencies` and
629
+ `teardown`. Timeouts, retries, workers, reporters, which files and names to
630
+ run, and projects added since schedule tests and change nothing an earlier
631
+ test reached. This needs the newer run to have measured Playwright.
632
+ - Any other configuration, such as `tsconfig.json`, `vite.config.*` or a
633
+ Babel file, may set how any test runs, and leaves the run out.
634
+
635
+ A run left out is named with the files and the functions that changed. The
636
+ merged run lists what differed and why it does not reach the earlier runs:
637
+ `merged across:` in `runs <id>`, and `mergedAcross` in JSON.
638
+
639
+ When you know a change Supercov counts does not alter what the earlier tests
640
+ ran, say so:
641
+
642
+ ```sh supercov-example
643
+ npx supercov merge --allow-changed
644
+ ```
645
+
646
+ The product code, the dependencies, and what is measured must still be the
647
+ same, and a run whose own test files changed is still left out. What was
648
+ accepted is listed in `merged across:` as accepted.
649
+
525
650
  `assertions assess` assesses a merged run across every suite merged into it,
526
651
  reusing the answers saved when its parts were assessed. `tests affected` is
527
652
  asked of the runs that were merged, which hold the record of what each test
@@ -114,12 +114,56 @@ partial output.
114
114
 
115
115
  ## Interrupted and overlapping commands
116
116
 
117
- One project can run one coverage or cleanup transaction at a time. A second
118
- operation fails clearly instead of racing the first.
117
+ Runs of one project can be started at the same moment. Each takes a copy of
118
+ the project to itself: a run alone always has the first,
119
+ `.supercov/workspaces/workspace/<name>`, and one started while that is in use
120
+ has `workspace-2`, the next `workspace-3`. None waits and none is refused:
121
+
122
+ ```sh
123
+ npx supercov -- npm run test:unit &
124
+ npx supercov -- npm run test:integration &
125
+ npx supercov -- npm run test:e2e &
126
+ wait
127
+ npx supercov merge
128
+ ```
129
+
130
+ `merge` with no run named adds up the runs recorded on the source as it is
131
+ now, so a script does not have to know what each was called.
132
+
133
+ A copy past the first is kept for a day after its last run, so that runs that
134
+ overlap again start from what they left, and is removed after that. Rust runs
135
+ build into one directory, as two `cargo test` do, and Cargo makes the second
136
+ wait while the first compiles.
137
+
138
+ Suites can also be started from one command. They are then one run with
139
+ nothing to merge, and each test keeps the kind its own process was given:
140
+
141
+ ```sh
142
+ npx supercov -- sh -c '
143
+ SUPERCOV_TEST_KIND=integration npm run test:integration & integration=$!
144
+ SUPERCOV_TEST_KIND=e2e npm run test:e2e & e2e=$!
145
+ wait $integration; first=$?
146
+ wait $e2e && exit $first
147
+ '
148
+ ```
149
+
150
+ `wait` with no argument ends with 0 whatever the suites did, which would turn a
151
+ failing suite green; waiting for each by its id keeps the status.
152
+
153
+ `runs clean` wants the store to itself. While a run is active it ends with
154
+ status 2 and names the run; a run started while it works is told so, and no
155
+ test of it runs.
119
156
 
120
157
  After interruption or host restart, the next command recovers unpublished
121
158
  staging state. Completed runs remain immutable.
122
159
 
160
+ Ctrl-C ends the test command and the run: nothing is recorded, and the exit
161
+ status is 130. A test command that reads the keyboard (a debugger at a
162
+ breakpoint, a watch mode, a question before an install) is given the terminal
163
+ when it first reads, on macOS and Linux. From then on Ctrl-C and Ctrl-Z go to
164
+ it, as they do without Supercov, and a command that Ctrl-C ended is still a
165
+ run that was interrupted.
166
+
123
167
  ## Clean up local data
124
168
 
125
169
  Preview cleanup before removing anything:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "4.4.0",
3
+ "version": "4.6.0",
4
4
  "description": "Coverage, security and code quality for coding agents",
5
5
  "keywords": [
6
6
  "coverage",
@@ -70,6 +70,7 @@
70
70
  "test:react": "cargo build -p supercov && node scripts/rust-vitest-projects-integration.mjs && npm --prefix examples/react-verification ci && npm --prefix examples/react-verification run demo && node scripts/react-hydration-compatibility.mjs",
71
71
  "test:release-tooling": "node --test tests/release/*.test.mjs",
72
72
  "test:runtime": "node --test tests/runtime/*.test.mjs",
73
+ "test:action": "cargo build -p supercov && node --test tests/action/*.test.mjs",
73
74
  "test:fixture": "cargo build -p supercov && node scripts/rust-fixture-matrix.mjs",
74
75
  "test:packed-npx": "cargo build --release -p supercov && node scripts/packed-npx-integration.mjs",
75
76
  "test:isolation": "cargo build -p supercov && node scripts/isolation-integration.mjs",
@@ -82,7 +83,7 @@
82
83
  "eval:skill": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag behavior trigger --allow-tools Bash Edit Write --max-cost-usd 15",
83
84
  "eval:skill-triggers": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag trigger --ablation none --max-cost-usd 5",
84
85
  "test:engine": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace && npm run test:runtime && npm run test:rust-assets && npm run test:engine-integrations",
85
- "test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs",
86
+ "test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/side-by-side.mjs node && node scripts/merge-across.mjs && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs",
86
87
  "test:platform": "cargo test --workspace && cargo build -p supercov && node scripts/rust-process-supervision.mjs && node scripts/workspace-crash-integration.mjs",
87
88
  "test:native-package": "cargo build --release -p supercov && node scripts/native-package-integration.mjs && node scripts/native-release-set-integration.mjs",
88
89
  "test:pypi-wheel": "node scripts/pypi-wheel-integration.mjs",
@@ -95,7 +96,7 @@
95
96
  "test:test262": "cargo build --release -p supercov && node scripts/test262-equivalence.mjs",
96
97
  "benchmark:check": "cargo build --release -p supercov && node scripts/rust-transform-benchmark.mjs",
97
98
  "benchmark:python": "cargo build -p supercov && node scripts/python-benchmark.mjs",
98
- "check": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && npm run test && npm run test:release-tooling && npm run test:runtime && npm run test:rust-assets && npm run test:agent-commands && npm run test:assertions && npm run test:sweep-target && npm run test:skip-guard && npm run test:windows-tls && node scripts/package-preflight.mjs && node scripts/verify-binstall-metadata.mjs --offline",
99
+ "check": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && npm run test && npm run test:release-tooling && npm run test:runtime && npm run test:action && npm run test:rust-assets && npm run test:agent-commands && npm run test:assertions && npm run test:sweep-target && npm run test:skip-guard && npm run test:windows-tls && node scripts/package-preflight.mjs && node scripts/verify-binstall-metadata.mjs --offline",
99
100
  "release:check": "cargo clean && node scripts/sweep-target.mjs && npm run check && npm run test:engine && npm run test:go && npm run test:jvm && npm run test:launcher && npm run test:fixture && npm run test:watchdog && npm run test:engine-contract && npm run test:agent && npm run test:child-attribution && npm run test:host-loader && npm run test:python && npm run test:ruby-coverage && npm run test:rust-public-cargo && npm run test:packed-npx && npm run test:clang-mcdc && npm run benchmark:check",
100
101
  "release:bump": "node scripts/bump-version.mjs",
101
102
  "sweep": "node scripts/sweep-target.mjs",
@@ -104,11 +105,11 @@
104
105
  "prepublishOnly": "npm run release:check",
105
106
  "test:child-attribution": "cargo build -p supercov && node scripts/rust-child-attribution-integration.mjs",
106
107
  "test:host-loader": "cargo build -p supercov && node scripts/rust-host-loader-integration.mjs",
107
- "test:python": "cargo build -p supercov && node scripts/python-integration.mjs && node scripts/failure-output.mjs python",
108
- "test:ruby-coverage": "cargo build -p supercov && node scripts/ruby-coverage-integration.mjs && node scripts/failure-output.mjs ruby",
109
- "test:go": "cargo test --no-fail-fast -p supercov-engine --test go_frontend --test go_run && cargo build -p supercov && node scripts/failure-output.mjs go",
110
- "test:jvm": "cargo test --no-fail-fast -p supercov-engine --test jvm_frontend --test jvm_run -- --include-ignored --test-threads=1",
111
- "test:rust-public-cargo": "cargo build -p supercov && node scripts/rust-public-cargo-integration.mjs && node scripts/failure-output.mjs rust",
108
+ "test:python": "cargo build -p supercov && node scripts/python-integration.mjs && node scripts/failure-output.mjs python && node scripts/side-by-side.mjs python",
109
+ "test:ruby-coverage": "cargo build -p supercov && node scripts/ruby-coverage-integration.mjs && node scripts/failure-output.mjs ruby && node scripts/side-by-side.mjs ruby",
110
+ "test:go": "cargo test --no-fail-fast -p supercov-engine --test go_frontend --test go_run && cargo build -p supercov && node scripts/failure-output.mjs go && node scripts/side-by-side.mjs go",
111
+ "test:jvm": "cargo test --no-fail-fast -p supercov-engine --test jvm_frontend --test jvm_run -- --include-ignored --test-threads=1 && cargo build -p supercov && node scripts/side-by-side.mjs jvm",
112
+ "test:rust-public-cargo": "cargo build -p supercov && node scripts/rust-public-cargo-integration.mjs && node scripts/failure-output.mjs rust && node scripts/side-by-side.mjs rust",
112
113
  "test:assertions": "cargo build -p supercov && node scripts/assertions-integration.mjs",
113
114
  "docs:sync": "node scripts/sync-docs.mjs",
114
115
  "docs:check": "node scripts/sync-docs.mjs --check",
@@ -119,14 +120,14 @@
119
120
  "test:windows-tls": "node scripts/windows-tls-test.mjs"
120
121
  },
121
122
  "optionalDependencies": {
122
- "@supercov/cli-darwin-arm64": "4.4.0",
123
- "@supercov/cli-darwin-x64": "4.4.0",
124
- "@supercov/cli-linux-arm64-gnu": "4.4.0",
125
- "@supercov/cli-linux-arm64-musl": "4.4.0",
126
- "@supercov/cli-linux-x64-gnu": "4.4.0",
127
- "@supercov/cli-linux-x64-musl": "4.4.0",
128
- "@supercov/cli-win32-arm64": "4.4.0",
129
- "@supercov/cli-win32-x64": "4.4.0"
123
+ "@supercov/cli-darwin-arm64": "4.6.0",
124
+ "@supercov/cli-darwin-x64": "4.6.0",
125
+ "@supercov/cli-linux-arm64-gnu": "4.6.0",
126
+ "@supercov/cli-linux-arm64-musl": "4.6.0",
127
+ "@supercov/cli-linux-x64-gnu": "4.6.0",
128
+ "@supercov/cli-linux-x64-musl": "4.6.0",
129
+ "@supercov/cli-win32-arm64": "4.6.0",
130
+ "@supercov/cli-win32-x64": "4.6.0"
130
131
  },
131
132
  "peerDependencies": {
132
133
  "@playwright/test": ">=1.55.0",
@@ -280,9 +280,10 @@ process.__SUPERCOV_FAILURE_OUTPUT__ ??= function supercovFailureOutput(runner) {
280
280
  return [copy];
281
281
  }
282
282
  })();
283
- // The copy lies in the project at .supercov/workspaces/workspace/<name>.
283
+ // The copy lies in the project at .supercov/workspaces/workspace/<name>,
284
+ // or at workspace-2 where it is one of several runs at once.
284
285
  const projects = copies
285
- .filter((copy) => /[\\/]\.supercov[\\/]workspaces[\\/]workspace[\\/][^\\/]+[\\/]$/.test(copy))
286
+ .filter((copy) => /[\\/]\.supercov[\\/]workspaces[\\/]workspace(?:-\d+)?[\\/][^\\/]+[\\/]$/.test(copy))
286
287
  .map((copy) => resolve(copy, "../../../..") + sep);
287
288
  // The path inside the copy of a rewritten file that lies under one of `roots`.
288
289
  const rewrittenUnder = (roots, file) => {