Skip to content

feat: emit coverage summary in coverage.json (v2) and render Markdown report#337

Merged
jwulf merged 2 commits into
mainfrom
feat/coverage-summary-report
May 21, 2026
Merged

feat: emit coverage summary in coverage.json (v2) and render Markdown report#337
jwulf merged 2 commits into
mainfrom
feat/coverage-summary-report

Conversation

@jwulf
Copy link
Copy Markdown
Member

@jwulf jwulf commented May 21, 2026

Stacked on top of #336. Once #336 merges, this PR will auto-retarget to main.

What

Extends the materializer's coverage artefact with a deterministic summary block, and adds an npm run coverage:report script that renders it as Markdown by default (with --format=json passthrough).

Why

The numbers behind "117 emitted feature specs / 73 suppressed-by-template / 190 spec operations / 70 variants / 26 lifecycle" were previously only derivable by hand-walking the planner outputs. Embedding a single source of truth in coverage.json (built once at emission time) means the JSON artefact and any rendered report agree by construction, and downstream tools can read a fixed shape instead of re-implementing the reconciliation.

Changes

  • materializer/src/coverageSummary.ts (new) — pure aggregation:
    • loadSpecOperationIds(specBundleDir) walks spec.paths.{path}.{method}.operationId from the bundled OpenAPI spec.
    • buildCoverageSummary({ allSpecOpIds, emittedFeatureOpIds, suppressedOpIds, entries, variantSpecs, lifecycleSpecs }) returns the summary block: totals, unmapped (sorted), per-template aggregates (sorted by name; specs / unique ops / entries / invoke steps / observe steps).
  • materializer/src/index.ts — tracks emittedFeatureOpIds while iterating the --all feature loop and writes coverage.json v2 with new top-level config, emitter, summary fields (existing suppressedOpIds / entries kept verbatim, so L3 invariants remain version-agnostic).
  • scripts/render-coverage-report.ts (new) + npm run coverage:report — pure transform over the embedded summary block. Markdown by default; --format=json passthrough; --input / --out for arbitrary artefacts. Throws on v1 input rather than guessing.
  • tests/codegen/coverage-summary.test.ts — 6 tests: reconciliation math (total = emitted + suppressed + unmapped), per-template alphabetical sort, unmapped sort, full Markdown layout against a hand-crafted fixture matching the production figures, empty-perTemplate branch, v1 artefact rejection.

Sample output

# Coverage report — camunda-oca

- Emitter: `playwright`
- Spec operations: **190**
- Emitted feature specs: **117**
- Suppressed by scenario-template coverage: **73**
- Variant specs: **70**
- Lifecycle (template) specs: **26**
- Operation coverage: **190 / 190 (100.0%)**
- Unmapped operations: **0**

Validation

  • All 6 new tests pass.
  • Full suite: 637 passed / 4 skipped.
  • L3 invariants (configs/camunda-oca/regression-invariants.test.ts) still green — they read suppressedOpIds / entries.emittedSpec, both unchanged.
  • Lint + typecheck (extractor / planner / emitter-sdk / materializer / request-validation / tests) clean.
  • Pipeline regen byte-stable on top of refactor: make materializer a generic transformer (#335) #336.

… report

Extends the materializer's coverage artefact with a deterministic
summary block (total spec ops, emitted feature specs, suppressed by
template, variant specs, lifecycle specs, unmapped operations, per-
template aggregates) and adds 'npm run coverage:report' which renders
that block as Markdown by default ('--format=json' passthrough).

The summary is built once at emission time and embedded in
coverage.json so the JSON artefact and the rendered report agree by
construction — the renderer is a pure transform and never re-walks
the planner outputs or the bundled spec.
@jwulf jwulf force-pushed the feat/coverage-summary-report branch from e90cb93 to 24f0b40 Compare May 21, 2026 05:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the materializer’s coverage.json artefact to a v2 shape that includes a deterministic summary block (computed at emission time from the bundled OpenAPI spec + emission/suppression data) and adds a CLI/reporting script to render that summary as a Markdown coverage report.

Changes:

  • Add materializer/src/coverageSummary.ts to load spec operationIds and aggregate deterministic coverage totals + per-template breakdowns.
  • Update materializer/src/index.ts to emit coverage.json v2 (config, emitter, summary) while preserving existing suppressedOpIds/entries.
  • Add scripts/render-coverage-report.ts + npm run coverage:report to render Markdown (default) or re-emit JSON, with unit tests in tests/codegen/coverage-summary.test.ts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/codegen/coverage-summary.test.ts Adds unit tests for summary reconciliation/sorting and for deterministic Markdown rendering + v1 rejection.
scripts/render-coverage-report.ts New CLI/script to read v2 coverage artefacts and render a Markdown report (or pass through JSON).
package.json Adds coverage:report script entry.
materializer/src/index.ts Tracks emitted feature opIds and writes coverage.json v2 with embedded summary.
materializer/src/coverageSummary.ts New aggregation + spec operationId loader used to build the embedded summary block.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/render-coverage-report.ts Outdated
Comment thread materializer/src/coverageSummary.ts Outdated
Base automatically changed from refactor/generic-materializer-335 to main May 21, 2026 05:50
…l and dedupe spec opIds

Two Copilot review comments on PR #337:

1. render-coverage-report.ts: findRepoRoot() walked up from cwd
   looking for any package.json, but this is a monorepo with nested
   workspace package.json files (materializer/, path-analyser/, …) so
   running the script from a workspace subdir would stop at the wrong
   directory and break getActiveConfigName() / getPlaywrightSuiteDir()
   lookups. Drop findRepoRoot() and derive REPO_ROOT from
   import.meta.url instead — matches the convention used by sibling
   scripts (export-ontology.ts, build-ontology.ts,
   run-pw-request-validation.ts).

2. coverageSummary.ts: loadSpecOperationIds() returned ids.sort()
   without deduping. OpenAPI does not technically forbid duplicate
   operationIds; if the bundled spec ever contained any,
   totalSpecOperations and unmappedOperations would inflate and the
   reconciliation math would silently disagree with the rest of the
   repo (configs/camunda-oca/regression-invariants.test.ts already
   treats spec opIds as a Set). Collect into a Set and return the
   sorted unique list.

Add tests/codegen/coverage-summary.test.ts coverage for both branches
of loadSpecOperationIds (dedupe + ENOENT), with a class-scoped
assertion that ids.length === new Set(ids).size for any spec.
@jwulf jwulf merged commit bd44da0 into main May 21, 2026
2 checks passed
@jwulf jwulf deleted the feat/coverage-summary-report branch May 21, 2026 05:55
@jwulf jwulf self-assigned this May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants