
Unmachined
- 5 repo stars
- Updated August 2, 2026
- 0xnyk/unmachined
unmachined is an agent skill that removes AI-generated patterns from text and UI: deterministic scanners score drafts 0-100 against catalogs of known tells and fail them at 40 before any model rewrite.
About
unmachined strips AI slop from text and interfaces using two deterministic scanners that score a draft 0 to 100 against severity-tiered catalogs of known tells and fail it at 40, before any model judges the output. Beyond banned-word lists, it enforces cross-output variety so two runs never share a voice or layout skeleton, plus an honesty gate that rewards concrete facts over vague corporate language. It covers prose (articles, READMEs, marketing copy, microcopy) and web UI, with deep rules for Next.js, Tailwind, and shadcn and non-negotiable WCAG 2.2 AA gates. The scanners are zero-dependency Python 3.8+ scripts with CI-friendly exit codes; MIT-licensed, working in Claude Code, Codex, and any Agent Skills host.
- Two deterministic scanners (text + UI) score 0-100 on known AI tells and fail drafts at 40, before any model judgment
- Severity-tiered catalog: critical tells cost 20 points, major 10, minor 3
- Variety requirement: consecutive outputs must not share a voice, layout skeleton, display font, or accent hue
- Zero dependencies, Python 3.8+, no network; exit code 1 wires straight into CI or pre-commit
- Honesty gate rewards concrete facts: no invented metrics, quotes, or anecdotes
Unmachined by the numbers
- Data as of Aug 3, 2026 (Skillselion catalog sync)
unmachined capabilities & compatibility
Free, MIT-licensed; scanners run locally with no API keys or network access.
- Use cases
- copywriting · ui design · documentation
- Runs
- Runs locally
- Pricing
- Free
npx skills add https://github.com/0xnyk/unmachined --skill unmachinedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| repo stars | ★ 5 |
|---|---|
| Last updated | August 2, 2026 |
| Repository | 0xnyk/unmachined ↗ |
Why does everything my AI writes sound the same, and how do I stop my site and copy from looking AI-generated?
Strip AI tells from prose and web UI before shipping: deterministic scanners score drafts 0-100 and block at 40, then a rewrite pass fixes what they flag.
Who is it for?
Builders shipping AI-assisted articles, landing pages, and UI who need a reproducible gate that catches machine tells before publishing.
Skip if: Encyclopedic or legal text, enforced house styles, judging whether third-party work is AI-generated, or as a substitute for having something concrete to say.
When should I use this skill?
Writing or editing prose that must not read AI-generated, building or auditing web UI that must not look AI-generated, or when the user says 'unmachined', 'de-slop', 'AI slop', or 'sounds like AI'.
What you get
Drafts and UI pass a deterministic slop scan under 40, read written rather than generated, and consecutive outputs stop sharing a voice or layout skeleton.
- Severity-ranked audit report with slop score (0-100)
- Repaired text or UI preserving meaning, routes, and IA
By the numbers
- Scores drafts 0-100; 40 or above fails
- Severity weights: critical 20, major 10, minor 3
- Zero dependencies, Python 3.8+, no network access
Files
unmachined
Make it read written and look made, not generated.
AI output converges on the statistical center: the same vocabulary, the same cadence, the same purple gradient on the same centered hero. This skill holds two lines of defense. First, catalogs of known tells with severity tiers. Second, a variety requirement, because the fix becomes a new tell when every output converges on the same alternative. Never swap one monoculture for another. Two outputs from this skill should not share a voice or a layout skeleton unless the brief demands it.
Priorities when effort must be split: text first, design and layout equal second, branding last.
Verbs
| Invocation | Behavior |
|---|---|
/unmachined <task> (default) | Build or write with all applicable rules active from the start |
/unmachined audit <target> | Score and report findings by severity. Never edit |
/unmachined fix <target> | Audit, repair, verify, and summarize the changes |
/unmachined text <target> | Run only the prose and copy pipeline |
/unmachined ui <target> | Run only the interface and frontend pipeline |
/unmachined diff [base] | Audit only changed lines and their necessary context; never widen into unrelated cleanup |
<target> may be a file, directory, URL, or pasted content. Detect the surface: prose gets the text pipeline, UI code gets the design pipeline, landing pages get both.
Hosts that omit the leading slash use the same words as a natural-language invocation. Separate command files for these verbs are unnecessary. The skill is the command, and the first argument selects the workflow.
Dispatch contract
1. Parse an explicit verb first. If none is present, use the default build workflow. A file named audit or fix remains a target when supplied as a path. 2. Resolve the smallest target that satisfies the request. For diff, use the working-tree diff by default; when <base> is given, compare against it. 3. State the detected surfaces in one short line when both text and UI rules apply. Skip this ceremony for a single obvious file. 4. audit and diff are read-only. A request to apply their findings changes the workflow to fix only after the user asks. 5. Load only the references named by the chosen pipeline. Leave unrelated catalogs unloaded.
Text pipeline (main priority)
1. Read references/text-tells.md for the full catalog. Microcopy and brand voice also need references/voice-and-copy.md. 2. Draft or edit. 3. Run the deterministic scanner. It is reproducible and cheap; run it before any self-judgment: python3 scripts/scan_text.py <file> [--json] [--threshold 40] [--mode prose|chat|ui] Use stdin for pasted content. Score >= 40 blocks a fix or build delivery. In audit, report the score without changing the target. Fix criticals first. 4. Self-critique loop: ask "what makes this still read as AI-generated?", answer it concretely, revise once. Vary sentence rhythm, keep one opinion, allow one rough edge. Do not sand every sentence to the same length. 5. Honesty gate: every number is real or a labelled placeholder. No invented metrics, quotes, or anecdotes. Superiority claims need a cited measurement.
Design pipeline
1. Read references/design-tells.md (what to avoid) and references/design-system.md (what to build instead). Audits and redesigns also need references/audit-playbook.md. On existing code, run the deterministic UI scanner first; it catches the grep-able tells before any visual judgment: python3 scripts/scan_ui.py <src-dir> [--json] [--threshold 40] 2. For Next.js/Tailwind/shadcn projects, read references/stack-rules.md and confirm actual versions from package.json before applying them. If the project pins older versions, follow the project and flag it. 3. Before emitting UI: pick structure first (layout skeleton before colors), set locked tokens (OKLCH custom properties, named fonts, spacing scale), and spend boldness in one place: one real aesthetic risk you can justify. 4. Pre-emit self-critique, scored 1-5 on six axes: philosophy, hierarchy, execution, specificity, restraint, variety. Any axis under 3 triggers a revision pass before showing output. 5. Hard gates, non-negotiable: WCAG 2.2 AA contrast (4.5:1 text, 3:1 large text and UI), prefers-reduced-motion support, keyboard focus visible, renders correctly at 320/375/414/768 px.
When the host provides image, browser, or screenshot tools, use them to inspect rendered UI after the source scan. A grep result is evidence of a candidate, not proof that the rendered design fails.
Cross-output variety
Track your own recent outputs within a project. Consecutive outputs must not share the same layout skeleton, display font, or accent hue unless the brief or an existing design system requires it. When a project has a design system, consistency with it beats variety.
Resolved rule conflicts
Sources in the research corpus disagree on a few points. These resolutions are final for this skill:
- Em-dash: banned in all generated text. Use a single hyphen, a comma, or
restructure. Sole exception: a labelled placeholder inside a stat block.
- Serif type: allowed as a deliberate, justified choice; banned as an
unconsidered default. Fraunces and Instrument Serif are trending toward cliche; require justification.
- Metrics in copy: real, cited, or a labelled placeholder. Never invented,
even as decoration.
- Eyebrows and section numbers: off by default; only when they encode a true
sequence, and never on every section.
Reference index
| File | Load when |
|---|---|
references/text-tells.md | Any text work |
references/voice-and-copy.md | Microcopy, UX writing, brand voice |
references/design-tells.md | Any UI work |
references/design-system.md | Building or restyling UI |
references/audit-playbook.md | audit or fix on existing UI |
references/stack-rules.md | Next.js, Tailwind, or shadcn detected |
scripts/scan_text.py | Every text deliverable, before shipping |
scripts/scan_ui.py | Auditing or fixing existing UI source |
Output discipline
This skill's own reports must pass its own rules. Findings are concrete: quote the offending line, name the rule, show the fix. No praise padding, no "overall this looks great". If nothing is wrong, say so in one sentence.
For audits, return: score, blocking status, findings ordered critical to minor, and the next smallest useful action. For fixes, return: what changed, scanner scores before and after, visual checks performed when relevant, and anything not verified. Never claim a render, accessibility check, or scanner pass that did not run.
* @0xNyk
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
open-pull-requests-limit: 5
labels:
- dependencies
name: Bug report
description: Report reproducible incorrect behavior in the skill or scanners.
title: "bug: "
labels: [bug]
body:
- type: markdown
attributes:
value: Do not include secrets, private drafts, or vulnerability details. Use SECURITY.md for security reports.
- type: textarea
id: behavior
attributes:
label: What happened?
description: Include the exact command, smallest safe input, actual output, and expected output.
validations:
required: true
- type: input
id: version
attributes:
label: Version or commit
placeholder: v0.2.0 or commit SHA
validations:
required: true
- type: input
id: environment
attributes:
label: Environment
placeholder: Host, host version, OS, and Python version
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
blank_issues_enabled: false
contact_links:
- name: Security report
url: https://github.com/0xNyk/unmachined/security/policy
about: Report vulnerabilities privately according to the security policy.
name: Propose or remove a tell
description: Add evidence for a new rule or a false positive in an existing one.
title: "tell: "
body:
- type: dropdown
id: action
attributes:
label: Proposed action
options:
- Add a tell
- Change severity
- Remove or narrow a tell
validations:
required: true
- type: textarea
id: evidence
attributes:
label: Evidence
description: Provide safe examples, links, or generation counts. Explain any known counterexamples.
validations:
required: true
- type: dropdown
id: surface
attributes:
label: Surface
options:
- Prose
- Chat
- UI copy
- Interface design
- Frontend source
validations:
required: true
- type: textarea
id: alternative
attributes:
label: Better alternative
description: Give a range of repairs, not one mandatory replacement.
validations:
required: true
Change
<!-- What changed and why? Keep this tied to the diff. -->
Evidence
<!-- For a new tell or severity change, include examples, counts, or sources. -->
Verification
- [ ]
python3 scripts/validate_skill.py - [ ]
python3 scripts/scan_text.py README.md - [ ]
python3 scripts/scan_text.py SKILL.md - [ ] Relevant scanner behavior was exercised
- [ ]
git diff --check
Commands and results:
Scope and risk
<!-- Note compatibility changes, false-positive risk, skipped checks, and anything intentionally left out. -->
name: ci
permissions:
contents: read
on:
push:
branches: [main]
pull_request:
jobs:
# The skill has to survive its own scanner. If a doc that ships in this repo
# reads as slop, the build fails. That is the whole point.
#
# references/*.md are deliberately excluded: they are catalogs OF slop and
# score 100 by design.
self-scan:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python: ["3.8", "3.11", "3.13"]
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python }}
- name: Validate the Agent Skills package
run: |
python3 -m py_compile scripts/*.py
python3 scripts/validate_skill.py
- name: Every shipped document passes the scanner
run: |
for f in README.md SKILL.md AGENTS.md ATTRIBUTION.md CHANGELOG.md \
CODE_OF_CONDUCT.md CONTRIBUTING.md GOVERNANCE.md SECURITY.md SUPPORT.md \
docs/maintainer-guide.md docs/brand.md; do
echo "--- $f"
python3 scripts/scan_text.py "$f"
done
behavior:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.11"
- name: Known slop is rejected
run: |
if echo "In today's evolving landscape, our cutting-edge platform empowers teams to unlock actionable insights. At the end of the day, it's a game-changer." \
| python3 scripts/scan_text.py -; then
echo "regression: slop passed the scanner"; exit 1
fi
- name: Clean prose is accepted
run: |
echo "The dashboard shows which of your accounts churned last week, and why. Most teams find two or three they had written off as healthy." \
| python3 scripts/scan_text.py -
- name: Markdown links do not trip the bracket-scaffold rule
run: |
echo "See [ATTRIBUTION.md](ATTRIBUTION.md) for the source list." \
| python3 scripts/scan_text.py -
- name: A real bracket scaffold is still caught
run: |
if printf 'Intro line.\n\n[Hook - sharp claim goes here]\n' \
| python3 scripts/scan_text.py - --threshold 20; then
echo "regression: scaffold not caught"; exit 1
fi
- name: A real TODO marker is caught
run: |
if printf 'Ship it.\n\nTODO: fix the thing\n' \
| python3 scripts/scan_text.py - --threshold 20; then
echo "regression: TODO marker not caught"; exit 1
fi
- name: Lowercase "todo" as an ordinary noun is not a template artifact
run: |
echo "Kanban columns: inbox, backlog, todo, in-progress, review, done." \
| python3 scripts/scan_text.py -
# cadence analysis needs 4+ sentences before it will judge repetition
- name: Repetitive sentence openers are caught
run: |
if printf 'However the cat sat. However the dog ran. However the bird flew. The rain fell.\n' \
| python3 scripts/scan_text.py - --threshold 10; then
echo "regression: repetition not caught"; exit 1
fi
- name: Markdown lists and HTML tags are not sentence openers
run: |
printf '<details>\n<summary>One</summary>\n<details>\n<summary>Two</summary>\n<details>\n\n1. **Round** one\n2. **Round** two\n3. **Round** three\n' \
| python3 scripts/scan_text.py -
- name: JSON output is valid
run: |
echo "test" | python3 scripts/scan_text.py - --json | python3 -m json.tool > /dev/null
- name: UI scanner reports the purple gradient
run: |
mkdir -p /tmp/ui
printf 'export default function Hero() {\n return <div className="bg-gradient-to-r from-purple-500 to-indigo-600 min-h-screen" />\n}\n' > /tmp/ui/hero.tsx
python3 scripts/scan_ui.py /tmp/ui | grep -q "AI purple gradient"
__pycache__/
*.pyc
.DS_Store
Agent operating rules
These rules apply to every coding agent working in this repository.
Scope
- Read
SKILL.mdbefore changing skill behavior. - Preserve user changes and unrelated working-tree edits.
- Keep the root
SKILL.mdfocused on routing. Put detailed rules in
references/ and deterministic checks in scripts/.
- Maintain Python 3.8 compatibility and zero runtime dependencies.
Verification
Before presenting a change as complete:
python3 scripts/validate_skill.py
python3 scripts/scan_text.py README.md
python3 scripts/scan_text.py SKILL.md
git diff --checkRun python3 scripts/scan_ui.py <path> when changing UI examples or frontend rules. Never claim a check, render, or test passed unless it ran successfully.
Git contract
- Inspection commands such as
git status,git diff, andgit logare
always allowed.
- Work on a topic branch named
agent/<short-task>unless the user explicitly
requests another branch or the host provides an isolated worktree.
- Stage only files or hunks belonging to the current task. Use
git add -p
when a touched file also contains unrelated edits.
- Review
git diff --cachedand rungit diff --cached --checkbefore every
commit.
- Commits must be cohesive, verified, and described by a short imperative
message. Do not use git commit -a or stage the whole repository by default.
- Commit only when the user asks, or when the task explicitly includes creating
a commit. Pushing, opening a pull request, merging, tagging, releasing, and posting external comments always require explicit user authorization.
- Push only the current topic branch. Never push directly to
mainunless the
user explicitly requests it and repository policy permits it.
- Never amend, rebase, reset, delete branches, or force-push without a specific
request. If a force-push is unavoidable, explain why and use --force-with-lease; plain --force is prohibited.
- After committing or pushing, report the branch, commit SHA, checks run, and
any remaining local changes.
Pull requests
- Prefer a pull request for changes to
SKILL.md, scanners, CI, security or
governance files, and release metadata.
- Base the PR summary on the actual diff. List commands that ran and disclose
checks that could not run.
- Do not approve or merge the agent's own pull request.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" role="img" aria-labelledby="title desc">
<title id="title">unmachined mark</title>
<desc id="desc">Letterpress lowercase u with a red proofreader prohibition over a struck rule.</desc>
<rect width="256" height="256" fill="#f3efe6"/>
<!-- registration marks -->
<g stroke="#1c1a17" stroke-width="1.2" opacity="0.45" fill="none">
<path d="M18 28h20M28 18v20"/>
<path d="M218 28h20M228 18v20"/>
<path d="M18 228h20M28 218v20"/>
<path d="M218 228h20M228 218v20"/>
</g>
<!-- black rule bar -->
<rect x="118" y="118" width="92" height="18" rx="2" fill="#1c1a17"/>
<!-- letterpress u -->
<path fill="#1c1a17" d="M62 78c0-4 2-6 6-6h22c3 0 5 2 5 5v62c0 18 8 28 24 28s24-10 24-28V77c0-3 2-5 5-5h22c4 0 6 2 6 6v66c0 40-22 62-57 62s-57-22-57-62V78z"/>
<!-- red proof circle with slash -->
<g transform="translate(148 128)" fill="none" stroke="#c41e1e" stroke-width="10" stroke-linecap="round">
<circle r="40"/>
<path d="M-26 26 L26 -26"/>
</g>
</svg>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>unmachined infographics</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap');
:root {
--paper: #f3efe6;
--paper-deep: #e8e2d4;
--ink: #1c1a17;
--ink-soft: #4a453c;
--rule: #2a4a7a;
--mark: #c41e1e;
--ok: #1f5c3a;
--fail: #c41e1e;
--hair: #c9c2b0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #888;
font-family: "IBM Plex Sans", system-ui, sans-serif;
color: var(--ink);
padding: 40px;
}
.sheet {
width: 1200px;
background: var(--paper);
position: relative;
margin: 0 auto 48px;
overflow: hidden;
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
/* paper grain */
.sheet::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.06;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.reg {
position: absolute;
width: 18px;
height: 18px;
border: none;
opacity: 0.55;
}
.reg::before, .reg::after {
content: "";
position: absolute;
background: var(--ink);
}
.reg::before { left: 8px; top: 0; width: 1.5px; height: 18px; }
.reg::after { top: 8px; left: 0; height: 1.5px; width: 18px; }
.reg.tl { top: 16px; left: 16px; }
.reg.tr { top: 16px; right: 16px; }
.reg.bl { bottom: 16px; left: 16px; }
.reg.br { bottom: 16px; right: 16px; }
.pad {
padding: 56px 64px 52px;
position: relative;
z-index: 1;
}
.eyebrow {
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--mark);
margin-bottom: 14px;
font-weight: 600;
}
h1 {
font-size: 42px;
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1.1;
margin-bottom: 10px;
}
.lede {
font-size: 18px;
line-height: 1.45;
color: var(--ink-soft);
max-width: 52ch;
margin-bottom: 36px;
}
/* ——— how it works ——— */
#how { height: 720px; }
.defense {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.card {
border: 1.5px solid var(--ink);
background: rgba(255,255,255,0.35);
padding: 28px 26px 26px;
position: relative;
min-height: 280px;
}
.card .num {
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
color: var(--rule);
letter-spacing: 0.08em;
margin-bottom: 12px;
}
.card h2 {
font-size: 24px;
letter-spacing: -0.02em;
margin-bottom: 12px;
line-height: 1.15;
}
.card p {
font-size: 16px;
line-height: 1.5;
color: var(--ink-soft);
}
.card ul {
margin-top: 16px;
list-style: none;
}
.card li {
font-size: 15px;
line-height: 1.45;
padding: 6px 0 6px 18px;
position: relative;
border-top: 1px solid var(--hair);
}
.card li::before {
content: "";
position: absolute;
left: 0;
top: 14px;
width: 8px;
height: 2px;
background: var(--mark);
}
.card .stamp {
position: absolute;
top: 18px;
right: 18px;
width: 54px;
height: 54px;
border: 2.5px solid var(--mark);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: "IBM Plex Mono", monospace;
font-size: 11px;
font-weight: 600;
color: var(--mark);
transform: rotate(12deg);
opacity: 0.85;
}
.footnote {
margin-top: 28px;
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
color: var(--ink-soft);
border-top: 1px solid var(--hair);
padding-top: 16px;
}
/* ——— verbs ——— */
#verbs { height: 780px; }
.verbs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.verb {
display: grid;
grid-template-columns: 220px 1fr;
border: 1.5px solid var(--ink);
background: rgba(255,255,255,0.28);
min-height: 88px;
}
.verb code {
font-family: "IBM Plex Mono", monospace;
font-size: 14px;
font-weight: 600;
background: var(--ink);
color: var(--paper);
padding: 16px 18px;
display: flex;
align-items: center;
line-height: 1.3;
}
.verb .desc {
padding: 16px 18px;
font-size: 15px;
line-height: 1.4;
color: var(--ink-soft);
display: flex;
align-items: center;
}
.verb.full {
grid-column: 1 / -1;
grid-template-columns: 220px 1fr;
}
.priority {
margin-top: 28px;
display: flex;
gap: 0;
border: 1.5px solid var(--ink);
overflow: hidden;
}
.priority div {
flex: 1;
padding: 14px 16px;
font-size: 14px;
border-right: 1px solid var(--ink);
}
.priority div:last-child { border-right: none; }
.priority strong {
display: block;
font-family: "IBM Plex Mono", monospace;
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mark);
margin-bottom: 4px;
}
/* ——— scanner ——— */
#scanner { height: 720px; }
.flow {
display: grid;
grid-template-columns: 1fr 56px 1fr 56px 1fr;
align-items: stretch;
gap: 0;
}
.panel {
border: 1.5px solid var(--ink);
background: rgba(255,255,255,0.32);
padding: 20px;
min-height: 340px;
display: flex;
flex-direction: column;
}
.panel .label {
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 14px;
font-weight: 600;
}
.panel.fail .label { color: var(--fail); }
.panel.pass .label { color: var(--ok); }
.panel.rewrite .label { color: var(--rule); }
.panel pre {
font-family: "IBM Plex Mono", monospace;
font-size: 12.5px;
line-height: 1.45;
white-space: pre-wrap;
flex: 1;
color: var(--ink);
}
.badge {
margin-top: 14px;
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
font-weight: 600;
padding: 8px 10px;
border: 1.5px solid currentColor;
text-align: center;
letter-spacing: 0.04em;
}
.panel.fail .badge { color: var(--fail); }
.panel.pass .badge { color: var(--ok); }
.panel.rewrite .badge { color: var(--rule); }
.arrow {
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: var(--ink);
font-weight: 300;
}
.scan-note {
margin-top: 28px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.scan-note .bit {
border-top: 2px solid var(--ink);
padding-top: 12px;
}
.scan-note .bit strong {
display: block;
font-size: 15px;
margin-bottom: 4px;
}
.scan-note .bit span {
font-size: 14px;
color: var(--ink-soft);
line-height: 1.4;
}
/* ——— architecture ——— */
#arch { height: 700px; }
.layers {
display: flex;
flex-direction: column;
gap: 12px;
}
.layer {
border: 1.5px solid var(--ink);
display: grid;
grid-template-columns: 160px 1fr;
min-height: 78px;
background: rgba(255,255,255,0.28);
}
.layer .slot {
background: var(--ink);
color: var(--paper);
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.06em;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 12px;
}
.layer .body {
padding: 16px 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.layer .body strong {
font-size: 17px;
letter-spacing: -0.01em;
}
.layer .body span {
font-size: 14px;
color: var(--ink-soft);
margin-top: 4px;
line-height: 1.4;
}
.layer.accent {
border-color: var(--mark);
}
.layer.accent .slot {
background: var(--mark);
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.chip {
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
border: 1px solid var(--ink);
padding: 4px 8px;
background: var(--paper-deep);
}
/* ——— pipelines blueprint ——— */
#pipelines { height: 820px; }
.pipe-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.pipe {
border: 1.5px solid var(--ink);
background: rgba(255,255,255,0.28);
display: flex;
flex-direction: column;
}
.pipe-head {
background: var(--ink);
color: var(--paper);
padding: 14px 18px;
display: flex;
justify-content: space-between;
align-items: baseline;
}
.pipe-head strong {
font-size: 18px;
letter-spacing: -0.02em;
}
.pipe-head span {
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
letter-spacing: 0.08em;
opacity: 0.8;
}
.pipe-head.design {
background: var(--rule);
}
.steps {
list-style: none;
padding: 8px 0;
}
.steps li {
display: grid;
grid-template-columns: 48px 1fr;
gap: 0;
border-bottom: 1px solid var(--hair);
min-height: 62px;
}
.steps li:last-child { border-bottom: none; }
.steps .n {
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
font-weight: 600;
color: var(--mark);
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid var(--hair);
}
.steps .t {
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: center;
}
.steps .t strong {
font-size: 15px;
letter-spacing: -0.01em;
}
.steps .t span {
font-size: 13px;
color: var(--ink-soft);
margin-top: 2px;
line-height: 1.35;
}
.dispatch {
margin-top: 20px;
border: 1.5px solid var(--ink);
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.dispatch .cell {
padding: 12px 14px;
border-right: 1px solid var(--ink);
font-size: 13px;
line-height: 1.35;
color: var(--ink-soft);
}
.dispatch .cell:last-child { border-right: none; }
.dispatch .cell strong {
display: block;
font-family: "IBM Plex Mono", monospace;
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mark);
margin-bottom: 4px;
}
/* ——— install / hosts blueprint ——— */
#install { height: 780px; }
.install-top {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 20px;
margin-bottom: 20px;
}
.terminal {
border: 1.5px solid var(--ink);
background: var(--ink);
color: var(--paper);
padding: 22px 24px;
font-family: "IBM Plex Mono", monospace;
font-size: 13.5px;
line-height: 1.55;
min-height: 280px;
white-space: pre-wrap;
}
.terminal .prompt { color: #b8c4a8; }
.terminal .cmt { color: #8a8478; }
.terminal .ok { color: #9dcc9a; }
.hosts {
display: grid;
grid-template-rows: repeat(4, 1fr);
gap: 10px;
}
.host {
border: 1.5px solid var(--ink);
background: rgba(255,255,255,0.32);
display: grid;
grid-template-columns: 110px 1fr;
align-items: center;
}
.host .name {
background: var(--paper-deep);
border-right: 1.5px solid var(--ink);
font-family: "IBM Plex Mono", monospace;
font-size: 13px;
font-weight: 600;
padding: 14px 12px;
text-align: center;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.host .path {
font-family: "IBM Plex Mono", monospace;
font-size: 12.5px;
padding: 12px 14px;
color: var(--ink-soft);
line-height: 1.35;
}
.constraints {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
}
.constraints .bit {
border: 1.5px solid var(--ink);
padding: 16px 18px;
background: rgba(255,255,255,0.28);
min-height: 110px;
}
.constraints .bit strong {
display: block;
font-size: 16px;
margin-bottom: 6px;
letter-spacing: -0.01em;
}
.constraints .bit span {
font-size: 14px;
color: var(--ink-soft);
line-height: 1.4;
}
</style>
</head>
<body>
<!-- 1. How it works -->
<section class="sheet" id="how">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · how it works</div>
<h1>Two lines of defense.<br>Neither is optional.</h1>
<p class="lede">Models converge on the same center. unmachined does not ask the model to “sound more human.” It checks known tells first, then refuses to replace one monoculture with another.</p>
<div class="defense">
<article class="card">
<div class="stamp">01</div>
<div class="num">LINE 01</div>
<h2>Severity-tiered catalogs</h2>
<p>Deterministic scanners score prose and UI source before any model self-judgment.</p>
<ul>
<li>critical 20 · major 10 · minor 3</li>
<li>score 0–100, fail at 40</li>
<li>exit code 1 for CI and hooks</li>
</ul>
</article>
<article class="card">
<div class="stamp">02</div>
<div class="num">LINE 02</div>
<h2>Variety requirement</h2>
<p>A fix becomes a new tell the moment every output converges on the same alternative.</p>
<ul>
<li>two runs should not share a voice</li>
<li>layout skeletons must not clone</li>
<li>banning “delve” just moves the cluster</li>
</ul>
</article>
</div>
<p class="footnote">Priority when effort splits: text first · design & layout equal second · branding last</p>
</div>
</section>
<!-- 2. Verbs -->
<section class="sheet" id="verbs">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · command surface</div>
<h1>Six verbs. One skill.</h1>
<p class="lede">The skill is the command. The first argument selects the workflow. Hosts without a leading slash use the same words in natural language.</p>
<div class="verbs">
<div class="verb full">
<code>/unmachined <task></code>
<div class="desc">Default. Build or write with every applicable rule active from the start.</div>
</div>
<div class="verb">
<code>audit <target></code>
<div class="desc">Score and rank findings. Change nothing.</div>
</div>
<div class="verb">
<code>fix <target></code>
<div class="desc">Audit, repair, verify, then summarize.</div>
</div>
<div class="verb">
<code>text <target></code>
<div class="desc">Prose and copy pipeline only.</div>
</div>
<div class="verb">
<code>ui <target></code>
<div class="desc">Interface and frontend pipeline only.</div>
</div>
<div class="verb full">
<code>diff [base]</code>
<div class="desc">Audit only changed lines and necessary context. Never widen into unrelated cleanup.</div>
</div>
</div>
<div class="priority">
<div><strong>01 text</strong>Tells, honesty, rhythm</div>
<div><strong>02 design</strong>Layout, tokens, a11y</div>
<div><strong>03 branding</strong>Only after the above</div>
<div><strong>read-only</strong>audit · diff never edit</div>
</div>
</div>
</section>
<!-- 3. Scanner flow -->
<section class="sheet" id="scanner">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · the scanner</div>
<h1>Facts pass. Filler fails.</h1>
<p class="lede">Deterministic checks run before the model grades anything. A model asked to judge its own prose will grade generously.</p>
<div class="flow">
<div class="panel fail">
<div class="label">draft · fail</div>
<pre>In today's rapidly evolving
landscape, our cutting-edge
platform empowers teams to
unlock actionable insights.
At the end of the day, it's
a game-changer.</pre>
<div class="badge">slop score 100 · FAIL</div>
</div>
<div class="arrow">→</div>
<div class="panel rewrite">
<div class="label">rewrite · say something</div>
<pre>The dashboard shows which
of your accounts churned
last week, and why. Most
teams find two or three
they had written off as
healthy.</pre>
<div class="badge">concrete claim · one fact</div>
</div>
<div class="arrow">→</div>
<div class="panel pass">
<div class="label">scan · pass</div>
<pre>$ python3 scripts/
scan_text.py draft.md
slop score: 0/100
(threshold 40)
→ PASS</pre>
<div class="badge">exit 0 · CI green</div>
</div>
</div>
<div class="scan-note">
<div class="bit">
<strong>Zero dependencies</strong>
<span>Python 3.8+. No install step. No network access.</span>
</div>
<div class="bit">
<strong>Two scanners</strong>
<span>scan_text.py for prose · scan_ui.py for UI source.</span>
</div>
<div class="bit">
<strong>Self-hosting</strong>
<span>This repo scans its own docs in CI. Sloppy README goes red.</span>
</div>
</div>
</div>
</section>
<!-- 4. Architecture -->
<section class="sheet" id="arch">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · package shape</div>
<h1>Load only what the task needs.</h1>
<p class="lede">SKILL.md is the router. References stay cold until a pipeline asks for them. Scanners stay deterministic and offline.</p>
<div class="layers">
<div class="layer accent">
<div class="slot">ROUTER</div>
<div class="body">
<strong>SKILL.md</strong>
<span>Verbs, pipelines, resolved rule conflicts, output discipline.</span>
</div>
</div>
<div class="layer">
<div class="slot">TEXT</div>
<div class="body">
<strong>text-tells · voice-and-copy</strong>
<span>Vocabulary kill list, cadence, honesty rules, microcopy tables.</span>
<div class="chips"><span class="chip">scan_text.py</span><span class="chip">threshold 40</span></div>
</div>
</div>
<div class="layer">
<div class="slot">DESIGN</div>
<div class="body">
<strong>design-tells · design-system · audit-playbook · stack-rules</strong>
<span>Visual tells, OKLCH tokens, pre-emit critique, Next/Tailwind/shadcn.</span>
<div class="chips"><span class="chip">scan_ui.py</span><span class="chip">WCAG 2.2 AA</span></div>
</div>
</div>
<div class="layer">
<div class="slot">HOSTS</div>
<div class="body">
<strong>Claude · Codex · Cursor · Hermes</strong>
<span>One skill directory. Symlink per host. No separate command files.</span>
</div>
</div>
</div>
<p class="footnote">MIT · Python scanners only · agent skill, not an AI detector</p>
</div>
</section>
<!-- 5. Pipelines blueprint -->
<section class="sheet" id="pipelines">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · pipeline blueprint</div>
<h1>Two pipelines. Shared discipline.</h1>
<p class="lede">Surface detection picks the path. Scan first, then model judgment. Hard gates stay offline and reproducible.</p>
<div class="pipe-grid">
<div class="pipe">
<div class="pipe-head">
<strong>Text pipeline</strong>
<span>MAIN PRIORITY</span>
</div>
<ol class="steps">
<li>
<div class="n">01</div>
<div class="t">
<strong>Load catalogs</strong>
<span>text-tells.md · voice-and-copy.md for microcopy</span>
</div>
</li>
<li>
<div class="n">02</div>
<div class="t">
<strong>Draft or edit</strong>
<span>Write the claim first. Vocabulary comes second.</span>
</div>
</li>
<li>
<div class="n">03</div>
<div class="t">
<strong>Run scan_text.py</strong>
<span>Score >= 40 blocks fix and build delivery</span>
</div>
</li>
<li>
<div class="n">04</div>
<div class="t">
<strong>Self-critique once</strong>
<span>What still reads generated? One revise pass.</span>
</div>
</li>
<li>
<div class="n">05</div>
<div class="t">
<strong>Honesty gate</strong>
<span>Real numbers only. No invented quotes or metrics.</span>
</div>
</li>
</ol>
</div>
<div class="pipe">
<div class="pipe-head design">
<strong>Design pipeline</strong>
<span>UI · LAYOUT</span>
</div>
<ol class="steps">
<li>
<div class="n">01</div>
<div class="t">
<strong>Load design rules</strong>
<span>design-tells · design-system · stack-rules when needed</span>
</div>
</li>
<li>
<div class="n">02</div>
<div class="t">
<strong>Run scan_ui.py</strong>
<span>Grep-able tells before visual judgment</span>
</div>
</li>
<li>
<div class="n">03</div>
<div class="t">
<strong>Lock structure + tokens</strong>
<span>Layout skeleton first. OKLCH tokens. One bold risk.</span>
</div>
</li>
<li>
<div class="n">04</div>
<div class="t">
<strong>Pre-emit critique</strong>
<span>Six axes, 1-5. Any axis under 3 triggers a rewrite.</span>
</div>
</li>
<li>
<div class="n">05</div>
<div class="t">
<strong>Hard gates</strong>
<span>WCAG 2.2 AA · reduced motion · focus · 320-768px</span>
</div>
</li>
</ol>
</div>
</div>
<div class="dispatch">
<div class="cell"><strong>dispatch</strong>Parse verb first. Default is build with all rules on.</div>
<div class="cell"><strong>target</strong>Smallest file, dir, URL, or paste that satisfies the ask.</div>
<div class="cell"><strong>read-only</strong>audit and diff never edit until the user asks for fix.</div>
<div class="cell"><strong>variety</strong>Consecutive outputs must not clone voice or skeleton.</div>
</div>
</div>
</section>
<!-- 6. Install / hosts blueprint -->
<section class="sheet" id="install">
<div class="reg tl"></div><div class="reg tr"></div>
<div class="reg bl"></div><div class="reg br"></div>
<div class="pad">
<div class="eyebrow">unmachined · install blueprint</div>
<h1>One skill directory.<br>Four host symlinks.</h1>
<p class="lede">Clone once. Point each agent host at the same tree. The skill supplies the command; no separate command files.</p>
<div class="install-top">
<pre class="terminal"><span class="cmt"># global install</span>
<span class="prompt">$</span> git clone https://github.com/0xNyk/unmachined.git
<span class="prompt">$</span> cd unmachined
<span class="prompt">$</span> ln -s "$(pwd)" ~/.claude/skills/unmachined
<span class="prompt">$</span> ln -s "$(pwd)" ~/.codex/skills/unmachined
<span class="prompt">$</span> ln -s "$(pwd)" ~/.cursor/skills/unmachined
<span class="prompt">$</span> ln -s "$(pwd)" ~/.hermes/skills/unmachined
<span class="cmt"># scanners: Python 3.8+, zero deps</span>
<span class="prompt">$</span> python3 scripts/scan_text.py draft.md
<span class="ok">slop score: 0/100 (threshold 40) -> PASS</span></pre>
<div class="hosts">
<div class="host">
<div class="name">Claude</div>
<div class="path">~/.claude/skills/unmachined<br>or .claude/skills/unmachined</div>
</div>
<div class="host">
<div class="name">Codex</div>
<div class="path">~/.codex/skills/unmachined<br>or .codex/skills/unmachined</div>
</div>
<div class="host">
<div class="name">Cursor</div>
<div class="path">~/.cursor/skills/unmachined<br>or .cursor/skills/unmachined</div>
</div>
<div class="host">
<div class="name">Hermes</div>
<div class="path">~/.hermes/skills/unmachined<br>project skill folder also fine</div>
</div>
</div>
</div>
<div class="constraints">
<div class="bit">
<strong>No package install</strong>
<span>Scanners need Python only. No pip, no network, no runtime deps.</span>
</div>
<div class="bit">
<strong>Project-scoped OK</strong>
<span>Put the directory under the host project skill folder when the skill should not be global.</span>
</div>
<div class="bit">
<strong>Not an AI detector</strong>
<span>A low score means known tells are absent. It is a writing tool for your drafts.</span>
</div>
</div>
</div>
</section>
</body>
</html>
Attribution
unmachined is a distillation. The rules were merged, deduplicated, re-severitied, and in places rewritten from a research pass across the anti-slop field. Where sources disagreed, SKILL.md picks a winner and says so rather than averaging them.
This file credits the sources and records their licenses. Where a source is quoted rather than distilled, the reference file carries an inline marker (for example (humanizer), (jalaalrd), (unslop)).
unmachined itself is MIT. See LICENSE.
Apache-2.0 sources
These two are acknowledged under the Apache License 2.0. Their rules informed references/design-system.md (interface copy, empty states) and references/design-tells.md (eyebrow and kicker discipline). The prose in this repo is rewritten, not copied.
| Source | License |
|---|---|
Anthropic, frontend-design skill | Apache-2.0 |
impeccable skill | Apache-2.0 |
MIT sources
| Source | Fed into | License |
|---|---|---|
| blader/humanizer | text-tells.md: vocabulary kill list, voice-and-soul rules | MIT (Copyright 2025 Siqi Chen) |
| theclaymethod/unslop | text-tells.md: 313-phrase lexicon, deterministic scanning, the regenerate-against-directive loop | MIT |
| jalaalrd/anti-ai-slop-writing | text-tells.md: punctuation budgets, specificity rules | MIT (asserted in README) |
| nutlope/hallmark | design-system.md, design-tells.md | MIT |
| leonxlnx/taste-skill | design-tells.md: decoration and metadata tells | MIT |
| Anthropic, frontend-aesthetics cookbook | design-system.md: the three dials | MIT (anthropics/claude-cookbooks) |
| Vercel, web-interface-guidelines | stack-rules.md, design-system.md | MIT (asserted in README) |
| shadcn/ui official skill | stack-rules.md: shadcn de-genericization | MIT |
| ofershap/tailwind-best-practices | stack-rules.md: Tailwind v4 | MIT |
CC BY-SA
Wikipedia, "Signs of AI writing" (WikiProject AI Cleanup), CC BY-SA 4.0. The taxonomy of written tells traces back here, largely by way of blader/humanizer, which ports it. unmachined's catalog is restructured and re-severitied rather than copied, but the debt is direct and worth naming.
Research
Sun et al., "Idiosyncrasies in Large Language Models", arXiv:2502.12150 (ICML 2025). The empirical basis for the claim that model idiosyncrasies live in word-level distributions and survive rewriting, translation, and summarization. That finding is why unmachined fixes cadence and structure instead of stopping at vocabulary.
Style references
Mailchimp Content Style Guide (open-sourced), Atlassian Design System, Slack, Intuit, and the GOV.UK Style Guide were consulted for voice-and-copy.md. Referenced, not reproduced.
---
Maintainers: if you want an attribution changed, or your material removed, open an issue.
Changelog
This file records user-visible changes. Versions follow semantic versioning: major for incompatible workflow changes, minor for new behavior, and patch for compatible fixes.
Unreleased
- Add pipeline and install blueprint infographics.
- Add brand assets (logo mark, lockup, social card, infographics) and
docs/brand.md.
- Add repository governance, contributor intake, agent Git rules, and portable
skill validation.
0.2.0 - 2026-07-12
- Add the README banner.
- Add
uianddiffworkflows. - Document Claude Code, Codex, Cursor, and Hermes installation and invocation.
- Align frontmatter with the Agent Skills specification.
0.1.0 - 2026-07-09
- Publish the initial text and UI tell catalogs.
- Add zero-dependency text and UI scanners.
Code of conduct
Be specific, honest, and useful.
Harassment, threats, discriminatory language, deliberate humiliation, and publishing another person's private information are not accepted. Critique the rule, evidence, or change rather than the contributor. Disagreement is welcome; contempt is not.
The maintainer may edit or remove comments, close threads, reject contributions, or restrict participation when conduct harms the project or its contributors. Report conduct problems privately through the contact route in SECURITY.md. Reports will be reviewed without public disclosure unless the reporter agrees otherwise or disclosure is legally required.
Contributing
The useful contribution is a new tell, or evidence that an existing one is wrong.
To propose a tell, open an issue or PR with three things:
1. The evidence. Where you saw it, and roughly how often. "Every model does this" is not evidence. A link, a sample, or a generation count is. 2. A severity. Critical (a phrase that damns the whole draft), major (a strong signal), or minor (a smell). Scoring runs 20 / 10 / 3 and blocks at 40, so calling something critical is a claim that four of them should never coexist in a passing draft. 3. The alternative. A tell without a fix is a complaint. And because the fix becomes the next tell once everyone converges on it, give a range rather than one replacement.
Machine-checkable rules go in the scanner (scripts/scan_text.py or scripts/scan_ui.py). Both are zero-dependency Python and deliberately blunt. Anything that needs judgment (rhythm, hierarchy, whether a joke lands) belongs in references/, not in a regex.
Removals are welcome too. A rule that fires on good human writing is worse than no rule at all, so a demonstrated false positive is a bug and will be treated as one.
Run the scanners on your own changes before opening the PR:
python3 scripts/scan_text.py README.md
python3 scripts/scan_ui.py path/to/ui/srcSources get credited in ATTRIBUTION.md. Lift a rule from someone, say so.
Workflow
Open an issue before changing scoring, severity, supported hosts, or the core philosophy. Small fixes can go straight to a pull request.
1. Fork the repository if you are an outside contributor. Regular collaborators should create a topic branch in this repository. 2. Keep each commit to one meaningful change. Stage explicit paths or use git add -p; do not sweep unrelated working-tree changes into the commit. 3. Run the checks below and record the results in the pull request template. 4. Open a pull request against main. Do not force-push after review unless a maintainer asks for it; a new push may invalidate an existing approval.
python3 scripts/validate_skill.py
python3 scripts/scan_text.py README.md
python3 scripts/scan_text.py SKILL.md
git diff --checkBy contributing, you agree that your contribution is licensed under the MIT license in LICENSE. See GOVERNANCE.md for how project decisions are made and CODE_OF_CONDUCT.md for participation expectations.
Brand
unmachined is an anti-slop tool. The brand should look edited by a human proofreader, not generated by a model. If a mockup could pass as a generic AI-product landing page, throw it out.
Name
- unmachined always, all lowercase, one word.
- Never UnMachined, Un-Machined, or UNMACHINED in marketing surfaces.
- Code and paths may use the same spelling; no camelCase rename.
Tagline
Primary:
Make it read written and look made, not generated.
Social / short:
text and UI that reads written, not generated
Do not invent alternate slogans. Do not stack multiple taglines on one asset.
Voice (one sentence)
Concrete, slightly dry, allergic to filler. Prefer a measured claim over a polished empty one. Full voice rules live in references/voice-and-copy.md.
Visual system
Metaphor
Letterpress proof on warm paper: registration marks, struck lines, red correction marks, a thin blue hairline rule. The product removes machine sameness; the art direction should feel handmade without looking messy.
Tokens
| Token | Value | Use |
|---|---|---|
| paper | #f3efe6 | backgrounds |
| paper-deep | #e8e2d4 | secondary panels |
| ink | #1c1a17 | type, rules, marks |
| ink-soft | #4a453c | secondary body |
| mark | #c41e1e | correction accent only |
| rule | #2a4a7a | hairline rules, labels |
| ok | #1f5c3a | pass / success only |
| fail | #c41e1e | fail / critical only |
Accent footprint stays small. Red is for proof marks and severity, not for large fills. No purple gradients, no glassmorphism, no neural-network illustrations, no floating 3D blobs.
Type
- Display / UI: IBM Plex Sans (or another deliberate grotesk with real
character). Not Inter, Roboto, Poppins, Space Grotesk, or system-ui as the hero face.
- Mono / labels: IBM Plex Mono.
- Wordmark in raster art stays letterpress-condensed black lowercase; do
not redraw it in a thin geometric sans.
Motifs that are on-brand
- Registration crosshairs in corners
- Horizontal ruled lines fading into the margin
- Red circle, caret, strike, or proof X
- Thin vertical blue rule
- Paper grain at low opacity
Motifs that are off-brand
- Purple/pink mesh gradients
- Centered hero with three feature cards by default
- Sparkle / magic / robot mascot
- Fake metrics or stock AI imagery
- Soft UI shadows as the main identity
Asset inventory
| File | Size / role |
|---|---|
assets/header.png | README banner |
assets/brand/logo-mark.png | Full square mark (u + proof slash) |
assets/brand/logo-mark-512.png | GitHub / avatar |
assets/brand/logo-mark-128.png | Favicon-scale |
assets/brand/logo-mark.svg | Vector mark for docs and sites |
assets/brand/logo-lockup.png | Square wordmark lockup |
assets/brand/social-card.png | 1280×640 Open Graph / GitHub social |
assets/infographics/how.png | Two lines of defense |
assets/infographics/verbs.png | Command surface |
assets/infographics/scanner.png | Fail → rewrite → pass |
assets/infographics/arch.png | Package layers |
assets/infographics/pipelines.png | Text and design pipeline blueprint |
assets/infographics/install.png | Host install and symlink map |
assets/brand/render-infographics.html | Source for regenerating infographics |
Source HTML for infographics is the source of truth for diagram copy. Prefer editing that file and re-rendering with headless Chrome over hand-editing PNG pixels.
GitHub setup (maintainer)
These are settings, not files:
1. Social preview: Repository → Settings → General → Social preview → upload assets/brand/social-card.png. 2. Topics: keep agent-skill, ai-slop, writing, design-system, llm, claude-skill. Add mit-license only if useful for discovery. 3. Description: keep the factual one-liner from the repo description field. Do not soft-sell. 4. Homepage: optional; only set when a real project site exists.
Contributor rules for brand assets
- New marketing art must use the token table above.
- Do not regenerate the wordmark in a new typeface without a maintainer
decision.
- Export social art at 1280×640. Keep text out of the outer 5% crop margin.
- Prefer SVG or code-built diagrams when labels must stay exact.
- License: same MIT as the repository unless a third-party source requires
more; credit those in ATTRIBUTION.md.
Anti-patterns (brand edition)
| Temptation | Why it fails here |
|---|---|
| "Powered by AI" badge | The product is a constraint on AI output |
| Detector framing | Scanner is a writing tool, not an accusation engine |
| Stock "human vs robot" art | Cliché; the proofreader mark is enough |
| Rainbow feature grids | Reads as generated SaaS |
Maintainer guide
This repository keeps intelligent editing separate from publication. Agents may inspect, edit, and verify on a topic branch. A human authorizes commits, pushes, pull requests, merges, tags, and releases.
GitHub repository settings
Configure a branch ruleset for main:
- require changes through a pull request;
- require the
self-scanandbehaviorchecks; - require conversation resolution;
- block force pushes and branch deletion;
- require linear history if squash or rebase merges are used;
- prevent bypass when a second maintainer joins.
Do not require an independent approval while the project has one maintainer; that would prevent ordinary releases. Add one required approval and CODEOWNER review before granting another person merge access.
Enable secret scanning, push protection, Dependabot alerts, and private vulnerability reporting. Keep Actions restricted to selected verified actions and read-only GITHUB_TOKEN permissions unless a workflow documents why it needs more.
These are GitHub settings, not repository files. Review them after ownership, plan, or maintainer changes.
After branding or README image changes, upload assets/brand/social-card.png under Settings → General → Social preview so link unfurls match the repo. Brand tokens and asset inventory live in docs/brand.md.
Change flow
1. Start from an up-to-date main and create agent/<short-task> or another descriptive topic branch. 2. Inspect the working tree before editing. Preserve unrelated changes. 3. Make the smallest cohesive change and run the commands in AGENTS.md. 4. Stage explicit paths or hunks. Inspect the staged diff and rerun applicable checks against the staged result. 5. Commit only after authorization. Push only the current topic branch after a separate authorization. 6. Open a pull request using the repository template. Merge after required checks pass and every review thread is resolved.
Releases
1. Move completed entries from Unreleased in CHANGELOG.md to a dated semantic version. 2. Set the same version in SKILL.md metadata. 3. Validate on every supported Python version through CI. 4. Merge the release pull request. 5. Create a signed or annotated vX.Y.Z tag from the merge commit and publish GitHub release notes from the changelog. 6. Never move an existing release tag. Publish a patch release for corrections.
The repository has no package build or runtime dependencies. Do not add a release artifact until a host or registry requires one; the tagged source tree is the distributable skill.
Periodic maintenance
- Review Dependabot pull requests monthly.
- Check the GitHub community profile and security overview before each release.
- Revalidate host discovery paths and Agent Skills frontmatter for minor
releases.
- Run an OpenSSF Scorecard review when CI, permissions, or release automation
changes.
- Archive stale issue forms and policies instead of leaving inaccurate guidance.
Governance
unmachined is currently maintained by 0xNyk. The maintainer sets release scope, merges changes, and resolves rule conflicts.
Project decisions are evidence-led. A new tell needs examples or measurements; a removal needs a demonstrated false positive. Compatibility and security fixes take priority over new catalogs.
Discussion happens in GitHub issues and pull requests. Small, reversible changes may be decided in their pull request. Changes to scoring, severity, supported hosts, or the skill's core philosophy should begin with an issue so users can inspect the reasoning before implementation.
Contributors earn no formal role through a fixed number of changes. If the project grows beyond one maintainer, this file will be revised to define reviewer and maintainer responsibilities before granting additional merge access.
MIT License
Copyright (c) 2026 0xNyk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
!unmachined
unmachined
An agent skill that removes AI slop from text and interfaces.
 
Current behavior and release history are recorded in CHANGELOG.md.
$ cat draft.md
In today's rapidly evolving landscape, our cutting-edge platform empowers
teams to unlock actionable insights. At the end of the day, it's a game-changer.
$ python3 scripts/scan_text.py draft.md
[CRITICAL] critical phrase: at the end of the day
[CRITICAL] critical phrase: game-changer
[CRITICAL] critical phrase: evolving landscape
[MAJOR ] major phrase: cutting-edge
[MAJOR ] major phrase: empower
[MAJOR ] major phrase: unlock
slop score: 100/100 (threshold 40) -> FAILRewrite it so it says something, and the score falls out on its own:
$ cat draft.md
The dashboard shows which of your accounts churned last week, and why. Most
teams find two or three they had written off as healthy.
$ python3 scripts/scan_text.py draft.md
slop score: 0/100 (threshold 40) -> PASSThe difference is not vocabulary. It is that the second version contains a fact.
Why
Every model converges on the same center. The same words (delve, seamless, not just X, it's Y), the same cadence, the same purple gradient on the same centered hero. The convergence is measurable: model idiosyncrasies live in word-level distributions and survive rewriting, translation, and summarization (arXiv:2502.12150, ICML 2025). Asking a model to "sound more human" does not fix this, because the model's idea of human is drawn from the same distribution.
So unmachined does not ask. It holds two lines of defense:
1. Severity-tiered catalogs of known tells, checked deterministically before any model self-judgment. 2. A variety requirement. A fix becomes a new tell the moment every output converges on the same alternative. Two runs of this skill should not share a voice or a layout skeleton unless the brief demands it.
Point two is the part most anti-slop tools miss. Banning delve just moves the cluster.
!Two lines of defense
What it does
| Invocation | Behavior |
|---|---|
/unmachined <task> | Build or write with every applicable rule active from the start |
/unmachined audit <target> | Score, report ranked findings, change nothing |
/unmachined fix <target> | Audit, repair, and verify |
/unmachined text <target> | Run only the prose and copy pipeline |
/unmachined ui <target> | Run only the interface and frontend pipeline |
/unmachined diff [base] | Audit only changed lines and their necessary context |
When effort has to be split: text first, design and layout equal second, branding last.
!Pipeline blueprint
When it fires
The skill routes on this description, verbatim from SKILL.md. If your situation is not in here, the agent will not load it:
Removes AI slop from text and interfaces. Use when writing or editing prose that must not
read as AI-generated (articles, posts, READMEs, marketing copy, microcopy), when building
or auditing web UI that must not look AI-generated (any stack; deep rules for
Next.js/Tailwind/shadcn), or when the user says "unmachined", "de-slop", "AI slop",
"sounds like AI", "looks AI-generated", "make this human", or asks for a design or copy
audit.
Install
Install the same skill directory on every host. The skill itself supplies the /unmachined command; no separate command files are needed.
git clone https://github.com/0xNyk/unmachined.git
ln -s "$(pwd)/unmachined" ~/.claude/skills/unmachined # Claude Code
ln -s "$(pwd)/unmachined" ~/.codex/skills/unmachined # Codex
ln -s "$(pwd)/unmachined" ~/.cursor/skills/unmachined # Cursor
ln -s "$(pwd)/unmachined" ~/.hermes/skills/unmachined # HermesFor a project-scoped install, put the directory under the host's project skill folder, such as .claude/skills/unmachined or .cursor/skills/unmachined. See host support for the discovery matrix and portability decisions.
!Install blueprint
The scanners need Python 3.8 or newer and nothing else. No dependencies, no install step, no network access.
The scanner
Deterministic checks run before the model judges anything, because a model asked to grade its own prose will grade generously.
python3 scripts/scan_text.py draft.md # human-readable findings
python3 scripts/scan_text.py draft.md --json # machine-readable
python3 scripts/scan_text.py draft.md --mode chat # prose | chat | ui
python3 scripts/scan_text.py draft.md --threshold 20 # stricter
echo "some text" | python3 scripts/scan_text.py - # stdin
python3 scripts/scan_ui.py src/ # grep-able design tellsBoth score 0 to 100, where lower is better, and fail at 40. Findings carry a severity that sets the weight: critical 20, major 10, minor 3. Exit code is 1 on failure, so either scanner drops into CI or a pre-commit hook as-is:
- run: python3 scripts/scan_text.py $(git diff --name-only --diff-filter=d origin/main | grep '\.md$')This repo runs its own scanners on its own documentation in CI. If the README ever reads as slop, the build goes red.
When not to use this
- Encyclopedic, legal, and reference text. Neutral and plain is the correct human voice
there. The rewrite loop knows to stop, but you should not reach for the skill at all.
- Anywhere a house style already wins. If your employer mandates em-dashes and Title
Case, this skill will fight your style guide and your style guide should win.
- As an AI detector. A low score means the text avoids known tells, not that a human
wrote it. Anyone can run the scanner and edit until it passes. It is a writing tool pointed at your own drafts, not a verdict on someone else's.
- As a substitute for having something to say. The scanner rewards concrete claims. It
cannot supply them.
Structure
SKILL.md router: verbs, pipelines, resolved rule conflicts
references/
text-tells.md vocabulary kill list, phrase and cadence tells,
punctuation rules, honesty rules, rewrite loop
voice-and-copy.md UX microcopy, voice profiles, honest-copy tables
design-tells.md severity-tiered visual tells, the 10-second scan
design-system.md what to build instead: type, OKLCH tokens, spacing,
motion, the three dials, pre-emit critique
audit-playbook.md audit flow, letter grading, redesign constraints
stack-rules.md Tailwind v4, Next.js 16, shadcn de-genericization
scripts/
scan_text.py deterministic prose scanner, zero dependencies
scan_ui.py deterministic UI-source scanner, zero dependencies
assets/
header.png README banner
brand/ logo mark, lockup, social card, SVG
infographics/ how / verbs / scanner / arch / pipelines / install
docs/
brand.md name, tokens, asset inventory, GitHub social setupOnly SKILL.md loads up front. The references load when the task needs them. Brand tokens and export rules: docs/brand.md.
Provenance
Distilled from a 24-source research pass: blader/humanizer, theclaymethod/unslop, jalaalrd/anti-ai-slop-writing, nutlope/hallmark, leonxlnx/taste-skill, impeccable, Anthropic's frontend-design skill and frontend-aesthetics cookbook, Vercel's web-interface-guidelines, Wikipedia's signs-of-AI-writing taxonomy, and the CMU idiosyncrasies paper. Where sources contradict each other, SKILL.md picks a winner and says why instead of averaging them.
Full credits and licenses: ATTRIBUTION.md.
Contributing
New tells, and evidence that an existing tell is wrong, are both welcome. A rule that fires on good human writing is worse than no rule. See CONTRIBUTING.md.
Bug reports and tell proposals use structured issue forms. Security reports must follow SECURITY.md rather than a public issue. Project decisions and support boundaries are documented in GOVERNANCE.md and SUPPORT.md.
Repository publication, branch protection, and releases follow the maintainer guide.
License
MIT. The skill distills material from third-party sources, two of them under Apache-2.0; those are acknowledged in ATTRIBUTION.md.
Audit playbook: scoring and fixing existing UI
How to audit a live or generated UI against design-tells.md and design-system.md, and how to fix what you find without breaking the product.
Audit flow
1. See it before judging it. Render the target and screenshot at 375px and 1440px (add 320px and 768px for anything layout-heavy). Read the source alongside the screenshots. 2. Run the 10-second scan from design-tells.md first: greps for gradients, banned fonts, gradient text, pure black/white, transition: all, z-index 9999, 100vw, invented metrics. These are cheap and deterministic. 3. Then the visual pass: nav and footer fingerprints, section rhythm, eyebrow count, card grids, hero composition, italic headings, spacing consistency, contrast. 4. Then the content pass: happy-talk intros ("Welcome to..."), vague button labels ("Submit", "Learn More"), duplicate-intent CTAs, visible instructions longer than one sentence, placeholder names and metrics. 5. Record every finding as: severity (CRITICAL / MAJOR / MINOR), tell name, file:line where possible (screenshot region when the source is not available), and the concrete fix. Findings without a location are opinions; anchor them. 6. Rank by severity first, then by visibility (above the fold beats footer).
Grading
Grade per category, then overall. Categories: typography, color and contrast, layout and spacing, motion, copy and content, accessibility.
- Each category starts at A.
- Each CRITICAL finding drops the category one full letter.
- Each MAJOR finding drops it half a letter.
- MINOR findings do not move the grade; list them as polish.
- Floor is F. Overall grade is the average of category grades, reported next to a
one-line verdict.
Report format: overall letter grade, per-category subscores, ranked findings table (severity, tell, location, fix), and a recommended fix order. An audit-only run stops here; do not edit unless asked to fix.
Audit modes
- Quick: steps 1-3 only, overall grade plus the top 5 findings. For "does this look AI"
triage.
- Full (default): all six steps, all categories.
- Regression: re-run a previous audit and diff the findings against the saved baseline;
report new, fixed, and persisting items.
Redesign constraints
A redesign changes the visual and interaction layer, nothing else.
Before touching anything, extract the design DNA of what exists: current macrostructure, type pairing, color anchor, spacing scale, nav and footer pattern. The extract separates what is brand (keep) from what is default (replace), and becomes the baseline the regression audit diffs against.
Preserve:
- Routes, page slugs, anchor IDs, and primary nav labels (SEO depends on them).
- Component APIs and data flow; the information architecture unless the user asks.
- Copy intent: rewrite slop phrasing, keep the claims, numbers, and meaning.
- Genuine brand tokens. If purple is actually the brand, purple stays; the tell is the
unbranded default, not the color.
Change:
- Typography pairing, scale, and weights. Color tokens. Spacing scale. Section-level
layout composition. Motion. Microcopy voice.
When in doubt whether something is brand or default, ask before replacing it.
Fix loop
1. Fix atomically: one finding per edit, commit-sized, referencing the finding ID. 2. Verify each fix at mobile widths (320/375) and desktop before moving on. A fix that breaks 320px is not a fix. 3. Cap risky auto-fixes. Track a running risk estimate: any revert, any edit touching files unrelated to a finding, or passing roughly 30 fixes in one session means stop, summarize, and hand the remainder back to the user as a punch list. 4. Fixes remove or restructure more often than they add. Never fix a tell by layering on decoration; that is how second-order monoculture starts. 5. Re-run the tell scan and re-grade after the batch. Report the true result ("B+, 3 MAJOR findings remain"), never a rounded-up pass.
Nielsen-style critique rubric (optional, /40)
For UX depth beyond the aesthetic scan, score the ten heuristics 0-4 each:
1. Visibility of system status 2. Match between system and the real world 3. User control and freedom 4. Consistency and standards 5. Error prevention 6. Recognition rather than recall 7. Flexibility and efficiency of use 8. Aesthetic and minimalist design 9. Help users recognize, diagnose, and recover from errors 10. Help and documentation
Scoring honesty: a 4 means genuinely excellent. Most real interfaces score 20-32. Tag issues P0-P3 and keep the total across runs so trend is visible; re-run after fixes to confirm the score moved.
Bias guard for high-stakes reviews: run two independent assessments that cannot see each other, then synthesize. Disagreements between the two passes are usually the most honest findings in the report.
Design system: the constructive rules
What to do instead of the defaults. Pair with design-tells.md: that file bans, this file builds. Every rule here is a decision to make on purpose; the tell is never the pattern itself, it is the pattern chosen by reflex.
Typography
Pairing
- Pick a deliberate display + body pairing before writing any code. The canonical shape is
2 + 1: two main families (display and body) plus one optional mono or utility outlier used in at most two slots. Three families is the ceiling. Four is slop.
- Pair on a contrast axis: serif display with geometric sans body, characterful display
with mono data face, or one variable family stretched across extreme weights.
- Serif is allowed as a deliberate, justified choice and banned as the unconsidered
"creative brief means serif" reflex. Avoid Fraunces and Instrument Serif; both are trending-to-cliche. If serif, pick one you can defend for this brief.
- Never Inter, Roboto, Open Sans, Poppins, Lato, Arial, or system-ui as display. Do not
swap in Space Grotesk as the safe alternative; that is the convergence trap.
Weight and style
- Commit to weight extremes: 100-200 against 800-900. Weight 400 next to weight 600 reads
as a default setting.
- Headings and display type are always roman (font-style: normal), h1 through h6. No
italicized emphasis word inside an upright headline.
- Display letter-spacing floor: never tighter than -0.04em. -0.02 to -0.03em is plenty.
- All-caps text gets 5-12% extra letterspacing and stays under one line.
Scale and measure
- Pick one type-scale ratio and hold it: 1.25 (major third) or 1.333 (perfect fourth) for
standard hierarchy; up to 1.5 or 1.618 for dramatic editorial work.
- Body text 15-25px on the web. Line spacing 120-145% of the point size (body line-height
1.5-1.65; display 1.05-1.2, with an absolute floor of 1.0 for all-caps display).
- Measure 45-90 characters including spaces; default max-width: 65ch.
- Size hero type by copy length, not by template:
- Self-written headlines: 7 words or fewer, 50 characters or fewer.
- 21-50 characters: full display size.
- 51-90 characters: one step down from display.
- Over 90 characters: rewrite the headline, or cap at a mid-size heading token.
- Headline never wraps past 2 lines. Use text-wrap: balance on h1-h3, text-wrap: pretty
on prose.
Color
Tokens
- Define the palette as OKLCH tokens in CSS custom properties on :root. 4-6 named values
covering paper, ink, neutrals, and accent.
- Lock the tokens. Every color and every font-family declaration in the artifact
references a named token. Inline hex, rgb(), or oklch() values outside :root are mid-render improvisation and are not allowed.
Neutrals and grounds
- Micro-tint every neutral. No zero-chroma grey as paper: minimum 0.005 chroma, typically
0.005-0.015, tinted toward the palette's anchor hue.
- Paper lightness: L 96-98% in light mode, 12-16% in dark mode. Ink: L 16-22% light,
92-96% dark. Never pure #000 or #fff.
- Dark versus light is never a default. Write one sentence about who uses this, where,
under what ambient light; if the sentence does not force the answer, it is not concrete enough yet.
Accent
- One chromatic accent per page, maximum two. Everything else is neutral.
- Accent footprint: 3% or less of any viewport, hard cap 5% (atmospheric background
washes may go higher, by decision, not by default).
- Accent chroma 0.12-0.22; keep saturation under 80%. Dominant color with sharp accents
beats a timid, evenly distributed palette.
- Never grey text on a colored background: keep the hue of the background and adjust
lightness and saturation instead.
Contrast: the deterministic gate
- WCAG 2.2 AA is the hard pass/fail gate, checkable by tool, not by eye:
- Body text (including placeholder text): 4.5:1 minimum.
- Large text (18px+, or 14px bold), UI components, icons, focus rings: 3:1 minimum.
- APCA is the design-stage signal, not the compliance bar: aim near Lc 75 for 14px body,
Lc 60 for large or bold headings. When WCAG and APCA disagree, WCAG AA decides compliance and APCA tells you whether people will actually read it.
- Quick pre-check in OKLCH: if |L_text - L_bg| is under 50 percentage points, it will
probably fail. Compute the real ratio before shipping.
Layout and spacing
Spacing scale
- Use a 4pt/8pt scale with semantic token names (--space-3xs through --space-5xl, or
2xs/xs/sm/md/lg/xl/2xl/3xl). All margins, padding, and gaps come from the scale; off-scale values are defects.
- Whitespace first: start with too much, then remove until it is right.
- Spacing is grouping: space inside a group must be smaller than space between groups.
Equal inside and outside spacing is ambiguous and reads as unconsidered.
Structure is information
- Structural devices (numbering, eyebrows, dividers, labels) must encode something true
about the content. Numbered markers (01 / 02 / 03) only when the content is actually a sequence. A divider only where the subject actually changes.
- Think macrostructure first: decide the page's structural concept (what kind of page is
this, what shape does its argument have) before styling any component.
- Structural variety across outputs: two pages for two different briefs must not share
the same hero, three-features, CTA, footer rhythm. Rotate macrostructure, nav, and footer patterns across consecutive outputs.
- Layout-family cap within a page: each layout family (card grid, split, full-bleed,
list, bento, marquee) appears at most once; a page with 8 sections needs at least 4 distinct families. Zigzag image/text splits: 2 in a row maximum.
- One job per section: one purpose, one headline, one short supporting sentence.
Hero constraints
- Headline 2 lines maximum. Subtext 20 words or fewer, 3-4 lines maximum.
- At most 4 text elements in the hero; primary CTA visible without scrolling.
- Logo walls and social proof go under the hero, never inside it.
- Full-height heroes use min-height: 100dvh, never h-screen/100vh.
Engineering floor
- overflow-x: clip on both html and body. Clip, not hidden.
- Image-bearing grid tracks use minmax(0, 1fr) so images cannot blow the track out.
- CSS Grid over flexbox percentage math (no width: calc(33% - 1rem) rows).
- Cards earn their existence: use a card only when the card is the interaction. Nested
cards are always wrong. Card radius tops out at 12-16px; full-pill radius is for tags and buttons only.
- Build a semantic z-index scale (dropdown, sticky, modal-backdrop, modal, toast,
tooltip). Never 999 or 9999.
Interaction states
- Every interactive component ships all eight states: default, hover, focus-visible,
active, disabled, loading, error, success. A button with only default and hover is half-built.
- Inputs share one minimum height (44px) and keep a fixed 1px border width across all
states, so state changes never shift layout.
- An empty state points somewhere; it does not shrug. Say what belongs here and hand over
the control that creates the first one.
Interface copy
Words carry as much of the design as the spacing does. Every label is there to make the thing easier to use, so treat copy as a material and not as trim.
- Label for the person, not the architecture. Users recognize what they operate; they do
not recognize your plumbing. Someone turns off alerts. Nobody edits a webhook payload.
- A control names its own consequence. "Save changes" beats "Submit", which promises
nothing. Cap primary CTA labels at 3 words.
- Hold one verb across the whole flow. Press "Publish" and the confirmation should say
"Published" - not "Success", not "Your content is live".
- One intent, one label. "Get in touch", "Contact us", and "Let's talk" are the same
intent wearing three costumes; pick one and reuse it.
- An error states the failure and the way out of it, phrased the way the rest of the
product speaks. No apology, no vagueness. Loading states end with an ellipsis ("Saving…", the single ellipsis glyph, not three periods).
- Specific beats clever, every time. If deleting 30% of the copy improves it, keep
deleting.
- Honest copy only: no invented metrics, no fabricated precision, no placeholder names.
One register per page; typographic quotes and apostrophes in display copy.
Motion
- Animate transform and opacity only. Never top, left, width, height, margin, or padding.
- Named easings only: ease-out expo/quart/quint, or cubic-bezier(0.16, 1, 0.3, 1). Never
the default ease keyword, never bounce or elastic on UI state changes.
- Duration bands: micro 50-100ms, short 150-250ms, medium 250-400ms, long 400-700ms.
Nothing over 700ms without a stated reason.
- prefers-reduced-motion is not optional: every animation collapses to a crossfade of
150ms or less, or to nothing.
- No scroll-listener jank: window.addEventListener("scroll") is banned, and so is
useState for continuous input values (mouse position, scroll progress). Use useMotionValue/useTransform/useScroll, GSAP ScrollTrigger, IntersectionObserver, or CSS scroll-driven animations. Do not mix GSAP or Three.js with Motion in one component tree.
- One orchestrated moment beats scattered effects: a single staggered page-load reveal
creates more delight than micro-interactions everywhere. Fewer than 3 microinteraction primitives per page. Cut motion before adding it.
- Motion must be motivated: if you cannot state the reason for an animation in one
sentence, drop it.
- Silent success over celebratory toasts; optimistic update with undo over confirmation
dialogs.
- Focus rings meet 3:1 contrast, appear instantly, and are never animated in. Reveal
animations enhance an already-visible default; never gate content visibility on a class-triggered transition.
The three dials
A configuration surface set from the brief before generating. Each dial is 1-10.
| Dial | 1 means | 10 means | Baseline |
|---|---|---|---|
| VARIANCE | perfect symmetry | artful chaos | 8 |
| MOTION | fully static | cinematic, physics | 6 |
| DENSITY | airy gallery | packed cockpit | 4 |
Conditional rules keyed to the dials:
- Centered hero allowed only when VARIANCE is 4 or lower.
- Reduced-motion support mandatory whenever MOTION is above 3; any motion claimed in copy
must actually be shown when MOTION is above 4; magnetic or perpetual micro-interactions only when MOTION is above 5.
- Generic card containers banned when DENSITY is above 7; render dense numbers in mono.
State the dial values out loud (in a comment or the design read) before building, so the choice is inspectable.
Mobile hard floor
- Verify at 320, 375, 414, and 768px before calling anything done. Desktop checks at
1280 and 1440 are secondary.
- No horizontal scroll at any width. Touch targets 44px minimum. No two-line clickable
text. Inputs share a consistent minimum height with fixed 1px borders across states.
The self-critique loop
Run before emitting, not after shipping.
1. Plan first: a compact token system (4-6 named color tokens, 2+ type roles, a layout concept, one signature element). Then critique the plan against the brief: "would I produce this for any similar brief?" If yes, revise the generic part and say what changed and why. Only then write code. 2. Pre-emit scoring on six axes, each 1-5: Philosophy, Hierarchy, Execution, Specificity, Restraint, Variety. Any axis below 3 triggers a revision pass. Two passes is normal; needing a third means the brief itself is wrong. 3. Spend your boldness in one place. One signature element is the memorable thing; everything around it stays quiet and disciplined. 4. Take one real aesthetic risk you can justify. Not taking a risk is itself a risk. 5. The Chanel rule: before shipping, remove one accessory. 6. Render it and look. A screenshot is worth a thousand tokens; beautiful code that does not mount is worth zero.
Design tells: the catalog of AI slop patterns
Score generated or existing UI against this catalog. Severity tiers:
- CRITICAL: instantly reads as AI-generated. Any single hit is a defect. Fix before shipping.
- MAJOR: strong tell. Two or more together read as AI. Fix unless the brief explicitly asks for it.
- MINOR: weak signal alone. Flag in clusters, not individually.
Scoring rule: no single tell is proof. One centered section or one rounded card is not slop. Score clusters of co-occurring tells, weighted by severity. This is the false-positive guard: detectors that fire on single tells flag human work constantly.
Root cause: distributional convergence
Models sample the statistical center of their training data unless steered. Given no direction, the same brief lands on the same purple gradient, the same Inter headline, the same three cards, every time. The output is not broken, it is average, and average is the tell.
Three counter-strategies:
1. Direct attention per design dimension. Decide typography, color, motion, and background explicitly instead of asking for "a nice design". 2. Reference, don't clone. Name inspirations (an IDE theme, a cultural aesthetic, a specific era or material world) without prescribing enough detail to copy them. 3. Name the defaults to avoid. Explicit bans on known defaults measurably raise output quality, and help smaller models most.
Critical tells
Color
- Purple or indigo gradient hero; any blue-to-purple, purple-to-cyan, cyan-to-magenta, or
orange-to-pink gradient scheme. Known as "AI purple" (provenance: Tailwind's bg-indigo-500 default). The single most-cited community tell.
- Gradient headline text (background-clip: text over a gradient fill).
- Pure #000 or #fff as page ground or ink. Always tint with a trace of chroma.
- Aurora blobs, floating orbs, mesh-gradient backgrounds, decorative wavy SVG dividers.
Typography
- Inter, Roboto, Open Sans, Poppins, Lato, Arial, Montserrat, or system-ui as the display
font. system-ui as primary is the "I gave up on typography" signal. Space Grotesk is the convergence trap: every AI tool reaches for it as the safe alternative to Inter.
- Italic headers. Any italic h1-h6, and especially one italicized emphasis word inside an
otherwise upright headline. One of the most reliable single tells. Headings are roman.
Layout
- The 3-column icon-card feature grid: icon in a colored circle, bold title, two-line
description, repeated three times symmetrically. The most recognizable AI layout.
- The AI nav: wordmark left, inline links, CTA button right, hairline bottom border.
- The AI footer: four link columns, social icon row, copyright line.
- Full-viewport centered hero: min-height 100vh with everything center-aligned.
- Card-in-card nesting. Nested cards are always wrong.
- Cookie-cutter section rhythm: hero, three features, testimonials, pricing, CTA, each
section the same height and pattern. Two pages for two different briefs must not share this rhythm.
Decoration and chrome
- Re-drawn UI chrome: hand-built fake browser bars, phone frames, terminal windows,
code-block windows, div-built dashboard screenshots. The user's environment already supplies real chrome; fake chrome is the top code-level tell.
- Emoji glyphs (sparkles, rocket, lightning bolt and friends) as feature icons, step
markers, list bullets, or pricing icons.
Copy and content
- Invented metrics: "10x faster", "trusted by 50,000+ teams", "99.9% uptime",
"+47% conversion". If the user did not supply a metric, do not invent one.
Major tells
Structure and layout
- Eyebrow on every section, and section-number eyebrows ("01 / FEATURES",
"001 · Capabilities"). Appears in 55-95% of generations. A kicker used once, on purpose, reads as a brand decision. A kicker used everywhere reads as a template. Cap: 1 per 3 sections.
- Tag-left/heading-right split section headers (small label one side, big headline the
other, in every section).
- Centered-everything: text-align center on every heading, card, and section.
- Icon tiles in colored circles or rounded squares as section decoration.
- Glassmorphism without purpose: backdrop-blur as the default surface treatment.
- Zigzag image/text alternation three or more times in a row.
- Colored accent stripe on the left edge of cards (border-left: 3px solid accent) as
decoration. Reserve the left rule for exactly one semantic role, or drop it.
- The hero-metric template: big number, small label, supporting stats, gradient accent.
- Bento grid as reflex; bento with empty filler cells (N items means exactly N cells).
- Ghost-card: 1px border plus a large soft box-shadow (blur 16px or more) on one element.
- Uniform bubbly border-radius on everything; radius of 32px or more on cards.
Color and surface
- The premium-consumer palette cluster: warm beige/cream grounds (#f5f1ea, #fbf8f1,
#efeae0 family) with brass, clay, or oxblood accents (#b08947, #b6553a, #9a2436 family) and espresso ink (#1a1714 family). The reflexive "premium" look; the brand disappears.
- The three named AI-default looks. Banned as defaults, allowed only when the brief asks:
1. Warm cream ground (near #F4F1EA), high-contrast serif display, terracotta accent. 2. Near-black ground, one saturated accent (acid-green or vermilion), nothing else. 3. Broadsheet: hairline rules, zero border-radius, dense newspaper columns.
- Serif conflict resolution: serif display is allowed as a deliberate, justified choice;
it is banned as the unconsidered "creative brief means serif" reflex. Fraunces and Instrument Serif are specifically flagged as trending-to-cliche.
Copy and content
- Generic hero copy: "Welcome to X", "Your all-in-one solution", "Build the future",
"Unlock the power of".
- Version labels in the hero: V0.6, BETA, INVITE-ONLY.
- Scroll cues: "Scroll to explore", bouncing chevrons, down arrows.
- Startup-cliche product names: Acme, Nexus, Pulse, SmartFlow.
- "Quietly in use at" social-proof tropes and logo walls inside the hero.
- Filler verbs as copy: Elevate, Unleash, Revolutionize, Seamless, Next-Gen. These are
banned vocabulary, not synonyms.
- Duplicate-intent CTAs: "Get in touch" plus "Let's talk" plus "Contact us". One intent,
one label, used everywhere.
- "Built for X" / "Designed for Y" marketing filler; "Get Started" and "Learn More" as
the only CTA vocabulary.
- Decorative status dots; a blinking "live" or "AI" dot in the nav.
- Locale/time/weather strips ("LIS 14:23 · 18°C") and decoration text strips
("BRAND. MOTION. SPATIAL.") on marketing pages.
- Stat banners of fake-round numbers; badge or pill floating above the H1.
- Numbered 1-2-3 step sections when the content is not actually a sequence.
- All-caps micro-labels on every section (the uppercase-tracking eyebrow in another coat).
- Dark mode as an unconsidered default, usually paired with low-contrast grey body text
that fails WCAG AA.
Motion
- Infinite-loop fade-ins and perpetual ambient micro-animations everywhere.
- Uniform hover:scale-105 on every card and button; several simultaneous hover effects
on one element.
- Bouncy or elastic easing on UI state changes; the default
easekeyword everywhere. - Animating layout properties (top, left, width, height, margin, padding).
- Focus rings that fade in; celebratory confetti or toast for routine visible actions.
- More than one marquee per page.
- Scroll-listener jank: window.addEventListener("scroll") or useState tracking scroll
and mouse position (visible as stutter on mobile).
Minor tells
- Placeholder names: Jane Doe, John Smith, Sarah Chan; generic avatars.
- z-index: 999 or 9999. Use a semantic z-index scale.
- 100vw widths (horizontal overflow once scrollbars exist).
- Straight (dumb) quotes and apostrophes in display copy.
- transition: all.
- h-screen heroes (iOS address bar jump); use min-height 100dvh.
- Middle-dot separator overuse (more than one per metadata line).
- Photo-credit captions as decoration; version footers (v1.4.2, Build 0048) on
marketing pages.
- Lorem ipsum or "your text here" placeholder content.
- Two icon libraries mixed in one page; lucide-react as an unconsidered default.
- Display letter-spacing crushed tighter than -0.04em.
- Uniform 16px radius, identical 24px padding, and shadows at exactly 0.1 opacity across
every component (the unedited-defaults fingerprint; each is fine alone, the triple is the signal).
- Pills or labels overlaid directly on images.
- Hand-rolled decorative SVG illustrations and sketchy doodle graphics where a real asset
or plain type would do.
- Teal (#16d5e6-adjacent) as the accent on CTA, headline, focus ring, and chart fill at
once: an accent chosen by tool default rather than by brand.
- Broken or hotlinked placeholder images (dead Unsplash URLs, picsum leftovers).
Second-order monoculture
The fixes become tells. The first wave of anti-slop guidance pushed outputs off purple-gradient-Inter, and unguided "fixed" outputs then converged, roughly 8 in 10, on warm cream paper, a Fraunces-style serif, a terracotta accent, mono micro-labels, a corner page counter, and a colored last word in the headline. That combination is now itself a tell. Any two of those together is a smell; the full set is the tell.
The rule that follows: require variety across outputs, not just quality within one output.
- First-order check: could someone guess the palette and theme from the product category
alone? Then it is the first training-data reflex. Rework it.
- Second-order check: could someone guess the aesthetic family from the category plus the
known anti-references? Then it is the trap one tier deeper. Rework it again.
- Rotate deliberately across consecutive outputs: macrostructure, ground (light/dark/tinted),
display style, and accent hue. Do not reuse the same serif or the same palette family in back-to-back projects.
- No fixed alternative is safe forever. This catalog is time-bound; refresh it by
periodically generating an unguided sample set and cataloging what the model currently converges on.
10-second scan
Fastest checks first. Grep before you look, look before you judge.
1. Grep for purple/indigo/violet gradients: gradient near indigo|violet|purple|#7c3aed|#8b5cf6|#6366f1. 2. Grep font stacks: Inter|Roboto|Open Sans|Poppins|Lato|Montserrat|system-ui|Space Grotesk. 3. Grep background-clip: text (gradient headlines). 4. Grep #000|#fff|black|white as ground/ink tokens. 5. Grep transition: all, z-index: 999, 100vw, h-screen. 6. Grep emoji codepoints in markup and invented-metric strings (10x|99.9%|50,000+). 7. Count feature cards: three identical icon-cards is a fail. 8. Look at nav and footer: wordmark-links-CTA-hairline nav, four-column footer. 9. Count eyebrows against sections; check for "01 /" numbering without a real sequence. 10. Check headings for italics, the hero for min-height 100vh centering, and cards for nesting.
If two or more CRITICAL groups hit, stop scanning and start fixing.
Host support
The portable artifact is the skill directory itself. Keep behavioral logic in SKILL.md, references, and scripts. Host adapters should only handle discovery or packaging.
Invocation
Use one command namespace everywhere:
/unmachined <task>
/unmachined audit <target>
/unmachined fix <target>
/unmachined text <target>
/unmachined ui <target>
/unmachined diff [base]Subcommands are ordinary arguments. This works on hosts that expose skills as slash commands and remains understandable on hosts that invoke skills through natural language. Separate unmachined-audit and unmachined-fix skills would duplicate the shared rules, increase discovery noise, and let the workflows drift.
Discovery and installation
| Host | User scope | Project scope | Slash behavior |
|---|---|---|---|
| Claude Code | ~/.claude/skills/unmachined/ | .claude/skills/unmachined/ | Skills are directly invocable as /unmachined |
| Codex | $CODEX_HOME/skills/unmachined/ | Use the current Codex project skill location when project scoping is needed | Invoke the discovered skill by name; do not rely on legacy prompt files |
| Cursor | ~/.cursor/skills/unmachined/ | .cursor/skills/unmachined/ | Current Agent Skills appear in the slash menu |
| Hermes | ~/.hermes/skills/unmachined/ or a category below skills/ | Install through the Hermes skill manager when distribution is preferred | Every installed skill becomes /unmachined |
Symlink the whole directory for local development. Copy or install it for a stable release. Restart or begin a new session after installation when the host caches skill metadata.
Portability rules
- Keep
nameanddescriptionwithin the Agent Skills specification. Put
version data under metadata.
- Make the description say both what the skill does and when it should load.
- Keep
SKILL.mdas a routing document. Load focused references only when the
selected pipeline needs them.
- Avoid host-specific tool names in the core workflow. Describe capabilities,
then use whichever read, shell, browser, image, or edit tool the host offers.
- Treat automatic invocation as optional. Every workflow must also work when a
user explicitly invokes /unmachined.
- Do not use pre-approved tool frontmatter for portability. Tool permission
support varies by client, and this skill does not need silent side effects.
Source basis
Checked July 2026 against the Agent Skills specification and the official documentation for Claude Code, Codex, Cursor, and Hermes Agent. Recheck host paths before publishing a new major release; discovery conventions can change.
Stack rules: Tailwind v4, Next.js 16, shadcn/ui, React
Framework-correctness layer. LLMs are trained on Tailwind v3 and pre-Cache-Components Next.js; they default to obsolete syntax and stock shadcn themes. Apply these rules whenever generating or reviewing code on this stack.
Version detection (do this first)
Before applying any rule below, confirm what the project actually runs:
1. Read package.json: check tailwindcss (v4.x vs 3.x), next (16 vs 15/14), react (19 vs 18). 2. Tailwind signal: tailwind.config.js/ts present and referenced = likely v3. @import "tailwindcss" in CSS + @theme block = v4. 3. Next.js signal: cacheComponents: true in next.config.ts = Cache Components model active. Without it, use cache rules do not apply. 4. shadcn signal: components.json present. Run npx shadcn@latest info --json to read live config (tailwindVersion, base: radix vs base-ui, isRSC, iconLibrary, aliases) before generating components.
If the project pins older versions, follow the project and flag the pin. Never mix v3 and v4 syntax in one codebase.
1. Tailwind v4 (not v3)
Banned v3 patterns and their v4 replacements
| v3 (banned) | v4 (correct) |
|---|---|
@tailwind base; @tailwind components; @tailwind utilities; | @import "tailwindcss"; |
tailwind.config.js JS object config | CSS-first @theme { } block |
postcss-import + autoprefixer plugins | single @tailwindcss/postcss (both built in) |
@layer utilities for custom utilities | @utility |
bg-opacity-50, text-opacity-*, border-opacity-*, ring-opacity-*, divide-opacity-*, placeholder-opacity-* | slash modifier: bg-red-500/50, text-white/80 |
bg-gradient-to-r | bg-linear-to-r (also bg-linear-45, bg-conic-*, bg-radial-*) |
prefix important: !flex | suffix important: flex! |
CSS var in brackets: bg-[--brand] | parens: bg-(--brand) |
comma in arbitrary grid values: grid-cols-[max-content,auto] | underscore: grid-cols-[max-content_auto] |
shadow-sm / shadow | shadow-xs / shadow-sm |
drop-shadow-sm | drop-shadow-xs |
blur-sm / blur | blur-xs / blur-sm |
backdrop-blur-sm | backdrop-blur-xs |
rounded-sm / rounded | rounded-xs / rounded-sm |
outline-none | outline-hidden |
ring (bare) | ring-3 (if you want the old 3px) |
flex-shrink-* / flex-grow-* | shrink-* / grow-* |
overflow-ellipsis | text-ellipsis |
decoration-slice | box-decoration-slice |
focus:transform-none | focus:scale-none (transforms are individual CSS props now) |
transition-[opacity,transform] | transition-[opacity,scale] |
v3 bare class prefix (tw-flex) | variant-style prefix at start: tw:flex tw:hover:bg-red-600 |
Removed v3 utilities fail silently: the compiler drops unknown classes with no error, so stale syntax ships as unstyled UI. Grep for the left column after generating.
Numeric and default-behavior gotchas
| Changed default | v3 | v4 |
|---|---|---|
ring width | 3px, blue-500 | 1px, currentColor |
border-* / divide-* color | gray-200 | currentColor |
| placeholder color | gray-400 | current text color at 50% opacity |
| button cursor | pointer | default |
hover: variant | always | only under @media (hover: hover); no sticky hover on touch |
space-y/x selector | margin-top on ~ :not([hidden]) | margin-bottom on > :not(:last-child) |
outline-<number> | width only | also sets outline-style: solid |
Browser floor: Safari 16.4+, Chrome 111+, Firefox 128+ (native cascade layers, @property, color-mix()). Flag v4 for projects that must support older targets.
@theme design tokens
Theme variables are not just CSS variables; they instruct Tailwind to generate utility classes. Namespaces map 1:1 to utility families:
| Namespace | Generates |
|---|---|
--color-* | bg-*, text-*, border-*, etc. |
--font-* | font-* |
--text-* | text-* sizes |
--font-weight-* | font-* weights |
--spacing-* | padding, margin, gap, width scales |
--radius-* | rounded-* |
--shadow-* | shadow-* |
--breakpoint-* | sm:, md:, ... |
--animate-* | animate-* |
Rules:
- Define brand tokens in
@theme { }, not:root(use:rootonly for vars that should not generate utilities). - Reset a namespace with
--color-*: initial;then define only your values. - Alias existing vars with
@theme inline(e.g.--font-sans: var(--font-inter)). - Prefer
@themetokens over repeated inline arbitrary values. - Container queries are built in:
@containeron the parent,@sm:/@md:variants on children. - CSS-only entry animations:
starting:variant (@starting-style), no JS needed. - Migrating a v3 project: run
npx @tailwindcss/upgrade, do not hand-migrate.
2. Next.js 16 App Router
Cache Components (cacheComponents: true) makes Partial Prerendering the default: static shell + cached content + Suspense-streamed dynamic holes. Uncached data accessed outside <Suspense> or use cache is a build error, not a style preference.
The use cache contract
Banned inside a use cache scope (build error or 50s cache-fill hang):
cookies(),headers(),searchParams,params. Read them outside and pass values as arguments.- Passing runtime Promises (e.g. the cookies store) via props, closure, or a shared Map into a cached function.
- Non-determinism:
Math.random(),Date.now(),crypto.randomUUID(). Defer withconnection()+ Suspense instead. - Non-serializable arguments: class instances, functions (except pass-through), Symbols, WeakMap/WeakSet, URL instances.
- Invoking a Server Action or introspecting
children/JSX slots. Pass through only, never call or read. React.cacheto smuggle data in:use cachehas an isolated React.cache scope; it reads back null.- Toggling Draft Mode (
enable()/disable()). - Relying on in-memory persistence across requests on serverless: it will not persist. Use
use cache: remote.
Arguments and closed-over values become the cache key: different inputs produce separate entries.
cacheLife TTL table
| Profile | stale (client) | revalidate (server) | expire |
|---|---|---|---|
| default | 5 min | 15 min | never |
| seconds | 30 s | 1 s | 1 min |
| minutes | 5 min | 1 min | 1 hr |
| hours | 5 min | 1 hr | 1 day |
| days | 5 min | 1 day | 1 week |
| weeks | 5 min | 1 week | 30 days |
| max | 5 min | 30 days | 1 year |
Hard rules: always set an explicit cacheLife; expire must exceed revalidate; client router enforces a 30-second minimum stale time regardless of config; caches with zero revalidate or expire under 5 minutes are excluded from prerenders and become dynamic holes (the seconds profile is always dynamic). Invalidate via cacheTag on read + updateTag/revalidateTag in a Server Action. Debug with NEXT_PRIVATE_DEBUG_CACHE=1.
Four-layer cache model (brief)
Request memoization (per render pass) -> Data Cache (use cache + cacheLife, survives requests) -> Full Route Cache (the prerendered static shell) -> Client Router Cache (30s minimum stale). Know which layer a bug lives in before touching config.
RSC data fetching
fetchis memoized per request tree but NOT cached by default; it blocks rendering until done. Cache withuse cacheor stream with<Suspense>.- Never sequential-await independent requests. Initiate all fetches first, then
await Promise.all([...]). Requests start whenfetchis called, not when awaited. React.cache()is per-request deduplication only; no sharing between requests.- Fetch data in the component that needs it; memoization makes prop drilling for data unnecessary.
Project structure
- Colocation is safe: a route is not public until
page.js/route.jsexists in the segment. _folderopts a folder and its subtree out of routing (private folders).(group)folders organize routes without affecting the URL; use route groups for multiple root layouts.- Do not put an empty-fallback
<Suspense>above<body>unless you intend to opt the whole app out of the static shell.
3. shadcn/ui de-genericization
The three dials
Stock --primary (muted indigo/zinc), stock --radius (0.5rem), and stock Inter are the instantly recognizable "default shadcn" fingerprint. People think they are choosing a color theme; they are choosing a product feel. Change all three before building anything:
| Dial | Stock (banned as-is) | Move to |
|---|---|---|
--primary | muted indigo or zinc | one saturated brand accent, used sparingly. One accent is the look; never add a second competing accent |
--radius | 0.5rem uniform | 0px (sharp), 1rem (soft), or 999px (pill). Avoid 0.5rem; the same components at a different radius read as a different product |
--font-sans / --font-display | Inter at defaults | a real pairing (serif display + clean sans body, or geometric display + humanist body) |
Then add exactly ONE signature token shadcn does not ship (grain texture, signature shadow stack, custom easing curve, or non-default border treatment) and apply it consistently.
Token layer
- Theme with OKLCH cssVars in
@theme, not hex. Example editorial neutrals: surfaceoklch(0.97 0.01 80), foregroundoklch(0.16 0.012 250). Replace untouched slate/zinc scales. - Semantic tokens only:
bg-primary,text-muted-foreground,bg-destructive. Never raw values likebg-blue-500. Base token = surface,-foreground= text/icons on that surface. - Single
--radiusderives the whole radius scale (radius-sm through radius-4xl); set it once at the token layer. classNameis for layout, not styling. Never override component colors or typography via className; restyle at the token layer.
Update-safe composition
- Never edit generated files in
components/ui/. Wrap them; keep originals unchanged so CLI updates apply cleanly. Preview updates with--dry-run/--diff. - Import from
@/components/ui/*, never from a package like@shadcn/ui(copy-paste ownership model). - Custom triggers:
asChild(radix) orrender(base-ui); check thebasefield from project config. - Variants via
cva, class merging viacn(). No template-literal conditional class strings. - Forms:
FieldGroup+Field, never rawdivwithspace-y-*. Validation:data-invalidon Field,aria-invalidon the control (React Hook Form + Zod, not manual validation). - Option sets of 2-7 choices:
ToggleGroup, not a loop of Buttons. - Items always inside their Group (SelectItem in SelectGroup, CommandItem in CommandGroup, etc.). Full Card composition (CardHeader/CardTitle/CardDescription/CardContent/CardFooter).
- Layout: flex +
gap-*, neverspace-x-*/space-y-*. Usesize-10when width equals height. No manual z-index on Dialog/Sheet/Popover. No sizing classes on icons inside components. - A11y structure: Dialog/Sheet/Drawer always need a Title (
sr-onlyif hidden); Avatar always needs AvatarFallback; icon-only buttons needaria-label. - Run
npx shadcn@latest info --jsonbefore generating; never import a component that was not added; ask which registry when unspecified.
4. React performance quick-flags
Highest-impact rules from Vercel's react-best-practices, condensed:
1. Parallelize independent async work with Promise.all(); sequential awaits are the top server-side sin (2-10x). 2. Import directly; avoid barrel files (major dev-boot, build, and cold-start cost). 3. Virtualize lists over 50 items (virtua, or content-visibility: auto). 4. <img> always gets explicit width and height (prevents CLS). Below-fold: loading="lazy"; above-fold: fetchpriority="high". 5. Never define components inside components (inputs lose focus on every keystroke). 6. Derive state during render, not in useEffect. 7. Pass a function to useState for expensive initial values. 8. Use Set/Map for hot lookups, not array.includes (O(1) vs O(n)). 9. Use toSorted(), not mutating sort(). 10. Ternary, not &&, for conditional render. 11. No layout reads in render (getBoundingClientRect, offsetHeight, scrollTop). 12. Animate transform/opacity only; never transition: all; honor prefers-reduced-motion; animations must be interruptible. 13. Authenticate Server Actions like public API endpoints. 14. after() for non-blocking post-response work; React.cache() for per-request dedup. 15. <button> for actions, <a>/<Link> for navigation; never <div onClick>. Never outline-none without a focus-visible replacement. Never block paste.
Also flag on sight: user-scalable=no or maximum-scale=1, form inputs without labels, hardcoded date/number formats (use Intl.*), unjustified autoFocus, "use client" sprawl on components that never touch state or browser APIs.
10-second scan
Grep these before shipping; any hit is stack slop:
@tailwind # v3 directives
tailwind.config.(js|ts) # JS config in a v4 project
bg-opacity-|text-opacity- # v3 opacity utilities
bg-gradient-to # v3 gradient syntax
bg-\[-- # v3 var-in-brackets
"!flex|!bg-|!text- # prefix important
bg-blue-500|bg-indigo- # raw colors in a shadcn project
space-x-|space-y- # use flex + gap
@shadcn/ui # wrong import path
w-10 h-10 # should be size-10
transition: all|transition-all
outline-none # v4: outline-hidden; and needs focus-visible
"use client" # count them; sprawl = server-first violated
Math.random()|Date.now() # inside any "use cache" file
cookies()|headers() # inside any "use cache" fileAlso check: every use cache has an explicit cacheLife; --primary, --radius, --font-sans differ from stock; no edits inside components/ui/ generated files.
Voice and copy: microcopy, tone, and honest claims
Sources: Mailchimp Voice & Tone (open-sourced), Atlassian Design System, Slack, Intuit, Shopify Polaris, Google Material and developer style, GOV.UK content design, VOICE.md spec, MindStudio voice profiles. Text tells in text-tells.md apply to every surface here first; this file adds the rules specific to product copy and voice.
UX microcopy rules
- Name things from the user's side. Sentences start with the user's goal (Google), not the
system's process: "To save your changes, press Save", not "The system requires saving".
- Active voice, present tense. The user does things; the interface responds.
- Errors don't apologize and are never vague. No "Oops!", no "Something went wrong" with
no next step. Say what happened, why, and what to do next, in that order. Never blame the user.
- Action labels are verb + object: "Save changes", "Delete project", not bare "OK",
"Submit", "Yes". Keep the same label for the same action through the entire flow; if the button says "Create workspace", the confirmation says "Workspace created", not "Environment ready".
- Empty states invite action. State what belongs here and give the first step:
"No projects yet. Import a repo to get started." Never a bare "No data".
- Links describe their destination: "View billing history", not "click here" or a bare
"Learn more".
- No generic CTAs everywhere. A page where every button reads "Get Started" or
"Learn More" is a tell. Name the value: "Start free scan", "See the pricing math".
- Silent success over celebratory toasts. Confirm quietly and get out of the way. Reserve
delight for genuine milestones; add the wink only when the user feels success, pride, or relief (Atlassian: "give flowers, not puppies").
- Copy should sound like something a person would actually say (Shopify Polaris). Read
every label aloud.
- Clear beats clever, always (Mailchimp: "always more important to be clear than
entertaining"). Omit unnecessary words, but don't be robotic (Slack).
- Dial personality DOWN in AI-generated copy. Intuit tested this: "breezy or funny comments
didn't test well... AI models have no ability to read the room." Wit is for human-authored surfaces.
Voice profile structure
- Constant voice, variable tone (Mailchimp): "You have the same voice all the time, but
your tone changes." Voice is who the product is; tone adapts to the moment.
- Emotional-state -> tone matrix (Atlassian): before writing, name the reader's likely
state. Frustrated at an error: practical, calm, zero jokes. Curious at onboarding: encouraging, brief. Proud at success: warmer, one light note allowed. Dial boldness, optimism, and practicality up or down per state; never change the underlying voice.
- The "X not Y" contrast device: define 3-5 named voice attributes, each with one contrast
and a do/don't example pair. "Confident but not cocky. Witty but never silly. Helpful but not overbearing." The negation forces a rejection test on every draft.
- Per-project voice profile (a compact VOICE.md, 400-700 words so it fits a system prompt):
voice: 3-5 attributes, each phrased "X not Y", each with a do/don't pair
spectra: formality, energy, warmth, complexity (mark a point on each)
case: sentence case for headings and UI text; caps policy (e.g. no ALL-CAPS lines,
or exactly one caps hook line, per brand)
banned claim terms: fastest, lowest latency, guaranteed, #1, best-in-class,
market-leading, unbeatable (exempt only with a cited measurement)
banned phrases: the brand's own list (e.g. game-changer, supercharge, seamless,
unlock, revolutionary, blazing fast)
brand spellings: canonical product names; internal names never said publicly
lexicon: protected terms (never rewrite) and forbidden terms, each with a reason
tones: per-surface adjustments (error, onboarding, success, marketing, support)
formatting tokens: date format, number style (spell out one-nine, numerals for 10+)
beliefs: 3-5 stated positions; every piece must trace to at least one- Teach voice by exemplar: keep 8-12 annotated samples of real on-voice writing
(MindStudio). Output gets checked against the samples, not the internet average.
- Store the profile in the repo and version it. Voice that lives in a PDF drifts; a
lint-able file doesn't. Export the forbidden lexicon as Vale existence rules for deterministic review.
Honest copy principles
- Replace buzzwords with plain words (GOV.UK, with its rationale: "we lose trust if we
write government buzzwords and jargon"). The core table:
| Banned | Write instead |
|---|---|
| deliver | make, create, provide (only pizzas get delivered) |
| leverage, utilize, deploy | use |
| facilitate | say specifically what you do |
| key (adjective) | important, or cut it |
| streamline | simplify |
| tackle, combat | solve, fix, reduce |
| liaise, collaborate | work with |
| overarching | cut it |
| one-stop shop | name the actual thing |
| agenda | plan |
| purchase | buy |
| assist | help |
| approximately | about |
- No false urgency. No countdown timers, "only 2 left", or "low stock" unless tied to real
inventory. Fake scarcity is a dark pattern and a legal exposure (UK CMA enforcement).
- Real numbers or labelled placeholders. Never invent a metric, testimonial, or user count
to fill a layout. Ship "users: [pending launch]" over a fabricated "10,000+ users".
- No superlatives or absolutes without a measurement. "Fastest" requires a number, a unit,
and a method.
- Define jargon on first use. Prefer the short word: buy, help, about, start.
- Substance over slogan: "seamless" is a placeholder for a missing feature description.
Replace it with the concrete claim or the number that proves it.
Brand voice (keep this layer thin)
Brand voice sits on top of the honesty and microcopy rules; it never overrides them.
- Never let character overwhelm content (Slack). If the joke costs clarity, cut the joke.
- Resolve the wit conflict by authorship: Mailchimp and Slack encourage dry humor in
human-authored copy; Intuit's tested finding says AI-generated copy should stay plain and warm. Default AI output to plain.
- One canonical banned-lexicon per brand, machine-checkable, not vibes. Per-brand
profiles may override fields (case policy, allowed hype level) without forking the rules.
Detection heuristic
Run these five checks first on any product copy:
1. Buttons and links: any bare "Get Started", "Learn More", "Submit", or "click here" without a named object or value? 2. Errors: any apology, blame, or "Something went wrong" without a stated next step? 3. Claims: any fastest/#1/guaranteed/best-in-class without a cited measurement, or any urgency not tied to real inventory? 4. Consistency: does the same action keep the same label across the whole flow, and does heading case follow the profile (sentence case unless the brand says otherwise)? 5. Evidence: any invented stats, fake testimonials, or unlabelled placeholder numbers in stat blocks?
#!/usr/bin/env python3
"""unmachined UI scanner: deterministic AI-design-tell detection. Zero dependencies.
Greps source files for the machine-checkable subset of references/design-tells.md
and references/stack-rules.md. Visual tells that need a rendered page (layout
rhythm, hierarchy) stay with the LLM audit; this catches the grep-able core.
Usage:
python3 scan_ui.py PATH [PATH...] [--json] [--threshold 40]
PATH may be files or directories (searched recursively for web source files).
Exit codes: 0 = pass, 1 = fail (score >= threshold), 2 = usage error.
"""
import argparse
import json
import re
import sys
from pathlib import Path
EXTENSIONS = {".tsx", ".jsx", ".ts", ".js", ".html", ".css", ".vue", ".svelte", ".astro", ".mdx"}
SKIP_DIRS = {"node_modules", ".next", "dist", "build", ".git", "coverage", ".turbo", "out"}
# (severity, rule, regex, hint)
CHECKS = [
# critical design tells
("critical", "AI purple gradient",
r"(from|via|to)-(purple|violet|indigo|fuchsia)-\d|linear-gradient\([^)]*(purple|violet|indigo|#7c3aed|#8b5cf6|#a855f7|#6366f1)",
"the single most recognized AI aesthetic; pick one deliberate accent instead"),
("critical", "default display font",
r"font-family:\s*['\"]?(Inter|Roboto|Open Sans|Poppins|Lato)\b|fontFamily.*['\"](Inter|Roboto|Open Sans|Poppins|Lato)['\"]",
"pair a deliberate display face with a body face; see design-system.md"),
("critical", "gradient headline text",
r"bg-clip-text|background-clip:\s*text",
"gradient-filled headings are a first-order tell"),
("critical", "emoji as feature icon",
r"[✨\U0001F680⚡\U0001F525\U0001F3AF✅\U0001F4A1\U0001F31F]",
"replace with a drawn icon set or none"),
("critical", "invented metric",
r"\b(10|20|50|100)x (faster|better|more)|trusted by [\d,]+\+|\+\d+% (conversion|productivity)",
"real numbers, a labelled placeholder, or nothing"),
("critical", "italic heading",
r"<h[1-6][^>]*(italic|font-style:\s*italic)|(className|class)=[\"'][^\"']*\b(text-[0-9]?xl|heading)[^\"']*\bitalic",
"headings stay roman; italics in display type is a reliable tell"),
# major
("major", "centered 100vh hero",
r"(h-screen|min-h-screen|100vh)[^\n]*(items-center|justify-center)|(items-center|justify-center)[^\n]*(h-screen|min-h-screen)",
"full-viewport centered hero is the template answer; consider asymmetry or content-led height"),
("major", "section-number eyebrow",
r">\s*0\d\s*[/·.]\s*[A-Z]",
"eyebrows off by default; numbering only for true sequences"),
("major", "icon-in-colored-circle tile",
r"rounded-full[^\n]*(bg-(blue|purple|indigo|green|pink|orange)-1?0?0)[^\n]*(p-[23])",
"SaaS-starter look; let icons sit unboxed or use a drawn treatment"),
("major", "transition-all",
r"transition-all|transition:\s*all",
"animate transform/opacity only, with named easings"),
("major", "universal hover scale",
r"hover:scale-10[2-9]",
"one orchestrated moment beats scattered hover effects"),
("major", "pure black or white surface",
r"(background(-color)?:\s*#(000|000000|fff|ffffff)\b)|bg-(black|white)(?![\w/-])",
"micro-tint surfaces; pure #000/#fff reads unconsidered"),
("major", "startup cliche name",
r"\b(Acme|Nexus|SmartFlow)\b",
"placeholder brand names are a tell; name the real thing"),
# stack slop (tailwind v3 in a v4 world; verify version first per stack-rules.md)
("major", "tailwind v3 directive",
r"@tailwind (base|components|utilities)",
"v4 uses @import \"tailwindcss\"; confirm project version first"),
("major", "raw palette color in component",
r"(bg|text|border)-(blue|indigo|purple|red|green)-[3-7]00",
"use semantic tokens (bg-primary, text-muted-foreground), not raw palette stops"),
("major", "space-* utility",
r"space-[xy]-\d",
"prefer flex/grid + gap-*"),
# minor
("minor", "placeholder person", r"\bJane Doe|John Doe\b", "use realistic, varied names"),
("minor", "z-index 9999", r"z-\[?9999\]?|z-index:\s*9999", "fix stacking context instead"),
("minor", "100vw", r"\b100vw\b", "causes horizontal scroll with scrollbars; use 100% or clip"),
("minor", "scroll cue", r"scroll (down|to explore)|↓ scroll", "content should invite scrolling on its own"),
]
WEIGHTS = {"critical": 20, "major": 10, "minor": 3}
def iter_files(paths):
for p in paths:
path = Path(p)
if path.is_file():
yield path
elif path.is_dir():
for f in sorted(path.rglob("*")):
if f.suffix in EXTENSIONS and not any(part in SKIP_DIRS for part in f.parts):
yield f
def scan_file(path):
findings = []
try:
text = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return findings
for lineno, line in enumerate(text.splitlines(), 1):
for severity, rule, pattern, hint in CHECKS:
if re.search(pattern, line):
findings.append({
"severity": severity, "rule": rule, "hint": hint,
"file": str(path), "line": lineno,
"excerpt": line.strip()[:110],
})
return findings
def main():
ap = argparse.ArgumentParser(description="unmachined deterministic UI scanner")
ap.add_argument("paths", nargs="+", help="source files or directories")
ap.add_argument("--json", action="store_true")
ap.add_argument("--threshold", type=int, default=40)
args = ap.parse_args()
findings = []
seen_rules_per_file = set()
for f in iter_files(args.paths):
for finding in scan_file(f):
# score each (file, rule) pair once to avoid one repeated utility
# class maxing the score, but report every location
finding["scored"] = (finding["file"], finding["rule"]) not in seen_rules_per_file
seen_rules_per_file.add((finding["file"], finding["rule"]))
findings.append(finding)
if not findings and not list(iter_files(args.paths)):
print("no scannable source files found", file=sys.stderr)
sys.exit(2)
total = min(100, sum(WEIGHTS[f["severity"]] for f in findings if f["scored"]))
passed = total < args.threshold
if args.json:
print(json.dumps({"score": total, "threshold": args.threshold,
"pass": passed, "findings": findings}, indent=2))
else:
order = {"critical": 0, "major": 1, "minor": 2}
for f in sorted(findings, key=lambda x: (order[x["severity"]], x["file"], x["line"])):
print(f"[{f['severity'].upper():8}] {f['file']}:{f['line']} {f['rule']}")
print(f" {f['excerpt']}")
print(f" fix: {f['hint']}")
print(f"\ndesign slop score: {total}/100 (threshold {args.threshold}) -> {'PASS' if passed else 'FAIL'}")
sys.exit(0 if passed else 1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Validate the portable Agent Skills package without third-party modules."""
import re
import sys
from pathlib import Path
NAME_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
def fail(message):
print("skill validation failed: {0}".format(message), file=sys.stderr)
return 1
def scalar(frontmatter, key):
match = re.search(r"(?m)^{0}:\s*(.+)$".format(re.escape(key)), frontmatter)
return match.group(1).strip().strip('"\'') if match else None
def folded_description(frontmatter):
match = re.search(
r"(?ms)^description:\s*>-?\s*\n(?P<body>(?:^[ \t]+.*\n?)+)",
frontmatter,
)
if not match:
return scalar(frontmatter, "description")
return " ".join(line.strip() for line in match.group("body").splitlines())
def main():
root = Path(__file__).resolve().parent.parent
skill_path = root / "SKILL.md"
if not skill_path.is_file():
return fail("SKILL.md is missing")
text = skill_path.read_text(encoding="utf-8")
parts = text.split("---", 2)
if len(parts) != 3 or parts[0].strip():
return fail("SKILL.md needs YAML frontmatter bounded by ---")
frontmatter = parts[1]
name = scalar(frontmatter, "name")
description = folded_description(frontmatter)
if not name or not NAME_RE.fullmatch(name):
return fail("name must contain lowercase letters, numbers, and single hyphens")
if name != root.name:
return fail("name must match the skill directory")
if len(name) > 64:
return fail("name exceeds 64 characters")
if not description or len(description) > 1024:
return fail("description must contain 1 to 1024 characters")
if len(text.splitlines()) >= 500:
return fail("SKILL.md must stay below 500 lines")
for directory in ("references", "scripts", "assets"):
if not (root / directory).is_dir():
return fail("{0}/ is missing".format(directory))
print("skill validation passed: {0}".format(name))
return 0
if __name__ == "__main__":
sys.exit(main())
Related skills
How it compares
Use instead of one-shot 'make this sound human' prompting, which draws from the same distribution that produced the slop.
FAQ
How does unmachined detect AI slop?
Two deterministic Python scanners check text and UI source against severity-tiered catalogs of known tells (critical 20, major 10, minor 3 points). A draft scoring 40 or above fails before any model-based rewrite runs.
Does unmachined work outside Claude Code?
Yes. It follows the Agent Skills format, so it works in Claude Code, Codex, and any skill host. The scanners are plain Python 3.8+ with zero dependencies and also run standalone in CI or pre-commit hooks.
How is unmachined different from banned-word de-slop skills?
Banning words like 'delve' just moves the cluster. unmachined also enforces variety across outputs so fixes don't converge on a new monoculture, and rewards concrete facts, which drop the slop score on their own.