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.
Aka — the branch goes out
Nature: Manipulator. Pushing is git-side operation on shared state by definition, whichever nature authored the diff.
Lint it, fold only unpublished history, put the base underneath it, re-lint if that moved the tree, and put that exact tree on the remote. No PR. No project-wide tests.
Aka is the first human call in the local plane. Everything before it —
hatsu:ren's turns, hatsu:kokusen's commits — happens
without asking, because none of it leaves the machine. Aka is where the work becomes something
another person can fetch, and that boundary is the maintainer's to cross.
It opens no pull request. That is hatsu:mukai's run, and
hatsu:shibari's step inside it. Aka ends with a pushed branch and nothing
else, deliberately: a push and a PR are two decisions, and folding them makes one of them
unaskable.
0. Standalone entry — this skill was ALWAYS the maintainer's call
Aka has no wired entry. § 1 already says it: the whole skill runs only on the maintainer's publish
call, and agents never propose it. The first carve-out, the maintainer's ruling of 2026-09-28 (b)
(docs/ROSTER.md § Rulings of 2026-09-26/27/28): a then step naming
this skill in a typed futon invocation is the maintainer's publish call —
the advance go mugetsu § 3 defines — still never prompted for, never proposed. docs/STANDALONE-ENTRY.md § 6 names it among the phases a report, a bell,
a stop's options and a composite must never offer — and a standalone run is precisely where such an offer
would read as the pipeline knowing best.
Second carve-out — a typed composite's delegated publication — the maintainer's ruling
HR-20 (zheref/hatsu#332), ratifying docs/ROSTER.md § Reading of 2026-10-19 — a typed composite's delegated publication unchanged. It stands on two
rulings already made: kazaana's delegate authors and publishes each queued issue (ROSTER § Rulings
of 2026-10-13, ruling 1), and a futon effort on an integration branch runs aka to publish its own effort branch (ROSTER
§ Rulings of 2026-09-30 — En never merges, row Where an effort stops); on the stacked or per-issue
shape its push is futon § 4 → build § 5's. A typed
futon or kazaana invocation is the maintainer's
publish call for the working branches that run authors — one standing call, never one per commit:
| The scope of a typed futon / kazaana line | |
|---|---|
| Covered | per effort in the run's queue: its initial publication (this skill, in the builder delegate or the session — kazaana § 3; build § 5) and every maintenance commit and push that delivers it — a review fix, a catch-up, a reviewed repair (murasaki § 6 inside mukai and en) — to the run's target repository and that effort's own working branch, through this skill's and murasaki's gates unchanged |
| Not covered | another remote or repository, another branch, branch.base, a force form or a rewrite of a published commit, a tag, a merge, a release. Outside the Covered row a push stands or falls on its owning skill's own rule: murasaki § 6 for a branch already published; futon § 5's integration branch, pushed only with nen wc publish, never through aka and never forced (ROSTER § Rulings of 2026-09-30 — En never merges); getsuga § 3/§ 3a for the proposal branch and § 4 (The cut) for the tag. A stacked child's retarget is the merge plan's (WORKFLOW.md § Stacked pull requests rule 5; ROSTER § Rulings of 2026-10-12). Where no skill owns an act, it is not performed, and is reported; it is a G5 only where it is an act only the maintainer can do |
| Lapses | when the run ends (futon § 10), at a halt, or a G5 that stops the run, or on the maintainer's word — never because the commit changed; an effort's own G5 lapses that effort's scope only — never another's — and it resumes when the maintainer answers; a G2/G4 merge prompt lapses nothing (as mugetsu § 3). A resumed, restarted or compacted session holds it only on a fresh typed invocation, never from a checkpoint, a run record or a summary |
The scope is the destination, never the SHA. A new commit on the same branch of the same repository
is inside it; asking again because the commit moved is the failure #332 records. Every handoff carries
it — parent to builder delegate (kazaana § 3) and every maintenance step after (mukai, en,
murasaki): the five-fact record — the invocation quoted verbatim, any maintainer
answer the push also stands on, the repository, the working branch, and scope: initial publication and
maintenance pushes to this branch until the run ends. A checkpoint, a run record or a
later handoff never narrows it to an exact commit; where an older line says otherwise, the later
correction replaces it rather than standing beside it.
The record survives every handoff, maintenance ones included, the same on every surface
(zheref/hatsu#529). This paragraph widens none of the three rows. A
prerequisite outside the selector's queue is not Covered by the typed line: its push stands only on the
maintainer's own words authorizing that publication (§ 1's call, in their own words), quoted as the
second fact, on the run's target repository — an answer approving only its preparation authorizes no
push, and is asked once. An idle run's integration delivery stays futon § 5's (Not covered above); its
request carries the same facts and cites futon § 5 as its authority. A routine push that needs the
host's approval presents that record in the request — on Codex, the justification of its supported
review route (docs/surfaces/codex.md § Publication requests) — so
a changed actor, worktree or commit is no reason to ask the maintainer again; what the host decides
stays the host's. The record is evidence for the reviewer, never a self-granted approval: an
out-of-scope destination, the protected base, a rewrite or a release act stays refused or behind its
own gate, and an actual rejection is the next paragraph's. The bounded cases are docs/ab/publication-authority.md.
A host refusal stays the host's. Where the harness's approval layer refuses a push inside the scope, the refusal is quoted to the maintainer with the invocation and the scope, and resolved only through that host's supported approval path — the maintainer's act: their approval there, or one re-submission through that same route carrying their newly given words quoted beside the record (zheref/hatsu#529) — never a re-attempt on evidence alone, never another command, route or surface to the same push, never a wider grant asked for, never a permission setting touched. An exact approval given there stays a valid receipt for its own action, and widens nothing.
A representative trajectory. hatsu:kazaana zheref/nen → the delegate's aka first-publishes
<model>/kurapika/<slug> at commit A → mukai opens the PR → en: a reviewed finding is fixed,
kokusen commits B, murasaki pushes B to the same branch. Both pushes stand on the one typed line
and neither asks; B pushed to any other branch is outside, and stands or falls on the Not-covered row.
The two carve-outs stay separate. A then aka step is the first carve-out's single-use go; the second
covers only the per-effort pushes the composite's engine makes. Standalone hatsu:aka is unchanged —
one call, one run (§ 1) — and neither carve-out reaches a merge into branch.base (en § 5's prompt) or a
release (getsuga, mugetsu).
P1 applies — hatsu:ten aka, which catches up this phase's prerequisites (ten § 6) — and P2 belongs in the report
before the squash: the branch, clean-or-dirty, how many commits are unpushed, and which are already on
the remote. § 4 squashes only unpushed commits and that boundary is read from the remote, never
remembered.
Nothing else changes. Aka still runs no project-wide tests and no coverage —
hatsu:kotoamatsukami and hatsu:byakugan own
those at mukai — still stops at G5 on a red required check, and still never force-pushes or
rewrites a commit already on the remote.
Hand-back. Next in the wired run: hatsu:mukai — review, impacted tests, coverage, evidence and
the PR. It is yours to call, and this run does not ask for it.
1. Invocation — and who is allowed to say it
hatsu:aka
No arguments, so there is nothing for nen parse to own — and saying so is the point, not an
omission. nen parse <skill> --grammar <template> --line <text> exists for a grammar; a bare verb
has none, and inventing an optional clause so that a parse can be echoed would be ceremony.
The call is the maintainer's, in their own words or by name. Two consequences, both binding:
- No agent ever prompts for it. Not
hatsu:jutaishoat the end of a turn, nothatsu:spiritual-messagein a report, not a composite offering it as a next step, not a lettered option in a stop. A report may say the branch is ready to go out; it may not say shall I push it? An agent that asks for permission it was told to wait for has converted a human call into a nudge, and the nudge is what the local plane's push discipline exists to prevent. - One call, one run. A
yesfor this push is not authority for the next one. Say the run has started and say when it ends. The one standing call is a typed composite's, scoped by destination (§ 0, second carve-out: a futon or kazaana line, whose efforts publish through build § 5); a standalone build's push stands on build § 5's own run-scoped delegation, not this carve-out.
2. The parameters — nen/workflow.json, with the defaults stated
| Key | Used for | Default when the key (or the file) is absent |
|---|---|---|
branch.base | the base § 4's squash and § 5's catch-up work against | main |
iteration.lane | which lane § 3 and § 6 lint | project.defaultLane |
commits.allowedAttributionTrailers | canonical persona/plane trailers the commit may carry | Hatsu-Agent, Akatsuki-Agent |
commits.forbiddenTrailers | attribution trailers that refuse the commit outright | includes Co-Authored-By, Claude-Session, Signed-off-by, Generated-by, Generated-with, Reviewed-by |
nen schema check --repo <path> VALIDATES this file at the pinned build — verified live, the row
reads ok nen/workflow.json coverage 80/85/90 (touched), branch '{model}/{persona}/{descriptor}' off 'main', checks: lint. A malformed key is a FAIL by pointer, so this skill no longer checks
the shape by eye; it reads the values, and states the defaults whenever they are what applied.
A missing argument or configuration item is asked for and set up inline (missing-argument,
missing-configuration; WORKFLOW.md § 4 Ask, set up, continue); a
default above that covers the key is used and stated, never asked about. § 4's refusals are not gaps:
rewriting published history, a forbidden trailer and a push of the trunk still refuse, never guessed past.
Declared change from
claude/agents/kurapika.md§ How you work — named, not slipped in. The agent definition today reads "No AI attribution beyond the trailers the maintainer's own harness mandates — todayCo-Authored-By:andClaude-Session:… you neither add attribution of your own nor strip theirs", and records that the final rule is a constitutional question left open.nen/workflow.jsonis where the maintainer answers it for a given repository, and this workflow answers it:Hatsu-AgentandAkatsuki-Agentare admitted, one per plane, and the other two are forbidden. Where the two disagree, the repository's owncommitsblock wins for that repository, because it is the maintainer's recorded decision rather than an unresolved tension. A repository that ships nocommitsblock gets the defaults above, and they say the same thing. This is a declared process change, disclosed here the wayhatsu:futon§ Declared process change discloses its own.
3. Pre-squash lint gate
Before nen wc squash can rewrite even unpublished commits, run the declared lint row for
iteration.lane:
nen shu lint --repo <path> --lane <iteration.lane> --dry-run
nen shu lint --repo <path> --lane <iteration.lane>
Dry-run once on an unfamiliar repository. A red lint ends the run before history changes. A seat is
quoted. This gate is deliberately before the squash; it is cheap and prevents aka from replacing
several useful checkpoints with one red commit. It is hatsu:gyo — the
linting process — not the regression phase.
Aka does not run tests. Focused tests already ran at kokusen on every ren turn.
Project-wide tests are hatsu:kotoamatsukami's and coverage is
hatsu:byakugan's, at mukai. Linting is hatsu:gyo's
— this skill runs it before squash and again after catch-up if the tree moved.
The push report says so rather than inheriting a proof nothing here produced: "tests: not due
until mukai (kotoamatsukami); coverage: not due until mukai (byakugan); this run runs gyo (lint),
squashes, catches up and pushes."
4. Squash the unpushed commits into one
A branch that leaves the machine leaves as one commit per effort, not as the fourteen turns
hatsu:ren produced getting there.
Which commits are "unpushed" is decided by whether the branch is published, and getting this wrong is a force-push:
| Branch state | Squash range | Why |
|---|---|---|
| Not on origin — first publish | everything since git merge-base origin/<base> HEAD | nothing has been fetched by anyone; all of it is still yours to shape |
| Already on origin | only the commits after origin/<branch> | anything at or below origin/<branch> is somebody else's history now |
# 1. the base must be fresh — the merge base is computed against it
git -C <path> fetch origin <base>
# 2. published? ASK THE REMOTE. This is the default form.
git -C <path> ls-remote --heads origin refs/heads/<branch> # empty output = not published
# 3. PUBLISHED ROW ONLY: ls-remote read the ref and transferred nothing. Make the object local;
# the verb proves it is behind HEAD before it is allowed to be the squash point.
git -C <path> cat-file -e <the SHA ls-remote printed>^{commit} # non-zero → the next line
git -C <path> fetch origin <branch>
# 4. the squash, --onto the point the table names — only after every commit it folds proves the run's
# provenance (zheref/hatsu#170): the squash rewrites authorship, so the check runs first.
# <onto> is the SHA ls-remote printed; on a first publish, the SHA the next line prints.
git -C <path> merge-base origin/<base> HEAD # first publish only
"$hatsu_root/scripts/branch_authorship_check.sh" --repo <path> --base <onto> --require-agent <responsible-persona> --trunk origin/<base> \
&& nen wc squash --repo <path> --onto <onto> --message-file <file>
No $(…) and no || to compute or branch a step — the SHA is read, then typed — while the
authorship gate stays chained to the write it gates (WORKFLOW.md § Writing a skill block, #218, #170).
Never pipe branch_authorship_check.sh through another command: its own exit code is the gate,
and the squash runs only when that exit is 0. A pipe to tail can hide a refusal.
Step 4's check routes as § 7's step 0b does: exit 1 reports every SHA it names, nothing is
squashed and the maintainer decides; exit 2 is a wiring stop, the script's sentence quoted,
nothing squashed.
ls-remote is the stated default, and the fetch of <branch> is the fallback. The two are not
equivalent and the difference is not stylistic:
-
ls-remoteanswers the question that was actually asked. Is this branch on origin, and at what SHA? — asked of origin, answered by origin, in one line, touching no local ref. On a first publish it exits0with empty output, which is a clean answer rather than a failure to interpret. And the SHA it prints is the squash point for the published row: norefs/remotes/origin/<branch>is consulted at all, so there is no stale tracking ref left in the decision to go wrong.But
ls-remotereads the ref advertisement and downloads no objects, so step 3 is not optional. On a checkout that has never fetched<branch>— a--single-branchclone, a machine that did not make the push, a branch advanced from somewhere else — the SHA it printed is not in the local object database, and using it directly dies:fatal: Could not parse object '<sha>', exit128, reproduced live against a--single-branchclone overfile://.git cat-file -e <sha>^{commit}is the cheap test andgit fetch origin <branch>is the cure; neither consults the tracking ref for the decision, so the property above survives intact. And a SHA that is present but is not an ancestor ofHEADis the worse case — a blind reset would take it and silently drop every commit in between — which is why the verb's own ancestry check runs before it resets and is § 4's third refusal below. -
git fetch origin <base> <branch>is the fallback, for a remote or a host wherels-remoteis not available or not permitted. It fetches the base — which step 1 needs anyway — and, when<branch>is not on the remote, exits non-zero withfatal: couldn't find remote ref <branch>. That failure is the answer, not an error, and it is tolerated. But it is a noisy way to be told no: the line looks alarming in a transcript, and a network failure and an absent branch reach this run as the same non-zero. Where the fallback is what ran, the published test isgit rev-parse --verify --quiet refs/remotes/origin/<branch>after that fetch, never before it.
Either way, a stale tracking ref must never select the first-publish range.
refs/remotes/origin/<branch> is what this checkout last heard, not what origin has: a branch
pushed from another machine, a worktree cut before the first publish, a --single-branch clone all
leave it missing or behind. Read it unrefreshed and an already-published branch takes the
first-publish row — every commit since the merge base is squashed away, history that other people
have already fetched is rewritten locally, and the push then fails non-fast-forward with the damage
already done. The default form sidesteps that hazard by never reading the ref; the fallback survives
it only by refreshing first.
State which form was used and what it answered, in the same line that names the squash range.
Then shape the one message with the verb that owns message shape:
nen commit format --type <feat|fix|chore|docs|refactor|test|perf|build|ci> \
--scope <scope> --subject "<short imperative subject>" \
--body "<what changed and why>" --repo <path> \
--trailer "Hatsu-Agent=<responsible-persona>"
The trailer must be in this message file: nen wc squash adds no trailer to it.
Verified live at v0.3.0 (docs/ab/aka.md § 2.2): the multi-line form renders subject, body and
trailer at exit 0; shape violations (a bad type, an empty subject, a header over 72 characters, a
trailing period) each refuse at exit 2 with a named reason — the transcripts
hatsu:tensho § 4 already records, re-confirmed here at this pin.
Then the message file, and the gate on it. The verb writes the message to stdout and its refusal to stderr, and at exit
2stdout is empty — verified live,0bytes out, the sentence on stderr; a clean run is the mirror,0bytes on stderr. So:nen commit format … > <message file> # exit 0 REQUIRED before the next line; NEVER 2>&1 nen wc squash --repo <path> --onto <ref> --message-file <message file>Read the exit code before committing, and never merge the streams into that file.
2>&1commits the refusal as the message — which happened, landing "nen: header line is 75 characters, over the 72-character convention" as a commit subject (docs/ab/mukai.md) — and a plain redirect that ignores the code commits an empty file. Exit0→ use it. Exit2→ stop, the message was refused and the reason is on stderr; fix the input and re-run. Exit1→ the trailer policy could not be read (v0.4.0+ with--repo, a malformednen/workflow.json), which is a repository defect to report and never to commit past. An empty<message file>is the tell for either refusal, and it is checked whichever way the code was read. The verb's own behaviour is correct; the residue path around it is what needed the gate.This matters more in
akathan anywhere else, because § 4's commit is the squash's single commit: a refusal handed to it becomes the only message for every squashed commit, and the branch is about to be published. It is cheap to catch before § 7's push and expensive after.
Four refusals run before the reset, every time — these are nen wc squash's own refusals
(brief § 4, P2), enforced by this skill until the verb exists:
-
A dirty tree refuses.
nen wc classify --repo <path> --base <base> --jsonmust reporton-branch-clean;state.uncommittedPaths[]is printed if it does not.A detached
HEADrefuses here too, and it is a different refusal — read it as one. The verb exits1with prose on stdout even under--json— "could not determine the current branch … This usually means a detached HEAD" (verified live on a fixture;docs/surfaces/evidence/surfaces.md§ 7, F5). That is not a dirty tree and it is not a branch that needs cleaning: it is a checkout with no branch to publish, whichakacannot invent —nen wc publishrefuses a detachedHEADat exit2, there being no ref to name, and the squash range in the table above has no<branch>to askoriginabout. Stop, say the checkout is detached, and say the fix ishatsu:breath§ 3 — which warms a detachedHEADthroughnen shu warmupand cuts the effort's branch from the trunk's fresh tip. This is the ordinary starting shape of a Codex reviewer's worktree, so it is a case rather than a curiosity, and it is not a G5: nothing has gone wrong, a step was skipped. -
A commit already on the upstream refuses (
nen/decisions.jsonrowrewrite-published-history). The § 4 table's range is computed, never assumed, and a range that would include an already-pushed commit is refused outright, not force-pushed past. -
A non-ancestor squash point refuses — if the computed point is not an ancestor of
HEAD, the branch has been rewritten under this run and the state is reported instead. -
A forbidden trailer refuses. § 2's
commits.forbiddenTrailers.
RETIRED at nen
0.5:nen wc squashIS the verb, and it enforces all four refusals itself.
nen wc squash --repo <path> --onto <ref> --message-file <file> [--dry-run] [--json]
It folds every commit
git merge-base <onto> HEADfinds into ONE, whose message is held to the same shapenen commit formatenforces — a Conventional Commits header ≤ 72 characters, trailers asKey: valuelines in the final paragraph, and any attribution trailer this repository'snen/workflow.jsondoes not admit refused, in the same words that verb uses. Every refusal runs before a single write, all at exit2: a dirty working tree (every uncommitted and untracked path named); an--ontothat is not an ancestor ofHEAD(whatgit merge-basefound instead, quoted); and any commit in the range already reachable from@{upstream}, fetched first — "already published; squashing would rewrite pushed history". Fewer than two commits to fold is not a refusal: exit0, one line, nothing moves.Both refusals verified live at
v0.5.0(docs/ab/aka.md§ Retired at nen 0.5): against a dirty fixture, exit2naming all five paths; against this branch, exit2naming the already-published commit by SHA and subject. The mechanism isgit reset --soft <merge-base>thengit commit -F, both through the seam — so a commit that then fails leaves the originals recoverable fromORIG_HEADand the reflog, which the verb's own error names. It never touches a remote beyond the read-only fetch, never pushes, and never force-anything.
Exit 3 — the squash happened, and a hook put a refused trailer on it (nen 0.19, zheref/nen#357).
The verb reads the commit it just wrote back through git's own trailer parser. Its injected[] lists
each refused key: one a prepare-commit-msg or commit-msg hook added, or one the message carried.
The commit exists. Exit 3 is never "nothing was squashed". The commit is unpublished, so row
injected-attribution-trailer's autonomous drop applies, as at § 7 step 0: the undo line the verb's
stderr prints (git -C <path> reset --soft ORIG_HEAD) restores the folded commits. Then row
harness-attribution-switch is asked once, and the squash runs again — once: a second exit 3 is
§ 7's re-injecting hook, a G5 before any push. Exit 1 whose stderr quotes a commit-msg
hook refusing an attribution key the composed message did not carry is the same hook (a key the
message carried is hook/policy drift: stop and name it,
docs/AGENT-ATTRIBUTION.md), refused before any squash commit exists:
the branch already sits at the squash point. Restore the folded commits with
git -C <path> reset --soft ORIG_HEAD and nothing else. The squash refused a dirty tree, so this
matches the --hard line the stderr prints without touching the working tree. Then § 7: on a
cloud agent, or wherever no documented switch reaches the hook, the G5 at once; otherwise row
harness-attribution-switch once. Since nen 0.19 the verb also
loads nen/workflow.json on every run, so a malformed policy is exit 1 before anything moves.
Enforcement of the trailer rule is three-layered, and the third layer is now the binary's. (a)
This skill refuses to write the trailer — the rendered message is read and compared against
commits.forbiddenTrailers before the commit, agent-side, and it is the layer that is live
everywhere and survives a forgotten flag; (b) the target repository's commit-msg hook, which
nen scaffold init generates from commits.allowedAttributionTrailers, and which stays
target-dependent — it exists only in a repository that has been scaffolded, and hatsu's own
checkout has not; (c) nen commit format --repo and nen wc squash refusing it outright, both
at the pinned build, verified live. Say which of (b) and (c) the repository in front of you
actually has; never describe a hook as installed where none is.
Layer (c) is live at this pin, and any wording that still calls it residue is stale. Re-verified on 2026-09-10 from this repository's checkout:
nen commit format --repo . --trailer "Co-Authored-By=someone"is refused at exit2, naming the policy file and the two admitted keys. The reason this needs saying is that a headless Cursor run against nen0.3.0found--repoaccepted and silently ignored there, so the guard never fired and the flag read as though it had (docs/surfaces/evidence/surfaces.md§ 8, F12). That was the old pin. Do not present the trailer guard as something no verb enforces — layer (a) stays because it survives a forgotten flag, not because (c) is missing.
One commit with configured author/committer metadata and the truthful canonical persona/plane trailer.
Use Hatsu-Agent for Hatsu work and Akatsuki-Agent only for autonomous Akatsuki work; never infer or
default the persona. No Co-Authored-By, Claude-Session, Signed-off-by, "Generated with" line,
model, runtime, surface, or session name belongs in the message. The PR's first ## Delivery heading
reports ledger-backed runtime facts; its final ## Agent attribution lists actual participants and contributions.
Never --no-verify — where the repository does carry
a commit-msg hook it is layer (b), and skipping it is skipping the rule.
5. Then hatsu:ao — the base underneath it
Aka's third step is ao's whole engine: fetch the base, rebase if nothing is published and merge if something is, classify every conflict, resolve the mechanical ones, stop at G5 on a semantic one. This skill restates none of it.
Two things aka relies on and does not re-derive:
- Ao never pushes (its own hard limit). The push below is aka's, and it happens after ao has returned and said what it did.
- A semantic conflict inside ao ends this run. The push does not happen; the branch is left where ao left it, said out loud.
Order matters: squash, then catch up. Squashing after a merge would fold the base's merge commit into the effort's commit and lose the ancestry that makes the merge legible.
6. Re-lint the caught-up tree
Reuse § 3's lint only when catch-up was a complete no-op. Any changed tree path — source, test, snapshot, execution configuration, metadata, or otherwise — reruns the declared lint row on the caught-up tree:
nen shu lint --repo <path> --lane <iteration.lane>
A red lint ends the run before the push. A seat is quoted. This is the last local check aka owns. It is gyo again, on the caught-up tree. It is not regression and it is not coverage.
Composites that used to call a prepublication-verification helper on aka do not. Mukai and
murasaki call hatsu:kotoamatsukami for impacted tests and
hatsu:byakugan for coverage. Aka grants them no helper authority and no
test run.
7. Push — or first-publish
git -C <path> merge-base origin/<base> HEAD # first publish only, read NOW (after § 5): <range base>; else the SHA ls-remote printed
git -C <path> log --format='%h%n%(trailers:only,unfold)' <range base>..HEAD # 0. read EVERY outgoing commit (zheref/hatsu#66)
"$hatsu_root/scripts/branch_authorship_check.sh" --repo <path> --base <range base> --require-agent <responsible-persona> --trunk origin/<base> --forbid-trailers-from <path>/nen/workflow.json \
&& nen wc publish --repo <path> [--dry-run] [--json] # 0b, then the push: already published; --forbid-trailers-from only where § 7's flag rule passes it
"$hatsu_root/scripts/branch_authorship_check.sh" --repo <path> --base <range base> --require-agent <responsible-persona> --trunk origin/<base> --forbid-trailers-from <path>/nen/workflow.json \
&& nen wc publish --repo <path> --set-upstream [--dry-run] [--json] # 0b, then the push: first publish; --forbid-trailers-from as above
The declared catch-up base is the trust boundary. In this section, origin/<base> may name
the run's integration branch, as well as the repository base. --trunk accepts either ref: it
excludes that base's history and exempts a trailer-less merge only when every later parent is
already on that declared ref and its tree equals Git's clean merge. A second parent merely
being an ancestor of HEAD proves nothing; every merge has that property. An undeclared side
branch, an edited merge tree or an incomplete proof still refuses (HA265).
Step 0 is the last read before the remote, and it reads the whole outgoing range, not HEAD —
after § 4's squash, § 5's catch-up (ao may add a merge commit above the squash, so HEAD alone would
miss the squash parent the push also sends) and § 6's re-lint. The range is § 4's own table, with § 4's own
refs: from the SHA ls-remote printed on a published branch (never origin/<branch>, which § 4
may have left absent or stale — an absent ref fails the read, a stale one re-reads published commits
and stops falsely), from git merge-base origin/<base> HEAD on a first publish — § 4's table, not
origin/<base>, which a moved base leaves off HEAD's ancestry (zheref/hatsu#159) — exactly what nen wc publish will send. Step 0b reads the same range for provenance, chained to the push by &&: a commit without
Hatsu-Agent: <responsible-persona> (a commit forging that persona passes this test; hanten § 4's
printed-SHA comparison is what catches one made during a review), or whose
author or committer is not this repository's user.email, is reported with its SHA and the push does
not run; § 4 already ran the same check before the squash erased the evidence, and this one covers
what § 5's catch-up added. Exit 2 is a wiring stop, the script's sentence quoted, nothing pushed.
Never pipe this guard, including through tail or tee. Its own exit 0 alone permits the
&& nen wc publish line above; a pipeline's exit is not the guard's verdict.
A harness can append a trailer at git commit time. An attribution key —
WORKFLOW § commits's one rule: a key ending -by/-with or listed in
commits.forbiddenTrailers; Closes and its kind are ordinary and never compared — outside
commits.allowedAttributionTrailers (keys compared case-insensitively, as nen commit format and
nen wc squash compare them) is a stop with a default (nen/decisions.json row
injected-attribution-trailer, kokusen § 5): a tip above the squash point is
unpublished by construction and is re-squashed once the maintainer answers row
harness-attribution-switch (their own toggle; no file this skill writes). A re-squash that reads
the key back again is a hook re-injecting on every commit — a Cursor cloud agent's, put there by the
cloud environment's commit-msg hook (docs/AGENT-ATTRIBUTION.md
§ A hook that re-injects): restore the folded commits with the undo line, push nothing — the
first publish included — and stop at G5 through row harness-attribution-switch option C
(nen stop --who Kurapika --gate G5 <efforts.md>), the branch unpublished and the change kept. That
stop is unconditional; it does not wait on whether a switch exists. Publishing and then disclosing is
never the recovery for a commit this run wrote (zheref/hatsu#470). Where the repository's
commit-msg hook refuses the key outright (§ 4's exit 1, nothing committed), it is the same
stop. Where no documented switch reaches the hook (a Cursor cloud agent, or a harness the run cannot
place), option A reaches nothing, so the first refusal is the G5. On an IDE or CLI harness with a
documented switch, ask the row once, and a second refusal is the G5. --no-verify,
-c core.hooksPath, an edited .git/hooks/ file or git commit-tree is never the way past it, and
no nen verb strips a hook's key at the pin (zheref/nen#594;
docs/AGENT-ATTRIBUTION.md § When the commit-msg hook
refuses the commit outright).
Whether step 0b carries --forbid-trailers-from is decided before the call, by reading
<path>/nen/workflow.json: pass --forbid-trailers-from <path>/nen/workflow.json where the file lists
a non-empty commits.forbiddenTrailers; where the file is absent or lists none, omit the flag and say
so. A workflow.json that is present but cannot be read as a key list passes the flag, so the
guard's own exit 2 stops the push. Every exit 2 from the guard is then a stop before any push — a missing file, a malformed key
list or a locale fault is never answered by dropping the flag and running again. The guard's exit 2 on a
host with no ruby is the same stop, labelled a host gap: the policy unread, nothing pushed.
Where the flag runs it refuses an outgoing commit carrying a key forbiddenTrailers lists, chained to
the push; the -by/-with suffix rule stays step 0's read, by the agent, and is not mechanical.
A commit at or below the squash point that carries one was published before this run read it and is
never rewritten (row rewrite-published-history): it is disclosed by shibari
§ 3 as its own checklist line, outside the attribution ledger, and the switch is fixed before the next commit. Step 0's read stays although wc squash reads its own
commit back (nen 0.19, § 4's exit 3): a clean catch-up merge is written by nen wc catch-up, which reads no trailers back.
Published provenance failure: stop. Do not amend or push over a published commit in this run.
Report the failing SHA and the remote tip from git ls-remote origin refs/heads/<branch>, then hand
the maintainer this repair line for a corrected branch they have prepared and checked themselves:
git -C <path> push --force-with-lease=refs/heads/<branch>:<published-remote-sha> origin HEAD:refs/heads/<branch>
Only the maintainer may execute that lease-protected rewrite. If the remote SHA moved, the lease refuses it; fetch and investigate rather than issuing a second push over the new tip.
RETIRED at nen 0.13: the push is a verb. wc publish pushes the current branch to origin
and nothing else: it refuses a detached HEAD, the trunk and any refspec or force form at exit 2,
and exits 1 with needsForce: true when the upstream is not an ancestor of HEAD.
- Never a force form, in any spelling: the verb has none to give, and § 4's range computation
exists so that a plain push always suffices. Exit
1withneedsForce: trueis a finding to report, and the run stops there: either something moved the remote branch under this run, or the squash folded a commit that was already published, and a published commit is never rewritten (nen/decisions.jsonrowrewrite-published-history). Say which, and say nothing was pushed. - Never pushes
main, or anybranch.base. The verb refuses the trunk at exit2; thePreToolUseguard inhooks/hooks.jsonrefuses a raw push on the trunk at the harness level; this skill refuses it at the rule level, and all three are the answer to the same question. - Never opens, edits or comments on a PR. Not even to say the branch moved.
8. Report, and stop
One line, then stop: the branch, whether it was a first publish, the one commit's subject, the base and how it got underneath (rebase or merge), that tests are not due until mukai, and the pushed SHA.
Re-render the turn report before stopping. The last render was truthful when it was written and
is stale one step later — it says nothing was pushed about a branch that is now on origin.
hatsu:spiritual-message § 5 owns this: the turn variant, re-rendered at the same
address, with the push written into 00 This last turn and 01 Accomplished. There is no
fourth variant and aka does not invent one. 00 answers the aka request: lint, squash, ao,
push — and that tests were not due.
A G5 on this run fills blocker on that same turn page (zheref/hatsu#56;
spiritual-message § 5a). Red lint, a semantic conflict from hatsu:ao, or any other stop
aka owns: the payload names the step, the rule, expected versus actual, and the next action.
Visual failures embed this run's actual / reference / diff. Do not wait for final or for a PR.
hatsu:jutaisho will refuse to hand off a report that does not contain
that content.
Then say what is available next, without asking for it: hatsu:mukai opens the PR.
hatsu:ren's loop is over for this effort — its own rule is that it ends only on
aka or hatsu:tensho, and this was aka.
Residue
- RETIRED at nen
0.5:nen wc squash --onto <ref> --message-file <f>— the verb folds the range and enforces all four refusals itself, before a single write (§ 4, both refusals verified live at exit2).git reset --softplus a hand-gatednen commit formatis no longer the path, and the by-hand precondition checks go with it: the verb runs the ancestry and upstream tests. Making an--ontothat names an object this checkout has not got usable is still the caller's —git fetch origin <branch>first, sincels-remotetransfers no objects. - RETIRED at nen
0.5: the forbidden-trailer refusal innen commit format --repo— verified live at the pin, exit2naming the file and the admitted key. The refusal is now (a) this skill's rule, (c) the verb's, and (b) acommit-msghook only in a repository that has been scaffolded with one — which hatsu's own checkout still has not. - The published/unpublished test —
git ls-remote --heads origin refs/heads/<branch>, asked of the remote directly, which is the default form;git fetch origin <base> <branch>(a missing remote branch tolerated) thengit rev-parse --verify --quiet refs/remotes/origin/<branch>is the fallback (§ 4).nen wc classify --jsonreports the branch and its distance from the base, never the remote, and nothing in nen refreshes the branch's tracking ref for this decision. - RETIRED at nen
0.13: the push itself isnen wc publish [--set-upstream](§ 7). - RETIRED at nen
0.5:nen/workflow.jsonis validated.nen schema check --repo <path>carries anok nen/workflow.jsonrow at the pinned build (verified live, exit1on Hatsu's own taxonomy-less checkout with that rowok), and a malformed policy file is a FAIL by pointer rather than something this skill notices by eye. § 2's keys are still read here — reading a file is not residue; nothing in nen hands the policy out exceptshu coverage's ladder andcommit format's trailer list. - The exit-
3undo afternen wc squash—git -C <path> reset --soft ORIG_HEAD, the line the verb's stderr names (§ 4): a raw git write, owned by no nen verb. - The exit-
1undo afternen wc squashrefused by acommit-msghook — the samegit -C <path> reset --soft ORIG_HEAD(§ 4). The verb printsgit reset --hard ORIG_HEAD; the soft form gives the same result on the clean tree the squash requires, and it never touches the working tree. A raw git write, owned by no nen verb.
Authority
- Permitted on the maintainer's own aka call: run the declared lint row; reset and re-commit
unpushed commits, the re-squash on an injected attribution key included (§ 7 step 0); invoke
hatsu:ao; push a non-base branch, including a first publish that sets upstream. - Not permitted: opening or touching a PR; any label; any merge of the base; any force-push;
any rewrite of a commit that exists on the remote;
--no-verify; pushingbranch.base; runningtests.required,ui-test, or coverage. Composites do not call aka for tests. - A standalone aka's delegation is one run wide and ends when this run ends. It is not standing authority to push this branch again later; the one standing scope is § 0's second carve-out.
Hard limits
- The whole aka run never runs unasked, and never prompts for itself — no agent, skill,
report or stop option proposes
hatsu:aka(§ 1); a typed futonthenstep (§ 0, first carve-out) and a typed futon or kazaana line's per-effort pushes (§ 0, second carve-out) are the maintainer's own call, not a proposal. - Never runs project-wide tests or coverage — that is mukai's, through kotoamatsukami and byakugan. Aka that "just validates everything still passes" is the cost this split exists to stop.
- Never force-pushes, and never rewrites a commit that is already on the remote — a rejected fast-forward is reported, never flagged past (§ 4, § 7).
- Never decides the squash range from a stale tracking ref — the remote is asked with
ls-remote(the default), ororigin/<branch>is refreshed by the fallback's fetch, before the range is chosen (§ 4). A ref this checkout last heard about is not evidence about origin. - Never carries untruthful or runtime attribution — carry the truthful canonical
Hatsu-AgentorAkatsuki-Agenttrailer only; never carryCo-Authored-By,Claude-Session,Signed-off-by, a model, surface, runtime, session, or generated-credit attribution. - Never hands
nen wc squasha message filenen commit formatdid not exit0for, and never merges the verb's two streams into that file (§ 4). In a squash, a refusal committed as the message is the whole effort's message. - Never
--no-verify, and never pushesmainor any configured base. - Never opens a pull request — that is
hatsu:mukai's, and the split is deliberate. - Never presents by-hand git as a verb's output — every step in § Residue is named where it runs.