Skip to content

Nen v0.20.0

Choose a tag to compare

@zheref zheref released this 05 Oct 02:00
· 254 commits to main since this release
d37c652

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 compatibility floor moves to 0.20.

Added

  • nen classify — a new family over a consumer's contracts/classify.taxonomy.json (hatsu.classify-taxonomy/v1: the lang/ and job/ label axes zheref/hatsu#224 introduced) (#370): classify labels renders the taxonomy's label set; classify install --write declares it into the consumer's nen/labels.json, and --sync --target refuses 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's contracts/direct.registry.json (hatsu.direct-registry/v1, zheref/hatsu#225) (#371, #380): direct registry validates 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 without also, non-contiguous effort bands and a phase name the phases do not carry; direct resolve derives 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 — unread is never a mismatch — and --record <effort-id> files the verdict under .nen/direct/<percent-encoded id>.json with symlink-real containment; direct answer --record <id> --answer continue|stop writes the picker's decision into that same record, under the ledger lock and by rename, refusing a record that is not a nen.direct.resolve/v0.1 document for that effort.
  • round_quorum fulfils its members' rounds (#359; the maintainer's ruling of 2026-10-04, #361): once at least minimum of any_of have a round, pr ready row 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 outside any_of is owed as before, and pr next-blocker agrees. A file with no round_quorum gets byte-identical output.
  • Declared check exclusions — nen/gates.json → checks.excluded[] (name, match exact or glob, reason, ruled, until a date or { "condition" }), validated by pointer at load (#359). pr ready, pr next-blocker and pr merge --release-unit read the list from nen/gates.json at the PR's base, so a PR can never exempt its own failing check, and every honoured exclusion is printed once, in --explain, in meta.warnings and in the merge transcript. The CLI's --exclude-check still applies on top.
  • nen pr merge --delivery — the one merge a run performs into a non-main base (#377; contract nen.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; then pr ready, the head pin, pr body-check, and whose PR, every verdict quoted; without --run it prints the plan only.
  • nen watch until --pr <ref> --until checks-settled|review-posted|ready|settled-and-reviewed — compound wake predicates over the same read pr ready makes (#376). Only --until ready is a merge signal; the others are wakes, and --json carries readyAtWake. Polls are paced by monitor.pollSeconds, bounded by monitor.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 --json shape (#379): names from every path any commit in the range touched, sizes from every blob it introduced, in-history on a finding the net change no longer carries, and undecodable names flagged rather than guessed.
  • nen stage list emits the exact add list a checkpoint should stage, through the same pass as stage triage (#364): newline, --nul or --json; exit 3 on an empty tree, exit 1 with the list withheld when anything needs a human.
  • nen shu coverage --touched --from-capture measures 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 under auto, with the six verdicts Hatsu's plugin_cache_check.sh gave (#378).
  • nen report data derives 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 is pr ready's own readiness.reason, quoted; on the --objects-from path 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-out writes the certifying read's exact bytes; every report says bodyCheck.atomic: false (#356).
  • nen issue file | comment | edit-body refuse 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 tools reports a host nen below dependency.pinned_ref as BEHIND, exit 7 when every row otherwise passes, with the nen bootstrap --ref remedy; --json rows gain pinnedRef and behindPinnedRef (#354). The scaffolded CI step treats 7 as a warning.
  • nen wc catch-up classifies conflicts as manifest, changelog, mirror or other against a new mechanical block in nen/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 preflight gathers every usage problem and refuses once, naming all of them, where it refused one per run; canon mirror generate names a destination's unresolved merge conflict by line instead of calling it hand-written (#353).
  • nen's own nen/workflow.json routes src/parse/**, src/watch/** and the files that decide the bounded merge to the security review scope, and declares release.unitPaths, so release unit-check and pr merge --release-unit have a unit to hold this proposal to (#352).

Breaking / consumer notes

  • Repin: "0.19" → "0.20", and v0.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 whose dependency.minimum is below 0.20 is refused at exit 5 by this build: raise it to "0.20" and move pinned_ref to v0.20.0 — zheref/hatsu#168 for Hatsu, whose classify, direct and round-quorum canon wait on exactly this pin.
  • shu — shu tools exits 7, where it exited 0, when the host nen satisfies dependency.minimum but sits below dependency.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-up exits 3, where it exited 1, when every conflicted path is one the repository's mechanical declaration covers (#368); exit 1 is unchanged for anything else. --json is nen.wc.catch-up/v0.2, every v0.1 key kept.
  • gates — on a file that declares round_quorum, pr ready answers ready where it answered not-ready: an unavailable any_of member'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 triage refuses --nul at exit 2; the NUL form is stage list --nul (#364).
  • issue — issue edit-body --json names nen.issue.edit-body/v0.3 (v0.2 by #356, v0.3 by #367), where it named v0.1; written widens to boolean | null, so branch on outcome; exit 1 under --json now prints a JSON document (uncertain, not-sent) on stdout; new fields bodySha256, outcome, bodyCheck, currentBodyOut, privateNameCheck. Exit 3 (conflict) is reachable only with --expect-body-sha256 (#356).
  • issue — issue file, issue comment and issue edit-body exit 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's repo changes value and type: the hosted owner/name, or null for a local-path or file:// origin (string | null), where it was the directory name; the new context keys are appended after objects, contract nen.report.data/v0.1 additive except repo (#375). A reader that used repo as a directory name reads worktree instead.