@domandigital/craft 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COPY.md ADDED
@@ -0,0 +1,280 @@
1
+ # The house copy standard
2
+
3
+ **Canonical.** Every client repo, every session, every piece of outbound
4
+ writing. This file ships in `@domandigital/craft`, next to the tells that
5
+ enforce it. Anything else claiming to be the copy rules is a copy: link here.
6
+
7
+ - The rules are this file.
8
+ - The detectors are craft's copy tells: `craft tells list` shows every one,
9
+ with why it is a default and what to do instead.
10
+ - The house policy, which tells fail a commit, is `src/character/house.ts`.
11
+ `craft copy --gate` applies it. `copy-check` in claude-kit is a thin wrapper
12
+ over that, and the pre-commit hook runs it.
13
+
14
+ ## Why
15
+
16
+ A reader who has seen a thousand generated pages recognises one in a line.
17
+ None of the patterns below is AI-only, and people use all of them. The signal
18
+ is **stacking**: one is plausible, three on a page reads as machine output.
19
+
20
+ What makes copy read as written by a person is not the absence of tells. It is
21
+ the presence of facts: a place, a time, a number, a name, who does the work.
22
+ The rules below remove the defaults; the writing directives say what to put in
23
+ their place.
24
+
25
+ ## Blocking tier
26
+
27
+ A single hit fails the check. These are not judgment calls.
28
+
29
+ - **Em dashes.** Use a full stop, a comma, a colon or a middot. En dashes for
30
+ genuine ranges (`Monday–Saturday`, `9am–5pm`) are correct and stay.
31
+ - **Emoji** in shipped copy. Typographic marks are fine: `★` for a rating, `·`
32
+ as a separator, `→` in a link.
33
+ - **Contrastive negation as a template**, in every form:
34
+ - copular: "It's not a website, it's a growth engine"
35
+ - verbal: "I don't teach the treatment you'd choose: I teach the standard"
36
+ - "not just X" / "not merely" / "not simply" / "more than just"
37
+ - "X isn't a job, it's art"
38
+ - "less about X, more about Y"
39
+ - parallel participle: "shipped from here, not imported from Seoul". Both
40
+ sides must be real participles, so "cosmetics, not medicines" (a plain
41
+ factual contrast) passes.
42
+ - **A "No X" claim as the whole string.** "NO JARGON GUIDE", "No catch." A
43
+ short standalone "No X" reads as a slapped-on badge. Empty-state and error
44
+ text is exempt ("No results found", "No bookings yet"), as is anything under
45
+ a signed-in route (`/admin/`, `/portal/`, `/dashboard/`).
46
+ - **"No X, no Y" as a list**, anywhere in a sentence. "No obligation, no
47
+ spam." Defining the business by what it is not, itemised.
48
+ - The phrase list: `let's dive in`, `let's unpack`, `let's break this down`,
49
+ `here's the thing`, `it's important to note`, `at its core`,
50
+ `the key takeaway`, `the reality is`, `the truth is`,
51
+ `in today's rapidly evolving…`, `increasingly digital`,
52
+ `whether you're a X or a Y`, `seamless(ly)`, `unlock your…`,
53
+ `meaningful impact`, `foster collaboration`, `empower`, `leverage`,
54
+ `harness`, `delve`, `a comprehensive guide to`, `plain English`.
55
+
56
+ Also blocking: `in the event that`, `at no additional cost to you`, `rest
57
+ assured`, `it is worth noting that`, `when it comes to`, `we've got you
58
+ covered`, `dive into`, `unlock the power`, `look no further`.
59
+
60
+ **Buzzwords**: synergy, cutting-edge, best-in-class, best of breed,
61
+ world-class, state of the art, game-changer, paradigm shift, mission-critical,
62
+ low-hanging fruit, move the needle, circle back, touch base, reach out (use
63
+ "get in touch"), value-add, robust, turnkey, holistic, disruptive.
64
+
65
+ **Negative reassurance**: `no risk`, `never locked in`, `nothing goes wrong`,
66
+ `no hidden fees`, `never pay`, `without the hassle`, `no surprises`. Do not
67
+ name a fear and deny it. Say what they get: "The price on the quote is the
68
+ price you pay", not "no hidden fees".
69
+
70
+ `empower`, `leverage` and `harness` block because a plainer word almost always
71
+ exists. Name the mechanism: "Your team can publish without us", not "we
72
+ empower your team".
73
+
74
+ `plain English` has one house replacement: **"properly explained"**.
75
+ "Explained plainly" swaps a synonym and keeps the tell.
76
+
77
+ ## Review tier
78
+
79
+ Printed, never blocks. One is normal English; several stacked is not. Only a
80
+ person reading decides which instance earns its place.
81
+
82
+ | Tell | What it catches |
83
+ | --- | --- |
84
+ | `review-phrase` | truly, genuinely, deeply, ultimately, great question, absolutely, one-size-fits-all, and superlative over-claiming: "the single most", "by far the most", "hands down the", "arguably the best", "bar none" |
85
+ | `ai-vocabulary` | tapestry, testament, realm, elevate, nestled, meticulous, unparalleled, moreover; and underscore, showcasing, pivotal, measured 10 to 14 times over-used in post-2022 text |
86
+ | `stock-phrase` | to the next level, one-stop shop, we pride ourselves, passionate about, peace of mind, "so here's", a journey used as a metaphor |
87
+ | `hollow-imperative` | Discover, Experience, Transform, Elevate as the opening verb of a heading or button |
88
+ | `rhetorical-opener` | "Looking for...?", "Tired of...?", "Ready to...?" |
89
+ | `staccato-triplet` | "Fast. Friendly. Local." |
90
+ | `where-x-meets-y` | "Where luxury meets comfort" |
91
+ | `ing-tail` | "..., ensuring peace of mind", "..., highlighting our commitment": significance claimed, no mechanism |
92
+ | `vague-attribution` | "studies show", "experts agree", "it is widely known": a claim credited to nobody |
93
+ | `closing-summary` | "Overall,", "In conclusion,", "In short," opening a paragraph that restates what came before |
94
+ | `false-range` | "from first-time buyers to seasoned investors alike", "everyone from students to retirees" |
95
+ | `vague-word` | innovative, scalable, end-to-end, streamline, solutions. Reported only when a file is named outright |
96
+ | `question-reveal` | "The result? Twice the bookings.", "What does this mean for you? It means...": a staged reveal |
97
+ | `inline-label-list` | three or more bullets in a row opening "**Label:**", where the label repeats the line |
98
+ | `placeholder` | "[Insert client name]", lorem ipsum, `TODO_PLACEHOLDER`. Fine in a draft commit; **zero in anything delivered** |
99
+ | `chatbot-residue` | "Certainly! Here's a revised version", "as an AI language model", "let me know if you'd like me to", chat citation tokens, links tracked `utm_source=chatgpt.com`. **Zero in anything delivered**, and the first candidate for the blocking tier |
100
+
101
+ ### Style is not evidence
102
+
103
+ Most tells here are house style: they make copy generic, whoever wrote it. The
104
+ research is clear that an em dash, a rule of three or a rhetorical question
105
+ proves nothing about authorship, and em dash rates even run in opposite
106
+ directions between models. Only `chatbot-residue` is evidence that a chat reply
107
+ was pasted. Report findings as an editorial diagnosis ("three benefit claims
108
+ lack a mechanism; two stock phrases recur"), never as a score or an
109
+ accusation.
110
+
111
+ Two more review patterns no regex can separate from honest English, so they
112
+ are for the read-through only:
113
+
114
+ - **Trailing contrastive negation**: "Real Square data, not a vanity metric."
115
+ The same shape is how a genuine disclaimer is written ("general information,
116
+ not a recommendation").
117
+ - **Abstract noun triads** with no concrete referent: "clarity, consistency,
118
+ confidence".
119
+
120
+ ## Density tier
121
+
122
+ Review only, never blocks. Every rule above fires on one line read in
123
+ isolation. These fire on a **rate**: each instance is legitimate English, and
124
+ only the count gives it away. They read documents (Markdown, MDX, text, HTML)
125
+ of 400 words or more, and the allowed count is a floor plus a rate per 1,000
126
+ words, so a proposal is not held to a landing page's limit.
127
+
128
+ Why they exist: a 30-page client proposal passed every per-line rule clean.
129
+ A person then found "rather than" 54 times in 10,500 words, no contractions,
130
+ one sentence pasted onto two pages, a heading repeated as its own first line,
131
+ and an italic one-liner closing almost every page.
132
+
133
+ | Tell | Fires above |
134
+ | --- | --- |
135
+ | `phrase-density` | "rather than" (4, +0.9 per 1,000 words), "instead of" (4, +0.7), "actually" (2, +0.35), "properly" (3, +0.4), "genuinely" (1, +0.2), "the single ...est" (1, +0.15), "That is / This is" openers (4, +0.7), "Every" openers (4, +0.6), "Worth noting" openers (3, +0.5), negation-led openers "None of / Not one / Nothing" (5, +1), participle triads "researched, written and referenced" (3, +0.6) |
136
+ | `aphorism-density` | two clipped sentences alone on a line, "It already exists. The job is making it findable." (5, +1.2) |
137
+ | `contraction-scarcity` | under one contraction per 1,000 words, in 800+ words. Formal legal pages are the deliberate exception |
138
+ | `sentence-rhythm` | sentence lengths that barely vary: spread (standard deviation over mean) under 0.42 across 40+ sentences |
139
+ | `repeated-sentence` | a sentence of nine words or more appearing twice, or two sentences sharing a nine-word run |
140
+ | `heading-shape` | headings sharing one shape: "X, and Y", "X, because Y", a bare comma list (3, +0.5) |
141
+ | `heading-echo` | a heading of four words or more repeated as the first words under it |
142
+
143
+ The fix is never to delete every instance. Keep the ones that land hardest and
144
+ vary the rest. That is why a density finding prints a count and an example,
145
+ not a line to go and edit.
146
+
147
+ ## Writing directives
148
+
149
+ These are what to do, not what to avoid. Read them **before** drafting. Do not
150
+ read the tell lists before drafting: naming a phrase to avoid puts it in the
151
+ writer's head, and it comes out anyway. Check against the lists afterwards.
152
+
153
+ - **Lead with the fact.** A place, a time, a number, a name. "Gas Safe
154
+ registered since 2009. Same-day callouts in Brackley." beats "Trusted local
155
+ experts".
156
+ - **Concrete nouns beat abstractions.** "Rated 5.0 from 58 reviews" beats
157
+ "trusted by the community".
158
+ - **Name the mechanism.** Not "hassle-free booking", but "Book in three taps.
159
+ We confirm by text within the hour."
160
+ - **Persuade with something checkable.** Sales copy has to sell, and
161
+ enthusiasm is allowed. Persuasion backed by a fact passes; persuasion with no
162
+ fact behind it is the tell.
163
+ - **Concise, scannable, objective.** In usability testing, cutting word count,
164
+ making text scannable and dropping promotional tone each helped, and all
165
+ three together roughly doubled usability (Nielsen Norman Group).
166
+ - **Vary sentence length.** Some long, some short, some flat. Uniform rhythm
167
+ reads generated, and so does uniform wit.
168
+ - **Use contractions** where you would in speech. Formal legal pages are the
169
+ exception.
170
+ - **If the sentence would read the same with a competitor's name in it**, cut
171
+ it or make it specific.
172
+ - **Never reuse a sentence across two clients.** Templated language across
173
+ clients is a stronger tell than any phrase.
174
+ - **Never open with a thesis-statement negation.** "This is a working build,
175
+ not a mockup." Judge by function: if the sentence exists only for the
176
+ contrast, cut it.
177
+ - **Compliance hedging must not eat the sell.** Put trial design and caveats
178
+ where substantiation belongs (an evidence section, an ingredients panel, a
179
+ footnote). In the selling paragraph, name the claim and stop.
180
+ - **End each major section with one action sentence**: what to do next.
181
+ "Book a call to see if we're a fit."
182
+
183
+ ## Register: British and professional
184
+
185
+ The tool cannot grade register; a person does. British spelling and phrasing
186
+ (colour, organise, licence as a noun, centre, towards, enquiries). Professional
187
+ and confident, never matey: no over-familiar asides ("with me nowhere near
188
+ it"), no American idiom ("shallow wins", "looks busy"), no "honestly" as an
189
+ intensifier. Do not change a brand name, domain or legal entity wording.
190
+
191
+ ## Word limits
192
+
193
+ | Element | Limit |
194
+ | --- | --- |
195
+ | H1 | 10 words |
196
+ | Subhead | 24 words |
197
+ | Paragraph | 2 sentences |
198
+ | Bullet | 7 words |
199
+
200
+ Hyphenated compounds count as one word. Not automated.
201
+
202
+ ## Proof rule
203
+
204
+ - **No invented results, testimonials, logos, certifications or numbers.
205
+ Ever.**
206
+ - Every proof point comes from the project's own proof source (a proofs or
207
+ reviews file, the CMS, the client). If one is missing, write
208
+ `TODO_PLACEHOLDER: <the exact field needed>` and say so. Never fill it.
209
+ - A quotation under a named person must be theirs. Copy drafted for someone,
210
+ in quote marks with their job title beneath it, is a fabricated attribution.
211
+
212
+ ## CTA rule
213
+
214
+ - One primary CTA label site-wide on commercial pages, in the form **action +
215
+ outcome**: "Book a free strategy call".
216
+ - The secondary CTA is the same everywhere it appears.
217
+ - Labels and links come from the project's config, never retyped per page.
218
+
219
+ ## Never edit to pass: quoted words
220
+
221
+ **A real person's own words are exempt from every rule here.** Reviews,
222
+ testimonials, a practitioner's bio, a quote from the client. Editing them to
223
+ pass an audit falsifies a quotation, which is far worse than an em dash. Mark
224
+ the line `copy-ok` or add the file to `.copyauditignore`, and leave it as they
225
+ said it.
226
+
227
+ **Owner-editable copy is advised on, never overridden.** Where a client types
228
+ their own words into a CMS, report what the check finds and tell them. Copy
229
+ that ships from the repo is ours to fix; copy the owner typed is theirs.
230
+
231
+ ## Rendered social assets: no raw links
232
+
233
+ A carousel slide, Story, Reel or caption never shows a raw URL: no `https://`,
234
+ no `www.`, no path. Show a bare domain (`RMP-ELECTRICAL.CO.UK`) or a plain
235
+ call to action ("Link in bio"). Keep the full address in source data and let
236
+ the render step transform it. craft cannot enforce this, because raw URLs are
237
+ legitimate almost everywhere else in a repo. The social generator's own render
238
+ gate enforces it, before its first render ships.
239
+
240
+ ## Suppressing a finding
241
+
242
+ For genuine exceptions only, and the reason matters more than the mechanism:
243
+
244
+ - `copy-ok` (or `craft-ok`) on the line or the line above: a one-off. Listed
245
+ in every report, never hidden.
246
+ - `.copyauditignore` at the repo root: globs, matched on the repo-relative
247
+ path and the bare filename.
248
+ - `.nocopycheck` at the repo root: skips the repo.
249
+ - An `exceptions` entry in `art-direction.json`, with a `because` of at least
250
+ a sentence.
251
+
252
+ Reaching for these more than occasionally means a rule is miscalibrated. Fix
253
+ it here and in craft, not in forty repos.
254
+
255
+ ## Rewriting without losing facts
256
+
257
+ A rewrite that removes every tell and drops the price is worse than the
258
+ draft. Before handing over any rewrite, compare it with the original:
259
+
260
+ ```bash
261
+ craft copy compare original.md rewrite.md
262
+ ```
263
+
264
+ It lists every protected fact (prices, numbers, dates, times, phone numbers,
265
+ emails, links, postcodes, names) the rewrite **lost**, and every one it
266
+ **added**. Exit 1 if either list is not empty. Restore a lost fact or say why
267
+ it went; source an added fact or remove it, because an unsourced number is an
268
+ invented one.
269
+
270
+ ## Changing a rule
271
+
272
+ 1. Edit this file.
273
+ 2. Add or change the tell in `src/character/tells/`, with a flag fixture per
274
+ detection path and a pass fixture that is real persuasive copy. A tell that
275
+ fires on good sales copy does not ship.
276
+ 3. Place it in `src/character/house.ts`. New tells start as `review`. A tell
277
+ moves to `block` only after its hits across the estate have been read by a
278
+ person and the baseline is recorded in `ROADMAP.md`.
279
+ 4. `pnpm --filter @domandigital/craft test`. The binding test fails if a
280
+ phrase in this file's blocking tier does not block.
package/README.md CHANGED
@@ -94,6 +94,18 @@ reason, in `art-direction.json`:
94
94
  { "exceptions": [{ "tell": "ai-violet", "because": "Violet is on the van, the cards and the fascia." }] }
95
95
  ```
96
96
 
97
+ ### Copy
98
+
99
+ [COPY.md](./COPY.md) is the house copy standard: the blocking tier, the review
100
+ tier, the density tier that reads whole documents, and what to write instead.
101
+ `craft copy --gate` applies the house policy, so the blocking tier fails the
102
+ run. A test fails if a phrase the standard blocks does not block.
103
+
104
+ ```bash
105
+ npx craft copy --gate content # the house gate: exit 1 on the blocking tier
106
+ npx craft copy compare draft.md rewrite.md # facts a rewrite lost or added
107
+ ```
108
+
97
109
  The same checks run in code:
98
110
 
99
111
  ```ts
@@ -0,0 +1,20 @@
1
+ import {
2
+ audit,
3
+ collectInPage,
4
+ loadPlaywright,
5
+ overlayInPage,
6
+ snapshotPage,
7
+ snapshotUrl,
8
+ snapshotUrls
9
+ } from "./chunk-Q4MN3OXF.js";
10
+ import "./chunk-OGSYVCMU.js";
11
+ import "./chunk-CZHRNPDD.js";
12
+ export {
13
+ audit,
14
+ collectInPage,
15
+ loadPlaywright,
16
+ overlayInPage,
17
+ snapshotPage,
18
+ snapshotUrl,
19
+ snapshotUrls
20
+ };