no-buzzword-stacks |
Flag sentences overloaded with abstract benefit nouns |
alignment, automation, collaboration, efficiency, engagement, experience, growth, impact, innovation, intelligence, optimization, platform, productivity, solution, strategy, transformation, value, velocity, workflow (19) |
terms?: string[], maxTermsPerSentence?: number (default 2) |
warn |
no-empty-transformation-claims |
Flag broad transformation cliches without a concrete outcome |
3 hard-coded regex patterns (transform/change/reimagine/revolutionize the way…; unlock your potential/productivity/etc.; take your X to the next level) |
allowedPhrases?: string[] (normalized lowercased exact-phrase opt-out) |
warn |
no-expletive-openers |
Flag sentence openings that delay the real subject |
there is, there are, there was, there were, there will be |
phrases?: string[] |
warn |
no-filter-words |
Ban filter phrases that distance the claim from the reader |
I think, it seems, basically, in order to |
phrases?: string[] |
error ⚠️ |
no-nominalized-phrases |
Flag nominalized X of Y phrases |
suffixes: tion, sion, ment, ance, ence, ity / allowed words: accessibility, availability, capacity, community, identity, opportunity, privacy, quality, reliability, security |
suffixes?: string[], allowedWords?: string[] |
warn |
no-passive-voice |
Flag likely passive-voice constructions via compromise POS tags |
allowed auxiliaries: is, are, was, were, be, been, being |
allowedAuxiliaries?: string[] |
warn |
no-pronoun-led-claims |
Flag vague claims starting with it/this/that/these/those + verb |
pronouns: it, this, that, these, those / verbs: brings, delivers, enables, gives, helps, keeps, lets, makes, turns, unlocks |
pronouns?: string[], verbs?: string[] |
warn |
no-redundant-pairs |
Flag redundant fixed phrases |
first and foremost, each and every, various different, end result, final outcome, past history, future plans, unexpected surprise, advance planning |
phrases?: string[] |
warn |
no-stacked-adjectives |
Flag noun phrases with multiple adjectives |
n/a — POS pattern #Adjective #Adjective+ #Noun |
allowedPhrases?: string[] |
warn |
no-weak-modals |
Flag hedged modal claims like can help, might improve |
modals: can, could, may, might / verbs: boost, drive, enable, help, improve, increase, make, reduce, support, transform, unlock |
modals?: string[], verbs?: string[] |
warn |
Faircopy Rule Coverage Audit — 2026-05-10
Snapshot of every rule on
omniaura/faircopyorigin/mainafter the 4 rule-batch shipped this round, with test/doc coverage observations and recommended follow-ups.Audit basis:
82c0167 chore(release): 1.8.0 [skip ci]packages/rules-nlp/src/*.ts,packages/rules-default/src/*.tspackages/rules-nlp/test/rules.test.mjs(only test file in the repo)README.md,packages/rules-nlp/README.md,packages/rules-default/README.mdResearch only — no source files modified.
1. Rule Inventory
1a.
@faircopy/rules-default(3 rules, all defaulterror)no-em-dash—flagEnDash?: boolean(defaultfalse),flagDoubleHyphen?: boolean(defaultfalse)errorno-weasel-wordsactually,truly,really,literallywords: string[](replaces defaults when non-empty)errorno-rhetorical-scaffoldingX is Y, not ZandWithout X / With XpatternsallowIsNotConstruction?: boolean(defaultfalse),allowWithoutWithConstruction?: boolean(defaultfalse),extraPatterns?: string[]error1b.
@faircopy/rules-nlp(10 rules, mostlywarn)no-buzzword-stacksalignment, automation, collaboration, efficiency, engagement, experience, growth, impact, innovation, intelligence, optimization, platform, productivity, solution, strategy, transformation, value, velocity, workflow(19)terms?: string[],maxTermsPerSentence?: number(default2)warnno-empty-transformation-claimsallowedPhrases?: string[](normalized lowercased exact-phrase opt-out)warnno-expletive-openersthere is, there are, there was, there were, there will bephrases?: string[]warnno-filter-wordsI think, it seems, basically, in order tophrases?: string[]errorno-nominalized-phrasesX of Yphrasestion, sion, ment, ance, ence, ity/ allowed words:accessibility, availability, capacity, community, identity, opportunity, privacy, quality, reliability, securitysuffixes?: string[],allowedWords?: string[]warnno-passive-voiceis, are, was, were, be, been, beingallowedAuxiliaries?: string[]warnno-pronoun-led-claimsit/this/that/these/those + verbit, this, that, these, those/ verbs:brings, delivers, enables, gives, helps, keeps, lets, makes, turns, unlockspronouns?: string[],verbs?: string[]warnno-redundant-pairsfirst and foremost, each and every, various different, end result, final outcome, past history, future plans, unexpected surprise, advance planningphrases?: string[]warnno-stacked-adjectives#Adjective #Adjective+ #NounallowedPhrases?: string[]warnno-weak-modalscan help,might improvecan, could, may, might/ verbs:boost, drive, enable, help, improve, increase, make, reduce, support, transform, unlockmodals?: string[],verbs?: string[]warnno-filter-wordsdefaults toerrorwhile every other NLP rule defaults towarn. Likely intentional (it’s a strict ban) but worth confirming and documenting.2. Test Coverage Observations
Single test file:
packages/rules-nlp/test/rules.test.mjs(200 lines, ~21 cases).2a. Per-rule test matrix
no-em-dashno-weasel-wordsno-rhetorical-scaffoldingno-buzzword-stacksmaxTermsPerSentencethresholdno-empty-transformation-claimsallowedPhrasesno-expletive-openersphrasesoverride testno-filter-wordsno-nominalized-phrasesallowedWordscontainingsecurity) — no explicit override testno-passive-voiceno-pronoun-led-claimsverbsoverrideno-redundant-pairsphrasesoverrideno-stacked-adjectivesallowedPhrasesno-weak-modalsmodals/verbsoverride test2b. Cross-cutting gaps
['warn'|'error', { ...options }]config tuple via@faircopy/coreconfig loader. Tests callrule.check()directly, so a regression in config-tuple parsing would not be caught here.no-stacked-adjectives: onlymatches message text — no exact range assertion. Easy fix.Array.from({ length }, (_, i) => i)). No test exercises the offset translation that real adapters (e.g.,@faircopy/astro) feed in. Adapter mapping bugs would slip through.terms?: [],phrases?: [], etc. Several rules treat empty arrays as "fall back to defaults" via?.lengthchecks — that contract is not asserted.rules-defaultis fully untested: zero unit tests exist for the three shipped-by-default rules. Theno-rhetorical-scaffoldingregex in particular is intricate (multiline, sentence-bounded) and has no regression coverage.ruleRegistrysmoke test (line 188) checks all 10 NLP rule IDs are present. No equivalent registry test exists forrules-default.3. Documentation Status
3a.
packages/rules-nlp/README.mdmaxTermsPerSentence,allowedPhrases,allowedWords,phrases,modals,verbs,suffixes). A user cannot tune any rule from the README alone.no-filter-words: errorvs the restwarn).allowedPhrases(must be exact normalized lowercase match — a non-obvious gotcha forno-empty-transformation-claimsandno-stacked-adjectives).3b. Root
README.mdno-empty-transformation-claimsandno-redundant-pairs(both shipped as part of recent batches). The example config block has the same omission.[severity, options]tuples.3c.
packages/rules-default/README.md3d. faircopy.dev
4. Recommendations — top 5 highest-payoff follow-ups
Ranked by payoff per unit of risk/effort. All are low-risk and avoid colliding with the in-flight rule batch.
Add
rules-defaulttest suite (highest payoff). Mirror the shape ofpackages/rules-nlp/test/rules.test.mjsfor the 3 default rules. Cover: each pattern fires; option toggles disable patterns (allowIsNotConstruction,allowWithoutWithConstruction,flagEnDash,flagDoubleHyphen);extraPatternswork; ranges are correct. These rules shiperrorby default — a regression here breaks every consumer's CI.Sync root
README.mdNLP rules table. Addno-empty-transformation-claimsandno-redundant-pairsto both the example config block and the rules table. Drift between root README and package README will only grow as more rules land.Document rule options in
packages/rules-nlp/README.md. Add a per-rule options block (mirror therules-defaultREADME format). Without this, every option is effectively undocumented and users have to read source. Especially important for the non-obvious ones:allowedPhrases(exact match),maxTermsPerSentence,allowedWordsforno-nominalized-phrases.Add a fixtures benchmark / snapshot test. Create
packages/rules-nlp/test/fixtures/with 3–5 realistic marketing copy samples (hero, feature grid, FAQ, pricing, landing-page paragraph). One test asserts{ ruleId → diagnostic count }per fixture stays stable across releases. Catches accidental rule-broadening when phrase lists or POS patterns are tweaked, and gives a single place to eyeball false-positive rates. (See Rind's parallel issue task feat: add NLP rules package and dynamic rule loading #2 — same idea, different lane.)Add opt-out / option-override tests for the 4 under-tested rules:
no-filter-words(customphrases),no-passive-voice(customallowedAuxiliaries),no-expletive-openers(customphrases),no-weak-modals(custommodals/verbs). Each is a 5-10 line test; together they bring the NLP package to uniform option-coverage.Honorable mentions (lower priority)
no-nominalized-phrases— make theallowedWordsoverride explicit.no-stacked-adjectivestest (currently only message-matched).rules-defaultregistry export + test analogous to the NLPruleRegistry.no-filter-wordsseverity asymmetry (errorvs the restwarn).@faircopy/astrointegration test) so adapter offset translation has at least one regression guardrail.5. Out of Scope This Window
no-vague-quantifiers,no-hedge-words,no-buzzword-stacksPR feat(rules-nlp): flag hedge words #13).@faircopy/coreconfig loader internals not audited — only rule sources were inspected.