Somewhere right now, a standup is stalled on a board that says In Progress for a feature that merged on Tuesday. Nobody lied. Someone just forgot to drag a card — because updating tickets is the unpaid ETL job of software development, and unpaid ETL jobs get skipped.
dev-process-toolkit is a plugin for Claude Code, Anthropic's AI coding agent. It already makes the agent write a spec and clear deterministic gates — a requirements doc with pass/fail acceptance criteria, then typecheck/lint/test where scripts decide, not the model — before it can earn a pull request. It also does the paperwork: the same workflow that writes the code moves the tickets — in Linear (the lightweight Jira alternative) or Jira itself — and then double-checks that the tracker didn't quietly ignore the update. Here's how that works, and why the hard part wasn't the API calls. It was the trust issues.
A ticket is a projection, not a chore
The core idea: ticket state is derived state. By the time you'd manually drag a card, the workflow already knows the answer. So it renders it, through the plugin's slash commands:
-
/spec-writecreates the ticket and writes the spec file — a markdown doc in the repo holding the feature's requirements and acceptance criteria. -
/implementclaims the ticket before touching code — In Progress, assigned to you. - The ticket carries those acceptance criteria as a checklist; as gate checks pass them, the workflow ticks the matching boxes.
-
/prflips it to In Review. - Landing the approved commit releases it — Done. (Archiving the finished spec re-runs the release as an idempotent backstop.)
At no point does a human update a ticket. The board is a rendered view of the repo. Your standup answers itself.
The filename is the binding
When a tracker is connected, the spec file for a ticket is literally named after it: specs/frs/STE-142.md (frs/ holds one file per functional requirement). No mapping table, no traceability matrix — the docs put it flatly: "the filename IS the binding."
Which leads to a rule with a scar behind it: create the ticket first, read back the real ID, then write the file. Never guess the next number. Trackers skip cancelled numbers and renumber things behind your back; an agent that confidently announces "this will be STE-143" ships that guess into filenames, acceptance-criteria IDs, and prose. The gate check — a numbered battery of small scripted assertions — has a dedicated probe that scans for exactly this, because an early session did exactly this.
No tracker? mode: none is the default: spec files get short ULID names (a ULID is like a UUID, but sortable by creation time), and nothing ever calls a tracker. Same workflow, minus the board.
Trust no write
Here's the founding war story of the whole integration. Linear's write API accepts any keys in its input and returns a success-looking response even for keys it doesn't understand. Back in April, the workflow set a ticket to In Progress, got back a cheerful response with status: "Backlog" tucked inside, and believed the transition had landed. It hadn't. Nothing errored. That's the worst kind of bug.
The rule ever since: after every tracker write, re-fetch the ticket and assert its timestamp actually advanced. If it didn't, that's a named error — TrackerWriteNoOpError — because the write silently did nothing.
Write-side ceremony isn't enough, though, so the gate check has read-side backstops. Probe #8 fails the gate if an archived spec's ticket isn't Done. Probe #14 fails it if an active spec's ticket isn't In Progress and assigned to you (with a documented exemption for the mid-milestone moment where the ticket is already Done). The docs have a name for the alternative — a lying tracker — and here a board that disagrees with the repo isn't a cosmetic problem. It fails the gate check exactly the way a red unit test would: the workflow stops until the board and the repo agree.
Two trackers, one four-operation abstraction
Every skill talks to trackers through exactly four operations: pull the acceptance criteria, toggle one checkbox, transition the status, upsert the ticket. Everything tracker-specific lives in an adapter — a markdown runbook plus a few pure TypeScript helpers, talking to the official Linear and Atlassian MCP servers (MCP is the protocol that lets an AI agent call external APIs as tools). Supporting your tracker means implementing four verbs; GitHub Issues is the worked example in the docs.
The adapters earn their keep, because Linear and Jira agree on almost nothing:
| Linear | Jira | |
|---|---|---|
| Write API | One tool does everything | A different tool per verb |
| Status change | Pass the state name | Fetch the workflow's transitions first, then match |
| Milestones | Native | No MCP support — the toolkit fakes them with labels, later upgraded to Epics |
| Checkbox round-trip | Re-normalizes your markdown | Returns - [x] as * \[x\]
|
| Deleting test data | Can't delete, can archive | Can't delete anything — teardown means label, query, and transition to Done, forever |
(Why does a ticket plugin need teardown at all? Because the self-tests create real tickets — more on that in a minute.)
And they share the deepest quirk: on both, a write can look successful while doing nothing. The abstraction's real job isn't hiding two APIs behind one interface. It's making two very different trackers equally distrusted.
When a human touches the board
Trackers have one more failure mode: teammates. If someone edits the acceptance criteria in the ticket while the repo has its own edits, the next time the workflow writes the spec back, it pulls both sides, classifies every criterion — identical, local-only, tracker-only, edited-both — and asks you about each one: keep local, keep tracker, merge, or cancel. No bulk shortcuts, no auto-merge. The plugin moves cards all day; deciding which human's requirements are correct is above its pay grade.
The self-tests hammer real trackers
Before each release, the plugin's smoke test drives the full workflow against a live tracker — "Real Linear writes are the point," say the docs; mocking would defeat it. A longer self-test — the conformance loop — goes further: it replays the same scenarios against Linear and Jira in parallel and dedupes whatever the two runs surface.
Those runs keep catching real things. My favorite recent one: prompted to run autonomously — no human in the loop to say no — the model skipped the clarifying question the workflow requires it to ask first and created a real Jira ticket before asking anything. (Hello, DST-49. Jira can't delete you, so you work here now.) Same species of failure as the magpie bypass from the workflow article — an earlier incident where the model, running autonomously, produced a pile of scaffolding before asking a single required question — and it's now pinned by its own gate probe, #72.
Try it
In a Claude Code session:
/plugin marketplace add nesquikm/dev-process-toolkit
/plugin install dev-process-toolkit@dev-process-toolkit
Then /dev-process-toolkit:setup — it asks for a tracker mode exactly once, and none is a first-class answer. Start there: the whole workflow runs without a tracker, on ULID-named specs. When you're ready, /setup --migrate switches to Linear or Jira and renames every active spec file to its new ticket ID in a single commit.
The tracker work has shipped under codenames like Bimodal, Symmetry, Runbook, and Reconciled. Read in order, the changelog is a couples-therapy arc between one plugin and two ticket systems.
Part of a series on engineering discipline for AI agents. Previously: I Built a Claude Code Plugin That Stops It from Shipping Broken Code (why deterministic gates exist) and Four Commands My AI Agent Can't Skip (the day-to-day workflow).



Top comments (0)