SpecAssay field guide

Follow the thread

The SpecAssay field guide: a tour of SpecAssay, the trace-manifest, and Loupe, one screenshot at a time. Every capture is a live render of a real HomesFlow trace-manifest: samples/homesflow.trace-manifest.json for sections 1 to 5, and samples/HomesFlow.refused.trace-manifest.json, which the Gate refused, for section 6.

Why bother

Everyone can ship plausible code now. The hard part is proving it does what you meant: which intent it served, what build filled that intent, and what proof showed it was done. When that answer takes longer than a coffee, the Golden Thread, the chain from each requirement to the code and the test that answer for it, is already lost. Requirements tools like Jira and HP ALM tried to hold the map in a second system that always lagged the repo. This stack keeps the map in the repo and checks it on every push; the checker is called the Gate. That check belongs in CI, not only on the nice developer's laptop: a cowboy without SpecAssay installed can still push, and the CI Gate is the property line.

Pull any feature and show its intent → build → proof chain before the coffee cools. That is the whole pitch. Loupe is the glass. SpecAssay is the assay office. The trace-manifest is the hallmarked record you keep.

Spec Kit stays Spec Kit. No second career in requirements tooling. The interface is already a little fun; the docs should not scare you out of trying it.

For the workflow story see reading-a-manifest.md; for terms see ../presets/specassay/GLOSSARY.md.

Don't panic about the IDs

Eighty-two durable IDs in the sample can look like a lot. Most of them live in the PRD and the specs, the ordinary places intent already gets written down. You do not paste an ID onto every line of code. The ID prefixes are the ordinary requirement types wearing name tags: US- user story, FR- functional requirement, NFR- non-functional, AC- acceptance criterion. If you've written requirements, you already know these; "intent" is just the word for what they all are before the build serves them.

Leave a mark when you touch the work. If the code serves a named intent, one short comment: @covers …. If a task is still open against an intent, name it on the task: Carries:. Those marks are greppable; SpecAssay watches them; you do not memorize the grid.

What you are looking at in the loupe is living documentation: the record is wound from the repo itself (names, comments, tasks, tests). When the work drifts, the Golden Thread shows it. That is the point.

Where these pictures came from

Sections 1 to 5 are a real render of HomesFlow's live trace-manifest, the same file shipped as ../samples/homesflow.trace-manifest.json. No hand-edited JSON. HomesFlow is a small real app used as public evidence of the practice; the count of IDs is "a product with a PRD," not "a compliance mountain." They are taken by a script rather than by hand, so any of them can be taken again from one command and none of them can quietly stop matching the file it was shot from.

Section 6 is a render of ../samples/HomesFlow.refused.trace-manifest.json, which the released Gate refused for real. It was made on purpose, from a scratch copy of HomesFlow with one test renamed so that its acceptance criterion lost the test that named it, and the note beside it gives the commit, the Gate version, the row, and the Gate's verdict lines word for word. The refusal is honest; only the break was staged, and now the break is a file anyone can read rather than a scratch tree somebody remembers.

These three were the exception on this page until today: shot by hand on 2026-09-10 from a copy that was deleted afterwards, impossible to take again, and left behind when the viewer's own words changed. They are not an exception now. Every capture here comes from one command over a manifest that lives in a repository.

1. The top bar: did the Gate accept this manifest?

Overview with the Gate accepting the manifest

Before reading any row, the top bar answers the only global question:

2. The map: every requirement in its area

The map, grouped by area, with a status badge on each group

The same rows the list holds, grouped into the eighteen areas HomesFlow's IDs are named for: A11Y, ADMIN, AUTH, EDIT, and so on. Each group shows its row count and one badge per status present in it, so ADMIN reads 1 BACKLOG · 1 TRACKED DEBT · 1 PROVEN while A11Y reads 4 PROVEN and nothing else.

Four statuses do all the work on this page, and they are the same four everywhere: on these badges, in the All statuses filter beside the search box, and in the tiles along the top bar.

By Area beside the filter rolls the groups up a level; Scan with its − and + sets how much of each statement a card shows.

3. End to end: one requirement, three legs

The pane on the right opens on the whole field: one strand per requirement, eighty-two of them, each drawn top to bottom in the colors above. Click any row in the list and that pane walks the one row instead, and ‹ Back to field returns you to all of them.

A walked row has three legs, and the names are the three questions:

The third tab, Descent, does the same walk for every row at once, for reading a whole manifest rather than one thread of it.

@covers in one breath. A one-line mark, // @covers AC-GUEST-01, FR-HOME-03, naming which statements of intent this code serves. Leave it when you touch the file. The Gate reads it; SpecAssay never invents one for you. Loupe lists those hits under Code. The task-side twin is **Carries**: on an open checkbox. Missing or made-up IDs fail closed, so the habit stays light: mark what you meant, and the check keeps you honest.

Every claim in the walk can be opened to the file and line that backs it, the requirement included. The braided line down the left side is the Golden Thread itself. Its two states matter more than any color: solid means the Thread holds; frayed means it is broken.

A proven requirement, test and all

AC-GUEST-01 end to end, proven, with its named test

AC-GUEST-01 end to end: the statement with its registry line (HomesFlow.prd.md:262), six @covers marks each labeled by file and line, and a named test test_AC_GUEST_01_guest_fields_only at GuestTests.swift:10. Green nodes, solid braid. This is what "proven" means: a named artifact you can open, not "the tests passed once."

Note this proven row also carries an Open debt (Carries:) block, an XCUITest still open on T064. Proven with additional open work is a normal, honest state.

Tracked debt: amber, but nothing is hidden

AC-HOME-10 end to end, tracked debt, with the task that admits it

AC-HOME-10 is not done, and nobody is pretending otherwise.

Amber or blue without fray means incomplete but excused: amber for owed debt, blue for not-yet. Someone wrote it down, so the Golden Thread stays intact.

4. Anointed backlog: minted on purpose, carried by a TODO

US-SHARE-01 end to end, backlog, carried by an open TODO

Minting an ID is a promise, and that is a feature, not a trap. Name an intent only when you mean it; if nothing claims it yet, write one open TODO that carries it (anointed backlog, usually in specs/backlog/tasks.md). That is enough ceremony that fat-finger drift still fails, while an honest "build this soon" stays clean.

Here US-SHARE-01 shows exactly that state: a minted-ahead Owner story (a read-only procedure-share link), status BACKLOG, the registry line it was minted on, and an Open debt (Carries:) block naming the open TODO T901. Zero code, zero tests, and the Gate still accepts the manifest. Drop the TODO without picking up the work and the next Gate run refuses: registry, specs, and tasks must all name the same IDs. The Thread will not carry an unclaimed promise.

Filter tile Backlog reads 5: four planning-altitude stories plus the anointed US-SHARE-01.

5. Backlog altitude: waiting, not broken

US-EDIT-01 end to end, backlog, with no carrying task at all

Stories and features (US-…, FR-…, NFR-…) are covered through their child ACs, not by stapling @covers US-… onto a file. When nothing in source carries their ID, they ride as backlog: blue nodes, solid braid, and both lower legs say so in words, the Code leg "No own @covers (backlog altitude)" and the Test leg "No test of its own; at backlog, so not a silent gap." Silent-gap refusal is AC-only.

US-EDIT-01 is the picture directly above, and the one thing it does not have is the thing section 4 was about: no Open debt (Carries:) block, because a planning-altitude story waiting on its own ACs needs no TODO to excuse it. That block is the whole visible difference between these two states.

6. The Gate refuses: what frays and what does not

The refused manifest: Gate refused this manifest, one refusal, the GAP tile hot at 1

The same viewer over a manifest the Gate refused. Three things change at once:

The other tiles do not line up with section 1's, and that is not the refusal's doing: this manifest was captured two months later, from a HomesFlow that had moved on, so its green baseline was already different. Only the GAP tile and the row behind it are what the staged break did. The note beside the file carries both sets of numbers.

That hot GAP tile is the assay office's whole reason for being. A silent gap is gilt: work gilded to gleam like solid gold, with no proof underneath. The assay strips the gilding; the thread frays. (It carries the other reading too: the guilt of unfinished work nobody admitted.)

The refused row itself

AC-GUEST-05 end to end: code behind it, and a red Test leg reading no test and no declared debt

AC-GUEST-05 promises that a Guest who tries to mark a step complete is refused and the attempt is logged. One test answered for it. In the scratch copy that test was renamed, from test_AC_GUEST_05_guest_cannot_update_step to test_guest_cannot_update_step, and nothing else was touched: it still exists, still compiles, still passes. Only its name stopped carrying the ID.

That is the whole lesson of this section. Nobody deleted a test and nobody wrote a bug. A promise quietly ended up with nothing answering for it, and the row still looks busy: the Code leg carries both its @covers marks, ProcedureRepository.swift:6 and GuestTests.swift:4. The braid frays between Code and Test, and the Test leg says why in plain words: "No test, and no declared debt: a silent gap."

It lands at GAP rather than tracked debt because its carrying tasks are ticked. No test and nobody admitting one is owed is exactly what a silent gap is.

Proven rows stay proven in a refused run

AC-A11Y-01 end to end in the same refused manifest, still proven, with two named tests

AC-A11Y-01 in that same refused manifest: two named tests, so its Test leg stays green and its braid stays solid. A refusal elsewhere does not un-prove this row's evidence, and the Gate's verdict is about the manifest, not about every row in it.

Rules of thumb

You seeIt means
Solid braid, green nodesGolden Thread holds; a named test exists
Solid braid, amber nodeOwed — tracked debt, admitted on an open task
Solid braid, blue nodeNot yet — backlog altitude, or an honest-missing step
Open debt (Carries:) blockThe exact open task that excuses (or carries) the incompleteness
▸ file:line / ▸ test nameExpand to the actual source behind the claim
Frayed braidGolden Thread broken: silent gap, or the manifest was refused as a whole
Requirement, Code, TestThe three legs of a walked row, in that order, top to bottom
Red banner, hot GAP tileAt least one AC has neither a test nor admitted debt

One sentence version: green is proven, amber is owed debt, blue is not-yet, fray is a broken Golden Thread.

You do not have to start out totally getting all of this from docs. These samples are someone else's project; they can only mean so much. Your own work and your team's work, the stuff you've been watching and building; that's another thing entirely... Put the Loupe on it and watch it light up.

← Back to SpecAssay