@cydo/punchcard
v0.4.1
Published
An autonomous ticket-to-pull-request pipeline. Claims a ticket, runs an agent in a worktree, runs your build, opens a PR, and stops.
Readme
Punch
An autonomous ticket-to-PR pipeline. You describe a goal; it becomes tickets, tickets become pull requests, and you merge. Linear is the work queue, Claude Code subprocesses are the workers, and Punch has no way to mark anything done.
goal ──▶ tickets ──▶ branch + PR ──▶ you merge
intake one run each the only path to doneInstall
bun add -g @cydo/punchcardBun is the only prerequisite for installing. Bun writes a small launcher onto your PATH rather than a compiled binary — which is also why this works on Windows, where an unsigned executable is blocked by Smart App Control before it can run.
The command is punch, not punchcard.
To update later:
punch updateOne command. It asks the registry what the latest is, clears bun's manifest cache, updates, reads the version back off disk, and recopies the skill — then tells you what you are on. The cache clear is why it exists: bun resolves updates against a stored manifest, so an update run soon after a release reinstalls what you already have and reports success.
First run
punch install # adds the /punch skill, so Claude Code can drive this
punch setup # configures a repository — it asks, it never guesses
punch check # confirms it worked, and names anything missingTo actually work a ticket you also need a Linear API key, the
gh CLI logged in, and Claude Code installed.
punch check names whichever is absent rather than letting a run discover it
at the last stage.
The key goes in a .env beside your config — never in punch.yaml, whose
schema is strict enough to reject one:
LINEAR_API_KEY=lin_api_...punch credentials --edit opens that file for you.
The shape of a day
punch plan "add rate limiting to the ingest webhook" # a goal into tickets
punch work TES-42 # one ticket, watched
punch run --yes # drain what is ready
punch ui # the board, localhost:4600
punch tui # the same board, in the terminalMost people never type those. The interface is a Claude Code session and the
/punch skill — you describe what you want, it writes the tickets, shows them
to you, and works them once you say yes:
/punch check
/punch feature "add rate limiting to the ingest webhook"
/punch implement TES-42punch install is what puts that skill where a session will read it.
How it works
punch run does one ticket and stops. That is the whole unit — run it by
hand, from a loop, or from cron, and the answer is the same.
1. RECOVER reclaim runs a crash left behind, and claims with no run at all
2. BUDGET spent today ≥ daily limit? stop
3. SYNC pull `ready` tickets from Linear into SQLite
4. SKIP anything with an unfinished blocker
5. CLAIM take one ticket — a conditional UPDATE exactly one worker wins
6. GATE is the ticket workable at all? if not, park it now
7. WORKTREE fresh checkout of the base branch, on its own branch
8. AGENT claude edits code, alone, in that worktree
9. VERIFY the repository's own build and test commands
10. RETRY failed? resume the same session with the failure output
11. COMMIT → push → open the pull request
12. PARK ticket → In Review, with the PR link in a commentFails verification twice, or fails for a reason retrying cannot fix? The ticket goes to Blocked and Punch stops touching it.
Merging is step thirteen and it is not in this list, because nothing on a
schedule may reach it. punch complete <TICKET…> is a person merging by hand in
one command — dependency order, checks waited for between merges, stopping at the
first failure — and a test asserts the tick cannot get to it.
The repository's own tests decide — not the agent's summary of its work.
bun test exiting non-zero is a fact; an agent saying "all done" is a claim,
and the engine only ever believes the first.
The manual
Eight pages, carried inside the package. No network, no checkout:
punch docs # the list
punch docs getting-started # a page
punch docs tutorial | less # long ones are printed whole| Page | |
|---|---|
| index | What this is, and where to go next |
| getting-started | Install → configure → first ticket |
| cli | Every verb, every flag, and the slash commands |
| configuration | punch.yaml, layer by layer |
| concepts | Board, project, ticket, stage, and the tick |
| ui | The screens and what each answers |
| operations | Several repositories, unattended runs, and the known gaps |
| tutorial | A full run, proving every uncertain part |
The board also serves them at /docs while punch ui is running.
Design commitments
Load-bearing, not preferences. What is deliberately not built is named in
punch docs operations rather than left to be discovered.
- Punch cannot mark anything done, and the pipeline cannot merge. No
donerole exists in the config, so no code path could reach one, and validation refusesautonomy.merge: auto.punch completemerges and is the single exception: a command a person types, unreachable fromrun, enforced by a test. What stays forbidden is Punch deciding to merge. - Tests decide, not agents. The reviewer is advisory and has no
EditorBash. - Tools are an explicit allowlist, never a bypass. No
--dangerously-skip-permissionsanywhere, and a test asserts its absence. - Secrets never live in
punch.yaml. The schema is strict, so a config file cannot inject one. - The tracker says what to do next, never what already happened. A ticket's column is a shared field any automation can rewrite; whether a pipeline finished is Punch's own observation, and where the two disagree both screens say so.
- SQLite is the only shared state. Claiming is a conditional
UPDATEexactly one worker can win. - Every line of agent output is kept verbatim — it is what lets you answer "why did it do that" three days later.
Where it stands
Working end to end against a real Linear workspace and a real GitHub repository: a goal has become ordered tickets, those tickets have become one pull request, and a human has merged it.
836 tests, tsc clean, load-bearing invariants mutation-tested — each one
watched to fail before being kept.
Licence
Proprietary. Free to install and run; no redistribution, no derivative works. See LICENSE — the grant is offered version by version and may be withdrawn for future versions, and a version installed under it stays licensed to you.
© 2026 Cody Stine. Licensing enquiries: [email protected]
