Repository navigation
Nen v0.20.0
v0.20.0 — 2026-10-05
Release unit for v0.19.0..v0.20.0. It brings the classify and direct verb families, the round quorum that fulfils its members' rounds, declared check exclusions, the bounded delivery merge, compound PR wakes, and a committed-range triage:
- the deliveries: #352, #353, #354, #356, #359, #364, #365, #367, #368, #369, #370, #371, #375, #376, #377, #378, #379;
- #380, which carries #371's commits to
main: #371 was merged into its base branch after that branch had already landed through #370, somainnever received thedirectfamily until #380.
The compatibility floor moves to 0.20.
Added
nen classify— a new family over a consumer'scontracts/classify.taxonomy.json(hatsu.classify-taxonomy/v1: thelang/andjob/label axes zheref/hatsu#224 introduced) (#370):classify labelsrenders the taxonomy's label set;classify install --writedeclares it into the consumer'snen/labels.json, and--sync --targetrefuses until that declaration has landed;classify status [--with-body]reports which axes each open issue still lacks;classify apply --plan [--run] [--include-low]applies a confidence-graded plan, refusing a plan with any invalid row, applying high and medium rows only unless told otherwise, and refusing to edit an issue whose ledger cannot be appended to before the first edit — a directory named as the ledger is refused by its own stat on every platform (#380).nen direct— a new family over a consumer'scontracts/direct.registry.json(hatsu.direct-registry/v1, zheref/hatsu#225) (#371, #380):direct registryvalidates the registry and prints its snapshot, aliases, surfaces and live-lookup sources, refusing by pointer a cell whose surface differs from its alias's, a reviewer runner-up withoutalso, non-contiguous effort bands and a phase name the phases do not carry;direct resolvederives the domain from the taxonomy's structured rules, collects one routing pair per job and language (a non-code language always reads the shared cell), aggregates a winner and a runner-up, scores the effort and compares the session's surface, model alias and effort in dial space —unreadis never a mismatch — and--record <effort-id>files the verdict under.nen/direct/<percent-encoded id>.jsonwith symlink-real containment;direct answer --record <id> --answer continue|stopwrites the picker's decision into that same record, under the ledger lock and by rename, refusing a record that is not anen.direct.resolve/v0.1document for that effort.round_quorumfulfils its members' rounds (#359; the maintainer's ruling of 2026-10-04, #361): once at leastminimumofany_ofhave a round,pr readyrow 4 no longer fails on a member that is unavailable — no round-check run at head, a run that completed without a round, or a pending review request with no round check — and names each excused member in the row's note; a member whose round-check run is still in flight stays owed, a reviewer outsideany_ofis owed as before, andpr next-blockeragrees. A file with noround_quorumgets byte-identical output.- Declared check exclusions —
nen/gates.json→checks.excluded[](name,matchexact or glob,reason,ruled,untila date or{ "condition" }), validated by pointer at load (#359).pr ready,pr next-blockerandpr merge --release-unitread the list fromnen/gates.jsonat the PR's base, so a PR can never exempt its own failing check, and every honoured exclusion is printed once, in--explain, inmeta.warningsand in the merge transcript. The CLI's--exclude-checkstill applies on top. nen pr merge --delivery— the one merge a run performs into a non-main base (#377; contractnen.pr.merge-delivery/v0.1): the base must not be the default branch,branch.base, a protected or ruleset-bound branch, or the head of an auto-merging PR into one; thenpr ready, the head pin,pr body-check, and whose PR, every verdict quoted; without--runit prints the plan only.nen watch until --pr <ref> --until checks-settled|review-posted|ready|settled-and-reviewed— compound wake predicates over the same readpr readymakes (#376). Only--until readyis a merge signal; the others are wakes, and--jsoncarriesreadyAtWake. Polls are paced bymonitor.pollSeconds, bounded bymonitor.maxCycles, never under 30 s, and stop after two hours with no bound.nen stage triage --range <base>..<head>triages a committed range with the working-copy detectors, exit codes and--jsonshape (#379): names from every path any commit in the range touched, sizes from every blob it introduced,in-historyon a finding the net change no longer carries, and undecodable names flagged rather than guessed.nen stage listemits the exact add list a checkpoint should stage, through the same pass asstage triage(#364): newline,--nulor--json; exit 3 on an empty tree, exit 1 with the list withheld when anything needs a human.nen shu coverage --touched --from-capturemeasures touched-file coverage from an existing capture a producing run wrote, provenance-checked by a tree fingerprint and the reports' digests, and never re-runs the lane (#369).nen surface mirror check --surface claude-code --plugin <name> --installed <dir|auto>judges a Claude Code plugin install in its own layout (nen.surface.mirror.check-plugin/v0.1), the worst copy winning underauto, with the six verdicts Hatsu'splugin_cache_check.shgave (#378).nen report dataderives the effort's context —repo,worktree,effortStage,gate,stageClass— from facts, or null with its reason on stderr (#375); and--register <desk>emits the backlog register document, every fact nen's and the desk file carrying judgement only (#365): on the live path every verdict ispr ready's ownreadiness.reason, quoted; on the--objects-frompath verdicts come from the caller's file, and each such row and the footer say so (verdict read from , not from GitHub) — an offline register is file evidence, never a fresh readiness assessment.nen issue edit-body --expect-body-sha256 <hex>refuses at exit 3, writing nothing, when the live body no longer hashes as the caller read it;--current-body-outwrites the certifying read's exact bytes; every report saysbodyCheck.atomic: false(#356).nen issue file | comment | edit-bodyrefuse a private repository's name before any write to a public target, at exit 4, naming only the field and line, with a normalised second pass over markup and spelling tricks, and failing closed on an unreadable list or visibility (#367).nen shu toolsreports a host nen belowdependency.pinned_refasBEHIND, exit 7 when every row otherwise passes, with thenen bootstrap --refremedy;--jsonrows gainpinnedRefandbehindPinnedRef(#354). The scaffolded CI step treats 7 as a warning.nen wc catch-upclassifies conflicts as manifest, changelog, mirror or other against a newmechanicalblock innen/contract.json, and exits 3 with the resolution steps per class when every conflicted path is mechanical — it never resolves and never runs anything (#368).
Fixed
nen release preflightgathers every usage problem and refuses once, naming all of them, where it refused one per run;canon mirror generatenames a destination's unresolved merge conflict by line instead of calling it hand-written (#353).- nen's own
nen/workflow.jsonroutessrc/parse/**,src/watch/**and the files that decide the bounded merge to thesecurityreview scope, and declaresrelease.unitPaths, sorelease unit-checkandpr merge --release-unithave a unit to hold this proposal to (#352).
Breaking / consumer notes
- Repin:
"0.19"→"0.20", andv0.19.0→v0.20.0. The maintainer moved the floor for this release because changes in #354, #359, #364 and #368 alter meaning on unchanged inputs (the notes below). A repository whosedependency.minimumis below0.20is refused at exit 5 by this build: raise it to"0.20"and movepinned_reftov0.20.0— zheref/hatsu#168 for Hatsu, whoseclassify,directand round-quorum canon wait on exactly this pin. - shu —
shu toolsexits 7, where it exited 0, when the host nen satisfiesdependency.minimumbut sits belowdependency.pinned_ref(#354). A caller that reads any non-zero exit as "missing" now reads a present-but-behind host as missing; read the row. - wc —
wc catch-upexits 3, where it exited 1, when every conflicted path is one the repository'smechanicaldeclaration covers (#368); exit 1 is unchanged for anything else.--jsonisnen.wc.catch-up/v0.2, every v0.1 key kept. - gates — on a file that declares
round_quorum,pr readyanswers ready where it answered not-ready: an unavailableany_ofmember's owed round is excused once the quorum is met, and the row's note says so (#359, #361). A file without the key is unchanged. - stage —
stage triagerefuses--nulat exit 2; the NUL form isstage list --nul(#364). - issue —
issue edit-body --jsonnamesnen.issue.edit-body/v0.3(v0.2 by #356, v0.3 by #367), where it namedv0.1;writtenwidens toboolean | null, so branch onoutcome; exit 1 under--jsonnow prints a JSON document (uncertain,not-sent) on stdout; new fieldsbodySha256,outcome,bodyCheck,currentBodyOut,privateNameCheck. Exit 3 (conflict) is reachable only with--expect-body-sha256(#356). - issue —
issue file,issue commentandissue edit-bodyexit 4 where they wrote: text bound for a public target names a private repository; a guard that could not run exits 1. Their dry runs are no longer offline: each reads the target's visibility, and the private list when the target is public (#367). - issue —
issue file --body-file <path>resolves a relative path against--repo's root, no longer the process's working directory, reads it once and hands gh the bytes on stdin; an unreadable file exits 2 before any gh call (#367). A script that passed a path relative to its own cwd moves it. - report —
report data'srepochanges value and type: the hostedowner/name, ornullfor a local-path orfile://origin (string | null), where it was the directory name; the new context keys are appended afterobjects, contractnen.report.data/v0.1additive exceptrepo(#375). A reader that usedrepoas a directory name readsworktreeinstead.