docs(voice): one shared voice core for blog and newsletter, and extend check:voice to issues - #139
Merged
Merged
Conversation
…d check:voice to issues
The blog's voice guide and the newsletter's mined style doc were written
independently, so the LLM tells the blog bans outright ("It's not X. It's Y.",
copula avoidance, trailing "-ing" commentary, mic-drop closers, the rule of
three) were unenforced in the newsletter, and the banned word lists were about
to drift apart in two files.
Split the shared core out into `.github/instructions/voice.instructions.md`,
applying to `src/content/blog/**` and `src/content/newsletter/**`, symlinked
from `.claude/rules/voice.md` per the repo convention. It holds personification,
sentence shape, no superfluous text, word choice, banned constructions, banned
words and the calibration pairs. `blog.instructions.md` keeps only what is
blog-specific (Title Case claim headings, the colon rule, bullet shape, what the
posts do, the ship/shipping ban); `references/style.md` keeps only the
newsletter's measured shape and register and now points at the core first. No
list is duplicated across the two.
Word-list conflicts resolved rather than papered over. `harness` is allowed as a
noun and banned as a verb: the newsletter reports on agent harnesses by name
("DeepSeek's Agent Harness") and cannot lose the word. `journey` and `showcase`
stay banned; their only corpus uses are a talk title and a product's own page
name, which the quoting exception already covers. `leverage` stays banned at 4
uses in 40 issues, where "use" always substitutes. `unlock` (7 uses) was never
banned and stays available. The newsletter's zero-occurrence never-list
(revolutionary, game-changing, moreover, furthermore) is safe for both media and
moves into the shared core, and into the mechanical check.
The newsletter's colon-lede is preserved explicitly, with the reason: at 58% of
sections it is the strongest structural tell of the voice, and it is the
permitted colon use (the lede introduces the detail that follows), not the
mid-prose apposition the blog merges. A future agent that reads only the blog
rule would "fix" every lede in the archive.
`check-voice.sh` now reads newsletter issues as well, from 402 on, mirroring the
blog's 2026-08-29 cutoff so that 359-401 are not retro-edited. The two arms
differ on one check only: the blog keeps the blanket non-ASCII grep, because a
post has no legitimate use for a non-ASCII character; the newsletter cannot use
it, because emoji are content there (the boilerplate alone carries seven), so it
greps for exactly what the ASCII punctuation rule exists to catch, as an
alternation of literal characters that matches the same UTF-8 bytes whatever
locale the runner has. Verified failing on each injected violation and passing
on emoji-only content, on 402 as published and on a pre-cutoff issue.
The `newsletter-issue` skill reads both documents at step 4 and runs
`npm run check:voice` at step 7. `blog.instructions.md`'s enforcement paragraph
now describes what the check actually does; the CI gate it claimed already
existed (CONTENT_PATHS covers both directories in `ci.yml`) is now true, since
the script previously read blog posts only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The blog's voice guide and the newsletter's mined style doc were written independently, so the LLM tells the blog bans outright were unenforced in the newsletter, and the banned word lists were about to drift apart in two files.
.github/instructions/voice.instructions.md- the shared core, applying tosrc/content/blog/**andsrc/content/newsletter/**, symlinked from.claude/rules/voice.md. Personification, sentence shape, no superfluous text, word choice, banned constructions, banned words, calibration pairs.blog.instructions.mdkeeps only blog-specific rules: Title Case claim headings, the colon rule, bullet shape, what the posts do, the ship/shipping ban.references/style.mdkeeps only the newsletter's measured shape and register, and points at the core first.No list is duplicated across the two.
Word-list conflicts
harnessjourneyshowcaseleverageunlockrevolutionary,game-changing,moreover,furthermoreThe colon-lede is preserved explicitly, with the reason. At 58% of sections it is the strongest structural tell of the voice, and it is the permitted colon use (the lede introduces the detail that follows), not the mid-prose apposition the blog merges.
The check
check-voice.shnow reads newsletter issues from 402 on, mirroring the blog's2026-08-29cutoff so 359-401 are not retro-edited. The two arms differ on one check: the blog keeps the blanket non-ASCII grep; the newsletter cannot use it, because emoji are content there, so it greps for en dash, em dash, curly quotes and the ellipsis character as an alternation of literal characters that matches the same UTF-8 bytes in any locale.Verified: fails on each violation injected into a scratch issue (banned word,
moreover, em dash, curly quote, ellipsis) and on a blog post with an em dash; passes on emoji-only content, on 402 as published, on a pre-cutoff issue carrying deliberate violations, and on the tree as it stands.npm run check:voice,npm run lint,npm run format:checkandnpm run checkpass.node scripts/newsletter/style-corpus.mjs --lint src/content/newsletter/402.mdstill reports the same 1 known error and 1 warning, both left standing. No published prose was edited.