zheref

backlog-board

Render the current backlog as the Kurapika gate board — the same sweep, arguments and gate assignment as $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 $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.

zheref 0 Updated 3w ago
GitHub

Install

npx skillscat add zheref/hatsu/surfaces-codex-backlog-board

Install via the SkillsCat registry.

SKILL.md

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

$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 on nen 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 fields board diff compares: 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 --json

objects[] 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, its
spendPhases, 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 opening
DECIDE, 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 is
ten'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 stop or 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-change mode (§ 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 a register render a dated file — only
    final gets 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.

Categories