Verification and release
Work reaches GitHub in three steps. A task is verified and reviewed on its branch, merged onto dev through the merge gate, and released from dev to GitHub main with a tag.
task branch ──review──verify──QA?──▶ merge gate ──▶ dev ──▶ cland release ──▶ github main + vX.Y.Z (worktree) (territory)Verification for any stack
Section titled “Verification for any stack”Tell cland how to check your project in .cland/config.toml:
[verification]prepare = "npm ci" # install dependenciescheck = "npm run typecheck" # fast static checkstest = "npm test" # the test suiteAny commands work. Some starting points:
| Stack | prepare |
check |
test |
|---|---|---|---|
| Node | npm ci |
npm run typecheck |
npm test |
| Python | uv sync |
ruff check . |
pytest -q |
| Go | go mod download |
go vet ./... |
go test ./... |
| Rust | cargo fetch |
cargo clippy -- -D warnings |
cargo test |
The onboarding Aide records them with your stack. To change one:
cland config set verification.test "pytest -q"Running a verification
Section titled “Running a verification”cland verify --report generalcland verify runs check, then test, at the current committed head, in the background. --report names the topic or session that receives the result: General here, usually the task’s own topic. The result is an artifact with the log, posted there and shown on the task’s card.
The hub owns the queue:
- One local run at a time, so the crew never overloads your machine. Everyone waits in the same queue, your own runs included.
- Results are cached by the tree they tested. A head whose tree already passed reuses that result instead of running again.
- A session cannot run the suite around the queue.
cland verify queue # who runs, for how long, who waits, the admission estimatecland verify cancel # cancel your active runcland verify --dry-run --report general # show what would run, queue nothingThe sessions panel’s verify: line summarises the queue; Enter on it opens the full queue view.
Review first, then verify
Section titled “Review first, then verify”The crew follows one order for every task:
- The Implementer pushes its branch and requests review.
- The Reviewer reads the source against the task and its criteria, and posts a verdict on that exact head.
- After a clear source verdict, the Implementer runs
cland verifyat that same head. - The Reviewer checks the result and posts the final verdict. A failed run is not clear.
- A fix means a new head: new review, then a new run.
Running the suite only after a clear review avoids spending runs on code that is about to change.
Local and cloud runners
Section titled “Local and cloud runners”Each run executes on an executor. The queue, the cache and the result look the same on both.
| Executor | Where it runs |
|---|---|
local |
Your machine. The default. |
droplet |
A fresh DigitalOcean machine per run, created from a prepared snapshot and destroyed afterwards. |
Choose the project default, and override it per run:
cland config set verification.executor dropletcland verify --executor local --report generalCloud runs let the crew verify several tasks in parallel without loading your machine. Pick local when there is no hurry, cloud when a release is urgent.
Setting up cloud runs
Section titled “Setting up cloud runs”-
Create a DigitalOcean API token that can read the account, sizes and regions, and create, read and delete droplets and SSH keys.
-
Export it in the shell that starts the hub:
Terminal window export DIGITALOCEAN_TOKEN=...cland hub stop && cland hub startcland reads it at each API call and sends it only to DigitalOcean’s API. It never prints, logs or saves it, and never sends it to a machine.
-
Build the snapshot every run starts from:
Terminal window cland executor prepareIt builds a machine image with your project checked out and your
preparecommand already run, so runs start ready. Prepare again after a release or a dependency change;cland executor statussays when the snapshot is stale.
Limits the hub enforces
Section titled “Limits the hub enforces”[verification]executor = "droplet"remote_budget = 4 # cloud runs at once, beside the one local runlocal_fallback = true # run locally when no cloud machine is availabledaily_spend_usd = 5 # the daily spend cap across all your projectslifetime = "110m" # a machine is destroyed after this, whatever happensrun_timeout = "75m" # a run is cancelled after this
[verification.droplet]size = "c-8"regions = ["lon1", "nyc1", "sfo2"]- The daily spend cap. The hub reserves each run’s maximum cost before creating a machine and refuses a run the cap cannot cover. The ledger is shared by all your projects.
- Lifetime and watchdog. Every machine carries the
clandtag and a deadline. A watchdog destroys it at the deadline, and the hub sweeps orphans. - No silent fallback by surprise. With
local_fallback = true, a run that cannot get a cloud machine runs locally and says why. Withfalse, it waits for cloud capacity. - Sessions never manage machines. Only the hub creates and deletes them.
cland executor status # token present, live machines, today's spend against the capcland executor reap # delete orphaned and expired cland machinesEach result names its machine, size, region, lifetime and estimated cost.
The merge gate
Section titled “The merge gate”When a task’s verdicts are in, the hub decides whether it can land. It requires:
- a clear review verdict on the current branch head;
- a clear QA verdict on the current head, when the epic’s plan requires per-item QA;
- every acceptance criterion marked done and verified;
- verdicts from Reviewer and QA sessions, not from the session that claims the task.
When everything holds, the hub sets the task to ready and tells the lead. A not-clear verdict or a failed criterion sends it back to in progress.
Merging
Section titled “Merging”cland merge T-42The Coordinator, or the epic’s Lead, merges on ready; you rarely run it yourself. A merge:
-
lands one squash commit on
dev, with the item and its verdicts in the message:Parse the config file (T-42)Item: T-42Reviewed-by: rev-1 (rev-run-id)QA-by: qa-1 (qa-run-id) -
refuses on a conflict and asks the Implementer to rebase; it never rewrites a task branch;
-
closes the task and its topic, releases the claim, and removes the worktree and branch;
-
stops the task’s Implementer when the crew says
stop_after = "merge".
Merges run one at a time. A refusal leaves every ref untouched and names what is missing.
You can bypass the gate from your own shell for tracker and documentation changes only:
cland merge T-50 --override "typo fix in the README"The override is recorded on the item and in the journal. Code, configuration and skill files always need review. Sessions can never override.
Publishing dev to the territory
Section titled “Publishing dev to the territory”The Coordinator commits tracker and docs work directly on dev in the root checkout, then publishes it to the territory:
cland pushIt only fast-forwards and never forces.
Releases
Section titled “Releases”A release is an item: a versioned set of epics. Its children are its contents.
cland task create "First version" --kind release --version 0.1.0The version is SemVer without the v. It can be set later, but a release needs one to ship. The body’s ## Notes section opens the release notes.
Releasing
Section titled “Releasing”cland release --item R-1 --dry-run # preview the notes and refscland release --item R-1 # publishA release:
- Refuses while any child is open, or when the version is missing or the tag exists.
- Builds the notes: the item’s Notes, each epic with its one-line goal, then one line per merged task.
- Creates an annotated tag
v<version>atdevand pushesdevas GitHubmainand the tag in one atomic push to thegithubremote. - Only after GitHub accepts it, moves your local
mainand the territory, comments the tag on the release item and closes it. The next open release becomes current.
No GitHub API is called and no version file in your project is changed. cland release is the only command that pushes to GitHub.
In practice the Coordinator prepares the release with the shipped release skill and asks you to approve the version and notes before it publishes. The version always comes from you.
For a hotfix without planning, release by version alone:
cland release v0.1.1 --dry-runcland release v0.1.1Collaborators’ work
Section titled “Collaborators’ work”If someone else pushed to GitHub main, cland release refuses and tells you to sync first:
cland syncIt fetches GitHub main and replays the unreleased dev commits on top of it, locally and in the territory. It never pushes to GitHub.