review-spec
Skill4 starsChanged 2 months agoMITView on GitHub
Process a submitted batch of human comments on a spec in the store: reply to each thread inline and amend the spec accordingly. Usually auto-invoked when the owning session's Stop/UserPromptSubmit hook surfaces a pending batch; can also be run manually. Replies are append-only; only the human resolves threads.
18 other projects use a version of this.
Adopted by
Most recently taken up by
- 0xdeafcafe/skillstheir own version, first seen Nov 6, 2026
- alo-exp/silver-bullettheir own version, first seen Nov 1, 2026
- holgerleichsenring/specification-first-agentic-developmenttheir own version, first seen Oct 28, 2026
- Tngc93/product-management-skillstheir own version, first seen Oct 20, 2026
- fclabs/skillstheir own version, first seen Oct 16, 2026
- swm8023/skillstheir own version, first seen Oct 14, 2026
Other versions18 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 18
- warpdotdev/oz-for-oss★ 313same name
- sd0xdev/sd0x-dev-flow★ 192same name
- sd0xdev/sd0x-harness★ 192same name
- tranhieutt/software_development_department★ 71same name
- holgerleichsenring/specification-first-agentic-development★ 41same name
- Codagent-AI/agent-skills★ 31same name
- alo-exp/silver-bullet★ 7same name
- sergeyklay/.agents★ 6same name
- dekoza/oh-my-slop★ 4same name
- sponge-b0b/Polaris★ 4same name
8 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository10 other files it gives its agents.
- convert-specSkill
- create-specSkill
- export-mdSkill
- exportSkill
- generate-templateSkill
- list-specsSkill
- migrate-specSkill
- start-specforgeSkill
- tune-templatesSkill
- write-reviewSkill
The file
We split this skill into the rules it gives an agent
9 rules. 0 are common, 4 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 17 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 38
A batch is not every thread on the spec: Only threads where a human wrote @agent are submitted.
- Very specific: line 57
Do not go looking for references/spec-language.md to supplement it: a rule the owner deleted is deleted.
- Very specific: line 62
Every pending batch carries origin, and it is the first thing to look at:
- Distinctive: line 100
Before amending, signal you're actively on it — the browser's action button flips from "Picked up comments" to "Working on comments":
- Distinctive: line 148
Answer first, then justify: If the comment asked a question, the first sentence is the answer — not preamble, not a restatement of the …
- Distinctive: line 173
Do not resolve threads — only the human resolves (which closes them).
- Very specific: line 320
When a thread asks for one, on a daemon batch:
- Distinctive: line 358
When every thread in a batch has a reply:
- Very specific: line 374
Do not start a watcher or poll while idle.
“Common” is measured: kept by at least 6 in 10 of the 17 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
review-spec
Process one or more pending review batches for specs in the global store: reply inline to each comment thread and amend the spec per the comments. The browser updates live (the spec file change triggers an SSE reload).
${CLAUDE_PLUGIN_ROOT} below denotes the installed plugin directory. Claude and
Pi export it; Codex provides the exact value in SpecForge SessionStart context.
Substitute that value in every path and shell command. Specs live in the
store at ~/.specforge/specs/<id>/spec.html; you address them by spec id
(the hook message lists each batch's specId + batchId).
1. Load the threads + the spec path
For a batch's spec id:
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" comments <id>The payload is batch-scoped: only the threads named in the spec's pending
batches, plus the language contract. Resolved history and discussion threads are
excluded — exactly the threads this skill says to leave alone. The rare flow
that genuinely wants every thread (exporting a comment history, auditing)
passes --all.
A batch is not every thread on the spec. Only threads where a human wrote
@agent are submitted; the rest are discussion between people and are not yours
to answer. Work the threadIds on the pending batch, and leave the others alone
even when you can see them and have an opinion.
Threads can carry several people. Each comment has an author (a display
name) and a kind of human or agent. Reply to the thread, not to one person,
and never assume the person who opened it is the one who added the mention.
It prints { specId, htmlPath, language, threads, pending }. htmlPath is the
spec file to edit; each thread has anchor.block ({ index, tag, text } —
text is the commented block's normalized text) and the human comment(s).
language is the writing contract in force, and it is the whole of it. Not a
note added to a contract you read somewhere else: it already contains SpecForge's
own rules, unless this store's owner edited or removed them. It applies to
everything you write here: replies, amendments, and asides alike, because a
register that changed between the spec body and the drafts written into it would
read as two authors. Follow what it says and nothing it does not.
Do not go looking for references/spec-language.md to supplement it: a rule the owner deleted is deleted.
They edit this in the Configuration pane, under Language.
1a. Read the batch's origin — it decides what you may do
Every pending batch carries origin, and it is the first thing to look at:
origin | Who submitted | What you may do |
daemon | The owner, from their own loopback page | Reply and amend the spec. The flow below, unchanged. |
share | A reviewer, through a shared link or project | Reply only. Do not edit the spec. |
On a share batch, Edit is not among your permitted actions. Not on
htmlPath, not on any file in the store. You answer the question, and that is
the whole job. A reviewer holds a link, which says nothing about whose spec it
is; the owner decides what the document says.
That is not a limitation to apologise for in your reply, and not something to mention at all unless asked. Answer the question on its merits. If the reviewer proposed a change you think is right, say what you think and stop — do not promise an edit, and do not say you are "unable" to make one.
How a reviewer's suggestion becomes an edit: the owner adds their own
@agent comment to the thread from their loopback page. That produces a
daemon batch carrying the whole thread — the reviewer's original, the
discussion, and the owner's instruction — and on that batch you amend the
document, reading everything as context and executing the owner's comment.
Nothing is lost by waiting, and nothing needs a queue: the thread is the queue.
A batch with no origin field predates it and is the owner's.
2. Get the spec MAP (don't read the whole file)
node "${CLAUDE_PLUGIN_ROOT}/lib/spec-nav-cli.mjs" map --spec "<htmlPath>"This gives the whole shape (sections, plan, line ranges, token sizes) in a few hundred tokens — most of the raw HTML is boilerplate. Pull only the sections you touch.
3. Mark the batch in-progress, then process each thread
Before amending, signal you're actively on it — the browser's action button flips from "Picked up comments" to "Working on comments":
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" batch-working <id> <batchId>Then, for each thread listed in the batch (not every thread on the spec).
On a share batch, skip steps 1 to 3 entirely — locating, cross-referencing
and amending are all edit work — and go straight to step 4, the reply:
Locate the commented block: grep a distinctive phrase from
anchor.block.text, then open just that section (it reports the exact line range):node "${CLAUDE_PLUGIN_ROOT}/lib/spec-nav-cli.mjs" grep "<phrase>" --spec "<htmlPath>" node "${CLAUDE_PLUGIN_ROOT}/lib/spec-nav-cli.mjs" section <id> --spec "<htmlPath>"- Find coupled sections before editing —
xrefs <id>andgrep "<old term>"to find every place a changed term/number appears; open and edit those too. Amend
htmlPathwith the Edit tool. Preserve every<section id="…">and its id; keep the theme CSS and the floating TOC (the review layer owns theme + width). After changing a term/number, re-rungrep "<old term>"and expect zero hits. Re-run the lint if you changed structure:node "${CLAUDE_PLUGIN_ROOT}/lib/lint-spec.mjs" "<htmlPath>" --project "${CLAUDE_PLUGIN_ROOT}".Prose you add is held to the same language contract as the original, which is the
languagefrom step 1 and not the file on disk. Read it there; no summary of it appears here, because a summary would go on stating a rule this store's owner had deleted. Answering a comment is where explanatory, persuading register creeps in: the spec is still a specification, not a reply. Watch the advisoryspec-languageline in the lint.Reply inline (append-only, attributed to the active harness) via the CLI — never edit
comments.jsonby hand, and never use the HTTP API (it is human-only):node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" reply <id> <threadId> --body "<concise reply, name the section you changed>" --effect "<batchId>:<threadId>:reply" --model <your model id>--modelis your own model id; the harness is detected. When you are not the agent credited as the spec's author, the reply also credits you as one of its reviewers, shown on the home page and in the spec's header. The author answering its own comments stays the author. To review a spec rather than answer its comments, use thewrite-reviewskill: it files the review as a child spec and credits you.Answer first, then justify. If the comment asked a question, the first sentence is the answer — not preamble, not a restatement of the question, not what you were about to do. Reasoning follows only if it is actually needed.
Two paragraphs maximum. Most replies are one or two lines: what you changed and where (name the section). Verbosity in a comment is a defect, not thoroughness. No flair, no throat-clearing, no summarising what the human just said back at them.
If it genuinely needs a longer explanation, it does not belong in a comment. Add a Q&A section to the spec (or append to the existing one) carrying the human's question and your answer, then reply with the short answer plus a pointer to that section. The spec is where long-form reasoning lives and stays discoverable; comments are for resolving a thread.
Comment bodies render a small markdown subset. Use it to make the point land faster, never to decorate:
**bold**for the one term that matters,*italic*,`code`/`§ids`inline.-/1.lists (each on its own line) when you made several distinct edits — one bullet per edit beats a comma-spliced sentence.- A blank line between paragraphs (the display renders them as separate blocks).
Unsupported (headings, tables, links, blockquotes) render as literal text — don't use them. When in doubt, a single plain sentence is the right answer.
Do not resolve threads — only the human resolves (which closes them).
Actions
A comment can name an action: a menu entry the reader picked instead of
typing the same request again. It looks like @agent @visualize, and the reader
may have typed a qualifier after it.
specforge comments <id> resolves it for you. A thread that names an action
carries an actions array, and everything you need is in it:
"actions": [{
"id": "visualize",
"kind": "aside",
"instruction": "Choose the form this content actually wants, a diagram, a table or a mock, …",
"detail": "",
"section": "object",
"block": "b583",
"run": "node \"…/specforge-cli.mjs\" aside c8fb987ad0 --section object --block b583 --action visualize --file <path>",
"next": "This writes an aside, not an edit. Do not edit the section. …"
}]batchIdsays which submission asked for it. Work only the actions whosebatchIdis the batch you are on. Two batches can be pending at once and a thread can appear in both, so an action belonging to the next batch will be sitting right there on the thread you are reading.instructionis what you execute. The name on the button is not the ask.@visualizereads like ordinary English and it is not: it stands for a written standard, and following the word instead of the standard is how this went wrong four times in a row.nextsays what to do with the result. Read it before you touch the spec.run, where present, is the command, already carrying this thread's section and block. Run it rather than composing your own.target, on@importonly, is the aside being folded in: the section and block it came from, andguidancefrom the action that wrote it. What importing means depends on what kind of draft it is — a diagram supersedes the prose it was drawn from, a plain-language rewrite sits beside it, a verification report is not spec prose at all and imports as corrections to the claims it found wrong.nextalready carries the guidance; the rule over all of them is cut only what the draft carries forward. A diagram covering three paragraphs of twelve replaces three, not twelve.
The whole list, outside a thread:
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" actions # all of them
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" actions <id> # oneRead it; do not remember it. The instructions are edited, and a copy of one in your head is the version that was true last month.
| id | Where the result goes |
@explain_simply | aside |
@visualize | aside |
@go_deeper | aside |
@verify_against_code | aside |
@help_me_decide | aside |
@show_an_example | aside |
@restructure | in-place |
@tighten | in-place |
@delete_block | never reaches you; the browser removes the block itself |
@fix_the_naming | in-place, whole spec |
@consistency_pass | in-place, whole spec |
@canonicalize | in-place, whole spec |
@import | in-place, on an aside |
@delete | never reaches you; the browser removes the aside itself |
@copy_link | never reaches you; the browser answers it |
The rule the table follows: an action edits in-place when it changes the
form of content that is already there, and writes an aside when it produces
content that is not there yet. Each record carries its kind, so read that
rather than inferring from the label.
- in-place — edit the commented block or section directly, following the
instruction.
@restructureand@canonicalizerewrite everything in scope and nothing keeps the old version, so re-read what you are replacing before you replace it. aside — the output is a draft the reader decides on, not a claim the spec makes yet. Do not edit the section the comment sits on. Write your output to a file and run:
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" aside <specId> \ --section <sourceSectionId> --action <actionId> \ --block <anchor.block.bid> --thread <threadId> --file <path-to-your-html>You do not have to assemble that: the thread's
runfield already carries it.It places the section, numbers the id and writes the attributes the review layer reads. Do not hand-write that markup. Getting any of them wrong produces a draft the reader cannot see or answer, and this has already happened once: a Visualize run wrote its diagram straight into the section, with no wrapper and no way to reject it.
--blockis thebidfrom the thread'sanchor.block, and it is what puts the marker on the paragraph the reader asked about rather than at the top of the section.--threadis whatbatch-donechecks: without it the draft answers no request, and a draft written last week on the same section would close today's batch. Pass both whenever the thread has them.What you write is the body only: the content, in the spec's own component vocabulary. The command adds the wrapper and the heading. An aside gets no entry in the table of contents and needs none added.
Everything else about it is an ordinary section. It is commentable, it exports, and the verification gate reads its prose, so hold it to the same language contract as the rest of the spec.
- whole spec — scope is the document, not the block the comment sits on. These arrive from a right-click on the page background and anchor to the title, so the anchor is a place to hang the thread rather than the thing to change. Read the whole spec before you start: a consistency pass that has read one section has not done the work its name claims.
- on an aside —
@importarrives from the button on an aside, so the comment is anchored inside one. Act on the aside the anchor sits in, and on nothing else. The other button, Delete, never reaches you: the browser removes the aside and its threads itself.
Deleting a section deletes its asides with it. They are drafts about that section, and one left behind attaches itself to whatever section now precedes it.
Two actions carry needsDetail: @verify_against_code and
@fix_the_naming. Neither can run on its instruction alone — one needs the claim
to check and where to look, the other needs both terms of the rename. If the
comment carries nothing beyond the action, say what you would have done and
ask. A confident verification of the wrong claim, or a rename inferred from
the prose, is worse than a question.
Everything else in this skill still applies: an action arrives as a comment,
origin still decides whether you may edit, and the thread still gets a reply
saying what you did.
Comments asking for a child spec
A child spec is a full spec of its own that records which spec it belongs to: its own id, its own URL, its own status, its own review cycle. It exists so one area of an over-long spec can be read on its own.
There is no action for it and no menu entry. It arrives as an ordinary comment, in whatever words the reviewer used: "move this into a child spec", "this deserves its own spec", "pull the code grounding out". Read the intent rather than matching a phrase.
When a thread asks for one, on a daemon batch:
Make it, with the parent set:
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" create \ --title '<title>' --type <type> --parent <parentSpecId> --model <your model id>The title is yours to write, not the reviewer's to supply. A comment is text somebody typed, and pasting it between double quotes hands the shell
$(…)and backticks to run before the CLI sees any of it. Write a short title from what the thread asked for, in single quotes, and if it contains a single quote of its own, reword it.The type is chosen the way
create-specchooses one: a child is not a kind of spec, it is a spec with a parent. Code grounding is usuallycode-exploration-spec, background isresearch, a test strategy istest-plan.- Author it through the
create-specflow. A child gets the same treatment as any other spec: the type's sections, the language contract, the lint. - Reply with its id and URL. The reviewer cannot see it otherwise: the parent's Child specs drawer will list it, but the thread is where they asked.
What this does not mean. The parent is not emptied. Moving existing sections out of a spec is a different operation, with its own problems (comment threads anchored to those sections, and every block index after them), and it is not part of this. If the comment asks for content that is already in the parent to be moved rather than for a new spec to be written, say so and ask: leaving the sections in place and writing a child that duplicates them is worse than either.
On a share batch the rule above holds: reply, and create nothing. A child spec
is a new document in somebody else's store.
4. Mark the batch done
When every thread in a batch has a reply:
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" batch-done <id> <batchId>5. Report + re-arm the watcher
Briefly summarize per spec: how many threads you replied to and which sections you
amended (on a share batch, say that it was a reviewer's and that you answered
without amending, so the owner knows a promotion is theirs to make). The human
sees your replies + edits live and resolves the threads they're satisfied with.
Then keep delivery ready for the next browser action:
- In Codex, finish the turn. The host-owned watcher delivers later browser work automatically.
Do not start a watcher or poll while idle. An explicit
review-waitcommand checks once and returns; anext-turnresult does not require re-arming. - In Claude Code, relaunch
node "${CLAUDE_PLUGIN_ROOT}/lib/specforge-cli.mjs" review-waitas a background task. - In Pi, the extension re-arms delivery when the review turn settles.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

