This page is the rubric for a new memo post. Run it on your draft before you open a pull request, whether you are staff, alumni or a guest. An agent can run it for you, and the last section says how.
The rubric has two parts. The mechanical checks come first: a script finds each problem and says where it is. The judgment checks come second: you answer yes or no and fix what fails. Both parts apply to every path. The media standard holds the file-level rules for images and SVG. Contribute to memo holds the paths.
Run the checker on your note. It needs Node 18 or newer and nothing else.
node _meta/scripts/post-check.mjs path/to/your-note.md
It prints PASS <file> or one WARN <id> <file:line> <message> per problem. It exits 0 by default, so it never blocks a commit. Add --strict to exit 1 on any warning. The pre-commit hook runs it in the warning-only mode on the notes you stage.
Fix every warning, or know why you keep it. A warning you keep goes in the pull request body in one line.
| Id | Rule | How to fix |
|---|---|---|
| dash | No em dash or en dash, in the body or the frontmatter. | Use a comma, colon, semicolon, brackets or a new sentence. |
| dek-long | The description is the dek: one light sentence, under 30 words. | Cut it to the one thing the reader gets. |
| heading-number | A heading carries no number or "Part 2" label. | Drop the number. Link with @sec-<heading-slug>. |
| sec-ref | Prose never says "section 4". | Write @sec-<heading-slug>. |
| fig-line | No separate _Fig. N_ caption line under a figure. | Put the caption in the image title. |
| fig-prose | Prose never says "Fig. 6". | Write @fig-<id>. |
| inline-code | At most one inline code span in a body paragraph. | Say it in words, or move the detail into a fence or the appendix. |
| hash-body | No commit hash in the body. | Move it to the appendix table. |
| version-body | No version string in the body. | Move it to the appendix table. |
| fileline-body | No file:line reference or #L12 link in the body. | Move it to the appendix table. |
| url-bare | No bare URL in prose. | Link it as [Title](url), or move it to References. |
| ai-tell | No phrase from the short tell list, such as "here's the thing" or "in conclusion". | Rewrite the sentence. The judgment checks cover the rest of the tells. |
| ascii-art | No box-drawing diagram in a code fence. | Make it a figure. |
| fence-lang | Every code fence sets a language. | Add one. Use text when nothing fits. |
| eq-id | Display math is one line: $$...$$ {#eq-id}. The formatting bot splits a multi-line block whose closing line carries the id. | Put the formula and the id on one line. |
| eq-where | A numbered equation is followed by a where: list. | List each symbol under the formula. |
| id-dup | Each {#fig-...}, {#tbl-...} or {#eq-...} id is used once. | Rename one. |
| ref-missing | Every @fig-, @tbl-, @eq- and @sec- points at an id that exists. The build fails on a miss. | Fix the id or add the target. |
| fig-alt | Every image has alt text. | Write what the figure shows. |
| fig-embed | Every image uses the head-figure form, with a title and a {#fig-id}. |  |
| fig-title | The caption's first sentence is the same short title as the alt text. | Start the caption with the title. |
| asset-missing | Every image path exists. | Add the file or fix the path. |
| asset-case | Asset paths are lowercase, because production is case-sensitive. | Rename the file and the link. |
| live-twin | A .live figure has a figkit .fig.html file beside its SVG. | Add the twin, or drop .live. |
| svg-viewbox | The SVG viewBox is 760 wide. | Redraw on a 760 wide plate. |
| svg-fixed-size | No fixed width, height or min-width in the SVG. | Delete them and keep the viewBox. |
| svg-font-size | Text is 9 to 10.5 in a plate, or about 11 to 13.5 in a dense live diagram. The script flags anything outside 9 to 13.5. | Resize the text. |
| svg-font-family | The SVG font includes ui-monospace. | Set font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace". |
| svg-dasharray-attr | stroke-dasharray is set in style, never as an attribute. | style="stroke-dasharray:5 4". |
| svg-smil | No SMIL (<animate>, <set>). | Use a figkit .fig.html for motion. |
| svg-script | No script or event handler in an SVG. | Remove it. |
| table-cell | Wide comparison tables use few words per cell. | Cut cells to a few words; move prose out. |
| table-label | Row labels in the first column are short. | Shorten them to six words or fewer. |
| ref-section | A post that cites [n] ends with one ## References list. | Add the list. |
| ref-format | Each entry reads n. **Term or Name**: definition or role, date. [Title](url). | Rewrite the entry. |
| ref-order | Entries are numbered 1, 2, 3 with no gap. | Renumber. |
| ref-bare-url | No bare URL in an entry. | Wrap it as [Title](url). |
| ref-multi | One source per entry: at most a source and one companion link, and under 450 characters. | Split it into entries. |
| ref-sublist | No list nested inside an entry, such as a list of files read. | Keep one definition and one link. The file list goes in the appendix. |
| ref-unused | Every entry is cited somewhere, as [n] or [term](#ref-n). | Cite it or delete it. |
| cite-missing | Every [n] has an entry. | Add the entry. |
The script reads the note and the SVG files it links. It cannot judge a figure's content, so the judgment checks still apply.
The script does not decide whether a rule is worth breaking once. A hash in the body is fine when the post is about that commit. Use your judgment, then say so in the pull request. It flags the bad examples quoted on this page too, because it cannot tell a quote from a mistake. It also skips a missing raster image on a note that is already merged and unchanged, because media-sync moves rasters out of git after the merge.
Answer each question yes or no. A "no" means rework, not rewording. The weakest items are usually 1, 2 and 11.
-
Do the sections follow the reader's questions?
- Good: a reader who stops after any section got a full answer to the question that section's heading asks.
- Bad: sections follow the order you did the work in. Good: "Why it costs so much", then "How it works", then "When it pays".
-
Does each section open with its answer, in one or two plain sentences?
- Good: the first lines answer. Detail follows.
- Bad: "To understand this, we first need to look at how requests flow." Good: "The agent pays for the tool list on every request, so cost grows with the tool count."
-
Is the dek one light sentence?
- Good: it names the reader's question or the result, with no setup.
- Bad: "A deep dive into agent costs, with benchmarks and a framework." Good: "An agent that writes one program costs less than one that calls tools one by one."
-
Is the core mental model stated before the mechanism?
- Good: one sentence of "what this is" comes before any stage, step or internals.
- Bad: the pipeline stages arrive in section two and the idea in section five.
-
Are the headings plain words, and do the cross-references use ids?
- Good: a heading names a topic or question, and prose links with @sec-....
- Bad: "4. Implementation details" and "see section 4".
-
Is each paragraph one idea at body density?
- Good: identifiers, hashes, versions and file locations live in appendix tables with the columns What we checked, What it does, Where.
- Bad: "fabric_exec calls bridge.ts:212 on v0.109.3" in a body paragraph. Good: "The tool call crosses one bridge to the host", with the file and version in the appendix.
-
Does each figure earn its place, and is it complete without motion?
- Good: one idea per figure, one accent color, text readable at phone width, and a caption that says what to look at.
- Bad: an ASCII diagram in a fence, or a figure that repeats the paragraph above it. A live figure's static SVG must tell the whole story.
-
Do figures sit at column width, with .wide only for content that is wider?
- Good: a small diagram or a chart stays at column width.
- Bad: .wide on a four-box diagram.
-
Do tables read at a glance?
- Good: a Table: title line where the table needs a name, short row labels, and a few words per cell.
- Bad: an eight-column comparison with full sentences. Good: three to five columns of two to six words, with the prose above it.
-
Does every fence the prose discusses name its file and highlight its lines?
- Good: {file="turn-8.ts" hl="2"} on the fence the paragraph walks through.
- Bad: a code block the text refers to as "the snippet above" with no file name.
-
Does every claim carry evidence?
- Good: a number names its run, a claim names its source, and a source is in References.
- Bad: "Code mode is much cheaper." Good: "Code mode used 4,100 input tokens against 29,000 on the same task, mean of three runs, source cited."
-
Are the references useful?
- Good: a term peek ([term](#ref-n)) exists only for a word the reader may not know, defined once at first use. Each entry is one source.
- Bad: a peek on every noun, or one entry that bundles five links and a file list.
-
Does it read like a person who did the work wrote it?
- Good: it holds a firsthand fact, a position a peer could dispute, and one thing that went wrong. See the voice gate.
- Bad: corrective negation ("it is not X, it is Y"), three parallel items in a row, and a tidy closing line that restates the point.
-
Is every sentence free of filler and em or en dashes?
- Good: short sentences, concrete nouns, and no summary paragraph at the end.
- Bad: "In today's fast-moving landscape, it is worth noting that..."
-
Did you check the render?
- Good: you ran the note locally, took screenshots at 1440, 1100 and 390 pixels wide in light and dark, and saw no sideways page scroll, legible figures, and margin notes that do not crowd the right rail.
- Bad: "the build passed". A passing build does not show a clipped figure or a wall of margin notes.
- How: run the memo app against your branch (see apps/memo/README.md in dwarvesf/foundation-apps), or ask an editor for a preview.
Return this at the end of the check, in this shape.
Mechanical: PASS | N warnings (list the ids and counts)
Judgment: 1 pass, 2 fail, 3 pass, ... 15 pass
Weakest: one sentence naming the single weakest spot and where it is
Blocking: the failing items that must be fixed before review (empty if none)
An item is blocking when it is 1, 2, 11 or 13, when a talk fails J1 or J6, or when ref-missing fires (the build fails on it). Every other failure, J2 to J5 included, is a fix before review that an editor may waive.
A note with layout: talk in its frontmatter is a talk: each slide sits beside the notes for it. How to publish a talk on memo is the worked example, and Contribute to memo has the file layout. A talk gets every check above, plus the eight below and the six judgment questions after them.
The same script runs these, and only on a talk. A slide image is not a figure, so the figure rules (fig-embed, fig-title, fig-alt, asset-missing) skip it and the talk checks take over. To build the slide files and see these results in one step, run node _meta/scripts/talk-build.mjs <note>.
| Id | Rule | How to fix |
|---|---|---|
| T1 | talk.deck is assets/<slug>/deck.pdf, a PDF with one page per slide. | Export the deck again, or add or drop slides to match. |
| T2 | Slides run slide-01 to slide-NN in order, each used once, no unused file. | Rename the files and links. Remove a slide file you cut. |
| T3 | The talk has 8 to 40 slides. | Merge thin slides, or split a long talk into two posts. |
| T4 | Alt text has 8 or more characters and says what the slide says. | Write the slide's point in words, not "Slide 4". |
| T5 | Webp or png, 500 KB at most, long edge 1200 to 3200, one ratio. | Export at 16:9, 16:10 or 4:3; set talk.ratio to match. |
| T6 | 15 to 150 words of notes per slide; the first and last may skip. | Say what you said over the slide. Move a long aside to the outro. |
| T7 | 2 to 8 ## sections, each holding 2 to 10 slides. | Group the slides by the beats of the argument. |
| T8 | image: points at og.png, a 1200 by 630 PNG of 300 KB or less. | Run talk-build.mjs, which renders it from slide 1. |
T5 accepts 16:9, 16:10 and 4:3. It also compares the slides with talk.ratio, which defaults to 16:9, because the site reads the ratio from the frontmatter once media-sync has moved the slides out of git. talk-build.mjs writes the field. Like the figure checks, these skip a missing slide, deck or share image on a talk that is already merged and unchanged, because media-sync moves those files out of git. The script's own test builds a small talk and breaks one rule at a time: node --test _meta/scripts/post-check-talk.test.mjs.
Answer each one yes or no, the same way as the fifteen questions above.
| Id | Question |
|---|---|
| J1 | Skim test: do the slides alone carry the whole argument? |
| J2 | One idea per slide, about 25 words at most on a text slide? |
| J3 | Do the notes add what the slide cannot, and never read it aloud? |
| J4 | Is there one visual language within the deck: one template, one type scale? |
| J5 | Does slide 1 name the speaker and role, and the last give the takeaway? |
| J6 | Is the piece a linear argument with distinct beats? |
Quotes and screenshots are exempt from the word limit in J2. The last slide also says where to find the speaker (J5). A reference page, a playbook or a data-heavy report fails J6 and stays as prose. J1 and J6 are blocking, like items 1, 2, 11 and 13. A J6 failure never rejects the piece: drop layout: talk and publish it as a normal post.
Add a line for the talk checks to the verdict block:
Talk: T1 to T8 PASS | N warnings; J1 pass, J2 pass, ... J6 pass
Fetch this page and run both parts before you open a pull request.
- Fetch the rubric with curl, and read it whole. A summary drops the rules. The page is at https://memo.d.foundation/memo-post-rubric, and the source is handbook/memo/post-rubric.md in dwarvesf/brainery.
- Run the checker from a clone of dwarvesf/brainery: node _meta/scripts/post-check.mjs <your note>. Fix every warning, or write one line in the pull request body for each one you keep. A guest writer without a clone applies the mechanical checks by hand from the table.
- Answer the fifteen judgment questions, plus J1 to J6 for a talk, and return the verdict block above. Put it in the pull request body.
- Do the render check from item 15 and attach the screenshots, or say that you could not.
- Stop and ask the human when the weakest spot needs facts only the author has, such as a number, a decision date, or something that broke. Never invent evidence to pass an item.