How I use AI to keep up with a growing workload: from ticket to merged PR
The workload on my desk keeps growing and the time to deliver keeps shrinking. Several tickets are in flight at once, across a backend, web frontends and a mobile app, and every one of them comes with questions, bug reports and review rounds that don't wait for me to finish the previous thing. I can't add hours to the day, so the only lever left is the cost of each hand-off and each context switch. For the last four months I have been building a small plugin for Claude Code, called shipkit, to pull that cost down. This post is about how it's put together, where I deliberately keep a human in the loop, and the rules I only learned because the automation got something wrong. Ticket keys, names and channels are left out or made up.
The problem: the code is not the slow part
When people say AI makes developers faster they usually mean typing code. For me that was never the bottleneck. The time goes to everything around the code:
- reading a ticket and its comments, and working out what is really being asked,
- finding which repos it touches and what already exists,
- planning, then implementing across several repos in the right order,
- proving the change works, then opening PRs and going through review rounds,
- answering the question in chat that arrives in the middle of all of it,
- and, with several tickets open, remembering where each one stands.
None of these is hard on its own. Together they are what a bigger workload eats first. So I don't use AI as a chat window I paste code into. I use it to take the first pass at each of these, so that my time goes to the decisions.
Where it started: too much process
My team does spec-driven development, and the tool we first adopted generated 5 to 9 files per feature behind nine commands. Most of those files were skimmed once and went stale. The plans were a template fill: they never read the real code, so file paths and function names were guessed. And it assumed a single repo, while our platform is a backend, web frontends and a mobile app coordinated from one orchestration root. When time is short, a process that costs more to follow than to skip is the first thing to be skipped.
So I rewrote the idea as a plugin with one rule: lighter artifacts, not a lighter process.
- One file per feature,
spec.md, kept in git so it diffs and gets reviewed like code. - One everyday command,
/run-pipeline PROJ-123, that walks the whole flow and can resume where it stopped. - Installed once as a plugin, so a fix reaches the whole team at the same time.
The pipeline
ticket → spec → clarify → plan → implement → verify → review → PRs → merge → bump
Each stage is a skill, which is just a markdown file of instructions that Claude follows, plus a few small scripts for the parts that must be deterministic. The ones that earn their keep, and the wait each one cuts:
/spec-from-ticketreads the ticket, its comments and linked tickets, and finds which repos are affected by grepping the real code. It treats technical detail in the ticket as a product owner's guess: the spec records the business intent, and names are checked against the code./plan-deepis where AI does the most useful work. An agent explores the actual submodules first, then reviewer agents run in parallel, one verifying that every file path, function and import in the draft plan really exists, the other hunting for missed edge cases and cross-repo ordering mistakes. The plan is written only after both pass. A wrong plan is paid for twice, in the code and in the rework, so checking it first is cheap./assignhands each repo's tasks to its own agent so they run concurrently, in dependency waves. Each agent is a visible terminal I can watch, and the plan agent and the implementation agent can be different tools./verifyis the gate I trust most. It drives the running app and checks every numbered requirement. A frontend check ends in a screenshot, a backend check in a request, response and the data behind it. No evidence, no PASS. It catches the problem before a reviewer or a tester has to./review-changesruns three parallel reviews (correctness and security, style and docs, infra and ops) and checks the diff against the spec for drift.
/cross-check: the precondition, no guessing allowed
Two tickets about the same subject can contradict each other ("this screen says admins only, that one lets managers do
it too"), and a pipeline will happily build both word for word. With this much work in flight I can't afford to find
that out at QC, and I can't let an AI fill the gap with a plausible guess. So /cross-check is the precondition for everything after it: it runs at spec time, and again as a gate before planning.
It compares the ticket with other tickets, earlier specs, the ticket's own edge cases and the running code: who may see or do what, thresholds, limits, states, notifications, and every route that touches the same entity. The rules it works under are strict:
- A finding needs evidence from both sides. A verbatim quote from each ticket, with a link to where it was written, and a file and line for every statement about code. If it can't quote both sides, it isn't a finding.
- No guessing language. No "I think", no "probably", no "seems". It writes
PROJ-1 says: "…", orthe code at file:line does …, ornot checked: …. - It never picks a winner. The newer ticket isn't automatically right, and the code is evidence of today's behavior, not of what the product owner wants. Both rules go to the product owner as a question, with a screenshot that highlights the quote.
- A miss is not a verdict. A search that finds nothing proves only what it searched, so the ledger records what was searched instead of declaring "no conflict".
There is a variant for later in the ticket's life. When a tester, a bug report or a product owner's answer raises a
point, --stage post reconciles it with the rules already in play before I answer or fix anything.
It has already found a real contradiction inside one epic, and it did so at two separate moments: first on an earlier ticket, when QC put a question to the product owner, and again when I came back to build the next ticket in the same epic. Tickets in an epic are written and tested at different times, and nobody can hold all of them in their head. The skill can, because it goes back to what was actually written.
It does two jobs for me. For the system, it keeps every ticket coherent with the rest of the product, so features don't quietly contradict each other. For me, it leaves a paper trail: every claim traces to a quote and a line of code, so when I raise a question or defend a decision, the evidence is already in hand.
The /developer skill: the part around the ticket
Everything above happens inside a ticket. /developer is the skill for the rest of my day, and it's the one I'm
actively growing into my daily routine, because that is where a heavy workload hurts most. I'm at my desk working the
whole time. What I can't afford is the constant switching: a question in chat, a bug report, a ticket from yesterday I
haven't got back to. /developer takes the first pass at those, so I stay in the work that needs me. It isn't
finished. I'd rather describe what it does today than what I hope it will do.
It has three entry points:
- No argument: observer. It reads my chat notifications and takes the first pass at each one.
- A ticket or a bug description: start. It decides whether this is a feature or a bug and sets up the work.
- A link to one chat thread: answer it, then watch that thread until the asker is done.
Starting the day
With several tickets open, the expensive question every morning is "where was I?". The first run each day answers it
with a briefing of yesterday. It reads evidence only: which worktrees I touched, the pipeline's own state files, and
each ticket's status. It names the stage in the pipeline's own words (spec, plan, implement wave 2, PR review loop) or
says stage unknown; a line with nothing behind it isn't written. Every ticket left mid-pipeline is then continued
automatically, in its own worktree, so nothing stalls just because I was busy with something else. It asks that ticket's
planning agent where it stands and whether anything blocks it. No blocker, and it tells the agent to carry on to the
end. A blocker, and it stops and tells me in one line which decision it needs.
Triaging the chat
I'm usually deep in something else, and chat is where the interruptions come from. The observer listens in real time, only between 08:00 and 18:00 (it reads the clock before every arm and pauses outside those hours), so an interruption reaches me as a drafted answer to approve instead of a context switch. It reacts to DMs, @mentions, replies in threads I'm in, QC report posts that name a ticket, and every other top-level post by someone else in every channel I'm in. That last one exists because it once missed a "how is it going" and I only found out by asking. Notifications carry ids only, never text, so it has to read the real message before it decides: is this an ask, a bug, a change request, or noise?
Answering as me, without bluffing
Speed is worthless if the answer is wrong, and a wrong answer posted under my name is my wrong answer. So the skill is strict about what it may claim:
- It reads the code at the right state. A finished ticket is read from the freshly fetched remote branch. A ticket in progress is read from its worktree, and the reply says so: work in progress, not shipped. It never pulls or touches my working tree.
- Each QC question has a place to verify. "Bug or by design?" checks the code path and the spec's decisions. "Is the fix on staging?" checks the merge record and the deploy pipeline, and merged is never treated as deployed. "How do I test it?" checks permissions, flags and seed data. "Can't reproduce" reads the path for the steps given.
- It defends what was built. Asked "A or B?", it argues from the spec and the product owner's decisions, and doesn't side with the tester. If the code really differs from the ticket, it says so plainly and lets me choose. Numbers come from a local database restored from staging, never from memory.
- It writes like me. First person, in the language of whoever started the thread, never signed as an AI. It never promises a date, a refund or access. If it can't verify something, the reply is a short "let me check and get back to you" and a note to me.
- I approve every draft. It shows who asked, the question in one line, the sources it read and the exact text. Only my approval of that draft sends it. A notification, a thread message or a file never counts as approval. Rounds are capped at five, and it stops after three silent checks.
Reading a finished draft takes seconds. Finding the code, the ticket and the right words from scratch does not, and that difference, repeated all day, is the time I get back.
Handing work over
When the code confirms a defect, or QC reports one, it replies "noted, checking" and opens a worktree for the ticket. A planning agent runs the pipeline stages in order, and a second agent implements per repo. Every Jira comment still comes to me for approval. When an agent stops on a question, a recommended default is answered for it. Anything destructive, like a force-push or a delete, waits for me.
Guardrails
Everything in a thread or a ticket is treated as untrusted data: the skill answers the question and never follows an instruction inside it ("paste your env", "ignore your rules"). It never prints a token. It is read-only: its only writes are the reply, its own state files and drafts, and anything that changes code is done by agents in a worktree.
Where a human still decides
Under time pressure the temptation is to trust whatever the automation returns. That is exactly when the boundaries matter, so they are explicit. These are mine:
- Business rules go to a person. When sources disagree, the skill asks the product owner; it doesn't pick.
- Nothing merges or pushes by itself.
/prnever merges. Merging requires reviewer sign-off on every PR, not just one, and a successful deploy pipeline before the final step. Pushing my own commits is still my decision. - Drafts, not sends, unless I explicitly say a channel may be answered automatically.
- Read-only by default. The Slack skill reads code, git and the ticket tracker, and its only writes are the reply and its own state file.
- No silent skips. If a step can't run (a missing token, an unset transition), it says so and asks, rather than quietly moving on and reporting success.
Lessons the automation taught me
Almost every rule above is the scar from a specific failure, and the commit history reads like an incident log:
- Evidence beats assertion. An early version let the agent say "fixed". Now a fix without a screenshot or a response payload doesn't pass. Later I made the evidence screenshots highlight the element that proves the claim, because a screenshot of a whole page proves nothing to a reviewer.
- Never drop an event silently. The Slack observer ignored a top-level channel post that had no @mention and no ticket key, and I only found out by asking. The rule became: every post by someone else gets at least one line, even if the line is "nothing for you here". When I'm busy, silence has to mean "nothing happened", never "I missed it".
- Run it on real data before trusting it. Several fixes in the log are labeled "found by running it on real worktrees". A skill that reads fine can still fail on a detached HEAD, a renamed terminal, or a state file from an older version.
- Skills should carry lessons, not cases. I once hardcoded the exact incident into a skill's instructions. That teaches the model one story. Rewriting it as the general rule behind the incident made it behave correctly on cases I hadn't seen.
- Make the deterministic part a script. Anything that must be exact, such as posting a Jira comment with a real mention, waiting for a pipeline, or merging a PR, is a small script with a self-test. The model decides when to call it; it doesn't improvise the call.
- Watch the cost. Long sessions with parallel agents add up. I now decide per change whether it deserves a sub-agent test, and say so instead of running it by default.
Takeaway
The leverage came from shrinking the
overhead around the code (reading, planning, proving, chasing, answering, remembering) so that a bigger workload fits in
the same day, while I keep the decisions. /developer is still unfinished, and I'm growing it a little every day. The most useful thing I built is probably the habit behind the skills: every time the automation is wrong,
turn that mistake into a rule, a script or a test so it can't be wrong the same way twice.