CLI
The command is cycle. It is not cc, because on every Unix machine cc is already the system C compiler, and a governance tool that shadows a compiler is one people uninstall.
The command comes from npm: npm i -g commitcycle. The plugin carries its own copy for its hook and its MCP server, and puts no cycle on your PATH. Every command below resolves its board the same way: the environment wins, and when the environment says nothing, the committed .zones/board.json answers.
Every command explains itself before it does anything. cycle <command> --help (or -h) prints what that command changes, and it is answered before anything else happens — no repository, board or network is needed to explain a command, so asking cannot be what runs it. The commands whose accidental run is expensive — submit, sync and verify among them — also refuse a flag they do not take, by name, and do nothing: a silently dropped --dry-run is a command that ran when somebody asked it to explain itself. A task id a command would not act on is refused the same way rather than dropped: a second id where it takes one, or an id after a title (CC-1383).
Getting connected
Section titled “Getting connected”| Command | Does |
|---|---|
cycle login |
Sign in to the board: an address, a six-digit code, done. More than one address is fine — each repository uses the one its organization knows |
cycle accounts |
Who this machine is signed in as, and which identity this repository uses |
cycle logout [email] |
End one identity for this board — at the board, then here — or all of them |
cycle pair |
Join this machine to a board: a code here, confirmed in your browser. Writes .zones/board.json — no name to type |
cycle unpair |
Revoke this machine’s key for this board and forget it. A machine key never expires, so this is the only way it stops |
cycle connect <ticket> |
The same, started from the board: paste the line it gave you |
More than one identity on one laptop is an ordinary case, not an edge one — two accounts on one machine covers which one answers where.
Both credentials are ended by the command that mints them, in one direction only: cycle logout ends a session and cycle unpair revokes a machine key. Pairing again undoes nothing — it mints a second key beside the first. Either can also be taken back from the console when the machine is gone or offline: sessions under Settings → Your sessions, machines under Settings → Connect a machine.
Setting a repository up
Section titled “Setting a repository up”| Command | Does |
|---|---|
cycle init [--yes] [--mode memory|governance] [--harness <names>] |
Set this repository up: write the zone map — no zones in memory mode, the default; proposed zones in governance — and the agent context |
cycle mode [memory|governance] |
Read the repository’s mode, or choose it (a signed-in person): memory needs only an active task to edit; governance adds zones. See modes |
cycle doctor [--enforcement] |
Why is CC behaving like this? A diagnosis: it moves no task |
cycle merge-driver %O %A %B |
Plumbing that git runs, not you: the AGENTS.md merge driver. It merges your text outside CC’s block and keeps this side’s block, so the generated part never conflicts. cycle init maps it in .gitattributes and cycle sync re-registers it in a clone that is missing it |
cycle init does not install the hook, and it does not write hook configuration unless a wrapper is already on this disk. Claude Code lets a write through when the script a hook names is missing, so config with nothing behind it is a repository that looks guarded and blocks nothing — worse than one that admits it, because you stop checking. The wall is the plugin.
What cycle init asks, and what it writes
Section titled “What cycle init asks, and what it writes”--mode memory, the default, writes a zone map with no zones and asks nothing about zones or owners: every edit needs an active task, and nothing else is judged. --mode governance is the flow below.
In governance, cycle init proposes zones from what is actually in the repository and asks one question about each — is this dangerous to change? — plus one about who owns them, offering the address you are signed in as, or else your git identity. A zone nobody said yes to is not written, and an owner nobody accepted stays TODO@example.com, which cycle doctor keeps flagging. The one proposal that is not a question is the schema system zone: spike mode is built on it, so it is accepted without asking.
--yes (or -y) accepts every proposal and the proposed owner without asking. It is also the only way to run cycle init without a terminal: piped or scripted without it, the command refuses and writes nothing rather than answering its own questions.
--harness picks which editors get a hook file — claude-code, codex, cursor, windsurf, or all — comma-separated, repeated, or both. Left out, it writes Claude Code’s and Codex’s, which is what cycle init has always written. A name that is not one of the four stops the command before anything is written. The selection does not bend the rule above: asking for all four on a machine with no wrapper still writes none.
| Harness | File it writes | What it does not promise |
|---|---|---|
claude-code |
.claude/settings.json |
The only harness whose wall has ever been seen to fire |
codex |
.codex/hooks.json |
Codex ignores project hooks until the project is trusted, and no Codex install has ever run this entry |
cursor |
.cursor/hooks.json |
Ignored in an untrusted workspace. failClosed: true is set, but only preToolUse is wired, so treat shell commands and file reads in Cursor as unguarded. Never exercised against a Cursor install |
windsurf |
.windsurf/hooks.json |
No channel for a denial’s reason, so a blocked action can stop with no explanation. Windsurf is becoming Devin Desktop, and whether this path keeps working is documented nowhere. Never exercised against a Windsurf install |
cycle init prints those caveats beside each file as it writes it. The three entries that are not Claude Code’s carry no argument that picks the hook’s reply format, so the hook answers them in Claude Code’s, and what those editors do with that reply has never been observed — cycle init says to treat those files as filed, not as enforcing. What blocks in each editor has the detail per editor.
Beside the zone map, cycle init writes the durable block in AGENTS.md — the zone map, the board and the deploy rule, never the active task — a CLAUDE.md that imports it (appended to one you already have, never replacing it), the .gitattributes line and the git config that register the AGENTS.md merge driver, and two .gitignore entries: CC’s runtime state and CLAUDE.local.md. It writes .mcp.json only when this project holds a CommitCycle CLI to start. A hook file or .mcp.json that already exists is left alone and listed as such. It names no board — when .zones/board.json is missing it tells you to run cycle pair — and it never moves a task.
What cycle doctor checks
Section titled “What cycle doctor checks”cycle doctor is the first thing to run when something is not behaving. It is a diagnosis, not a repair: it moves no task, and every question it asks the board is a read. Each check prints as one line marked ok, warn or FAIL, with the fix beneath it where there is one, and the last line sums them up. It exits 1 when any check failed; warnings alone exit 0.
Several lines print only when there is something to say. In the order they appear:
| Line | What it answers |
|---|---|
git repository |
The repository root being diagnosed |
zone map, zone owners |
Whether .zones/zones.yml exists and parses, what it declares, and which owners still say TODO |
hook installed, hook executable |
Which layer is wired — this repository’s hook configuration, a plugin installed on this machine, or neither — and whether the wrapper it invokes is on disk and can run on this platform |
enforcing bundle, built bundle vs source, shipped bundle vs source, session vs bundle, one hook, not two |
How old the artifact judging this session is, and whether two hooks are registered for one repository and are the same artifact |
shipped CLI vs source |
Only in a checkout that builds the CLI: whether the committed plugin/bin/cc.js, the bytes every plugin and npm install runs, is what this checkout’s source builds (CC-605). It is not one of the --enforcement lines |
active task, branch matches the grant, grant holder is live |
Whether the branch names a task, and that task’s grant: missing, expired (the fix is cycle renew), issued for another branch, or held by a checkout git can no longer find |
guard prerequisites, hook timeout |
Whether the working tree can be read, which containing Bash commands depends on, and whether a hung hook can be cut off |
deploy guard |
Whether a deploy script — in the root package.json, or one under apps/, packages/ or services/ — can publish without going through cycle guard-deploy |
AGENTS.md merge driver |
Whether .gitattributes maps AGENTS.md to the merge driver, and whether this clone has the driver registered |
board scope |
Which organization and repository this reports to, and whether the environment or .zones/board.json said so |
tool surfaces |
Which tool surfaces this checkout’s event log has seen, and whether each one is accounted for |
one identity |
Whether the signed-in address and the git identity name the same person |
board address |
Whether the board is a local one, whose task ids will collide with the shared board’s |
With a board address configured, two more lines ask the board: version handshake — do the CLI and the server speak the same protocol — and up to date, which says whether a newer CLI has shipped and is never a failure. With an organization as well, doctor compares the board’s task list with the record files (records match the board, record titles drift, record state is stale, ahead of the board, work with no record, In Progress is running, stranded grants, unreadable record) and asks whether each zone’s owner is a member of the board: zone owners are members, or zone owners unchecked when the board cannot say. Among those, only a record that describes different work from the board’s task under the same id is a failure; the rest can only warn.
cycle doctor --enforcement answers one question only, and it is a question the other lines cannot reach: how old is the bundle actually enforcing this session, measured against the source it claims to be built from. It prints the four bundle lines — enforcing bundle, built bundle vs source, shipped bundle vs source and session vs bundle — and nothing else: no board, no task, no hook wiring. It exits non-zero when any of them fails: a build older than its source, a shipped bundle behind it, a session running an older or different artifact than this checkout produces, or an enforcing bundle that is not where the wrapper looks.
That matters because the hook runs from a built bundle loaded once per session, and a bundle can be behind the source that fixed something — in which case the repository is being judged by a rule its own code no longer contains, and every other doctor line still says ok. What it does not check: whether the source itself is correct, whether the board agrees, or whether the bundle on any other machine matches. It compares the artefacts on this machine against the source in this checkout, right now.
The task lifecycle
Section titled “The task lifecycle”| Command | Does |
|---|---|
cycle start "title" | <task> |
One command: intake → scope → branch → gate → grant → record |
cycle scope <task> |
Correct what a task says. It cannot move it — that is the gate |
cycle show [task] |
Read a task as the board holds it, or list the board. Read-only |
cycle status |
What task this branch is bound to, what its grant opens and for how long, and which board this repository reports to. Needs no board |
cycle sync [--no-reconcile] · cycle sync --policy-only |
Push the zone map and events, pull the grant for this branch, and repair the drift a checkout collects. Never moves a task. --policy-only fetches the failure policy and nothing else |
cycle pull |
Reconcile the record files against the board’s task states |
cycle start|scope … --files a,b |
The files the task expects to touch. The board answers with open tasks that expect or touched the same files, and the CLI prints the playbook lessons that name them, before the work starts |
cycle why <file|folder/> |
Which tasks changed a file or everything under a folder, newest first, with the goal each was opened for. The same answer is cc_why for an agent and Files on the board |
cycle export [task] [--audits] [--out <file>] |
The board as one Markdown file: tasks, history, files touched, audit records and playbooks. Read-only |
cycle renew |
The grant ran out and the work has not: re-issue exactly the zones it already holds, for another TTL |
cycle submit [--close] [--evidence <url|path>]… [--note "<report>" | --note-file <path>] [--pr <url|number>] |
Hand the work in: In Progress → In Review, close the zones, and keep the report and the PR on the ticket |
cycle reply --hook |
Run by the plugin’s Stop hook: keeps the agent’s last reply on the task bound to the branch. Prints nothing, always exits 0 |
cycle context --read-hook | --prompt-hook | --session-hook |
Run by the plugin’s hooks: the tasks that wrote a file before its first Read, related tasks on each prompt, the active task’s spec at the start of a session, on resume and after compaction. Additive, prints nothing on failure, always exits 0 |
cycle pause [task] --reason "…" |
Stop without giving up: In Progress → Todo, and hand the grant back |
cycle discard [task] [--manual "…"] · cycle promote [task] |
End a spike. discard reverts the branch to its base and proves nothing was left behind, with no confirmation; promote keeps what the spike found, after one confirmation. Neither moves the task |
cycle ack [when] |
Mentions addressed to this branch’s task, and the way to discharge one. Advisory — it closes a warning, opens nothing |
cycle handoff [--closed N] [--out <path>] [--offline] |
A day-1 briefing for whoever picks this repository up next: what is running, what is undecided, what was just closed, and the order to read it in. Prints; --out writes. With --offline, or when the board cannot answer, live states come from the record files and the briefing says so |
cycle start and cycle scope share the same scoping flags — the four fields, plus what the task will touch:
cycle start "the thing" \ --goal "…" --non-goals "…" --criteria "…" \ --zone schema:write --topics backend,db --priority high --gate nonecycle start <task> on a task whose branch already exists reuses that branch — it is the task’s binding — and first says where it stands against the trunk: how many commits behind origin/main it is, which commits on it name another task, and which tracked files checking it out would take back to an older state. If any of the three is not zero, it refuses before anything is scoped, checked out or started, and names them. --accept-position adopts the branch where it stands anyway; bring the trunk in afterwards with git merge origin/main, which adds a commit and rewrites none. Nothing moves or re-cuts the branch.
When there is nobody to hand to
Section titled “When there is nobody to hand to”Two steps in the loop are waits for another person, and on a board with one member there is no other person to wait for. Both collapse — the wait, never the check (D-33).
cycle renewre-issues the grant this task already holds when its eight hours run out: the same zones, the same modes, the same branch, a new expiry. It cannot widen anything — the board re-issues what the grant carried, not what the task now declares — and a high-risk zone whose approval was withdrawn or timed out is refused exactly as it would be at the gate. Every renewal is recorded with an actor and appears in the audit record.cycle submit --closecarries the hand-in straight on into the close instead of leaving the work in In Review for a reader who is you. Nothing about the close changes: the same checks run, the same audit record is written, and the task still passes through In Review.
Both read the board’s own membership list — there is no mode to set and nothing to remember. Adding a second person to the board turns both off in the same act, and the commands then say so and name the path that is theirs.
If the board reports more than one member — or does not say how many it has, which is what an older board looks like — cycle submit --close stops after the hand-in, says so, and exits 0: the task is in In Review and the work is safe. If the close itself is refused, the hand-in stands: the task stays in In Review, the refusals print, and the command exits 1. Fix what they name and run cycle verify --close — not a second cycle submit.
cycle scope deliberately cannot move a task between states. Correcting a description and deciding that work may begin are different acts, and only the gate does the second. Once a task has left Todo, the board also refuses a change to the three things the gate and the close judge it by — its affected zones, its quality gate and its mode — from cycle scope, cc_scope and the console alike, so what passed the gate is what the close checks (CC-1313). The goal, the non-goals, the criteria, the topics and the priority stay correctable.
Two of those flags carry meaning beyond being stored, and both are easy to pass without knowing what they buy:
--topics a,bnames the disciplines the work needs, and each one loads its playbook for the agent — the per-topic, per-project context in.zones/playbooks/that a close can feed back into. Topics are advisory: none of them blocks, and a topic nobody declares never loads, which is why the loop’s own playbook is named in the agent guide rather than left to a topic.--priorityis the triage property, on the same scale a reader already knows from Linear:urgent,high,medium,low,none. It orders the board and nothing else — the gate does not read it, and a high priority does not shorten a grant or soften a check.
cycle pull takes --adopt and --write-missing: the first catches stale record files up to the board’s states, the second writes a record for a task the board holds and disk does not. A record ahead of the board is left alone.
With "records": "board" in .zones/board.json, none of the commands above writes a record file: the board holds the tasks and their audit records, and cycle export is how you read them as Markdown. See keeping the records on the board.
What cycle status reads
Section titled “What cycle status reads”cycle status answers from this machine and needs no board: it reads the branch, the local grant file, the zone map, and the board binding — the environment, or .zones/board.json. It prints the branch and the task it names, the organization and repository this reports to, which hook bundle is deciding the rules here and whether a newer release is known, and every zone as closed, closed — holds secrets, readable, closed for writing, readable, writing needs a grant, or open for the mode the grant gave it. A zone whose policy is open is open for reading only: writing it still takes a grant, exactly as a read-only zone does, and a zone that holds secrets is closed for reading whatever its policy says. When the hook core is missing, the deciding-rules line says what the repository’s failure policy makes of that — open, with nothing checked, unless a person chose otherwise on the board (CC-1143). While a grant is active it ends with how long the grant has left.
Three situations get their own paragraph: a grant that has expired — which is why writes stopped, and cycle renew is the way back — a grant issued for a different branch than the one checked out, which would attribute a manifest computed here to work the grant never covered, and a grant held by another checkout, since write access lives where the grant is held. With no zone map, or one that does not parse, it says so and stops there. On a spike it names the mode and the time box.
What cycle sync does in one run
Section titled “What cycle sync does in one run”cycle sync sends some things up, brings some down and settles a few files here, each kind of data in one direction only. It never moves a task between states.
- Up: the zone map; this task’s event log, from after the last event the board took; and, where the records live in the repository, the state each record file says, only for the records that changed since the board last took them (CC-1383).
- Down: the grant for this branch’s task — including a revocation. When the board says there is no grant and one is sitting on disk, sync deletes it, because the hook reads that file and never the board. It also brings down the two repository policies a person sets on the board — the failure policy below, and whether this repository’s agents may open pull requests — and any mentions addressed to this task, which are advisory.
- Here: the durable block in
AGENTS.md, rewritten only when the zone map, the board or the enforcement it describes has changed;CLAUDE.local.md, which names the active task while a grant exists and is cleared when none does; the playbook catalog in.zones/playbooks/README.md; and theAGENTS.mdmerge driver, registered in this clone if it is missing. A merge conflict inAGENTS.mdthat touches only CC’s block is resolved to this side; one outside it is left for you.
A board that cannot be reached, or cannot see this repository, is a warning, not a failure: nothing local is deleted on a call that got no real answer, and the offline half of the run still completes. Every board call gives up after ten seconds (CC-1383).
When the board did answer, sync also repairs the drift a checkout collects when nobody runs maintenance, one printed line per repair:
- a record file whose state is behind the board catches up to it;
- a task the board holds past Triage with no record file gets one written from the board — unless another checkout is standing on that task, or a file in
.zones/tasks/carries no id; - a grant file for a task the board holds in any state other than In Progress is deleted — never the current branch’s own.
On a branch that names no task, the two record repairs are only offered: nothing would record the write, and the next cycle start would refuse on the files it left behind, so sync names cycle pull --adopt --write-missing instead (CC-1383).
What it will not do on its own: pause an In Progress task with nothing running under it (a pause needs your reason, so it prints the command instead), rewrite a record that is ahead of the board (that is evidence — cycle pull reads its events), or touch anything under an id where a record file and the board describe different work. That id is skipped, the rest of the pass still runs, and the command exits 1 (CC-1383; until then it aborted the whole pass). Commit what the repairs write with the task that ran them. --no-reconcile skips the stage entirely.
cycle sync --policy-only fetches the failure policy below and nothing else: one call, one gitignored file, no tracked file touched. It exits 1 when the board did not answer, and it is what session provisioning runs (CC-1383).
What cycle sync also brings down
Section titled “What cycle sync also brings down”Of the things cycle sync brings down, one is worth knowing in detail: the repository’s failure policy — what the hook’s wrapper honors when the core cannot answer at all.
It is set per repository, by a signed-in person on the board, and never by a machine: a token can read it, and an agent choosing how enforced its own outage is would defeat the point. cycle sync carries it down to .zones/state/failure-policy as a single line the wrapper can read with shell builtins, because the premise of that code path is that nothing more capable is working.
That file is runtime state, not configuration you edit: it is gitignored, it lives inside the trust root the hook reads its own grants from, and cycle sync overwrites it with whatever the board says. Change the policy on the board and sync; a hand-edit is either reverted on the next sync or refused outright.
| Value | The wrapper’s answer while the core is down |
|---|---|
open |
The default. Each call proceeds under the person’s own permission settings, nothing is granted, and the first call of every session names the repair |
closed |
Deny everything until the core answers. A choice, for repositories where a stopped session costs less than an unchecked one |
reads |
A fixed list of tool names passes, each announced as degraded |
journal |
Everything passes onto the unwitnessed record — and if the journal cannot be written, it falls back to closed, because allow-without-record is the one combination nobody chose |
No file means open, the same default the board answers with (CC-1143). A board you cannot reach changes nothing at all: an outage must not loosen or tighten anything by itself. What is not configurable is honesty — the wrapper always answers, and every degraded call says so in its own reason string.
This is here rather than under Operations because it is a property of a command in this table: cycle sync is what writes it, and editing that file by hand is not how it is set.
The hand-in report stays on the ticket
Section titled “The hand-in report stays on the ticket”What the agent says when it finishes no longer ends in a terminal. cycle submit --note "<report>" (or cc_submit with note) keeps it on the task, and the board reads these headings into the reviewer’s view: ## Done, ## Beyond the ask, ## Different from the ask, ## Verification, ## Review against the ask, ## Merge danger and ## Criteria (one line per acceptance criterion, 1. <evidence>). Parsed from the note, never generated.
The review against the ask is what a fresh subagent reports when it is given only the diff against the base and the task’s four answers: criteria missing or partial, work beyond the ask (above all anything touching a non-goal), and criteria that look done but are wrong, each finding quoting the line of the spec it answers to. The plugin’s review-against-the-ask skill runs it in the agent’s own harness — the board calls no model. The merge danger says whether the change is a one-way door (hard to undo: data, deletions, a published contract) or a two-way door (a revert undoes it), and names the blast radius. Neither is a gate: nothing refuses a hand-in or a close over them.
Without --note, the agent’s last reply goes instead: the Claude Code plugin runs cycle reply --hook on every Stop and keeps the latest reply on the task (and in .zones/state/replies/, ignored by git). --pr keeps the pull request on the ticket; the GitHub webhook does the same on its own once it is set up. The commit the branch left the trunk at travels with the start and the hand-in.
Why the code is the way it is
Section titled “Why the code is the way it is”On the trunk since 2026-10-14 (CC-1347), and newer than the published 0.1.25 — see what the newest release does not carry yet. The plugin runs cycle context on three hooks. Before an agent first reads a file in a session it gets the tasks that wrote it, from git blame → commit → task record (top three by lines, with each goal and what differed from the ask), plus open tasks on the same file. Each prompt gets up to three related tasks from the board, or from the task records when the board is unreachable — which means the prompt’s text, up to its first 2,000 characters, is sent to the board’s related-task search on every prompt in a repository with a board. At the start of a session, on resume and after compaction, the active task’s spec is restated. None of the three can block anything: they add context, print nothing when they fail, and always exit 0.
Secrets are redacted from replies and notes before they leave the machine and again before the board stores them. When the repository keeps its records in git, cycle verify --close writes the hand-in into the task’s record under ## Hand-in — from --note when you pass one there, else the board’s copy of the hand-in, else the agent’s last reply.
What cycle submit changes
Section titled “What cycle submit changes”cycle submit takes no task id — the branch names the task — and asks the gate to move it In Progress → In Review. If the gate refuses, its own words print and nothing here changes. If it says yes, in the same run:
- the task’s grant file is deleted, so protected zones close here in the same breath the board revokes them;
CLAUDE.local.mdstops naming the task;- the durable block in
AGENTS.mdis re-rendered, and committed on its own only when that changed it. The active task no longer lives inAGENTS.md— it moved toCLAUDE.local.md— so on a repository already in that form there is ordinarily nothing to commit.
It does not close the task. Its last lines name cycle verify --close, which is In Review → Done and writes the audit record; until that runs, the task sits in In Review and leaves no trail.
--evidence <url|path> names what the task’s quality gate asks for, and it can be repeated. It is handed to the close that --close runs, but it is checked first: a path that is not in the repository refuses the whole command before anything is handed in. A value that starts with http:// or https:// is a URL; anything else is a path.
Access
Section titled “Access”| Command | Does |
|---|---|
cycle request-access [task] --zone <id> --mode read|write --reason "…" --alternative "…" |
Ask the zone’s owner to open it for this task |
cycle requests |
What is waiting on you — the zone, the task, why, and what they would do instead |
cycle approve <id> [--hours N] · cycle deny <id> |
Answer one from here |
cycle wrong ["why"] [--command "…"] [--all] · cycle wrong --list |
Say the last block was wrong, in one step. Lands on disk, goes nowhere. --all looks for the newest block across every task’s log, not only this branch’s. --list is the count and what it does not prove |
--zone, --reason and --alternative are all mandatory, and a request missing any of them is refused before anything is sent. A request without an alternative gives the approver no way to weigh saying no, and approval-as-reflex is how gates rot. --mode defaults to write; pass --mode read (or --read) when reading is enough — read never grants write.
cycle request-access asks for this branch’s task and no other. It refuses, before the board is called, when the checkout is not on a task branch, when the [task] it is given is not the branch’s own, when the zone map on disk declares no such zone, and when it is given a flag it does not take (CC-1383). It grants nothing and changes no file here: the ask is filed at the board for the zone’s owner, the reply prints the request’s id and the console address where it is answered, and once it is answered cycle sync brings the decision down.
If the board reports that the zone’s owner is not a member, or that the zone declares no owner, the request is still filed — and the reply says that nobody who can answer it will see it until that is fixed. cycle doctor lists every zone in that state. Either way, carry on with the rest of the task: working around the block is worse than the block.
Answering from the terminal is supported, and the console is the stronger door — the trail records which one answered. The board checks who is answering: a signed-in session answers as its own address, and only for a zone that address owns; a paired machine answers only on a board with one member, recorded as whoever paired it. A zone whose owner is still cycle init’s TODO@ placeholder is decided by an owner of the organization (CC-1324). The link the reply prints names the board (?board=<tenant>/<repo>), so the console opens the right one whichever it showed last (CC-764).
Closing
Section titled “Closing”| Command | Does |
|---|---|
cycle verify |
Run the closing checks against this branch. Changes nothing, and needs no board |
cycle verify --from-history [task]… |
The same, for work that merged before it closed — against the task’s own commits in the trunk. It takes several ids, each checked (and with --close closed) on its own |
cycle verify --base <ref> |
The same, against a fork point you name instead of the one CC works out |
cycle verify --close [--evidence <url|path>]… [--override "reason"] [--guard-change "why"] [--note "<report>"] |
Close it: submit the manifest and write the audit record. --note is the hand-in written into the task’s record, when the records live in git |
Without --close, cycle verify changes nothing, locally or at the board. It builds the manifest — every file changed since the fork point, committed or not, and a renamed file under its old path as well as its new one (CC-1382) — and runs the same local checks the close runs, in the same words, so a refusal costs no round trip. It exits 1 when they refuse. Rehearse with it before every close. The closing gate describes what the close checks; the one part the rehearsal cannot run is the evidence a quality gate asks for, which lives on the board — so the rehearsal warns about it from the task record, and the board checks it at the close.
Beyond the gate’s own checks, three refusals are worth knowing before they happen:
- The guard moved. When the diff touches a file that defines the guard itself —
.zones/zones.yml,.claude/settings.json,.mcp.json, anything under.github/workflows/orplugin/— the check refuses until--guard-change "why"says why. Moving the boundary is allowed, andcycle protectandcycle dismissdo it on purpose; what is refused is the move passing unsaid. An empty reason is refused too. The reason is written under its own heading in the audit record, and passing the flag when no such file changed says that nothing was acknowledged. - The diff is empty because the work already merged. An empty manifest on a branch that is already in the trunk is refused rather than passed — closing on it would check nothing — and the refusal says whether commits in the trunk name the task, so
--from-historycan verify those instead. - The task id is not the branch’s. A
[task]is honoured only with--from-history, the one mode that reads a task out of history. Without it the branch names the task, and an id that is not the branch’s own is refused rather than silently ignored. With it, several ids are checked, and with--closeclosed, each on its own: one summary says which closed, the exit is1if any did not, and--note,--evidence,--overrideand--guard-change, which describe one task, are refused with more than one (CC-1383).
With no grant on disk — the ordinary state after a hand-in — the declared-zones check does not run locally, and cycle verify says so: the transition runs it against the board’s copy of the task.
--from-history exists because work that merges before it closes cannot be verified from its branch: the merge base is the tip, so the diff is empty. It rebuilds the manifest from the task’s own commits and labels it as such, so no audit record mistakes it for a branch diff.
--base overrides the fork point the manifest is measured from, and the output says so — it reports “the --base you passed” rather than the base it found, so a record never hides that a human chose the comparison. Most of the time you will not need it: CC finds the fork point against the trunk on its own, and when it is right the flag adds nothing. Reach for it when it is wrong — a branch cut from another branch, a rebase that moved the base out from under the task, or a trunk this checkout resolves differently from the one the work actually left. The wrong base is not a cosmetic problem: it decides which files land in the manifest, and a manifest is what the closing gate checks the declared zones against.
With --close, cycle verify needs a board. It runs the same local checks first and stops on any refusal before the board is asked; then it mirrors this task’s event log to the board, asks for In Review → Done with the manifest and the evidence, and writes the audit record to .zones/audit/<task>.md — commit it with the work. With "records": "board" in .zones/board.json, nothing is written there once the board confirms it kept the record, and cycle export <task> prints it. A record already there that differs is kept beside the new one as .superseded, never overwritten. A successful close ends by offering a playbook feed for each declared topic whose playbook the branch did not touch, and skipping it is legal.
--evidence is a URL or a path, and it can be repeated. A path that is not in the repository is refused before anything else runs: the repository is the source of truth, not object storage.
--override "reason" records a bypass rather than hiding one, and only the board applies it. It travels with the transition, signed by the actor this shell reports — CC_ACTOR, or else the address signed in to this board, or else git’s user.email, or else USER — and the local checks run before the board is asked, so an override cannot answer a refusal the local checks print: the close stops there, and the override goes nowhere. When the board does apply one, the audit record carries it as an exception, never as a pass, and the close says so — or warns when the record it wrote does not carry it.
The zone map, over time
Section titled “The zone map, over time”| Command | Does |
|---|---|
cycle protect <glob> --zone <id> |
Answer “should this be protected?” with yes |
cycle dismiss <glob> |
Answer it with no — reviewed, left open, stop asking |
These two exist because a close asks about paths that fell in no zone. Deciding once, at close, with the evidence in front of you, is cheaper than deciding at declaration time when it is hypothetical.
Both are local edits to .zones/zones.yml, in your diff: neither talks to the board, and neither grants anything. The edit keeps the file’s comments, and it is recorded in the task’s event log as CC’s own write, so the close does not read it as a change that went around the hook. A zone map that is missing or does not parse is refused before anything is written, and repeating either command with the same glob does nothing the second time.
cycle protect <glob> --zone <id> adds the glob to that zone’s paths. Put the glob first: the command takes the first argument that is not a flag as the glob. It cannot create a zone — a zone needs a name, an owner and a risk level, which are judgement calls for the editor with the rest of the map in view — so an id the map does not declare is refused, with the ids it does. Run cycle sync afterwards so the board learns the new boundary.
cycle dismiss <glob> adds the glob to the map’s unprotected: list, creating the list — with a comment saying what it means — the first time. The list only silences the closing gate’s question: anything no zone names is unprotected whether it is listed or not.
Because zones.yml defines the guard, a close whose diff carries either edit refuses until --guard-change "why" says why the boundary moved — see Closing.
What the work taught
Section titled “What the work taught”| Command | Does |
|---|---|
cycle feed <topic> "one bullet (CC-xxx)" |
Append what a close taught to the topic’s playbook — always optional |
cycle seed [--topics a,b] [--yes] |
Draft playbooks from the codebase, as choices. Claims cite real files or are dropped; topics with nothing to cite are not born |
cycle challenge |
The seed’s counterweight: proposals for what this repository lacks, each with a market source |
cycle challenge has no --yes. Accepting and rejecting both write a decision file, and deciding is yours.
Shipping
Section titled “Shipping”| Command | Does |
|---|---|
cycle guard-deploy |
Refuse a deploy that is not from the trunk |
Put it in front of the deploy command — cycle guard-deploy && <your deploy>. It exits 0 only when the bytes are the trunk’s: HEAD at the trunk’s tip on origin, fetched just now, and a clean working tree. It exits 2 on a branch, or on a trunk that is ahead of, behind or diverged from origin (nothing deployed), and 1 when no branch name can be found or when the deploy would ship uncommitted or untracked changes. When the fetch cannot run, the branch name decides alone. A bare && turns that 2 into a failed build; the deploy guard has the wrapper that keeps a skip green, and the full reasoning.
For MCP clients
Section titled “For MCP clients”| Command | Does |
|---|---|
cycle mcp |
Serve the MCP tools over stdio, for a client’s .mcp.json |
Seven tools, none of which moves a task. The MCP reference covers what they are and how they differ from the connector’s eleven.
Names borrowed rather than invented
Section titled “Names borrowed rather than invented”init from git init and npm init. status from git status. doctor from brew doctor. submit from Gerrit. request-access from Teleport’s access requests. A command set you can guess is a command set people keep.