Render the current backlog as the Kurapika gate board — the same sweep, arguments and gate assignment as hatsu:backlog-state, painted as a Rikugan page by nen report render --variant register instead of a markdown table. Use when the maintainer asks to see the board, the gate register, or the state as a page, or invokes hatsu:backlog-board <repo|all>@<G1|G1-M|G2|G3|G4|G5|all> [every <turn|state-change|once>] — the trailing clause makes the board re-render on every turn or whenever a scoped item's state changes, instead of the once-and-stop default. This skill also owns the render path futon, backlog-loop and the dated final report use. Strictly read-only — it never labels, merges, pushes, comments or opens anything.
Install
npx skillscat add zheref/hatsu/backlog-board Install via the SkillsCat registry.
Shared policy: `PROCESS.md` § Standalone entry.
Backlog board — the same board, painted
Nature: Manipulator. The board-facing half of the work.
`backlog-state` answers "what is at my gate right now, and what does
each thing need from me?" as a markdown table. This skill answers the identical question with the
identical computation and paints it as a Rikugan page. No second source of truth.
Read-only, without exception. Inherited from
backlog-state. If reading the board makes the
next action obvious, say what the action is on the board — do not take it.
1. Invocation
hatsu:backlog-board <repo | all>@<G1 | G1-M | G2 | G3 | G4 | G5 | all> [every <turn | state-change | once>]nen parse backlog-board \
--grammar "<repo>[@<gate:G1|G1-M|G2|G3|G4|G5|all>] [every <freq:turn|state-change|once>]" --line "<the invocation>"The <repo>@<gate> half is backlog-state's grammar, deliberately — same tokens, same
case-insensitivity, same optional @, same G1 ⊇ G1-M inclusion, same rule that an unknown repo
is asked for, never guessed. Read it there; do not restate it. Two rules live in prose because the
grammar cannot say them: an absent @<gate> means all, and an omitted repo token is the
repository you are standing in, resolved from the working directory's origin — never a silent
widening to all. 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 nen/repos.json entry in the registry's own checkout.
1a. Rendering frequency
The clause controls how many times this session loops through §§ 2–4 for the scope; it changes
nothing computed.
once(default, and what an omitted clause means). Sweep, render, publish, say the line, stop.every turn. Re-run §§ 2–4 before each subsequent maintainer message, republished to the
same URL. Echo the parsed mode before the first render; a later re-render that changed nothing
is silent.every state-change. The same loop gated onnen board diff(§ 2a), never on a by-hand field
comparison. Before the first poll, echo the mode, the interval and what counts as a change —
exactly the fieldsboard diffcompares:gate,status,needs, and a row entering or leaving
the scoped set. Only after that echo does nothing moved ⇒ no render, no line apply.- Both repeating modes borrow `izanami`'s loop discipline: no iteration cap
(nothing here writes), one line per render, the loop in the session — no timer, no cron. - What ends it: the maintainer says stop, moves on, invokes this skill again, or the session
ends. A frequency mode is session-scoped, never standing.
2. Compute the picture
Follow `backlog-state` end to end for everything about what the rows
are: the repo set, nen backlog fetch / nen backlog order, the gate (nen gate derive composed
with nen pr ready's verdict), status colour (nen color status), the expected-action line, session
· lane, and ordering. Nothing about the computation changes because the output is HTML.
Then the object rows, from the verb that owns them:
nen report data --repo <path> --target <owner/name> --backlog --jsonobjects[] is one row per open issue and PR:{ kind, number, title, url, state, labels[], head, mergeStateStatus, checks{}, threads{}, reviewRequests[], linked[], readiness{verdict,reason,source} }, with readiness read from the head
SHA's readiness check where there is one, else computed from the in-process gate, else null with
the reason on stderr. nen board build --repo-slug <owner/name> --rows-from <path> assembles the
same rows into a Board for the diff; it fetches nothing and derives nothing itself.
2a. nen board diff — the state-change engine
nen board diff --before <path> --after <path> is a field-level diff of two Board snapshots by row
id; every state-change runs on it. A null diff is {"rows": [], "changed": false}. A malformed snapshot is refused at exit 2 naming the file,
row and field, never diffed — a caller bug in the § 2 mapping: fix the row and re-run.
nen watch until cannot drive this poll — a fetch → board build → board diff chain is three
programs and its shell-less --command refuses it. The loop runs at the session's own pace.
3. Render — the Rikugan, never hand-authored HTML
The page is `templates/rikugan.html`, filled by the verb:
nen report render --variant register --template templates/rikugan.html \
--data <the data document> --out <reports.dir>/current.html --repo <path> [--graph <file>]The variant's blocks come from reports.sections.register in nen/workflow.json
(masthead, tally, desk, register, spend, legend), injected as presence flags before the fill. The
data document is § 2's objects[] ∪ nen board build's rows ∪ the desk asks you compose:
{ variant, title, scope, gate, generatedAt, generatedAtLocal, footerNote, footerCount,
tallyScope, tallyNeedsYou, tallyBlockers, tallyReady, tallyInFlight,
gates[{ gate, label, cleared, asks[{ kind: DECIDE|DO|MERGE, rank, title, why, verdict,
options[{letter,label,command,consequence,star,starredClass}], objects[{label,url}] }] }],
objects[ <a § 2 row> ∪ { notation, marks, gate, gateClass, verdict, needs, session, lane, thought,
labelsLine, checksLine, threadsLine, linkedLine, head, notes: string[] } ],
architectureCaption, graphJson, graphMermaid, graphNodes[], graphEdges[],
spendEfforts[{name, actionsMinutes, spendNote, hasSpendPhases, noSpendPhases, spendPhases[{lane,percent,amount,steps}],
spendUsage[{surface,model,input,output,cacheRead,cacheWrite,minutes,source,notReported,reported}]}],
legendRows[{mark,meaning}] }Keys and values follow `PROCESS.md` § Escaping and validation of report
data — every url field included (objects[].url, gates[].asks[].objects[].url). The four …Line strings are
flattened for display, so an issue row and a PR row carry the same keys; notes is the row's free
lines and is [], never absent, when there are none. spendEfforts[] is one row per effort, itsspendPhases, spendUsage and actionsMinutes recorded and built exactly as
`spiritual-message` § 4 builds them; spendNote is "" or one muted
line. notation and footerNote follow `PROCESS.md` § Publishing a
report.
The desk is the board's point. Asks grouped by gate then ranked by unblocking power, each openingDECIDE, DO or MERGE, each carrying lettered options with exactly one star — the same list,
same order, same letters the surface's own picker will show. A gate with nothing owed is rendered
cleared, not omitted. The page briefs; the picker asks.
A drawing is optional. Where the scope has a shape worth seeing — a chore's PR graph, an epic's
child tree — pass a nen.report.graph/v0.1 document as --graph; otherwise graphNodes stays
empty and the block does not render.
Other callers
`futon`, `backlog-loop` and
`black-voice` render through this exact line, the register variant, in
§ 3's data shape, generatedAtLocal from "$hatsu_root/scripts/report_time.sh" on every render
(WORKFLOW.md § Report time). The dated final report is the same template at --variant final, titled by the effort's headline (§ Report titles): one effort, a cleared desk, at<reports.dir>/<generatedDateLocal>-<effort>.html (ruling 2026-09-19) — the one render kept on disk.
4. Publish it
An Artifact titled Gate Register, one URL per repository, published as
`PROCESS.md` § Publishing a report says — a page that moved means the
backlog is re-resolved, never repainted from what this session holds. Say the one line in chat and
stop — the repo and gate filter, the count needing him, the link.
5. Not a gate event
A requested board is not a gate event (`PROCESS.md` § Reporting a
phase), on every repeat too; a board that makes the merge obvious is still not a reason to act.
6. When the pipeline cannot run
nen report render, board build or board diff unavailable (nen out of range isten's D10 contract), or the backlog-state sweep
failing for its own reasons: relay it in one line, then fall back to backlog-state's markdown
table with the same arguments, saying so. Inside a repeating loop the same failure ends the
loop — one fallback pass, then stop; it never retries blind.
Hard limits
Everything in backlog-state's own never-list, plus:
- Never hand-authors the page. § 3 is a verb call over a fixed template; only a rendered page is
checkable. - Never publishes a board whose rows came from session memory rather than a fresh sweep, on any
render. - Never fires the banner,
nen stopor a push notification for a requested board. - Never silently substitutes the markdown table when the pipeline failed.
- Never diverges from
backlog-state's grammar or classification while advertising them as one. - Never re-derives "what changed" by eye in
every state-changemode (§ 2a). - Never runs a repeating mode with no stated pace, echoed frequency or way to end it, or carries
one past its session. - Never omits a cleared gate from the desk, and never renders an ask without a starred option.
- Never writes outside
<reports.dir>, and never gives aregisterrender a dated file — onlyfinalgets one. - Never labels, merges, pushes, comments, opens or closes anything.
History and retired findings: this effort's history file; dated live verifications:docs/ab/backlog-board.md.