WaveLens Docs
User Guide

wavsummary

A command-line tool that recursively scans a directory for wavelens analyze HTML reports and produces one aggregate summary — how many passed, how many failed, and which check(s) failed for each failure — so a directory of 100 individual reports doesn't have to be opened and read one at a time.

wavsummary 0.7.1-beta · single self-contained executable · no external runtime dependencies

Overview

wavsummary runs no analysis itself and reads no WAV files — it reads the verdicts wavelens analyze already computed and rendered into its HTML report, the same way a test-results dashboard reads JUnit XML rather than re-running the tests. Point it at a directory (or a whole tree of subdirectories) and it produces one page: total reports found, how many passed, how many failed, how many runs errored out without a verdict, and for each failure, exactly which check(s) — Spectral Fidelity, Bit-Exactness, Level Fidelity, Artifact Detection, Distortion, Loudness, and so on — caused it.

It's not a batch-analysis driver. There's no --ref/--dut concept and it never invokes wavelens itself — running the individual analyze calls that produce the reports in the first place is still an external script or CI job's responsibility. wavsummary picks up after that step: point it at wherever those reports landed, recursively, and it does the rest.

Requirements

  • A valid, active WaveLens license file (see License below)
  • A directory containing one or more wavelens analyze HTML reports, at any depth — the scan is always recursive

License

wavsummary requires an active WaveLens license to run, checked before any directory scan begins. There are three ways to point it at your license file, checked in this order — the first one that's set wins.

1. Explicit path with --license

# explicit --license overrides both the env var and the next-to-executable default
wavsummary --license /path/to/wavelens.lic --input reports/ -o summary.html

2. WAVELENS_LICENSE_FILE environment variable

# bash / zsh
export WAVELENS_LICENSE_FILE=/path/to/wavelens.lic
wavsummary --input reports/ -o summary.html

3. Zero configuration — no flag, no env var

Place a file named exactly wavelens.lic in the same folder as the wavsummary executable.

i

Don't have a license file, or need a renewal? Get one at wavelens.dev, or contact us.

Quick start

Point wavsummary at a directory of wavelens analyze reports, recursively:

wavsummary --input ci_reports/ -o summary.html
# output:
wrote summary.html (100 wavelens reports: 92 passed, 8 failed)

Open summary.html — it reads as a member of the same report family wavelens analyze itself produces (same theme, same light/dark support), with a Failed section listing which check(s) each failing case tripped, followed by a Passed section, both sorted alphabetically by path for a stable, scannable order.

Command syntax

One flat command — no subcommands, no modes. The scan is always recursive, so there's no --no-recursive or --depth option.

wavsummary [--license <file.lic>] --input <directory> -o <summary.html> [--json]

Run wavsummary --help (or -h) to print this usage in the terminal. It needs no license.

OptionDescription
--input DIRRoot directory to scan, walked recursively into every subdirectory.
-o PATHWhere to write the HTML summary. Folders in the path that do not exist yet are created.
--jsonOptional. Also writes a machine-readable companion alongside -o's output — same basename, .json instead of .html/.htm. See --json schema.

What counts as a report

A scanned .html/.htm file is treated as a wavelens analyze report if and only if it contains the exact Overall Verdict tile markup analyze itself emits. This marker is unique to analyze reports — a wavelens plot report has its own nav bar (for its Spectrum/Overview tabs) but never an Overall Verdict tile, since a single-file plot has no ref/DUT comparison to verdict on.

Any scanned HTML file without that marker — a plot report, a stray non-wavelens HTML file, or wavsummary's own previously-generated output (see Self-exclusion) — isn't treated as a test case. It's listed separately under Other Files in the summary, informational only, never counted toward the pass/fail totals. The scan never errors out over a file that doesn't match; an unrecognized file is data, not a failure.

One more kind of report counts as a run: when wavelens analyze cannot compare its two files — one can't be read, their formats don't match, or the alignment fails — it writes a small error report in place of the normal one, with the verdict ERROR and the reason. wavsummary counts each as an errored run and lists it with that reason, so a run that never produced a verdict is not missing from the summary. (A run that fails before it starts — a missing flag, an invalid license or --config — writes no report, so check the exit status of the analyze step for those.)

The Overall Verdict and each report's failed checks are both read verbatim from the rendered HTML, never recomputed from the report's own embedded raw JSON. That way wavsummary always reports exactly the verdict wavelens itself reached — which checks gate the overall verdict and which stay informational-only — and never second-guesses it.

Summary report layout

Top banner — four tiles: Total Reports, Passed, Failed, and a Suite Verdict tile (PASS only if every report passed, no run errored and nothing was unreadable, FAIL otherwise) — so the headline doesn't require counting table rows. When any run errored, a fifth tile, Errored, appears, and Total Reports counts those runs too.

Results section — an Errored table (only if any run errored, with the reason in place of the checks), then a Failed table (omitted entirely if there are no failures), then a Passed table. Within each, rows are sorted alphabetically by relative path, and numbered continuously by a # column that runs across both tables rather than restarting at 1 for Passed.

#Test CaseAnalysis ModeVerdictFailed Checks
1mbcompressor/case_jFrequency DomainFAILDistortion, Spectral Fidelity
2ampsweep_fail_stereoFrequency SweepFAILSpectral Fidelity
3bitexact_caseBit-Exact · Frequency DomainPASS—
4codec_roundtripFrequency DomainPASSNot gating: Bit-Exactness

Test Case is the file's path relative to --input, extension stripped, and links to the original report — the href is computed relative to wherever the summary itself was written, so the link resolves correctly whether -o sits inside the scanned tree or off to the side of it. Analysis Mode mirrors the same KPI tile wavelens itself shows at the top of each report — Bit-Exact/Bit Accuracy (plus the underlying spectral profile as a muted trailing label) when either mode gates or ran, otherwise just the spectral profile (Frequency Domain/Frequency Sweep/Amplitude Sweep). Failed Checks lists the checks that failed the run. A check that differs without counting toward the verdict — Bit-Exactness and Loudness, unless you asked for them to gate with --bitexact or --loudness — is shown after them in muted text as Not gating, not as a failure (row 4 above passed; its Bit-Exactness difference does not count). A row with neither shows —, never blank.

Other Files section (only rendered if any exist) — every scanned HTML file that didn't match the analyze-report marker, listed by relative path only, no verdict. A file that couldn't be read at all is listed here too, marked (unreadable).

--json schema

Pass --json to also get a machine-readable companion, written alongside -o's HTML output with the same basename:

{
    "scanned_directory": "ci_reports",
    "total_reports": 100,
    "passed": 91,
    "failed": 8,
    "errored": 1,
    "reports": [
        {
            "relative_path": "mbcompressor/case_j.html",
            "verdict": "FAIL",
            "analysis_mode": "Frequency Domain",
            "analysis_mode_sub": "",
            "failed_checks": ["Distortion", "Spectral Fidelity"],
            "non_gating_checks": ["Bit-Exactness"],
            "error": ""
        }
    ],
    "other_files": [
        { "relative_path": "plot_demo.html", "reason": "not_analyze_report" },
        { "relative_path": "corrupted.html", "reason": "unreadable" }
    ]
}

reports covers every file counted in total_reports, in scan order; total_reports is passed + failed + errored. verdict is "PASS", "FAIL" or, for an errored run, "ERROR" — whose reason is in error (an empty string for every other entry). analysis_mode/analysis_mode_sub mirror the Results table's own Analysis Mode column — empty strings, not omitted fields, when the report's Analysis Mode tile wasn't found. failed_checks lists the checks that failed the run, using the report's own check names (Spectral Fidelity, Bit-Exactness, and so on) — an empty array for a passing report, never omitted; non_gating_checks lists the checks that differ without counting toward the verdict. other_files is everything excluded from those counts, each tagged with a reason — "not_analyze_report" for a scanned file that didn't match the detection marker, "unreadable" for one that couldn't be opened at all — so a downstream consumer doesn't have to guess why a file didn't count.

Self-exclusion

The exact path passed to -o is skipped if it's encountered during the scan — relevant whenever -o points inside --input, e.g. regenerating a summary in place:

# -o lands inside the same directory being scanned
wavsummary --input reports/ -o reports/summary.html

Without this, a second run would still classify its own prior output correctly as "not an analyze report" (it has no Overall Verdict tile) and list it under Other Files rather than misreport it as a test case — self-exclusion just keeps that list free of the tool's own output. It works regardless of whether -o and --input were spelled relative or absolute, or how deep in the recursive walk the match turns up.

Exit codes

CodeMeaning
0Every analyze report found passed, no run errored, and no file was unreadable.
1At least one analyze report failed, or at least one run errored, or at least one file was unreadable, or --input itself was invalid.

Lets a CI pipeline gate directly off wavsummary's own exit code, without a caller having to parse HTML or JSON output just to know pass/fail:

wavsummary --input ci_reports/ -o summary.html || echo "suite failed"
i

Exit code 1 doesn't distinguish "some reports failed" from "a run errored" or "a file was unreadable" — if that distinction matters to your pipeline, read --json's own failed and errored counts and other_files array instead of relying on the exit code alone.

Common recipes

Summarize a CI output directory and gate the pipeline

wavsummary --input ci_reports/ -o summary.html --json || exit 1

Regenerate a summary in place, inside the tree it scans

wavsummary --input reports/ -o reports/summary.html

Write the summary somewhere outside the scanned tree entirely

wavsummary --input tests/reports/ -o tests/summary.html

Just want the numbers, no HTML to open

wavsummary --input reports/ -o summary.html --json
python3 -c "import json; d = json.load(open('summary.json')); print(d['passed'], d['failed'])"

Errors & troubleshooting

MessageMeaning & fix
error: license INVALID -- no license found at '...'Check whichever of the three lookup mechanisms actually resolved — see License.
error: license EXPIRED -- license expired on ...Your license has expired. Renew at wavelens.dev, or contact us.
error: wavsummary requires --input and -oBoth are required on every invocation.
error: '--input' directory '...' doesn't exist or isn't a directory (expected a directory of wavelens analyze HTML reports)Check the path you passed to --input. Unlike a bad file found mid-scan (which is listed and skipped), a bad --input stops the run, and no output is written.
error: unrecognized argument '...'Check the flag name — wavsummary only accepts --license, --input, -o, --json, and --version.
error: failed to write '...'The output file can't be written — check that you have permission to write there.
error: could not create the folder for '...'A folder in the -o path doesn't exist and can't be created — a file with the same name is in the way, or there is no permission.
!

An unreadable file found during the scan is not an error — it's logged as an Other Files entry with reason unreadable, and the summary still gets written. Only a bad --input itself, or a failure writing the output file, actually aborts the run.

FAQ

Does wavsummary run wavelens analyze for me?

No. It only reads reports that already exist on disk. Producing them in the first place — running analyze once per ref/DUT pair — is still a script or CI job's job, exactly as before wavsummary existed.

What happens to a wavelens plot report sitting in the same directory?

It's scanned, recognized as not an analyze report (no Overall Verdict tile), and listed under Other Files — informational only, never counted toward the pass/fail totals.

Can I scan only a subset of a directory with a filter or glob?

No — every .html/.htm file under --input is scanned, recursively, with no include/exclude pattern. If you need a subset, point --input at a narrower directory, or copy the reports you want summarized into their own folder first.

A comparison failed with an error — does it show up in the summary?

Yes, if wavelens analyze got as far as comparing: a file that can't be read, mismatched formats or a failed alignment each leave an error report, which the summary counts as an errored run and lists with its reason. A run that fails before that point (a missing flag, an invalid license or --config) writes no report; check the exit status of the analyze step for those.

Zero reports were found — is that an error?

No. A summary is still written (0 total, 0 passed, 0 failed) — that's itself information (wrong directory pointed at, or a directory of only plot reports), not a reason to abort.

Does the summary recompute anything from the reports' raw JSON?

No — every verdict and every failed-check label is read verbatim from what wavelens analyze already rendered. See What counts as a report.

wavsummary user guide · part of the WaveLens Audio QA toolset