From 056e639bed20f6b6567c36ac9139ae6f12be4330 Mon Sep 17 00:00:00 2001 From: buildpulser Date: Sun, 16 Aug 2026 17:50:58 -0700 Subject: [PATCH] fix: remove the internal voice doc from the public action repo EXTERNAL-VOICE.md is an internal style rulebook. It was copied here by its own sync convention, which targets "every repo that owns a public surface": agents, web-client, customer-agents, test-reporter-action. Three of those are private. This one is not, so the convention put an internal document into the only repo where it is world readable. What it exposed: - The rationale for the rule, which states that we avoid the em-dash because it is the most recognizable tell of machine written prose. - Paths inside three private repos (blog-generator/validate.go, social-poster/compose.go, video-poster/shotlist.go, review-trigger/capnotice_test.go) and which guard covers which surface. - A production bug, named and dated. - A declined cleanup: roughly 750 em-dashes across 88 published posts, left in place on purpose. None of it is a credential. All of it is internal reasoning about public copy, sitting in the repo customers clone. The README's "Writing for this repo" section linked the file, so that link is gone too. The practical instruction stays, because a contributor does need to know the house style. What leaves is the reasoning behind it, the map of private code, and the two admissions. Deleting the file removes it from the tip. It remains reachable in history at the commits that added it, and purging that needs a rewrite, which is a separate decision. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015WfnKeNtnjCawdQQwWhybU --- EXTERNAL-VOICE.md | 119 ---------------------------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 120 deletions(-) delete mode 100644 EXTERNAL-VOICE.md diff --git a/EXTERNAL-VOICE.md b/EXTERNAL-VOICE.md deleted file mode 100644 index 10a5a11..0000000 --- a/EXTERNAL-VOICE.md +++ /dev/null @@ -1,119 +0,0 @@ -# External voice: no em-dashes - -This file is the canonical rule. It is copied verbatim into every repo that owns -a public surface: `agents`, `web-client`, `customer-agents`, -`test-reporter-action`. Change it in one place and port the change to the rest. - -## Scope - -Everything BuildPulse publishes or sends outside the company: - -- Blog posts and the prompts that generate them -- Marketing pages, pricing, comparison, and about copy -- Social captions, video scripts, voiceover, and audio explainers -- Public READMEs and docs -- Review comments and notices we post on customer pull requests -- In-product copy a customer reads -- Outbound email - -Out of scope: internal code comments, internal runbooks, log lines, commit -messages, and anything only employees read. Those are not worth the diff noise. - -## The rule - -Do not use the em-dash (`—`, U+2014) or the horizontal bar (`―`, U+2015). - -Do not use an en-dash (U+2013) with a space on either side in their place. That -is the same tell wearing a hat. - -Keep the unspaced en-dash in numeric ranges: `20–40%`, a `100–800` person -company. That is correct typography and reads as human. - -A range whose endpoints contain spaces takes a spaced en-dash, and that is also -correct: `Aug 7 – Sep 7, 2026`. The test bans the spaced en-dash because that is -overwhelmingly a dash joining two clauses, so a genuine range like this one is -the rare case where the guard is too broad and the code is right. Pin the line -rather than mangling the date. - -Never substitute a hyphen with a space on either side. It is a worse em-dash and -it reads as a find-and-replace job rather than a rewrite. - -(Those two rules are written out in words on purpose. Written as inline code the -significant spaces get trimmed by a formatter, which silently inverts what the -rule says.) - -And no HTML entity dashes: `—`, `—`, `—`, or a spaced -`–`. They render as exactly the characters above, but they are plain ASCII -in source, so a search for the character does not find them. In JSX they are -worse than a rendering nit: an entity inside a multi-line text run makes the -transform drop the space after an adjacent expression, which has already shipped -`vs the 30before` to production once. - -## How to rewrite, by job - -An em-dash does at least five different jobs. Each has its own correct fix. -Pick by what the dash was actually doing, not by what is quickest. - -| Job | Use instead | Before | After | -| ------------------------------------ | ------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------ | -| Abrupt pivot into a punchline | Full stop, two sentences | `You didn't fix it — you bribed it.` | `You didn't fix it. You bribed it.` | -| Introducing an explanation | Colon | `Code blocks don't count — a code-heavy post needs the same prose.` | `Code blocks don't count: a code-heavy post needs the same prose.` | -| Label before a definition, in a list | Colon | `**Bugs** — logic errors, nil deref.` | `**Bugs**: logic errors, nil deref.` | -| Paired aside | Parentheses, or commas if short | `environments — fintech, healthtech — where CI gates` | `environments (fintech, healthtech) where CI gates` | -| Clause tacked on the end | Comma, or recast | `...in AI features — or email support.` | `...in AI features, or email support.` | - -If none of those land cleanly, rewrite the sentence. The dash was often hiding a -sentence that wanted to be two. - -## Why - -Two reasons, and the second is the one that matters. - -The em-dash is the single most recognizable tell of machine-written prose, and -our audience is engineers who spot it instantly. A post that reads as generated -undercuts the argument in it, and a review comment that reads as generated gets -dismissed before the finding is read. - -More importantly, the dash is a way to avoid deciding what the relationship -between two clauses actually is. Choosing a colon, a full stop, or parentheses -forces that decision, and the prose gets sharper for it. - -## For prompts specifically - -A prompt that bans em-dashes while being written in em-dash style will lose to -its own example. Models copy the register of their instructions more reliably -than they follow a rule buried in a bullet list. - -So: strip the dashes from the prompt's own prose, then state the rule. Both, not -either. - -State the rule as a taxonomy, not a prohibition. Told only "no em-dashes", a -model swaps in a spaced hyphen and you have gained nothing. - -## Enforcement - -| Surface | Guard | -| ---------------------------- | ------------------------------------------------------------------------------------- | -| Blog posts | `agents/blog-generator/validate.go` hard reject, fed back into the model's retry loop | -| Social captions | `agents/social-poster/compose.go` re-asks once, then publishes and warns | -| Video captions and voiceover | `agents/video-poster/shotlist.go` critique pass checks for it | -| Customer PR cap notice | `customer-agents/review-trigger/capnotice_test.go` | -| Marketing pages | `web-client/__tests__/marketing/emDashGuard.test.ts` | - -Anything not in that table is on the author. The guards exempt this file, since -it necessarily quotes the character it bans, and they exempt fenced code blocks, -because a dash inside sample output belongs to the code and not to our voice. - -### Coverage - -`web-client` is swept end to end: marketing, the site-wide social metadata, the -explainer prompts, the invitation emails, and the in-product dashboard copy -(tooltips, empty states, banners, chart subtitles, Stripe invoice descriptions, -the Slack quarantine notice). The guard scans all of it, and bans entity dashes -across the whole of `src` regardless. - -Not covered by any guard: post bodies already stored in `blog_posts`. Fixing the -generator did nothing for the roughly 750 em-dashes in the 88 posts published -before the rule existed. A backfill was considered and deliberately declined, so -buildpulse.io/blog will keep showing them in older posts. That is a decision, -not an oversight. diff --git a/README.md b/README.md index 5d37bff..fd48cde 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ The committed `dist/index.js` must stay in sync with `src/`. If you change `src/ ## Writing for this repo -This repository is public and this README is the first thing a prospective user reads, so its prose follows [EXTERNAL-VOICE.md](EXTERNAL-VOICE.md). +This repository is public and this README is the first thing a prospective user reads, so its prose follows the house style below. The short version: no em-dashes. Rewrite with a full stop, a colon, parentheses, or a comma depending on what the dash was doing, and never a spaced hyphen. The same rule covers the action description in `action.yml` and any log line the action prints into a user's CI output.