@heyramzi/lint 1.34.0 → 2.1.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 +52 -5
- package/bin/heyramzi-comment-length.js +5 -6
- package/bin/heyramzi-file-tokens.js +2 -2
- package/bin/heyramzi-old-names.js +72 -0
- package/bin/heyramzi-push-diff.js +16 -5
- package/bin/heyramzi-slop.js +3 -1
- package/bin/heyramzi-ui-rules.js +18 -9
- package/comment-length.js +18 -12
- package/config/oxlintrc.json +3 -4
- package/data/jev-code-bench.js +5 -5
- package/data/slop/artifacts.js +87 -0
- package/data/slop/pattern-parts.js +56 -0
- package/data/slop/patterns/appositives-and-sayings.js +81 -0
- package/data/slop/patterns/comma-clauses.js +106 -0
- package/data/slop/patterns/diagram-and-script-tells.js +71 -0
- package/data/slop/patterns/dropped-elements.js +149 -0
- package/data/slop/patterns/duration-and-openers.js +102 -0
- package/data/slop/patterns/headings-and-laws.js +116 -0
- package/data/slop/patterns/numbers-and-runs.js +94 -0
- package/data/slop/patterns/openers-and-tails.js +90 -0
- package/data/slop/patterns/parallel-shapes.js +100 -0
- package/data/slop/patterns/reveals-and-tallies.js +101 -0
- package/data/slop/patterns/setups-and-closers.js +83 -0
- package/data/slop/patterns/subject-voice.js +67 -0
- package/data/slop/patterns/vague-and-flourish.js +102 -0
- package/data/slop/patterns/wikipedia-signs.js +103 -0
- package/data/slop/patterns.js +62 -0
- package/data/slop/phrases-en-closers.js +66 -0
- package/data/slop/phrases-en-core.js +185 -0
- package/data/slop/phrases-en-wikipedia.js +136 -0
- package/data/slop/phrases-fr.js +152 -0
- package/data/slop/shapes-density.js +81 -0
- package/data/slop/shapes-framing.js +101 -0
- package/data/slop/shapes-reveals.js +88 -0
- package/data/slop/title-tells.js +8 -0
- package/data/slop/words-en.js +178 -0
- package/data/slop/words-fr.js +101 -0
- package/data/slop-words.js +18 -2576
- package/data/vendor/heading-words.js +6 -0
- package/file-tokens.js +26 -13
- package/index.js +4 -6
- package/inline-path.js +2 -0
- package/jev-code.js +1 -1
- package/old-names-mask.js +206 -0
- package/old-names.js +145 -0
- package/package.json +8 -2
- package/push-diff.js +2 -2
- package/repo-rules/packages.js +5 -5
- package/repo-rules.js +2 -2
- package/rules/convex-index-name.js +3 -1
- package/rules/convex-internal-only.js +2 -1
- package/rules/{convex-no-deprecated-crons.js → convex-no-hourly-daily-crons.js} +8 -7
- package/rules/convex-runtime-imports.js +2 -1
- package/rules/{no-legacy-compat.js → no-underscore-reexport.js} +1 -1
- package/slop.js +4 -2
- package/ui-rules-css.js +221 -0
- package/ui-rules-markup.js +130 -0
- package/ui-rules.js +98 -25
- package/vendor/old-names-words.js +9 -0
- package/rules/no-relative-type-imports.js +0 -40
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Patterns for vague stand-ins, named feelings and flourish endings. */
|
|
2
|
+
export const patternsVagueAndFlourish = [
|
|
3
|
+
{
|
|
4
|
+
id: "indefinite-stand-in",
|
|
5
|
+
name: "Indefinite stand-in",
|
|
6
|
+
// Bounded to the temporal and existential frames, where the indefinite is standing in for a named thing.
|
|
7
|
+
|
|
8
|
+
// An ordinary "anything else you need" is a real quantifier over a real set and passes.
|
|
9
|
+
test: /\b(?:before|until|when|while)\s+(?:anything|nothing|anyone|no\s?one|nobody|something|someone)\s+(?:exists|existed|is\s+(?:built|written|made|there|real)|happens|happened|has\s+happened)\b/i,
|
|
10
|
+
rule: "An indefinite pronoun standing where the thing belongs. It sounds absolute and costs no research, and the reader cannot picture it. Name what does not exist yet: the site, the quote, the first invoice.",
|
|
11
|
+
example:
|
|
12
|
+
'"A quote to sign before anything exists." / "You commit before anything is built." / "They bill you when nothing has happened."',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "recap-opener",
|
|
16
|
+
name: "Recap opener",
|
|
17
|
+
// Bounded to recap verbs, because "Now that you have a big enough audience" is a real condition Ramzi says out loud.
|
|
18
|
+
|
|
19
|
+
// From Vercel's writing guidelines, 13 Oct 2026; its two hits in our corpus were both other people's YouTube scripts.
|
|
20
|
+
test: /(?:^|[.!?]["”]?\s+)(?:Now that (?:we|you)(?:'ve|’ve| have)? (?:covered|seen|explored|learned|understand|know|set up|looked at|gone through|walked through|built)|With (?:this|that|these|everything|all (?:of )?(?:this|that))\b[^.,]{0,30}\b(?:in place|complete|done|set up|out of the way|covered|sorted|settled)|Having (?:covered|set up|explored|looked at|seen|established|walked through)|(?:So far|Up to now|By now),? (?:we|you)(?:'ve|’ve| have))\b/i,
|
|
21
|
+
rule: "A sentence that opens by summing up what the reader just read. They were there. Start on the next point.",
|
|
22
|
+
example:
|
|
23
|
+
'"Now that you understand the mechanics, the next question is which platform." / "With this setup complete, deploy the worker." / "Having covered the basics, we move to pricing." / "So far we\'ve built the form."',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "named-feeling",
|
|
27
|
+
name: "Named feeling",
|
|
28
|
+
// The label form only, where the copula does all the work. A feeling verb acting on something ("it frustrates the client every Friday") carries a fact and passes.
|
|
29
|
+
|
|
30
|
+
// Zero hits across his 60,000 words, counted 10 Sep 2026.
|
|
31
|
+
test: /\b(?:i\s+(?:was|am|felt|feel)\s+|it\s+(?:was|is)\s+(?:a\s+|an\s+)?)(?:truly\s+|really\s+|genuinely\s+)?(?:frustrated|frustrating|exciting|excited|thrilled|thrilling|humbling|humbled|proud|amazed|amazing|grateful|overwhelmed|overwhelming|inspiring|inspired|rewarding|incredible|surreal)\b/i,
|
|
32
|
+
rule: "The emotion named where its cause belongs. Delete the label and write the thing that caused it, and the reader supplies the word themselves. Never invent a cause to fill the gap: go and get the real one, or cut the sentence.",
|
|
33
|
+
example: '"I was frustrated." / "It was exciting." / "It was a humbling experience."',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "clean-resolution",
|
|
37
|
+
name: "Clean resolution",
|
|
38
|
+
// The closing formulas only. The diagnosis behind them, a piece with no
|
|
39
|
+
// loose ends left, is `clean-resolution` in `shapes` and needs a read.
|
|
40
|
+
test: /\b(?:that(?:(?:'|’)?s| is| was) when it (?:all )?clicked|looking back,? i (?:would ?n(?:'|’)?t|would not) change|the lesson here is|that(?:(?:'|’)?s| is) the (?:real )?lesson|i\s+could ?n(?:'|’)?t believe what i was)\b/i,
|
|
41
|
+
rule: "A closing formula that ties every thread off and states the takeaway. It is the sound of a piece built backwards from its conclusion. End on what is still open, or on the fact, and let the reader draw the lesson.",
|
|
42
|
+
example:
|
|
43
|
+
'"And that is when it all clicked." / "Looking back, I would not change a thing." / "The lesson here is simple."',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "vague-magnitude",
|
|
47
|
+
name: "Vague magnitude",
|
|
48
|
+
// A magnitude word standing in for a count. Zero hits across his 60,000 words, counted 10 Sep 2026, so the shape is nobody's ordinary English here.
|
|
49
|
+
|
|
50
|
+
// `hundreds of thousands of X` is excluded: it modifies a real scale.
|
|
51
|
+
test: /\bcountless\s+[a-z]|(?<!\bof\s)\b(?:hundreds|thousands|millions|dozens|scores)\s+of\s+(?!thousands\b|millions\b)[a-z]/i,
|
|
52
|
+
rule: "A magnitude word where a count belongs. It sounds measured and proves nothing, and it costs the writer no research. Write the figure, ugly as it is: 213 hours, 47 clients. If there is no figure, drop the claim.",
|
|
53
|
+
example:
|
|
54
|
+
'"hundreds of hours saved" / "thousands of dollars a month" / "countless integrations"',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "comma-not-pivot",
|
|
58
|
+
name: "Comma pivot",
|
|
59
|
+
// The `negation-pivot` shape was review-only because "is not" is ordinary English.
|
|
60
|
+
|
|
61
|
+
// The comma form is not: a clause, a comma, then `not` plus a determiner is the swap every time, so it is decidable and it fails.
|
|
62
|
+
test: /,\s+(?:and\s+|but\s+)?not\s+(?:a|an|the|just|only|your|our|my|its|his|her|their|this|that)(?![\p{L}\p{N}_])/iu,
|
|
63
|
+
rule: "A line that lands on what the thing is not. The half after the comma exists to make the half before it sound earned, and the reader is handed a contrast where an instruction was due. Write the instruction alone: what it is not is implied by what it is.",
|
|
64
|
+
example:
|
|
65
|
+
'"Open on the state they want, not the app." / "Sell the change, not the tool." / "Write the mechanism, not the slogan."',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "comma-not-pivot-fr",
|
|
69
|
+
name: "Comma pivot (FR)",
|
|
70
|
+
test: /,\s+(?:et\s+)?pas\s+(?:(?:un|une|le|la|les|de|votre|vos|ton|ta|tes|son|sa|ses)(?![\p{L}\p{N}_])|[ld]['’])/iu,
|
|
71
|
+
rule: "La version française du pivot: une virgule, puis ce que la chose n'est pas. Écrire l'instruction seule.",
|
|
72
|
+
example: '"Vendez le changement, pas l\'outil."',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "singular-count-flourish",
|
|
76
|
+
name: "The one X in this Y",
|
|
77
|
+
// `the one <noun>`, a preposition, then a deictic `this` or `that`, which is always the writer pointing at their own artefact.
|
|
78
|
+
|
|
79
|
+
// Bounded to one noun on each side so it cannot reach across a clause.
|
|
80
|
+
|
|
81
|
+
// `the` is deliberately not accepted in the second slot: "the one file in the folder" is a fact about the world and stays legal,
|
|
82
|
+
|
|
83
|
+
// while "in this" and "in that" only ever grade the thing the writer just made.
|
|
84
|
+
test: /\bthe one\s+[a-zà-ÿ][a-zà-ÿ-]+\s+(?:in|of|on|inside|behind|about)\s+(?:this|that)\s+(?:whole\s+|entire\s+|single\s+)?[a-zà-ÿ][a-zà-ÿ-]+/i,
|
|
85
|
+
rule: "A count used as a promotion. It tells the reader that what follows is the only one of its kind, which is the writer grading their own work, and the count is never checkable. It fits any noun, so it spreads: 6 courses closed on the same heading and 5 more files carried the same figure. Name the thing plainly and let it be the only one.",
|
|
86
|
+
example:
|
|
87
|
+
'"The one ask in this Classroom" / "the one rule in this whole system" / "the one thing on this panel that must keep its height"',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "echoed-word",
|
|
91
|
+
name: "Echoed word",
|
|
92
|
+
// The last word of one sentence opening the next, article allowed.
|
|
93
|
+
|
|
94
|
+
// The echo is the whole tell, so no negation is required: `nobody drops the ball. the ball has nowhere to sit.` is the same figure as `you are the glue.
|
|
95
|
+
|
|
96
|
+
// glue does not scale.`
|
|
97
|
+
test: /\b([a-zà-ÿ]{3,})\s*[.!?]["'’”)\]]?[\s\n]+(?:the |a |an |it |that )?\1\b/i,
|
|
98
|
+
rule: "The last word of a sentence handed straight to the next one as its subject. It reads as a written figure rather than as speech, and the second sentence almost always spends itself restating the first. Say the second thing without picking the word back up, or cut one of the two.",
|
|
99
|
+
example:
|
|
100
|
+
'"you are the glue. glue does not scale." / "nobody drops the ball. the ball has nowhere to sit."',
|
|
101
|
+
},
|
|
102
|
+
];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { HERITAGE_HEADING_WORD } from "#lint/data/vendor/heading-words.js";
|
|
2
|
+
|
|
3
|
+
/** Patterns from Wikipedia's signs of AI writing whose signature is literal, English and French. */
|
|
4
|
+
export const patternsWikipediaSigns = [
|
|
5
|
+
// --- Wikipedia:Signs of AI writing, read 2026-09-05 --- The signs from that page whose signature is literal enough to lint.
|
|
6
|
+
|
|
7
|
+
// The ones that are only literal across a whole document (title case, heading levels, boldface density, thematic breaks, mixed apostrophes) are the structural checks in `slop.js`;
|
|
8
|
+
|
|
9
|
+
// the machine residue is `artifacts` in `data/slop/artifacts.js`. Both languages, because the French page (fr:Aide:Identifier l'usage d'une IA générative) names the same signs and a draft can be in either.
|
|
10
|
+
{
|
|
11
|
+
id: "superficial-ing-clause",
|
|
12
|
+
name: "Superficial -ing analysis",
|
|
13
|
+
// A comma, then one of a closed set of participles, then a determiner.
|
|
14
|
+
|
|
15
|
+
// The determiner is what keeps it one-directional: "..., reflecting on it" and "..., ensuring we ship" have no determiner and read clean,
|
|
16
|
+
|
|
17
|
+
// while "..., highlighting the importance of X" is the shape every time.
|
|
18
|
+
test: /,\s+(?:highlighting|underscoring|emphasi[sz]ing|showcasing|reflecting|symboli[sz]ing|cementing|solidifying|illustrating|demonstrating|reinforcing|signal(?:l)?ing|marking|contributing to|ensuring)\s+(?:the|its|his|her|their|a|an|how|why|that)\b/i,
|
|
19
|
+
rule: "A trailing participial clause that restates the sentence as significance instead of adding a fact. The lint half of the `superficial-ing-analysis` shape. Cut the clause, or replace it with the fact it was standing in for.",
|
|
20
|
+
example:
|
|
21
|
+
'"..., highlighting the importance of clear processes." / "..., underscoring its role in the region."',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "superficial-ing-clause-fr",
|
|
25
|
+
name: "Clause participiale creuse",
|
|
26
|
+
// The French form of the same shape. The participle set is closed and each
|
|
27
|
+
// one is rare outside this construction, so the determiner is not needed to
|
|
28
|
+
// keep it one-directional.
|
|
29
|
+
test: /,\s+(?:soulignant|mettant en (?:lumière|avant|évidence)|illustrant|témoignant|reflétant|renforçant|marquant|démontrant|confirmant|consacrant|contribuant|permettant ainsi)\b/i,
|
|
30
|
+
rule: "La même forme en français : une virgule puis un participe présent qui commente la phrase au lieu d'ajouter un fait. Coupez la clause ou remplacez-la par le fait qu'elle remplaçait.",
|
|
31
|
+
example:
|
|
32
|
+
'"..., soulignant l\'importance de la démarche." / "..., témoignant d\'un savoir-faire reconnu."',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "copula-avoidance",
|
|
36
|
+
name: "Copula avoidance",
|
|
37
|
+
// WP:AINOCOPULA. A closed set of verbs standing in for "is".
|
|
38
|
+
|
|
39
|
+
// Each one takes a determiner straight after, which is what an ordinary "he stands as the last witness" does not do often enough to matter,
|
|
40
|
+
|
|
41
|
+
// and "the server operates as a proxy" is the one real use the set deliberately excludes by requiring the abstract nouns after `represents` and `marks`.
|
|
42
|
+
test: /\b(?:serves|stands|functions|acts)\s+as\s+(?:a|an|the)\b|\brepresents\s+a\s+(?:shift|significant|major|departure|milestone|turning)\b|\bmarks\s+(?:a|the)\s+(?:beginning|start|first|shift|turning|new)\b/i,
|
|
43
|
+
rule: 'A verb hired to avoid writing "is". Wikipedia measured a 10% drop in "is" and "are" in academic writing the year the chatbots landed, and this is where it went. Write "is", or write the verb that says what the thing actually does.',
|
|
44
|
+
example:
|
|
45
|
+
'"Gallery 825 serves as the exhibition arm." / "The launch marks a turning point." / "It stands as a vibrant town."',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "copula-avoidance-fr",
|
|
49
|
+
name: "Évitement de la copule",
|
|
50
|
+
test: /\b(?:s'impose|se pose|s'affirme|se présente|se veut)\s+comme\s+(?:un|une|le|la|l')\b|\bconstitue\s+(?:un|une)\s+(?:tournant|jalon|étape|pilier|témoignage|référence)\b/i,
|
|
51
|
+
rule: 'Un verbe employé pour ne pas écrire "est". Écrivez "est", ou le verbe qui dit ce que la chose fait vraiment.',
|
|
52
|
+
example:
|
|
53
|
+
'"La plateforme s\'impose comme une référence." / "Ce lancement constitue un tournant."',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "notability-claim",
|
|
57
|
+
name: "Canned notability claim",
|
|
58
|
+
// WP:OVERATTRIBUTION. A coverage verb, then a quantifier or a media-class
|
|
59
|
+
// word rather than a named outlet. A named source ("featured in Le Monde")
|
|
60
|
+
// has neither and reads clean.
|
|
61
|
+
test: /\b(?:featured|profiled|covered|cited|mentioned|recognized|recognised)\s+in\s+(?:multiple|numerous|several|various|major|leading|top|national|regional|international|independent|respected|widely)\b|\b(?:maintains?|has)\s+an?\s+active\s+(?:social media|online)\s+presence\b/i,
|
|
62
|
+
rule: "Notability asserted by describing the sources rather than saying what they reported. Name the outlet and what it said, or drop the sentence.",
|
|
63
|
+
example:
|
|
64
|
+
'"Profiled in multiple independent outlets." / "He maintains an active social media presence."',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "notability-claim-fr",
|
|
68
|
+
name: "Notoriété affirmée",
|
|
69
|
+
test: /\b(?:cité|mentionné|présenté|couvert|repris)e?s?\s+(?:dans|par)\s+(?:de nombreux|plusieurs|divers|différents|les principaux|de multiples)\b|\bprésence active sur les réseaux\b/i,
|
|
70
|
+
rule: "La notoriété affirmée en décrivant les sources au lieu de dire ce qu'elles ont rapporté. Nommez le média et ce qu'il a écrit, ou supprimez la phrase.",
|
|
71
|
+
example: '"Cité dans de nombreux médias nationaux."',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "challenges-outlook",
|
|
75
|
+
name: "Despite these challenges",
|
|
76
|
+
// WP:FACESCHALLENGES. The formula, not the word. "the challenges are real"
|
|
77
|
+
// has no `despite` in front of it and does not match.
|
|
78
|
+
test: /\bdespite\s+(?:its|these|those|the|such|ongoing)\s+(?:[a-z]+\s+){0,2}(?:challenges|limitations|drawbacks|setbacks|obstacles|hurdles|criticisms?)\b|\bfaces?\s+(?:several|a number of|numerous|various|many|its own)\s+(?:challenges|hurdles|obstacles)\b/i,
|
|
79
|
+
rule: "The closing paragraph that concedes a difficulty in order to end on an upswing. It states nothing checkable in either half. Write the difficulty and what happens because of it, or write neither.",
|
|
80
|
+
example:
|
|
81
|
+
'"Despite its rapid growth, the sector faces several challenges." / "Despite these challenges, it continues to thrive."',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "challenges-outlook-fr",
|
|
85
|
+
name: "Malgré ces défis",
|
|
86
|
+
test: /\bmalgré\s+(?:ces|ses|les|un|de)\s+(?:[a-zà-ÿ]+\s+){0,2}(?:défis|limites|obstacles|difficultés|critiques|freins)\b|\bfait face à\s+(?:plusieurs|de nombreux|divers|certains)\s+(?:défis|obstacles|difficultés)\b/i,
|
|
87
|
+
rule: "Le paragraphe de clôture qui concède une difficulté pour finir sur une note positive. Écrivez la difficulté et sa conséquence, ou aucune des deux.",
|
|
88
|
+
example: '"Malgré ces défis, le secteur continue de croître."',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "formulaic-heading",
|
|
92
|
+
name: "Formulaic section heading",
|
|
93
|
+
// The Wikipedia section on undue emphasis and the fr page's "Conclusion / Héritage" sign. A whole markdown heading, anchored at both ends, so a sentence that happens to contain "conclusion" does not match.
|
|
94
|
+
|
|
95
|
+
// The bare-noun heading is included.
|
|
96
|
+
test: new RegExp(
|
|
97
|
+
`^#{1,6}\\s*(?:awards? and recognition|recognition and awards?|challenges and (?:${HERITAGE_HEADING_WORD}|opportunities|future prospects)|future (?:outlook|prospects|directions)|${HERITAGE_HEADING_WORD} and impact|impact and ${HERITAGE_HEADING_WORD}|significance and ${HERITAGE_HEADING_WORD}|conclusion|${HERITAGE_HEADING_WORD}|overview|key takeaways?|final thoughts?|héritage|conclusion et perspectives|perspectives|perspectives d'avenir|prix et distinctions|défis et perspectives|points clés|en résumé)\\s*$`,
|
|
98
|
+
"im",
|
|
99
|
+
),
|
|
100
|
+
rule: "A section heading from the outline a model reaches for when it has run out of subject. The section under it is always summary rather than fact. Delete the section, or retitle it after the one thing it actually says.",
|
|
101
|
+
example: '"## Awards and recognition" / "## Future Outlook" / "## Conclusion" / "## Héritage"',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { patternsSubjectVoice } from "#lint/data/slop/patterns/subject-voice.js";
|
|
2
|
+
import { patternsRevealsAndTallies } from "#lint/data/slop/patterns/reveals-and-tallies.js";
|
|
3
|
+
import { patternsOpenersAndTails } from "#lint/data/slop/patterns/openers-and-tails.js";
|
|
4
|
+
import { patternsCommaClauses } from "#lint/data/slop/patterns/comma-clauses.js";
|
|
5
|
+
import { patternsSetupsAndClosers } from "#lint/data/slop/patterns/setups-and-closers.js";
|
|
6
|
+
import { patternsNumbersAndRuns } from "#lint/data/slop/patterns/numbers-and-runs.js";
|
|
7
|
+
import { patternsVagueAndFlourish } from "#lint/data/slop/patterns/vague-and-flourish.js";
|
|
8
|
+
import { patternsAppositivesAndSayings } from "#lint/data/slop/patterns/appositives-and-sayings.js";
|
|
9
|
+
import { patternsWikipediaSigns } from "#lint/data/slop/patterns/wikipedia-signs.js";
|
|
10
|
+
import { patternsHeadingsAndLaws } from "#lint/data/slop/patterns/headings-and-laws.js";
|
|
11
|
+
import { patternsParallelShapes } from "#lint/data/slop/patterns/parallel-shapes.js";
|
|
12
|
+
import { patternsDroppedElements } from "#lint/data/slop/patterns/dropped-elements.js";
|
|
13
|
+
import { patternsDurationAndOpeners } from "#lint/data/slop/patterns/duration-and-openers.js";
|
|
14
|
+
import { patternsDiagramAndScriptTells } from "#lint/data/slop/patterns/diagram-and-script-tells.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The sentence shapes that CAN be matched exactly, so they are linted rather than left to a human read.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* WHY this list is separate from `shapes`: `shapes` is deliberately unlintable, because a regex for "negation pivot" would fire on every ordinary "is not".
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* These are the sub-cases where the shape has a literal signature, and a signature is the only thing that turns a rule into a gate.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* The first two were caught in shipped slides on 24 Aug 2026, in a batch that had passed the word and phrase lists clean,
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* which is what a rule with nothing behind it is worth.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* A pattern only belongs here if it can be wrong in one direction only. Anything that needs judgment stays in `shapes` and is enforced by the humanizer read.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* `id`, `name`, `test`, `rule` and `example` are required, and `example` is asserted against `test` by the suite, so a documented case its own regex cannot see fails the build.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* `view` is optional and names which reading of the draft the regex judges: the whole draft by default, or `faqless` for the one check an FAQ is allowed to fail.
|
|
46
|
+
*/
|
|
47
|
+
export const patterns = [
|
|
48
|
+
...patternsSubjectVoice,
|
|
49
|
+
...patternsRevealsAndTallies,
|
|
50
|
+
...patternsOpenersAndTails,
|
|
51
|
+
...patternsCommaClauses,
|
|
52
|
+
...patternsSetupsAndClosers,
|
|
53
|
+
...patternsNumbersAndRuns,
|
|
54
|
+
...patternsVagueAndFlourish,
|
|
55
|
+
...patternsAppositivesAndSayings,
|
|
56
|
+
...patternsWikipediaSigns,
|
|
57
|
+
...patternsHeadingsAndLaws,
|
|
58
|
+
...patternsParallelShapes,
|
|
59
|
+
...patternsDroppedElements,
|
|
60
|
+
...patternsDurationAndOpeners,
|
|
61
|
+
...patternsDiagramAndScriptTells,
|
|
62
|
+
];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The English banned phrases added later: the humanizer catch-up, pause-for-effect closers and first-person softeners. */
|
|
2
|
+
export const phrasesEnClosers = [
|
|
3
|
+
// --- blader/humanizer 3.0.0 catch-up, 21 Sep 2026. Only the distinctive multi-word forms; upstream's bare "linked to", "tied to" and "of course" are ordinary English and stay out.
|
|
4
|
+
|
|
5
|
+
// --- Pause-for-effect closers and sayings that sound deep.
|
|
6
|
+
"read that again",
|
|
7
|
+
"that's the real win",
|
|
8
|
+
"that is the real win",
|
|
9
|
+
"the real question is",
|
|
10
|
+
"what really matters",
|
|
11
|
+
"the deeper issue",
|
|
12
|
+
"the heart of the matter",
|
|
13
|
+
// Staged run-ups before the point.
|
|
14
|
+
"let's explore",
|
|
15
|
+
"let's break this down",
|
|
16
|
+
"here's what you need to know",
|
|
17
|
+
"now let's look at",
|
|
18
|
+
"without further ado",
|
|
19
|
+
"let's be honest",
|
|
20
|
+
"real talk",
|
|
21
|
+
// Arguing with no one, and the fake alternative.
|
|
22
|
+
"i'm not saying",
|
|
23
|
+
"to be clear",
|
|
24
|
+
"don't get me wrong",
|
|
25
|
+
"this is not to say",
|
|
26
|
+
"a tempting approach",
|
|
27
|
+
"one might be tempted",
|
|
28
|
+
"it would be easy to just",
|
|
29
|
+
// Stacked qualifiers.
|
|
30
|
+
"could potentially",
|
|
31
|
+
"might arguably",
|
|
32
|
+
"this is an inference",
|
|
33
|
+
// Send-off inflation and sales language.
|
|
34
|
+
"the future looks bright",
|
|
35
|
+
"exciting times ahead",
|
|
36
|
+
"a step in the right direction",
|
|
37
|
+
"must-visit",
|
|
38
|
+
"active social media presence",
|
|
39
|
+
"several publications",
|
|
40
|
+
// Chatbot residue and guesses passed off as fact.
|
|
41
|
+
"should i continue",
|
|
42
|
+
"it is believed that",
|
|
43
|
+
|
|
44
|
+
// --- First-person softeners.
|
|
45
|
+
|
|
46
|
+
// Ramzi, 21 Sep 2026: "we need to speak with a little bit more confidence and assertiveness, like removing I think and please." He dictates them to Claude 1.8 times per
|
|
47
|
+
|
|
48
|
+
// 1,000 words; in copy they are hedges. Bare "please" stays out: error and legal microcopy needs it. ---
|
|
49
|
+
"i think",
|
|
50
|
+
"i believe",
|
|
51
|
+
"in my opinion",
|
|
52
|
+
"i guess",
|
|
53
|
+
"i suppose",
|
|
54
|
+
"i feel like",
|
|
55
|
+
"i'd say",
|
|
56
|
+
"i would say",
|
|
57
|
+
"i'm not sure",
|
|
58
|
+
"i was wondering",
|
|
59
|
+
"just wanted to",
|
|
60
|
+
"if that makes sense",
|
|
61
|
+
"hopefully",
|
|
62
|
+
"please feel free",
|
|
63
|
+
"please don't hesitate",
|
|
64
|
+
"please note that",
|
|
65
|
+
"please be aware",
|
|
66
|
+
];
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** The English banned phrases, first half: the workspace bans, throat-clearing, filler and the corpus reads. */
|
|
2
|
+
export const phrasesEnCore = [
|
|
3
|
+
// --- Existing workspace bans ---
|
|
4
|
+
"not only",
|
|
5
|
+
"but also",
|
|
6
|
+
|
|
7
|
+
// --- Negation pivots: the "it is not X, it is Y" reveal --- Substring-matched, so only distinctive multi-word forms belong here.
|
|
8
|
+
|
|
9
|
+
// A bare "is not" is normal English and must never be listed.
|
|
10
|
+
"it's not just",
|
|
11
|
+
"it is not just",
|
|
12
|
+
"not just about",
|
|
13
|
+
"it's not about",
|
|
14
|
+
"it is not about",
|
|
15
|
+
"isn't about",
|
|
16
|
+
"it's not merely",
|
|
17
|
+
"it is not merely",
|
|
18
|
+
"not merely a",
|
|
19
|
+
"this isn't just",
|
|
20
|
+
"this is not just",
|
|
21
|
+
"that's not just",
|
|
22
|
+
"more than just a",
|
|
23
|
+
"ce n'est pas seulement",
|
|
24
|
+
"il ne s'agit pas seulement",
|
|
25
|
+
"non seulement",
|
|
26
|
+
"ever wondered why",
|
|
27
|
+
"reach new heights",
|
|
28
|
+
"in the realm of",
|
|
29
|
+
"think of it as",
|
|
30
|
+
"that's a wrap",
|
|
31
|
+
"it's like",
|
|
32
|
+
"let's dive in",
|
|
33
|
+
"dive in",
|
|
34
|
+
"buckle up",
|
|
35
|
+
"to the next level",
|
|
36
|
+
"in the fast-paced",
|
|
37
|
+
"in today's",
|
|
38
|
+
"hidden gems",
|
|
39
|
+
|
|
40
|
+
// --- Stop-slop: throat-clearing openers ---
|
|
41
|
+
"here's the thing",
|
|
42
|
+
"the uncomfortable truth",
|
|
43
|
+
"it turns out",
|
|
44
|
+
"let me be clear",
|
|
45
|
+
"the truth is",
|
|
46
|
+
"can we talk about",
|
|
47
|
+
"here's what i find",
|
|
48
|
+
"here's the problem",
|
|
49
|
+
"i'll say it again",
|
|
50
|
+
"i'm going to be honest",
|
|
51
|
+
|
|
52
|
+
// --- 2026 corpus: the three "here's the X" openers named alongside the two above by copy and marketing sources.
|
|
53
|
+
|
|
54
|
+
// Same shape, same job, and the copy one was caught in a shipped email arguing against AI writing.
|
|
55
|
+
"here's the fix",
|
|
56
|
+
"here's the kicker",
|
|
57
|
+
"here's the gap",
|
|
58
|
+
|
|
59
|
+
// --- Transition crutches.
|
|
60
|
+
|
|
61
|
+
// Held only in prose until 3 Sep 2026, in a "Transitions to Avoid" list inside seo-copywriting, where nothing checked them: 7 of its 10 entries were already here and 3
|
|
62
|
+
|
|
63
|
+
// were not, so the file was both a duplicate and the only home for the rest. ---
|
|
64
|
+
"that being said",
|
|
65
|
+
"this begs the question",
|
|
66
|
+
"let's delve into",
|
|
67
|
+
"lets delve into",
|
|
68
|
+
|
|
69
|
+
// --- Stop-slop: emphasis crutches ---
|
|
70
|
+
"let that sink in",
|
|
71
|
+
"this matters because",
|
|
72
|
+
"make no mistake",
|
|
73
|
+
"here's why that matters",
|
|
74
|
+
|
|
75
|
+
// --- Stop-slop: business jargon phrases ---
|
|
76
|
+
"lean into",
|
|
77
|
+
"double down",
|
|
78
|
+
"deep dive",
|
|
79
|
+
"take a step back",
|
|
80
|
+
"moving forward",
|
|
81
|
+
"circle back",
|
|
82
|
+
"on the same page",
|
|
83
|
+
"game-changer",
|
|
84
|
+
"game changer",
|
|
85
|
+
"cutting-edge",
|
|
86
|
+
"cutting edge",
|
|
87
|
+
"best-in-class",
|
|
88
|
+
"best in class",
|
|
89
|
+
"next-generation",
|
|
90
|
+
"state-of-the-art",
|
|
91
|
+
"end-to-end",
|
|
92
|
+
|
|
93
|
+
// --- Stop-slop: filler phrases ---
|
|
94
|
+
"no more",
|
|
95
|
+
"say goodbye to",
|
|
96
|
+
"at its core",
|
|
97
|
+
"at the end of the day",
|
|
98
|
+
"in a world where",
|
|
99
|
+
"in conclusion",
|
|
100
|
+
"it's worth noting",
|
|
101
|
+
"rest assured",
|
|
102
|
+
"the reality is",
|
|
103
|
+
"when it comes to",
|
|
104
|
+
|
|
105
|
+
// --- Stylometric markers from guillaumemeyer/watermarks-remover --- Its detector scores 24 weighted markers.
|
|
106
|
+
|
|
107
|
+
// 26 of the 34 surface forms those cover were already banned here; these are the eight that were not, checked against 56,568 words of first-party corpus first so none of
|
|
108
|
+
|
|
109
|
+
// them is his. Read 2026-08-16.
|
|
110
|
+
"it is important to note",
|
|
111
|
+
"it is essential to note",
|
|
112
|
+
"it is crucial to note",
|
|
113
|
+
"it is worth noting",
|
|
114
|
+
"to summarize",
|
|
115
|
+
"plays a crucial role",
|
|
116
|
+
"plays a key role",
|
|
117
|
+
"plays a vital role",
|
|
118
|
+
|
|
119
|
+
// --- Assistant leakage: a chatbot's own turn-taking, left in the copy ---
|
|
120
|
+
"as an ai",
|
|
121
|
+
"i hope this helps",
|
|
122
|
+
|
|
123
|
+
// --- Stop-slop: meta-commentary ---
|
|
124
|
+
"plot twist",
|
|
125
|
+
"you already know this",
|
|
126
|
+
"is a feature, not a bug",
|
|
127
|
+
"let me walk you through",
|
|
128
|
+
"as we'll see",
|
|
129
|
+
|
|
130
|
+
// --- The unfinished list, and the writer narrating his own argument --- Read from a Nicolas Cole newsletter on 26 Aug 2026, a specimen of the creator-newsletter register.
|
|
131
|
+
|
|
132
|
+
// "the list goes on and on" closes two separate lists in it, both times in place of the items the writer did not have.
|
|
133
|
+
|
|
134
|
+
// Zero occurrences in Ramzi's corpus for all of these except "possibilities are endless", which appears once, in a 2025 ClickUp post from the generic marketer era this voice system was
|
|
135
|
+
|
|
136
|
+
// built to end.
|
|
137
|
+
"the list goes on",
|
|
138
|
+
"the list is endless",
|
|
139
|
+
"possibilities are endless",
|
|
140
|
+
"and so much more",
|
|
141
|
+
"my point is",
|
|
142
|
+
"so you see",
|
|
143
|
+
"half the battle",
|
|
144
|
+
"the name of the game",
|
|
145
|
+
|
|
146
|
+
// --- Stop-slop: vague declaratives ---
|
|
147
|
+
"the implications are",
|
|
148
|
+
"the stakes are high",
|
|
149
|
+
"the consequences are",
|
|
150
|
+
"the reasons are structural",
|
|
151
|
+
|
|
152
|
+
// --- petergyang/no-ai-slop, read 2026-08-31 --- Faux-insight setups, weasel attribution, rhetorical setups, reader guidance and dated-world filler.
|
|
153
|
+
|
|
154
|
+
// Every one is a distinctive multi-word form, so it can be substring-matched without firing on ordinary English; the ones that cannot ("in other words",
|
|
155
|
+
|
|
156
|
+
// "literally", "simply", "overall") stay judgement calls in patterns.md 48. Zero occurrences across the first-party corpus.
|
|
157
|
+
"this changes everything",
|
|
158
|
+
"this is huge",
|
|
159
|
+
// "in today's" is listed above; only the curly apostrophe needs its own entry.
|
|
160
|
+
"in today\u2019s",
|
|
161
|
+
"in the age of",
|
|
162
|
+
"in the world of",
|
|
163
|
+
"in this article",
|
|
164
|
+
"going forward",
|
|
165
|
+
"with regard to",
|
|
166
|
+
"in terms of",
|
|
167
|
+
"widely regarded as",
|
|
168
|
+
"experts agree",
|
|
169
|
+
"industry reports suggest",
|
|
170
|
+
"many argue",
|
|
171
|
+
"what if i told you",
|
|
172
|
+
// "think about it" is NOT listed: the slop form is the standalone imperative ("Think about it:"), and a substring ban also catches the ordinary subordinate clause,
|
|
173
|
+
|
|
174
|
+
// which shipped copy uses ("whether you think about it or not"). It stays in patterns.md 33 as a human read.
|
|
175
|
+
|
|
176
|
+
// "as you can see" is NOT listed: voice-measured.md measures it 20 times in his own spoken corpus, tagged "spoken only, needs a screen".
|
|
177
|
+
|
|
178
|
+
// It is his tutorial register pointing at a screen, not reader guidance. patterns.md 48 carries it as a written-copy read.
|
|
179
|
+
"the key point is",
|
|
180
|
+
"this distinction matters",
|
|
181
|
+
"nobody tells you",
|
|
182
|
+
"the part everyone misses",
|
|
183
|
+
"most people get wrong",
|
|
184
|
+
"most people skip",
|
|
185
|
+
];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/** The English banned phrases from Wikipedia's signs of AI writing, one block per section of that page. */
|
|
2
|
+
export const phrasesEnWikipedia = [
|
|
3
|
+
// --- Wikipedia:Signs of AI writing, read 2026-09-05 --- One block per section of that page, so a reader can trace a ban back to the sign it came from.
|
|
4
|
+
|
|
5
|
+
// Only distinctive multi-word forms are here; the signs that need an anchor are `patterns` in `data/slop/patterns.js`, and the ones that need a whole document are the structural checks in `slop.js`.
|
|
6
|
+
|
|
7
|
+
// WP:AITREND and the Wikipedia section on undue emphasis. Significance asserted instead of stated.
|
|
8
|
+
"a testament to",
|
|
9
|
+
"plays a significant role",
|
|
10
|
+
"plays a pivotal role",
|
|
11
|
+
"a pivotal moment",
|
|
12
|
+
"a key turning point",
|
|
13
|
+
// "serves as a", "stands as a", "marks a shift" and "represents a shift" are not listed here: the `copula-avoidance` pattern in `data/slop/patterns/wikipedia-signs.js` owns that whole family,
|
|
14
|
+
|
|
15
|
+
// and a phrase entry would only report the same sentence twice.
|
|
16
|
+
"reflects a broader",
|
|
17
|
+
"reflects broader",
|
|
18
|
+
"part of a broader",
|
|
19
|
+
"setting the stage for",
|
|
20
|
+
"indelible mark",
|
|
21
|
+
"deeply rooted",
|
|
22
|
+
"evolving landscape",
|
|
23
|
+
"focal point",
|
|
24
|
+
"underscores the importance",
|
|
25
|
+
"highlights the importance",
|
|
26
|
+
"emphasizes the importance",
|
|
27
|
+
"underscoring its significance",
|
|
28
|
+
"contributing to the broader",
|
|
29
|
+
"in the broader context",
|
|
30
|
+
"ongoing efforts to",
|
|
31
|
+
|
|
32
|
+
// WP:OVERATTRIBUTION, WP:AIATTR. Notability asserted instead of shown.
|
|
33
|
+
"independent coverage",
|
|
34
|
+
"media outlets",
|
|
35
|
+
"trade publications",
|
|
36
|
+
"widely read outlets",
|
|
37
|
+
"has been profiled in",
|
|
38
|
+
"has been featured in",
|
|
39
|
+
"active social media presence",
|
|
40
|
+
"significant coverage",
|
|
41
|
+
"substantial coverage",
|
|
42
|
+
"independent and reliable sources",
|
|
43
|
+
|
|
44
|
+
// WP:AIPUFFERY, WP:AIPEACOCK. The travel-guide register.
|
|
45
|
+
"rich cultural heritage",
|
|
46
|
+
"rich history",
|
|
47
|
+
"natural beauty",
|
|
48
|
+
"in the heart of",
|
|
49
|
+
"nestled in",
|
|
50
|
+
"nestled within",
|
|
51
|
+
"diverse array",
|
|
52
|
+
"wide array of",
|
|
53
|
+
"a wide range of",
|
|
54
|
+
"commitment to excellence",
|
|
55
|
+
"a fascinating glimpse",
|
|
56
|
+
"a glimpse into",
|
|
57
|
+
"shed light on",
|
|
58
|
+
"valuable insights",
|
|
59
|
+
"align with",
|
|
60
|
+
"aligns with",
|
|
61
|
+
"resonate with",
|
|
62
|
+
"resonates with",
|
|
63
|
+
"an integral part of",
|
|
64
|
+
|
|
65
|
+
// WP:FACESCHALLENGES. The formulaic closing section.
|
|
66
|
+
"faces several challenges",
|
|
67
|
+
"faces a number of challenges",
|
|
68
|
+
"despite these challenges",
|
|
69
|
+
"despite its challenges",
|
|
70
|
+
"challenges and opportunities",
|
|
71
|
+
"future prospects",
|
|
72
|
+
"future outlook",
|
|
73
|
+
"awards and recognition",
|
|
74
|
+
"recognition and awards",
|
|
75
|
+
|
|
76
|
+
// WP:CONCLUSION, WP:INCONCLUSION. The section summary. "in conclusion" is
|
|
77
|
+
// listed in `phrases-en-core.js` with the filler phrases.
|
|
78
|
+
"in summary",
|
|
79
|
+
"to sum up",
|
|
80
|
+
|
|
81
|
+
// WP:DIDACTIC. The disclaimer aimed at an imagined reader.
|
|
82
|
+
|
|
83
|
+
// The uncontracted "it is important/essential/crucial to note" and "it's worth noting" are listed in `phrases-en-core.js`; these are the forms a substring match on those misses.
|
|
84
|
+
"it's important to note",
|
|
85
|
+
"it's essential to note",
|
|
86
|
+
"it's crucial to note",
|
|
87
|
+
"important to remember",
|
|
88
|
+
"crucial to remember",
|
|
89
|
+
"worth remembering",
|
|
90
|
+
"important to consider",
|
|
91
|
+
"may vary depending",
|
|
92
|
+
|
|
93
|
+
// WP:CERTAINLY, WP:COLLABCOMM. The chatbot's own turn, left in the copy.
|
|
94
|
+
// "as an ai" and "i hope this helps" are listed in `phrases-en-core.js`.
|
|
95
|
+
"as an ai language model",
|
|
96
|
+
"as a large language model",
|
|
97
|
+
"i cannot provide",
|
|
98
|
+
"i'm sorry, but",
|
|
99
|
+
"i am sorry, but",
|
|
100
|
+
"you're absolutely right",
|
|
101
|
+
"you are absolutely right",
|
|
102
|
+
"great question",
|
|
103
|
+
"would you like me to",
|
|
104
|
+
"let me know if you",
|
|
105
|
+
"is there anything else",
|
|
106
|
+
"detailed breakdown",
|
|
107
|
+
"here is a breakdown",
|
|
108
|
+
"here's a breakdown",
|
|
109
|
+
"i hope this message finds you well",
|
|
110
|
+
"i hope this finds you well",
|
|
111
|
+
"feel free to reach out",
|
|
112
|
+
|
|
113
|
+
// WP:AICUTOFF, WP:AIDISCLAIMER. The model speculating about its own gaps.
|
|
114
|
+
"as of my last knowledge update",
|
|
115
|
+
"as of my last training update",
|
|
116
|
+
"up to my last training update",
|
|
117
|
+
"based on available information",
|
|
118
|
+
"based on the available information",
|
|
119
|
+
"while specific details are limited",
|
|
120
|
+
"specific details are scarce",
|
|
121
|
+
"not widely documented",
|
|
122
|
+
"not widely available",
|
|
123
|
+
"not publicly available",
|
|
124
|
+
"in the provided sources",
|
|
125
|
+
"in the available sources",
|
|
126
|
+
"in the search results",
|
|
127
|
+
"maintains a low profile",
|
|
128
|
+
"keeps personal details private",
|
|
129
|
+
|
|
130
|
+
// WP:AICONNECT, WP:AIASSOCIATION. A relationship alluded to, never stated.
|
|
131
|
+
"widely interpreted as",
|
|
132
|
+
"has been associated with",
|
|
133
|
+
"widely associated with",
|
|
134
|
+
"in connection with",
|
|
135
|
+
"in association with",
|
|
136
|
+
];
|