Skip to content

docs(voice): one shared voice core for blog and newsletter, and extend check:voice to issues - #139

Merged
axsaucedo merged 1 commit into
masterfrom
feat/voice-unification
Aug 30, 2026
Merged

docs(voice): one shared voice core for blog and newsletter, and extend check:voice to issues#139
axsaucedo merged 1 commit into
masterfrom
feat/voice-unification

Conversation

@axsaucedo

Copy link
Copy Markdown
Collaborator

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.

  • New .github/instructions/voice.instructions.md - the shared core, applying to src/content/blog/** and src/content/newsletter/**, symlinked from .claude/rules/voice.md. Personification, sentence shape, no superfluous text, word choice, banned constructions, banned words, calibration pairs.
  • blog.instructions.md keeps only blog-specific rules: 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 points at the core first.

No list is duplicated across the two.

Word-list conflicts

Word Newsletter evidence Resolution
harness 9 uses, always the noun, often a proper name ("DeepSeek's Agent Harness") Noun allowed, verb banned
journey 4 uses: a talk title, a corpus quote, an anniversary aside Banned; the quoting exception covers the title
showcase 1 use, Streamlit's own page name Banned; same exception
leverage 4 uses in 40 issues Banned; "use" always substitutes
unlock 7 uses Never banned anywhere; stays available
revolutionary, game-changing, moreover, furthermore 0 uses Added to the shared core and to the mechanical check

The 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.sh now reads newsletter issues from 402 on, mirroring the blog's 2026-08-29 cutoff 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:check and npm run check pass. node scripts/newsletter/style-corpus.mjs --lint src/content/newsletter/402.md still reports the same 1 known error and 1 warning, both left standing. No published prose was edited.

…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.
@axsaucedo
axsaucedo merged commit 68e7290 into master Aug 30, 2026
7 checks passed
@axsaucedo
axsaucedo deleted the feat/voice-unification branch August 30, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant