Spec lifecycle and PR linkage
Close the loop between a spec, the work that implements it, and the PRs that ship it — so a reader can answer "is this built, and is it available to users?" without grepping prose.
Close the loop between a spec, the work that implements it, and the PRs that ship it — so a reader can answer "is this built, and is it available to users?" without grepping prose.
Today the chain spec → work → implementation exists in the data (work items carry source="SPEC-xxx", validated by SPEC-045's reference checks) but the completion half of the chain is unstructured:
Agents almost never link the PR back. CLAUDE.md tells agents to include PR: refrakt-md/refrakt#NNN in the --resolve body, but only 2 of 177 work items in plan/ actually contain a PR reference. The instruction lives inside a HEREDOC example, not as a standalone "MANDATORY" checklist bullet, and it's evidently being skimmed past.
PR data isn't structured. Resolution parses a PR: line out of the prose body, but it's not an attribute, so it can't be validated, can't be queried, and isn't rendered as a real link in the plan UI or MCP resources. There is no plan status rollup that says "SPEC-027 → WORK-093, WORK-094 → PRs #142, #156."
Specs have no terminal state. Spec statuses today are draft | review | accepted | superseded | deprecated. accepted answers "we agreed to do this," not "it's built" or "users can install it." For a project that publishes to npm via Changesets, the gap between merged and available is real — and specs (the closest thing to a user-facing "what was promised" surface) have nowhere to record it.
Structure what we want to query. PR references are the natural pivot for traceability rollups ("which PRs implemented SPEC-X," "what shipped in v0.11.4"). Burying them in prose makes them invisible to tooling; promoting them to an attribute makes them first-class.
Separate "built" from "shipped". The two questions deserve two states. implemented reflects engineering reality (code in main); shipped reflects user reality (released to npm). Conflating them either lies to users or under-reports progress to maintainers.
Manual transitions over coupled automation. Don't auto-flip shipped from milestone state or git tags in v1 — the coupling is brittle and the manual flip aligns naturally with the existing npm run release step. plan status can suggest transitions ("all linked work is done — consider marking implemented") without forcing them.
Carrot before stick on pr. v1 does not enforce a "must have pr when done" warning. Direct-to-main commits are a legitimate workflow for human contributors, and CLAUDE.md is the right lever for agent-authored items where the convention applies. Instead, lean on positive surfaces: plan status rollups make missing PRs visible structurally (a spec with five done work items but three PRs makes the gap obvious), without per-item warnings that either nag legitimate cases or invite an opt-out attribute zoo.
The instruction has to be loud. A line of guidance inside an example block has a 1% hit rate in practice. The completion checklist already uses imperative voice and "MANDATORY" framing — PR linkage belongs there.
| Rune | Attribute | Cardinality | Format |
|---|---|---|---|
work | pr | multi-valued, comma-separated | <org>/<repo>#<number> |
bug | pr | multi-valued, comma-separated | <org>/<repo>#<number> |
spec | released-in | single-valued | semver (e.g. v0.11.4) |
released-in is required when a spec is status="shipped" (validator errors otherwise). pr is optional and unvalidated for absence in v1 — the format of values is validated when set (malformed refs error), but a missing pr does not trigger a warning. See Open Questions for the future-enforcement discussion.
| Status | Meaning | Transition trigger |
|---|---|---|
implemented | All linked work items are done; code is in main. | Manual flip after the last work item lands. plan status may suggest the flip when all implemented-by work is done. |
shipped | Released to users in an npm version. | Manual flip after npm run release, paired with released-in="vX.Y.Z". |
Existing statuses (draft | review | accepted | superseded | deprecated) are unchanged. accepted → implemented → shipped is the new happy path; accepted → superseded and accepted → deprecated remain valid.
| Surface | Change |
|---|---|
plan.update / refrakt plan update | Accept pr and released-in as known attributes; existing --resolve flow unchanged |
plan.validate / refrakt plan validate | Error on malformed pr values; error on shipped spec with no released-in. No warning on missing pr in v1. |
plan.status / refrakt plan status | Roll up PRs per spec; suggest implemented flip when all linked work is done |
Resolution parser (plugins/plan/src/scanner-core.ts) | Continue parsing PR: line for legacy items, but the attribute takes precedence |
CLAUDE.md "MANDATORY: Work Item Completion Checklist" gets a new top-level numbered step (between current steps 1 and 2):
Set the
prattribute on the work item before marking it done:npx refrakt plan update <id> --pr "refrakt-md/refrakt#<number>"
The --resolve HEREDOC example keeps Branch: / PR: lines for narrative continuity, but the structured attribute is the source of truth.
The 175 legacy done work items without a pr attribute are largely recoverable from git history: every status flip went through a commit, and most of those commits sit under a merge commit whose subject reads Merge pull request #NNN from <branch>. A one-shot migration tool walks the history once and backfills the attribute, so plan status rollups are rich from day one rather than skeletal.
Tool: refrakt plan migrate pr-attrs [--apply] [--git], modeled on the existing refrakt plan migrate filenames command.
Algorithm:
work with status="done" (or bug with status="fixed") lacking a pr attribute, find the commit that introduced that status — git log -G '^status\s*=\s*"(done|fixed)"' -- <file> is a reasonable proxy.Merge pull request #(\d+), capture the PR number; the repo slug comes from origin.pr="<org>/<repo>#<num>" back to the work item file.Residuals: items the tool can't resolve are listed but not modified. The maintainer can fill them in by hand if a PR exists, or leave them alone — since v1 doesn't warn on missing pr, unresolved residuals incur no ongoing cost beyond a slightly less complete rollup in plan status.
Sequencing: run refrakt plan migrate pr-attrs --apply --git once before the first release that surfaces PR rollups in plan status, so the rollup is meaningful out of the gate. There's no validate-warn pressure on the timing.
work and bug runes accept an optional, multi-valued pr attribute matching <org>/<repo>#<number>spec runes accept implemented and shipped status valuesspec runes accept an optional released-in attribute (semver format)plan validate errors on malformed pr values (anything not matching <org>/<repo>#<number>)plan validate errors on status="shipped" specs that lack released-inplan validate does not warn on missing pr in v1 (deliberate — see Open Questions)plan status includes a "PRs" rollup per spec, listing the unique PRs across its implemented-by workplan status suggests the implemented flip when every implemented-by work item of an accepted spec is doneplan.update MCP tool exposes pr and released-in in its input schema with the same validationpr attribute, distinct from the --resolve examplesite/content/docs/plan/ describes the new statuses, the pr attribute, and the happy-path lifecyclePR: in the resolution body are not broken; the parser continues to read that line for backward compatrefrakt plan migrate pr-attrs (CLI + MCP) backfills pr on legacy done work / fixed bug items from git merge-commit history, supports --apply --git, and reports unresolved items without modifying themFuture: stricter enforcement of pr. v1 deliberately omits any warning for missing pr, on the grounds that direct-to-main commits are a legitimate workflow for human contributors and CLAUDE.md is the right lever for agents. If the convention stabilizes and plan status rollups show gaps the checklist isn't closing, revisit — likely as a --strict validate flag or a per-repo config opt-in (e.g. plan.requirePr: true) rather than an unconditional warning. Adding it later is strictly cheaper than removing a noisy warning that contributors have learned to ignore.
Multi-PR work items vs. multi-work PRs. A work item that took two PRs needs pr="x,y". A PR that closes three work items means the same PR ref appears on three items — fine, but it means the "PRs per spec" rollup should dedupe. Recommend: multi-valued attribute, dedupe in rollups, no constraint on multiplicity.
Auto-implemented flip. Tempting to auto-transition a spec to implemented when its last work item flips to done. Couples spec lifecycle to work-graph completeness, which is sensitive to broken refs and missing source links. Recommend: suggestion in plan status, manual flip — not automatic.
Agent session breadcrumb. Out of scope for this spec, but related: an optional Session: line in the resolution body could record the agent run that produced the work item. Owner-only by virtue of Claude Code session URLs being private; useful as an audit bookmark even if not as shared provenance. Track separately.
Migration false positives. The forward-walk-to-merge-commit heuristic can attribute a work item to the wrong PR if its status flip landed in a squash-merge alongside unrelated changes, or if the resolve commit was rebased on top of an unrelated merge. Mitigation: when multiple plausible PRs reach the resolve commit, skip the item and list it as unresolved rather than guessing. Accept some manual cleanup over silent misattribution.