Skip to content

feat: update and expand rule set - #2

Open
jgagne wants to merge 11 commits into
mainfrom
feat/rule-sync
Open

feat: update and expand rule set#2
jgagne wants to merge 11 commits into
mainfrom
feat/rule-sync

Conversation

@jgagne

@jgagne jgagne commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

New rules across all four styles (ApifyContent, ApifyDocs, ApifyUI, Apify). Updated existing rules with expanded patterns, improved messages, and false positive fixes.

One rule removed (PlusInProse, superseded by SpellOutAnd). No existing rule severity promotions. AltTextMissingPeriod is introduced at warning level for sentence-like alt text; short fragment alt text stays quiet.

New rules:

  • Apify: AcronymCase, MixedRegister, OrphanedConditional, Readability, SelfCongratulatory, SpellOutAnd, SubjectlessConsequence
  • ApifyContent: README-specific checks (pricing format, input tab phrasing, placeholder output, platform scaling filler, backticked product names, editorial process residue)
  • ApifyDocs: alt text quality, description/heading redundancy
  • ApifyUI: gerund detection, blame language, verbose confirmations, performative emotion, ambiguous connectors, compliance language, currency format, enter-human-noun placeholder pattern

Pattern fixes:

  • FillerWords: word boundary around 'very' to prevent mid-word matches
  • AcronymCase: exclude ICU select case keys from matching
  • Brands: remove terms now covered by vocabulary (HTTP/HTTPS, URL, API, JSON, HTML, CLI, OAuth, GitHub)
  • Message punctuation standardized across ~80 rules

Review follow-ups:

  • Fixed rules that compiled but didn't fire in Vale.
  • Restored scoped matching and vocab: false where they regressed.
  • Removed duplicate and high-noise rules.
  • Kept DashAsAside strict for spaced hyphens used as separators or asides.
  • Changed AltTextMissingPeriod to warning for sentence-like alt text.
  • Restored edit suggestions for DeviceAgnosticVerbs.

Validation:

  • Ran focused Vale fixtures for restored scopes, dead-rule fixes, duplicate removals, dash handling, em dash handling, and alt text punctuation.
  • Confirmed all four styles load and rule filenames are unique.
  • Checked the local package against copy-lint-internal Vale tooling from a temporary workspace.

@jgagne jgagne self-assigned this Aug 14, 2026
@jgagne jgagne added the enhancement New feature or request. label Aug 14, 2026
New rules:
- ApifyContent: README-specific checks (pricing format, input tab phrasing,
  placeholder output, platform scaling filler, backticked product names)
- ApifyDocs: alt text quality (8 rules), description/heading redundancy
- ApifyUI: gerund detection, blame language, verbose confirmations,
  performative emotion, ambiguous connectors, compliance language

No severity promotions or removals.
@jgagne
jgagne marked this pull request as ready for review August 14, 2026 14:44
@jgagne
jgagne requested a review from TC-MO August 14, 2026 14:44
jgagne added 4 commits August 14, 2026 16:58
Catches phrases that cite a UI surface as evidence rather than stating facts directly. Source provenance belongs in issues files, not public copy.

Patterns detected:
- The [surface] says/shows/lists...
- According to the [surface]...
- As listed/shown/described on/in the [surface]...
- Based on the [surface]...

Surfaces: Pricing tab, Store page, Input tab, API docs, input schema, output schema, OpenAPI schema, Store listing, README.
New rules:
- Apify/AcronymCase: flags lowercase acronyms that should be uppercase in prose
- Apify/MixedRegister: detects formal/informal register mixing in the same string
- Apify/OrphanedConditional: catches conditionals with no stated consequence
- Apify/Readability: Flesch-Kincaid readability metric check
- Apify/SelfCongratulatory: flags self-promotional language in product copy
- Apify/SpellOutAnd: catches '+' and '&' used as prose connectors
- Apify/SubjectlessConsequence: consequence clause with no subject
- ApifyContent/EditorialProcessResidue: residue phrases from editing workflows
- ApifyUI/CurrencyFormat: currency formatting consistency
- ApifyUI/EnterHumanNoun: 'Enter your [noun]' placeholder pattern check

Removed:
- Apify/PlusInProse: superseded by SpellOutAnd (broader coverage, exception list)

Pattern fixes:
- FillerWords: add word boundary around 'very' to prevent mid-word matches
- AcronymCase: exclude ICU select case keys (e.g. 'html {HTML Table}') from matching
- Brands: remove http/https, url/urls, api/apis, json, html, cli, oauth, github (covered by vocabulary)
- WeakQualifiers and others: standardise message punctuation (dash to colon)
PlainLanguage: shadowed by AcademicRegister and BureaucraticVoice, which fire first:
- subsequently
- Additionally,
- Furthermore,
- Moreover,
- Consequently,

Brands: duplicate of ApifyProductNames, producing double findings on the same string:
- apify dashboard
- apify console
- apify store
- apify proxy
- apify platform
@jgagne

jgagne commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Several rules compile cleanly but never fire at runtime

Testing this branch (feat/rule-sync) against real content, I found a class of rules that pass validation and review but silently produce zero matches in Vale. I ran Vale directly from this branch against inputs that should trigger each rule. These are confirmed live, not inferred:

Rule Test input Hits Root cause
Apify/CodeFenceLanguage a bare ``` fence 0 ^```\s*$. The ^/$ don't behave as line anchors in scope: raw
Apify/LatinParenStrip Use a proxy (e.g. residential) 0 tokens end in a space ('\(e\.g\.,? ') without nonword: true, so Vale's auto-\b can't form after a trailing space
Apify/Repetition We need to to update it 0 extends: repetition with a regex exceptions entry doesn't fire in Vale 3.x
Apify/HeadingDepth #### Deep heading 0 ^#{4,} needs line-anchor behavior it doesn't get
Apify/LatinSentenceStart It is fast. E.g. it scales 0 lookbehind (?:^|\.\s+) anchoring
ApifyUI/HTMLEntities The Actor's output 0 extends: substitution without scope: raw, so Vale decodes entities before matching

Root cause

Two consistent mechanisms:

  1. \b wrapping. Vale wraps existence and substitution tokens in \b unless nonword: true is set. Any token that ends in a non-word character (space, ., and so on) can never match, because a \b boundary needs a word/non-word transition that isn't there.
  2. Markup pre-processing. Vale decodes HTML entities and strips markup before rules run. Patterns that need the raw source (entities, code fences, **bold**) have to use scope: raw.

Suggested fixes

Per rule:

  • CodeFenceLanguage: match \n```\s*\n instead of ^```\s*$.
  • LatinParenStrip: add nonword: true.
  • Repetition: rewrite as an existence rule with an explicit repeated-word list (backreferences aren't supported in Vale's regex engine).
  • HeadingDepth: match \n#{4,} instead of ^#{4,}, with scope: raw.
  • LatinSentenceStart: split the lookbehind into separate tokens (E\.g\. and \. E\.g\.).
  • HTMLEntities: add scope: raw.

Options

Whichever fits the workflow:

  1. Merge as-is and I open a follow-up PR against main with these 6 fixes once this lands, so your release isn't blocked.
  2. I open a PR into feat/rule-sync with the fixes if you'd rather release it in one clean version.
  3. You take it from here. The per-rule fixes are all above if you'd prefer to apply them yourself.

Happy to go whichever way. I'm leaning toward option 1 so I don't hold up your release, but it's your call.

@TC-MO

TC-MO commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review

Ran both rulesets over apify-docs/sources (375 files) with apify-docs' own IgnoredScopes and Docs vocab, plus isolated single-rule harnesses for all 45 new rules. Vale 3.20.0. Counts are measured.

Warning-level findings drop 848 → 330. That part works. Four things block the tag.

1. Scope and vocab: false dropped from 14 rules

cbceb9e removed scope: from 15 files and vocab: false from 4. e91dbe2 restored two (ActorCapitalization, PricingProvenance). The rest still ship changed.

Rule Before After Level
Apify/Typos [summary, heading] (none) error
Apify/ApifyProductNames [summary, heading] + vocab: false heading warning
Apify/TechnologyNames [summary, heading] + vocab: false heading warning
Apify/Brands [summary, heading] + vocab: false (none) warning
ArticleSound, DeviceAgnosticVerbs, DoubleHedge, EmDashAvoidance, Grammar, InclusiveLanguage, LatinSentenceStart, Numbers, Punctuation, Spelling narrow (none) mostly warning

Three consequences:

A new blocking error. Typos is unscoped at error, and this PR adds goto: 'go to'. It fires on academy/tutorials/apify_scrapers/puppeteer_scraper.md:714**Pre goto function** in the **Input and options** section — a real Puppeteer Scraper field name. Errors go 11 → 12. The new '\?\?': '?' swap carries the same risk for nullish coalescing written outside backticks.

Product-name casing is no longer checked in apify-docs. ApifyProductNames is now scope: heading; apify-docs sets IgnoredScopes = ..., heading, so the rule is inert there: 35 → 0. 1b46c80 separately removed apify console / store / proxy / dashboard / platform from Brands as duplicates of it. Neither rule covers them now. TechnologyNames: 13 → 0.

Ten rules widened to whole-text. Brands goes 0 → 76 warnings, ~17 of them false positives: xml ×12 (every one a sitemap.xml or index.xml filename), rest ×1 ("encrypted at rest"), AI Agents ×4 (external link titles).

Fix: restore scope: and vocab: false on the 14 files. Re-run to confirm errors hold at 11 and ApifyProductNames returns to 35.

2. Three new rules never fire

Verified in isolation — single-rule style, minimal input, zero matches.

Rule Cause Fix
Apify/AcronymCase space inside (?![/.:@\w{ ]), so json and api can never match split the guard: (?![/.:@\w])(?! ?\{)
ApifyUI/CurrencyFormat trailing $/ against Vale's implicit \b nonword: true
ApifyUI/UnnecessaryBreak <br> stripped before matching scope: raw, and (?m)^ — under raw, ^ anchors to the document, not the line

AcronymCase returns 10 hits across all of apify-docs, against hundreds of lowercase api/url/json in prose. It only matches before a comma or end of line.

3. Three duplicate files

New file Duplicates Difference
ApifyContent/VagueQuantities.yml ApifyContent/ConcreteNumbers.yml byte-identical
ApifyDocs/AltTextFilename.yml Apify/AltTextFilename.yml byte-identical
ApifyDocs/ImageAltText.yml Apify/ImageAltText.yml same token, suggestion vs error

Every match reports twice. 1b46c80 removes duplicates elsewhere for the same reason.

4. False-positive rates on real content

Delta on apify-docs, main → this branch:

  • ApifyDocs/DoubleParticiple +336. Sampled hits are ordinary English: "rising or falling", "growing and evolving", "browsing or searching", "matching the following". \b\w+ing\s+\w+(?:s|es)?\s+\w+ing\b cannot separate the target pattern from normal coordination. Drop it.
  • ApifyDocs/DescriptionRestatesHeading +29. The name promises a heading/description comparison; the tokens flag any definitional opener — "Webhooks are a…", "Proxies are one…", "Games are extremely…". Rename to match the behaviour, or drop.
  • ApifyDocs/AltTextMissingPeriod +532. scope: raw bypasses apify-docs' deliberate IgnoredScopes: alt. All eight new alt-text rules are suggestion, and apify-docs runs at MinAlertLevel = warning, so none of them surface there. Decide whether they are meant to be enforced.
  • ApifyContent/ContrastiveFormulas. Token "It's not (?:just |merely |simply )?" — the trailing ? makes the group optional, so it matches every "It's not …". Confirmed on "It's not available on the free plan."
  • Apify/SpellOutAnd +116. Most hits are UI labels and section titles: "Import & Export", "Publication & monetization". Widen the exceptions.

5. Two rewrites worth flagging in the release notes

DeviceAgnosticVerbs moves from substitution to existence (394 → 120). Editors lose the clickselect fix action, and coverage narrows to nine literal phrases.

GerundHeading exception ^[A-Z][a-z]+ing your passes "Configuring your proxy" but flags "Configuring the proxy".

"No severity promotions" checks out — no level: changed on any pre-existing rule.

6. On your six dead rules

All six confirmed dead on this branch. Three corrections to the fixes:

  • Backreferences work in Vale 3.20. \b(\w+)\s+\1\b matches "to to" and "the the" and does not match "It is". Repetition does not need a word list — and a flat list produces false positives, since cross-product pairs match "It is".
  • (?m) works. For HeadingDepth, use (?m)^#{4,} . \n#{4,} misses a level-4 heading on line 1.
  • CodeFenceLanguage: \n```\s*\n matches closing fences, including those of correctly tagged blocks. ```json gets flagged. \n\n```[ \t]*\n matched only the bare opening fence in my test.

LatinParenStrip: nonword: true is right, though the cause is the leading \b\(, not the trailing space. HTMLEntities with scope: raw: confirmed.

Recommendation

Option 2 — fix in feat/rule-sync, release once. The six dead rules are not what blocks this; sections 1–3 are, and all of them are in this diff. Tagging as-is adds a blocking error to apify-docs and drops product-name casing coverage there, both harder to reverse after a release than before one.

- Update nonword token boundaries for punctuation and currency matches
- Update raw-scope anchors so line-based rules can fire
- Split regex patterns that failed on lookbehind or spacing
- Add raw matching where markup preprocessing hid the source text
- Verify the affected rules with local Vale examples
- Restore scope settings dropped from shared Apify rules
- Restore vocab false for product and technology name checks
- Restore edit suggestions for device-neutral verb guidance
- Remove the noisy goto typo replacement
- Reduce false positives from whole-document matching
- Remove duplicate VagueQuantities, AltTextFilename, and ImageAltText rules
- Remove broad DoubleParticiple and DescriptionRestatesHeading rules
- Keep each rule single-sourced across the shipped styles
- Reduce repeated and misleading Vale output
- Update EmDashAvoidance to ignore paired parenthetical dashes
- Update SpellOutAnd exceptions for labels and headings
- Update ContrastiveFormulas to require a contrast qualifier
- Update AltTextMissingPeriod to enforce sentence-like alt text
- Update GerundHeading to treat your and article headings consistently
- Keep DashAsAside strict for spaced hyphens between words
- Preserve quiet handling for ranges, CLI flags, and Markdown list markers
- Update alt text checks to recognize common sentence forms
- Remove code examples from alt text matching
- Track opening and closing code fences
- Preserve link wording and editor replacements
- Add focused Vale regression tests
@jgagne

jgagne commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

@TC-MO fixes are in, please review updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants