Field feature · illuminate rung

Thread Report

What SpecAssay pins to every pull request.

SpecAssay's per-push check, the Gate, can prove the thread holds. But a reviewer still has to read the diff, and a green check quietly borrows credibility for everything in it. So on every PR, SpecAssay posts one briefing that sorts the whole diff. What moved on the Golden Thread arrives pre-explained: each change tied to the statement of intent it serves, with the proof that answers for it one click away. What sits off the thread is flagged as having no story yet, so you look first where nothing has been vouched for. The briefing illuminates; it never blocks: a broken Gate is refused by a separate check.

The pull request

This walkthrough is a real PR on the bundled example app. Acceptance criterion AC-SYNC-02 (edits to different fields merge cleanly) had been riding as tracked debt: the team owed it a test and said so on an open task. This PR pays the debt: it adds the named test, closes the task, and drops in a small metrics module along the way. Four files changed, and at a glance, SpecAssay already sorts them against the thread:

tests/test_sync.pyadds test_AC_SYNC_02_…, the proofon the thread
specs/sync/tasks.mdcloses the debt task T005on the thread
src/sync.pycarries @covers AC-SYNC-02on the thread
src/metrics.pynew: event counters, no markoff thread

Two marks make that sorting possible: @covers AC-SYNC-02 is a one-line comment saying this code serves that intent, and test_AC_SYNC_02_… is the proof: a test named for the criterion it answers. And SpecAssay changes nothing about the PR itself: not its title, not its description, not its diff. It leaves the human's artifact alone and posts a comment beside it. What follows is that comment, verbatim, section by section.

The briefing it pins to the PR

1
🧵 Thread Report
🟢 Golden Thread intact

One line, one fact: does the thread hold. A passing Gate does not mean "everything is done"; it means no acceptance criterion is quietly unfinished. No colour on the word; the dot carries it, so it reads the same in a comment and here. What changed lives below.

2

What moved

AC-SYNC-02tracked-debtproven · test_sync.py sync.py

The debt was paid. On every run the Gate writes a trace-manifest, the record of every intent's status. SpecAssay compares the one from before this PR with the one after. Here one acceptance criterion moved from owed to answered-for. The ID links to its line in the registry; the trailing files are the on-thread changes that did the moving, its proof and @covers, each a click to the exact diff. New IDs, retired ones, and rows that gained a mark or a proof show here too.

3

Thread Status — SYNC

AC-SYNC-01proven
AC-SYNC-02proven◀ changed
+2 untouched backlog rows not shown.

The touched story, walked top to bottom, as it stands after this PR, with the moved row flagged. Untouched backlog rows, planned work this PR never moved, are hidden, with the count stated, never silently dropped, so the reviewer sees the live part of the thread, not the whole planning tree.

4

Off Thread

1 changed file sits off the thread — it changed, but nothing in it carries a mark tying it to an intent this PR moved:

This is the payoff, and only the marking makes it possible. Subtract every change that serves a named statement of intent, and this list is the remainder: the changes nobody has vouched for yet. No diff tool can hand you that list, because unaccounted-for is not a property of the diff alone; it is the diff read against a record of intent. metrics.py changed, but nothing in it touches an intent this PR moved, so SpecAssay surfaces it as a clickable spotlight straight to the diff hunk, and stops there. A legitimate refactor and unwanted scope look identical from here, so the machine refuses to guess. It hands the reviewer a spotlight, not a verdict.

One claim the report never makes: that the tests are good. It shows which test answers for which criterion and puts them one click apart; whether that test is a real proof or a rubber stamp is the reader's judgement. The report sorts and links. You judge.

And one section this PR never triggers: Intent Changed. It appears only when a PR restates an intent's wording: the ID holds, but the meaning moves, and the code and tests written against the old text now owe a re-confirmation. The report flags the criterion and points straight at them. The restatement itself is governed, too: an agent can reword a spec, but a person signs before it merges. See the PR for Intent walkthrough →

When the thread breaks

The green PR above is the happy path. But the report earns its keep loudest on the unhappy one. Here is PR #2: a developer marks the debt task done and moves on, but never writes the proof it promised. AC-SYNC-02 now has nothing standing behind it. The Gate refuses, and the report says so, in the same shape:

🧵 Thread Report
🔴 Golden Thread broken

What moved

AC-SYNC-02tracked-debtGAP

The break is illuminated, not hidden. An acceptance criterion slid from owed-but-tracked to a silent gap: no proof, no open TODO excusing it. The report posts this briefing anyway, headed broken, so a reviewer reads what broke in thread terms, not just a red ✕.

Post the report, then block: two steps, never one. The comment always posts, even when the Gate refuses; a separate CI step then re-reads the verdict and fails the check. The briefing illuminates; the check refuses. The comment is never itself the block.

Why a comment, and never a block

The report never fails the build. That's by design. Three postures, from always-on to rare:

The machine may only refuse what it can prove is a defect. For the rest, it makes a human look.

And it stays a comment: SpecAssay posts beside your PR, it never edits it. The diff is the record; the Thread Report is the reading glass.

This page is the walkthrough. The reference doc carries the technical depth: how it decides off-thread (the repo-vs-project path bridge, the reserved distance field), the offthread_ack: off · record · required config, and the CI wiring that posts the briefing then blocks separately.