copywriting · diff

git:20261013.c331b05 to git:20261018.e7ad4e7

12 added, 3 removed. Audit A to A.

---
name: copywriting
description: Use when writing, rewriting or editing any text a user will read — interface strings, buttons, errors, empty states, landing and pricing pages, app store listings, blog posts, changelogs, social posts and captions, short-video scripts and their hooks, ads, lifecycle email. Triggers - "write copy" / "напиши текст", "App Store description" / "описание для App Store", "store listing" / "описание в сторе", "rewrite this" / "перепиши", "headline" / "заголовок", "CTA" / "кнопка", "microcopy" / "микрокопия", "error text" / "текст ошибки", "this sounds like AI" / "звучит как нейросеть", "post for X" / "пост в твиттер", "reel script" / "сценарий рилса", "video hook" / "хук ролика", "build a landing page" / "сделай лендинг" (the copy for it; the visual layer is sheleg-design's). For defining the voice itself, see brand-voice.
compatibility: Any agent with file read/write. The closing lint (python3 docs/brand/lint.py, seeded by this pack) needs python3 3.9+, stdlib only - nothing to pip install.
license: MIT
---
# copywriting — write it in the product's own voice
> Part of **super-ux** — see [system-map.md](references/system-map.md).
> After changes, resolve and run the brand linter as described below.
**First action, every time: read the brand pack.** `docs/brand/voice.md`,
`terminology.md`, and the `channels.md` record for the surface being written.
**No pack? Stop and hand off to `brand-voice`.** Do not improvise a voice.
Guessing it and being wrong costs more than the pause, and a product whose
copy was invented surface by surface is exactly the drift this layer exists
to remove.
**The opt-out is spoken, never assumed:** **"no brand"** / **«без бренда»** —
or **"draft it"** / **«черновиком»** — is the operator declining this route:
write directly and say the pack was skipped on request, never skip it silently.
**Ownership is by FILE, not by directory.** `brand-voice` owns the pack's
sources of truth — `voice.md`, the terminology dictionary, `facts.md`, the
channel playbooks and the locale policy — and this skill **never writes those**:
a term missing from the dictionary, or a number with no row in `facts.md`, is
**reported**, never invented to finish the sentence — adding it is
`brand-voice`'s decision. This skill DOES own **`strings.md`** (the interface-
string registry, written with `Status: proposed`) and the product text itself.
Both live under `docs/brand/`, but the directory is not the owner — the file
is. Writing a `strings.md` row is this skill's job and needs no extra approval
for its LOCATION; a coordinated run claims the file before editing, and voice
and facts stay untouched in the same pass.
## References
| Read | When |
|---|---|
| [ui-copy.md](references/ui-copy.md) | any string inside the product |
| [marketing-copy.md](references/marketing-copy.md) | pages, long form, the seven sweeps, grounding |
| [landing-pages.md](references/landing-pages.md) | assembling a landing page: the offer, awareness, proof, the action |
| [channel-playbooks.md](references/channel-playbooks.md) | a social, blog, changelog, ads or email surface |
| [video-script.md](references/video-script.md) | a reel, short, TikTok or video ad: angle, structure, timing, shot list, gates |
| [hooks.md](references/hooks.md) | the first three seconds: the triple, 26 formulas, 40 templates, the `B044` filter |
| [research-outliers.md](references/research-outliers.md) | before a video: which videos beat their own account |
| [store-copy.md](references/store-copy.md) | App Store or Google Play |
| [seo-aeo-safety.md](references/seo-aeo-safety.md) | anything a crawler or answer engine reads |
| [ai-tells.md](references/ai-tells.md) | every mode that produces text: the pass runs by default |
| [localization.md](references/localization.md) | any locale that is not primary |
| [surface-registers.md](references/surface-registers.md) | the register for a surface |
## Modes
**The humanization pass runs by default, in every mode that produces text.**
It is not a mode you enter, it is the last step of Write, Edit and Adapt, under
the guards in [ai-tells.md](references/ai-tells.md) which are not optional. The
reason it is a default rather than an option: a draft nobody swept carries the
markers that file grades, and a reader registers them before they can name why.
`voice.md` records the state in two fields that answer different questions.
**`Humanization: on | off`** is whether the pass runs, and it defaults to `on`
when the field is absent. **`Humanization pass:`** names which implementation
runs, and absent it is `own`, the only one that reads this pack's registers and
canonical facts. Turning it off is a legitimate decision that outlives whoever
made it, so `off` carries a `Humanization declined:` line with the reason and
the date. `B064` checks all three states.
**Every delivery of copy states what happened**, in one line, before the copy
or immediately after it:
```
- Humanization: on — own pass, 7 markers at 2 densities, 4 addressed, 11% changed
- Humanization: off — declined 2026-08-30, wording fixed by counsel
+ Humanization: on (own pass): 7 markers at 2 densities, 4 addressed, 11% changed
+ Humanization: off (declined 2026-08-30): wording fixed by counsel
```
The line is not decoration. A pass that runs invisibly is indistinguishable
from one that did not run, and the reader of the copy is usually not the person
who chose the setting.
### Write
1. Name the surface. It must exist in `channels.md`; if it does not, that is
a `brand-voice` decision, not an improvisation here.
For marketing work only, read `marketing-copy.md` → Before writing and
seed/update the current brief from `templates/brand/copy-brief.md`. Keep
unresolved inputs `unknown`; no extra brief load for interface-only work.
2. Apply the register: the axes from `voice.md`, plus that surface's deltas.
**Deltas move axes; they never cross the invariants.**
3. Write. Every claim traces to `facts.md`. Every product term comes from
`terminology.md`.
4. Deliver the copy first, then the reasoning. For headlines and CTAs give
two or three options with what each trades away.
5. For interface strings, add or update the `strings.md` row — key,
`file:line`, scenario, `Status: proposed`.
6. **Run the humanization pass** under the Humanize guards below, then print
the status line. On new text the change rate is measured against the draft
you just wrote, so the 50% ceiling does not apply the way it does to an
edit; report the rate regardless.
### Edit
The seven sweeps from `marketing-copy.md`, in order, looping back after each:
clarity, voice and tone, so-what, prove-it, specificity, emotion, zero risk.
Deliver sweep by sweep, prioritised by impact, not by reading order.
**The humanization pass runs last**, after the seven, and then the status line.
Last because the sweeps rewrite whole sentences and a humanization pass run
before them is measured against text that no longer exists; and because the
semantic-preservation checklist is cheapest to apply to a version nobody is
about to rewrite again.
### Adapt
One piece across several surfaces. Re-write per surface — never paste one
register into another. What survives adaptation is the claim and the proof;
what changes is length, structure, CTA policy and register.
State plainly what each version drops. A thread is not a page with line
breaks.
**The pass runs per surface, not once on the source.** Each version is
different text in a different register, and a marker density that is fine in a
blog post is not fine in a landing hero. One status line per surface.
### Humanize
The standalone mode, for auditing text that already exists without writing any:
an inherited page, a competitor's copy, a draft somebody else wrote. The same
guards govern the pass wherever it runs, and they are not optional:
- Above ~10 markers per 500 words, say so and rewrite from the argument — a
patch produces the same patterns with better words.
- **Above a 50% change rate, do not ship.** Report the rate and ask. That is
no longer an edit.
- Run the semantic-preservation checklist before output: numbers, dates and
proper nouns intact; causal direction unchanged; no negation inverted;
quotations untouched; the core claim the same.
- Text that already reads naturally is left alone. Editing what is fine to
prove the pass ran is this mode's failure.
- **A marker count is not a verdict and never gates anything.** Say which markers
are present at what density; never say a text was AI-written. A number of
markers does not prove authorship — the false positives fall hardest on people
writing in a second language, and a writer is not a defect to be edited into
fluency they did not ask for. The humanization pass is ADVISORY throughout:
`B060` warns, it does not error. `ai-tells.md` carries the measurement and
- what it binds.
+ what it binds. **The one exception is the em dash** (AT-06): it is a hard
+ ban on text you write, never a count, and it is listed under Non-negotiables.
- **A quote, a registered term, and an explicit `off` never force a rewrite.** A
marker inside a quotation is the source's word; a term in `terminology.md` is
meant to recur; and `Humanization: off` (with its reason) preserves the text
exactly. **Brand bans are a separate user policy** — a forbidden word is the
brand's own choice in `terminology.md`, not a machine-drafting tell, and it is
that check's business, not this pass's.
- **Read `voice.md`'s two fields first.** `Humanization:` is whether the pass
runs and defaults to `on`; `Humanization pass:` names which implementation,
and absent it is `own`. Neither absence stops work: run the default, print the
status line saying it was a default, and offer to record it once. A value naming
a tool that is not installed falls back to `own` and says so — a missing optional
tool must not stop copy being written.
- Other implementations exist and two are worth knowing —
`npx sshlg-skills humanizers` lists what this machine has. Reach for one for an
audit with no rewrite, for long-form prose, or when the writer has a sample of
their own writing to match. Stay here for product copy: the brand pack's
registers and canonical facts are the constraint, and a general-purpose
humanizer does not read them.
## Non-negotiables
+ - **No em dash (U+2014) in text you write, in any language** (owner decision,
+ 2026-10-18). Not a frequency target: one is an error to fix before delivery,
+ and no substitute stands in for it (`--`, an en dash or a spaced hyphen doing
+ a dash's job). Use a full stop, a comma, a colon or brackets, or rewrite the
+ sentence. The rare exception is a dash Russian grammar needs where no rewrite
+ reads naturally. Quotations, code, commands, paths and historical records keep
+ theirs. `B062` and `B067` check what a machine can prove; the rest is
+ [ai-tells.md](references/ai-tells.md) AT-06.
- **No decorative full stop in headings or labels**, including nested HTML,
formatted Markdown and staccato fragments such as “Your agents. Your tools.”
Read the rendered role, not merely a flat copy export. Keep real questions,
ellipses, abbreviations, versions, URLs and normal paragraph punctuation.
A project may extend this rule to hero summaries and captions; record that
scope in its brand policy and test the generated page too.
When restoring historical copy, reapply the current project punctuation policy;
an old exact-text snapshot is not an intentional exception.
- **No fabricated facts, statistics, quotes or experts.** Not for a deadline,
not for a benchmark, not because a placeholder would look better. Refuse,
say why, offer to find a real one.
- **No humor on `error`, `destructive confirm`, `billing and receipts` or
`paywall and upgrade`** — in any pack. The user is losing something there.
- **One action, one name.** Before naming an action, search `strings.md` for
it. A second name for an existing action is a defect, not a synonym.
- **Never quote a number that is not in `facts.md`.**
## Definition of done
- Every string or section traces to a surface record and a voice.
- The humanization status line is printed, whichever state it reports.
- New interface strings are in `strings.md` with location and scenario.
- Resolve the linter before running it: use `docs/brand/lint.py` if present;
otherwise locate the installed super-ux linter `brand_lint.py` in its scripts directory and pass the
project's `docs/brand` path. Do not invent a project-local script or assume
the shell exports a plugin-root variable. If neither exists, name the missing
tool and the manual coverage; never call it a lint pass.
- Confirm `Sources:` resolves to the touched surfaces. HTML heading roles must
survive a copy projection; flat paragraphs do not prove heading coverage.
Run the resolved script with `--fail-on B063` when title punctuation is a
required gate (or `--strict` for all warnings). An older script that rejects
the flag needs an update, not a retry that quietly drops the gate.
- Report scanned surfaces, remaining warnings and the exact command/exit code.
Exit zero with advisory warnings is not a clean review. Inspect the rendered
copy and generators; a source-only check cannot see external CSS or runtime text.
- Anything reported as missing — a term, a fact, a surface — is named
explicitly, not worked around.
**What else is in `references/`.** This skill names 13 contract(s) directly; `references/` holds 15, because a contract this skill names links others and the whole closure ships inside the skill — the skills CLI installs one directory and a sibling's file would arrive dangling. Open the ones named here; the rest are reached from them, by name, when a contract sends you.