taste-lint 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/data/config.schema.json +7 -0
- package/data/corpus/copywriting.jsonl +4 -4
- package/data/rule-drafts/interaction/interaction-layout-shift.yaml +3 -3
- package/data/rules/copywriting/copywriting-anthropomorphism.yaml +4 -3
- package/data/rules/copywriting/copywriting-antithesis-contrast.yaml +43 -0
- package/data/rules/copywriting/copywriting-antithesis-frame.yaml +23 -0
- package/data/rules/copywriting/copywriting-artificial-balance.yaml +4 -1
- package/data/rules/copywriting/copywriting-boilerplate-offer.yaml +1 -1
- package/data/rules/copywriting/copywriting-canned-phrasing.yaml +1 -1
- package/data/rules/copywriting/copywriting-claim-without-evidence.yaml +10 -1
- package/data/rules/copywriting/copywriting-click-here-links.yaml +4 -3
- package/data/rules/copywriting/copywriting-conditions-first.yaml +2 -2
- package/data/rules/copywriting/copywriting-copula-avoidance.yaml +21 -0
- package/data/rules/copywriting/copywriting-dead-link.yaml +28 -0
- package/data/rules/copywriting/copywriting-em-dash-in-ui.yaml +23 -0
- package/data/rules/copywriting/copywriting-error-descriptions.yaml +2 -2
- package/data/rules/copywriting/copywriting-excessive-hedging.yaml +1 -1
- package/data/rules/copywriting/copywriting-external-arrow-internal-link.yaml +26 -0
- package/data/rules/copywriting/copywriting-fabricated-specificity.yaml +6 -3
- package/data/rules/copywriting/copywriting-filler-words.yaml +4 -3
- package/data/rules/copywriting/copywriting-generic-framing.yaml +12 -6
- package/data/rules/copywriting/copywriting-generic-language.yaml +12 -4
- package/data/rules/copywriting/copywriting-generic-validation.yaml +1 -1
- package/data/rules/copywriting/copywriting-latinisms.yaml +6 -4
- package/data/rules/copywriting/copywriting-machine-prose.yaml +4 -1
- package/data/rules/copywriting/copywriting-mechanism-over-outcome.yaml +4 -1
- package/data/rules/copywriting/copywriting-meta-commentary.yaml +1 -1
- package/data/rules/copywriting/copywriting-missing-reasoning-link.yaml +4 -1
- package/data/rules/copywriting/copywriting-nominalisations.yaml +4 -3
- package/data/rules/copywriting/copywriting-over-explanation.yaml +4 -1
- package/data/rules/copywriting/copywriting-participle-tail.yaml +21 -0
- package/data/rules/copywriting/copywriting-passive-voice.yaml +4 -3
- package/data/rules/copywriting/copywriting-promotional-docs-voice.yaml +4 -3
- package/data/rules/copywriting/copywriting-prompt-restatement.yaml +5 -2
- package/data/rules/copywriting/copywriting-punctuation-tics.yaml +4 -1
- package/data/rules/copywriting/copywriting-reader-first.yaml +4 -1
- package/data/rules/copywriting/copywriting-repetitive-transitions.yaml +1 -1
- package/data/rules/copywriting/copywriting-requirements-language.yaml +2 -2
- package/data/rules/copywriting/copywriting-searchable-headings.yaml +2 -2
- package/data/rules/copywriting/copywriting-semantic-links.yaml +2 -2
- package/data/rules/copywriting/copywriting-slogan-headings.yaml +27 -0
- package/data/rules/copywriting/copywriting-structural-symmetry.yaml +4 -1
- package/data/rules/copywriting/copywriting-tee-up.yaml +23 -0
- package/data/rules/copywriting/copywriting-temporal-words.yaml +4 -3
- package/data/rules/copywriting/copywriting-unexplained-action.yaml +5 -2
- package/data/rules/copywriting/copywriting-unnatural-polish.yaml +8 -5
- package/data/rules/copywriting/copywriting-unverifiable-proof.yaml +1 -1
- package/data/rules/copywriting/copywriting-vague-error.yaml +2 -0
- package/data/rules/craft/craft-atmosphere-gradient.yaml +28 -0
- package/data/rules/craft/craft-decoration-no-role.yaml +2 -1
- package/data/rules/craft/craft-decorative-ordinals.yaml +26 -0
- package/data/rules/craft/craft-default-indigo.yaml +28 -0
- package/data/rules/craft/craft-faux-product-chrome.yaml +4 -1
- package/data/rules/motion/motion-will-change-misuse.yaml +28 -0
- package/data/rules/product/product-nested-modals.yaml +1 -1
- package/data/rules/product/product-select-for-few-options.yaml +1 -1
- package/data/rules/schema/rule.schema.json +7 -1
- package/dist/cli.js +748 -131
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +38 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +459 -51
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/data/rules/copywriting/copywriting-idea-repetition.yaml +0 -34
package/README.md
CHANGED
|
@@ -40,15 +40,17 @@ Checks bill your key and cache between runs. `--dry-run` previews the cost first
|
|
|
40
40
|
|
|
41
41
|
## What it checks
|
|
42
42
|
|
|
43
|
-
- **Motion:** `transition-all`, ease-in, linear easing, transitions over 300ms, entrances from scale zero.
|
|
44
|
-
- **Copy:** “click here” links, “successfully” toasts, emoji in UI,
|
|
43
|
+
- **Motion:** `transition-all` and `transition: all`, ease-in, linear easing, transitions over 300ms, entrances from scale zero, `framer-motion` imports.
|
|
44
|
+
- **Copy:** the [ghostwriter](https://github.com/mblode/agent-skills/tree/main/skills/ghostwriter) structure tells (participle tails, “it’s not X, it’s Y”, “Here’s the thing”), “click here” links, “successfully” toasts, emoji in UI, nominalisations, anthropomorphism, long sentences that chain clauses.
|
|
45
45
|
- **Typography:** straight quotes, three-dot ellipses, hyphens for dashes, `x` for ×, missing space before a unit, light weights on small body text, uppercase without tracking.
|
|
46
46
|
- **Under review:** vague errors, empty states with no action, bare “Confirm” labels, claims with nothing to check, missing error states. These go to [Jev by TypeSafe AI](https://docs.typesafe.ai/introduction), which answers with a probability.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
28 checks block. 125 more report and never fail a run. They are pattern ports nobody has yet watched on a real codebase, and Jev questions no labelled corpus has yet promoted. See [usage](https://blode.co/taste-lint/docs/usage) for how promotion works.
|
|
49
49
|
|
|
50
50
|
Profiles scope a run: `product` (tsx, jsx, css), `writing` (md, mdx, README), `instructions` (AGENTS.md, skills, plans), `all`.
|
|
51
51
|
|
|
52
|
+
Add your own voice with a rule pack: `"rules": ["$GHOSTWRITER_HOME/taste-lint"]` in `taste-lint.config.json` checks drafts against your [ghostwriter](https://github.com/mblode/agent-skills/tree/main/skills/ghostwriter) profile.
|
|
53
|
+
|
|
52
54
|
## Docs
|
|
53
55
|
|
|
54
56
|
<p>
|
package/data/config.schema.json
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
"smartQuotesAtBuild": {
|
|
15
15
|
"type": "boolean"
|
|
16
16
|
},
|
|
17
|
+
"rules": {
|
|
18
|
+
"description": "Extra rule directories, laid out like data/rules (domain folders of YAML rules). Paths resolve from the scan root; a leading ~ and $VARS expand, so a voice pack can live in $GHOSTWRITER_HOME.",
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
17
24
|
"components": {
|
|
18
25
|
"type": "object",
|
|
19
26
|
"additionalProperties": false,
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
{"categoryId":"actionable-cta","id":"tt-x-qc-locate-default-button","kind":"jsx-text","labelSource":"manifest-weak","labels":{"copywriting-cta-names-outcome":true,"copywriting-friction-cta":true},"source":{"id":"qc-locate-default","path":"apps/web/content/exercises/train-your-eye.tsx","repo":"mblode/taste-training","option":"button"},"split":"dev","text":"Submit","context":{"docType":"ui","role":"body"}}
|
|
134
134
|
{"categoryId":"typographic-detail","id":"tt-x-tye-repair-quotes-before","kind":"jsx-text","labelSource":"manifest-weak","labels":{"typography-straight-quotes":true,"typography-dashes":true,"typography-ellipsis":true,"typography-nbsp-value-unit":true},"source":{"id":"tye-repair-quotes","path":"apps/web/content/exercises/train-your-eye.tsx","repo":"mblode/taste-training","option":"before"},"split":"dev","text":"\"Sync everything.\"","context":{"docType":"ui","role":"body"}}
|
|
135
135
|
{"categoryId":"typographic-detail","id":"tt-x-tye-repair-quotes-fixed","kind":"jsx-text","labelSource":"manifest-weak","labels":{"typography-straight-quotes":false,"typography-dashes":false,"typography-ellipsis":false,"typography-nbsp-value-unit":false},"source":{"id":"tye-repair-quotes","path":"apps/web/content/exercises/train-your-eye.tsx","repo":"mblode/taste-training","option":"curly"},"split":"dev","text":"“Sync everything.”","context":{"docType":"ui","role":"body"}}
|
|
136
|
-
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-1","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":
|
|
137
|
-
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-2","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":
|
|
138
|
-
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-3","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":
|
|
139
|
-
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-4","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":
|
|
136
|
+
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-1","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":false},"source":{"id":"SAMPLE_COPY","path":"apps/web/components/demos/copywriting/sweep-runner.tsx","repo":"mblode/taste-training"},"split":"dev","text":"We believe that teams of all sizes deserve powerful, easy to use analytics. Our mission is to help you be more productive and save time and money."}
|
|
137
|
+
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-2","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":false},"source":{"id":"SAMPLE_COPY","path":"apps/web/components/demos/copywriting/sweep-runner.tsx","repo":"mblode/taste-training"},"split":"dev","text":"This is a comprehensive platform that provides seamless integration with all the tools you need. It is a robust solution designed to be flexible and intuitive."}
|
|
138
|
+
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-3","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":true,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":false,"copywriting-machine-prose":true},"source":{"id":"SAMPLE_COPY","path":"apps/web/components/demos/copywriting/sweep-runner.tsx","repo":"mblode/taste-training"},"split":"dev","text":"Whether you're a startup founder or an enterprise leader, our cutting-edge product helps you grow your business and take it to the next level. In order to get started, sign up and create an account."}
|
|
139
|
+
{"categoryId":"evidence-over-claims","context":{"docType":"marketing","role":"body"},"id":"tt-sweep-sample-4","kind":"paragraph","labelSource":"sweep","labels":{"copywriting-dead-weight-hedges":false,"copywriting-generic-audience":true,"copywriting-claim-without-evidence":true,"copywriting-register-shift":true},"source":{"id":"SAMPLE_COPY","path":"apps/web/components/demos/copywriting/sweep-runner.tsx","repo":"mblode/taste-training"},"split":"dev","text":"Furthermore, our innovative approach utilizes next-generation technology to deliver best-in-class results. Contact us to request a demo and see everything you need, and much more."}
|
|
@@ -31,11 +31,11 @@ question:
|
|
|
31
31
|
- TODO
|
|
32
32
|
what: >-
|
|
33
33
|
- Skip files with `// ui-audit-ignore:states-layout-shift`.
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
- Below-the-fold `content-visibility: auto` may CLS-shift inside its own subtree (acceptable).
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
- Skip components declaring `min-height` via CSS class (Tailwind `min-h-*`); inspect class strings first.
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
- Skip Storybook fixtures.
|
|
40
40
|
"true":
|
|
41
41
|
examples:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# Ported from mblode/agent-skills skills/docs-writing/rules/voice-no-anthropomorphism.md
|
|
1
|
+
# Ported from mblode/agent-skills skills/docs-writing/rules/voice-no-anthropomorphism.md, now
|
|
2
|
+
# skills/ghostwriter/references/docs.md after docs-writing was retired.
|
|
2
3
|
id: copywriting-anthropomorphism
|
|
3
4
|
title: Software described as thinking, wanting or knowing
|
|
4
5
|
categoryId: machine-prose
|
|
5
6
|
source:
|
|
6
7
|
{
|
|
7
8
|
repo: mblode/agent-skills,
|
|
8
|
-
path: skills/
|
|
9
|
-
line:
|
|
9
|
+
path: skills/ghostwriter/references/docs.md,
|
|
10
|
+
line: 13,
|
|
10
11
|
}
|
|
11
12
|
unit: [paragraph]
|
|
12
13
|
mechanical:
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# The short denial forms models write ("A folder, not a portal", "Not another
|
|
2
|
+
# place to work."). A regex cannot tell them from a plain instruction ("Use
|
|
3
|
+
# their numbers, not benchmarks") or a clarification ("Unknown: ... Not a
|
|
4
|
+
# pass."), so the regex only finds candidates and Jev decides.
|
|
5
|
+
id: copywriting-antithesis-contrast
|
|
6
|
+
title: Denies a thing nobody claimed to sharpen a point
|
|
7
|
+
categoryId: machine-prose
|
|
8
|
+
source:
|
|
9
|
+
{
|
|
10
|
+
repo: mblode/agent-skills,
|
|
11
|
+
path: skills/ghostwriter/references/tells.md,
|
|
12
|
+
line: 12,
|
|
13
|
+
}
|
|
14
|
+
unit: [paragraph, heading, jsx-text]
|
|
15
|
+
mechanical:
|
|
16
|
+
regex: "[\\p{L}\\p{N}’'], not (?:a |an |the |another |just |only )?[\\p{L}\\p{N}’'-]+|(?:^|[.!?]\\s+)Not (?:a|an|another|the|just|only|one) [\\p{L}\\p{N}’'-]+"
|
|
17
|
+
flags: gu
|
|
18
|
+
question:
|
|
19
|
+
context: [docType, element]
|
|
20
|
+
instructions: >-
|
|
21
|
+
Does TEXT deny something that nobody in the text claimed, only to make its own point sound sharper? Return false when the contrast is an instruction choosing between two real options the reader has, when it corrects a misreading the reader is likely to make, or when it states a real trade-off or limit.
|
|
22
|
+
criteria:
|
|
23
|
+
true:
|
|
24
|
+
what: A denial of a straw alternative, used as rhetoric.
|
|
25
|
+
examples:
|
|
26
|
+
- "A folder, not a portal."
|
|
27
|
+
- "A shared folder. Not another place to work."
|
|
28
|
+
- "Conflicts are kept, not resolved for you."
|
|
29
|
+
- "Side by side, not overwritten."
|
|
30
|
+
false:
|
|
31
|
+
what: A contrast the reader can act on or would otherwise get wrong.
|
|
32
|
+
examples:
|
|
33
|
+
- "Use their numbers, not generic benchmarks."
|
|
34
|
+
- "Unknown: a check that could not decide. Not a pass."
|
|
35
|
+
- "Put links in the comments, not the post body."
|
|
36
|
+
- "Billed per seat, not per device."
|
|
37
|
+
thresholds: { review: 0.4, act: 0.75 }
|
|
38
|
+
severity: minor
|
|
39
|
+
fix:
|
|
40
|
+
hint: Say the positive thing. "A folder, not a portal" becomes "A folder on every laptop."
|
|
41
|
+
preconditions: { notInCode: true }
|
|
42
|
+
status: review-only
|
|
43
|
+
handWritten: [mechanical, question, fix, preconditions, status]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# The short denial forms ("A folder, not a portal") need a judge, so they live
|
|
2
|
+
# in copywriting-antithesis-contrast; this rule stays exact enough to be active.
|
|
3
|
+
# Hand-cut from mblode/agent-skills skills/ghostwriter/references/tells.md (structure tells).
|
|
4
|
+
id: copywriting-antithesis-frame
|
|
5
|
+
title: Sets up a claim by denying one nobody made
|
|
6
|
+
categoryId: machine-prose
|
|
7
|
+
source:
|
|
8
|
+
{
|
|
9
|
+
repo: mblode/agent-skills,
|
|
10
|
+
path: skills/ghostwriter/references/tells.md,
|
|
11
|
+
line: 12,
|
|
12
|
+
}
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
|
+
mechanical:
|
|
15
|
+
regex: "(?<![\"“‘'])\\bnot (?:just|only|merely) (?:a |an |the |about )?[\\w-]+(?: [\\w-]+){0,4}(?:[,;]? but|[,;] (?:it|they|we|this)(?:['’](?:s|re)| is| are))\\b|(?<![\"“‘'])\\b(?:it|this|that)(?:['’]s| is)(?: not|n['’]t) (?:just |only )?(?:about )?[^.!?,;]{1,40}[.,;] (?:it|this|that)(?:['’]s| is)\\b"
|
|
16
|
+
flags: giu
|
|
17
|
+
severity: minor
|
|
18
|
+
fix:
|
|
19
|
+
hint: >-
|
|
20
|
+
Say the positive thing. "It's not a tool, it's a workflow" becomes "It's a workflow."
|
|
21
|
+
preconditions: { notInCode: true }
|
|
22
|
+
status: active
|
|
23
|
+
handWritten: [mechanical, fix, preconditions]
|
|
@@ -10,7 +10,10 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: artificial_balance
|
|
13
|
-
|
|
13
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
14
|
+
# words is a label, button or greeting, not prose.
|
|
15
|
+
unit: [paragraph, jsx-text]
|
|
16
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
14
17
|
question:
|
|
15
18
|
context: [docType]
|
|
16
19
|
instructions: >-
|
|
@@ -10,7 +10,7 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: boilerplate_offer
|
|
13
|
-
unit: [paragraph]
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
14
|
mechanical:
|
|
15
15
|
flags: gui
|
|
16
16
|
regex: "let me know|feel free to|happy to help|hope this helps|if you have any (?:other |further )?questions|reach out if"
|
|
@@ -10,7 +10,7 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: canned_phrasing
|
|
13
|
-
unit: [paragraph]
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
14
|
mechanical:
|
|
15
15
|
phrases:
|
|
16
16
|
- in today's fast-paced world
|
|
@@ -30,10 +30,17 @@ mechanical:
|
|
|
30
30
|
- next-generation
|
|
31
31
|
- enterprise-grade
|
|
32
32
|
- blazing
|
|
33
|
+
- faster
|
|
34
|
+
- fastest
|
|
35
|
+
- simpler
|
|
36
|
+
- easier
|
|
37
|
+
- smarter
|
|
38
|
+
- trusted by
|
|
39
|
+
- loved by
|
|
33
40
|
question:
|
|
34
41
|
context: [headingAbove, docType]
|
|
35
42
|
instructions: >-
|
|
36
|
-
Read the TEXT. Return true when the TEXT describes a product, feature, service or company with an evaluative adjective or adverb (for example powerful, seamless, fast, intuitive, robust, best-in-class, simple) and the same TEXT gives the reader nothing they could check: no
|
|
43
|
+
Read the TEXT. Return true when the TEXT describes a product, feature, service or company with an evaluative adjective or adverb (for example powerful, seamless, fast, intuitive, robust, best-in-class, simple) and the same TEXT gives the reader nothing they could check: no measurement with its conditions, no named mechanism that explains how, no named standard, customer or third party. A rounded boast is a claim, not evidence: a user count ("10,000+ teams"), an "up to" multiplier, or "trusted by" with no names. Return false when the TEXT contains such a checkable fact, when the adjective names a category a reader can verify (open source, end-to-end encrypted, GDPR compliant), or when the TEXT is not describing a product at all (instructions, narrative, a lesson explaining the rule). Ignore code, URLs and the HEADING line; judge only the TEXT.
|
|
37
44
|
criteria:
|
|
38
45
|
true:
|
|
39
46
|
what: A quality is asserted about a product and nothing in the text lets the reader check it.
|
|
@@ -42,6 +49,8 @@ question:
|
|
|
42
49
|
- "Enterprise-grade security, built for teams that take compliance seriously."
|
|
43
50
|
- "Our blazing-fast platform simply supports parallel execution."
|
|
44
51
|
- "Seamless sync across all your devices."
|
|
52
|
+
- "Loved by 50,000+ designers worldwide."
|
|
53
|
+
- "Up to 5x faster builds."
|
|
45
54
|
false:
|
|
46
55
|
what: The text names what happens, how much, or by which standard, or is not selling anything.
|
|
47
56
|
examples:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
# Ported from mblode/agent-skills skills/docs-writing/rules/format-descriptive-links.md
|
|
1
|
+
# Ported from mblode/agent-skills skills/docs-writing/rules/format-descriptive-links.md, now
|
|
2
|
+
# skills/ghostwriter/references/docs.md after docs-writing was retired. and taste-training
|
|
2
3
|
# full-stack-audit.tsx (click-here).
|
|
3
4
|
id: copywriting-click-here-links
|
|
4
5
|
title: Link text that names nothing
|
|
@@ -6,8 +7,8 @@ categoryId: actionable-cta
|
|
|
6
7
|
source:
|
|
7
8
|
{
|
|
8
9
|
repo: mblode/agent-skills,
|
|
9
|
-
path: skills/
|
|
10
|
-
line:
|
|
10
|
+
path: skills/ghostwriter/references/docs.md,
|
|
11
|
+
line: 21,
|
|
11
12
|
}
|
|
12
13
|
unit: [jsx-text]
|
|
13
14
|
mechanical:
|
|
@@ -3,8 +3,8 @@ title: An instruction hides its prerequisite after the action
|
|
|
3
3
|
categoryId: page-structure
|
|
4
4
|
source:
|
|
5
5
|
repo: mblode/agent-skills
|
|
6
|
-
path: skills/
|
|
7
|
-
line:
|
|
6
|
+
path: skills/ghostwriter/references/docs.md
|
|
7
|
+
line: 15
|
|
8
8
|
ruleId: structure-conditions-first
|
|
9
9
|
unit:
|
|
10
10
|
- paragraph
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Hand-cut from mblode/agent-skills skills/ghostwriter/references/tells.md (structure tells).
|
|
2
|
+
id: copywriting-copula-avoidance
|
|
3
|
+
title: Dresses up "is" or "has"
|
|
4
|
+
categoryId: machine-prose
|
|
5
|
+
source:
|
|
6
|
+
{
|
|
7
|
+
repo: mblode/agent-skills,
|
|
8
|
+
path: skills/ghostwriter/references/tells.md,
|
|
9
|
+
line: 13,
|
|
10
|
+
}
|
|
11
|
+
unit: [paragraph, jsx-text]
|
|
12
|
+
mechanical:
|
|
13
|
+
regex: "(?<![\"“‘'])\\b(?:serves|stands) as (?:a|an|the)\\b|(?<![\"“‘'])\\bboasts\\b"
|
|
14
|
+
flags: giu
|
|
15
|
+
severity: minor
|
|
16
|
+
fix:
|
|
17
|
+
hint: >-
|
|
18
|
+
Write "is" or "has". "The index serves as a cache" becomes "The index is a cache."
|
|
19
|
+
preconditions: { notInCode: true }
|
|
20
|
+
status: review-only
|
|
21
|
+
handWritten: [mechanical, fix, preconditions]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Four of five models wrote href="#" on nav and CTA links (2026-09-23 generation
|
|
2
|
+
# test). Demo and registry files use it on purpose, so they are excluded.
|
|
3
|
+
id: copywriting-dead-link
|
|
4
|
+
title: Link with nowhere to go
|
|
5
|
+
categoryId: page-structure
|
|
6
|
+
source:
|
|
7
|
+
repo: mblode/agent-skills
|
|
8
|
+
path: skills/ui-design/rules/nav-semantic-links.md
|
|
9
|
+
line: 10
|
|
10
|
+
ruleId: nav-semantic-links
|
|
11
|
+
scope:
|
|
12
|
+
include: ["**/*.tsx", "**/*.jsx"]
|
|
13
|
+
exclude: ["**/examples/**", "**/registry/**", "**/*demo*"]
|
|
14
|
+
unit: [source]
|
|
15
|
+
mechanical:
|
|
16
|
+
regex: href=["']#["']
|
|
17
|
+
flags: gu
|
|
18
|
+
severity: major
|
|
19
|
+
fix:
|
|
20
|
+
hint: Point the link at the route its label names. If there is none yet, it is not a link; cut it or make it a button that does the thing.
|
|
21
|
+
status: review-only
|
|
22
|
+
review:
|
|
23
|
+
applicability: "A link or CTA whose href is a bare #."
|
|
24
|
+
exceptions: "Placeholder links in a demo, story or registry example."
|
|
25
|
+
evidence: "The link label and where the product actually sends that action."
|
|
26
|
+
verification: "Confirm the label names a real destination, then point the link there or remove it."
|
|
27
|
+
sourceHash: 156607414ec8464ecc9e40d0441c9242ec94547f5de51ed83d04d6333c305470
|
|
28
|
+
handWritten: [mechanical]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Fable and Grok put em dashes in interface copy when every other tell was gone
|
|
2
|
+
# (2026-09-23 generation test). Unspaced em dashes are accepted (Matthew,
|
|
3
|
+
# 2026-09-24), matching typography-audit; a full or no-break space either side
|
|
4
|
+
# is the tell. Hair and thin spaces are allowed (spacing-hair-thin-spaces).
|
|
5
|
+
id: copywriting-em-dash-in-ui
|
|
6
|
+
title: Spaced em dash in interface copy
|
|
7
|
+
categoryId: machine-prose
|
|
8
|
+
source:
|
|
9
|
+
{
|
|
10
|
+
repo: mblode/agent-skills,
|
|
11
|
+
path: skills/typography-audit/rules/punct-dashes.md,
|
|
12
|
+
line: 7,
|
|
13
|
+
}
|
|
14
|
+
unit: [jsx-text, attr-string]
|
|
15
|
+
mechanical:
|
|
16
|
+
regex: "[ \\u00a0]\\u2014|\\u2014[ \\u00a0]"
|
|
17
|
+
flags: gu
|
|
18
|
+
severity: minor
|
|
19
|
+
fix:
|
|
20
|
+
hint: Close up the em dash ("fast\u2014and safe"), or use a comma, full stop or parentheses.
|
|
21
|
+
preconditions: { notInCode: true, docType: [ui, unknown] }
|
|
22
|
+
status: review-only
|
|
23
|
+
handWritten: [mechanical, fix, preconditions, status]
|
|
@@ -3,8 +3,8 @@ title: An error explanation provides no recovery guidance
|
|
|
3
3
|
categoryId: reference-reading
|
|
4
4
|
source:
|
|
5
5
|
repo: mblode/agent-skills
|
|
6
|
-
path: skills/
|
|
7
|
-
line:
|
|
6
|
+
path: skills/ghostwriter/references/docs.md
|
|
7
|
+
line: 19
|
|
8
8
|
ruleId: code-error-descriptions
|
|
9
9
|
unit:
|
|
10
10
|
- paragraph
|
|
@@ -10,7 +10,7 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: excessive_hedging
|
|
13
|
-
unit: [paragraph]
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
14
|
mechanical:
|
|
15
15
|
phrases:
|
|
16
16
|
- might
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Hand-ported: the upstream rg pattern, narrowed to JSX.
|
|
2
|
+
id: copywriting-external-arrow-internal-link
|
|
3
|
+
title: External-link arrow on a link that stays on the site
|
|
4
|
+
categoryId: page-structure
|
|
5
|
+
source:
|
|
6
|
+
repo: mblode/agent-skills
|
|
7
|
+
path: skills/ui-design/rules/slop-external-arrow-internal-link.md
|
|
8
|
+
line: 10
|
|
9
|
+
ruleId: slop-external-arrow-internal-link
|
|
10
|
+
scope:
|
|
11
|
+
include: ["**/*.tsx", "**/*.jsx"]
|
|
12
|
+
unit: [source]
|
|
13
|
+
mechanical:
|
|
14
|
+
regex: '<(?:(?:a|Link)\b(?![^>]*target="_blank")(?:=>|[^>])*\bhref="(?:#|/(?!/))[^"]*"|button\b)(?:=>|[^>])*>(?:(?!</(?:a|Link|button)>)[\s\S]){0,300}?(?:↗|↗|↗|ArrowUpRight|ExternalLink)'
|
|
15
|
+
flags: gu
|
|
16
|
+
severity: minor
|
|
17
|
+
fix:
|
|
18
|
+
hint: Remove the arrow. It promises a new site or tab; keep it for links that leave.
|
|
19
|
+
status: review-only
|
|
20
|
+
review:
|
|
21
|
+
applicability: "An up-right arrow sits on a link to a route or anchor on the same site."
|
|
22
|
+
exceptions: "A link that opens a new tab or another site, where the arrow is true."
|
|
23
|
+
evidence: "The href and target of the link carrying the arrow."
|
|
24
|
+
verification: "Confirm the link stays on the site, then remove the arrow."
|
|
25
|
+
sourceHash: 526719aa2eb755c1615c9100e8c9e433ddfd2965270148b94f1a66841f5f47d4
|
|
26
|
+
handWritten: [mechanical]
|
|
@@ -10,11 +10,14 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: fabricated_specificity
|
|
13
|
-
|
|
13
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
14
|
+
# words is a label, button or greeting, not prose.
|
|
15
|
+
unit: [paragraph, jsx-text]
|
|
16
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
14
17
|
question:
|
|
15
|
-
context: [docType]
|
|
18
|
+
context: [docType, brief]
|
|
16
19
|
instructions: >-
|
|
17
|
-
Does the writing include suspiciously specific details without grounding or attribution? Ignore a number, name or date the passage attributes to a source, a test, or the writer
|
|
20
|
+
Does the writing include suspiciously specific details without grounding or attribution? Ignore a number, name or date the passage attributes to a source, a test, or the writer’s own measurement. When BRIEF is given, it is the product’s own source of truth: a detail BRIEF states is grounded, and a specific that BRIEF does not state is ungrounded however plainly it is written.
|
|
18
21
|
criteria:
|
|
19
22
|
true:
|
|
20
23
|
what: Precise-sounding detail with no source in sight.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-defaults.md
|
|
1
|
+
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-defaults.md, now
|
|
2
|
+
# skills/ghostwriter/references/docs.md after docs-writing was retired. (cut filler) and the lesson
|
|
2
3
|
# edit-one-lens-at-a-time.mdx.
|
|
3
4
|
id: copywriting-filler-words
|
|
4
5
|
title: Intensifiers and filler that add nothing
|
|
@@ -6,8 +7,8 @@ categoryId: machine-prose
|
|
|
6
7
|
source:
|
|
7
8
|
{
|
|
8
9
|
repo: mblode/agent-skills,
|
|
9
|
-
path: skills/
|
|
10
|
-
line:
|
|
10
|
+
path: skills/ghostwriter/references/docs.md,
|
|
11
|
+
line: 17,
|
|
11
12
|
}
|
|
12
13
|
unit: [paragraph, jsx-text]
|
|
13
14
|
mechanical:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# One of the 21 AI-writing checks Every ran with Jev (Mike Taylor, "Jev judged
|
|
2
|
-
# everything I've written in 0.7 seconds", 15 Sep 2026). The question
|
|
3
|
-
# published wording;
|
|
2
|
+
# everything I've written in 0.7 seconds", 15 Sep 2026). The question started as
|
|
3
|
+
# the published wording; it now also says what cannot be a violation, because
|
|
4
|
+
# Jev read plain labels, identifiers and instructions as violations (2026-09-23 eval).
|
|
4
5
|
# Candidate list from EveryInc/compound-writing skills/cw-ai-check/SKILL.md, category 1 (generic frames and openers).
|
|
5
6
|
id: copywriting-generic-framing
|
|
6
7
|
title: Relies on generic framing instead of concrete context
|
|
@@ -10,22 +11,27 @@ source:
|
|
|
10
11
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
12
|
line: 1
|
|
12
13
|
ruleId: generic_framing
|
|
13
|
-
|
|
14
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
15
|
+
# words is a label, button or greeting, not prose.
|
|
16
|
+
unit: [paragraph, jsx-text]
|
|
17
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
14
18
|
question:
|
|
15
19
|
context: [docType]
|
|
16
20
|
instructions: >-
|
|
17
|
-
Does the writing
|
|
21
|
+
Does the writing open or connect with a generic frame, a sentence that sets up the topic with claims that would fit any topic, instead of concrete context? Only a sentence that frames can be generic framing. Answer no for text that frames nothing: labels, names, headings, identifiers, URLs, placeholders, instructions to the reader, and plain statements of fact or opinion. Short or unspecific is not the same as generic. Ignore a first sentence that names a specific product, person, number, place or date.
|
|
18
22
|
criteria:
|
|
19
23
|
true:
|
|
20
|
-
what: A frame
|
|
24
|
+
what: A setup sentence whose frame would fit any topic.
|
|
21
25
|
examples:
|
|
22
26
|
- "In today's fast-paced world, teams need tools that help them move quickly and stay aligned."
|
|
23
27
|
- "When it comes to onboarding, first impressions matter more than ever."
|
|
24
28
|
false:
|
|
25
|
-
what: A frame anchored in the specific situation.
|
|
29
|
+
what: A frame anchored in the specific situation, or text that does no framing at all.
|
|
26
30
|
examples:
|
|
27
31
|
- "Our onboarding email had a 9 percent click rate in August; the one paragraph we changed took it to 21."
|
|
28
32
|
- "Three of the five new hires this quarter could not find the deploy runbook in their first week."
|
|
33
|
+
- "Run commands from the repository root:"
|
|
34
|
+
- "Pricing"
|
|
29
35
|
thresholds: { review: 0.4, act: 0.75 }
|
|
30
36
|
severity: minor
|
|
31
37
|
fix:
|
|
@@ -10,22 +10,30 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: generic_language
|
|
13
|
-
|
|
13
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
14
|
+
# words is a label, button or greeting, not prose.
|
|
15
|
+
unit: [paragraph, jsx-text]
|
|
16
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
14
17
|
question:
|
|
15
18
|
context: [docType]
|
|
16
19
|
instructions: >-
|
|
17
|
-
|
|
20
|
+
Could TEXT sit unchanged on a competitor's page and stay just as true? Return true when it could because its words are abstract (platform, solution, experience, empower, seamless, value, teamwork, flow) instead of naming what this product does. Return false when TEXT names a specific object, action, number, limit or place, however short, and for instructions, validation messages, greetings, sign-offs, labels, and titles or headings that say what a page or section contains. A slogan headline is judged like any sentence.
|
|
18
21
|
criteria:
|
|
19
22
|
true:
|
|
20
23
|
what: Words that could describe any product or team.
|
|
21
24
|
examples:
|
|
22
25
|
- "Our robust, seamless platform empowers teams to unlock powerful insights and drive meaningful outcomes."
|
|
23
26
|
- "This comprehensive approach leverages best practices to deliver value at scale."
|
|
27
|
+
- "Collaboration, Elevated for the Modern Team"
|
|
28
|
+
- "Less busywork. More momentum."
|
|
24
29
|
false:
|
|
25
|
-
what: Words that name the thing.
|
|
30
|
+
what: Words that name the thing, however plainly.
|
|
26
31
|
examples:
|
|
27
32
|
- "The sync engine merges edits made offline without overwriting anyone else, so field staff can work in basements and tunnels."
|
|
28
|
-
- "
|
|
33
|
+
- "Free for up to 3 projects. No card needed."
|
|
34
|
+
- "Use 60 characters or fewer."
|
|
35
|
+
- "Where your files live, and what happens when you go offline."
|
|
36
|
+
- "How Northwind merges two offline edits"
|
|
29
37
|
thresholds: { review: 0.4, act: 0.75 }
|
|
30
38
|
severity: minor
|
|
31
39
|
fix:
|
|
@@ -10,7 +10,7 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: generic_validation
|
|
13
|
-
unit: [paragraph]
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
14
|
mechanical:
|
|
15
15
|
phrases:
|
|
16
16
|
- great question
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-no-latin.md
|
|
1
|
+
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-no-latin.md, now
|
|
2
|
+
# skills/ghostwriter/references/docs.md after docs-writing was retired. Review-only:
|
|
3
|
+
# ghostwriter keeps "plain words" but no longer bans Latin abbreviations outright.
|
|
2
4
|
id: copywriting-latinisms
|
|
3
5
|
title: Latin abbreviation in running text
|
|
4
6
|
categoryId: reader-first-framing
|
|
5
7
|
source:
|
|
6
8
|
{
|
|
7
9
|
repo: mblode/agent-skills,
|
|
8
|
-
path: skills/
|
|
9
|
-
line:
|
|
10
|
+
path: skills/ghostwriter/references/docs.md,
|
|
11
|
+
line: 17,
|
|
10
12
|
}
|
|
11
13
|
unit: [paragraph, heading]
|
|
12
14
|
mechanical:
|
|
@@ -21,5 +23,5 @@ preconditions:
|
|
|
21
23
|
docType:
|
|
22
24
|
[tutorial, howto, reference, explanation, agent-instructions, lesson],
|
|
23
25
|
}
|
|
24
|
-
status:
|
|
26
|
+
status: review-only
|
|
25
27
|
handWritten: [mechanical, fix, preconditions]
|
|
@@ -42,11 +42,14 @@ mechanical:
|
|
|
42
42
|
tapestry,
|
|
43
43
|
testament to,
|
|
44
44
|
it's important to remember,
|
|
45
|
+
here is some,
|
|
46
|
+
here's some,
|
|
47
|
+
here are some,
|
|
45
48
|
]
|
|
46
49
|
question:
|
|
47
50
|
context: []
|
|
48
51
|
instructions: >-
|
|
49
|
-
Read the TEXT. Return true when it reads as generated boilerplate: a chatbot opener ("I'd be happy to", "Let's dive in"), a vague attribution ("experts believe", "studies show") with no source, a stock flourish ("game-changer", "unlock the power of", "in today's fast-paced world", "a testament to"), or a symmetrical both-sides summary that adds no information. Return false when the TEXT makes a specific claim with a named source, gives an instruction, tells a concrete story, or quotes these phrases in order to warn against them.
|
|
52
|
+
Read the TEXT. Return true when it reads as generated boilerplate: a chatbot opener or preamble ("I'd be happy to", "Let's dive in", "Here is some copy for"), a vague attribution ("experts believe", "studies show") with no source, a stock flourish ("game-changer", "unlock the power of", "in today's fast-paced world", "a testament to"), a sentence that announces its own importance instead of making the point ("here is where it gets interesting", "the implications are significant"), a false contrast that denies a claim nobody made ("not just a tool, it is a way of working"), or a symmetrical both-sides summary that adds no information. Return false when the TEXT makes a specific claim with a named source, gives an instruction, tells a concrete story, or quotes these phrases in order to warn against them.
|
|
50
53
|
criteria:
|
|
51
54
|
true:
|
|
52
55
|
what: Generated boilerplate.
|
|
@@ -8,7 +8,10 @@ source:
|
|
|
8
8
|
repo: every.to
|
|
9
9
|
path: https://every.to/also-true-for-humans/mini-vibe-check-typesafe-s-jev-judged-everything-i-ve-written-in-0-7-seconds
|
|
10
10
|
line: 1
|
|
11
|
-
|
|
11
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
12
|
+
# words is a label, button or greeting, not prose.
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
12
15
|
question:
|
|
13
16
|
context: [docType]
|
|
14
17
|
instructions: >-
|
|
@@ -10,7 +10,7 @@ source:
|
|
|
10
10
|
path: https://typesafe-parallel-judgment-lab.every-4573.chatgpt.site/downloads/typesafe-lab-source.zip (scripts/run-ai-checker.mjs)
|
|
11
11
|
line: 1
|
|
12
12
|
ruleId: meta_commentary
|
|
13
|
-
unit: [paragraph]
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
14
|
mechanical:
|
|
15
15
|
phrases:
|
|
16
16
|
- in this section
|
|
@@ -8,7 +8,10 @@ source:
|
|
|
8
8
|
repo: every.to
|
|
9
9
|
path: https://every.to/also-true-for-humans/mini-vibe-check-typesafe-s-jev-judged-everything-i-ve-written-in-0-7-seconds
|
|
10
10
|
line: 1
|
|
11
|
-
|
|
11
|
+
# Prose wherever it is: a Markdown paragraph or a JSX sentence. Under five
|
|
12
|
+
# words is a label, button or greeting, not prose.
|
|
13
|
+
unit: [paragraph, jsx-text]
|
|
14
|
+
preconditions: { minWords: 5, notInCode: true }
|
|
12
15
|
question:
|
|
13
16
|
context: [docType]
|
|
14
17
|
instructions: >-
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-no-nominalizations.md
|
|
1
|
+
# Ported from mblode/agent-skills skills/docs-writing/rules/clarity-no-nominalizations.md, now
|
|
2
|
+
# skills/ghostwriter/references/docs.md after docs-writing was retired.
|
|
2
3
|
id: copywriting-nominalisations
|
|
3
4
|
title: Verb buried in a noun
|
|
4
5
|
categoryId: reader-first-framing
|
|
5
6
|
source:
|
|
6
7
|
{
|
|
7
8
|
repo: mblode/agent-skills,
|
|
8
|
-
path: skills/
|
|
9
|
-
line:
|
|
9
|
+
path: skills/ghostwriter/references/docs.md,
|
|
10
|
+
line: 17,
|
|
10
11
|
}
|
|
11
12
|
unit: [paragraph, jsx-text]
|
|
12
13
|
mechanical:
|