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.
Install
npx skillscat add zheref/hatsu/surfaces-codex-ao Install via the SkillsCat registry.
Shared policy location: docs/DISCOVERY.md, docs/WORKFLOW.md,docs/LAUNCH-MIGRATION.md, docs/STANDALONE-ENTRY.md, docs/PROCESS.md and docs/SURFACES.md belong to the resolved Hatsu plugin
root, not the consuming repository. On an installed surface, use the absolute root printed byten 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 withnen/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 barefrom 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> --jsonVerified 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 case |
What ao does |
|---|---|
must-move — on the trunk |
Refuse. 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-dirty |
Refuse, 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-clean |
Proceed to § 3 |
nen wc classifyis 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 reportson-branch-dirtywith the conflicted paths counted among "uncommitted path(s)", saying nothing
about the merge in progress. So check for one before classifying: a.git/MERGE_HEADor.git/rebase-mergepresent means a previous run stopped here, and this run reports that state
rather than starting a second operation on top of it.
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. A dirty tree refuses at exit 2 before any mutation. 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". A conflict exits 1 withconflicted: [{path, ours, theirs}] and the abort line printed, and § 4 takes over.
strategy: merge→ the branch exists on the remote → the base merged in.strategy: rebase→ nothing published → the branch replayed ontoorigin/<base>.
A rebase with nothing to rebase onto still rewrites the SHA, and that is expected. Verified
live: a branch already sitting directly onorigin/<base>'s tip answered "Successfully rebased
and updated" and movedHEADfromc8826c7to84b0444— 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 whileHEADmoved 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 at0.13. The merge half ran throughnen pr cascade-main --repo <path> --trunk <base> --no-push(verified live atv0.5.0,docs/ab/ao.md§ Retired at
nen 0.5) and the rebase half had no verb at any flag. Both halves are nownen wc catch-up's,
which never pushes.cascade-mainkeeps itsmutatingrow in izanami's automation table,--no-pushincluded, 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 hasVerified live against a constructed fixture carrying one of each (docs/ab/ao.md § 2.4):
| Porcelain | Kind | Stages present |
|---|---|---|
UU |
both-modified — both sides changed the same file | 1 base, 2 ours, 3 theirs |
AA |
add-add — both sides created the same path | 2 ours, 3 theirs; no base |
UD / DU |
delete-modify — one side deleted what the other edited | the surviving side only |
DD, AU, UA |
the rarer shapes; classify by the same table and name them | as git reports |
nen wc catch-up --jsoncarriesconflicted[]at exit1, one entry per unmerged path, withoursandtheirsverbatim. What it does not carry is the kind, so the porcelain table above
is still read for that, and § 6 showsours/theirsexactly 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, 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.
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 anAA whose two files are not the same file.
The textbook case, verified live (
docs/ab/ao.md§ 2.4). Base hasretries = 3; the branch
raised it to5; the trunk raised it to9and also movedtimeout. 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:
| Exit | What it means | What ao does |
|---|---|---|
0 |
the message is on stdout | use it — nen commit write --message-file, which validates it again and refuses an empty index |
2 |
refused. A shape violation (undeclared type, empty subject, header over 72, trailing punctuation) or — with --repo — an attribution trailer nen/workflow.json does not admit |
stop. Quote the sentence from stderr, fix the input, re-run. Never commit the file |
1 |
the 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 |
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 useschore(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: oneHatsu-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[]'soursandtheirsas the verb printed them,
withgit 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.jsonrowsemantic-conflict): "A — keepretries = 5(ours)", "B — takeretries = 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
linewc catch-upprinted, 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, andmurasaki'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
- RETIRED at nen
0.5: the merge half (§ 3) is `nen pr cascade-main --repo --trunk --no-push` — verified live, exit `0`, `not pushed (--no-push)` and `--json`'s `noPush: true`. - RETIRED at nen
0.13: the rebase half (§ 3) isnen wc catch-upunderautoor--strategy rebase; nothing in ao types a rebase. - RETIRED at nen
0.13: the published/unpublished test (§ 3) iswc catch-up's own, fetched
first and reported back asstrategy; no tracking ref is read by hand for the decision. - RETIRED at nen
0.5: the conflict enumeration (§ 4),conflicts[]oncascade-main; at0.13wc catch-up'sconflicted[]carriespath,oursandtheirs, the kind still read
off the porcelain table. - RETIRED at nen
0.13: showing both sides (§ 6) isconflicted[]'sours/theirs; only a:1:base stage is stillgit show. - The in-progress-merge check (§ 2) —
.git/MERGE_HEAD/.git/rebase-mergeon disk;nen wc classifyfolds an unresolved merge intoon-branch-dirty(verified live). - RETIRED at nen
0.13: the merge commit itself (§ 5) isnen commit write --message-file,
gated oncommit format's exit code with the two streams kept apart; a rebase finishes by
re-runningwc 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; touchingmain. - 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 whynen wc catch-up, which never pushes, is the verb here andnen pr cascade-mainis 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-upfetches before it decides,
and ao reads thestrategyit 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 theirson 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.