Ordo is a set of agent skills for running a multi-step change as a plan: one roadmap entry becomes a ledger folder, each step is briefed, built in its own git worktree, reviewed by a fresh reviewer that changes nothing, and cherry-picked onto main only after its checks pass there. Around that loop, repo-setup and ordo-init set a repository up for it, roadmap keeps the entries the plans open, and plan-retro turns what the reviewers keep finding into rules. The skills carry no project name and no path. Everything specific to a repository comes from that repository's .agents/plan.yaml, so the same skills run a C++ engine, a TypeScript tool or a research project, under Claude Code or Codex, with either as the worker.
| Skill | What it does |
|---|---|
repo-setup |
Sets up a new repository: CLAUDE.md with the shared rules and AGENTS.md linked to it, the change and prose standards, a roadmap, an ADR folder, .gitignore, LICENSE, the project skills, then /ordo-init; sync keeps an existing repository's shared rules equal to the template |
ordo-init |
Sets a repository up for the others: drafts .agents/plan.yaml from the repository, offers the pages it lacks, fixes the ignore rules; on an existing file, checks it |
roadmap |
Keeps the roadmap /plan opens entries from: shows the open entries in order, adds an entry with its goal, gate and place, moves, marks done with the gate's output, drops; learns the file's own format, including an ordered build plan over a capability map |
plan |
Opens a plan for one roadmap entry: the ledger folder, plan.md with a drafted step list for approval, orchestrator-state.md |
spec |
Prepares one step: checks the step's premises against the tree, writes the brief, creates the worktree, stages the base binaries |
refute |
Reviews a built step without changing it: reruns every check and every command the builder's report quotes, writes findings |
land |
Cherry-picks a reviewed step onto main, runs the checks there, books the step, commits by explicit path, removes the worktree |
plan-orchestration |
Runs an open plan unattended, step by step, and stops only where a decision belongs to the user |
plan-help |
Prints the command sequence, and for a named plan its position and the command that comes next |
plan-retro |
Reads every refuter report, groups the findings by kind, and for each kind that recurs proposes the rule, the standards page or the check that stops it |
The order of use, shortened from what /plan-help prints:
/repo-setup once, for a new repository: the tree, the shared rules, the standards, then /ordo-init
/ordo-init once per existing repository: writes .agents/plan.yaml, or checks the one there
/roadmap add <goal> an entry with its goal, gate and place in the order
/plan <entry> once per entry: opens the plan, shows the step list for approval
for every step:
/spec <entry> <step> writes the brief, makes the worktree, stages the base binaries
"build it" the session writes the code in the worktree, runs the checks, writes the report
/refute <entry> <step> a fresh reviewer reads the diff and reruns the checks, writes findings
"close them" a repair round, up to repair_rounds times
/land <entry> <step> onto main, checks on main, the booking, the commit
/plan-orchestration <entry> instead of the step lines: runs them for every step unattended
/plan-retro after plans have run: the findings that recur, and the rule, page or check that stops each
/plan-help prints the full sequence, including what to do when a command stops.
- git, POSIX
sh, andpython3with PyYAML; the verify runner also needsbashandps. nodeandnpxonPATH, forskills/land/templates/land.sh(its index-lock wait and the usage rows) and for the skills CLI, which the CLI install andrepo-setup's project skills use.- Claude Code, Codex, or both.
The skills call each other and read each other's templates, so install all of them. Claude Code reads skills from ~/.claude/skills (or $CLAUDE_CONFIG_DIR/skills for a second account); Codex reads ~/.agents/skills. Remove any copy of these skills under a repository's .agents/skills or .claude/skills, so that the installed copy is the only one loaded.
npx skills add TheAxeC/ordo --skill '*' -g -a claude-code -a codexThis copies each skill folder into ~/.agents/skills and links it from $CLAUDE_CONFIG_DIR/skills, or ~/.claude/skills when that variable is unset. For a second Claude Code account, run it again with that account's CLAUDE_CONFIG_DIR set. Updating is npx skills update -g.
rm -rf /tmp/ordo && git clone --depth 1 https://github.com/TheAxeC/ordo.git /tmp/ordo
for dir in ~/.claude/skills ~/.agents/skills; do
mkdir -p "$dir"
for skill in land ordo-init plan plan-help plan-orchestration plan-retro refute repo-setup roadmap spec; do
rm -rf "$dir/$skill" && cp -R /tmp/ordo/skills/$skill "$dir/"
done
doneFor a second Claude Code account, add that account's $CLAUDE_CONFIG_DIR/skills to the list of folders. Updating is the same commands again: each skill folder is replaced whole, so a file a newer version removes does not linger.
A new repository is set up with /repo-setup from an empty folder. It asks for the name, the kind, the license, the commit rule, the coding standard and the project skills; shows the whole tree and every file; and after approval writes CLAUDE.md (with AGENTS.md as a symlink to it), the change and prose standards, a roadmap, an ADR folder, .gitignore, LICENSE and README.md, installs the project skills (writing skills-lock.json), and runs /ordo-init.
The shared rules in CLAUDE.md sit between <!-- ordo:shared-rules begin --> and <!-- ordo:shared-rules end --> and are a copy of skills/repo-setup/templates/shared-rules.md. /repo-setup sync compares a repository's block with the template, shows the diff and rewrites it after approval; on a repository with no block yet it drafts where the block goes and which existing rules it replaces. The same comparison runs on its own (<skills> is ~/.agents/skills, or skills/ in a clone):
python3 <skills>/repo-setup/templates/sync_rules.py <repository>An existing repository opts in with .agents/plan.yaml at its root. Run /ordo-init from the repository root: it drafts the file from the repository, shows it with any page it would create and the .gitignore lines it would add, and writes after you approve. On a repository that already has the file, it checks it. The same check runs on its own:
python3 <skills>/ordo-init/templates/check_config.py <repository>To write the file by hand, start from one of the two example files in the plan skill's templates/ folder:
cp <skills>/plan/templates/plan.yaml .agents/plan.yaml # one project
cp <skills>/plan/templates/plan.projects.yaml .agents/plan.yaml # several projects; a plan is then named <project>/<entry>plan.yaml describes every key. Eight are required: roadmap, verification, rules, ledger_root, archive_root, worktree_root, worker and reviewer. A skill that needs a missing required key stops and names it. Every other key is optional, and when it is left out it takes the default written beside it in plan.yaml; for example, a missing worktree_paths means the whole tree and a missing look means no look step. launch_note holds the absolute path of a command that records each builder the orchestrator starts as its own process, following the interface in the plan-orchestration skill's templates/launch-note.md; left empty, nothing is recorded.
Git must ignore worktree_root and must not ignore .agents/plan.yaml.
Each script under a skill's templates/ or under utils/ has a test beside it that runs on scratch repositories:
sh skills/land/templates/land.test.sh
sh skills/ordo-init/templates/check_config.test.sh
sh skills/plan-retro/templates/collect_findings.test.sh
sh skills/repo-setup/templates/sync_rules.test.sh
sh skills/plan-orchestration/templates/launch.test.sh
sh utils/pin.test.sh
sh utils/verify.test.sh
sh utils/check_skill_layout.test.sh
sh utils/check_rule_inventory.test.sh
sh utils/check_coverage.test.shland.test.shproves the landing on scratch repositories: a clean landing, a conflicting one and its refused rerun, a builder that committed everything, an index lock held while agitprocess runs (the wait stopped at its bound, shortened for the test throughLANDING_LOCK_WAIT), a stale lock removed, a lock gone before the bound, a stop at the bound after the worktree's checkout followed by a rerun that lands, a rerun refused when the landing branch holds a change made by hand or main holds staged changes, a bound that is not a whole number, and a tool directory set on theADAPTline. It checksusage.pyon a Claude Code log and a Codex rollout (the Codex count taken from its assistant messages, which the fixture holds in a number different from itstoken_countevents, and log lines without an offset skipped) and its refusal of a window time without an offset or unreadable. It checks that both exampleplan.yamlfiles carry exactly the keys the state template's configuration block needs, each optional key's value equal to its stated default; inside an Ordo checkout a missing example fails, and only a copy outside one skips the check.check_config.test.shchecks thatcheck_config.pypasses a complete configuration, in both forms, and names each kind of error.collect_findings.test.shruns the collector over reports in the shapes the refuter writes. Findings are dashed or numbered, under numbered or plain headings. A heading may end in a colon, a full stop, closing hashes or a parenthetical. Repair rounds come with and without### Specto### Behavioursubheadings. The Verification, Not checked, Closed, Closures and Usage lists give no finding. Nor does a subheaded round's list before its subheadings, a closure that holds, or an item in one of the forms that report nothing. A closure that does not hold stays a finding, and so does each near miss of those forms. Fences of backticks or tildes of any length are skipped, indented ones included. A continuation line indented with spaces or a tab joins its finding. A report is read once when the archive sits inside the ledger root. With--exclude-listed, the runs the earlier retro lists under "Reports read" are skipped by plan folder, step and run, also after the plan moves into the archive. A round added to a report later is still read. The test also checks the refusals: a retro with no such heading, a missing retro, one that is not UTF-8, and an entry or a run in another form.sync_rules.test.shchecks that a block equal to the template passes, withCLAUDE.mdor the template in LF or CRLF, and that a drifted block fails with its diff and is repaired by--write, which keeps every byte outside the block and writes the block in the ending most of the file's lines use, the first line's on a tie. It checks the refusals, each an exit 2 with oneerror:line on stderr: a missing block, reversed markers, a second begin or end marker, a second block, a missingAGENTS.mdsymlink, a missingCLAUDE.md, aCLAUDE.mdorshared-rules.mdthat is not UTF-8, a missingshared-rules.md, aCLAUDE.mdthat--writecannot write, and a write that does not read back as written.launch.test.shrunslaunch.shwith stubclaude,codexand launch-note commands, in paths that contain spaces. It checks that each recipe runs with its exact arguments and keeps the builder's exit code with no note, an empty note and a note (start, the builder,end, thentranscript). It also covers a resumed session for each harness, the waysstartcan fail to give an id, a launch that returns before its builder ends and survives a hangup, an exit file left by an earlier run, relative files, and every usage error with its message.pin.test.shruns every case under a scratchHOMEwhose path holds a space, writes only under its two scratch roots, and checks that no path a split of a skill folder on a space would name appears. In pin mode it covers the link of every skill of a tag, the removal of a link to a skill the next tag drops, the replacement of a live-clone link for a skill the tag holds, a line printed for each change and none for a write that failed, and the summary line's folders joined by,. Check mode is tested on a link into the live clone, named once, and on a link into the pinned worktree for a skill the tag lacks. Its other cases are the check after linking on a link that could not be made, a pinned worktree deleted by hand and created again while another missing worktree keeps its registration, and the default folders, the$CLAUDE_CONFIG_DIRfolder and both forms ofORDO_SKILL_DIRS, the space-separated form split on spaces and tabs. Each refusal is checked with its message and shown to change nothing: a link into the live clone for a skill the tag lacks, a worktree with local changes, a path that exists and is not a git worktree, a real directory or a foreign link in a skill folder, an unknown tag, anORDO_SKILL_DIRSthat names no folder, a folder that is not an absolute path, and one with leading or trailing whitespace.verify.test.shchecks thatverify.shpasses a list holding a summary test, a plain command and a command written as a folded scalar, and turns red on a summary test that printsPASS:but exits 1, a summary test whose last line does not start withPASS:, and a plain command that exits 1. It checks that a test printingPASS:and exiting 1 is red under each spelling of a pipe intotail:| tail -n 1, two spaces before the pipe, no2>&1, no spaces, a redirection or;aftertail, a comment holding a pipe, a backslash-newline,| grep PASS | tail -1. It checks that a pipe inside quotes runs as written, that a command ending in; trueis judged on its exit status, that a command reads end-of-file from standard input, that quotes, a newline and a carriage return reach a command as written, thatymlandYAMLfences count, and that the run stops at the first red command. Each of INT, HUP, QUIT and TERM stops the running command and its session at once, runs no later command, removes the scratch folder and exits 128 plus the signal number, a command that ignores TERM is killed, and so is a second process group in the command's session. Each state file the runner cannot use exits 64 with its message: a missing file, a file that is not UTF-8, noyamlblock, averify:list that is missing, empty or not a list, a command that is empty, not a string or holds a NUL, a block that is not valid YAML or never closed, and a list found only in a secondyamlblock. A missingpython3, PyYAML,bashorpsexits 69, a command killed by a signal reports exit status 128 plus the signal number, and a scratch folder that cannot be created exits 1. The file runs itself twice, starting the runner withshand, when it is installed, withdash; each run ends with its ownPASS:line, which the outer run checks, and the outerPASS:line names the shells the runner ran under.check_skill_layout.test.shchecks thatcheck_skill_layout.pypasses a completeSKILL.md, and fails one per rule ofdocs/dev/skill-layout.mdit enforces: the frontmatter, the title, the section order, what each section holds, the table headers, bold outside a label, a version tag in a heading; and that headings and bold inside fenced code of any form are not read.check_rule_inventory.test.shchecks thatcheck_rule_inventory.pypasses a complete inventory and fails each error it exists to catch: a header line missing or given twice, a commit that is not a hexadecimal id or not in the repository, an old or new path outside the repository, a missing new file, an old or new file that is not UTF-8, a table header or cell count that is wrong, a row after the table, an old line with text in no row, a range that is malformed, out of bounds or backwards, a range that crosses a blank line, a heading, a frontmatter delimiter or a fence boundary, or opens more than one list item (at any depth, with any marker), table row or frontmatter key, an empty rule, an unknown section or subsection, and an item number of 0 or past the end. It also checks what is not an error: a YAML comment and a fenced~~~line needing a row, a row of dashes that is not a separator, an inventory table without a separator row, a row naming one heading line alone, fenced rows in the inventory, an escaped pipe in a rule, section names holding/or ending in a digit, and fenced lines, nested bullets, indented tables and table headers not counted as items.check_coverage.test.shchecks thatcheck_coverage.pypasses a complete coverage list over scratch skill folders and a scratch roadmap. The passing cases are a hidden file, a nested file, an escaped pipe, the same file name in two skills' sections, and an empty table for an empty folder. So do a skill folder that is a link, fenced lines, a backtick in a fence's info string that opens no fence, and closing hashes. Further passing cases are file names holding U+2028 or U+0085, and file names stored in one Unicode form and listed in the other. On the roadmap side, a lettered heading passes, and so does a done lettered entry written- [x] 4.C.or- [x] 7.D. The check reads only the sections of the skills named on the command line, with a control that reads a malformed one when it is named. A skill named twice on the command line is checked once, so each of its errors is printed once. The check fails a file not listed, listed twice or in the wrong section, a listed path that is no file or is not a plain relative path, and a file cell not in backticks. Each of these marks fails too: an unknown mark, a mark naming a skill the New skills table does not hold, and an empty reason. In a table, a wrong cell count or header, a missing separator row and a row after the table fail. A section with no table fails, as does a missing or repeated section, a fence left open, or a link inside a skill folder. A file whose name holds a newline is reported as one name. In New skills, an entry that is not in the roadmap fails, as does a skill named twice or an empty cell. Such an entry may follow a roadmap heading with a trailing dot after its letter (## 6.B.), a line separator inside a roadmap line, or a last cell ending in\|with no closing pipe. The errors print sorted by line number, then by message. With--built <skill>, the check passes a row markedrebuild: <skill>whose reason names in backticks a repository pathskills/<skill>/<path>that is a file of that folder. Spans that do not startskills/<skill>/are not read, and a span spelled in another Unicode form still matches. A reason that names no span startingskills/<skill>/fails, including one that names only the source skill's path of the same file name. Askills/<skill>/span fails when the folder'sfind -type flisting does not hold it. The failing spans in the test are a missing file, a path not in normal form (skills/paper/./SKILL.md,skills/paper/../paper/SKILL.md) and the folder itself (skills/paper/.,skills/paper/). Further failing spans are a folder inside the skill folder, a case variant of a file's name, and a link to a file outside the repository. A--builtskill that is not a row of New skills fails, as does one with no row markedrebuild: <skill>in the sections read. Controls show that--builtreads only the rows of the skills given to it, reads a repeated skill once, and does not read arebuild later:row. The check exits 2 on each usage error the test exercises. These are no skill argument, a missing skills root, a missing skill folder, a missing coverage list and a missing roadmap. They also include a list that is not UTF-8, a list outside a git repository, and a find that fails. The--builtusage errors are no skill after it, a name that is empty,.or..or holds/, and a skill with no folder underskills/. Every run checks that the check changed nothing under its scratch folder.
The skills themselves are checked against docs/dev/skill-layout.md, from the repository root; the check prints ok: <path> for each skill that follows the layout and each error with its file and line, and exits 0 when every skill passes:
python3 utils/check_skill_layout.pyA plan's verify list, the verify: key of the first yaml or yml block of its orchestrator-state.md, runs through sh utils/verify.sh <state file> from the root of the checkout it checks. The runner needs python3 with PyYAML, bash and ps. It runs each command as written through bash -o pipefail -c, so a pipeline fails when any of its stages fails, and a test that exits non-zero in a pipeline into tail makes the pipeline fail however the pipe is spelled. The runner judges the status the whole command returns, so a command that consumes a pipeline's status itself (!, if, while, ||, or a pipeline sent to the background with &) passes or fails on what it returns.
A command whose text after its last single pipe is tail and its options prints a test's summary: it passes only when it exits 0 and its last line starts with PASS:, and the runner prints that line. Any other command, one ending in ; true included, passes when it exits 0, and the runner prints its whole output. The runner stops at the first red command and prints the command, its exit status and its output.
Each command runs in a session of its own with standard input from /dev/null. On INT, HUP, QUIT or TERM the runner sends TERM to every process group of that session and KILL two seconds later, removes its scratch folder and exits 128 plus the signal number. It behaves the same started with sh, bash or dash. Its exit status:
0: every command passed.1: a command is red, or the scratch folder cannot be created under$TMPDIR(default/tmp).64: no single argument; a state file that cannot be read or is not UTF-8; noyamlblock, or a first one that is never closed or is not valid YAML; noverify:key, averify:key that is not a list, or an empty list; a command that is not a string, is empty or holds a NUL character.69:python3, PyYAML,bashorpsis missing.128plus the signal number: INT, HUP, QUIT or TERM stopped the run.
skills/land/templates/land.sh does the cherry-pick, the checks on main and the booking data as one command. A plan copies it into its ledger folder and makes the three ADAPT edits: landing_tool_path (the directory a step's changes are scoped to), the dependency install and verify commands with their pass rules, and the harness and model names in the usage rows. Copy land.test.sh beside it; it reads landing_tool_path from land.sh and proves the landing on scratch repositories.
land.sh finds usage.py beside itself, then in the land skill's templates/ under the repository's .agents/skills, ~/.agents/skills or $CLAUDE_CONFIG_DIR/skills (default ~/.claude/skills).
This section is for changing Ordo itself. To use the skills, install them as above.
While Ordo is being changed, the installed skills must not change with it: the plan skills run the change, so they stay at a fixed version until the change is done. The installed skills are links into a pinned checkout, a detached git worktree of the clone at a tag, and the clone's main is where the work happens.
git clone https://github.com/TheAxeC/ordo.git ~/workspace/ordo
cd ~/workspace/ordo
utils/pin.sh v1.0.0 # the worktree ~/.local/share/ordo-stable at v1.0.0, every skill linked from it
utils/pin.sh # checks that every link points into the pinned worktree; changes nothingpin.sh links into ~/.claude/skills, ~/.agents/skills and, when it is set, $CLAUDE_CONFIG_DIR/skills, and works with a home folder whose path holds a space. Every skill folder must be an absolute path with no leading or trailing whitespace, and the refusal quotes the folder. ORDO_STABLE moves the worktree. The summary line names the folders joined by , .
ORDO_SKILL_DIRS replaces the list of folders. It is split on spaces and tabs, or read one folder per line when it holds a newline, which is the form for a folder whose path holds a space. A list that names no folder is refused.
Moving to a new version is a tag on main and utils/pin.sh <tag>; going back is utils/pin.sh <older tag>. The pinned worktree is never edited, and pin.sh refuses to move one that has local changes. A pinned worktree deleted by hand is created again at the next utils/pin.sh <tag>, through git worktree add --force, which leaves the registration of every other worktree of the clone as it is.
Check mode fails on a link into the live clone and on a link into the pinned worktree whose skill the tag lacks, and prints each one. Pin mode checks the links before it changes the worktree or any link. A link into the live clone for a skill the tag lacks is a refusal: pin.sh prints it, exits 1 and changes nothing. A link into the live clone for a skill the tag holds is replaced, and a link into the pinned worktree whose skill the tag lacks is removed, each with a line that names it.
MIT. See LICENSE.