Linking a PR to a requirement

How Spectrace decides which requirement a pull request is verified against, how to write a reference that resolves, and what to do when it does not.

This page shows you how to make a pull request resolve to exactly one requirement, and what Spectrace does when it cannot.

Prerequisites

  • The repository is mapped to a Spectrace project. See GitHub App.
  • You know the requirement's reference number, for example sss-050. It appears on the requirement's page and in its URL.

The resolution ladder

When a pull request opens, Spectrace does not know which requirement it implements. The verification job works that out with a fixed ladder. The first rung that produces an answer decides; the ladder is not a vote.

Rung 0 honours a link an AI agent registered for the PR via MCP. Rung 1 reads references you wrote into the pull request. Rung 2 falls back to which requirement the changed files are already linked to. Last is "unmatched", which posts a comment saying why and stops before verification.

References are matched only against requirements in the project the repository maps to. A reference to a requirement in another project resolves to nothing.

An agent can register a pull request against a requirement with spectrace_pr_verifications_create (mode: "register", or an inline run that used the real PR number). The newest such link decides the requirement before anything else — it is the most deliberate signal there is, naming a specific requirement for a specific PR number.

Only MCP-created links qualify. Rows written by the webhook itself are its own past resolutions, and honouring those would pin a pull request to a stale guess after its branch or description changed. Placeholder pre-push runs (prNumber: 0) never qualify either, since real PR numbers start at 1.

When a registered link and an explicit reference disagree — the registration says one requirement, the branch or title names another — the registration wins, and the verification comment carries a "Conflicting reference" note naming the referenced requirement. The disagreement is surfaced, never swallowed. Traceability confirmation and drift are computed against the registered requirement exactly as they are for rung 1. See Verify before you push.

Rung 1 — an explicit reference

Spectrace scans three sources in order: the branch name, then the PR title, then the PR description. In each source it looks for tokens shaped like a requirement reference:

<project key>-<number>

The project key is 2–10 characters and starts with a letter; the number is 1–6 digits. Matching is case-insensitive, and the number is normalised by zero-padding to three digits, so SSS-50, sss-050, and the retired spelling RIP-SSS-050 all resolve to the same requirement sss-050. In a branch name, only the part after the last / is scanned, so a feat/ or fix/ prefix is ignored.

Each token is then looked up against the requirements that exist in the project. A token that matches nothing — utf-8, api-3, or a typo — is dropped. Walking branch, then title, then description, the first source that names exactly one existing requirement decides. This is why the VS Code extension's branch format, feat/{refNumber}-{slug}, resolves cleanly even when the description mentions related work.

References that resolve, assuming sss-050 exists in the mapped project:

WhereTextResult
Branchfeat/sss-050-webhook-consolidationsss-050
Branchfeat/RIP-SSS-050-routingsss-050
TitleImplements SSS-050sss-050
Descriptionfixes sss-50sss-050

References that do not resolve:

WhereTextWhy
Branchsss-050/webhookOnly the part after the last / is scanned, and webhook contains no reference.
TitleTidy my_sss-050 helperA token glued to a word character or hyphen on its left is not a reference.
Anysss-999 when no such requirement existsThe token matches nothing in the project and is dropped. If nothing else resolves, the comment names it as unknown.

Ambiguity

A single source that names two or more existing requirements is ambiguous, whatever order they appear in. Spectrace skips that source and continues to the next one. If a later source names exactly one requirement, that source decides.

If no source names exactly one requirement, the result is unmatched with the reason ambiguous_reference, and Spectrace does not use file traceability as a tiebreak. The fix is to keep only the primary reference in the branch name or title and move the other references into the description, which is read last. Spectrace verifies against one requirement per pull request.

The traceability cross-check

When rung 1 resolves a requirement, Spectrace also runs a second lookup in parallel: which requirements are linked to the changed files through traceability links — a stored link between a requirement and a file path, created when a file is linked to a requirement from the VS Code extension, by an AI agent calling spectrace_requirements_link_file, or automatically by a verified pull request (see the learning loop below). If the explicit requirement is among the top ten linked requirements, the comment's footer reads "Confirmed by file traceability".

If it is not, and traceability has a top hit for a different requirement whose links have been seen in the last 180 days, the comment gains a drift note naming that other requirement. The explicit reference still wins; the note is information, not a change of verdict. See Spec drift.

Rung 2 — file traceability

If rung 1 finds no reference at all, or only references that match nothing, the requirement with the most traceability links to the changed files wins. Rung 2 results carry no confirmation note and no drift note. Links of any age count here — with no explicit reference, a stale hint still beats no hint.

If two requirements are tied on link count, Spectrace does not choose between them. The result is unmatched with the reason ambiguous_traceability.

A rung-2 winner also passes a sanity check that the deliberate rungs never need: the pull request title's embedding is compared against the requirement's stored embedding, and when the two clearly do not relate, the inference is vetoed — the result is unmatched with the reason low_title_affinity, and the comment names the requirement the changed files pointed at so a genuinely related author knows exactly which reference to add. The veto can only prevent a wrong link, never create one: a requirement with no embedding, or a failed similarity lookup, means no veto, and rungs 0 and 1 are never second-guessed. This exists because file overlap alone once linked an unreferenced pull request to whichever requirement had most recently touched the same files, and a confident wrong verification costs more than an honest unmatched.

The learning loop

Verified pull requests teach rung 2. When a run that resolved through an explicit reference (or a caller-supplied requirement) completes with overall status satisfied or partial, Spectrace links the requirement to the changed files automatically: the 20 files with the largest changes, skipping deleted files. So a team that names requirements in branches or titles builds the file map as a side effect, and the occasional pull request with no reference starts resolving on rung 2 for free.

The loop is deliberately conservative:

  • Machine links are second-class. They carry confidence 0.9 — below the 1.0 of a link a person made deliberately — and metadata recording which pull request and verification produced them.
  • Re-pushes refresh, never duplicate. Verifying the same requirement and file again updates the link's last-seen time instead of creating a second row. A verified pull request touching a manually linked file refreshes that link's freshness too, without rewriting who made it.
  • Capped and pruned. A requirement holds at most 50 machine-written links; the least recently seen are pruned first. Links made from the VS Code extension or by an AI agent are never auto-deleted.
  • The loop never feeds itself. A pull request that was resolved by rung 2 writes no links — the system does not record its own inference as evidence. Unmatched and failed runs write nothing, and results judged not to implement the requirement (not_addressed, unclear) write nothing.

Unmatched

If no rung resolves a requirement, the run stops before verification. The verification is recorded with status unmatched, the check run concludes as neutral, and Spectrace posts a comment explaining what it saw.

The unmatched comment

The comment's heading and first line depend on the reason:

ReasonHeadingWhat it says
ambiguous_referenceSpectrace — which requirement?Lists up to four of the requirements the pull request references and asks you to keep only the primary one in the branch name or title.
reference_not_foundSpectrace — no requirement linkedNames up to three tokens that looked like references but match no requirement in the project.
ambiguous_traceabilitySpectrace — no requirement linkedThe changed files are linked to several requirements with no clear winner.
no_referenceSpectrace — no requirement linkedNothing reference-shaped was found.

The "no requirement linked" variants then list the three places a reference can go — branch name, title, description — and end by saying a change with no requirement, such as a dependency bump or a typo fix, can ignore the comment. Both variants end with a "Browse requirements" link to the project's requirements list (when the worker has NEXT_PUBLIC_APP_URL set; otherwise the link falls back to the pull request itself).

There is no screen in Spectrace for attaching a pull request to a requirement after the fact. The reference lives in the pull request, where reviewers on GitHub can see it.

The nudge can be turned off per project under Project settings → Verification, and never posts in checks-only or silent mode; the neutral check run still concludes whenever checks are enabled. See How PR verification works.

Runs started by the GitHub App webhook always resolve the requirement with the ladder above, from the pull request as it is at that push — which now includes rung 0, so a pull request registered against a requirement by an AI agent (spectrace_pr_verifications_create in register mode) is honoured on the App path and on the legacy per-repository webhook alike. A reference in the branch name or title remains good practice: it is visible to reviewers on GitHub, and it is what resolves the PR when no registration exists. See Verify before you push.

What done looks like

A pull request is linked when its verification comment is headed with the requirement's reference — for example sss-050 — Consolidate webhook routing — and the requirement's PR verification tab in Spectrace lists the pull request.

If instead the pull request receives "Spectrace — which requirement?" or "Spectrace — no requirement linked", read the reason in the first line, fix the branch name or title, and push again.