CommunityCoding & Developmentgithub.com

zheref/ao

Bring the branch up to date with its base — rebase when nothing has been published, merge when it has, resolve the mechanical conflicts, and stop at G5 with both sides shown for a semantic one. Use when the maintainer invokes $ao [from <base>], asks to pull main in, catch the branch up, or rebase onto main, or whenever $aka, $murasaki or $en needs the branch current. Kurapika classifies every conflicted path before touching one and never picks a side on a conflict of meaning. Never pushes, never force-pushes, never rewrites a commit that is already on the remote.

What is ao?

ao is a Claude Code agent skill that bring the branch up to date with its base — rebase when nothing has been published, merge when it has, resolve the mechanical conflicts, and stop at G5 with both sides shown for a semantic one. Use when the maintainer invokes $ao [from <base>], asks to pull main in, catch the branch up, or rebase onto main, or whenever $aka, $murasaki or $en needs the branch current. Kurapika classifies every conflicted path before touching one and never picks a side on a conflict of meaning. Never pushes, never force-pushes, never rewrites a commit that is already on the remote.

Works with✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/zheref/hatsu/tree/HEAD/surfaces/codex/ao

Installed? Explore more Coding & Development skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Shared policy location: docs/DISCOVERY.md, docs/WORKFLOW.md, docs/LAUNCH-MIGRATION.md, docs/STANDALONE-ENTRY.md, docs/PROCESS.md, docs/using/README.md and docs/using/mirrors.md belong to the resolved Hatsu plugin root, not the consuming repository. On an installed surface, use the absolute root printed by ten to read those files (re-resolve through that skill if unavailable). Relative links below identify source locations; a missing consumer docs/ copy is not a missing policy and must not trigger a duplicate filing. Never copy or invent a second policy in the target repository.

Ao — the branch, caught up

Nature: Manipulator. Reconciling a branch against its base is git-side operation on a body of work that is not only yours, whichever nature authored the diff.

Put the base underneath me, resolve what is only text, and hand me anything that is meaning.

Ao is $murasaki's first step, $aka's third, and the step $en runs when a PR falls behind. It is also invocable alone. The whole of its discipline is one distinction — text versus meaning — and one refusal: it never pushes what it produced.


0. Standalone entry — already total

Ao already reads the checkout rather than a caller. § 2 reads the state before anything, § 3 decides rebase-vs-merge from whether anything has been published, and from <base> carries the base with nen/workflow.json → branch.base underneath it. docs/STANDALONE-ENTRY.md § 4 lists it as already total; the clauses that apply are P1 — $ten ao when no composite ran it, catching up this phase's prerequisites (ten § 6) — and the base resolving against the fetched origin/ ref, which § 3 already requires.

Nothing about a standalone call relaxes § 6: a semantic conflict is a G5 stop with both sides shown, and it is never resolved by picking the side that makes the run continue. Ao still never pushes, never force-pushes, and never rewrites a commit already on the remote.

Hand-back. Next in the wired run: the caller that needed the branch current — $murasaki, $aka or $en. Nothing was pushed, and evidence taken before this catch-up is stale if any tracked path moved.


1. Invocation

$ao [from <base>]
nen parse ao --grammar "from [<base>]" --line "<the invocation, minus the $ao prefix>"

Verified live at v0.3.0 (docs/ab/ao.md § 2.1): from release/1.4 → base: release/1.4; a bare from parses with the clause absent, exit 0. The clause is anchored behind a literal for the reason $spiritual-message § 1 records.

With no clause the base is nen/workflow.json → branch.base, default main when the key or the file is absent. Name the base out loud before fetching it — a branch quietly caught up against the wrong base is a diff nobody can read afterwards.

A missing argument or configuration item is asked for and set up inline (missing-argument, missing-configuration; WORKFLOW.md § 4 Ask, set up, continue).

2. Read the checkout first

nen wc classify --repo <path> --base <base> --json

Verified live at v0.3.0 (docs/ab/ao.md § 2.2), --json carries exactly what this skill needs before it touches anything: state.branch, state.isTrunk, state.dirty, state.aheadOfBase, state.existingCommitSubjects[], state.uncommittedPaths[].

nen wc classify caseWhat ao does
must-move — on the trunkRefuse. There is nothing to catch up: the trunk is the base. Say so and hand the caller to $breath or $tensho, whichever the situation wants
on-branch-dirtyRefuse, naming the uncommitted paths the verb printed. A merge or a rebase over uncommitted work either refuses at git's own hands or buries the work in a conflict nobody can untangle. Commit it ($kokusen) or stash it deliberately, then re-run
on-branch-cleanProceed to § 3

nen wc classify is not a conflict detector, and a half-finished merge reads as ordinary dirt — verified live (docs/ab/ao.md § 2.2). Run inside an unresolved merge it reports on-branch-dirty with the conflicted paths counted among "uncommitted path(s)", saying nothing about the merge in progress. So ask git for one before classifying, never .git/ on disk (in a linked worktree .git is a file): git rev-parse -q --verify MERGE_HEAD resolving, or git rebase --show-current-patch exiting 0 (a rebase stopped on a patch: a conflict, or an edit stop), means a previous operation stopped here, and this run reports that state and starts nothing — never a second operation on top of it, and never a re-run of wc catch-up from here, which would commit a merge with git's default message and no re-proved build (§ 5). The probe's other answers: 1 (a rebase paused with no current patch, at a break or a failed exec) is not ao's, so the run reports it with git's own --continue / --abort and stops here, never relying on catch-up to refuse it, which a host still on nen 0.18.0 or 0.18.1 does not (zheref/hatsu#182); 128 is no rebase, so classify; anything else is left to § 3's unanswered-probe row.

3. Which operation — rebase or merge, decided by whether anything was published

Rebase when nothing on this branch has been published. Merge when something has. Rewriting a commit somebody else may already have fetched is the one git operation that costs other people work, and the local plane never does it.

nen wc catch-up --repo <path> --base <base> [--strategy rebase|merge|auto] [--dry-run] [--json]

The verb fetches first, then decides, and the decision is made against what origin says now, never against what this checkout last heard. auto (what ao passes) rebases when nothing of the branch is on @{upstream} and merges otherwise; --strategy rebase|merge names one only where the caller has a reason, said out loud. Exit 0 reports {base, strategy, before, after, behindBefore, aheadBefore, noOp}, and a noOp: true is still reported with both SHAs, never as "nothing to do". Every other exit, read off nen v0.20.0:

ExitWhat it isao
1, with a report whose conflicted[] is non-emptya conflict; the abort line is printed§ 4 takes over
3, a report whose conflicted[] paths are all in nen/contract.json's top-level mechanical block (v0.20.0)a conflict nen classed manifest, changelog or mirror; the abort line and each class's commands are printed, none run§ 4 takes over, nen's classes and commands quoted as evidence; § 4 still classifies every path itself
1, no reporta fetch, status or HEAD read failed, a rebase or merge failed without a conflict, or a --continue failedstop: quote nen's stderr sentence verbatim, say the tree is as git left it; never § 4
2, a rebase paused at a break or a failed exec (v0.18.2)not ao's rebasequote nen's sentence, which names git rebase --continue / --abort; never continue or abort it
2, a rebase probe git did not answer (v0.18.2)there may be no rebase at allquote nen's sentence and stop, naming no git command
2, anything elsea refusal before any mutation: a dirty tree, a --base git rejects, a --strategy that disagrees with the operation in progress, a git too old for --end-of-options, no repository rootquote it verbatim and stop
any other exitunexpectedstop and quote it
  • strategy: merge → the branch exists on the remote → the base merged in.
  • strategy: rebase → nothing published → the branch replayed onto origin/<base>.

A rebase with nothing to rebase onto still rewrites the SHA, and that is expected. Verified live: a branch already sitting directly on origin/<base>'s tip answered "Successfully rebased and updated" and moved HEAD from c8826c7 to 84b0444 — same tree, same message, new commit object, because the rebase replays the commit rather than noticing it need not. It is harmless here and only here: § 3 rebases only an unpublished branch, so the rewritten commit is one nobody has fetched. It is not harmless to report as "nothing to do" — § 7 asks for the base and its resolved SHA, and a run that says no change while HEAD moved leaves every later reference to the old SHA silently wrong.

Say which one the verb chose and why, in one line, from its report, and name the refresh: "origin fetched; the branch is on @{upstream}, so strategy: merge, not a rebase."

RETIRED at nen 0.5, superseded at 0.13. The merge half ran through nen pr cascade-main --repo <path> --trunk <base> --no-push (verified live at v0.5.0, docs/ab/ao.md § Retired at nen 0.5) and the rebase half had no verb at any flag. Both halves are now nen wc catch-up's, which never pushes. cascade-main keeps its mutating row in izanami's automation table, --no-push included, and is no longer what ao calls.

4. Classify every conflicted path before resolving one

git -C <path> status --porcelain                    # the conflict rows
git -C <path> diff --name-only --diff-filter=U      # the conflicted paths, alone
git -C <path> ls-files -u                           # which stages each path has

Verified live against a constructed fixture carrying one of each (docs/ab/ao.md § 2.4):

PorcelainKindStages present
UUboth-modified — both sides changed the same file1 base, 2 ours, 3 theirs
AAadd-add — both sides created the same path2 ours, 3 theirs; no base
UD / DUdelete-modify — one side deleted what the other editedthe surviving side only
DD, AU, UAthe rarer shapes; classify by the same table and name themas git reports

nen wc catch-up --json carries conflicted[] at exit 1 or 3, one entry per unmerged path, with ours and theirs verbatim (and, from nen.wc.catch-up/v0.2, nen's class). What it does not carry is the kind, so the porcelain table above is still read for that, and § 6 shows ours/theirs exactly as the verb printed them. The verb's own rule is the one worth quoting where it lands: it never picks a side.

Classify every path before resolving any of them, and print the table. A run that resolves the first three and discovers the fourth is semantic has already spent the maintainer's trust on the first three.

5. Mechanical, or semantic

Mechanical — the resolution is determined, by the repository, not chosen by anyone:

  • an append-only region both sides appended to (an import block, a .gitignore, a changelog list) — keep both, in the order the file's own convention gives;
  • a generated or locked file the repository regenerates (a lockfile, a snapshot, a mirror) — take the base's side and regenerate (Hatsu mirrors: bash scripts/surface_mirror_regenerate.sh), then prove it with the declared build (nen shu build; the exit table is $rasengan § 6's);
  • a pure formatting or whitespace divergence with no behavioural difference;
  • an identical change made on both sides.

A Hatsu version collision can survive a clean catch-up: parallel branches may have chosen the same number. After every catch-up, compare both actual manifests (.claude-plugin/plugin.json and .codex-plugin/plugin.json) with the fresh base. If the branch's version is equal to or lower than the base's, re-bump both to the same version strictly above that base before readiness; update the version line in this effort's existing changelog.d/ fragment too. This is Hatsu authoring, not an automatic nen wc catch-up re-bump. After the next version has been decided, run bash scripts/plugin_version_refs.sh --from <old> --to <new> --body-file <local-draft> to list tracked references and draft lines. Add --write to rewrite declared tracked forms and every exact old-version token on unambiguous draft lines; read the printed review lines, which stay unchanged. The helper never chooses the version, rewrites generated mirrors or edits a remote PR. Regenerate the mirrors with the command above, then refresh the catch-up-invalidated body facts:

Body factRe-derive after catch-up
Plugin versiongit show HEAD:.claude-plugin/plugin.json and git show HEAD:.codex-plugin/plugin.json after committing the bump
Head and base commitsgit rev-parse HEAD origin/<base> after the final commit and fetch
Figures measured against the base (bytes, diff, coverage, review evidence)re-measure against that fresh base and final head
PR number in the Drive linegh pr list --repo <owner/name> --state open --head <branch> --json number,url; use the returned current PR
Changelog fragment prerequisites linere-measure against the caught-up dependency contract and this effort's actual prerequisites

Review the refreshed local draft, then use nen pr edit-body as the remote writer. Re-run the bump check against the fresh base; the original branch point cannot prove this comparison.

Semantic — anything where a person has to prefer one intent to another. UD/DU is always semantic: "deleted" and "still being edited" are two beliefs about whether a thing exists, and no merge algorithm holds the belief. So is a UU where both sides changed the same behaviour, and an AA whose two files are not the same file.

The textbook case, verified live (docs/ab/ao.md § 2.4). Base has retries = 3; the branch raised it to 5; the trunk raised it to 9 and also moved timeout. Textually this is one small hunk. Semantically it is two people deciding a policy number, and taking either side silently ships somebody's decision that nobody made. git show :1: / :2: / :3: on the path prints the three versions exactly — base, ours, theirs — and that is what the stop shows.

Under ten § 6's unasked catch-up, two classes are always semantic whatever their shape (ruling 2026-09-28, ROSTER ruling 9): a conflicted path matched by nen/workflow.json → review.scopes.security.paths, and any lockfile — the base's side is not taken and nothing is regenerated without the maintainer seeing both sides, because a silent resolution there is a supply-chain or credential decision nobody made.

Resolve the mechanical ones, one at a time, saying what each was and why the resolution was determined. Then re-run the declared build before the merge is committed — a green merge that was never built is a claim, not a result.

Then ao commits it. This is the one commit ao makes and it has always been implied — § 5 says "before the merge is committed" and the hard limits say "never commits a resolved merge without re-proving the declared build" — so it is written out here rather than left to be inferred:

nen commit format --repo <path> --type chore --scope merge \
  --subject "bring origin/<base> into <descriptor>" \
  --body "<one line per conflicted path: kind, and why the resolution was determined>" \
  --trailer "Hatsu-Agent=<responsible-persona>" > <msg-file> # exit 0 REQUIRED before the next line
nen commit write --repo <path> --message-file <msg-file> [--dry-run] [--json]   # -> {sha, subject, trailers}

Gate the second line on the first's exit code, and keep the two streams apart. The refusal goes to stderr with nothing on stdout — verified live at the pin: a 87-character header exits 2 with 0 bytes on stdout and the sentence on stderr, while the accepted message exits 0 with 0 bytes on stderr. So 2>&1 into the message file commits the refusal as the message, and a plain redirect commits an empty one. Both happened; the second was repaired by an amend that was only safe because origin had not seen the commit (docs/ab/mukai.md). The reading is kokusen § 6's and holds identically here:

ExitWhat it meansWhat ao does
0the message is on stdoutuse it — nen commit write --message-file, which validates it again and refuses an empty index
2refused. A shape violation (undeclared type, empty subject, header over 72, trailing punctuation) or — with --repo — an attribution trailer nen/workflow.json does not admitstop. Quote the sentence from stderr, fix the input, re-run. Never commit the file
1the trailer policy could not be read — nen/workflow.json present and malformed (with --repo)stop. The message is unshaped because the policy is unreadable; that is a repository defect to report, not to commit past

nen commit write exit 3 (nen 0.19, zheref/nen#357): the merge commit exists, and a hook put a refused trailer on it (injected[]). The run takes row injected-attribution-trailer, and commit write is never re-run. The undo nen prints (reset --soft HEAD~1) would drop the merge's second parent, and a re-commit would then be a non-merge commit. So the stop is handed to the maintainer with the merge commit's sha: the switch (row harness-attribution-switch) first, then this catch-up again from the pre-merge tip. An exit 1 whose stderr begins "committed" is the same reading, the trailers unchecked: the merge commit exists and is never committed again. Any other exit 1 committed nothing.

RETIRED at nen 0.5: pass --repo <path> and the binary enforces the house rule. Verified live at the pin against this repository: --trailer "Co-Authored-By=someone" is refused at exit 2 naming the file and the keys it admits, while a truthful --trailer "Hatsu-Agent=<responsible-persona>" renders at exit 0. The policy is opened only when the invocation carries a --trailer, so --repo is not optional here: without it there is no policy to open and nothing is refused. The caller's own restraint is still the layer that survives a forgotten flag.

Why ao shapes the message at all: git's default is "Merge remote-tracking branch 'origin/main' into ", which is not Conventional Commits, and a repository whose own history uses chore(merge): … for a hand-made merge (nen's does) gets a stranger's commit in the middle of it otherwise. The trailer rule is nen/workflow.json → commits, exactly as anywhere else: one Hatsu-Agent, no AI attribution trailer. Akatsuki-Agent is the CI plane's key and ao never writes it — policy admits both so one hook passes a commit from either plane, which is not permission (kokusen § 5).

A rebase has no merge commit, and ao makes none: once the mechanical resolutions are staged, nen wc catch-up is re-run on the same tree with the same --strategy to finish the operation in progress, each replayed commit keeping its own message. The build is re-proved the same way before the rebase is allowed to finish.

A mechanical conflict, and a base that merely moved, are resolved and caught up without a word (nen/decisions.json row stale-base).

6. A semantic conflict is a stop, at G5

Never pick a side. Not "ours is newer", not "theirs is bigger", not -X ours, not -X theirs, and never a blend that is neither side's intent.

The stop is $jutaisho's shape, in full — the nen stop banner and efforts table (nen stop --who Kurapika --gate G5 <efforts.md>, verified live, docs/ab/ao.md § 2.5), the $spiritual-message report's link, lettered options with a ⭐ on the report, and the question through the surface's own option picker. What this skill adds to it:

  • Both sides shown, verbatim — conflicted[]'s ours and theirs as the verb printed them, with git show :1:<path> where a base stage exists — never summarised, never paraphrased into "they disagree about retries".
  • The options are the actual resolutions — four of them, by the maintainer's ruling of 2026-09-19 (nen/decisions.json row semantic-conflict): "A — keep retries = 5 (ours)", "B — take retries = 9 (theirs)", "C — 9 with the branch's timeout" (a genuine third the run proposes), and "D — resolve it and show me the diff before anything is committed" — the run types, the maintainer decides. This is always Crazy Slots; the run never picks a side.
  • The tree is left conflicted, and that is stated, with the one-line undo printed: the abort line wc catch-up printed, quoted as it came. Leaving it is deliberate — the conflict markers and the three stages are what the maintainer needs in front of them — but a checkout left mid-merge without anyone saying so is a trap.

The run ends there. A G5 stop is not a thing to retry past; the answer resumes it.

7. Then hand it back — never pushed

Ao finishes with a branch that is current and a working copy that is clean, and nothing on the remote has moved. Push is $aka's, on the maintainer's call, and murasaki's third step for a branch that is already published. Ao invoked from inside either of them still does not push: the caller pushes, after ao returns, having seen what ao did.

Report, in one line: the base and its resolved SHA, rebase-or-merge and why, the conflicts by kind, which were mechanical and how each was resolved, that the build was re-proved, and — where the operation was a merge — the merge commit ao made: its SHA and its two parents (§ 5). "…build re-proved bun run typecheck exit 0; merged as 2d4d5ed, parents 84b0444 cda6b03. Nothing pushed by ao." The commit is the whole of what ao left behind that the caller has to publish, so a report that omits it leaves the caller guessing what its own push is about to move.

Where the operation was a rebase, report HEAD before and after — even when nothing was replayed onto anything. § 3's note is why: the SHA moves whether or not the content did, so the line reads "rebased onto origin/main at 675cbda; no commits to replay and no conflicts; HEAD c8826c7 → 84b0444" rather than "already up to date". Anything holding the old SHA — a report already rendered, a message the maintainer is reading, a range somebody typed — is stale from that moment, and the pair of SHAs is the cheapest possible way to say so.

Residue

  1. RETIRED at nen 0.5: the merge half (§ 3) is `nen pr cascade-main --repo --trunk
  2. RETIRED at nen 0.13: the rebase half (§ 3) is nen wc catch-up under auto or --strategy rebase; nothing in ao types a rebase.
  3. RETIRED at nen 0.13: the published/unpublished test (§ 3) is wc catch-up's own, fetched first and reported back as strategy; no tracking ref is read by hand for the decision.
  4. RETIRED at nen 0.5: the conflict enumeration (§ 4), conflicts[] on cascade-main; at 0.13 wc catch-up's conflicted[] carries path, ours and theirs, the kind still read off the porcelain table.
  5. RETIRED at nen 0.13: showing both sides (§ 6) is conflicted[]'s ours/theirs; only a :1: base stage is still git show.
  6. The in-progress check (§ 2) — git rev-parse -q --verify MERGE_HEAD and git rebase --show-current-patch, asked of git, never of .git/ on disk, which a linked worktree's .git file hides; nen wc classify folds an unresolved merge into on-branch-dirty (verified live).
  7. RETIRED at nen 0.13: the merge commit itself (§ 5) is nen commit write --message-file, gated on commit format's exit code with the two streams kept apart; a rebase finishes by re-running wc catch-up.

Every one of these is run in the open and reported as by-hand, per the Nen-first rule's second half (claude/agents/kurapika.md): a missing verb is a finding, not a gap to route around silently.

Authority

  • Permitted: fetch; rebase an unpublished branch; merge the base into a published one; resolve a mechanical conflict; re-run the declared build; commit the resolved merge (§ 5, one commit, chore(merge), after the build is green); write the working copy and the local branch ref.
  • Not permitted: push of any kind, force-push above all; rewriting any commit that exists on the remote; resolving a semantic conflict; --no-verify; touching main.
  • Carries no delegation, and being invoked inside $aka, $murasaki or $en does not lend it one.

Hard limits

  • Never pushes. Not on a clean merge, not "to save a step", not because the caller was going to push anyway. This is why nen wc catch-up, which never pushes, is the verb here and nen pr cascade-main is not.
  • Never rewrites a published commit — the published/unpublished test (§ 3) runs before either operation, every time, and its answer is stated.
  • Never answers that test from a stale tracking ref. wc catch-up fetches before it decides, and ao reads the strategy it reported, never a ref this checkout last heard about: letting one select the rebase path is how a published branch gets rewritten.
  • Never picks a side on a semantic conflict, and never -X ours / -X theirs on any merge.
  • Never resolves anything before every conflicted path is classified and the table printed (§ 4).
  • Never starts a second operation on top of a merge or rebase somebody already left in progress.
  • Never commits a resolved merge without re-proving the declared build.
  • Never leaves a conflicted checkout without saying so and printing the abort line.
  • Never presents by-hand git as a verb's output — every step in § Residue is named where it runs.

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Related Skills