Flow ids: F, D, X, L, W, I
F<n> is the n-th process in package order: the entry flow first when you arrive from Whole system and it is ticked, otherwise the sidebar order of the ticked processes. Inside a flow the counters run in document order, nested decisions included. F<n>.D<k> is the k-th .if; F<n>.D<k>.<slug> is one of its .then branches, where the slug is the label transliterated (ı and İ to i, ş to s, ğ to g, ü to u, ö to o, ç to c, other diacritics stripped), lowercased, with every run of non-alphanumerics turned into a hyphen, cut at 24 characters, never empty (a label with no letters or digits becomes branch), and two branches of one decision that collapse to the same slug get -2 and -3. So a .then Hayır becomes F1.D1.hayir, and a .then “Evet — 50.000 TL üstünde” becomes F2.D1.evet-50-000-tl-ustunde. F<n>.X<k> is the k-th .fail, F<n>.L<k> the k-th .back, F<n>.W<k> the k-th .wait and F<n>.I<k> the k-th .input.
Each branch also carries a happy flag: true when its own body holds no .fail, .back or .go and it is not nested inside a branch that does. A .do stays happy, because a call comes back. The flag decides which branches Milestone 1 builds and which go to Milestone 2, and it decides the journey the design brief walks: into the first happy branch of every decision, down to the terminal step. The set that needs a test, called the branch ids, is every .then branch, every .fail, every .back and every alternative section of a sequence; waits and inputs get ids for tables and screens but no acceptance line of their own.
Sequence ids: S, M, A
S<n> is the n-th ticked sequence diagram in sidebar order. S<n>.M<k> is the k-th message in reading order with fragments flattened, the same order .autonumber prints, so the number you see on the rendered diagram is the number in the id. Each message carries a kind: sync for ->, async for ->>, reply for -->. S<n>.A<k> is one alternative section: the first section of an .alt (kind alt), each of its .else sections (kind else), an .opt and a .break. A .loop, .par, .critical or .group gets no id, because it shapes timing, not outcome. A message inside a section remembers the innermost one, which the ID INDEX prints as “(inside S1.A2)”.
The contracts table is built on these ids, one row per message, and the row's other columns are read off the diagram's structure: the failures of a request are the .else sections of the next .alt at the same level and every .break below it, however many messages sit in between, because a .break interrupts the whole level; replies in between are skipped, so a request-reply-alt diagram puts the failure on the request; the retry policy is the label of the innermost .loop, the parallel marker the innermost .par, and the note is the nearest adjacent .note, where a note written right after a fragment belongs to the fragment's last message. A callee whose .actor is declared external is marked to be stubbed in tests. None of this is typed by the model.
Spec ids: US, AC, E, SC, R, NF, M
The spec-side ids come from the object the model returns, but their numbers come from position, whatever the model wrote. US-<n> is the n-th user story and US-<n>.AC<k> its k-th acceptance criterion; an id the model typed is replaced by the positional one in every file. E-<Entity> is E- plus the entity name with spaces and punctuation removed and casing kept, so Sipariş Kalemi is E-SiparişKalemi, never E-Sipariş Kalemi; fields are cited as E-SiparişKalemi.adet. SC-<n> numbers the spec's screens first, then the screens Runic derives for flows the spec gave none. R-<slug> is the role: the model's id slugged, else the role name slugged, else role<n>, so “R-saha elemani” and R-saha-elemani meet in the matrix. NF-<n> is the n-th non-functional row, and the WCAG 2.2 AA row Runic appends when no row mentions WCAG takes the next number. M<n> is the milestone: the spec's id when it already has that form, else the position, so “Milestone 1” renders as M1 and the plan's headings stay parseable.
The model sees every flow and sequence id before it writes, under “ID INDEX (copy these ids exactly)”: one line per id with the text it names, indented, such as F1.D1 = .if Tutar limit içinde mi? and F1.D1.evet = .then Evet, S1.M2 = Uygulama → Banka: ödeme isteği (inside S1.A1). The prompt tells it that every id it writes is copied from the index character for character, never invented or renumbered. Where the model cites ids: a criterion's covers list, a transition's by, a permission subject that is a gating decision, a screen action's target and the screen's flow, a milestone's scope and acceptance, and an NF row's SLI naming the message it measures.
Why tests and commits cite them
The ids are the join. An acceptance criterion says which branches and messages it proves through covers; a state transition names the branch that performs it; a milestone lists the ids it closes and its /goal line names the tests that must pass; a screen action names the branch it triggers; the design brief names artboards SC-n and states by branch id; the decisions log has a column IDs affected. CLAUDE.md turns that into a working rule: “Tests are named after the id they prove (F1.D2.evet, F1.X1, S1.M3, US-4.AC2); a test name without an id is a smell.”, commits read m<n>: <summary> (US-…, F…), and the PR body is the milestone's acceptance checklist, ticked. The kickoff's step (a) asks for one test per acceptance line and per branch id before any code, and step (d) has a fresh subagent check that every listed branch has a test.
The ten quality tells join on the same ids: branch coverage checks that every branch id is cited by some covers list, milestone closure that every branch id, criterion and NF id sits in exactly one milestone block, message coverage that every S-message has a row and every A-section is mentioned, field closure that every E-Entity.field cited exists and every field is cited somewhere, and verbatim consistency that an id-shaped token in the plan or the brief exists somewhere. A test named F1.D2.evet that goes red therefore points at one .then branch in one file, and a commit that cites US-4.AC2 tells a reviewer which Given, When, Then it claims to satisfy.
Where to find an id
docs/spec.md lists every flow id under Flows in a table of ID, Kind and Text, with the source below it, and every message and section under Contracts. The Data model gives the E- ids, the User stories the US and AC ids, the Screens the SC ids, the Roles × permissions matrix the R- ids in its first column, and the Non-functional requirements table the NF ids. docs/plan.md repeats the ids per milestone as checklists. The design brief marks each journey with its branch ids in parentheses, “→ (F1.D2.evet) Tahsilat kaydedilir”, and heads each embedded flow with its F id and file path. .claude/rules/runic-dsl.md carries the legend so Claude Code can read an id it meets in a test name. The Runic editor itself does not show these ids; they exist in the package, and the audit's line numbers are what the editor cites.
Limits and gotchas
Ids are positional, so they move when the input moves. Untick a process and every process after it renumbers; insert an .if above another and the D counters shift; rename a .then label and its slug changes; reorder the sidebar and the F numbers follow, unless a seed pins the entry flow first. The same documents in the same order always give the same ids, which is what a re-export relies on, but a package built after an edit is a different package: diff docs/spec.md against the committed one before pointing Claude Code at it. Branch slugs are cut at 24 characters and a -2 suffix lands after the cut, so two long labels that differ only past that point become F1.D1.<same-slug> and F1.D1.<same-slug>-2. The spec-side ids depend on the model listing stories, screens and entities; a story it did not write has no US id, and a flow the spec gave no screen gets a derived SC id after the spec's own. An id-shaped word you typed yourself, such as a step reading “F9 formunu doldurur”, is excused by the verbatim tell and is not an id.
How to use it
- Read an id right to leftF2.D1.hayir: the hayır branch of the first decision of the second flow. S1.A2: the second alternative section of the first sequence. US-3.AC2: the second criterion of the third story.
- Find it in the specOpen docs/spec.md and search for the id. Flow ids sit in the Flows tables, message and section ids in Contracts, entity, story, screen, role and NF ids under their own headings.
- Find it in the flowThe Flows table gives the Kind and the Text of the line; the fenced source below it, and the same file under docs/flows/, is where that line lives. The design brief embeds every flow again.
- Name the test after itOne test per branch id and per criterion, named F1.D2.evet or US-4.AC2, as CLAUDE.md and the kickoff ask. The milestone's /goal line lists the names an unattended run must turn green.
- Cite it in the commitCommit as m<n>: <summary> (US-…, F…) and paste the milestone's acceptance checklist, ticked, as the PR body. Log any decision in docs/decisions.md with the ids it affects.
Commands & shortcuts
| F<n> | The n-th process in package order, entry flow first when seeded. |
| F<n>.D<k>.<slug> | One .then branch of the k-th decision: slug from the label, transliterated, lowercased, hyphenated, at most 24 characters, deduped with -2 and -3. |
| F<n>.X<k> · L<k> · W<k> · I<k> | The k-th .fail, .back, .wait and .input of the flow, in document order. |
| S<n>.M<k> | The k-th message of the n-th sequence in reading order, fragments flattened, with a kind of sync, async or reply. |
| S<n>.A<k> | One .alt, .else, .opt or .break section; .loop, .par, .critical and .group get none. |
| US-<n>.AC<k> | The k-th criterion of the n-th story, numbered by position whatever the model wrote. |
| E-<Entity> | E- plus the entity name without spaces or punctuation, casing kept; fields as E-<Entity>.<field>. |
| SC-<n> · R-<slug> · NF-<n> · M<n> | Screens (the spec's first, then derived ones), roles, non-functional rows (the WCAG row appended last) and milestones. |
| ID INDEX | The block the spec prompt receives: one line per flow and sequence id with the text it names, computed on the uncut documents in package order. |
Tips
- Freeze the documents once Claude Code has started. An edit that inserts a decision renumbers every id after it in the next package, and tests named after the old ids point at the wrong lines.
- Label branches with words, not punctuation. A .then label of only symbols becomes F1.D1.branch, and the second one F1.D1.branch-2, which no reader can tell apart.
- Write the outcome in the .then label. “Evet” gives F1.D2.evet; “Evet — limit içinde” gives F1.D2.evet-limit-icinde, which reads as the state it names in a test list.
- Declare external actors with .actor … external in your sequence diagrams. That is how a contract row knows the callee must be stubbed in tests and how CLAUDE.md lists the external systems.
Questions
Who assigns the ids?
Runic's parser, in code, from the ticked documents in package order. The model receives the finished index and copies from it; it never numbers anything. The spec-side ids are numbered by position from the object the model returns.
Why are there no ids in the editor?
The ids belong to a package, which depends on which documents are ticked and in what order. The editor's audit cites line numbers instead; the spec's Flows table maps each id to the line's text.
What is a happy branch?
A .then branch whose own body holds no .fail, .back or .go and that is not nested inside a branch that does. A .do stays happy. Happy branches are Milestone 1; the rest are Milestone 2.
Why is the Turkish letter transliterated instead of dropped?
So the id still reads as the branch it names: Hayır becomes hayir, not hayr. File slugs use the same rule, so Ödeme API Akışı is odeme-api-akisi.runic and a search for odeme finds it.
Are the ids the same over MCP?
Yes, the same code assigns them; export_build_package numbers the documents it receives in your sidebar order, processes first, and the build-spec prompt tells the client's model to use the same forms.