notabene docs review loop: process review comments left on the docs and verify the docs. Use when the user says "address the comments", "process the doc comments", "apply the review feedback", "review/check the docs", or references the global /comments page. Reads the notabene store, edits the docs per the feedback, marks them resolved + writes the journal, then verifies (renderer build, links, project checks). Ignores comments on "hold". This skill does NOT install, configure, or launch the ...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add z29k/notabene --skill notabene --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Notabene?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/z29k-notabene)More formats (shields.io, HTML) on the badges page.
---
name: notabene
description: >-
notabene docs review loop: process review comments left on the docs and verify
the docs. Use when the user says "address the comments", "process the doc
comments", "apply the review feedback", "review/check the docs", or references
the global /comments page. Reads the notabene store, edits the docs per the
feedback, marks them resolved + writes the journal, then verifies (renderer
build, links, project checks). Ignores comments on "hold". This skill does NOT
install, configure, or launch the review server — that's `notabene-setup`. Never
commits without an explicit request.
---
<!-- Generated from docs/reference/agent-protocol.md + packages/claude-plugin/overlays/notabene.md by scripts/gen-protocol.mjs — do not edit. -->
# Docs review loop (comments + verification)
**notabene**: a navigable renderer over a repo's docs + a human↔agent review loop.
**Stateless** — the data lives in the consumer repo's git, not in the tool. What follows
is the generic, agent-agnostic protocol; **three rules override it inside this plugin**:
1. **Not set up here?** No `notabene.config.mjs` or no `.notabene/` store → **hand off to
the `notabene-setup` skill** (or `/notabene:setup`) to install/configure and launch,
then resume. Don't fail; delegate — ignore the generic `npx … init` fallback below.
2. **Every CLI call goes through the plugin forwarder** — `node
"${CLAUDE_PLUGIN_ROOT}/bin/nb.mjs" <cmd> --root <repo-root>` — never `npx notabene`
(unscoped: not our package) and never a hardcoded version. This replaces **every**
`npx -y @z29k/notabene@latest …` shown below (`comments ls`, `build`, `lint`,
`journal add`). The loop still never depends on the CLI: file tools are enough.
3. **Authoring palette** — for what you can put in a page (Mermaid diagrams, GFM tables,
code blocks, inter-doc links) and the MDX-safety rules, use the **`notabene-authoring`**
skill rather than the web page linked in Step 4.
## Discovery — EVERYTHING comes from the config (nothing hardcoded)
Read **`notabene.config.mjs`** at the repo root to learn:
- **`store`** — comments + journal folder (e.g. `docs/.notabene`). **One file per
comment**: `<store>/<page>/<id>.json` (so branches don't conflict on merge).
Journal: `<store>/journal.json`. Schema version: `<store>/meta.json`
(`{ "schemaVersion": <n> }`, currently **3**). Older stores keep one array per page
(`<store>/<page>.json`) — both are read; `notabene migrate` upgrades a store to the current
schema (v3, one file per comment).
- **`roots[]`** — the doc spaces: `{ key, label, path, exclude }`. A comment's `page`
field is prefixed by a root's `path` (e.g. root `docs/plans` → `page:
"docs/plans/services/x"`).
- **`i18n`** (optional, `{ locales, defaultLocale, strategy }`) — the doc is multi-language
and a comment's `page` key is **locale-encoded**, mapping straight to that language's file:
`strategy: "directory"` → `page: "docs/fr/guide/x"` = file `docs/fr/guide/x.md`;
`strategy: "suffix"` → `page: "docs/guide/x.fr"` = file `docs/guide/x.fr.md` (the default
locale is unsuffixed: `page: "docs/guide/x"` = `docs/guide/x.md`). Edit **that** file — a
comment belongs to one language; don't touch the other language's file or auto-translate
unless asked.
- **`verify[]`** — project-specific checks to run after editing.
- **`review`** — `"auto"` (default) or `"approve"`. In **approve** mode you don't resolve
comments yourself: you edit, mark them **`addressed`**, and a human validates them (with
a diff) at `/review`. See Step 5.
Assume **no** path, port or label. Do not require a live server or a port.
## Strict rules (no exceptions)
- **NEVER commit or run git operations without an explicit request** ("continue"/
"go on" ≠ commit). Offer the commit at the end.
- **NEVER bulk-delete the store** (`rm -rf <store>`): those are the user's **real
comments** (precious, committed). To clean a test, delete a single comment by `id`
(edit its page file), never the folder.
- **Ignore `hold: true`** ("⏸ on hold") and `status` ≠ `open` (`addressed`/`resolved`
already handled): only process `open` **and not on hold**.
- **Account for EVERY comment in the roster** (step 1). A pass is not "some of the
comments" — each id ends the pass either handled (`resolved`/`addressed`) or
**explicitly declined**, with the reason posted as a `thread` reply so the human sees
it. Leaving one silently untouched is a **failed pass**, not a partial success: an
untouched comment is indistinguishable from one the human wrote a minute ago, so
nothing downstream can flag it — not `/review`, which only ever shows what you DID,
and not `comments verify`, for which an `open` comment is perfectly legal. You are the
only check. A long roster is a reason to work in batches, never a reason to stop early.
- **MDX-safety** (format `"mdx"` only): when editing a **`.mdx`** file, don't introduce
stray `{` or `<` outside code fences (MDX parses them as expression/JSX). **`.md`**
files (CommonMark/GFM) are lenient — no such constraint. Validated by the renderer build.
- **File-I/O first**: read/write the `<store>/` files **directly** with your file
tools. The `astro dev` server need NOT be running — the HTTP `/api/comments` is only
a convenience when the site is already open. Depend on **neither a port nor a process**.
## Step 1 — Read the comments to process
List the actionable set with the CLI (any agent can shell out — no store-parsing to
reimplement, no `python3`):
```bash
npx -y @z29k/notabene@latest comments ls --open --json # open AND not-on-hold, machine-readable
npx -y @z29k/notabene@latest comments ls --open # …or human-readable
```
**That list is the pass's roster. Take it ONCE, whole, and write the ids down** — into
your task list, a scratch file, whatever survives the pass. Every later step is measured
against it, and step 6 reconciles with it. Neither the CLI nor the HTTP API paginates,
truncates or caps: one call returns every eligible comment, however many there are (the
only ellipsis anywhere is the 100-character quote preview in the *human-readable*
listing — use `--json` and you get the full text). So a short roster means a short store,
never a partial read. **Count the ids and state the number before you start** — a pass
that never named its own size cannot notice it dropped half of it, which is exactly how
a real store ended up with fifteen comments handled and six untouched, in a run that
reported success.
If the CLI isn't available (offline, no Node, a policy against `npx`), read the store with
your file tools directly: each `<store>/**/*.json` (except `journal.json`/`meta.json`) is
**one comment**, or — in older v1 stores — a **legacy array** of comments; keep those with
`status == "open"` **and** `hold != true`. The loop never depends on the CLI being present.
A comment reopened after a **rejection** (approve mode) carries the human's reason as
later `thread` replies — **read them** and adjust accordingly before editing. (A human
rejects from `/review`, or with `comments reopen <id> --reply "<why>"`.)
A `thread[].author` is a plain string that may be git-style **`Name <email>`** (the browser
embeds the reviewer's email for a unique identity) — treat the whole string as the author;
split on the trailing `<…>` only if you need the bare display name.
## Step 2 — Locate the source page
`page` (= `data-page`) → source file, via `roots[]`: a `page` starting with
`<root.path>/…` maps to a file **under `<root.path>`** at the same relative path.
- `<root.path>/<x>` → `<root.path>/<x>.md` or `.mdx`
- **Index page**: if `<x>.{md,mdx}` doesn't exist, it's **`<x>/index.{md,mdx}`** (the
loader strips `index` from the id → some `data-page` values omit `/index`). Test both.
## Step 3 — Resolve the anchor
`anchor.quote` is the **rendered text** (markdown stripped: no `**`, links as plain
text…). To find it in the source, search tolerantly, using `anchor.prefix`/`suffix`
(disambiguating context) and `anchor.section` (nearest heading). `scope: "page"` = a
page-wide comment, no anchor.
**Block comments** (`scope: "block"`, store v3) target a **diagram or image**, not text —
the anchor is `{ kind, key, label, section, index }` (no quote; `index` disambiguates
repeated blocks with the same key). `kind: "image"` → find the
`` whose src matches `key`/`label` and act on it; `kind: "mermaid"` → find the
` ```mermaid ` fence for that diagram (its source hashes to `key`; `label` = the diagram
type + first line) and edit the **diagram source**. `anchor.section` narrows the search.
## Step 4 — Edit the docs (faithfully)
Apply each piece of feedback **faithfully** at the right spot. A comment is a user
decision. If the change touches public behavior documented elsewhere, update it (see
project hooks below). For **what you can put in a page** — Mermaid diagrams (```mermaid),
GFM tables, code blocks, inter-doc links — and the MDX-safety rules, see the authoring
reference: <https://z29k.github.io/notabene/guide/authoring/>.
## Step 5 — Mark the comment + write the journal
Set the status by `review` mode (from the config):
- **`auto`** (default): `status = "resolved"`.
- **`approve`**: `status = "addressed"` — you propose; the human validates at `/review`.
Do **not** resolve it yourself.
In both cases set `resolution = { note, journalEntryId }` and **append** a
`<store>/journal.json` entry: `{ id, date (YYYY-MM-DD), title, summary, changes[] { page,
commentIds[], what, why } }`. Each resolution's `journalEntryId` = the journal entry's
`id`.
**Prefer the CLI for this step** — it picks the status from `review` for you, preserves
every other field, and writes atomically:
```bash
# 1. journal first: --json echoes { id } so you can chain it
echo '{ "id": "j-2026-07-28", "date": "2026-07-28", "title": "…", "summary": "…",
"changes": [{ "page": "docs/guide/x", "commentIds": ["c1"], "what": "…", "why": "…" }] }' \
| npx -y @z29k/notabene@latest journal add --json
# 2. then the comments it covers (status = resolved | addressed, per the config)
npx -y @z29k/notabene@latest comments done c1 c2 --note "…" --journal j-2026-07-28
```
Editing the JSON by hand is still valid (`journal.json`: 2-space indent + trailing
newline) — just never lose a field, and never write `resolved` in **approve** mode.
**Cascade (load-bearing for the review UI):** if fixing a comment touched **several
pages** (a cross-ref, behavior documented elsewhere), emit **one `changes[]` entry per
page actually touched**, each listing that `commentId`. The reviewer's diff is built by
inverting the journal — a page you don't record there won't be shown.
## Step 6 — Verify
1. **ALWAYS: build the renderer** — a broken doc file breaks the tool itself
(`npx -y @z29k/notabene@latest build`, or the project's renderer build).
2. **Lint the inter-doc links** — `npx -y @z29k/notabene@latest lint`. It validates every
relative `.md` link against the routes the build just emitted (with did-you-mean
suggestions; `--json` for machine reading). A broken link is a **failed verification** —
fix it before reporting. If it exits 2, the build of step 1 didn't run — never skip it.
3. **Audit the store you just wrote** — `npx -y @z29k/notabene@latest comments verify`.
It checks statuses, the comment↔journal links **in both directions**, the file layout
and dangling pages. The one to care about: a comment whose journal entry doesn't list
it back in `changes[]` makes `/review` show the human an **empty diff**. Exit 1 = fix
it before reporting.
4. **Reconcile the roster** — re-run `comments ls --open --json` and subtract: **no id
from step 1's roster may still be there.** Any that is was silently dropped — go back
and handle it, or decline it with a reply; it is a defect to fix, not a result to
report. Do not simply check that the list is empty: a comment written *during* your
pass is legitimately open and must be left alone, which is precisely why the
comparison is against the roster and not against zero.
5. **`config.verify[]`** — the project's own checks (build/lint/memory update).
6. **Project memory** — if the project keeps a memory doc (`CLAUDE.md`/`AGENTS.md`),
update it for any public-behavior change.
> Steps 5–6 are the **project extension point**. The core loop is generic; a consumer
> declares its post-edit steps via `verify[]` and its memory conventions. The core
> does not know any specific project.
## Step 7 — Report (without committing)
**Open with the arithmetic of the pass: `N eligible → N handled + N declined`, and make
the three numbers add up.** Say it even when nothing was skipped — a report that cannot
be wrong about its own coverage is what turns "I think it's done" into something the
human can check at a glance. If a comment was declined, name it and say why.
Then summarize as a **table**: per comment → the change made (section) + the why. Point to
`/journal` (and, in **approve** mode, to **`/review`** — the human validates each edit
against its diff there, then approves → resolved or rejects → reopened). Then **ask**
whether to commit, and **what** (doc edits only / + resolved store + journal / + project
artifacts). Wait for an explicit go-ahead.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!