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
@@ -488,6 +488,34 @@ async def test_fail_review_requires_at_least_one_issue() -> None:
assert "finding" in body["message"].lower()
@pytest.mark.asyncio
async def test_fail_review_rejects_prose_file_names_evidence_in_remediate() -> None:
qa_id = uuid4()
task_id = uuid4()
t = _qa_owned_task(task_id, qa_id)
task_svc = AsyncMock()
task_svc.get.return_value = t
task_svc.agent_for.return_value = _qa_agent_mock(qa_id)
journal_svc = AsyncMock()
journal_svc.has_learning_for_task.return_value = True
deps = _make_deps(task=task_svc, journal=journal_svc)
c = Choreographer(deps)
findings = [
{
"file": "PR #676 description",
"severity": "major",
"expected": "matches the acceptance criteria",
"actual": "diverges from the acceptance criteria",
}
]
env = await c.fail_review(qa_id, task_id, findings=findings)
body = env.as_dict()
assert body["error"] == "invalid_state"
assert "evidence" in body["remediate"]
assert "file" in body["remediate"]
@pytest.mark.asyncio
async def test_fail_review_not_assigned_returns_not_authorized() -> None:
qa_id = uuid4()