--- name: unslop description: Strip AI writing tells from Rove's user-facing prose — README, the docs/ pages that sync to docs.rove.run, landing copy, and release notes. Use when the user says "unslop", "洗一下文案", "这读起来像 AI 写的", or after drafting any user-facing page. Adapted for this repo from cursor/plugins pstack/skills/unslop. metadata: internal: true --- # Unslop Rewrite text so it stops reading as machine-generated. Two halves, both required: cut the tells, then make sure something human is left. Upstream pattern list: . This file keeps the repo-specific parts — what counts as prose here, what is data and must survive, and which surfaces are in scope. ## Scope in this repo In scope (the product's public face): - `README.md` - The `docs/` pages listed in `SECTIONS` in `packages/rove-docs/scripts/sync-docs.mjs` (they publish to docs.rove.run) - `packages/rove-landing/**` — English copy only - Release notes and changeset bodies Out of scope unless asked: `docs/PLAN.md`, `HARNESS.md`, `ARCHITECTURE.md`, `DESIGN.md`, `RELEASING.md`, `AGENTS.md`, and `docs/design/**`. Those are operator manuals for agents, not product surface. ## Never rewrite these The same character can be punctuation in one line and data in the next. Check before replacing: - **Table cells meaning "not applicable"** — `| copilot | — | ... |` in ENGINES.md and SESSIONS.md. An em dash there is the value. - **Verbatim CLI output** — e.g. the `rove doctor` sample in TROUBLESHOOTING.md. Quoting output means quoting it exactly. - **Code, including placeholders** — `${ev.taskId ?? "—"}` in PLUGIN-SDK.md. - **CSS/JS/HTML comments** in landing files. - **Chinese copy.** `——` is correct CJK punctuation, not a tell. The landing page's `zh` i18n dictionary keeps its em dashes. - **A file's own established separator.** PLUGIN-EVENTS.md uses `·` between a heading and its engine-support marks; match the file rather than inventing a new convention. ## The patterns Full list upstream. The ones that actually show up here, in order of volume: 1. **Em dashes in prose.** By far the biggest. Resolve each one *per sentence* into a period, comma, semicolon, colon, or parens. Do not global-replace with a single substitute — swapping every `—` for `,` just trades one tic for another, and swapping for parens trades it for a different one. 2. **Inline-header lists.** `- **Label** — restatement of the label` becomes `- **Label.** `. If the text after the label only rephrases it, the line has no content; write the content. 3. **Curly quotes** → straight. 4. **Filler.** "simply", "in order to", "it is important to note that". 5. **Fancy ways to say "is"** — "serves as", "stands as", "boasts". 6. **Title-case headings** → sentence case. Proper nouns and defined product terms (Terminal Tab, Claude Code) stay capitalized. 7. **Sentences that say nothing specific to this project.** If the line could appear verbatim in another tool's docs, cut it. Then add voice back: vary sentence length, state the tradeoff instead of listing both sides neutrally, and let a sentence be blunt. ## Two failure modes this repo has actually hit Both produced broken output that looked fine at a glance. Check for them. **Fragments from over-eager splitting.** Turning `X — y` into `X. y` mid-clause leaves a sentence starting lowercase, or a verbless remainder. After editing, scan for it: ```bash python3 - <<'PY' import sys for p in sys.argv[1:]: lines = open(p, encoding='utf-8').read().split('\n') for i in range(len(lines) - 1): a, b = lines[i], lines[i + 1] if a.rstrip().endswith('.') and b[:1].islower() and b.strip() \ and not b.startswith(('|', '-', '*', '`', '#', ' ')): print(f"{p}:{i+1}: {a[-50:]} || {b[:50]}") PY ``` Also read each rewritten sentence whole. The scan only catches line-broken prose, not a fragment that fits on one line. **Escaping breaks in i18n strings.** The landing dictionaries are single-quoted JS. Replacing `agent’s` with a literal `'` breaks the file. Always: ```bash node --check packages/rove-landing/index.js node --check packages/rove-landing/themes.js ``` ## Verify before claiming done ```bash bun run lint bun run --filter @sma1lboy/rove-docs build # the docs-site CI gate node --check packages/rove-landing/*.js ``` Then confirm the em dashes that remain are all data, not prose: ```bash grep -rn "—" README.md docs/*.md packages/rove-landing/*.html ``` Every hit should be a table cell, CLI output, a code placeholder, a comment, or Chinese. If one is a sentence, it was missed. Editing the landing page's English also means checking its Chinese counterpart still has the same keys: ```bash node -e ' const s = require("fs").readFileSync("packages/rove-landing/index.js", "utf8") const keys = (b) => new Set([...b.matchAll(/'"'"'([a-zA-Z0-9._]+)'"'"':/g)].map(m => m[1])) const zh = keys(s.split("var en =")[0].split("var zh =")[1]) const en = keys(s.split("var en =")[1].split("var dicts")[0]) console.log("zh only:", [...zh].filter(k => !en.has(k))) console.log("en only:", [...en].filter(k => !zh.has(k))) ' ```