From 2d9a4fd5f294a17ac47fd175dc0d22e06d1e064a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 11 Oct 2026 21:54:17 +0000 Subject: [PATCH 1/2] feat(report): add a fix-available-only filter to HTML reports vg report --format html now includes an unchecked "Fix available only" checkbox. Checking it hides rows whose data-fix-available attribute is "no", using fixed versions already stored on the scan artifact. The generated page stays byte-stable, still contains every row, and does not call a registry or embed a script. Closes #249 Signed-off-by: Cursor Agent Co-authored-by: vibgrate-team --- CHANGELOG.md | 7 +++++ DOCS.md | 5 +++- README.md | 2 +- src/reporting/formatters/html.test.ts | 30 +++++++++++++++++++-- src/reporting/formatters/html.ts | 38 ++++++++++++++++++++++----- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b48e848..2dbce325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,13 @@ backward compatible. ### Added +- **HTML reports can hide findings that have no known fix.** + `vg report --format html` includes an unchecked **Fix available only** + checkbox. Checking it hides table rows that do not name a fixed version + already stored on the scan artifact. The file bytes do not change with + that choice, summary counts stay complete, and the report still does not + contact a registry. (#249) + - **Unknown optional lockfile fields warn and continue.** When a lockfile still lists dependencies vg can read, an extra field does not drop that graph or the SBOM rows built from it. The command prints one warning per lockfile path and field name, sorted by path then field: `pnpm-lock.yaml: unknown optional lockfile field ""; continuing with the fields this version understands.` A field name that is not a plain identifier is reported as `(name omitted)`. A truncated lockfile, or one missing the structure its format requires, still exits non-zero. - **`vg build --attest` and `vg build --verify` are documented.** The docs diff --git a/DOCS.md b/DOCS.md index def4288b..f3b2d33f 100644 --- a/DOCS.md +++ b/DOCS.md @@ -500,6 +500,7 @@ external files. The page opens with a summary: - DriftScore and the four component scores. A missing score is `n/a`. A measured `0` stays `0`. - Counts by severity for vulnerability findings and for security findings. A check that did not run stays "Not scanned" rather than a row of zeros. A check that finished with nothing to report shows zeros. - When a finding carries `details.fixedVersions`, how many of those findings name a fix. +- An unchecked **Fix available only** checkbox. Checking it hides table rows that do not name a fix. The saved page does not change, and the summary counts stay complete. - Links to the full tables under the summary: vulnerabilities, security findings, and the other findings. The same artifact always produces the same page. Save it with a shell redirect: @@ -598,6 +599,8 @@ When set: `vg report --format html` writes one self-contained page from the same artifact. It does not call the network and does not load external files. The page opens with the summary listed above (DriftScore, severity counts, a fix-available count when a finding carries `details.fixedVersions`, and links to the tables), then the full vulnerability, security, and other-finding tables. A check that did not run stays "Not scanned". A missing score is `n/a`. The same artifact always produces the same page. +The summary includes a **Fix available only** checkbox (`#fix-available-only`). It is unchecked in the file, so opening the page shows every row. Checking it hides rows whose `data-fix-available` attribute is `no`. That attribute is `yes` only when `details.fixedVersions` on the scan artifact already lists a published fix. Rows without that list, including security findings, stay in the file and are hidden only while the box is checked. Summary counts are not rewritten. The filter is a style rule in the page: no script, no registry call, and no change to the HTML bytes. + ##### Differences - Markdown is a short table. Text is the terminal report: banner and CLI version, project `path` and `targetFramework`, the extended sections above, findings with a fix hint, derived priority actions, architecture, solutions, score bars, and the scanned-at footer. @@ -609,7 +612,7 @@ When set: - DriftScore: Markdown puts the lower-is-better gloss in the opening cell. Text puts the score in **DriftScore Summary** without that gloss. - VCS: Markdown uses separate VCS, Branch, and Commit rows, and Commit requires `vcs.sha`. Text uses one line and can show `shortSha` without requiring `sha`. - Runtime wording: Markdown always says `major(s)`. Text says `major` or `majors`. -- HTML is the browser page: the summary above, then the vulnerability, security, and other-finding tables. It does not print Markdown's recommended-standards section or the text inventory. +- HTML is the browser page: the summary above, then the vulnerability, security, and other-finding tables, plus an unchecked **Fix available only** checkbox that hides rows with no known fix. It does not print Markdown's recommended-standards section or the text inventory. ##### JSON diff --git a/README.md b/README.md index d4893a3b..369495ea 100644 --- a/README.md +++ b/README.md @@ -829,7 +829,7 @@ All HCS computation runs in an optional, separately-licensed engine module that | `vg evidence` | Signed, reproducible regulatory evidence — jurisdiction-neutral regimes (EU CRA first, DORA incident reporting too): `init`, `product`, `release`, `exposure`, `readiness`, `drill`, `watch`, `pack`, `verify`, `push`, `export` | | `vg fix` | Ranked, risk-tiered upgrade plans from the hosted planner — then apply the one you choose | | `vg init [path]` | Initialise config and `.vibgrate/` | -| `vg report` | Render a saved scan artifact as text (default), Markdown, or JSON. There is no HTML report. Field coverage is in [DOCS.md](./DOCS.md#report-format-coverage) | +| `vg report` | Render a saved scan artifact as text (default), Markdown, JSON, or HTML. The HTML page shows every finding and includes an unchecked **Fix available only** checkbox that hides rows with no known fix. Field coverage is in [DOCS.md](./DOCS.md#report-format-coverage) | | `vg review` | **Vibgrate Review** — architecture + security-control review of the current change, locally (`--in-place`, `--local`, `--loop`). Deterministic blast-radius findings from the code graph via `vg review findings-from-diff`; `vg review propose ` attaches a PatchIR dry-run (same `--base` / `--in-place` / `--diff`, `--findings`, or `.vibgrate/review-propose-handoff.json`). One decision (`pass` / `needs_review` / `fail` / `undetermined`) in a signed receipt (Ed25519 over the receipt digest; `vg review verify ` checks it offline); protected findings cannot be blessed into a pass. Reports change integrity, not a proof of security. Builds or refreshes the code map itself when it is missing or stale (`--no-auto-build` opts out) | | `vg sbom export` / `delta` / `vex` | Export CycloneDX/SPDX SBOM, diff two artifacts, or emit an OpenVEX document | | `vg scan [path]` | Scan for upgrade drift | diff --git a/src/reporting/formatters/html.test.ts b/src/reporting/formatters/html.test.ts index 96ab9deb..49010d4c 100644 --- a/src/reporting/formatters/html.test.ts +++ b/src/reporting/formatters/html.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import type { SecurityFinding } from '../../core-open/types.js'; import type { ScanArtifact } from '../types.js'; -import { ADVISORY_CORPUS_FOOTER_ID, FIX_AVAILABLE_ATTR, formatHtmlReport } from './html.js'; +import { ADVISORY_CORPUS_FOOTER_ID, FIX_AVAILABLE_ATTR, FIX_AVAILABLE_ONLY_ID, formatHtmlReport } from './html.js'; const TOKEN = ['ghp', '0123456789abcdefghijklmnopqrstuvwxyzA'].join('_'); @@ -293,8 +293,34 @@ describe('formatHtmlReport', () => { expect(again).not.toMatch(/]/i); expect(again).not.toMatch(/\ssrc\s*=/i); expect(again).not.toMatch(/
]*\\schecked`)); expect(again).toContain(``); expect(again.startsWith('')).toBe(true); }); + + it('ships an unchecked fix-available-only control and keeps every row in the page', () => { + const control = `

`; + const rule = `body:has(#${FIX_AVAILABLE_ONLY_ID}:checked) tr[${FIX_AVAILABLE_ATTR}="no"] { display: none; }`; + const empty = formatHtmlReport(artifact()); + const html = formatHtmlReport(sample()); + + expect(empty).toContain(control); + expect(html).toContain(control); + expect(empty).toContain(rule); + expect(html).toContain(rule); + expect(html).not.toMatch(new RegExp(`]*id="${FIX_AVAILABLE_ONLY_ID}"[^>]*checked`)); + + const vulns = section(html, 'vulnerabilities'); + const findings = section(html, 'findings'); + expect(vulns).toContain(`${FIX_AVAILABLE_ATTR}="yes"`); + expect(vulns).toContain(`${FIX_AVAILABLE_ATTR}="no"`); + expect(vulns).toContain('GHSA-crit'); + expect(vulns).toContain('GHSA-mod'); + expect(findings).toContain('vibgrate/runtime-lag'); + expect(html).not.toMatch(/