fix(findings): path-shaped file refs + per-round collapsible findings (#687)

* fix(findings): enforce path-shaped file refs; group panel findings by round

- The findings chokepoint rejects a file that is not a repo-relative
  path shape (prose like a PR reference validated before, and the panel
  then rendered a doomed file-content fetch for it) — narrative belongs
  in evidence, the remediate says so.
- The task-detail Findings tab groups findings into per-round
  collapsible sections (newest expanded) and only attempts a code
  snippet for a path-shaped file ref, so historical prose refs render
  as plain metadata instead of a broken loader.

* fix(findings): admit client-repo path conventions; teach the file-less option

- The shape gate reviews arbitrary client projects, not just this repo:
  plus and at-sign join the character class so SvelteKit route files,
  @types dirs, and @2x assets stay citable. Spaces stay excluded — they
  are the prose signal.
- The file-rejection remediate names the file-less option for
  cross-cutting findings.
- The client mirror notes its deliberate non-ASCII divergence from the
  server gate (unicode server-pass renders snippetless, fail-open).

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-24 17:09:49 +02:00
committed by GitHub
co-authored by Renn F
parent 21910d75ea
commit 4b2546ae19
9 changed files with 302 additions and 52 deletions
@@ -346,6 +346,51 @@ def test_finding_accepts_dot_segment_and_double_dot_substring() -> None:
assert ok.file == "./roboco/services/foo..bar.py"
def test_finding_rejects_prose_file_with_spaces() -> None:
# Live bug: a finding's `file` carried a PR reference ("PR #676
# description") instead of a path — it validated, and the panel then
# tried (and failed) to fetch a git blob literally named that.
with pytest.raises(ValidationError):
Finding.model_validate(_finding(file="PR #676 description"))
with pytest.raises(ValidationError):
Finding.model_validate(_finding(file="the description in the PR"))
def test_finding_accepts_real_nested_path() -> None:
ok = Finding.model_validate(
_finding(file="roboco/services/gateway/choreographer/findings.py")
)
assert ok.file == "roboco/services/gateway/choreographer/findings.py"
def test_finding_accepts_nextjs_route_group_and_dynamic_segment_paths() -> None:
# This repo's own tracked tree uses parens (route groups) and brackets
# (dynamic segments) in real, common paths — the shape gate must not
# reject them.
ok = Finding.model_validate(
_finding(file="panel/src/app/(dashboard)/tasks/[taskId]/page.tsx")
)
assert ok.file == "panel/src/app/(dashboard)/tasks/[taskId]/page.tsx"
def test_finding_accepts_client_repo_path_conventions() -> None:
# Findings reference paths in arbitrary reviewed projects, not just this
# repo: SvelteKit route files (+), @types dirs and @2x assets (@) are
# real, common tracked paths a reviewer must be able to cite.
for path in (
"src/routes/+page.svelte",
"src/@types/foo.d.ts",
"assets/logo@2x.png",
):
ok = Finding.model_validate(_finding(file=path))
assert ok.file == path
def test_finding_file_none_bypasses_the_shape_gate() -> None:
f = Finding.model_validate(_finding(file=None))
assert f.file is None
def test_finding_rejects_non_positive_line() -> None:
with pytest.raises(ValidationError):
Finding.model_validate(_finding(line=0))