supercov 0.0.16 → 0.0.18
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 +21 -7
- package/bin/supercov.js +10 -1
- package/docs/agent-loop.md +157 -0
- package/docs/cli.md +145 -0
- package/docs/coverage-model.md +134 -0
- package/docs/evidence.md +122 -0
- package/docs/getting-started.md +145 -0
- package/docs/supported-suites.md +119 -0
- package/docs/verification.md +102 -0
- package/docs/workspace-isolation.md +11 -9
- package/package.json +11 -7
- package/runtime/javascript/launchSupervisor.js +30 -0
- package/runtime/javascript/playwright.js +2 -2
- package/runtime/javascript/runtime.js +51 -25
- package/runtime/javascript/vitestReporter.js +23 -3
package/README.md
CHANGED
|
@@ -7,6 +7,17 @@ suites.
|
|
|
7
7
|
npx supercov -- npm test
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
For coding agents, put the same rule in the repository instructions they read
|
|
11
|
+
before running commands (for example `AGENTS.md` or `CLAUDE.md`):
|
|
12
|
+
|
|
13
|
+
```md
|
|
14
|
+
Measure coverage with `npx supercov -- npm test`. Prefix the project's full
|
|
15
|
+
test command; do not substitute a single unit, integration, or E2E script.
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`npx supercov --help` explains the full-command rule, and
|
|
19
|
+
`npx supercov docs agent-loop` prints the bounded query workflow as Markdown.
|
|
20
|
+
|
|
10
21
|
For local development before publication, a Supercov contributor can expose
|
|
11
22
|
the checkout globally. Consumer repositories still remain untouched:
|
|
12
23
|
|
|
@@ -141,8 +152,9 @@ Use `--filter passed` for verified coverage from successful attempts of
|
|
|
141
152
|
ultimately passing tests, or `--filter failed` to inspect only execution from
|
|
142
153
|
failed attempts (including failed retries of flaky tests). Evidence records
|
|
143
154
|
attempt status and classify each test as passed, failed, flaky, skipped, timed
|
|
144
|
-
out, interrupted,
|
|
145
|
-
same immutable archive rather than
|
|
155
|
+
out, interrupted, unknown, or selected but unstarted after fail-fast. Passed
|
|
156
|
+
and failed views are derived from the same immutable archive rather than
|
|
157
|
+
duplicated into presentation files.
|
|
146
158
|
|
|
147
159
|
The run ID is positional because all coverage queries operate on one immutable
|
|
148
160
|
run. `latest` is a convenience selector for interactive use. Every query
|
|
@@ -191,12 +203,13 @@ configuration, instrumenter, schema, and denominator fingerprints. It rewrites
|
|
|
191
203
|
the run scope inside every evidence record, namespaces shard paths, publishes a
|
|
192
204
|
new immutable run atomically, and leaves all input runs untouched. This is the
|
|
193
205
|
distributed/multi-host primitive; incompatible shards fail clearly instead of
|
|
194
|
-
producing a plausible but invalid aggregate.
|
|
206
|
+
producing a plausible but invalid aggregate. A rejection names each exact
|
|
207
|
+
domain that differs instead of presenting a generic list of possibilities.
|
|
195
208
|
|
|
196
209
|
For a JavaScript or TypeScript project, the CLI:
|
|
197
210
|
|
|
198
211
|
1. refreshes a stable isolated source namespace under
|
|
199
|
-
|
|
212
|
+
`supercov/workspace/<project>/`, links the existing
|
|
200
213
|
dependency tree, and creates generated runner configuration and build output
|
|
201
214
|
only there; file data uses copy-on-write reflinks where the filesystem
|
|
202
215
|
supports them, and falls back to copying where it does not; the stable path
|
|
@@ -229,9 +242,10 @@ For a JavaScript or TypeScript project, the CLI:
|
|
|
229
242
|
rebuilt afterward.
|
|
230
243
|
|
|
231
244
|
Only the Supercov-owned `.supercov/` run store and marker-protected
|
|
232
|
-
|
|
233
|
-
`supercov/` directory without Supercov's ownership marker
|
|
234
|
-
|
|
245
|
+
`supercov/workspace/` cache are modified in the user's checkout. A user-created
|
|
246
|
+
`supercov/` directory without Supercov's ownership marker remains ordinary
|
|
247
|
+
project source; Supercov selects a deterministic non-dotted fallback container
|
|
248
|
+
instead. A per-project lock rejects overlapping runs before either can build. Run state is durably written
|
|
235
249
|
through preparing/building/testing/publishing phases; SIGINT, SIGTERM,
|
|
236
250
|
and SIGHUP are forwarded to the entire child process group. If the process is
|
|
237
251
|
killed without a cleanup opportunity, the next invocation marks the dead PID's
|
package/bin/supercov.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { resolveNativeBinary } from "./native.js";
|
|
5
7
|
|
|
6
8
|
let rustBinary;
|
|
@@ -15,10 +17,17 @@ const child = spawn(
|
|
|
15
17
|
process.argv.slice(2),
|
|
16
18
|
{
|
|
17
19
|
stdio: "inherit",
|
|
18
|
-
env:
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
SUPERCOV_PACKAGE_ROOT: resolvePackageRoot(),
|
|
23
|
+
},
|
|
19
24
|
},
|
|
20
25
|
);
|
|
21
26
|
|
|
27
|
+
function resolvePackageRoot() {
|
|
28
|
+
return dirname(dirname(fileURLToPath(import.meta.url)));
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
23
32
|
process.once(signal, () => {
|
|
24
33
|
try {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Agent loop
|
|
2
|
+
|
|
3
|
+
Use Supercov in a repeatable agent loop: run the suite, query uncovered
|
|
4
|
+
obligations, add a focused test, rerun, and compare results. This page covers
|
|
5
|
+
the loop, the recommended prompt, and failure handling.
|
|
6
|
+
|
|
7
|
+
## The shape of the loop
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
run the suite -> ask what is open -> write one test -> re-run -> diff
|
|
11
|
+
^ |
|
|
12
|
+
+-------------------------------------------------------------------+
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Each pass should close a small number of related obligations and end with
|
|
16
|
+
evidence that it did. An agent that writes ten tests before re-running has no
|
|
17
|
+
way to attribute the outcome; an agent that re-runs after every trivial edit
|
|
18
|
+
spends its budget on test execution instead of thinking.
|
|
19
|
+
|
|
20
|
+
## One pass, in commands
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
# 1. Establish a baseline. Only needed once per session.
|
|
24
|
+
npx supercov -- npm test
|
|
25
|
+
|
|
26
|
+
# 2. Orient without loading a report into context.
|
|
27
|
+
npx supercov runs latest --json
|
|
28
|
+
npx supercov runs latest gaps --limit 5 --json
|
|
29
|
+
|
|
30
|
+
# 3. Understand one target.
|
|
31
|
+
npx supercov runs latest file app/checkout/session.ts --json
|
|
32
|
+
npx supercov runs latest decision app/checkout/session.ts:64 --json
|
|
33
|
+
|
|
34
|
+
# 4. Check what already exercises that line, to avoid writing a duplicate.
|
|
35
|
+
npx supercov runs latest line app/checkout/session.ts:64 --json
|
|
36
|
+
|
|
37
|
+
# 5. Write one test. Then re-run and prove the gain.
|
|
38
|
+
npx supercov -- npm test
|
|
39
|
+
npx supercov diff <previous-run-id> latest --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Step 4 is the one agents skip and should not. `line` answers "what already
|
|
43
|
+
executes this line", which usually reveals either an existing test to extend or
|
|
44
|
+
the exact reason nothing reaches it.
|
|
45
|
+
|
|
46
|
+
## A prompt you can paste
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
You are improving test coverage for this repository using Supercov.
|
|
50
|
+
|
|
51
|
+
Baseline:
|
|
52
|
+
npx supercov -- npm test
|
|
53
|
+
|
|
54
|
+
Then repeat this loop until coverage completeness stops improving, the target
|
|
55
|
+
is met, or you run out of time:
|
|
56
|
+
|
|
57
|
+
1. npx supercov runs latest gaps --limit 5 --json
|
|
58
|
+
2. Pick the file with the highest-value open obligations.
|
|
59
|
+
3. npx supercov runs latest file <path> --json
|
|
60
|
+
npx supercov runs latest decision <path>:<line> --json
|
|
61
|
+
npx supercov runs latest line <path>:<line> --json
|
|
62
|
+
4. Write ONE focused test that closes the specific obligations you just read.
|
|
63
|
+
The assertion must be meaningful on its own; never assert something trivial
|
|
64
|
+
just to execute a line.
|
|
65
|
+
5. npx supercov -- npm test
|
|
66
|
+
6. npx supercov diff <previous-run-id> latest --json
|
|
67
|
+
If the diff shows no gain, revert the test rather than keeping it.
|
|
68
|
+
|
|
69
|
+
Rules:
|
|
70
|
+
- Never modify application source to make coverage easier.
|
|
71
|
+
- Never weaken or delete an existing assertion.
|
|
72
|
+
- If a decision cannot be reached from any public entry point, say so and move
|
|
73
|
+
on instead of exporting internals to reach it.
|
|
74
|
+
- Report the run ids you compared and the obligations you closed.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The last rule matters more than it looks. An unattended agent that cannot reach
|
|
78
|
+
a branch will otherwise start reshaping the code so it can, which is exactly
|
|
79
|
+
the failure mode that gives coverage targets a bad name.
|
|
80
|
+
|
|
81
|
+
## Budgeting an overnight session
|
|
82
|
+
|
|
83
|
+
Test execution dominates the wall clock, so the number of passes is roughly the
|
|
84
|
+
time budget divided by suite duration. Two adjustments help:
|
|
85
|
+
|
|
86
|
+
- Narrow the command while iterating. `npx supercov -- npx vitest run
|
|
87
|
+
app/checkout` produces a valid run over a smaller denominator; use the full
|
|
88
|
+
suite for the baseline and the final verification.
|
|
89
|
+
- Let the build cache work. When the source, configuration and toolchain
|
|
90
|
+
fingerprint is unchanged, the instrumented build is reused and that phase
|
|
91
|
+
costs approximately nothing. Changing a dependency or a build config in the
|
|
92
|
+
middle of a session throws that away.
|
|
93
|
+
|
|
94
|
+
## Choosing what to attack
|
|
95
|
+
|
|
96
|
+
`gaps` is ordered to be useful, but not every open obligation deserves a test.
|
|
97
|
+
For a project that prefers end-to-end evidence, start with the existing
|
|
98
|
+
projection rather than inventing a new test taxonomy:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
npx supercov runs latest gaps --kind e2e
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Each file distinguishes obligations covered by another test kind from those
|
|
105
|
+
uncovered everywhere. The former are candidates for stronger E2E coverage;
|
|
106
|
+
the latter are gaps in the combined suite. When an error path cannot be reached
|
|
107
|
+
through E2E, first check whether the test double can express that failure before
|
|
108
|
+
falling back to a narrower unit test.
|
|
109
|
+
|
|
110
|
+
Two queries help an agent argue about value rather than count:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
# What does the suite prove today, minus redundancy?
|
|
114
|
+
npx supercov runs latest minimize --filter passed
|
|
115
|
+
|
|
116
|
+
# Reach a target with the smallest possible subset.
|
|
117
|
+
npx supercov runs latest minimize --filter passed --metric mcdc --target 80
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`minimize` is an exact branch-and-bound solver, not a greedy approximation: the
|
|
121
|
+
subset it returns is a proved minimum. It refuses to answer for a view that
|
|
122
|
+
contains background or unattributed evidence, because there is no honest way to
|
|
123
|
+
name an exact subset of tests when the runner never exposed test boundaries.
|
|
124
|
+
|
|
125
|
+
## Reading a run that is not the newest
|
|
126
|
+
|
|
127
|
+
`latest` is a convenience for interactive use. An agent that resumes work later,
|
|
128
|
+
or that compares across a session, should use the immutable run id:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
npx supercov runs --limit 10 --json
|
|
132
|
+
npx supercov runs run_0123456789abcdef gaps --json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Queries compare the stored fingerprint with the current workspace and mark a run
|
|
136
|
+
stale when the code has moved on. Treat a stale run as history, not as a
|
|
137
|
+
description of the working tree.
|
|
138
|
+
|
|
139
|
+
## What to do about honest gaps
|
|
140
|
+
|
|
141
|
+
Some obligations are open because the tooling says so, not because a test is
|
|
142
|
+
missing:
|
|
143
|
+
|
|
144
|
+
- **Background or unattributed evidence.** An unsupported runner, or work that
|
|
145
|
+
arrived without a carrier, is recorded under a first-class background scope.
|
|
146
|
+
It appears in the default all-attempt view and is excluded from per-test
|
|
147
|
+
passed-only coverage. Writing more tests will not move it; adding runner
|
|
148
|
+
support will.
|
|
149
|
+
- **Ambiguous source scope.** A candidate file that Supercov could not
|
|
150
|
+
confidently classify as first-party blocks a complete verdict. Inspect with
|
|
151
|
+
`coverage scope` and set `SUPERCOV_SOURCE_ROOTS` to declare the authoritative
|
|
152
|
+
scope.
|
|
153
|
+
- **Semantic-safety blockers.** A function whose source is coerced or reflected
|
|
154
|
+
on at runtime is left uninstrumented on purpose, and direct `eval` cannot have
|
|
155
|
+
a stable denominator at all. Both are recorded with their exact location.
|
|
156
|
+
|
|
157
|
+
An agent should surface these rather than grind against them.
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Every command is local. Nothing is uploaded, and no command runs your test
|
|
4
|
+
suite unless you ask it to.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
supercov --help
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Creating a run
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
supercov -- <test command>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Everything after `--` is executed as written. Supercov propagates coverage
|
|
17
|
+
through every Node child process the command launches, then publishes one
|
|
18
|
+
immutable run.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npx supercov -- npm test
|
|
22
|
+
npx supercov -- npx playwright test --project=chromium
|
|
23
|
+
npx supercov -- npx vitest run app/checkout
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A per-project lock rejects overlapping runs before either can build.
|
|
27
|
+
|
|
28
|
+
## Listing runs
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
supercov runs [--limit N] [--json]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Runs are listed newest first with their id, duration, phase timings and
|
|
35
|
+
integrity state. Use the id — not `latest` — when work spans a session.
|
|
36
|
+
|
|
37
|
+
## Coverage queries
|
|
38
|
+
|
|
39
|
+
All coverage queries take the form:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
supercov runs <run-id> [query] [options]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`<run-id>` is positional because every coverage view belongs to exactly one
|
|
46
|
+
immutable run. `latest` selects the newest local run.
|
|
47
|
+
|
|
48
|
+
| Query | Answers |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| no query | Overall completeness for the selected view |
|
|
51
|
+
| `kinds` | Completeness split by semantic level (`unit`, `e2e`, …) |
|
|
52
|
+
| `runners` | Completeness split by executing runner |
|
|
53
|
+
| `scope` | Which source files are included, excluded or ambiguous |
|
|
54
|
+
| `files` | Every included source file, ranked |
|
|
55
|
+
| `gaps` | Only files with unresolved obligations or measurement limits |
|
|
56
|
+
| `file <path>` | Every open obligation in one file |
|
|
57
|
+
| `decision <id \| path:line>` | Observed vectors and missing witnesses for one decision |
|
|
58
|
+
| `line <path:line>` | Line state, nested obligations, covering tests, and phases |
|
|
59
|
+
| `test <id \| name fragment>` | What one test contributes |
|
|
60
|
+
| `minimize` | The smallest test subset that preserves coverage |
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
| Option | Applies to | Meaning |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `--filter all \| passed \| failed` | most queries | Which attempts contribute. `all` is the default and matches conventional tools. |
|
|
67
|
+
| `--kind <kind>` | most queries | Restrict to a semantic level, for example `--kind e2e`. |
|
|
68
|
+
| `--runner <runner>` | summary | Restrict to one executing runner, for example `--runner playwright`. |
|
|
69
|
+
| `--metric all \| lines \| statements \| functions \| branches \| mcdc` | `minimize` | Which obligations the solver must preserve. |
|
|
70
|
+
| `--target 0..100` | `minimize` | Stop once the metric reaches this level. |
|
|
71
|
+
| `--limit N`, `--offset N` | collections | Pagination. Collections default to 20 items and print a copyable next-page command. |
|
|
72
|
+
| `--json` | every query | The stable machine format. |
|
|
73
|
+
|
|
74
|
+
### Examples
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
# Orient in a few lines.
|
|
78
|
+
npx supercov runs latest
|
|
79
|
+
npx supercov runs latest --filter passed
|
|
80
|
+
npx supercov runs latest kinds
|
|
81
|
+
|
|
82
|
+
# Find and open one target.
|
|
83
|
+
npx supercov runs latest gaps --kind e2e --limit 10
|
|
84
|
+
npx supercov runs latest file app/routes/example.ts
|
|
85
|
+
npx supercov runs latest decision app/routes/example.ts:42
|
|
86
|
+
npx supercov runs latest line app/routes/example.ts:57
|
|
87
|
+
|
|
88
|
+
# Understand contribution and redundancy.
|
|
89
|
+
npx supercov runs latest test "checkout retry"
|
|
90
|
+
npx supercov runs latest minimize --filter passed
|
|
91
|
+
npx supercov runs latest minimize --filter passed --metric mcdc --target 80
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
With `--kind`, gap and file queries additionally distinguish obligations covered
|
|
95
|
+
only by other test levels from obligations uncovered everywhere. On a combined
|
|
96
|
+
unit/E2E run, the default summary also prints the line count reached by other
|
|
97
|
+
test kinds but not by E2E, followed by the exact `gaps --kind e2e` query.
|
|
98
|
+
|
|
99
|
+
## Comparing runs
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
supercov diff <older-run> <newer-run> [--limit N] [--json]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Reports what the newer run covers that the older one did not, and what it lost.
|
|
106
|
+
Both runs remain untouched.
|
|
107
|
+
|
|
108
|
+
## Combining shards
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
supercov merge <run-id> <run-id> [...]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Accepts only runs with identical source, test, dependency, configuration,
|
|
115
|
+
instrumenter, schema and denominator fingerprints. It rewrites the run scope
|
|
116
|
+
inside every evidence record, namespaces shard paths, and publishes a new
|
|
117
|
+
immutable run atomically. Input runs are never modified. Incompatible shards
|
|
118
|
+
fail clearly rather than producing a plausible but invalid aggregate; the
|
|
119
|
+
error names each exact fingerprint domain that differs.
|
|
120
|
+
|
|
121
|
+
## Retention
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
supercov clean [--keep N] [--dry-run]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`clean` removes all history and the isolated build workspace by default.
|
|
128
|
+
`--keep N` preserves the N newest runs. It never runs automatically, takes the
|
|
129
|
+
same lock as a coverage run, refuses to race an active run, and deletes only
|
|
130
|
+
exactly marker-owned Supercov storage.
|
|
131
|
+
|
|
132
|
+
## Environment variables
|
|
133
|
+
|
|
134
|
+
| Variable | Effect |
|
|
135
|
+
| --- | --- |
|
|
136
|
+
| `SUPERCOV_SOURCE_ROOTS` | Declares the authoritative first-party source scope, resolving ambiguity that would otherwise block a complete verdict. |
|
|
137
|
+
| `SUPERCOV_TEST_KIND` | Declares the semantic level of the tests in this command, overriding every inference. |
|
|
138
|
+
|
|
139
|
+
## Exit codes
|
|
140
|
+
|
|
141
|
+
| Code | Meaning |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `0` | The run or query succeeded. |
|
|
144
|
+
| The test command's own code | A coverage run exits with the status of your command, so `supercov -- npm test` remains usable as a CI gate. |
|
|
145
|
+
| `2` | Supercov itself failed: an unknown command, an unreadable run, an incompatible merge, or a lock conflict. |
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Coverage model
|
|
2
|
+
|
|
3
|
+
Supercov derives coverage obligations from code structure. It reports which
|
|
4
|
+
obligations ran and the quality of the available evidence, including line,
|
|
5
|
+
branch, value-path, control-flow, and MC/DC coverage.
|
|
6
|
+
|
|
7
|
+
## Obligations
|
|
8
|
+
|
|
9
|
+
An obligation is one thing the code structure requires a test to demonstrate.
|
|
10
|
+
The denominator is fixed before the run from the source itself, so a percentage
|
|
11
|
+
cannot drift when tests are added or removed.
|
|
12
|
+
|
|
13
|
+
| Family | Obligation |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Lines | Each executable line executes |
|
|
16
|
+
| Statements | Each statement executes |
|
|
17
|
+
| Functions | Each function is entered |
|
|
18
|
+
| Branches | Each alternative is taken: `true`, `false`, switch fallthrough, and the implicit no-match arm |
|
|
19
|
+
| MC/DC | Each atomic condition is shown to independently determine its decision |
|
|
20
|
+
| Value selection | Optional-chain short-circuits, logical assignments, and parameter or destructuring defaults each resolve both ways |
|
|
21
|
+
| Control flow | `try` versus `catch`, and zero-iteration versus entered `for-in` / `for-of` |
|
|
22
|
+
|
|
23
|
+
The value-selection and control-flow families are the ones most tools omit.
|
|
24
|
+
`a?.b`, `x ??= y` and `function f(a = 1)` each hide a decision that never
|
|
25
|
+
appears as a branch in a conventional report, and a `for-of` that never runs
|
|
26
|
+
with an empty collection is an untested path even though every line inside it
|
|
27
|
+
is green.
|
|
28
|
+
|
|
29
|
+
## MC/DC in one example
|
|
30
|
+
|
|
31
|
+
Modified condition/decision coverage asks more than "was this condition true and
|
|
32
|
+
false at some point". It asks whether each condition was shown to *independently
|
|
33
|
+
change the outcome*, which requires a pair of executions differing in that one
|
|
34
|
+
condition and producing different decisions.
|
|
35
|
+
|
|
36
|
+
For `isAdmin || (total > limit && !locked)`:
|
|
37
|
+
|
|
38
|
+
| Vector | `isAdmin` | `total > limit` | `!locked` | Decision |
|
|
39
|
+
| --- | --- | --- | --- | --- |
|
|
40
|
+
| v1 | F | T | T | true |
|
|
41
|
+
| v2 | F | F | T | false |
|
|
42
|
+
| v3 | F | T | F | false |
|
|
43
|
+
| v4 | T | F | T | true |
|
|
44
|
+
|
|
45
|
+
- v1 and v2 differ only in `total > limit` and disagree, so that condition is
|
|
46
|
+
proven.
|
|
47
|
+
- v1 and v3 do the same for `!locked`.
|
|
48
|
+
- v4 and v2 do the same for `isAdmin`.
|
|
49
|
+
|
|
50
|
+
Remove v2 and two of the three proofs collapse, even though every condition has
|
|
51
|
+
still been observed both true and false, and every line is still green. That is
|
|
52
|
+
the gap MC/DC exists to catch, and it is why the criterion is required for the
|
|
53
|
+
highest software assurance levels in avionics.
|
|
54
|
+
|
|
55
|
+
Supercov stores **vector-level provenance**: which test produced each observed
|
|
56
|
+
vector, not just which tests touched the decision. A filtered query therefore
|
|
57
|
+
recomputes valid witness pairs for the tests it selected, rather than filtering
|
|
58
|
+
a percentage computed for a different set. A witness assembled from one unit
|
|
59
|
+
vector and one end-to-end vector counts for the combined suite and for neither
|
|
60
|
+
level alone — and Supercov reports it that way.
|
|
61
|
+
|
|
62
|
+
## Quality of evidence
|
|
63
|
+
|
|
64
|
+
Not all coverage is equally convincing. Each line, branch alternative, vector
|
|
65
|
+
and condition records how it was reached:
|
|
66
|
+
|
|
67
|
+
| Level | Meaning |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| Unexecuted | No evidence |
|
|
70
|
+
| Executed | Reached during a test, with no explicit causal link |
|
|
71
|
+
| Action-linked | Reached inside a recognised browser action such as `locator.click()` |
|
|
72
|
+
| Assertion-linked | Reached inside an `expect()` matcher, or in the code path an assertion depends on |
|
|
73
|
+
|
|
74
|
+
Only an explicit browser or server event can raise confidence to
|
|
75
|
+
assertion-linked. Where Supercov has to fall back on timing correlation — an
|
|
76
|
+
early cross-origin iframe probe, for example — the evidence stays
|
|
77
|
+
execution-only and is labelled as such. Code reached outside a recognised
|
|
78
|
+
action, such as setup work or a helper making its own HTTP requests, still has
|
|
79
|
+
exact test attribution but may carry no action phase at all.
|
|
80
|
+
|
|
81
|
+
In Playwright, the phase travels with the request: an action opened in the
|
|
82
|
+
browser is still the active phase inside the server route it triggers, so a
|
|
83
|
+
chain of `click → application decision → visible assertion` is queryable.
|
|
84
|
+
|
|
85
|
+
## Provenance
|
|
86
|
+
|
|
87
|
+
Every test carries two independent labels.
|
|
88
|
+
|
|
89
|
+
**Runner** is the process that executed it — `playwright`, `vitest`, `jest`,
|
|
90
|
+
`node`.
|
|
91
|
+
|
|
92
|
+
**Kind** is its semantic level — `e2e`, `integration`, `component`, `unit`.
|
|
93
|
+
Kind is resolved in descending confidence from an explicit `SUPERCOV_TEST_KIND`,
|
|
94
|
+
then the Playwright project name, then the test path, then the runner default
|
|
95
|
+
(Playwright is end-to-end, Vitest is unit). Queries preserve how the label was
|
|
96
|
+
established, so an inferred kind is never presented as a declared one.
|
|
97
|
+
|
|
98
|
+
Vitest module-import and setup execution is retained as a separate setup scope
|
|
99
|
+
rather than being attributed to whichever test happened to run first.
|
|
100
|
+
|
|
101
|
+
## Attempts and filters
|
|
102
|
+
|
|
103
|
+
Evidence records attempt status, so a test is classified as passed, failed,
|
|
104
|
+
flaky, skipped, timed out, interrupted, unknown, or selected but unstarted
|
|
105
|
+
after fail-fast. `--filter` selects which attempts contribute to a view:
|
|
106
|
+
|
|
107
|
+
- `all` — every executed attempt, including attempts that later failed. This is
|
|
108
|
+
the default and matches conventional coverage tools.
|
|
109
|
+
- `passed` — successful attempts of tests that ultimately passed.
|
|
110
|
+
- `failed` — failed attempts only, including failed retries of a flaky test.
|
|
111
|
+
|
|
112
|
+
Passed and failed views are derived from the same immutable archive rather than
|
|
113
|
+
duplicated into separate report files, so they cannot disagree.
|
|
114
|
+
|
|
115
|
+
## When completeness is blocked
|
|
116
|
+
|
|
117
|
+
A verdict is only useful if it refuses to be complete when it cannot be:
|
|
118
|
+
|
|
119
|
+
- **Ambiguous scope.** Every candidate source file is retained as included,
|
|
120
|
+
excluded, or ambiguous. Ambiguity blocks a complete verdict and is
|
|
121
|
+
inspectable with `coverage scope`. Set `SUPERCOV_SOURCE_ROOTS` to declare the
|
|
122
|
+
authoritative scope.
|
|
123
|
+
- **Semantic-safety blockers.** When application code coerces or observes a
|
|
124
|
+
function's own source, Supercov leaves that body uninstrumented and records
|
|
125
|
+
the blocker rather than transforming code whose text is being read.
|
|
126
|
+
- **Unknowable denominators.** Direct `eval` and `Function` source cannot
|
|
127
|
+
receive a stable pre-run denominator. Their exact locations are recorded as
|
|
128
|
+
completeness blockers instead of being silently excluded.
|
|
129
|
+
- **Unattributed evidence.** Execution that arrives without a carrier is stored
|
|
130
|
+
under a first-class background scope, visible in the all-attempt view and
|
|
131
|
+
excluded from per-test passed-only coverage.
|
|
132
|
+
|
|
133
|
+
None of these are rounded away. A blocked verdict is more useful than a
|
|
134
|
+
comfortable 100%.
|
package/docs/evidence.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Evidence and runs
|
|
2
|
+
|
|
3
|
+
Each run stores one evidence artifact and its metadata. Reports, queries, and
|
|
4
|
+
comparisons are derived from those files on demand.
|
|
5
|
+
|
|
6
|
+
## What a run is
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
.supercov/runs/2026-08-24T01-25-11Z/
|
|
10
|
+
evidence.raw.gz exact denominator manifest + raw per-worker and background evidence
|
|
11
|
+
run.json fingerprints, phase timings, schema version, integrity state
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Two files. No HTML, no derived report, no query cache. Loose evidence written
|
|
15
|
+
during the run is removed only after the whole run directory is atomically
|
|
16
|
+
visible, so a run is either complete or absent.
|
|
17
|
+
|
|
18
|
+
Run ids are UTC timestamps, which makes them sort chronologically and makes
|
|
19
|
+
retention deterministic.
|
|
20
|
+
|
|
21
|
+
## Derived, never stored
|
|
22
|
+
|
|
23
|
+
Every coverage view — the summary, per-file rankings, gap lists, decision
|
|
24
|
+
detail, per-test contribution, the minimizer, and the passed and failed filters
|
|
25
|
+
— is reconstructed from the archive when you ask for it. Nothing is written back.
|
|
26
|
+
|
|
27
|
+
This is why `--filter passed` and `--filter all` can never contradict each
|
|
28
|
+
other, and why a query added in a future version can answer questions about a
|
|
29
|
+
run recorded today: the stored schema is the raw evidence, not a rendering of it.
|
|
30
|
+
|
|
31
|
+
Fresh-process summary, files and gaps queries take roughly two tenths of a
|
|
32
|
+
second on the reference run described in [Performance](/docs/performance).
|
|
33
|
+
|
|
34
|
+
## Integrity and staleness
|
|
35
|
+
|
|
36
|
+
Each run stores SHA-256 fingerprints for:
|
|
37
|
+
|
|
38
|
+
- first-party source
|
|
39
|
+
- test files
|
|
40
|
+
- dependency lockfiles
|
|
41
|
+
- test and build configuration
|
|
42
|
+
- the instrumenter itself
|
|
43
|
+
|
|
44
|
+
plus the evidence schema version and the Git revision and dirty state at the
|
|
45
|
+
time of the run.
|
|
46
|
+
|
|
47
|
+
Queries compare the stored fingerprint against the current workspace and
|
|
48
|
+
visibly mark a stale run. Evidence carrying a different run scope is rejected
|
|
49
|
+
outright rather than merged in.
|
|
50
|
+
|
|
51
|
+
## Comparing two runs
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npx supercov diff <older-run> <newer-run>
|
|
55
|
+
npx supercov diff <older-run> <newer-run> --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`diff` reports what the newer run covers that the older one did not, and what
|
|
59
|
+
it lost. Both inputs are immutable and untouched, which is what makes the
|
|
60
|
+
comparison meaningful: neither side can have been rewritten by the act of
|
|
61
|
+
comparing them.
|
|
62
|
+
|
|
63
|
+
## Merging shards
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx supercov merge <first-run-id> <second-run-id> [...]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`merge` accepts only runs whose source, test, dependency, configuration,
|
|
70
|
+
instrumenter, schema and denominator fingerprints are identical. It rewrites
|
|
71
|
+
the run scope inside every evidence record, namespaces shard paths, and
|
|
72
|
+
publishes a new immutable run atomically. Input runs are never modified or
|
|
73
|
+
deleted.
|
|
74
|
+
|
|
75
|
+
This is the distributed and multi-host primitive. Incompatible shards fail with
|
|
76
|
+
the exact differing fingerprint domains rather than producing a plausible but
|
|
77
|
+
invalid aggregate — two shards built from different source trees do not have a
|
|
78
|
+
common denominator, and no amount of arithmetic creates one.
|
|
79
|
+
|
|
80
|
+
## Durability
|
|
81
|
+
|
|
82
|
+
Everything that can be interrupted is written to survive it.
|
|
83
|
+
|
|
84
|
+
- Evidence archive, metadata and state writes use sibling temporary files,
|
|
85
|
+
`fsync`, and atomic rename.
|
|
86
|
+
- Lock acquisition uses exclusive creation followed by `fsync`.
|
|
87
|
+
- Run state is written durably through the preparing, building, testing and
|
|
88
|
+
publishing phases.
|
|
89
|
+
- `SIGINT`, `SIGTERM` and `SIGHUP` are forwarded to the entire child process
|
|
90
|
+
group.
|
|
91
|
+
- If the process is killed without a cleanup opportunity, the next invocation
|
|
92
|
+
marks the dead PID's run abandoned and refreshes the isolated namespace
|
|
93
|
+
before reusing it.
|
|
94
|
+
|
|
95
|
+
The published `run.json` is the durable terminal record, so terminal work state
|
|
96
|
+
is not retained after publication.
|
|
97
|
+
|
|
98
|
+
## Retention
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
npx supercov clean
|
|
102
|
+
npx supercov clean --keep 20 --dry-run
|
|
103
|
+
npx supercov clean --keep 20
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Cleanup never runs automatically. `clean` removes explicit history, orphaned
|
|
107
|
+
and terminal transient data, and the marker-owned build workspace; `--keep N`
|
|
108
|
+
preserves the N newest runs. It acquires the same lock as a coverage run,
|
|
109
|
+
refuses to race an active run, and never touches unowned paths.
|
|
110
|
+
|
|
111
|
+
## Phase timings
|
|
112
|
+
|
|
113
|
+
Every run records monotonic durations for initialization, workspace
|
|
114
|
+
preparation, adapter setup, the instrumented build, your unchanged test command,
|
|
115
|
+
and evidence publication. They are stored in `run.json` and returned by
|
|
116
|
+
`supercov runs --json`.
|
|
117
|
+
|
|
118
|
+
These are timings, not an overhead claim. A test script that performs its own
|
|
119
|
+
build may overlap work with the instrumented-build phase, and true end-to-end
|
|
120
|
+
overhead requires an explicit control run — which Supercov never performs
|
|
121
|
+
automatically, because an arbitrary test command can write data or cost money.
|
|
122
|
+
[Performance](/docs/performance) documents the comparison methodology.
|