Jerry Orta
← Concepts

From Backlog to Merged PR

  • claude
  • tooling
  • workflow
  • process

A backlog item is two different things wearing one label. It is a status — where the work sits between conception and release — and it is a substance — the plan, the acceptance criteria, the reasoning that says what the work actually is. Most trackers store both in the same place, in the same field, and pay for the confusion later. The system described here refuses that merge: it keeps status in one store built for status, keeps substance in another built for substance, and lays a thin band of automation across the seam between them. Status stays in Jira. The plan lives in version control. A family of /epic-* skills reads both, does the work, and writes the status back.

This note completes a set. Three companion notes describe the substrates an agent here reads from: the skills are the actions it performs, the agent-files architecture is the internal context those actions are read against, and the pinned framework source is the external ground truth they are checked against. Each of those is a thing an agent consults at the scale of a single task. The subject here is the axis the others leave out — process: how the same substrates are composed across the whole life of a feature, from an unrefined backlog item to a reviewed and merged change.

The split — status in Jira, content in git

The central decision is to store status and substance in different systems, each chosen for what it is suited to. Jira holds status and almost nothing else: the workflow state a ticket occupies, its assignee, its transitions, its comments. What it deliberately does not hold is the ticket's real content — the description, the implementation plan, the acceptance criteria. That content lives in a separate repository under version control, as one markdown file per ticket at /.md. Jira keeps only a stub: a one-line summary and a link to the file on GitHub.

The reason is partly economy and partly principle. The economy is immediate: the Jira integration returns a multi-kilobyte envelope for every issue it reads and ignores requests to narrow the fields, so pulling content through it grows expensive the moment more than one ticket is involved. A markdown file fetched from git is a fraction of the size. The principle is the more durable half. A description held in Jira is a buried text field — unversioned, unreviewable, invisible to the tools that make code trustworthy. The same description held as a tracked file is diffable, reviewable in a pull request, and versioned alongside the work it plans. Moving content out of the tracker does not merely make it cheaper to read; it makes the plan a first-class artifact of the repository rather than a note pinned to a card. That content is, in shape, a Claude Code plan — a statement of intent, the surfaces the work will touch, and the criteria that will judge it — which is precisely the artifact a planning pass produces before any code is written, and precisely why it belongs under version control rather than in a tracker field.

The bridge — one script over the seam

A split is only workable if crossing it stays cheap. One script, scripts/jira_to_md.py, sits over the seam and exposes three public workflow verbs, one for each direction the work needs to move:

jira_to_md.py board  --jql '<jql>'   →  live status list, ~200 B per issue
jira_to_md.py ensure <KEY>           →  path to <board>/<KEY>.md, written on miss
jira_to_md.py stub   <KEY>           →  Jira description ⇢ one-line summary + GitHub link

board reads live status: given a query it returns a compact list of tickets and their current states — a few hundred bytes each rather than the full envelope — so status navigation stays fast. ensure reconciles content: given a ticket key it returns the path to the local plan file, fetching and writing that file only when it is missing, so reading a description is always a file read. stub closes the loop back to the tracker: it rewrites a ticket's Jira description down to the one-line summary and the GitHub link, so the tracker points at the file rather than duplicating it.

The three compose into how a ticket is born split. The key is minted through the Jira integration, the real content is written to /.md, and stub aims the freshly created description at it. From its first moment the ticket has its status in Jira and its substance in git, with nothing left to reconcile between the two after the fact.

The skills — one per stage

With status and content both addressable, the work itself is carried by a family of skills, each responsible for one stage of a story's life. Where a single skill encodes a single action, the /epic-* family encodes a sequence — the stages a story passes through, in order.

  • Plan. /epic-plan reads an epic's child stories, analyses how deep each one's routes reach and what depends on what, and orders them into waves. /epic-config views or edits the board configuration the rest of the family reads.
  • Triage. /epic-triage runs a fail-fast readiness check across the next several to-do stories, surfacing anything under-specified before the work starts rather than after.
  • Work. Two skills cover this stage in two modes. /epic-next picks and works the next unstarted story interactively, with a person in the loop. /epic-pipeline runs the same work autonomously across a batch, driving an external epic_runner process that carries each story from plan to branch without supervision.
  • Verify. /epic-cross-actor-test drives a realtime scenario across more than one persona, confirming that a change made by one actor propagates to another — the check a unit test cannot make.
  • Complete. /epic-story-complete commits the work, opens the pull request, and transitions the ticket, moving a finished story into review.
  • Maintain. /epic-evolution-audit looks sideways rather than forward: it finds sibling and downstream stories a just-finished change has left stale and applies the corrections, so a plan written days ago does not silently diverge from the code it once described.

Each skill reads status through board, reads content through ensure, does its stage of the work, and — where the stage changes state — writes status back to Jira and content back to git. The split is what lets the skills stay simple. None of them has to hold both the state of the work and its substance at once, because the two never lived in one place to begin with.

The loop, end to end

The pieces are easiest to see assembled. Consider an epic of related stories — for a concrete case, the very epic this note belongs to (ARCH-230), which groups a short series of documentation stories including this one. /epic-plan reads its children and orders them into waves; /epic-triage confirms the next wave is ready. For each story, work begins by reading the plan file through ensure — the same file a reviewer later reads in the pull request — and proceeds either interactively through /epic-next or autonomously through /epic-pipeline. When the work is done, /epic-story-complete commits it, opens a pull request, and moves the ticket to review. Every one of those stories was itself born split: minted in Jira, written to that repository, and stubbed, before a line of it was implemented.

Nothing in that loop asks the tracker to be a content store or the content store to track status. Each system does the one thing it is suited to, and the skills move between them along a seam that one small script keeps cheap to cross.

Why it holds together

Three stores, one seam, and a family of skills across it:

  • Status in Jira — the tracker holds workflow state, assignment, and transitions, plus a stubbed description that points at the real content rather than containing it.
  • Content in git — one markdown file per ticket, in its own repository, makes every plan versioned, diffable, and reviewable in the same pull request as the code it describes.
  • A script over the seamjira_to_md.py exposes three public verbs — board to read status, ensure to read content, stub to write the tracker's pointer — so crossing between the two stores is always cheap.
  • A skill per stage — the /epic-* family carries a story through planning, triage, work, verification, and completion, each stage reading status and content the same way and writing state back only where it changes it.

The principle underneath is separation of concerns applied to process rather than to code. A backlog conflates the state of work with its substance; drawing the two apart lets each live in the store built for it and lets the automation stay simple, because no skill has to be a tracker and a document at the same time. That is the workflow I hand to Claude Code: the skills are the actions, the agent-files are the context those actions read, and this is the process that sequences them into shipped work.