supercov 4.4.0 → 4.5.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
 
@@ -516,12 +578,37 @@ assessment, each commit cost under a cent.
516
578
 
517
579
  ```sh supercov
518
580
  npx supercov merge <shard-a> <shard-b> <shard-c>
581
+ npx supercov merge
519
582
  ```
520
583
 
521
584
  Merge creates a new run. The inputs must describe the same source,
522
585
  configuration, toolchain, schema, and coverage denominator. Supercov rejects an
523
586
  incompatible merge rather than publishing a misleading aggregate.
524
587
 
588
+ With no run named, `merge` takes the runs recorded on the source as it is now:
589
+ the newest of each command, leaving out runs that are themselves a merge. It
590
+ prints the runs it took. This is what a script uses after measuring its
591
+ suites, one after another or side by side, without knowing what each run was
592
+ called. Asked again with nothing new, it names the merge already made.
593
+
594
+ A test that is in two of the runs is one test in the merge. If it failed in
595
+ either, it is failed, which is why a command run again replaces its earlier
596
+ run and is not merged with it.
597
+
598
+ The runs may differ in their tests when the later ones have only gained test
599
+ files. Measuring a new test alone and adding it to the runs before it is
600
+ then the whole suite's figure, without the whole suite's time:
601
+
602
+ ```sh supercov-example
603
+ npx supercov -- npm test # the suite, once
604
+ npx supercov -- npx vitest run test/new.test.ts # a test added since, alone
605
+ npx supercov merge
606
+ ```
607
+
608
+ A run is left out, and named, when a file of its own tests has changed since
609
+ it ran, or a file tests load that is not itself a test's: what it recorded is
610
+ then of other tests. Measure those again to count them.
611
+
525
612
  `assertions assess` assesses a merged run across every suite merged into it,
526
613
  reusing the answers saved when its parts were assessed. `tests affected` is
527
614
  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.5.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/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.5.0",
124
+ "@supercov/cli-darwin-x64": "4.5.0",
125
+ "@supercov/cli-linux-arm64-gnu": "4.5.0",
126
+ "@supercov/cli-linux-arm64-musl": "4.5.0",
127
+ "@supercov/cli-linux-x64-gnu": "4.5.0",
128
+ "@supercov/cli-linux-x64-musl": "4.5.0",
129
+ "@supercov/cli-win32-arm64": "4.5.0",
130
+ "@supercov/cli-win32-x64": "4.5.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) => {