Development record
An Issue Graduates Into An Intent Without Retyping
Press Release
Capture stops being a dead end. When a one-line issue turns out to be a
capability, abcd capture promote <iss-N> graduates it into an intent draft
without retyping — the minted draft carries the issue's text, the issue is
stamped with the itd-N it became, and the trail survives in both directions.
"I used to hesitate at capture time — issue or intent? Now I capture everything
as one line and promote the ones that grow up," says Iris, product lead.
Why This Matters
Step 2 of the twelve-step record walk — decide it is a capability — has no
verb. The schema already models the graduation: promoted_to (historical) is validated
against ^itd-[0-9]+$ and documented as "the itd-N this issue graduated into",
yet no verb writes it and no issue in the ledger carries it (refines the
promoted_to (historical) half of iss-245; the resolved_by half is a sibling intent). The
current commands/capture.md promote path is skill-orchestrated retyping with
no back-link. A native verb closes the forced intent-vs-issue choice the
"Which ledger?" note imposes at the moment of lowest information: capture now,
decide later, promote the ones that grow up.
The press release of the promoted intent is stated at its planning interview, not at promotion time — promotion mints a draft with the standard placeholder, so the promote moment stays one cheap command.
Scope Conditions
None stated.
Acceptance Criteria
BDD format, per the itd-1 discipline. Walked and confirmed by the maintainer, 2026-08-16.
- Given an issue in the ledger, when
abcd capture promote <iss-N>runs, then a new intent draft is filed underdrafts/— its slug reused from the issue's slug, its body carrying the standard placeholder Press Release section plus a by-id pointer ("Graduated from iss-N") and the issue's one-line summary, never a copy of the issue body (SSOT) — and the issue'spromoted_to(historical) is stamped with the minteditd-Nin the same invocation. - Given an issue in any status (
open/,resolved/,wontfix/), when promote runs, then the graduation succeeds and the issue keeps its folder — promotion is orthogonal to fix-status and is not resolution. - Given an issue already carrying
promoted_to(historical), when promote runs again, then the verb refuses and reports the existingitd-N— no duplicate drafts. - Given a malformed or unknown
iss-N, when promote runs, then it fails structurally with a diagnostic and nothing is written. - Given the two-store write, when promote executes, then it mints
the draft first and stamps the issue second; a failure after the mint reports
the orphan draft's path and the remedy —
capture promote <iss-N> --intent <itd-N>, a stamp-only mode that links an existing draft instead of minting (which also serves "I already filed the intent by hand; link them"). Concurrent ledger transitions serialize under the existing ledger lock. - Given the minted draft, then its frontmatter records the source issue (field named in the spec), so the edge is two-sided.
- Given
--json, then the result reports the issue id, the minted (or linked) intent id, and both repo-relative paths. - Given the sweep, then
commands/capture.mddocuments the native verb (replacing the skill-orchestrated-retyping paragraph),02-constraints/04-naming.mddrops the design-target marker, and the04-surfacesregistry reflectspromoteas shipped — coordinating with the sub-verb-table intent if its rows land first.
SOTA
Promote-with-link is a ubiquitous tracker pattern (Linear convert issue →
project, Jira "convert to epic", GitHub issue → discussion); nothing
importable —
abcd's record stores are native. Chosen path: bespoke, thin Go over
existing primitives, reusing the intent-create core function
(one-canonical-primitive) and the capture ledger's transition lock. No new
dependency.
Open Questions
None gating. Grill findings (two-store ordering, seed shape) were confirmed and folded into the criteria, 2026-08-16.
Audit Notes
Fidelity review — receipt rcp-1e7e37b98414 (verifier abcd:intent-auditor claude-fable-5-1).
Provenance: abcd:intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:708063e97662066d79a5984ff11edbbe2675de36fb27723003d684c4e05a742c Input attestations: diff:tree at da7b7cf409b41758b3502d3873687dc8b817d8c4 (worktree HEAD; the host supplied no commit range, so the whole tree at that commit is the delivered reality)@-;
Acceptance rollup: MET 7 · MET_WITH_CONCERNS 1 · NOT_MET 0 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET: Mint mode reuses the issue slug, seeds a by-id pointer plus the first body line (never the body), writes promoted_from, then stamps promoted_to in the same call; the test asserts every one of those facts on the minted draft and the stamped issue.
evidence: internal/core/capture/promote.go:198 — "slug := asString(fm["slug"])"
evidence: internal/core/capture/promote.go:200 — "seed := "Graduated from
" + req.ID + ": " + title +" evidence: internal/core/capture/promote.go:243 — "newContent, err := setScalarField(content, "promoted_to", rawScalar(itdID))" evidence: internal/core/capture/promote_test.go:107 — "if !strings.Contains(draft, "## Press Release") {" evidence: internal/core/capture/promote_test.go:120 — "if iss.PromotedTo != res.IntentID {" - ac-2 — MET: The stamp is an in-place atomic write to the file's current status directory and a dedicated test graduates issues from resolved and wontfix without moving them. evidence: internal/core/capture/promote.go:263 — "// In place, atomic — the file keeps its status directory (promotion is" evidence: internal/core/capture/promote_test.go:129 — "func TestPromoteWorksInAnyStatusAndKeepsFolder(t *testing.T) {"
- ac-3 — MET: A promoted_to already present refuses before the mint and again under the lock, naming the existing intent id; the test and the CLI surface test both exercise the second run. evidence: internal/core/capture/promote.go:157 — "return PromoteResult{}, fmt.Errorf("%s is already promoted to %s; refusing to promote twice", req.ID, existing)" evidence: internal/core/capture/promote_test.go:166 — "func TestPromoteRefusesAlreadyPromoted(t *testing.T) {" evidence: internal/surface/cli/capture_surface_test.go:303 — "// Second promote refuses (exit non-zero) and names the existing intent."
- ac-4 — MET: findIssue refuses a malformed id by shape and an unknown id by absence before anything is minted; the test proves zero drafts after each bad id and no stamp after a bad link target. evidence: internal/core/capture/alloc.go:410 — "return "", "", fmt.Errorf("invalid iss-N identifier: %q", issID)" evidence: internal/core/capture/promote_test.go:186 — "func TestPromoteUnknownOrMalformedIDWritesNothing(t *testing.T) {" evidence: internal/core/capture/promote_test.go:193 — "t.Fatalf("Promote(%q) minted a draft on a structural fault", bad)"
- ac-5 — MET: Mint precedes the stamp; a stamp failure returns the orphan draft path with the stamp-only remedy, --intent links an existing draft after verifying it exists, and the stamp runs inside withLedgerLock with a serialisation test.
evidence: internal/core/capture/promote.go:284 — ""%w — the minted draft %s (%s) is orphaned; complete the link with
abcd capture promote %s --intent %s --grounds %s"," evidence: internal/core/capture/promote.go:189 — "rel, ok := findRecordFile(repoRoot, intentStoreRelDirs(), req.LinkIntent)" evidence: internal/core/capture/promote.go:227 — "stampErr := withLedgerLock(repoRoot, issuesRoot, func() error {" evidence: internal/core/capture/promote_test.go:210 — "func TestPromoteStampFailureReportsOrphanAndLinkRepairs(t *testing.T) {" evidence: internal/core/capture/promote_test.go:276 — "func TestPromoteSerializesOnLedgerLock(t *testing.T) {" - ac-6 — MET: The draft skeleton writes a bare promoted_from line when the draft graduated from a record, the intent reader parses it, and the mint test asserts the line on the minted draft.
evidence: internal/core/intent/create.go:424 — "b.WriteString("promoted_from: " + opts.PromotedFrom + "\n")"
evidence: internal/core/intent/intent.go:80 — "PromotedFrom string
json:"promoted_from,omitempty"" evidence: internal/core/capture/promote_test.go:113 — "if !strings.Contains(draft, "promoted_from: "+issID) {" - ac-7 — MET: PromoteResult carries issue id, intent id and both repo-relative paths under json tags, the CLI renders it through the shared marshaller under --json, and the surface test decodes the envelope and stats both reported paths.
evidence: internal/core/capture/promote.go:44 — "IssueID string
json:"issue_id"" evidence: internal/core/capture/promote.go:293 — "IssuePath: fsutil.RepoRel(repoRoot, stamped.path)," evidence: internal/surface/cli/capture_surface_test.go:296 — "t.Fatalf("promote paths must be repo-relative and non-empty: %+v", r)" - ac-8 — MET_WITH_CONCERNS: The plugin page documents the native verb with no retyping paragraph and the capture surface's sub-verb table lists promote as shipped; concern: the naming register no longer carries a capture promote row at all (the only promote row left is the predecessor promote-check mode), so the design-target marker is gone by removal of the row rather than by editing it.
evidence: commands/capture.md:384 — "## Promote an issue into an intent"
evidence: commands/capture.md:394 — "One invocation mints a new intent draft under"
evidence: .abcd/development/brief/04-surfaces/06-capture.md:31 — "|
promote| — | shipped |" evidence: .abcd/development/brief/02-constraints/04-naming.md:186 — "|promote-check mode| **(predecessor vocabulary; superseded by the native spec store"
Gap audit:
- honoured:
- one invocation mints the draft and stamps the issue, with the trail in both directions evidence: internal/core/capture/promote.go:84 — "// Promote graduates an issue into an intent without retyping" evidence: internal/core/capture/promote_test.go:76 — "func TestPromoteMintsDraftAndStampsIssue(t *testing.T) {"
- the promote moment stays one cheap command; the press release is a placeholder filled at planning evidence: internal/core/intent/create.go:447 — "b.WriteString("> " + seedNote(opts) + "\n\n")"
- a post-mint failure names the orphan and the stamp-only remedy evidence: internal/core/capture/promote.go:283 — "return PromoteResult{}, fmt.Errorf("
- diverged:
- the naming register drops the design-target marker on capture promote
evidence: .abcd/development/brief/02-constraints/04-naming.md:186 — "|
promote-check mode|" evidence: .abcd/development/brief/02-constraints/04-naming.md:154 — "Where the machinery a row describes is a design target rather than shipped behaviour, the row says (staged)"
- the naming register drops the design-target marker on capture promote
evidence: .abcd/development/brief/02-constraints/04-naming.md:186 — "|
- missing: (none)
Frontmatter
| id | itd-119 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | major |
.abcd/development/intents/shipped/itd-119-an-issue-graduates-into-an-intent.md
open on GitHub ↗ · commit history ↗
References1
- itd-4Nothing You Notice Gets Lostbuilds on
Referenced by2
mentions11
- dcn-26092300000000232026-09-23 — The payload example in spc-2609231435545473 (closed) is stale on one point: its itd-121 quote opens `"... what I'd do next,"`, a truncation the release-page ingest refuses by design, since a quote is a whole quoted sentence as the press release has it. The example's verb is right: the ingest accepts an attribution after `says` as well as after `said`, because six intents of that release (itd-119, itd-120, itd-121, itd-123, itd-124, itd-125) quote as `says <Name>, <role>.`. The closed spec stays as written; `TestQuoteSaysFormFromARealRecord` carries a working `says` quote, taken from itd-121 as its record has it (lane implementer, on review 2 of the release-page build).
- iss-245The capture store's schema models resolution provenance it has no verb to write. ResolvedBy{Intent,Spec,Commit} (capture.go:72) is parsed on read (validate.go:275) but Resolve (workflow.go:162) writes only resolution and impact and exposes no flag to supply it, so a resolved issue can assert it was fixed in prose but cannot point at the intent, spec or commit that fixed it. PromotedTo (capture.go:92) is validated against ^itd-[0-9]+$ (validate.go:121) and documented in the issues README as 'the itd-N this issue graduated into', yet no verb writes it and no issue in the ledger carries it — so the capture-to-intent promotion the schema anticipates is unreachable, which is why the 'Which ledger?' note in commands/intent.md forces the intent-vs-issue call at the moment of lowest information with no way to revise it. Contrast the intent side, where the fidelity review binds its verdict to a sha256 receipt over the acceptance criteria: one record store, two very different evidence standards.
- iss-2609290448510918The predecessor-store qualifier sweep reaches past the six intents iss-2609261536147903 named: outside drafts/ and those six, 192 citation sites across about forty intents name a spc-N at or below spc-70 that is not the citing intent's own spec, with no '(predecessor store)' on the line. Some are live cross-references (the cold-reading family's intents cite each other's live specs), and some are the predecessor store's (itd-4, itd-6, itd-29, itd-47 and itd-49 describe pre-rebuild work in its terms), so each site needs the same reading against the live spec it collides with; the specs charter's Two spc-N Namespaces section is the rule. Found while sweeping the pattern of iss-2609261536147903 in lane drainDrift3; a census script over intents/{planned,shipped,disciplines} reproduces the count.
- iss-2610262034550860itd-4 has capture promote call intent new with the issue's body; the capture surface page says promotion mints the draft natively with a by-id pointer, not a copy.
- iss-2610262035091535itd-46 has promotion pass the issue text to the quoted-text create; the capture chapter has promotion mint a draft whose body is a by-id pointer, not a copy
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-120A Resolved Issue Points At What Fixed It
- itd-121Type The Id, Get Your Next Move
- itd-2609231013154443Every release arrives with a press release composed from what shipped
- spc-25a-resolved-issue-points-at-what-fixed-it-abcd-capture-resolv
- spc-2609212015054359drain-ledger-triage