feat: update and expand rule set - #2
Conversation
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.
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
Several rules compile cleanly but never fire at runtimeTesting this branch (
Root causeTwo consistent mechanisms:
Suggested fixesPer rule:
OptionsWhichever fits the workflow:
Happy to go whichever way. I'm leaning toward option 1 so I don't hold up your release, but it's your call. |
ReviewRan both rulesets over Warning-level findings drop 848 → 330. That part works. Four things block the tag. 1. Scope and
|
| 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\bcannot 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: rawbypasses apify-docs' deliberateIgnoredScopes: alt. All eight new alt-text rules aresuggestion, and apify-docs runs atMinAlertLevel = 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 click → select 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\bmatches "to to" and "the the" and does not match "It is".Repetitiondoes not need a word list — and a flat list produces false positives, since cross-product pairs match "It is". (?m)works. ForHeadingDepth, use(?m)^#{4,}.\n#{4,}misses a level-4 heading on line 1.CodeFenceLanguage:\n```\s*\nmatches closing fences, including those of correctly tagged blocks.```jsongets flagged.\n\n```[ \t]*\nmatched 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
|
@TC-MO fixes are in, please review updates. |
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.
AltTextMissingPeriodis introduced at warning level for sentence-like alt text; short fragment alt text stays quiet.New rules:
Pattern fixes:
Review follow-ups:
vocab: falsewhere they regressed.DashAsAsidestrict for spaced hyphens used as separators or asides.AltTextMissingPeriodto warning for sentence-like alt text.DeviceAgnosticVerbs.Validation:
copy-lint-internalVale tooling from a temporary workspace.