supercov 2.0.0 → 3.0.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.
Files changed (37) hide show
  1. package/README.md +52 -22
  2. package/docs/agent-loop.md +8 -13
  3. package/docs/assertions.md +161 -75
  4. package/docs/cli.md +111 -59
  5. package/docs/coverage-model.md +5 -6
  6. package/docs/evidence.md +0 -11
  7. package/docs/getting-started.md +1 -1
  8. package/docs/performance.md +6 -14
  9. package/docs/quality.md +1 -1
  10. package/docs/security.md +43 -12
  11. package/docs/supported-suites.md +44 -48
  12. package/docs/troubleshooting.md +12 -5
  13. package/docs/workspace-isolation.md +23 -2
  14. package/package.json +36 -21
  15. package/runtime/javascript/atomic.mjs +46 -2
  16. package/runtime/javascript/ava.mjs +82 -0
  17. package/runtime/javascript/bootstrap.cjs +43 -0
  18. package/runtime/javascript/jest.cjs +52 -15
  19. package/runtime/javascript/jest.config.mjs +9 -1
  20. package/runtime/javascript/jestReporter.mjs +5 -6
  21. package/runtime/javascript/launchSupervisor.mjs +127 -15
  22. package/runtime/javascript/mocha.mjs +180 -0
  23. package/runtime/javascript/nodeTest.mjs +57 -3
  24. package/runtime/javascript/playwright.mjs +241 -32
  25. package/runtime/javascript/provenance.mjs +1 -1
  26. package/runtime/javascript/register.mjs +290 -2
  27. package/runtime/javascript/resolve-loader.mjs +40 -1
  28. package/runtime/javascript/runnerEvidence.mjs +3 -5
  29. package/runtime/javascript/runtime.mjs +551 -61
  30. package/runtime/javascript/tap.mjs +130 -0
  31. package/runtime/javascript/transport.mjs +7 -2
  32. package/runtime/javascript/vitest.mjs +5 -8
  33. package/runtime/javascript/vitestReporter.mjs +3 -10
  34. package/docs/assertion-agent.md +0 -150
  35. package/docs/assertion-evidence.md +0 -103
  36. package/docs/assertion-maps.md +0 -316
  37. package/schemas/assertions.schema.json +0 -276
@@ -0,0 +1,130 @@
1
+ // Supercov node-tap adapter. The tap CLI runs each test file as a process of
2
+ // its own and marks it with TAP_CHILD_ID; there the preload imports this
3
+ // adapter, which patches the class every subtest is made by -- TestBase in
4
+ // @tapjs/core (tap 18 and newer), Test in libtap (tap 15-16) -- so a suite
5
+ // runs exactly as written. tap may run subtests in parallel (t.jobs)
6
+ // and runs each body through an AsyncResource made when the subtest is, so
7
+ // each subtest is made, and therefore runs, inside a coverage carrier of its
8
+ // own, as node:test subtests do. What ran under it is written with the
9
+ // subtest's outcome once it ends. The file's own top level, outside any
10
+ // subtest, counts for the run. It computes no coverage.
11
+ import { realpathSync } from "node:fs";
12
+ import { createRequire } from "node:module";
13
+ import { dirname, join } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ import { beginBufferedServerEvidence, flushBufferedServerEvidence, takeNodeAssertionPhases, withCoverageCarrier, } from "./runtime.mjs";
16
+ import { runnerExecutionScope, writeRunnerEvidence } from "./runnerEvidence.mjs";
17
+
18
+ // The classes that make subtests in the project's tap.
19
+ async function subtestClasses() {
20
+ const classes = [];
21
+ let fromTap;
22
+ try {
23
+ const fromProject = createRequire(join(process.cwd(), "package.json"));
24
+ const tap = realpathSync(dirname(fromProject.resolve("tap/package.json")));
25
+ fromTap = createRequire(join(tap, "package.json"));
26
+ }
27
+ catch {
28
+ return classes;
29
+ }
30
+ let core;
31
+ try {
32
+ core = dirname(fromTap.resolve("@tapjs/core/package.json"));
33
+ }
34
+ catch {
35
+ core = undefined;
36
+ }
37
+ if (core) {
38
+ // An ESM test file gets the ESM build and a CommonJS one the
39
+ // CommonJS build; they are different classes, so both are patched.
40
+ try {
41
+ classes.push((await import(pathToFileURL(join(core, "dist", "esm", "test-base.js")).href)).TestBase);
42
+ }
43
+ catch {
44
+ // Not every release ships both builds.
45
+ }
46
+ try {
47
+ classes.push(createRequire(import.meta.url)(join(core, "dist", "commonjs", "test-base.js")).TestBase);
48
+ }
49
+ catch {
50
+ // As above.
51
+ }
52
+ return classes;
53
+ }
54
+ // libtap is CommonJS, and its exports hide lib/: reach it from its main.
55
+ try {
56
+ const lib = dirname(fromTap.resolve("libtap"));
57
+ classes.push(createRequire(import.meta.url)(join(lib, "test.js")));
58
+ }
59
+ catch {
60
+ // Neither tap's own classes nor libtap's: tap 14 or older.
61
+ }
62
+ return classes;
63
+ }
64
+
65
+ export async function adaptTap(testFile) {
66
+ const evidenceDirectory = process.env.SUPERCOV_EVIDENCE_DIR;
67
+ if (!evidenceDirectory)
68
+ return;
69
+ for (const Class of await subtestClasses()) {
70
+ if (typeof Class?.prototype?.sub === "function" && !Class.prototype.sub.supercovTap)
71
+ patch(Class, testFile, evidenceDirectory);
72
+ }
73
+ }
74
+
75
+ function patch(TestBase, testFile, evidenceDirectory) {
76
+ const sub = TestBase.prototype.sub;
77
+ // Subtests of one parent that share a name, told apart by order.
78
+ const registrations = new WeakMap();
79
+ TestBase.prototype.sub = function supercovTapSub(Class, extra = {}, caller = this.sub) {
80
+ const names = [];
81
+ for (let test = this; test?.parent; test = test.parent)
82
+ names.unshift(test.name);
83
+ const name = [...names, extra.name || "(unnamed test)"].join(" > ");
84
+ const counts = registrations.get(this) ?? new Map();
85
+ registrations.set(this, counts);
86
+ const ordinal = counts.get(name) ?? 0;
87
+ counts.set(name, ordinal + 1);
88
+ const identity = {
89
+ runner: "tap",
90
+ file: testFile,
91
+ name,
92
+ ...(ordinal ? { registrationOrdinal: ordinal } : {}),
93
+ };
94
+ const scope = runnerExecutionScope(identity);
95
+ beginBufferedServerEvidence(scope);
96
+ let written = false;
97
+ const finish = (status) => {
98
+ if (written)
99
+ return;
100
+ written = true;
101
+ writeRunnerEvidence(identity, status, scope, evidenceDirectory, takeNodeAssertionPhases(scope), flushBufferedServerEvidence(scope));
102
+ };
103
+ // Both libtap and @tapjs/core announce the subtest they make, before
104
+ // they may start it and take it off their lists.
105
+ let subtest;
106
+ const made = (test) => {
107
+ subtest ??= test;
108
+ };
109
+ this.on?.("subtestAdd", made);
110
+ let result;
111
+ try {
112
+ result = withCoverageCarrier({ version: 1, scope }, () => Reflect.apply(sub, this, [Class, extra, caller]));
113
+ }
114
+ finally {
115
+ this.removeListener?.("subtestAdd", made);
116
+ }
117
+ if (!subtest) {
118
+ // Skipped, todo or filtered out: tap records a pass and makes no
119
+ // subtest. Nor does a parent that bailed out or already ended.
120
+ finish("skipped");
121
+ return result;
122
+ }
123
+ const settle = () => finish(extra.skip || extra.todo ? "skipped"
124
+ : typeof subtest.passing === "function" && !subtest.passing() ? "failed"
125
+ : "passed");
126
+ Promise.resolve(result).then(settle, () => finish("failed"));
127
+ return result;
128
+ };
129
+ TestBase.prototype.sub.supercovTap = true;
130
+ }
@@ -4,6 +4,11 @@ export const COVERAGE_SCOPE_COOKIE = "__supercov_scope";
4
4
  export const COVERAGE_PHASE_COOKIE = "__supercov_phase";
5
5
  export const COVERAGE_CARRIER_ENV = "SUPERCOV_CONTEXT";
6
6
  export const DEFAULT_SERVER_EVIDENCE_ROOT = "/tmp/supercov-server-evidence";
7
+ // Buffer as it was when Supercov loaded. Tests replace globals -- axios's set
8
+ // `globalThis.Buffer = undefined` to check it copes -- and the runtime runs
9
+ // inside those tests; it must not fail where the application does not.
10
+ var capturedBuffer = typeof Buffer === "undefined" ? void 0 : Buffer;
11
+
7
12
  function configuredServerEvidenceRoot() {
8
13
  return typeof process !== "undefined" &&
9
14
  process.env?.["SUPERCOV_SERVER_EVIDENCE_ROOT"]
@@ -68,13 +73,13 @@ export function decodeCoverageScope(encoded) {
68
73
  }
69
74
  }
70
75
  export function encodeCoverageCarrier(carrier) {
71
- return Buffer.from(JSON.stringify(carrier), "utf8").toString("base64url");
76
+ return capturedBuffer.from(JSON.stringify(carrier), "utf8").toString("base64url");
72
77
  }
73
78
  export function decodeCoverageCarrier(encoded) {
74
79
  if (!encoded)
75
80
  return undefined;
76
81
  try {
77
- const value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
82
+ const value = JSON.parse(capturedBuffer.from(encoded, "base64url").toString("utf8"));
78
83
  if (value.version !== 1)
79
84
  return undefined;
80
85
  if (value.scope) {
@@ -1,10 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { mkdirSync } from "node:fs";
3
2
  import { relative, resolve, sep } from "node:path";
4
3
  import { afterEach, beforeEach } from "vitest";
5
4
  import { coverageSnapshot as localCoverageSnapshot, activateCoverageScope as localActivateCoverageScope, enableRuntimeSnapshotEvidence as localEnableRuntimeSnapshotEvidence, resetCoverage as localResetCoverage, takeNodeAssertionPhases as localTakeNodeAssertionPhases, } from "./runtime.mjs";
6
5
  import { inferTestProvenance } from "./provenance.mjs";
7
- import { atomicWriteFileSync } from "./atomic.mjs";
6
+ import { appendEvidenceRecord } from "./atomic.mjs";
8
7
  const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"];
9
8
  const emittedSetupFiles = new Set();
10
9
  const attempts = new Map();
@@ -39,12 +38,10 @@ function titlePath(task) {
39
38
  }
40
39
  return names;
41
40
  }
42
- function writeEvidence(payload, suffix) {
41
+ function writeEvidence(payload) {
43
42
  if (!evidenceDirectory)
44
43
  return;
45
- const directory = resolve(process.cwd(), evidenceDirectory, suffix);
46
- mkdirSync(directory, { recursive: true });
47
- atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
44
+ appendEvidenceRecord(resolve(process.cwd(), evidenceDirectory), "vitest-worker", payload);
48
45
  }
49
46
  beforeEach((context) => {
50
47
  const task = context.task;
@@ -72,7 +69,7 @@ beforeEach((context) => {
72
69
  runtime: [setupSnapshot],
73
70
  browser: [],
74
71
  server: [],
75
- }, `vitest-${task.file.id}-setup`);
72
+ });
76
73
  }
77
74
  }
78
75
  const testId = `vitest:${task.id}`;
@@ -118,7 +115,7 @@ afterEach((context) => {
118
115
  browser: [],
119
116
  server: [],
120
117
  };
121
- writeEvidence(payload, `vitest-${task.id}-${retry}`);
118
+ writeEvidence(payload);
122
119
  activeScopes.delete(task.id);
123
120
  runtime.activateCoverageScope();
124
121
  });
@@ -1,8 +1,7 @@
1
- import { mkdirSync } from "node:fs";
2
1
  import { relative, resolve, sep } from "node:path";
3
2
  import { fileURLToPath } from "node:url";
4
3
  import { inferTestProvenance } from "./provenance.mjs";
5
- import { atomicWriteFileSync } from "./atomic.mjs";
4
+ import { appendEvidenceRecord } from "./atomic.mjs";
6
5
  function sourcePath(moduleId) {
7
6
  const absolute = moduleId.startsWith("file:")
8
7
  ? fileURLToPath(moduleId)
@@ -63,11 +62,8 @@ export default class SupercovVitestReporter {
63
62
  browser: [],
64
63
  server: [],
65
64
  };
66
- const safeId = testCase.id.replace(/[^a-zA-Z0-9_-]/g, "_");
67
65
  this.reportedAttempts.add(`${testCase.id}:${retry}`);
68
- const directory = resolve(process.cwd(), evidenceDirectory, `vitest-${safeId}-${retry}-status`);
69
- mkdirSync(directory, { recursive: true });
70
- atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
66
+ appendEvidenceRecord(resolve(process.cwd(), evidenceDirectory), "vitest-status", payload);
71
67
  }
72
68
  /** Vitest 2 compatibility; Vitest 3+ uses onTestCaseResult above. */
73
69
  onFinished(files = []) {
@@ -111,10 +107,7 @@ export default class SupercovVitestReporter {
111
107
  browser: [],
112
108
  server: [],
113
109
  };
114
- const safeId = String(task.id ?? names.join("-")).replace(/[^a-zA-Z0-9_-]/g, "_");
115
- const directory = resolve(process.cwd(), evidenceDirectory, `vitest-${safeId}-${retry}-status`);
116
- mkdirSync(directory, { recursive: true });
117
- atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
110
+ appendEvidenceRecord(resolve(process.cwd(), evidenceDirectory), "vitest-status", payload);
118
111
  }
119
112
  for (const child of task.tasks ?? [])
120
113
  visit(child, file);
@@ -1,150 +0,0 @@
1
- # Mapping assertions with an agent
2
-
3
- Use these instructions when asking a coding agent to create or update an
4
- assertion map. The agent reads your tests and source, explains what each
5
- assertion checks, and edits the run's `assertions.json`. Supercov supplies the
6
- run evidence and checks the file's references and freshness.
7
-
8
- Start with [Understanding assertion coverage](assertions.md) for a prompt you
9
- can copy. The steps below are the agent's working instructions.
10
-
11
- ## Choose a run and keep it fixed
12
-
13
- 1. Use a matching current run, or run the requested suite through
14
- `supercov -- <test command>`. Use the full suite for a suite-wide result.
15
- 2. Read `supercov runs latest assertions --json` and keep `data.run` fixed for
16
- this investigation. Edit the file at `data.map`.
17
- 3. Check `data.inheritance` for reused work or fallback errors. Keep the old
18
- runs and maps. Inspect changes before renewing inherited flows.
19
- 4. Read the [map format](assertion-maps.md), also available through
20
- `supercov docs assertion-maps`.
21
-
22
- Use a single writer for the map. Save atomically if your editor supports it.
23
- Do not change application code or tests unless the user also requested those
24
- changes. Never edit `assertions.state.json` or the run's recorded evidence.
25
-
26
- ## Inspect the assertions and source
27
-
28
- ```sh supercov-example
29
- npx supercov runs <run-id> assertions --limit 100 --json
30
- npx supercov runs <run-id> assertions report --view statements --limit 100 --json
31
- npx supercov runs <run-id> assertions report --view tests --limit 100 --json
32
- npx supercov runs <run-id> assertions report --view changes --limit 100 --json
33
- npx supercov runs <run-id> assertion <assertion-id>
34
- npx supercov runs <run-id> source src/shipping.js --offset 0 --limit 100
35
- ```
36
-
37
- Follow every `pagination.nextOffset`; a page is not the whole result. Restart a
38
- paged read if its `revision` changes. Current source must match the pinned run.
39
- Use ordinary source-reading tools to inspect setup, inputs, mocks, callbacks,
40
- branches, called functions, helpers and relevant configuration.
41
-
42
- Preserve each assertion's ID and complete `at` anchor. Locations use one-based
43
- lines and UTF-8 byte columns. Restore recognized sites marked `inMap: false`
44
- when investigating them; removing an entry does not remove the assertion from
45
- Supercov's list.
46
-
47
- ## Write precise explanations
48
-
49
- For each assertion:
50
-
51
- - Describe exactly what its predicate distinguishes in `observes`. Existence,
52
- truthiness, length and substring checks do not imply equality of every field.
53
- - Split independently maintainable explanations into flows with stable IDs.
54
- Start a new or changed flow with `basis: null`.
55
- - Choose `appliesTo` tests from the tests view using their exact file and name.
56
- Select applicable cases explicitly for shared or parameterized assertions.
57
- - Use complete source statements from the statements view as node anchors when
58
- possible. Write the relationships that connect those nodes to `$assertion`.
59
- - Put a node in `countsAsAsserted` only when its behavior is checked by this
60
- assertion. Every counted node needs a recorded path to `$assertion`.
61
- Counting a guard or block does not count the nested body automatically.
62
- - Add helper, configuration and other dependency files to `watch`. The
63
- declarations holding your nodes, the top level of their files, the assertion
64
- file and the selected test files are already dependencies. Include relevant
65
- guards and alternatives even when they do not appear as graph nodes.
66
- - Keep uncertainty in `questions`. A flow's unresolved questions block its
67
- credit. Assertion-level questions record broader unfinished investigation.
68
-
69
- A fixture-only or absence explanation can have `countsAsAsserted: []`. For
70
- absence, describe the ordering or barrier and the observation window. Never
71
- credit an unexecuted body merely because executing it would violate the check.
72
-
73
- There is no known number of flows an assertion should have. Do not label an
74
- assertion complete just because you recorded one explanation.
75
-
76
- ## Check why a node receives no credit
77
-
78
- Read the node's credit reason in assertion detail, or its computed `nodeCredit`
79
- entry in JSON. Compare the statement's execution evidence and selected test's
80
- outcome. Keep these report fields out of the editable map.
81
-
82
- A source node can be context only, lack a measured statement, have stale inputs,
83
- or lack matching passing assertion and execution evidence. Shared setup and
84
- background execution do not count as execution by every consuming test. Skipped
85
- and TODO tests supply no passing witness. An unobserved site in a passing test
86
- can indicate an untaken branch or missing measurement.
87
-
88
- Do not borrow another assertion's identity or another test's execution. Preserve
89
- zero-credit explanations when they accurately describe what the test checks.
90
- Use `assertions report --view excludedStatements` to inspect erased TypeScript
91
- imports. See [Investigating assertion evidence](assertion-evidence.md) for
92
- asynchronous and missing-evidence cases.
93
-
94
- For a large flow, page `assertion <id> --flow <flow-id> --view nodes --json` and
95
- `--view edges` separately. `--compact` omits repeated source text from the
96
- report; read the matching source separately and never save the compact objects
97
- back into the map.
98
-
99
- ## Assess changes before renewing flows
100
-
101
- When inheriting a map, read every entry in `--view changes`. Each names the
102
- flows it already made stale (`knownFlows`) and, under `exposed`, the tests that
103
- ran the changed code and how many flows they carry; start with those, and
104
- investigate whether other flows are affected too. A flow's own `notices` list changes near its
105
- nodes that could not have reached it.
106
-
107
- Add a `changeAssessments` entry for each managed change ID. Include all listed
108
- `knownFlows` that still exist, plus any other affected flows. Explain why the
109
- remaining claims are unaffected. An empty affected-flow list needs a reason.
110
- Repair missing watches and update the affected graphs or selectors.
111
-
112
- Save these edits, then validate:
113
-
114
- ```sh supercov-example
115
- npx supercov assertions validate --file <map-path> --json
116
- npx supercov runs <run-id> assertions validate --view changes --json
117
- npx supercov runs <run-id> assertions validate --view flows --json
118
- ```
119
-
120
- Page the validation views too. Overall validity covers the whole map even when
121
- only one page is returned. Copy change `expectedBasis` tokens only after
122
- examining the corresponding impact assessments, then save. Validate again and
123
- copy flow tokens only for the claims you have examined.
124
-
125
- Obtain final flow tokens after saving change tokens: a change assessment can
126
- invalidate additional flows. Treat tokens as opaque; do not implement their
127
- hashing or manufacture them. Changing a claim after getting its token makes
128
- that token stale.
129
-
130
- ## Check and summarize
131
-
132
- ```sh supercov-example
133
- npx supercov runs <run-id> assertions check --require-mappings --json
134
- npx supercov runs <run-id> assertions report --view statements --limit 100 --json
135
- npx supercov runs <run-id>
136
- ```
137
-
138
- Add `--require-observed` when every mapped site and test selector is expected to
139
- have passing evidence. Report skipped, TODO or untaken cases when that gate is
140
- unmet. Use `--min <percentage>` only for the user's chosen target; do not lower
141
- it or hide gaps to make a check pass.
142
-
143
- Report the run ID, map path, assertion percentage and counts, unresolved changes,
144
- missing execution and useful next tests. Distinguish statements credited by the
145
- map from raw line coverage and MC/DC. Mention which claims were newly examined
146
- and which were reused.
147
-
148
- A valid file does not prove the explanation or its completeness. A credited
149
- statement can change without failing a test when the edit preserves the observed
150
- property. Keep that distinction clear when recommending code changes.
@@ -1,103 +0,0 @@
1
- # Investigating assertion evidence
2
-
3
- Use assertion details when a statement runs but receives no assertion credit,
4
- or when a mapped assertion has no passing occurrence. The report shows the
5
- recorded explanation and the evidence supporting it separately.
6
-
7
- ```sh supercov-example
8
- npx supercov runs <run-id> assertions --needs-attention --limit 5
9
- npx supercov runs <run-id> assertion <assertion-id>
10
- ```
11
-
12
- Replace the placeholders with the run and assertion IDs from your report.
13
-
14
- ## Follow one assertion
15
-
16
- The detail identifies the exact assertion in the test and the source nodes in
17
- each flow. Each node has an explanation and a credit decision:
18
-
19
- | Decision | Meaning |
20
- | --- | --- |
21
- | Credited | The map counts this statement, its flow is current, and the selected passing test supplies both assertion and execution evidence. |
22
- | Not credited | The map claims this statement, but a reference, freshness or execution requirement is missing. Read the reason beside it. |
23
- | Context only | The node helps explain the assertion; the map does not claim it as an asserted statement. |
24
-
25
- The agent's explanation describes why a value or behavior reaches the assertion.
26
- Supercov's credit reason describes what the recorded evidence supports. A credit
27
- decision does not independently prove that explanation.
28
-
29
- ## When a test has no source coverage
30
-
31
- You may see a warning that a test made assertions but has no source-coverage
32
- evidence. For example, a test might check a third-party parser, a constant, or
33
- configuration without calling measured application code.
34
-
35
- It can also mean application code ran in shared setup or across a process or
36
- network boundary that was not attributed to the test. Missing evidence alone
37
- cannot distinguish these cases. Inspect the test and the relevant statement:
38
-
39
- ```sh supercov-example
40
- npx supercov runs <run-id> assertions report --view tests --limit 20
41
- npx supercov runs <run-id> assertions report --view statements --file src/shipping.js
42
- ```
43
-
44
- In JSON output, a statement's `executionEvidence` distinguishes any recorded
45
- execution, passing tests and execution outside those tests. The tests view
46
- includes outcomes and identifies setup scopes. Use that information to decide
47
- whether to add a test, adjust the test setup or investigate measurement.
48
-
49
- ## When a mapped assertion was not observed
50
-
51
- An assertion can appear in source without being reached in this run. Common
52
- causes include skipped or TODO tests and untaken branches inside passing tests.
53
- An unsupported custom assertion form or missing attribution can also prevent
54
- Supercov from recording the occurrence.
55
-
56
- Read the selected test's outcome before changing the map. A recorded skipped
57
- test and a test with no record are different cases. A passing sibling test does
58
- not supply evidence for an assertion it never reached.
59
-
60
- Use the stricter gate when you expect every mapped assertion and selected test
61
- to have passing evidence:
62
-
63
- ```sh supercov-example
64
- npx supercov runs <run-id> assertions check --require-observed
65
- ```
66
-
67
- This gate can intentionally fail for a suite with TODO or skipped cases. Keep
68
- those entries visible instead of deleting them to make the check pass.
69
-
70
- ## Shared setup and asynchronous work
71
-
72
- Code executed once in shared setup does not count as execution by every test
73
- that later uses its result. Setup remains visible separately. If a statement
74
- needs its own test evidence, arrange a test that executes it and checks its
75
- behavior.
76
-
77
- Per-test `t.after` assertions are included for Node's test runner. Supported HTTP
78
- requests and test-owned WebSocket connections can carry a test's attribution
79
- into callbacks. A WebSocket shared across tests may still lack enough information
80
- to distinguish individual messages. Do not assign that work to a test merely
81
- because it ran around the same time.
82
-
83
- The regular report's **Runtime action phases** section is also separate from
84
- assertion coverage. Zero lines recorded inside action phases does not mean the
85
- agent-authored map credits zero statements.
86
-
87
- ## Read a large flow
88
-
89
- Page the nodes and edges of one flow when the complete graph is too large:
90
-
91
- ```sh supercov-example
92
- npx supercov runs <run-id> assertion <assertion-id> --flow <flow-id> --view nodes --limit 10 --compact
93
- npx supercov runs <run-id> assertion <assertion-id> --flow <flow-id> --view edges --limit 10
94
- npx supercov runs <run-id> source src/shipping.js --offset 0 --limit 20
95
- ```
96
-
97
- `--compact` leaves source locations and credit reasons visible while omitting
98
- repeated source text. It only changes the displayed report. Follow the printed
99
- next-page command, or `pagination.nextOffset` in JSON, until the view is complete.
100
- Never copy compact report objects back into `assertions.json`.
101
-
102
- See [Assertion map format](assertion-maps.md) for fields and validation rules,
103
- and [Mapping assertions with an agent](assertion-agent.md) for the editing loop.