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 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 ask git for one before classifying, never.git/on disk (in a linked worktree.gitis a file):git rev-parse -q --verify MERGE_HEADresolving, orgit rebase --show-current-patchexiting0(a rebase stopped on a patch: a conflict, or aneditstop), 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 ofwc catch-upfrom 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 abreakor a failedexec) is not ao's, so the run reports it with git's own--continue/--abortand 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);128is 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:
| Exit | What it is | ao |
|---|---|---|
1, with a report whose conflicted[] is non-empty | a 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 report | a fetch, status or HEAD read failed, a rebase or merge failed without a conflict, or a --continue failed | stop: 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 rebase | quote 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 all | quote nen's sentence and stop, naming no git command |
2, anything else | a 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 root | quote it verbatim and stop |
| any other exit | unexpected | stop and quote it |
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 on
origin/<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 has
Verified 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 exit1or3, one entry per unmerged path, withoursandtheirsverbatim (and, fromnen.wc.catch-up/v0.2, nen'sclass). 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 (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 fact | Re-derive after catch-up |
|---|---|
| Plugin version | git show HEAD:.claude-plugin/plugin.json and git show HEAD:.codex-plugin/plugin.json after committing the bump |
| Head and base commits | git 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 line | gh pr list --repo <owner/name> --state open --head <branch> --json number,url; use the returned current PR |
| Changelog fragment prerequisites line | re-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 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 |
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[]'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
line
wc 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, 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
- RETIRED at nen
0.5: the merge half (§ 3) is `nen pr cascade-main --repo --trunk - 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 check (§ 2) —
git rev-parse -q --verify MERGE_HEADandgit rebase --show-current-patch, asked of git, never of.git/on disk, which a linked worktree's.gitfile hides;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,$murasakior$endoes 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 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.