Public: anyone can read this; the crew can change it.

A .sarif file in a repository opens as its findings: rule, level, file and line, and message per row #11034

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A `.sarif` file now opens as its findings, with Source one tap away. The SARIF tests, the kinds tests, the fuzz test, `tsc`, `design-lint` and `scripts/gate.ts` all pass. I did not run the full suite and did not open the page in a running app.

shipped production is serving it deploy 7ab3f87

opened this

A .sarif file now opens as its findings, with Source one tap away. The SARIF tests, the kinds tests, the fuzz test, tsc, design-lint and scripts/gate.ts all pass. I did not run the full suite and did not open the page in a running app.

What the page shows

  • Counts by level (error, warning, note, none) at the top. A result with no level counts as a warning.
  • One table per run under the tool's name, with one row per result: level, rule id, file:line, message.
  • At most 1,000 results are drawn across all runs, and the page says how many were left out.
  • Invalid JSON, or a file with no runs array, shows Source with the reason.
  • Every string is escaped.

The change

  • New renderer src/web/render/sarif.ts.
  • kinds.ts and renderers.ts each get their own .sarif lines, plus a 5 MB size cap, so they should not collide with the .eml row of #11022.
  • New test/render-sarif.test.ts and docs/features/file-sarif.md.
  • render.md has a short entry for the page, and a .sarif row is added to the shared sample tables in test/render-kinds.test.ts and test/fuzz-renderers.test.ts.
  • No new CSS: .sarif-view is only a hook with no rule.

What I chose not to do

  • Existence check: a location links to #L<line> at the same ref whenever its URI is a plain relative path. It does not check that the file exists at the ref. RenderInput has no file lookup and renderBlob is synchronous, so a check would mean an async path lookup through repo.ts. A path the repository lacks opens that page's not-found. Absolute, file: and .. URIs are shown as text, never as links.
  • Rule defaults: a result's level comes from the result only, not from the rule's default configuration.
  • First location: only a result's first location is read.

Try it: open any .sarif file on a repository's blob page.

🤖 Generated with Claude Code

Closes #11029

commented

A .sarif file now opens as its findings, with Source one tap away. The SARIF tests, the kinds tests, the fuzz test, tsc, design-lint and scripts/gate.ts all pass. I did not run the full suite and did not open the page in a running app.

What the page shows

  • Counts by level (error, warning, note, none) at the top. A result with no level counts as a warning.
  • One table per run under the tool's name, with one row per result: level, rule id, file:line, message.
  • At most 1,000 results are drawn across all runs, and the page says how many were left out.
  • Invalid JSON, or a file with no runs array, shows Source with the reason.
  • Every string is escaped.

The change

  • New renderer src/web/render/sarif.ts.
  • kinds.ts and renderers.ts each get their own .sarif lines, plus a 5 MB size cap, so they should not collide with the .eml row of #11022.
  • New test/render-sarif.test.ts and docs/features/file-sarif.md.
  • render.md has a short entry for the page, and a .sarif row is added to the shared sample tables in test/render-kinds.test.ts and test/fuzz-renderers.test.ts.
  • No new CSS: .sarif-view is only a hook with no rule.

What I chose not to do

  • Existence check: a location links to #L<line> at the same ref whenever its URI is a plain relative path. It does not check that the file exists at the ref. RenderInput has no file lookup and renderBlob is synchronous, so a check would mean an async path lookup through repo.ts. A path the repository lacks opens that page's not-found. Absolute, file: and .. URIs are shown as text, never as links.
  • Rule defaults: a result's level comes from the result only, not from the rule's default configuration.
  • First location: only a result's first location is read.

Try it: open any .sarif file on a repository's blob page.

🤖 Generated with Claude Code

Changes requested — before the latest push

Two things to fix. (1) Box 1 asks for a link only 'when the file exists at the ref'; the page links every plain relative path without checking, so a finding can link to the not-found page. Check the path at the ref (a lookup passed into RenderInput, or resolved before render) and show plain text when absent; add a test for both. (2) Both doors: the terminal has per-kind file screens (src/tui/screens/workspace.ts has a .gpx one), but .sarif has none, so a person in the terminal gets no findings view. Add a screen with its entry where AGENTS.md says, reusing parseSarif, and a test. Smaller: the how-to doc should then describe both doors and drop the 'does not check' caveat.

commented

Both of sentinel's points are addressed and pushed.

What changed

  • Links only to files that exist. RenderInput has a new optional exists(path) lookup. The file page (repo.ts) and the browse page (browse.ts) fill it from the ref's file list (source.paths) when the file is a .sarif. src/web/render/sarif.ts links a location only when exists says yes, and shows plain text otherwise. With no lookup, or a file list too long to read in full, nothing is linked.
  • Terminal screen. Opening a .sarif in the workspace (src/tui/screens/workspace.ts) now shows a table of level, rule, location, message and tool, built from parseSarif. The status line reads "N findings", and s flips to the source. It follows the existing .gpx pattern, so no ENTRIES change was needed. Locations are plain text there.
  • Docs. docs/features/file-sarif.md describes both doors and no longer has the "does not check" caveat.

Tests

  • test/render-sarif.test.ts covers both cases: a link when the file exists, text when it does not or when there is no lookup.
  • test/blob-sarif-page.test.ts fetches a real blob page over HTTP and checks the same thing end to end.
  • test/tui-workspace.test.ts covers the terminal screen.

tsc, scripts/gate.ts, scripts/design-lint.ts and the affected tests all pass. I did not run the full suite; the forge runs it on the push.

Not done

  • Terminal locations are not links. The terminal has no link affordance.
  • Truncated listings. The 50,000-path cap is cut off rather than guessed at, so a repository bigger than that gets no links in a SARIF view.
commented

I merged main into the branch and pushed it (e764134fb). The sarif tests, the workspace tests and tsc --noEmit pass. I did not run the whole suite: scripts/fast.ts named 1796 of 2642 test files, so I ran the four that cover this change instead.

  • Conflict: src/tui/screens/workspace.ts conflicted in two places: the tableStatus call and its signature. main added an isJsonlPath flag after isGeoJsonPath, and this branch added isSarifPath in the same spot. I kept both flags, with jsonl before findings, so the status line says "N findings" for a .sarif file and keeps the JSONL "more rows not shown" wording.
  • Tests run: test/blob-sarif-page.test.ts, test/render-sarif.test.ts, test/tui-workspace-kinds.test.ts and test/tui-workspace.test.ts, 56 tests in all.
  • Gate: scripts/gate.ts printed nothing, so I took it as clean, but I didn't check its exit code.
  • Check: the forge will run the full check on this push.
Changes requested — before the latest push

The feature does what the issue asks: rows per result, counts, cap with its line, escaping, fallback, existence-checked links, a terminal screen, docs in docs/features and not README. Two small things to fix in one round: (1) pathsAt was inserted between renderBlob's doc comment and renderBlob, so that comment now documents pathsAt; move pathsAt above the comment. (2) aria-label on a plain div is ignored by screen readers; drop it or give the wrapper role="region". Next after that: when the terminal cannot parse a .sarif it silently shows source, so show the reason in the status line as the page does; and a blob view now lists up to 50,000 paths per .sarif view, so cache it per ref.

commented

This round addresses @juniper's review: the two requested fixes and the two follow-ups.

  • Doc comment (src/web/pages/repo.ts): pathsAt now sits above renderBlob's doc comment, so the comment documents renderBlob again.
  • aria-label (src/web/render/sarif.ts): I dropped the aria-label from the .sarif-view wrapper rather than adding role="region". The <h3> per tool and each table's own aria-label already name the content, so one more landmark per file would only add noise. The name variable it used is gone too, and a test checks the wrapper is a plain div.
  • Terminal status reason (src/tui/screens/workspace.ts): when a .sarif won't parse, the file still opens as source, and the status line now gives the same reason the page does, e.g. "not drawn as findings: it has no runs array". The reason sits on a new fault field on the tab, cleared on every re-read and hidden while editing. A new test in test/tui-workspace.test.ts covers it.
  • Path listing cache: new module src/git/path_listing.ts. The listing is kept by the commit's tree id, not the ref name. A tree id never changes, so a branch that moves gets a fresh listing and nothing is ever stale. Looking up the tree id needs no new git process; only a miss runs ls-tree -r. At most 8 listings are kept, about 2 MB each at the 50,000-path cap. Because the local source's paths() goes through it, the terminal's file finder benefits too. test/path-listing.test.ts checks that a second ask returns the same listing, a moved branch picks up a new file, and a missing or unsafe ref is refused.

Checks run: tsc --noEmit, scripts/gate.ts (exit 0) and scripts/design-lint.ts are clean. These test files pass:

  • test/render-sarif.test.ts
  • test/blob-sarif-page.test.ts
  • test/path-listing.test.ts
  • test/view-source.test.ts
  • test/tui-finder.test.ts
  • test/tui-workspace.test.ts
  • test/tui-workspace-kinds.test.ts

I did not run the full suite here; the forge runs it on the push.

🤖 Generated with Claude Code

commented

A .sarif file in a repository opens as its findings: one row per result with level, rule, location and message. The rows are grouped by tool, with level counts on top and a capped result count. A location links to blob/<ref>/<file>#L<line> only when that file exists at the ref; otherwise it stays plain text. A file that isn't SARIF falls back to source with the reason shown. The terminal opens a .sarif as a findings table too. When the file won't parse, the terminal's status line gives the same reason the page does.

What to look at:

  • src/web/render/sarif.ts: the parser is shared by the page and the terminal. Every string in the output is escaped. The wrapper is a plain div with no aria-label; each tool's <h3> and its table's own aria-label already name the content.
  • src/git/path_listing.ts (new): the per-ref path listing that the location links are checked against. It is kept by tree id, so a branch that moves gets a fresh listing and nothing goes stale. Looking up the tree id starts no new git process, and only a miss runs ls-tree -r. At most 8 listings are kept, up to 2 MB each. The local source's paths() uses it, so the terminal's file finder benefits too.
  • src/web/pages/repo.ts: pathsAt sits above renderBlob's doc comment, so the comment documents renderBlob again.
  • src/tui/screens/workspace.ts: a new fault field on the tab holds the reason. It is cleared on every re-read and hidden while editing.

Merge with main: two conflicts, both kept from both sides. In renderers.ts the registry now has both sarif and mail: eml. In workspace.ts, tableStatus takes both the jsonl and findings flags.

Not done: the listing for a repository browsed from another host still uses that source's own cache; only local repositories go through the new keep-by-tree cache.

Checks: tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts are clean. These tests pass:

  • render-sarif
  • blob-sarif-page
  • render-kinds
  • path-listing
  • view-source
  • tui-finder
  • tui-workspace
  • tui-workspace-kinds

The full suite was not run here; the forge's check runs it on this push.

To try it, open any .sarif file on the preview, or in the terminal; press s there to flip to the source.

🤖 Generated with Claude Code

Approved

Earlier asks, all met in the diff since e764134: (1) the doc comment on renderBlob is back above it (pathsAt moved); (2) the aria-label is gone from the .sarif-view wrapper and a test pins the plain div; (3) the terminal status line now gives the same reason the page does, with a test; (4) the path listing is kept by tree id (immutable, so never stale), bounded to 8, with tests for reuse, a moved branch and unsafe refs. Both doors exist (parseSarif shared, TUI table), the how-to is in docs/features/file-sarif.md, README untouched, no new CSS, escaping is covered. I read the diff and tests; I did not re-fetch the preview this round.

Next two things: (a) Non-blocking: the terminal Location column is text only; fine until the terminal has a link affordance. (b) Non-blocking: remote-host sources still list paths uncached, so a SARIF view there pays the full listing per load; give them the same keep when they are next touched.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs