drydock
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Specs in, pull requests out — an agentic pipeline for Claude Code. Git is the queue, Markdown files are the rules, and adversarial review happens before any PR exists.
drydock
Specs in, pull requests out — an agentic pipeline for Claude Code.
Interactive sessions produce specs. A git repo is the queue. An orchestrator dispatches executor agents into worktrees, and adversarial review happens before any PR exists — ships are built and inspected in the dock, and launched only when ready.
Quickstart · How it works · Contracts · Board · Architecture
$ claude # a normal work session; you were debugging
> /drydock:spec
Acceptance criteria and Assumptions, for confirmation before I write the file:
AC-1 go build ./services/ledger/... exit 0
AC-2 go test ./services/ledger/transfers/... all pass, new test for FR-001
AC-3 replayed POST returns the first entry, no new row curl script, 1 row in ledger_entries
✓ spec: 2026-09-02-transfers-idempotency → specs/inbox/ (dispatchable)
$ claude # a second pane, pinned, left running
> /drydock:orchestrate
board: http://127.0.0.1:8642
tick 1 preflight ok → active → worktree ledger-api-wt/2026-09-02-transfers-idempotency
executor dispatched (opus, background session)
tick 2 active: RUN.md 4m ago, session alive — no change
tick 3 ⚠ blocked: 2026-09-02-transfers-idempotency
"Idempotency-Key collision across tenants: reject 409, or scope the key
per tenant? Spec says neither, and the schema has no tenant column."
→ claude "/drydock:spec unblock 2026-09-02-transfers-idempotency"
$ claude "/drydock:spec unblock 2026-09-02-transfers-idempotency"
> scope per tenant — the header is client-supplied, tenants must not collide
✓ SPEC.md amended (FR-003, AC-4), QUESTION.md records the decision and what was ruled out
✓ unblock → specs/inbox/ # a FRESH executor resumes from the spec, never the old session
tick 4 re-dispatched
tick 5 READY.md → adversarial review on the worktree (round 1)
tick 6 REVIEW.md verdict: fix — 2 findings
· migration is not reversible; no down path internal/db/0042_idem.sql:1
· AC-3 asserts row count, not that the response body is the FIRST entry
→ fix executor, same worktree
tick 7 REVIEW.md verdict: ship — 0 findings
"Tried: concurrent replay under -race, expired-key eviction, a 24h clock skew."
✓ deliverable ready: 2026-09-02-transfers-idempotency — reviewed, github.com/…/pull/4471
$ claude "/drydock:review"
1 ready for review, 0 blocked
── transfers: idempotency keys on POST /v1/transfers ──────────────────────
4/4 criteria pass, evidence in deliverables/…/evidence/
review: ship, 1 fix round. Assumptions: 24h key TTL; 409 on body mismatch.
draft PR #4471 (open, mergeable)
> approve
✓ gh pr ready #4471 — draft → ready for your team's normal review
✓ archive: 2026-09-02-transfers-idempotency
The pull request in the last line is the first time a human sees a diff. Everything above it —
the clarifying question, the reversibility finding, the gamed acceptance criterion — was resolved
before the PR existed.
What drydock is
drydock is a queue, a loop, and five Markdown contracts. Work enters as a spec written by the
interactive session that was already thinking about the problem. A git repository is the state
machine: a spec's directory location is its state, and every transition is a commit, so the
queue's history is the audit log. An orchestrator ticks over that queue, dispatching executor
agents into isolated git worktrees. When an executor believes it is done, an adversarial
reviewer reads the diff and tries to reject it.
[!NOTE]
Most agentic pipelines review code in the pull request. drydock reviews it in the worktree, and
the pull request only exists once the work has already survived review and been fixed in-branch.
A PR appearing in your team's repo means a robot already tried to break it and failed.
The other half is what happens when an agent can't finish. Delivering with caveats is forbidden:
an executor that hits an unresolved question, a failing criterion, or a step outside its declared
blast radius stops, writes the concrete decision you need into QUESTION.md, and files itself intospecs/blocked/. Everything you must decide reaches you before any PR exists — never as a footnote
in a description you would have skimmed.
Quickstart
Prerequisites: Claude Code, Python 3.10+, git 2.x with
worktree support, and gh authenticated (drydock opens draft PRs and
reads their state through it).
/plugin marketplace add phspagiari/drydock
/plugin install drydock@drydock
/drydock:install
The first two lines install the plugin itself — code, contracts, board — the same way you'd
install any other Claude Code plugin; /plugin update drydock keeps it current later. /drydock:install
then sets up your STATE_HOME (~/.drydock by default): a private, local-only git repository —
never given a remote, ever — that holds your queue, deliverables, archive, and priors. It asks
before it writes anything: where STATE_HOME should live, your branch namespace (usually your
git forge handle — it prefixes every branch drydock creates, as <namespace>/drydock-<id>), and
what your permission posture is. Then it verifies the toolchain and fails loudly on anything
missing. See docs/QUICKSTART.md for the walkthrough and your first spec.
[!NOTE]
Your queue and the plugin's code are deliberately two different things in two different places.
STATE_HOME is yours, local, and remote-less by construction — nothing you write into a spec can
end up pushed anywhere, including back into this project if you ever also clone it to contribute
a fix. See docs/ARCHITECTURE.md.
[!WARNING]
The orchestrator dispatches agents that write to your repositories unattended, and the board
serves file contents from the STATE_HOME it is pointed at over127.0.0.1. Executors run as background
sessions with--permission-mode bypassPermissions— which does not widen what drydock may do,
because every executor is a separate Claude Code process that loads yoursettings.json, hooks
and deny rules. Your permission policy is the boundary. If you do not have a deny layer you
trust, run executors with--permission-mode acceptEditsand accept that some runs stall on
prompts. Read SECURITY.md before the first dispatch.
How it works
The queue
A spec's directory is its state. Nothing else records it — no database, no daemon, no sidecar file.
All of it lives under STATE_HOME (~/.drydock by default) — a private, local-only git repo
created by /drydock:install, never this repository, and never given a remote.
| Directory (under STATE_HOME) | API key | What it means | What moves it out |
|---|---|---|---|
specs/inbox/<id>/ |
inbox |
Written and dispatchable — or waiting on a depends_on id that has not shipped |
Preflight passes → active; an unresolved [NEEDS CLARIFICATION] → blocked |
specs/active/<id>/ |
active |
An executor is running in a worktree. Review and fix rounds happen here | Reviewer's ship → deliverables/; flag → blocked |
specs/blocked/<id>/ |
blocked |
A human decision is needed. QUESTION.md holds it: the question, the options, what was ruled out |
/drydock:spec unblock <id> writes the answer into the spec → inbox |
deliverables/<id>/ |
delivered |
Draft PR open and already reviewed — ready for your review | /drydock:review: approve → archive/; reject → inbox |
archive/<id>/ |
archive |
Approved, or the PR merged | Terminal |
delivered is the key the board's API uses for the middle column; the human-facing state is
"ready for review". A merged PR found during housekeeping archives itself — the merge was the
approval completing.
The contracts
The rules are Markdown, not code, because they are meant to be edited by a human who disagrees with
them. Every skill re-reads its contract on every invocation, so an edit takes effect on the next
tick of a loop that is already running.
| Contract | Governs | Read by | When |
|---|---|---|---|
ORCHESTRATOR.md |
The tick: inbox dispatch, active verification, housekeeping, notification policy | The orchestrator session | Every tick |
DISPATCH.md |
One spec from inbox to landed deliverable — preflight, worktree, the zero-calls gate, ship, comment rounds. Steps 1–17 | Orchestrator, /drydock:dispatch, and executors (steps 9–11) |
Any tick that dispatches or lands work |
REVIEWER.md |
The adversarial pass: what to hunt for, the ship/fix/flag verdict, the round cap |
The reviewer session | When an executor writes READY.md |
PRIORS.md + priors/*.md (in STATE_HOME) |
Accumulated lessons — advisory knowledge, not policy. Seeded empty from PRIORS.seed.md at install. Hot file global; cold files loaded per target repo and per phase (design note) |
Every executor before work, every reviewer while grounding, /drydock:spec and the retro |
Start of every run |
PROPOSALS.md (in STATE_HOME) |
Rule changes a retro wants, with evidence and a suggested diff. Applied only by a human | /drydock:retro, /drydock:review |
A per-item retro queues; the review pass decides |
The commands
| Command | Does | Where you run it |
|---|---|---|
/drydock:spec |
Converges the session you are in into a spec instead of building mid-analysis | Any work session |
/drydock:spec unblock <id> |
Answers an escalation, writes the decision into the spec, re-queues it | Its own session |
/drydock:orchestrate |
Starts the loop, or runs one tick | A pinned pane, on your longest-running model |
/drydock:dispatch <id> |
Runs one spec now instead of waiting for a tick | Anywhere |
/drydock:review |
The human pass: blocked questions first, then deliverables | Daily |
/drydock:board |
Starts and opens the board | Anywhere |
/drydock:retro |
Mines runs into priors and queued rule proposals | Automatic after each ship; the full sweep is interactive |
/drydock:install |
Bootstraps a fresh environment | Once |
The rules that hold it together
These are distilled from the contracts; each is load-bearing, and each exists because the failure
it prevents is expensive.
- The zero-calls gate. Delivering with caveats is forbidden. Any failing criterion, any fired
escalation condition, any step outside the declared blast radius — including a breach already
committed — is an escalation, not a footnote. "The work is complete and the failure isn't mine"
is not an exemption. - No PR until
ship. The reviewer's verdict is what opens it, and the title and body ship
verbatim fromREADY.md. - Executors are stateless by design. After an unblock, a fresh executor resumes from the
amended spec, the branch, andRUN.md— never the old session. Which is why an escalating
executor must push its work and leaveRUN.mda handoff a stranger could resume from. - The target repo carries zero drydock metadata. No spec files, labels or tags committed
there; the branch and the PR are the entire footprint. STATE_HOME is the sole registry of
which PRs are yours, and PR state is read back per recorded URL — never by scanning the target
repo's PR list. - Executors never speak on the PR. A comment needing an answer becomes a drafted reply you
post or rewrite. Every word on the PR is yours. - Round cap 2. Judgement that survives two fix rounds needs a human, not a third robot — those
findings become aflag. A finding the reviewer tagged mechanical — a verbatim replacement plus
an executable check, applicable without rewriting a commit — goes through the repair pass, then
ships. - Deterministic FIFO. The inbox is ordered lexicographically by id, never by mtime, because
moves and edits reset mtime. - Bounded concurrency. Two executions at a time, one relaunch per spec, and a heartbeat file
that stops a second orchestrator from starting.
Full lifecycle, the actor model, and the state diagram: docs/ARCHITECTURE.md.
The board
python3 plugin/board/server.py serve --port 8642 # --root defaults to ~/.drydock
Standard library only, no install step, no build. It reads the queue from disk on every request —
no cache, no background daemon, no regeneration step, so what you see is what is on disk right
now. It binds 127.0.0.1 only and serves a fixed allowlist of item files. Blocked and delivered
cards carry the paste-ready claude "…" command that works them.
Writing a spec
Start from templates/spec-template.md; readexamples/example-spec.md for every section filled the way it
should be. The sections are Context, Goal, Non-goals, Constraints & blast radius, Requirements,
Acceptance criteria, Ship criteria, Escalation conditions, and Assumptions.
Acceptance criteria are the gate, and the section newcomers under-fill. Each one is a command
plus the result that counts as a pass. The eligibility test is blunt: if you cannot write every
criterion as a command runnable without you, the work is not drydock-eligible yet — name the
verifier that is missing and keep the work interactive. A check that needs a pull request, a
merge, a deploy or a human goes in the Ship criteria table instead: the executor carries it
forward unevaluated, and it does not make the work ineligible. "Tests pass" alone is rarely enough; at
least one criterion has to encode the feature's actual intent rather than compilation health.
Gaps become [NEEDS CLARIFICATION: …] markers rather than invented answers. Any marker still
present at dispatch blocks the spec instead of executing it.
Design principles
- Markdown contracts over code. A human can edit the rules, mid-flight, without a deploy.
- Git as the state machine. Directory = state, transition = commit, history = audit log. No
database, no daemon, no scheduler. - State is never code. STATE_HOME (
~/.drydock) and the installed plugin are two different
git repositories on purpose — STATE_HOME never gets a remote, so nothing you write into a spec
can end up pushed anywhere by accident. - Standard library only. A fresh install runs. The board has no dependency surface to audit.
- Review before the PR exists. The expensive round trip is a human reading a bad diff.
- Fail closed. Preflight aborts loudly, escalation beats guessing, and a caveat is a stop.
- The system learns from its own arguments.
/drydock:retromines unblock resolutions and
reviewer findings into priors, and queues rule changes for a human — it never amends a contract
itself.
Status
[!NOTE]
Early. The model is settled and the contracts are stable enough to run against real repositories,
but the surface will move: the model-routing table has two tracks (code,report) and is meant
to be extended, andPRIORS.mdships empty in every fresh STATE_HOME because priors are only
true of the repos that taught them. Contract disagreements are the most useful issue you can
file — bring them to
Discussions.Upgrading from 0.1.x: the install model changed — the plugin is now installed via
/plugin marketplace addinstead of a manual clone + symlink, and your queue moved out of that
clone into its own STATE_HOME (~/.drydock)./drydock:installdetects the old layout and
offers to migrate your real queue data across; see its step 4.
More
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found