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
Check · Review · Land · Live done — Landed and live in production.
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
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
runsarray, shows Source with the reason. - Every string is escaped.
The change
- New renderer
src/web/render/sarif.ts. kinds.tsandrenderers.tseach get their own.sariflines, plus a 5 MB size cap, so they should not collide with the.emlrow of #11022.- New
test/render-sarif.test.tsanddocs/features/file-sarif.md. render.mdhas a short entry for the page, and a.sarifrow is added to the shared sample tables intest/render-kinds.test.tsandtest/fuzz-renderers.test.ts.- No new CSS:
.sarif-viewis 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.RenderInputhas no file lookup andrenderBlobis synchronous, so a check would mean an async path lookup throughrepo.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
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.
Both of sentinel's points are addressed and pushed.
What changed
- Links only to files that exist.
RenderInputhas a new optionalexists(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.tslinks a location only whenexistssays 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
.sarifin the workspace (src/tui/screens/workspace.ts) now shows a table of level, rule, location, message and tool, built fromparseSarif. The status line reads "N findings", andsflips to the source. It follows the existing.gpxpattern, so noENTRIESchange was needed. Locations are plain text there. - Docs.
docs/features/file-sarif.mddescribes both doors and no longer has the "does not check" caveat.
Tests
test/render-sarif.test.tscovers both cases: a link when the file exists, text when it does not or when there is no lookup.test/blob-sarif-page.test.tsfetches a real blob page over HTTP and checks the same thing end to end.test/tui-workspace.test.tscovers 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.
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.tsconflicted in two places: thetableStatuscall and its signature.mainadded anisJsonlPathflag afterisGeoJsonPath, and this branch addedisSarifPathin the same spot. I kept both flags, withjsonlbeforefindings, so the status line says "N findings" for a.sariffile 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.tsandtest/tui-workspace.test.ts, 56 tests in all. - Gate:
scripts/gate.tsprinted 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.
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.
This round addresses @juniper's review: the two requested fixes and the two follow-ups.
- Doc comment (
src/web/pages/repo.ts):pathsAtnow sits aboverenderBlob's doc comment, so the comment documentsrenderBlobagain. - aria-label (
src/web/render/sarif.ts): I dropped thearia-labelfrom the.sarif-viewwrapper rather than addingrole="region". The<h3>per tool and each table's ownaria-labelalready name the content, so one more landmark per file would only add noise. Thenamevariable 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.sarifwon'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 norunsarray". The reason sits on a newfaultfield on the tab, cleared on every re-read and hidden while editing. A new test intest/tui-workspace.test.tscovers 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 runsls-tree -r. At most 8 listings are kept, about 2 MB each at the 50,000-path cap. Because the local source'spaths()goes through it, the terminal's file finder benefits too.test/path-listing.test.tschecks 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.tstest/blob-sarif-page.test.tstest/path-listing.test.tstest/view-source.test.tstest/tui-finder.test.tstest/tui-workspace.test.tstest/tui-workspace-kinds.test.ts
I did not run the full suite here; the forge runs it on the push.
🤖 Generated with Claude Code
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 plaindivwith noaria-label; each tool's<h3>and its table's ownaria-labelalready 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 runsls-tree -r. At most 8 listings are kept, up to 2 MB each. The local source'spaths()uses it, so the terminal's file finder benefits too.src/web/pages/repo.ts:pathsAtsits aboverenderBlob's doc comment, so the comment documentsrenderBlobagain.src/tui/screens/workspace.ts: a newfaultfield 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-sarifblob-sarif-pagerender-kindspath-listingview-sourcetui-findertui-workspacetui-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
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.
A
.sariffile now opens as its findings, with Source one tap away. The SARIF tests, the kinds tests, the fuzz test,tsc,design-lintandscripts/gate.tsall pass. I did not run the full suite and did not open the page in a running app.What the page shows
file:line, message.runsarray, shows Source with the reason.The change
src/web/render/sarif.ts.kinds.tsandrenderers.tseach get their own.sariflines, plus a 5 MB size cap, so they should not collide with the.emlrow of #11022.test/render-sarif.test.tsanddocs/features/file-sarif.md.render.mdhas a short entry for the page, and a.sarifrow is added to the shared sample tables intest/render-kinds.test.tsandtest/fuzz-renderers.test.ts..sarif-viewis only a hook with no rule.What I chose not to do
#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.RenderInputhas no file lookup andrenderBlobis synchronous, so a check would mean an async path lookup throughrepo.ts. A path the repository lacks opens that page's not-found. Absolute,file:and..URIs are shown as text, never as links.Try it: open any
.sariffile on a repository's blob page.🤖 Generated with Claude Code
Closes #11029