@heyramzi/lint 2.0.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.
Files changed (37) hide show
  1. package/bin/heyramzi-comment-length.js +4 -3
  2. package/bin/heyramzi-file-tokens.js +2 -2
  3. package/bin/heyramzi-slop.js +3 -1
  4. package/config/oxlintrc.json +1 -1
  5. package/data/slop/artifacts.js +87 -0
  6. package/data/slop/pattern-parts.js +56 -0
  7. package/data/slop/patterns/appositives-and-sayings.js +81 -0
  8. package/data/slop/patterns/comma-clauses.js +106 -0
  9. package/data/slop/patterns/diagram-and-script-tells.js +71 -0
  10. package/data/slop/patterns/dropped-elements.js +149 -0
  11. package/data/slop/patterns/duration-and-openers.js +102 -0
  12. package/data/slop/patterns/headings-and-laws.js +116 -0
  13. package/data/slop/patterns/numbers-and-runs.js +94 -0
  14. package/data/slop/patterns/openers-and-tails.js +90 -0
  15. package/data/slop/patterns/parallel-shapes.js +100 -0
  16. package/data/slop/patterns/reveals-and-tallies.js +101 -0
  17. package/data/slop/patterns/setups-and-closers.js +83 -0
  18. package/data/slop/patterns/subject-voice.js +67 -0
  19. package/data/slop/patterns/vague-and-flourish.js +102 -0
  20. package/data/slop/patterns/wikipedia-signs.js +103 -0
  21. package/data/slop/patterns.js +62 -0
  22. package/data/slop/phrases-en-closers.js +66 -0
  23. package/data/slop/phrases-en-core.js +185 -0
  24. package/data/slop/phrases-en-wikipedia.js +136 -0
  25. package/data/slop/phrases-fr.js +152 -0
  26. package/data/slop/shapes-density.js +81 -0
  27. package/data/slop/shapes-framing.js +101 -0
  28. package/data/slop/shapes-reveals.js +88 -0
  29. package/data/slop/title-tells.js +8 -0
  30. package/data/slop/words-en.js +178 -0
  31. package/data/slop/words-fr.js +101 -0
  32. package/data/slop-words.js +18 -2576
  33. package/data/vendor/heading-words.js +6 -0
  34. package/inline-path.js +2 -0
  35. package/package.json +1 -1
  36. package/slop.js +4 -2
  37. package/ui-rules.js +4 -2
@@ -101,15 +101,16 @@ function gate(files) {
101
101
  return;
102
102
  }
103
103
 
104
+ // A long comment is a warning: one word over the cap never broke anything. A hack marker fails.
104
105
  for (const { file, words } of failures) {
105
- console.error(`${file}: longest comment ${words} words (over ${max})`);
106
+ console.warn(`warning ${file}: longest comment ${words} words (over ${max})`);
106
107
  }
107
- if (failures.length > 0) console.error("\nCompress the comment to the point it makes.");
108
+ if (failures.length > 0) console.warn("Compress the comment to the point it makes.\n");
108
109
  for (const { file, line, label } of markers) {
109
110
  console.error(`${file}:${line}: ${label}`);
110
111
  }
111
112
  if (markers.length > 0) {
112
113
  console.error("\nFix what the comment names, or delete it. No old name survives a refactor.");
114
+ exit(1);
113
115
  }
114
- exit(1);
115
116
  }
@@ -28,8 +28,8 @@ const real = failures.filter(({ file }) => !generated(file));
28
28
  if (real.length === 0) {
29
29
  console.log(`file-tokens: every ${fileMode ? "" : "staged "}file is under its cap`);
30
30
  } else {
31
- console.error(report(real));
32
- exit(1);
31
+ // A warning: splits made only to clear a few tokens were churn. Split by job when you're in the file.
32
+ console.warn(`warning ${report(real)}`);
33
33
  }
34
34
 
35
35
  function gitRoot(cwd) {
@@ -53,7 +53,9 @@
53
53
 
54
54
  import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
55
55
  import { join } from "node:path";
56
- import { patterns, shapes, titleTells } from "#lint/data/slop-words.js";
56
+ import { shapes } from "#lint/data/slop-words.js";
57
+ import { patterns } from "#lint/data/slop/patterns.js";
58
+ import { titleTells } from "#lint/data/slop/title-tells.js";
57
59
  import { REVIEW, auditShapes, isUnavailable, kiloKey, proseOf } from "#lint/jev.js";
58
60
  import { slopGate } from "#lint/slop.js";
59
61
 
@@ -57,7 +57,7 @@
57
57
  "@heyramzi/lint/svelte-section-order": "warn",
58
58
  "@heyramzi/lint/no-inline-svg": "error",
59
59
  "@heyramzi/lint/no-inline-path": "error",
60
- "@heyramzi/lint/no-relative-imports": "error",
60
+ "@heyramzi/lint/no-relative-imports": "warn",
61
61
  "@heyramzi/lint/no-underscore-reexport": "error",
62
62
  "@heyramzi/lint/convex-react-client-module-scope": "error",
63
63
  "@heyramzi/lint/no-chained-type-assertions": "warn",
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Machine residue. The citation and formatting markup a chatbot leaks into its own output, one entry per tool that leaks it.
3
+ */
4
+
5
+ /*
6
+ * WHY this is separate from `patterns`: a pattern is a judgement about a sentence and a person can disagree with it. These are certainties.
7
+ */
8
+
9
+ /*
10
+ * No writer types `:contentReference[oaicite:0]`, `【85†L261-269】` or `utm_source=chatgpt.com` into finished copy, so a hit is not a style opinion, it is the receipt.
11
+ */
12
+
13
+ /*
14
+ * Wikipedia:Signs of AI writing calls it "an unambiguous indicator that the text originated with AI" (WP:OAICITE, WP:STARTSPAN, WP:GROKCARD), and it is the only half of that page that needs no
15
+ */
16
+
17
+ /*
18
+ * corpus check before shipping.
19
+ */
20
+
21
+ /*
22
+ * Language neutral by construction: the residue is the same in every language, which is why the French page lists `:contentReference[oaicite:2]{index=2}` under "Sources rédigées avec une syntaxe inconnue".
23
+ */
24
+ export const artifacts = [
25
+ {
26
+ id: "openai-citation",
27
+ name: "ChatGPT citation residue",
28
+ test: /:contentReference\[oaicite:\d+\]|\boai_citation\b|\bcontentReference\b|\bcite\u{E200}?turn\d+|\bturn\d+(?:search|image|news|file|view|forecast)\d+|\{"attribution":\s*\{"attributableIndex"/u,
29
+ rule: "ChatGPT's internal reference markup, pasted through. Delete it and write the citation, or delete the sentence that needed one.",
30
+ example: '":contentReference[oaicite:0]{index=0}" / "citeturn0search3"',
31
+ },
32
+ {
33
+ id: "gemini-citation",
34
+ name: "Gemini citation residue",
35
+ test: /\[cite:\s*[\d,\s]+\]|\[span_\d+\]\((?:start|end)_span\)|\((?:start|end)_span\)/,
36
+ rule: "Gemini's citation and span markup, pasted through. Delete it.",
37
+ example: '"[cite: 3, 12, 13]" / "[span_1](start_span)"',
38
+ },
39
+ {
40
+ id: "grok-citation",
41
+ name: "Grok citation residue",
42
+ test: /grok_render_citation_card_json|<\/?grok-card\b|\bgrok_card\b|[?&]referrer=grok\.com\b/i,
43
+ rule: "Grok's citation card markup, pasted through. Delete it.",
44
+ example: '"<grok-card data-id=...>" / "grok_render_citation_card_json"',
45
+ },
46
+ {
47
+ id: "deepseek-citation",
48
+ name: "DeepSeek citation residue",
49
+ test: /【\d+†[^】]*】/,
50
+ rule: "DeepSeek's lenticular-bracket footnote markup, pasted through. Delete it.",
51
+ example: '"【85†L261-269】"',
52
+ },
53
+ {
54
+ id: "perplexity-citation",
55
+ name: "Perplexity citation residue",
56
+ test: /\[(?:attached_file|web):\d+\]|ppl-ai-file-upload/i,
57
+ rule: "Perplexity's file and web reference markup, pasted through. Delete it.",
58
+ example: '"[attached_file:1]" / "[web:1]"',
59
+ },
60
+ {
61
+ id: "assistant-utm",
62
+ name: "Assistant UTM tag",
63
+ // The link the model handed back, with its own referral tag still on it. It
64
+ // proves the URL came from a chatbot even when a human wrote the sentence.
65
+ test: /[?&](?:utm_source=(?:chatgpt\.com|openai|copilot\.com|perplexity\.ai)|referrer=grok\.com)\b/i,
66
+ rule: "A URL still carrying the chatbot's referral parameter. Strip the query string back to the link the reader needs.",
67
+ example: '"https://example.com/article?utm_source=chatgpt.com"',
68
+ },
69
+ {
70
+ id: "document-block-markup",
71
+ name: "Document block markup",
72
+ // The `:::writing{variant="document" id=...}` wrapper, and the fenced
73
+ // wikitext block a chatbot emits when asked for markup it does not speak.
74
+ test: /:::\s*writing\{|```\s*wikitext\b|↩️?/,
75
+ rule: "A document wrapper or a footnote return arrow the chat interface added around the answer. Delete the wrapper and keep the text.",
76
+ example: ':::writing{variant="document" id="12345"} / "```wikitext"',
77
+ },
78
+ {
79
+ id: "assistant-turn-markup",
80
+ name: "Assistant turn markup",
81
+ // A model's own control tags. `function_calls` and `thinking` reach copy
82
+ // when a transcript is pasted rather than the answer.
83
+ test: /<\/?(?:thinking|antml:[a-z_]+|function_calls|invoke name=)\b/i,
84
+ rule: "A control tag from the model's own turn, pasted with the answer. Delete it, and check what else came with it.",
85
+ example: '"<thinking>" / "<function_calls>"',
86
+ },
87
+ ];
@@ -0,0 +1,56 @@
1
+ // Grammar parts shared by `elided-passive` and `parallel-clauses`, kept as
2
+ // strings so each regex reads as the sentence shape it names.
3
+ export const SUBJECT_STOP =
4
+ "(?!(?:I|We|You|They|He|She|It|This|That|These|Those|There|Here|Who|What|Whatever|Which|When|Where|Why|How|If|Once|Then|And|But|So|Or|Every|Each|Anything|Everything|Something|Nothing|Everyone|Anyone|Someone|Nobody|No|Not|Please)\\b)";
5
+ export const PARTICIPLE =
6
+ "(?:[a-z]{3,}ed|(?:re|un|pre|over)?(?:built|made|kept|done|written|shown|sent|held|drawn|given|taken|chosen|driven|known|sold|told|paid|found|brought|bought|taught|grown|seen|spent|laid|hidden|tied))";
7
+ // Words that end a noun phrase: a determiner means verb plus object, an auxiliary means the verb is there.
8
+ export const NOUN_PHRASE_STOP =
9
+ "(?!(?:the|a|an|your|our|their|its|my|his|her|this|that|it|them|me|us|you|anything|everything|something|nothing|is|are|was|were|be|been|being|has|have|had|can|will|would|should|may|might|must|do|does|did|get|gets|got|not|never|to)\\b)";
10
+ // Past tenses that take a preposition without being passive: "the team worked on".
11
+ export const INTRANSITIVE_PAST =
12
+ "(?!(?:started|worked|moved|happened|stayed|waited|lived|failed|talked|looked|ended|lasted|belonged|listened|responded|reacted|agreed|arrived|returned|walked|travelled|traveled|asked|applied|voted|replied|opted|relied|depended|focused|pointed|referred)\\b)";
13
+ export const PREPOSITION =
14
+ "(?:around|for|by|with|into|from|in|on|to|at|over|under|through|across|inside|onto|per|behind|beside|without|before|after|until|as)";
15
+ export const FINITE_AUXILIARY =
16
+ "(?:is|are|was|were|has|have|had|can|will|would|should|must|may|might|does|do|did|gets|get)";
17
+ export const CLAUSE_SUBJECT = `(?:(?:[Tt]he|[Yy]our|[Oo]ur|[Tt]heir|[Ee]ach|[Ee]very)[ \\t]+[a-z][\\w'’-]*|${SUBJECT_STOP}[A-Z][\\w&'’-]*)`;
18
+ export const PRESENT_VERB =
19
+ "(?!(?:is|was|has|does|this|its|his|us|yes|plus|less|thus|across|always|perhaps|as|towards)\\b)[a-z]{2,}s";
20
+ export const OBJECT_DETERMINER = "(?:the|a|an|your|its|their|our|his|her|every|each|all|it|them)";
21
+
22
+ export const PARTICIPLE_LIST =
23
+ "(?:[a-z]{3,}ed|built|made|kept|done|written|shown|sent|held|drawn|given|taken|chosen|driven|known|sold|told|paid|found|brought|bought|taught|grown|seen|spent|laid|hidden)";
24
+
25
+ // A quantity, written either way. Thousands take a comma, because "3,556 videos"
26
+ // is how the measured claims in the video skills are written.
27
+ export const COUNT =
28
+ "(?:\\d{1,3}(?:,\\d{3})+|\\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)";
29
+ // Time units, for `bare-duration-clause`. Closed on purpose: every one of these
30
+ // is a noun in every reading, which is what makes a missing verb decidable.
31
+ export const DURATION =
32
+ "(?:sec|secs|second|seconds|min|mins|minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)";
33
+ // A word that cannot carry a real second clause: no auxiliary, and no pronoun
34
+ // that would supply the subject ("15 minutes, it takes time" is a sentence).
35
+ export const TAIL_WORD =
36
+ "(?!(?:is|are|was|were|be|been|being|has|have|had|can|will|would|should|may|might|must|does|do|did|gets|get|i|it|we|you|they|he|she|there|this|that)(?![\\w'’-]))[a-zà-ÿ][\\w'’-]*";
37
+ /**
38
+ * Where a sentence starts, for the patterns that judge a whole clause.
39
+ */
40
+
41
+ /*
42
+ * A bare `\n` is deliberately not in it.
43
+ */
44
+
45
+ /*
46
+ * Markdown prose here is hard-wrapped, so every line start looks sentence-initial and a clause anchored on `\n` reads the middle of a paragraph as its beginning:
47
+ */
48
+
49
+ /*
50
+ * it put 4 false hits on shipped skills before the paragraph anchor replaced it, all of them mid-sentence ("the next video without the algorithm's help").
51
+ */
52
+
53
+ /*
54
+ * A blank line is a real paragraph start, and `.!?:;` plus a space is a real sentence start.
55
+ */
56
+ export const SENTENCE_OPEN = "(?:^|(?<=[.!?:;][\"'’”)\\]]?\\**[ \\t])|(?<=\\n\\n)|(?<=\\n\\n[ \\t]))";
@@ -0,0 +1,81 @@
1
+ /** Patterns for appositive headings and clauses, sayings that sound deep and narration about a previous version. */
2
+ export const patternsAppositivesAndSayings = [
3
+ {
4
+ id: "appositive-heading",
5
+ name: "The X, participled",
6
+ // Anti-voice 13, the half that is decidable.
7
+
8
+ // The shape is a determiner-led noun phrase, a comma, and a participle or a relative pronoun, with the whole thing being a heading: no main verb ever arrives.
9
+
10
+ // The determiner is the entire tell, and it is what a first attempt on 6 Sep 2026 missed: bare `<noun phrase>, <participle>` matched 25 lines across the UpSys corpus and
11
+
12
+ // almost all were legitimate feature labels ("Your tools, connected", "Automations, switched on", "Price per user, per month, billed yearly").
13
+
14
+ // A label drops the article; a comment on a thing already named keeps it.
15
+
16
+ // Re-measured with the article required: 6 of 6 known instances of the shape, 0 false hits on 24,366 copy units.
17
+
18
+ // Noun phrase widened to 8 words, 8 Sep 2026: at 4 it missed the shipped slide "The same 5 containers for every client, nested 3 deep".
19
+
20
+ // Anchored to a quote, a backtick or a markdown heading on the left and to a quote or a line end on the right,
21
+
22
+ // so it only fires on a whole string.
23
+
24
+ // That is what keeps an ordinary mid-sentence appositive out ("The report, published in 2024, showed a 40% gap"): the sentence runs on past a full stop the character class refuses.
25
+
26
+ // The line end refuses a lowercase continuation, added 6 Sep 2026 after a hard-wrapped source string ("...that doc moves run\n\t\t\t\ton.") read as a heading.
27
+
28
+ // A wrapped sentence resumes in lowercase; a heading's body does not. English only.
29
+
30
+ // French uses the definite article for a generic plural, so "Les automatisations, activées" is the legitimate label "Automations, switched on" and the tell does not survive translation.
31
+ test: /(?:^|["'`]|#{1,6}[ \t])[ \t]*(?:the|a|an)\s+(?:[\w-]+\s+){0,7}[\w-]+,\s+(?:[a-z]+(?:ed|ing)|(?:re|un|over|pre)?(?:built|run|made|done|kept|held|sold|told|given|taken|written|drawn|shown|set|put|sent|spent|left|lost|found|chosen|driven|known|paid|read|met|won|cut|bought|brought|caught|taught|thought)|which|who|that|where|when)\b[^.!?\n"'`<>]{0,70}(?=[ \t]*(?:["'`]|$(?![\r\n][ \t]*[a-z])))/im,
32
+ rule: "A heading built as a thing, a comma and a comment on that thing, with no verb anywhere. It reads as a caption rather than as a claim, and the half after the comma grades the half before it instead of saying what happens. Write the sentence: name the subject and give it a verb.",
33
+ example:
34
+ '"The architecture, decided before it is built" / "The build, run against your real delivery" / "The adoption, which is the part that fails"',
35
+ },
36
+
37
+ {
38
+ id: "appositive-as-clause",
39
+ name: "The X, as we found it",
40
+ // `appositive-fragment`'s `as` half: noun phrase, comma, `as`, pronoun, verb, no main verb
41
+ // ("The cut, as it stands"). 17 Sep 2026: 11 hits in 30,898 files, 0 false.
42
+ test: /(?:^|["'`]|#{1,6}[ \t])[ \t]*(?:the|a|an|your|our|their|his|her|its|my)\s+(?:[\w-]+\s+){0,7}[\w-]+,\s+as\s+(?:we|you|they|it|i|he|she)\s+[\w-]+[^.!?\n"'`<>]{0,70}(?=[ \t]*(?:["'`]|$(?![\r\n][ \t]*[a-z])))/im,
43
+ rule: "A thing, a comma, and an `as` clause that only says when it was seen. No main verb arrives, so it reads as a museum caption rather than a claim, and on a page written to somebody it also drops them into the third person. Write the sentence: say who did what.",
44
+ example:
45
+ '"Your website, as we found it" / "The cut, as it stands" / "The entitlement, as it now stands"',
46
+ },
47
+
48
+ // --- blader/humanizer 3.0.0 catch-up, 21 Sep 2026. Four shapes its rebuilt
49
+ // list names that no phrase can hold. ---
50
+ {
51
+ id: "deep-saying",
52
+ test: /\b(?:is|are)\s+the\s+(?:language|currency|architecture|grammar|oxygen)\s+of\b|\bbecomes\s+a\s+trap\b/i,
53
+ name: "Saying that sounds deep",
54
+ rule: "An ordinary point dressed as a hidden truth. Replace the aphorism with the specific claim it stands in for.",
55
+ example:
56
+ '"Symmetry is the language of trust." / "Efficiency becomes a trap when teams forget the human layer."',
57
+ },
58
+ {
59
+ id: "clipped-negative-tail",
60
+ test: /,\s+no\s+(?:guessing|guesswork|fluff|fuss|surprises|hassle|jargon|ceremony|bloat|nonsense|hype|gimmicks)\b/i,
61
+ name: "Clipped negative tail",
62
+ rule: "A comma and a `no X` that denies something nobody claimed, so the sentence sounds larger. State what the thing does.",
63
+ example:
64
+ '"The options come from the selected item, no guessing." / "Setup takes ten minutes, no fluff."',
65
+ },
66
+ {
67
+ id: "dotted-emphasis",
68
+ test: /\b(?:every|each)\.\s+(?:single|one)\b/i,
69
+ name: "Periods between the words",
70
+ rule: "Emphasis by full stop after every word. Write the sentence and let the fact carry the weight.",
71
+ example: '"Every. single. day." / "Each. one. matters."',
72
+ },
73
+ {
74
+ id: "previous-version-narration",
75
+ test: /\b(?:was|were)\s+(?:added|introduced|created)\s+to\s+replace\b|\breplaces?\s+the\s+(?:previous|old|former|earlier|original)\s+(?:approach|version|implementation|behaviou?r|way)\b/i,
76
+ name: "Writing about the previous version",
77
+ rule: "Docs and comments describe what the text replaced instead of the current behavior. Say what it does now; the history lives in the changelog and git.",
78
+ example:
79
+ '"This function was added to replace the previous approach of iterating all items." / "It replaces the old implementation."',
80
+ },
81
+ ];
@@ -0,0 +1,106 @@
1
+ /** Patterns where a comma hangs a clause, participle or aside on the sentence. */
2
+ export const patternsCommaClauses = [
3
+ {
4
+ id: "comma-and-opener",
5
+ // Anchored to the start of the text, a sentence or a paragraph, and limited to ONE word before the comma, so a mid-sentence clause ("he said yes,
6
+
7
+ // and then left") and a real adverbial pair ("On Monday, and only then, we ship") cannot reach it.
8
+
9
+ // The word is letters only, so a date or a number opener does not match.
10
+
11
+ // Not per line: a hard-wrapped Oxford list breaks before "integrators, and autonomous agents" and failed the CBAM about page, 15 Sep 2026.
12
+ test: /(?:^|(?<=[.!?]\s)|(?<=\n\n)|(?<=\n\n[ \t]))[ \t]*[A-Za-z]{2,12},\s+(?:and|but)\b/,
13
+ name: "Comma-and opener",
14
+ rule: "A sentence that spends its first word on a verdict, then hinges on a comma plus and or but before it says anything. The opener carries nothing the next clause does not, and the hinge is where a model hedges before answering. Ramzi, 14 Sep 2026, on a shipped Reddit comment. Delete the opener and start on the answer.",
15
+ example:
16
+ '"Yes, and the honest answer is that a full reconcile is not available." / "No, but the reason matters more than the number." / "Honestly, and this is the part nobody says, it never worked."',
17
+ },
18
+ {
19
+ id: "comma-and-title",
20
+ name: "The rest of the site, and what each part is for",
21
+ // A LINE with no sentence punctuation in it, hinged on a comma plus `and` and a wh-word.
22
+
23
+ // The line shape is what makes it decidable: a heading, a card label or a copy-module value, never a comma mid-sentence in prose,
24
+
25
+ // which ends on a stop and cannot reach the line anchor. The trailing `["'`]?[,;]?` is the source view, where a value ends on `",`.
26
+
27
+ // The wh-word takes a space and a clause after it, so an Oxford list of bare wh-words ("What you do, where, and when") is a list and stays one.
28
+ test: /^[ \t]*(?:#{1,6}[ \t]+|[-*+][ \t]+|\*\*)?[A-Z][^.!?\n]{2,70},[ \t]+(?:and|or)[ \t]+(?:what|where|why|how|who|when|which|whether)[ \t][^.!?\n]{2,70}?["'`]?[,;]?[ \t]*$/m,
29
+ rule: 'A title that names a thing, then bolts a wh-clause on with a comma and `and`. The second half promises an explanation the page was going to give anyway, and the shape says the writer could not choose which half was the subject. Ramzi, 17 Sep 2026, on "The rest of the site, and what each part is for": the sloper should have caught it. Cut to the half that carries the page: "The rest of the site".',
30
+ example:
31
+ '"The rest of the site, and what each part is for" / "## Mail, and where every secret lives" / "The queue, and how a job gets claimed"',
32
+ },
33
+ {
34
+ id: "comma-hung-participle",
35
+ // The decidable half of `comma-hung-fragment`: a LINE that ends on a comma plus one participle.
36
+
37
+ // Any `-ed` word, not a closed list: the list missed "resolved" and shipped it to the CBAM footer, 15 Sep 2026.
38
+
39
+ // Per line, and the whole line is the fragment, so the shape is a headline, a card label or a slide band and never a comma mid-sentence in prose.
40
+
41
+ // `[ \t]` and not `\s`, so a hard-wrapped paragraph cannot hand the anchor the next line.
42
+ test: /^[ \t]*(?:#{1,6}[ \t]+)?[A-Z0-9][^.!?\n]{2,60},[ \t]*(?:[a-zà-ÿ]{3,}ed|back|done|gone|built)[ \t]*[.!?]?[ \t]*$/m,
43
+ name: "Comma-hung participle",
44
+ rule: "A noun, a comma, and a past participle standing in for a sentence. Write the sentence: say who does it and what happens.",
45
+ example:
46
+ '"Sales tax report, filed" / "The week, planned" / "4 to 5 hours a week, back" / "EU carbon border data, resolved."',
47
+ },
48
+ {
49
+ id: "comma-hung-phrase",
50
+ // Per LINE, like `comma-hung-participle`: a label or headline whose second
51
+ // half is a stock verbless tail, or a mirrored "one X, one Y".
52
+ test: /^[ \t]*(?:#{1,6}[ \t]+|[-*+][ \t]+|[\w$]+:[ \t]*["'`])?(?:[A-Za-z][^.!?\n,]{1,50},[ \t]+(?:end to end|start to finish|top to bottom|front to back|beginning to end|step by step|in one place|at a glance|all in one)|(?:[Oo]ne|[Nn]o|[Ee]very|[Ee]ach)[ \t]+[\w'’-]+,[ \t]+(?:one|no|every|each)[ \t]+[\w'’-]+)[ \t]*[.!]?["'`]?[,;]?[ \t]*$/m,
53
+ name: "Comma-hung phrase",
54
+ rule: 'A name, a comma, then a stock tail with no verb ("end to end", "in one place"), or a mirrored "one X, one Y". It reads as a slogan and says nothing a sentence would not. Ramzi, 28 Sep 2026, on "Editor OS, end to end". Write the sentence, or cut to the name.',
55
+ example: '"Editor OS, end to end" / "one camera, one go" / "No setup, no code"',
56
+ },
57
+ {
58
+ id: "guided-tour-promise",
59
+ // A relative clause restating a thing's obvious job as hand-holding.
60
+
61
+ // The verb set is closed and always takes "you" plus a direction word, so ordinary English does not reach it: "it takes you 3 minutes" has no direction word,
62
+
63
+ // and "the wiki that covers one page per space" has no second person.
64
+ test: /\b(?:that|which)\s+(?:walks?|takes?|guides?|shows?)\s+you\s+(?:through|step by step|exactly)\b/i,
65
+ name: "Guided-tour promise",
66
+ rule: "A thing named, then a relative clause that restates its obvious job as hand-holding, usually with a totality claim bolted on. Say what the thing covers and let the reader work out that reading it helps.",
67
+ example:
68
+ '"A wiki that walks you through every piece." / "A guide that takes you step by step through everything you need." / "A dashboard that shows you exactly where you stand."',
69
+ },
70
+ {
71
+ id: "vague-attribution",
72
+ // An unnamed authority as the subject of a reporting verb.
73
+
74
+ // A named source does not match, because the subject has to be the bare category word: "The Gartner report says 40%" and "Researchers and conservationists study the river" both read
75
+
76
+ // clean.
77
+ test: /\b(?:experts?|observers?|analysts?|critics?|researchers?|industry reports?|studies|sources)\s+(?:say|says|argue|argues|believe|believes|note|notes|suggest|suggests|have cited|show|shows)\b/i,
78
+ name: "Vague attribution",
79
+ rule: "An unnamed authority standing in for evidence. Name the source or drop the claim.",
80
+ example:
81
+ '"Experts believe it plays a crucial role." / "Industry reports say the market doubled."',
82
+ },
83
+ {
84
+ id: "wasted-time-confession",
85
+ // Simple past plus a duration. The present perfect that the rule explicitly
86
+ // allows, "I have spent 10 years inside project tools", cannot match,
87
+ // because `spent` there is preceded by `have`.
88
+ test: /\bi\s+(?:spent|wasted|burned|lost)\s+(?:a|an|\d+|one|two|three|six)\s+(?:year|years|month|months|week|weeks|day|days)\b/i,
89
+ name: "Wasted-time confession",
90
+ rule: 'Simple past plus a duration plus the writer\'s own past foolishness, used as a credential or a closer: "I spent a year ...". The reader is asked to trade attention for a stranger\'s regret. Time served that built competence is a different sentence and is fine ("I have spent 10 years inside project tools"): present perfect, and the years bought something.',
91
+ example: '"I spent a year following up on people who were still inside their terms."',
92
+ },
93
+ {
94
+ id: "advisory-aside",
95
+ // The announcement, not the advice. Closed set of openers, each one a fixed phrase nobody writes by accident.
96
+
97
+ // "one thing I'd/I'll/I would" added 14 Sep 2026, Ramzi: "this sentence is pure AI slop 'One thing I'd revisit'" — the same self-assigned-critique announcement as "one thing worth",
98
+
99
+ // one pronoun over.
100
+ test: /\b(?:one thing worth|a quick note before|worth flagging|one last thing that|a small tip before|one thing (?:i'd|i'll|i would|i will))\b/i,
101
+ name: "Advisory aside",
102
+ rule: "The writer awarding themselves a bonus tip and announcing it before giving it. The announcement is pure throat-clearing and it grades the advice on the reader's behalf. State the thing.",
103
+ example:
104
+ '"One thing worth checking once: your own payment terms." / "Worth flagging: the Edge key expires."',
105
+ },
106
+ ];
@@ -0,0 +1,71 @@
1
+ /** Patterns read off a diagram caption and a script about spotting slop. */
2
+ export const patternsDiagramAndScriptTells = [
3
+ // --- Next 4: Ramzi, 28 Sep 2026, on the "Editor OS" diagram: "This is AI slop." Every line he
4
+ // pointed at passed this gate with no hit. ---
5
+ {
6
+ id: "not-because-pivot",
7
+ name: "Not because X. Because Y.",
8
+ // `negation-pivot` with the reason swapped in for the thing, but decidable: a sentence opening
9
+ // on "Not because" never opens anything else. The comma form is the same swap.
10
+ test: /(?:^|[.!?]["'’”)\]]?\s+|\n)(?:#{1,6}[ \t]+)?(?:(?:it|that)(?:'s|’s|\s+is|\s+was)\s+)?not\s+because\b[^.!?\n]{2,120}(?:[.!?]\s+|,\s+|\n)(?:(?:it|that)(?:'s|’s|\s+is|\s+was)\s+)?because\b/i,
11
+ rule: "A wrong reason set up so the right one can knock it down. The reader never held the wrong reason, so the first sentence is there for the drama. Give the real reason as a plain sentence.",
12
+ example:
13
+ '"Not because the editing got faster. Because the editing stopped being a decision." / "It\'s not because the tool is slow, it\'s because nobody owns it."',
14
+ },
15
+ {
16
+ id: "comma-then-slogan",
17
+ name: "Do X, then Y",
18
+ // 2 bare verb phrases hung on ", then", no subject, alone on a line, 7 words max: on the UpSys
19
+ // corpus, 28 Sep 2026, every longer hit was UI copy.
20
+ view: "prose",
21
+ test: /^[ \t]*(?=(?:[^\s\n]+[ \t]+){1,6}[^\s\n]+[ \t]*$)[\p{L}][^.!?\n,]{0,40},\s+then\s+[^.!?\n,]{2,40}[.!]?[ \t]*$/imu,
22
+ rule: 'Two orders on one line with a comma and a `then` between them. It reads like a product promise on a box and says nothing about what happens. Say who does what: "You install the layouts once. Every video after that starts at the record button."',
23
+ example:
24
+ '"Set up once, then press record" / "Record, then hand it over" / "Brief it once, then walk away."',
25
+ },
26
+ {
27
+ id: "in-out-pair",
28
+ name: "X goes in. Y comes out.",
29
+ test: /\b(?:goes|go|went|comes|come|came)\s+in\b[^.!?\n]{0,20}[.!?]\s+[^.!?\n]{2,60}\b(?:comes|come|came|goes|go|went)\s+out\b/i,
30
+ rule: "A machine drawn as a pipe with two ends. It skips everything the reader wanted to know, which is what happens in the middle. Name the steps, or the one step that matters.",
31
+ example:
32
+ '"A raw take goes in. A cut, dressed video comes out." / "Your notes go in. A finished deck comes out."',
33
+ },
34
+ {
35
+ id: "zero-tail",
36
+ name: "X, zero Y",
37
+ // Verbless: noun phrase, comma, `zero` and a noun phrase, or "No X, no Y". A bare `, no` tail
38
+ // only hit status labels ("Parked, no slot yet"), 28 Sep.
39
+ view: "prose",
40
+ test: /^[ \t]*(?:[^.!?\n,]{3,40},\s+zero|no\s+[^.!?\n,]{2,40},\s+no)\s+[\p{L}][^.!?\n,]{1,30}[.!]?[ \t]*$/imu,
41
+ rule: "A thing and a missing thing, joined by a comma, with no verb. It's a tagline, and the tag is always a cost the reader never mentioned. Write the sentence: \"The edit took under an hour and I didn't choose a single layout.\"",
42
+ example:
43
+ '"An hour of editing, zero decisions" / "No brand, no named pieces" / "Two clicks, zero setup"',
44
+ },
45
+ // The next 3 are Ramzi's, 2 Oct 2026, on a CutKit script about spotting AI slop that scored 94 and passed this gate with zero findings.
46
+ {
47
+ id: "once-twice-law",
48
+ name: "Once is craft. Twice is a tell.",
49
+ test: /\b(?:once|one\s+time|a\s+single\s+time)\s+(?:is|=)\s+(?:an?\s+)?[\w-]+(?:\s+[\w-]+)?[.,;]\s*(?:twice|two\s+times|again)\s+(?:is|=)\s+(?:an?\s+)?[\w-]+|\b(?:appears?|happens?|shows?\s+up)\s+once\s+(?:is|are)\s+[^.!?]{2,25},?\s+(?:while|but|and)\s+[^.!?]{2,40}\b(?:repeats?|twice|again)\b[^.!?]{0,20}\b(?:is|are)\b/i,
50
+ rule: 'A two-beat law: "once is X, twice is Y". It sounds like a rule and is a slogan, because the reader can\'t check it and it stands in for the sentence that would say what to look for. Say the thing: what repeats, how often, and what you do about it.',
51
+ example:
52
+ '"Once is craft. Twice is a tell." / "A shape that appears once is craft, while a shape that repeats is a tell"',
53
+ },
54
+ {
55
+ id: "while-antithesis",
56
+ name: "Real writing jumps, while raw AI stays flat",
57
+ view: "prose",
58
+ test: /[^.!?\n]{8,80},\s+while\s+(?:a|an|the|raw|real|good|bad|plain|your)\s+[^.!?\n,]{2,60}\s+(?:is|are|stays?|runs?|ships?|sits?|reads?|hides?|repeats?)\b/i,
59
+ rule: 'A comma, "while", and the mirror clause. The sentence exists to set two things side by side, and the contrast does the work a fact should. One per script is a style. Several is the shape of a model. Split it into two sentences and give each its own evidence, or drop the half that only mirrors the other.',
60
+ example:
61
+ '"Real writing jumps from 3 words to 40, while raw AI stays flat." / "Accuse a good writer and you lose them, while the real slop ships."',
62
+ },
63
+ {
64
+ id: "youd-think-setup",
65
+ name: "You'd think finding one word is proof",
66
+ test: /\byou(?:'|’)?d\s+(?:think|assume|expect)\b|\byou\s+(?:might|may|would)\s+(?:think|assume|expect)\b/i,
67
+ rule: 'A strawman handed to the reader so the next clause can correct it: "you\'d think X, but Y". It sets a belief up only to knock it over and attributes it to the viewer. State what is true, then the evidence. When a real belief needs breaking, name who holds it ("most agencies check for...").',
68
+ example:
69
+ '"You\'d think finding one word is proof, but a shape that appears once is craft." / "You might think it needs a rewrite"',
70
+ },
71
+ ];
@@ -0,0 +1,149 @@
1
+ import { FINITE_AUXILIARY, COUNT, SENTENCE_OPEN } from "#lint/data/slop/pattern-parts.js";
2
+
3
+ /** Patterns where the sentence drops the element a reader would check. */
4
+ export const patternsDroppedElements = [
5
+ {
6
+ id: "interrogative-heading",
7
+ name: "What the answer is built on",
8
+ // A whole line opening on an interrogative and ending on a verb, with no question mark: the heading asks the section's own question instead of naming what is in it.
9
+
10
+ // Distinct from `question-heading`, which needs the `?`.
11
+
12
+ // The trailing lookahead drops a hard-wrapped paragraph whose next line continues in lower case, and a line that ends in `.` is a sentence,
13
+
14
+ // not a heading, so it never reaches the anchor.
15
+ test: /^[ \t]*(?:#{1,6}[ \t]+)?(?:\d+ • )?(?:What|How|Where|Which|Why|When|Who)[ \t]+(?:[A-Za-zà-ÿ0-9][\w'’-]*[ \t]+){1,9}[a-zà-ÿ0-9][\w'’-]*[ \t]*$(?!\n[ \t]*[a-zà-ÿ])/m,
16
+ rule: 'A heading that restates the section as a question the reader did not ask, then leaves the answer to the body. Ramzi, 15 Sep 2026, on the CBAM calculator page, and 23 Sep 2026 on a Skool lesson card titled "What you take away from lesson 4". Name what the section holds.',
17
+ example: '"What the answer is built on" / "What Annex I covers" / "How the dataset is built"',
18
+ },
19
+ {
20
+ id: "positioned-noun-heading",
21
+ name: "The X beside the Y",
22
+ // A whole line made of two noun phrases and a position word, with no verb.
23
+
24
+ // Per line and anchored both ends, like `the-x-that-y-headline`, so the same words inside a sentence ("the rule beside the field is cited") are untouched.
25
+ test: /^[ \t]*(?:#{1,6}[ \t]+)?The[ \t]+[a-zà-ÿ-]+[ \t]+(?:beside|behind|beneath|below|above|under|over|inside|within|alongside|around|between|next[ \t]+to|across[ \t]+from|in[ \t]+front[ \t]+of|on[ \t]+top[ \t]+of)[ \t]+(?:the|its|your|each)[ \t]+[a-zà-ÿ-]+[ \t]*[.!?]?[ \t]*$/m,
26
+ rule: "A card title that places one noun next to another and stops, with no verb and nothing asserted. Ramzi, 15 Sep 2026, on the CBAM home cards. Say what the thing does where it sits.",
27
+ example: '"The rule beside the field" / "The source behind each number"',
28
+ },
29
+ {
30
+ id: "the-x-follows",
31
+ name: "When X changes, the Y follows",
32
+ // A "when" clause, a comma, then the product as the subject of a bare reaction verb that ends the sentence.
33
+
34
+ // The verb must close the sentence, so "the API follows the weekly price" or "and the API follows it" passes.
35
+ test: /\bwhen[ \t]+[^,.!?\n]{2,50},[ \t]*(?:the|our|your)[ \t]+[\w'’-]+[ \t]+(?:follows|adapts|keeps[ \t]+up|keeps[ \t]+pace|responds|moves[ \t]+with[ \t]+it)[ \t]*[.!]/i,
36
+ rule: 'A headline that casts a regulator as an actor and the product as its shadow, which reads as a slogan and never says what the product is. Ramzi, 16 Sep 2026, on the CBAM home headline: "too pompous". Name the category: what it is and what it is for.',
37
+ example:
38
+ '"When Brussels changes CBAM, the API follows." / "When the market moves, our data keeps up."',
39
+ },
40
+ // --- 17 Sep 2026. Three shapes named off one cold sales email, which opened "42 skills across seven folders" about a repo holding 26.
41
+
42
+ // One fault under all three: the sentence drops the element that would let a reader check it.
43
+
44
+ // The count has no verb, so nobody counted; the promise names the drawback and not the mechanism; the benefit names the reader's absence and not the work.
45
+
46
+ // Ramzi the same day, setting how they had to be written: "think about the heuristic of the grammar so that the rule is strong enough",
47
+
48
+ // "understand the first principles of the grammatical choice to fix the root cause".
49
+ {
50
+ id: "bare-count-clause",
51
+ name: "N things across M things",
52
+ /**
53
+ * Two counts, a distributive preposition, and no verb: a measurement parked where the predicate belongs.
54
+ */
55
+
56
+ /*
57
+ * The preposition set is closed to the three that can ONLY distribute.
58
+ */
59
+
60
+ /*
61
+ * `over` and `in` were measured and dropped: "159 comments and over 7,000 likes" and "28 emails over 10 days" are a comparative and a duration, and neither is this shape.
62
+ */
63
+
64
+ /*
65
+ * The clause has to CLOSE on a comma, a stop or "and", which is what leaves a verb out: "627 transcripts across 13 channels put the winner ahead" has a
66
+ */
67
+
68
+ /*
69
+ * predicate and passes.
70
+ */
71
+
72
+ /*
73
+ * 7 hits across 190 posts and 1,458 files, every one of them the shape.
74
+ */
75
+ test: new RegExp(
76
+ `(?:^|[.!?:;]["'’”)\\]]?\\**[ \\t]+|\\n)[ \\t]*\\**${COUNT}[ \\t]+(?:[a-zà-ÿ-]+[ \\t]+){0,2}[a-zà-ÿ]{3,}[ \\t]+(?:across|spanning|throughout)[ \\t]+${COUNT}[ \\t]+(?:[a-zà-ÿ-]+[ \\t]+){0,2}[a-zà-ÿ]{3,}\\**(?=[,.;!?]|[ \\t]+and[ \\t]|[ \\t]*\\n|[ \\t]*$)`,
77
+ ),
78
+ rule: 'A count, a second count, and no verb between them. Breadth stands in for the claim, and because nobody is said to have counted anything the number is never checked: the email that named this wrote "42 skills across seven folders" about a repo that shipped 26 in 6. Give the count a doer and a verb, and the reader can test it: "I counted 270 frames on ten channels", "the pack holds 86 face plates in 26 expressions".',
79
+ example:
80
+ '"42 skills across seven folders." / "30 agents across nine business areas." / "270 frames across ten channels, each split into two bands."',
81
+ },
82
+ {
83
+ id: "promise-minus-drawback",
84
+ name: "X without the Y",
85
+ /**
86
+ * A noun phrase, a privative `without`, and still no verb. The hair-dye frame: "colour without the ammonia".
87
+ */
88
+
89
+ /*
90
+ * Three things keep it off ordinary English, and each was measured.
91
+ */
92
+
93
+ /*
94
+ * The head noun cannot end in `s`, `d` or `ing`, so a finite verb in that slot fails ("The build runs without the dev server").
95
+ */
96
+
97
+ /*
98
+ * No auxiliary may sit in the filler, so a copula fails ("The output is right without the correction").
99
+ */
100
+
101
+ /*
102
+ * And the `without` phrase has to end the clause, so a predicate after it fails ("A feature tour without the claim line is a screen recording").
103
+ */
104
+
105
+ /*
106
+ * 0 hits across 190 posts and 1,458 files.
107
+ */
108
+ test: new RegExp(
109
+ `${SENTENCE_OPEN}\\**(?:[Aa]ll[ \\t]+the|[Tt]he|[Aa]|[Aa]n|[Yy]our|[Oo]ur|[Ee]very)[ \\t]+(?:(?!without[ \\t])(?!${FINITE_AUXILIARY}[ \\t])[a-zà-ÿ-]+[ \\t]+){0,3}[a-zà-ÿ]{2,}(?<![sd])(?<!ing)[ \\t]+without[ \\t]+(?:the|a|an|any)[ \\t]+(?:[a-zà-ÿ-]+[ \\t]+){0,2}[a-zà-ÿ-]{3,}\\**[ \\t]*(?=[,.;!?]|[ \\t]*\\n|$)`,
110
+ ),
111
+ rule: "A thing offered, then the drawback it does not have, and nothing about how. It is the negation pivot sold as a benefit: the reader learns what is absent, never what is present, and a competitor's weakness carries the sentence instead of the product. Say what it does. If the missing drawback is the point, name the mechanism that removes it.",
112
+ example:
113
+ '"A hair colour change without the ammonia." / "All the power without the complexity." / "The reach of an agency without the retainer."',
114
+ },
115
+ {
116
+ id: "absentee-benefit",
117
+ name: "While you sleep",
118
+ /**
119
+ * The work described as happening in the reader's absence, which is the value the sentence offers instead of a mechanism.
120
+ */
121
+
122
+ /*
123
+ * Second person is required throughout, because the state words are ordinary on their own: Ramzi's own 2023 post, "90% is on autopilot until I start the client project",
124
+ */
125
+
126
+ /*
127
+ * is a bounded fact about a system and is spared.
128
+ */
129
+
130
+ /*
131
+ * "so you do not have to" needs the clause to END there, for the same reason: the ellipsis IS the fault, and naming the work makes the sentence informative ("so
132
+ */
133
+
134
+ /*
135
+ * you do not have to open it").
136
+ */
137
+ test: new RegExp(
138
+ `\\bwhile[ \\t]+you(?:[ \\t]+(?:are|'re|’re))?[ \\t]+(?:sleep\\w*|asleep|rest\\w*|relax\\w*|away|off|out|do[ \\t]+nothing|focus[ \\t]+on|get[ \\t]+on[ \\t]+with)\\b` +
139
+ `|\\bin[ \\t]+your[ \\t]+sleep\\b` +
140
+ `|\\b(?:you|your)\\b[^.!?\\n]{0,60}\\bon[ \\t]+autopilot\\b|\\bon[ \\t]+autopilot\\b[^.!?\\n]{0,60}\\b(?:you|your)\\b` +
141
+ `|\\bset[ \\t]+it[ \\t]+and[ \\t]+forget[ \\t]+it\\b` +
142
+ `|\\bso[ \\t]+you[ \\t]+(?:do[ \\t]*not|don'?t|never|no[ \\t]+longer)[ \\t]+have[ \\t]+to[ \\t]*(?=[.!?,;]|[ \\t]*$)`,
143
+ "i",
144
+ ),
145
+ rule: 'The benefit stated as the reader\'s absence. It says when the work happens and never what does it, so the sentence cannot be wrong and cannot be checked, and it carries the passive-income register every ad in the category uses. Ramzi, 17 Sep 2026, on "A calendar takes them while you sleep": the subject is an abstraction and "while you" is slop. Name what runs and what it does: "the booking page writes the event to your calendar".',
146
+ example:
147
+ '"A calendar takes them while you sleep." / "It books the call while you focus on the work." / "Your pipeline runs on autopilot." / "The agent files them so you do not have to."',
148
+ },
149
+ ];