@faircopy/rules-nlp 1.16.0 → 1.17.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 CHANGED
@@ -63,6 +63,7 @@ Package-qualified IDs like `@faircopy/rules-nlp/no-passive-voice` still work and
63
63
  | `no-vague-quantifiers` | Flag bare quantifiers without numeric anchors |
64
64
  | `no-meaningless-modifiers` | Flag intensifiers like `very` and `obviously` that add no information |
65
65
  | `no-superlative-claims` | Flag unproven superlatives like `best` and `world-class` |
66
+ | `no-non-inclusive-language-nlp` | Flag non-inclusive terms and suggest neutral alternatives |
66
67
  | `sentence-complexity` | Flag sentences that exceed a word or clause threshold |
67
68
 
68
69
  ### `no-absolute-intensifiers`
@@ -249,3 +250,46 @@ Suggested fix: keep the strongest qualifier or replace the phrase with concrete
249
250
  ```text
250
251
  The result is excellent.
251
252
  ```
253
+
254
+ ### `no-non-inclusive-language-nlp`
255
+
256
+ Non-inclusive terms can alienate readers. This rule flags common non-inclusive language and suggests neutral alternatives using NLP-aware matching so it understands word boundaries and part-of-speech context.
257
+
258
+ ```ts
259
+ rules: {
260
+ 'no-non-inclusive-language-nlp': 'warn',
261
+ }
262
+ ```
263
+
264
+ Options:
265
+
266
+ | Option | Type | Default | Description |
267
+ |---|---|---|---|
268
+ | `terms` | `NonInclusiveTerm[]` | See source | Terms to flag, each with a list of alternatives |
269
+ | `allowedTerms` | `string[]` | `[]` | Terms to allow and skip |
270
+
271
+ Example with custom terms:
272
+
273
+ ```ts
274
+ 'no-non-inclusive-language-nlp': ['warn', {
275
+ terms: [
276
+ { term: 'guys', alternatives: ['everyone', 'team', 'folks'] },
277
+ { term: 'blacklist', alternatives: ['denylist', 'blocklist'] },
278
+ ],
279
+ allowedTerms: ['whitelist'],
280
+ }]
281
+ ```
282
+
283
+ Flagged example:
284
+
285
+ ```text
286
+ Hey guys, add this to the blacklist and do a sanity check.
287
+ ```
288
+
289
+ Suggested fix:
290
+
291
+ ```text
292
+ Hey everyone, add this to the blocklist and do a quick check.
293
+ ```
294
+
295
+ The rule uses `compromise` to avoid partial-word matches (e.g., `guyses` is not flagged) and skips verb-only usages of ambiguous words such as `master` in `master the skill`.
package/dist/index.d.ts CHANGED
@@ -65,6 +65,20 @@ interface NoMeaninglessModifiersOptions {
65
65
  }
66
66
  declare const noMeaninglessModifiers: Rule<NoMeaninglessModifiersOptions>;
67
67
 
68
+ interface NonInclusiveTerm {
69
+ term: string;
70
+ alternatives: string[];
71
+ /** Require the whole phrase to match with word boundaries. Default false. */
72
+ exact?: boolean;
73
+ }
74
+ interface NoNonInclusiveLanguageOptions {
75
+ /** Terms to flag with suggested alternatives. */
76
+ terms?: NonInclusiveTerm[];
77
+ /** Terms to allow and skip. */
78
+ allowedTerms?: string[];
79
+ }
80
+ declare const noNonInclusiveLanguage: Rule<NoNonInclusiveLanguageOptions>;
81
+
68
82
  interface NoNominalizedPhrasesOptions {
69
83
  suffixes?: string[];
70
84
  allowedWords?: string[];
@@ -163,4 +177,4 @@ declare const sentenceComplexity: Rule<SentenceComplexityOptions>;
163
177
  /** All NLP rules keyed by their rule ID. */
164
178
  declare const ruleRegistry: Map<string, Rule>;
165
179
 
166
- export { type NoAbsoluteIntensifiersOptions, type NoAdverbOveruseOptions, type NoBuzzwordStacksOptions, type NoComplexReadabilityOptions, type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoFuturePromisesOptions, type NoHedgeWordsOptions, type NoJargonOptions, type NoMeaninglessModifiersOptions, type NoNominalizedPhrasesOptions, type NoOverlyComplexSentencesOptions, type NoOverusedAdverbsOptions, type NoPassiveVoiceOptions, type NoPronounLedClaimsOptions, type NoQualifierCreepOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoSuperlativeClaimsOptions, type NoVagueComparativesOptions, type NoVagueQuantifiersOptions, type NoWeakModalsOptions, type SentenceComplexityOptions, noAbsoluteIntensifiers, noAdverbOveruse, noBuzzwordStacks, noComplexReadability, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noFuturePromises, noHedgeWords, noJargon, noMeaninglessModifiers, noNominalizedPhrases, noOverlyComplexSentences, noOverusedAdverbs, noPassiveVoice, noPronounLedClaims, noQualifierCreep, noRedundantPairs, noStackedAdjectives, noSuperlativeClaims, noVagueComparatives, noVagueQuantifiers, noWeakModals, ruleRegistry, sentenceComplexity };
180
+ export { type NoAbsoluteIntensifiersOptions, type NoAdverbOveruseOptions, type NoBuzzwordStacksOptions, type NoComplexReadabilityOptions, type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoFuturePromisesOptions, type NoHedgeWordsOptions, type NoJargonOptions, type NoMeaninglessModifiersOptions, type NoNominalizedPhrasesOptions, type NoNonInclusiveLanguageOptions, type NoOverlyComplexSentencesOptions, type NoOverusedAdverbsOptions, type NoPassiveVoiceOptions, type NoPronounLedClaimsOptions, type NoQualifierCreepOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoSuperlativeClaimsOptions, type NoVagueComparativesOptions, type NoVagueQuantifiersOptions, type NoWeakModalsOptions, type SentenceComplexityOptions, noAbsoluteIntensifiers, noAdverbOveruse, noBuzzwordStacks, noComplexReadability, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noFuturePromises, noHedgeWords, noJargon, noMeaninglessModifiers, noNominalizedPhrases, noNonInclusiveLanguage, noOverlyComplexSentences, noOverusedAdverbs, noPassiveVoice, noPronounLedClaims, noQualifierCreep, noRedundantPairs, noStackedAdjectives, noSuperlativeClaims, noVagueComparatives, noVagueQuantifiers, noWeakModals, ruleRegistry, sentenceComplexity };
package/dist/index.js CHANGED
@@ -613,6 +613,87 @@ function escapeRegExp5(value) {
613
613
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
614
614
  }
615
615
 
616
+ // src/no-non-inclusive-language.ts
617
+ var DEFAULT_TERMS2 = [
618
+ { term: "guys", alternatives: ["everyone", "team", "folks"] },
619
+ { term: "manpower", alternatives: ["workforce", "staffing", "personnel"] },
620
+ { term: "whitelist", alternatives: ["allowlist"] },
621
+ { term: "blacklist", alternatives: ["denylist", "blocklist"] },
622
+ { term: "master", alternatives: ["primary", "main", "leader"] },
623
+ { term: "slave", alternatives: ["secondary", "replica", "follower"] },
624
+ { term: "crazy", alternatives: ["unexpected", "intense", "extreme"] },
625
+ { term: "insane", alternatives: ["extreme", "unbelievable", "remarkable"] },
626
+ { term: "dumb", alternatives: ["unhelpful", "poor", "uninformed"] },
627
+ { term: "lame", alternatives: ["unimpressive", "inadequate", "weak"] },
628
+ { term: "sanity check", alternatives: ["quick check", "confidence check", "verification"], exact: true },
629
+ { term: "blind spot", alternatives: ["unaware area", "gap", "oversight"], exact: true },
630
+ { term: "grandfathered", alternatives: ["legacy status", "exempted"] },
631
+ { term: "mankind", alternatives: ["humanity", "humankind", "people"] }
632
+ ];
633
+ var VERB_AMBIGUOUS_TERMS = /* @__PURE__ */ new Set(["master", "slave"]);
634
+ function toMatchPattern(rawTerm) {
635
+ return rawTerm;
636
+ }
637
+ function isProblematicUsage(term, terms) {
638
+ if (!VERB_AMBIGUOUS_TERMS.has(term.term.toLowerCase())) return true;
639
+ if (terms.length !== 1) return true;
640
+ const tags = terms[0].tags ?? [];
641
+ const isVerb = tags.includes("Verb");
642
+ const isNounOrAdjective = tags.includes("Noun") || tags.includes("Adjective");
643
+ return !isVerb || isNounOrAdjective;
644
+ }
645
+ function getOccurrenceFromMatch(entry, originalText) {
646
+ const terms = entry.terms;
647
+ if (!terms?.length) return null;
648
+ const firstOffset = terms[0].offset;
649
+ const lastOffset = terms[terms.length - 1].offset;
650
+ if (typeof firstOffset?.start !== "number" || typeof firstOffset.length !== "number" || typeof lastOffset?.start !== "number" || typeof lastOffset.length !== "number") {
651
+ return null;
652
+ }
653
+ const start = firstOffset.start;
654
+ const end = lastOffset.start + lastOffset.length;
655
+ if (end <= start) return null;
656
+ return {
657
+ text: originalText.slice(start, end),
658
+ start,
659
+ end
660
+ };
661
+ }
662
+ var noNonInclusiveLanguage = {
663
+ id: "no-non-inclusive-language-nlp",
664
+ description: "Flag non-inclusive terms and suggest neutral alternatives using NLP-aware matching",
665
+ defaults: { terms: DEFAULT_TERMS2, allowedTerms: [] },
666
+ help: "Non-inclusive terms can alienate readers. Replace them with neutral alternatives that name the same idea without relying on identity, ability, or historical power metaphors.",
667
+ check({ text, sourceMap, options }) {
668
+ const terms = options.terms?.length ? options.terms : DEFAULT_TERMS2;
669
+ const allowed = new Set((options.allowedTerms ?? []).map((term) => term.toLowerCase()));
670
+ const doc = createDoc(text);
671
+ const diagnostics = [];
672
+ for (const term of terms) {
673
+ if (allowed.has(term.term.toLowerCase())) continue;
674
+ const pattern = toMatchPattern(term.term);
675
+ const matches = doc.match(pattern);
676
+ const json = matches.json({ offset: true, text: true, terms: { offset: true, text: true, tags: true } });
677
+ for (const entry of json) {
678
+ if (!isProblematicUsage(term, entry.terms ?? [])) continue;
679
+ const occurrence = getOccurrenceFromMatch(entry, text);
680
+ if (!occurrence) continue;
681
+ const range = getOccurrenceRange(sourceMap, occurrence);
682
+ if (!range) continue;
683
+ const suggestion = term.alternatives.join(", ");
684
+ diagnostics.push({
685
+ ruleId: "no-non-inclusive-language-nlp",
686
+ severity: "error",
687
+ message: `replace "${occurrence.text}" with a neutral alternative such as "${suggestion}"`,
688
+ range,
689
+ help: noNonInclusiveLanguage.help
690
+ });
691
+ }
692
+ }
693
+ return diagnostics.sort((left, right) => left.range.start - right.range.start);
694
+ }
695
+ };
696
+
616
697
  // src/no-nominalized-phrases.ts
617
698
  var DEFAULT_SUFFIXES = ["tion", "sion", "ment", "ance", "ence", "ity"];
618
699
  var DEFAULT_ALLOWED_WORDS = [
@@ -1361,6 +1442,7 @@ var ruleRegistry = /* @__PURE__ */ new Map([
1361
1442
  ["no-hedge-words", noHedgeWords],
1362
1443
  ["no-jargon", noJargon],
1363
1444
  ["no-meaningless-modifiers", noMeaninglessModifiers],
1445
+ ["no-non-inclusive-language-nlp", noNonInclusiveLanguage],
1364
1446
  ["no-nominalized-phrases", noNominalizedPhrases],
1365
1447
  ["no-overly-complex-sentences", noOverlyComplexSentences],
1366
1448
  ["no-overused-adverbs", noOverusedAdverbs],
@@ -1388,6 +1470,7 @@ export {
1388
1470
  noJargon,
1389
1471
  noMeaninglessModifiers,
1390
1472
  noNominalizedPhrases,
1473
+ noNonInclusiveLanguage,
1391
1474
  noOverlyComplexSentences,
1392
1475
  noOverusedAdverbs,
1393
1476
  noPassiveVoice,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/no-absolute-intensifiers.ts","../src/utils.ts","../src/no-adverb-overuse.ts","../src/no-buzzword-stacks.ts","../src/no-complex-readability.ts","../src/no-expletive-openers.ts","../src/no-filter-words.ts","../src/no-empty-transformation-claims.ts","../src/no-future-promises.ts","../src/no-hedge-words.ts","../src/no-jargon.ts","../src/no-meaningless-modifiers.ts","../src/no-nominalized-phrases.ts","../src/no-overly-complex-sentences.ts","../src/no-overused-adverbs.ts","../src/no-passive-voice.ts","../src/no-pronoun-led-claims.ts","../src/no-qualifier-creep.ts","../src/no-redundant-pairs.ts","../src/no-stacked-adjectives.ts","../src/no-superlative-claims.ts","../src/no-vague-comparatives.ts","../src/no-vague-quantifiers.ts","../src/no-weak-modals.ts","../src/sentence-complexity.ts","../src/index.ts"],"sourcesContent":["import type { Diagnostic, Rule, RuleInput, Suggestion } from '@faircopy/core'\n\nexport interface NoAbsoluteIntensifiersOptions {\n intensifiers?: string[]\n absolutes?: string[]\n}\n\nconst DEFAULT_INTENSIFIERS = [\n 'very',\n 'really',\n 'completely',\n 'totally',\n 'absolutely',\n 'utterly',\n 'quite',\n 'extremely',\n 'perfectly',\n 'entirely',\n]\n\nconst DEFAULT_ABSOLUTES = [\n 'unique',\n 'finished',\n 'destroyed',\n 'essential',\n 'perfect',\n 'impossible',\n 'dead',\n 'empty',\n 'full',\n 'silent',\n 'unanimous',\n 'infinite',\n 'eternal',\n 'flawless',\n 'ultimate',\n 'final',\n 'complete',\n 'total',\n 'absolute',\n]\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction longestFirst(values: string[]): string[] {\n return [...values].sort((left, right) => right.length - left.length)\n}\n\nfunction buildPattern(intensifiers: string[], absolutes: string[]): RegExp {\n const intensifierPattern = longestFirst(intensifiers)\n .map(phrase => escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+'))\n .join('|')\n const absolutePattern = longestFirst(absolutes)\n .map(phrase => escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+'))\n .join('|')\n\n return new RegExp(\n `\\\\b(${intensifierPattern})\\\\s+\\\\b(${absolutePattern})(?![\\\\w'-])`,\n 'gi',\n )\n}\n\nexport const noAbsoluteIntensifiers: Rule<NoAbsoluteIntensifiersOptions> = {\n id: 'no-absolute-intensifiers',\n description: 'Flag intensifiers before absolute adjectives',\n defaults: {\n intensifiers: DEFAULT_INTENSIFIERS,\n absolutes: DEFAULT_ABSOLUTES,\n },\n help: 'Absolute adjectives already express an extreme. Intensifiers like \"very\" ' +\n 'before them are redundant and weaken the claim. Remove the intensifier ' +\n 'or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoAbsoluteIntensifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const intensifiers = options.intensifiers?.length ? options.intensifiers : DEFAULT_INTENSIFIERS\n const absolutes = options.absolutes?.length ? options.absolutes : DEFAULT_ABSOLUTES\n\n if (!intensifiers.length || !absolutes.length) return diagnostics\n\n const re = buildPattern(intensifiers, absolutes)\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedText = match[0]\n const intensifier = match[1]!\n const absolute = match[2]!\n const matchStart = match.index\n const matchEnd = matchStart + matchedText.length\n\n const start = sourceMap[matchStart]\n const end = sourceMap[matchEnd - 1]\n if (start === undefined || end === undefined) continue\n\n const suggest: Suggestion = {\n description: `remove \"${intensifier}\" before \"${absolute}\"`,\n edits: [{ range: { start, end: end + 1 }, replacement: absolute }],\n }\n\n diagnostics.push({\n ruleId: 'no-absolute-intensifiers',\n severity: 'warn',\n message: `\"${matchedText}\" is redundant — \"${absolute}\" is already absolute`,\n range: { start, end: end + 1 },\n help: noAbsoluteIntensifiers.help,\n suggest,\n })\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import nlp from 'compromise'\nimport type { Diagnostic } from '@faircopy/core'\nimport type { DocView, JsonOffsetEntry, JsonOffsetTerm, MatchView } from './types.js'\n\nexport interface MatchOccurrence {\n text: string\n start: number\n end: number\n}\n\nexport function createDoc(text: string): DocView {\n return nlp(text) as unknown as DocView\n}\n\nexport function getMatchOccurrences(text: string, matches: MatchView): MatchOccurrence[] {\n const json = matches.json({ offset: true, text: true, terms: { offset: true } }) as JsonOffsetEntry[]\n\n return json.flatMap((entry) => {\n const start = entry.offset?.start ?? entry.terms?.[0]?.offset?.start\n const length = entry.offset?.length ?? sumTermLengths(entry.terms)\n\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) {\n return []\n }\n\n return [{\n text: entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n }]\n })\n}\n\nexport function getOccurrenceRange(sourceMap: number[], occurrence: MatchOccurrence): Diagnostic['range'] | null {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) return null\n\n return { start, end: end + 1 }\n}\n\nfunction sumTermLengths(terms: JsonOffsetTerm[] | undefined): number | undefined {\n if (!terms?.length) return undefined\n\n let total = 0\n for (const term of terms) {\n const length = term.offset?.length\n if (typeof length !== 'number') return undefined\n total += length\n }\n\n return total\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getOccurrenceRange } from './utils.js'\n\nexport interface NoAdverbOveruseOptions {\n /** Maximum number of -ly adverbs allowed per sentence before the rest are flagged. */\n maxAdverbs?: number\n /** Adverbs that are allowed and do not count toward the threshold. */\n allowedAdverbs?: string[]\n}\n\nconst DEFAULT_MAX_ADVERBS = 2\nconst DEFAULT_ALLOWED_ADVERBS = ['only']\n\nexport const noAdverbOveruse: Rule<NoAdverbOveruseOptions> = {\n id: 'no-adverb-overuse',\n description: 'Flag sentences with more than two -ly adverbs',\n defaults: { maxAdverbs: DEFAULT_MAX_ADVERBS, allowedAdverbs: DEFAULT_ALLOWED_ADVERBS },\n help: 'Too many -ly adverbs in one sentence make copy feel padded. Remove the adverb, replace it with a stronger verb or adjective, or add it to allowedAdverbs if it is essential.',\n\n check({ text, sourceMap, options }: RuleInput<NoAdverbOveruseOptions>): Diagnostic[] {\n const maxAdverbs = options.maxAdverbs ?? DEFAULT_MAX_ADVERBS\n const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS).map(value => value.toLowerCase()))\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.terms) continue\n\n const occurrences = sentence.terms\n .filter((term) => term.tags?.includes('Adverb'))\n .map((term) => {\n const start = term.offset?.start\n const length = term.offset?.length\n return {\n text: term.text?.replace(/[^a-zA-Z]+$/, '') ?? '',\n start: typeof start === 'number' ? start : 0,\n end: typeof start === 'number' && typeof length === 'number' ? start + length : 0,\n }\n })\n .filter((occurrence) => /ly$/i.test(occurrence.text))\n .filter((occurrence) => !allowed.has(occurrence.text.toLowerCase()))\n .sort((left, right) => left.start - right.start)\n\n for (let index = maxAdverbs; index < occurrences.length; index += 1) {\n const occurrence = occurrences[index]!\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-adverb-overuse',\n severity: 'warn',\n message: `reduce adverb overuse: \"${occurrence.text}\" exceeds the limit of ${maxAdverbs} -ly adverb${maxAdverbs === 1 ? '' : 's'} per sentence`,\n range,\n help: noAdverbOveruse.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoBuzzwordStacksOptions {\n terms?: string[]\n maxTermsPerSentence?: number\n}\n\nconst DEFAULT_TERMS = [\n 'alignment',\n 'automation',\n 'collaboration',\n 'efficiency',\n 'engagement',\n 'experience',\n 'growth',\n 'impact',\n 'innovation',\n 'intelligence',\n 'optimization',\n 'platform',\n 'productivity',\n 'solution',\n 'strategy',\n 'transformation',\n 'value',\n 'velocity',\n 'workflow',\n]\n\nexport const noBuzzwordStacks: Rule<NoBuzzwordStacksOptions> = {\n id: 'no-buzzword-stacks',\n description: 'Flag sentences overloaded with abstract benefit nouns',\n defaults: { terms: DEFAULT_TERMS, maxTermsPerSentence: 2 },\n help: 'Buzzword-heavy sentences make copy sound interchangeable. Replace abstract benefit nouns with the specific product behavior, customer outcome, or proof point.',\n\n check({ text, sourceMap, options }: RuleInput<NoBuzzwordStacksOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const terms = options.terms?.length ? options.terms : DEFAULT_TERMS\n const maxTermsPerSentence = options.maxTermsPerSentence ?? 2\n if (maxTermsPerSentence < 1) return diagnostics\n\n for (const sentence of getSentenceRanges(text)) {\n const hits = getTermHits(sentence.text, terms)\n if (hits.length <= maxTermsPerSentence) continue\n\n const startOffset = sentence.start + hits[0]!.start\n const endOffset = sentence.start + hits[hits.length - 1]!.end\n const start = sourceMap[startOffset]\n const end = sourceMap[endOffset - 1]\n if (start === undefined || end === undefined) continue\n\n const words = hits.map(hit => hit.text.toLowerCase()).join(', ')\n diagnostics.push({\n ruleId: 'no-buzzword-stacks',\n severity: 'warn',\n message: `replace buzzword stack: ${words}`,\n range: { start, end: end + 1 },\n help: noBuzzwordStacks.help,\n })\n }\n\n return diagnostics\n },\n}\n\ninterface SentenceRange {\n text: string\n start: number\n}\n\ninterface TermHit {\n text: string\n start: number\n end: number\n}\n\nfunction getSentenceRanges(text: string): SentenceRange[] {\n const ranges: SentenceRange[] = []\n const re = /[^.!?]+[.!?]?/g\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const leadingWhitespaceLength = match[0].match(/^\\s*/)?.[0].length ?? 0\n const sentence = match[0].trim()\n if (!sentence) continue\n ranges.push({ text: sentence, start: match.index + leadingWhitespaceLength })\n }\n return ranges\n}\n\nfunction getTermHits(text: string, terms: string[]): TermHit[] {\n const termPattern = terms.map(escapeRegex).join('|')\n if (!termPattern) return []\n\n const hits: TermHit[] = []\n const re = new RegExp(`\\\\b(${termPattern})\\\\b`, 'gi')\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n hits.push({ text: match[0], start: match.index, end: match.index + match[0].length })\n }\n return hits\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoComplexReadabilityOptions {\n /** Target Flesch-Kincaid grade level. Text scoring above this is flagged. */\n maxGradeLevel?: number\n /** Minimum sentence count before scoring. Shorter passages are too noisy. */\n minSentences?: number\n /** Minimum word count before scoring. */\n minWords?: number\n}\n\nconst DEFAULT_OPTIONS: Required<NoComplexReadabilityOptions> = {\n maxGradeLevel: 12,\n minSentences: 3,\n minWords: 30,\n}\n\nexport const noComplexReadability: Rule<NoComplexReadabilityOptions> = {\n id: 'no-complex-readability',\n description: 'Flag prose whose Flesch-Kincaid grade level exceeds a target',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Landing-page copy should be readable by a broad audience. Break long sentences, replace jargon, and front-load the point until the grade level drops.',\n\n check({ text, sourceMap, options }: RuleInput<NoComplexReadabilityOptions>): Diagnostic[] {\n const maxGradeLevel = options.maxGradeLevel ?? DEFAULT_OPTIONS.maxGradeLevel\n const minSentences = options.minSentences ?? DEFAULT_OPTIONS.minSentences\n const minWords = options.minWords ?? DEFAULT_OPTIONS.minWords\n\n const sentences = getSentences(text)\n const words = getWords(text)\n\n if (sentences.length < minSentences || words.length < minWords) {\n return []\n }\n\n const syllables = words.reduce((sum, word) => sum + countSyllables(word), 0)\n const grade = fleschKincaidGrade(words.length, sentences.length, syllables)\n\n if (grade <= maxGradeLevel) {\n return []\n }\n\n const start = sourceMap[0]\n const end = sourceMap[sourceMap.length - 1]\n if (start === undefined || end === undefined) return []\n\n return [{\n ruleId: 'no-complex-readability',\n severity: 'warn',\n message: `readability is grade ${grade.toFixed(1)} — simplify to ${maxGradeLevel} or below`,\n range: { start, end: end + 1 },\n help: noComplexReadability.help,\n }]\n },\n}\n\nfunction getSentences(text: string): string[] {\n // Split on sentence terminators, keeping the delimiter so trailing spaces are preserved.\n const parts = text.split(/([.!?]+)/)\n const sentences: string[] = []\n for (let i = 0; i < parts.length; i += 2) {\n const sentence = parts[i]\n const terminator = parts[i + 1] ?? ''\n const combined = (sentence ?? '') + terminator\n const trimmed = combined.trim()\n if (trimmed) sentences.push(trimmed)\n }\n return sentences\n}\n\nfunction getWords(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s'-]/g, ' ')\n .split(/\\s+/)\n .filter(word => word.length > 0 && /[a-z0-9]/.test(word))\n}\n\nfunction countSyllables(word: string): number {\n const cleaned = word.toLowerCase().replace(/[^a-z]/g, '')\n if (!cleaned) return 0\n if (cleaned.length <= 3) return 1\n\n const vowels = cleaned.match(/[aeiouy]+/g)\n if (!vowels) return 1\n\n let count = vowels.length\n if (cleaned.endsWith('e')) count--\n if (cleaned.endsWith('le') && cleaned.length > 2 && !/[aeiouy]$/.test(cleaned[cleaned.length - 3] ?? '')) {\n count++\n }\n return Math.max(1, count)\n}\n\nfunction fleschKincaidGrade(words: number, sentences: number, syllables: number): number {\n if (sentences === 0 || words === 0) return 0\n return 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoExpletiveOpenersOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'there is',\n 'there are',\n 'there was',\n 'there were',\n 'there will be',\n]\n\nexport const noExpletiveOpeners: Rule<NoExpletiveOpenersOptions> = {\n id: 'no-expletive-openers',\n description: 'Flag sentence openings that delay the real subject',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Expletive openers like \"there are\" make copy indirect. Start with the actor, product, or benefit so the sentence lands faster.',\n\n check({ text, sourceMap, options }: RuleInput<NoExpletiveOpenersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n if (!startsSentence(text, occurrence.start)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-expletive-openers',\n severity: 'warn',\n message: `start with the real subject instead of \"${occurrence.text.toLowerCase()}\"`,\n range,\n help: noExpletiveOpeners.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction startsSentence(text: string, start: number): boolean {\n let index = start - 1\n while (index >= 0 && /\\s/.test(text[index]!)) index--\n return index < 0 || /[.!?]/.test(text[index]!)\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoFilterWordsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'I think',\n 'it seems',\n 'basically',\n 'in order to',\n]\n\nexport const noFilterWords: Rule<NoFilterWordsOptions> = {\n id: 'no-filter-words',\n description: 'Ban filter phrases that distance the claim from the reader',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Filter phrases announce a perspective or pad the sentence instead of making the point. Delete the phrase or rewrite the sentence so the claim stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoFilterWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-filter-words',\n severity: 'error',\n message: `remove \"${occurrence.text.toLowerCase()}\" — state the claim directly`,\n range: { start, end: end + 1 },\n help: noFilterWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoEmptyTransformationClaimsOptions {\n allowedPhrases?: string[]\n}\n\ninterface Pattern {\n re: RegExp\n message: string\n}\n\nconst PATTERNS: Pattern[] = [\n {\n re: /\\b(?:transform(?:s|ed|ing)?|chang(?:e|es|ed|ing)|reimagin(?:e|es|ed|ing)|revolutioniz(?:e|es|ed|ing))\\s+the\\s+way\\s+(?:you|your\\s+team|teams|companies|businesses|people)\\s+(?:work|build|sell|operate|collaborate|communicate|create|grow|ship|scale|learn|manage)\\b/gi,\n message: 'replace empty transformation claim with a concrete outcome',\n },\n {\n re: /\\bunlock\\s+(?:your|their|team|teams'|your\\s+team's|the\\s+team's|the\\s+full)\\s+(?:potential|productivity|growth|creativity|efficiency)\\b/gi,\n message: 'replace empty unlock claim with the specific benefit',\n },\n {\n re: /\\btake\\s+(?:your|their|team|teams'|your\\s+team's|the\\s+team's)?\\s*(?:productivity|workflow|workflows|growth|collaboration|business|operations|process|processes)\\s+to\\s+the\\s+next\\s+level\\b/gi,\n message: 'replace next-level claim with measurable value',\n },\n]\n\nexport const noEmptyTransformationClaims: Rule<NoEmptyTransformationClaimsOptions> = {\n id: 'no-empty-transformation-claims',\n description: 'Flag broad transformation claims that do not name a concrete outcome',\n defaults: { allowedPhrases: [] },\n help: 'Transformation cliches promise a feeling instead of a result. Replace them with the specific workflow, metric, or customer outcome the product changes.',\n\n check({ text, sourceMap, options }: RuleInput<NoEmptyTransformationClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const allowedPhrases = new Set((options.allowedPhrases ?? []).map(value => normalize(value)))\n\n for (const pattern of PATTERNS) {\n const re = new RegExp(pattern.re.source, pattern.re.flags)\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const phrase = match[0]\n if (allowedPhrases.has(normalize(phrase))) continue\n\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-empty-transformation-claims',\n severity: 'warn',\n message: `${pattern.message}: \"${phrase}\"`,\n range: { start, end: end + 1 },\n help: noEmptyTransformationClaims.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction normalize(value: string): string {\n return value.toLowerCase().replace(/\\s+/g, ' ').trim()\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoFuturePromisesOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'will help you',\n 'will enable',\n 'will transform',\n 'will allow you to',\n 'will empower',\n]\n\nexport const noFuturePromises: Rule<NoFuturePromisesOptions> = {\n id: 'no-future-promises',\n description: 'Flag future-tense promises without present evidence',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Future promises defer proof. Rewrite the claim around current behavior, concrete evidence, or a specific outcome already available.',\n\n check({ text, sourceMap, options }: RuleInput<NoFuturePromisesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-future-promises',\n severity: 'warn',\n message: `replace future promise \"${matchedPhrase}\" with present evidence`,\n range: { start, end: end + 1 },\n help: noFuturePromises.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoHedgeWordsOptions {\n hedges?: string[]\n}\n\nconst DEFAULT_HEDGES = [\n 'kind of',\n 'sort of',\n 'somewhat',\n 'fairly',\n 'pretty',\n 'rather',\n 'quite',\n 'arguably',\n 'relatively',\n 'more or less',\n]\n\nexport const noHedgeWords: Rule<NoHedgeWordsOptions> = {\n id: 'no-hedge-words',\n description: 'Flag hedge words that soften claims',\n defaults: { hedges: DEFAULT_HEDGES },\n help: 'Hedge words make claims sound uncertain. Remove the hedge or replace the sentence with a specific proof point. Words like \"pretty\" and \"quite\" can be intentional adjectives in some contexts; override hedges when that trade-off is too noisy for your copy.',\n\n check({ text, sourceMap, options }: RuleInput<NoHedgeWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const hedges = options.hedges?.length ? options.hedges : DEFAULT_HEDGES\n\n for (const hedge of hedges) {\n const re = new RegExp(`\\\\b${escapeRegExp(hedge).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const phrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-hedge-words',\n severity: 'warn',\n message: `remove hedge \"${phrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noHedgeWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoJargonOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'leverage',\n 'synergy',\n 'ideate',\n 'circle back',\n 'deep dive',\n 'drill down',\n 'move the needle',\n 'best of breed',\n 'best in class',\n]\n\nexport const noJargon: Rule<NoJargonOptions> = {\n id: 'no-jargon',\n description: 'Flag business jargon and vague workplace idioms',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Jargon makes copy sound generic. Replace it with the specific action, technical behavior, or customer outcome, or tune the phrase list for terms that are legitimate in your context.',\n\n check({ text, sourceMap, options }: RuleInput<NoJargonOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-jargon',\n severity: 'warn',\n message: `replace jargon phrase \"${matchedPhrase}\"`,\n range: { start, end: end + 1 },\n help: noJargon.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoMeaninglessModifiersOptions {\n modifiers?: string[]\n}\n\nconst DEFAULT_MODIFIERS = [\n 'very',\n 'really',\n 'actually',\n 'basically',\n 'literally',\n 'essentially',\n 'truly',\n 'definitely',\n 'clearly',\n 'obviously',\n]\n\nexport const noMeaninglessModifiers: Rule<NoMeaninglessModifiersOptions> = {\n id: 'no-meaningless-modifiers',\n description: 'Flag intensifiers that add no information',\n defaults: { modifiers: DEFAULT_MODIFIERS },\n help: 'Meaningless modifiers inflate a claim without adding evidence. Delete the modifier, replace it with a concrete detail, or opt out specific words such as \"clearly\" and \"obviously\" when they are part of your voice.',\n\n check({ text, sourceMap, options }: RuleInput<NoMeaninglessModifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const modifiers = options.modifiers?.length ? options.modifiers : DEFAULT_MODIFIERS\n\n for (const modifier of modifiers) {\n const re = new RegExp(`\\\\b${escapeRegExp(modifier).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedModifier = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedModifier.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-meaningless-modifiers',\n severity: 'warn',\n message: `remove meaningless modifier \"${matchedModifier}\"`,\n range: { start, end: end + 1 },\n help: noMeaninglessModifiers.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoNominalizedPhrasesOptions {\n suffixes?: string[]\n allowedWords?: string[]\n}\n\nconst DEFAULT_SUFFIXES = ['tion', 'sion', 'ment', 'ance', 'ence', 'ity']\nconst DEFAULT_ALLOWED_WORDS = [\n 'accessibility',\n 'availability',\n 'capacity',\n 'community',\n 'identity',\n 'opportunity',\n 'privacy',\n 'quality',\n 'reliability',\n 'security',\n]\n\nexport const noNominalizedPhrases: Rule<NoNominalizedPhrasesOptions> = {\n id: 'no-nominalized-phrases',\n description: 'Flag nominalized \"X of Y\" phrases that hide the action in a noun',\n defaults: { suffixes: DEFAULT_SUFFIXES, allowedWords: DEFAULT_ALLOWED_WORDS },\n help: 'Nominalized phrases bury the action. Rewrite the phrase with a verb so the sentence says who does what.',\n\n check({ text, sourceMap, options }: RuleInput<NoNominalizedPhrasesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const suffixes = options.suffixes?.length ? options.suffixes : DEFAULT_SUFFIXES\n const allowedWords = new Set((options.allowedWords?.length ? options.allowedWords : DEFAULT_ALLOWED_WORDS).map(value => value.toLowerCase()))\n const suffixPattern = suffixes.map(escapeRegex).join('|')\n if (!suffixPattern) return diagnostics\n\n const doc = createDoc(text)\n const matches = doc.match(`/[a-z]+(${suffixPattern})/ of .`)\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const nominalization = occurrence.text.trim().split(/\\s+/)[0]?.toLowerCase()\n if (!nominalization || allowedWords.has(nominalization)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-nominalized-phrases',\n severity: 'warn',\n message: `rewrite \"${occurrence.text}\" with a verb`,\n range,\n help: noNominalizedPhrases.help,\n })\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc } from './utils.js'\n\nexport interface NoOverlyComplexSentencesOptions {\n /** Maximum total conjunctions allowed in a single sentence. */\n maxConjunctions?: number\n /** Maximum coordinating conjunctions allowed in a single sentence. */\n maxCoordinating?: number\n /** Maximum subordinating conjunctions allowed in a single sentence. */\n maxSubordinating?: number\n /** Words or phrases to treat as coordinating conjunctions. */\n coordinating?: string[]\n /** Words or phrases to treat as subordinating conjunctions. */\n subordinating?: string[]\n /** Words or phrases to ignore when counting. */\n allowList?: string[]\n}\n\nconst DEFAULT_MAX_CONJUNCTIONS = 4\nconst DEFAULT_MAX_COORDINATING = 3\nconst DEFAULT_MAX_SUBORDINATING = 2\n\nconst DEFAULT_COORDINATING = ['and', 'but', 'or', 'nor', 'yet', 'so']\nconst DEFAULT_SUBORDINATING = [\n 'because',\n 'although',\n 'though',\n 'while',\n 'since',\n 'unless',\n 'if',\n 'when',\n 'after',\n 'before',\n 'until',\n 'whether',\n 'once',\n]\n\ninterface ConjunctionPattern {\n phrase: string\n category: 'coordinating' | 'subordinating'\n regex: RegExp\n}\n\nexport const noOverlyComplexSentences: Rule<NoOverlyComplexSentencesOptions> = {\n id: 'no-overly-complex-sentences',\n description: 'Flag sentences with too many coordinating or subordinating conjunctions',\n defaults: {\n maxConjunctions: DEFAULT_MAX_CONJUNCTIONS,\n maxCoordinating: DEFAULT_MAX_COORDINATING,\n maxSubordinating: DEFAULT_MAX_SUBORDINATING,\n coordinating: DEFAULT_COORDINATING,\n subordinating: DEFAULT_SUBORDINATING,\n allowList: [],\n },\n help: 'Sentences packed with conjunctions are often run-ons or nested too deeply. Break them into shorter sentences so each point stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoOverlyComplexSentencesOptions>): Diagnostic[] {\n const maxConjunctions = options.maxConjunctions ?? DEFAULT_MAX_CONJUNCTIONS\n const maxCoordinating = options.maxCoordinating ?? DEFAULT_MAX_COORDINATING\n const maxSubordinating = options.maxSubordinating ?? DEFAULT_MAX_SUBORDINATING\n\n const coordinating = (options.coordinating ?? DEFAULT_COORDINATING).map(value =>\n value.toLowerCase().trim(),\n )\n const subordinating = (options.subordinating ?? DEFAULT_SUBORDINATING).map(value =>\n value.toLowerCase().trim(),\n )\n const allowList = new Set(\n (options.allowList ?? []).map(value => value.toLowerCase().trim()),\n )\n\n const patterns: ConjunctionPattern[] = [\n ...coordinating.map(phrase => ({\n phrase,\n category: 'coordinating' as const,\n regex: phraseToRegex(phrase),\n })),\n ...subordinating.map(phrase => ({\n phrase,\n category: 'subordinating' as const,\n regex: phraseToRegex(phrase),\n })),\n ].sort((a, b) => b.phrase.length - a.phrase.length)\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.offset || !sentence.terms) continue\n\n const sentenceStart = sentence.offset.start ?? 0\n const sentenceText = sentence.text ?? ''\n const matchedRanges: Array<{ start: number; end: number }> = []\n\n let coordinatingCount = 0\n let subordinatingCount = 0\n\n for (const { phrase, category, regex } of patterns) {\n if (allowList.has(phrase)) continue\n\n let match: RegExpExecArray | null\n while ((match = regex.exec(sentenceText)) !== null) {\n const matchStart = match.index\n const matchEnd = match.index + match[0].length\n\n // Avoid counting the same tokens twice when shorter phrases overlap.\n if (matchedRanges.some(range => matchStart < range.end && matchEnd > range.start)) {\n continue\n }\n\n matchedRanges.push({ start: matchStart, end: matchEnd })\n\n if (category === 'coordinating') {\n coordinatingCount += 1\n } else {\n subordinatingCount += 1\n }\n }\n }\n\n const total = coordinatingCount + subordinatingCount\n if (\n total <= maxConjunctions &&\n coordinatingCount <= maxCoordinating &&\n subordinatingCount <= maxSubordinating\n ) {\n continue\n }\n\n const length = sentence.offset.length ?? 0\n const sentenceEnd = sentenceStart + length\n\n const sourceStart = sourceMap[sentenceStart]\n const sourceEnd = sourceMap[sentenceEnd - 1]\n if (sourceStart === undefined || sourceEnd === undefined) continue\n\n const reasons: string[] = []\n if (total > maxConjunctions) {\n reasons.push(`${total} conjunctions (max ${maxConjunctions})`)\n }\n if (coordinatingCount > maxCoordinating) {\n reasons.push(`${coordinatingCount} coordinating (max ${maxCoordinating})`)\n }\n if (subordinatingCount > maxSubordinating) {\n reasons.push(`${subordinatingCount} subordinating (max ${maxSubordinating})`)\n }\n\n diagnostics.push({\n ruleId: 'no-overly-complex-sentences',\n severity: 'warn',\n message: `sentence is overly complex: ${reasons.join(', ')} — consider splitting it`,\n range: { start: sourceStart, end: sourceEnd + 1 },\n help: noOverlyComplexSentences.help,\n suggest: {\n description: 'Split this sentence into shorter sentences, one idea each.',\n edits: [],\n },\n })\n }\n\n return diagnostics\n },\n}\n\nfunction phraseToRegex(phrase: string): RegExp {\n const escaped = phrase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const spaced = escaped.replace(/\\s+/g, '\\\\s+')\n const source = spaced.includes('\\\\s+') ? spaced : `\\\\b${spaced}\\\\b`\n return new RegExp(source, 'gi')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getOccurrenceRange } from './utils.js'\n\nexport interface NoOverusedAdverbsOptions {\n /** Maximum occurrences of a single adverb allowed before later ones are flagged. */\n threshold?: number\n /** Minimum character length for an adverb to be considered. */\n minLength?: number\n /** Adverbs that are allowed and do not count toward the threshold. */\n allowedAdverbs?: string[]\n /** If provided, only these adverbs are checked. */\n adverbs?: string[]\n}\n\nconst DEFAULT_THRESHOLD = 3\nconst DEFAULT_MIN_LENGTH = 3\nconst DEFAULT_ALLOWED_ADVERBS = ['only', 'not']\n\nexport const noOverusedAdverbs: Rule<NoOverusedAdverbsOptions> = {\n id: 'no-overused-adverbs',\n description: 'Flag adverbs that appear excessively across the text',\n defaults: {\n threshold: DEFAULT_THRESHOLD,\n minLength: DEFAULT_MIN_LENGTH,\n allowedAdverbs: DEFAULT_ALLOWED_ADVERBS,\n },\n help: 'Repeating the same adverb throughout a passage weakens copy and reads as filler. Replace the adverb with a stronger verb or adjective, cut it, or add it to allowedAdverbs if it is essential.',\n\n check({ text, sourceMap, options }: RuleInput<NoOverusedAdverbsOptions>): Diagnostic[] {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD\n const minLength = options.minLength ?? DEFAULT_MIN_LENGTH\n const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS).map(value => value.toLowerCase()))\n const adverbFilter = options.adverbs ? new Set(options.adverbs.map(value => value.toLowerCase())) : null\n\n const doc = createDoc(text)\n const sentences = doc.json({ offset: true, terms: { offset: true, tags: true } })\n\n const occurrencesByAdverb = new Map<string, Array<{ text: string; start: number; end: number }>>()\n\n for (const sentence of sentences) {\n for (const term of sentence.terms ?? []) {\n if (!term.tags?.includes('Adverb')) continue\n\n const rawText = term.text?.replace(/[^a-zA-Z]+$/, '') ?? ''\n const normalized = rawText.toLowerCase()\n if (!normalized || normalized.length < minLength) continue\n if (allowed.has(normalized)) continue\n if (adverbFilter && !adverbFilter.has(normalized)) continue\n\n const start = term.offset?.start\n const length = term.offset?.length\n if (typeof start !== 'number' || typeof length !== 'number') continue\n\n const occurrence = { text: rawText, start, end: start + length }\n const existing = occurrencesByAdverb.get(normalized)\n if (existing) {\n existing.push(occurrence)\n } else {\n occurrencesByAdverb.set(normalized, [occurrence])\n }\n }\n }\n\n const diagnostics: Diagnostic[] = []\n\n for (const [, occurrences] of occurrencesByAdverb) {\n if (occurrences.length <= threshold) continue\n\n for (let index = threshold; index < occurrences.length; index += 1) {\n const occurrence = occurrences[index]!\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-overused-adverbs',\n severity: 'warn',\n message: `reduce overused adverb: \"${occurrence.text}\" appears ${occurrences.length} times`,\n range,\n help: noOverusedAdverbs.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoPassiveVoiceOptions {\n allowedAuxiliaries?: string[]\n}\n\nconst DEFAULT_ALLOWED_AUXILIARIES = ['is', 'are', 'was', 'were', 'be', 'been', 'being']\n\nexport const noPassiveVoice: Rule<NoPassiveVoiceOptions> = {\n id: 'no-passive-voice',\n description: 'Flag likely passive-voice constructions using POS tagging patterns',\n defaults: { allowedAuxiliaries: DEFAULT_ALLOWED_AUXILIARIES },\n help: 'Passive voice often hides the actor and adds drag. Prefer naming who did the action unless the actor genuinely does not matter.',\n\n check({ text, sourceMap, options }: RuleInput<NoPassiveVoiceOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const auxiliaries = new Set((options.allowedAuxiliaries?.length ? options.allowedAuxiliaries : DEFAULT_ALLOWED_AUXILIARIES).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('(#Copula|#Auxiliary) #PastTense')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n if (words.length < 2) continue\n if (!auxiliaries.has(words[0]!.toLowerCase())) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-passive-voice',\n severity: 'warn',\n message: `rewrite passive construction \"${occurrence.text}\" with a named actor`,\n range: { start, end: end + 1 },\n help: noPassiveVoice.help,\n })\n }\n\n return dedupeDiagnostics(diagnostics)\n },\n}\n\nfunction dedupeDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {\n const seen = new Set<string>()\n return diagnostics.filter((diagnostic) => {\n const key = `${diagnostic.range.start}:${diagnostic.range.end}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoPronounLedClaimsOptions {\n pronouns?: string[]\n verbs?: string[]\n}\n\nconst DEFAULT_PRONOUNS = ['it', 'this', 'that', 'these', 'those']\nconst DEFAULT_VERBS = [\n 'brings',\n 'delivers',\n 'enables',\n 'gives',\n 'helps',\n 'keeps',\n 'lets',\n 'makes',\n 'turns',\n 'unlocks',\n]\n\nexport const noPronounLedClaims: Rule<NoPronounLedClaimsOptions> = {\n id: 'no-pronoun-led-claims',\n description: 'Flag vague claims that start with it, this, that, these, or those',\n defaults: { pronouns: DEFAULT_PRONOUNS, verbs: DEFAULT_VERBS },\n help: 'Pronoun-led claims make the reader infer the subject. Name the feature, product, or user action that creates the outcome.',\n\n check({ text, sourceMap, options }: RuleInput<NoPronounLedClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const pronouns = options.pronouns?.length ? options.pronouns : DEFAULT_PRONOUNS\n const verbs = options.verbs?.length ? options.verbs : DEFAULT_VERBS\n if (!pronouns.length || !verbs.length) return diagnostics\n\n const re = new RegExp(\n `(?:^|[.!?]\\\\s+)(${pronouns.map(escapeRegex).join('|')})\\\\s+(${verbs.map(escapeRegex).join('|')})\\\\b`,\n 'gi'\n )\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const phrase = `${match[1]} ${match[2]}`\n const phraseStart = match.index + match[0].indexOf(phrase)\n const start = sourceMap[phraseStart]\n const end = sourceMap[phraseStart + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-pronoun-led-claims',\n severity: 'warn',\n message: `name the subject instead of \"${phrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noPronounLedClaims.help,\n })\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoQualifierCreepOptions {\n /** Qualifier words that count toward a stacked-qualifier pattern. */\n qualifiers?: string[]\n /** Maximum qualifiers allowed in a row before the excess are flagged. */\n maxQualifiers?: number\n}\n\nconst DEFAULT_QUALIFIERS = [\n 'very',\n 'really',\n 'quite',\n 'fairly',\n 'somewhat',\n 'rather',\n 'pretty',\n 'basically',\n 'actually',\n 'literally',\n 'essentially',\n 'truly',\n 'definitely',\n 'clearly',\n 'obviously',\n 'absolutely',\n 'completely',\n 'totally',\n 'utterly',\n]\n\nconst DEFAULT_MAX_QUALIFIERS = 1\n\nexport const noQualifierCreep: Rule<NoQualifierCreepOptions> = {\n id: 'no-qualifier-creep',\n description: 'Flag stacked qualifiers or intensifiers before an adjective or adverb',\n defaults: { qualifiers: DEFAULT_QUALIFIERS, maxQualifiers: DEFAULT_MAX_QUALIFIERS },\n help: 'Stacked qualifiers dilute the claim and feel hedged. Keep the strongest qualifier or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoQualifierCreepOptions>): Diagnostic[] {\n const qualifiers = new Set((options.qualifiers?.length ? options.qualifiers : DEFAULT_QUALIFIERS).map(value => value.toLowerCase()))\n const maxQualifiers = options.maxQualifiers ?? DEFAULT_MAX_QUALIFIERS\n if (maxQualifiers < 1) return []\n\n const doc = createDoc(text)\n const diagnostics: Diagnostic[] = []\n const seenRanges: Array<{ start: number; end: number }> = []\n\n const patterns = ['#Adverb #Adverb+ #Adjective', '#Adverb #Adverb+ #Adverb']\n for (const pattern of patterns) {\n const matches = doc.match(pattern)\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n const qualifierWords = words.filter(word => qualifiers.has(word.toLowerCase().replace(/[^a-zA-Z]+$/, '')))\n if (qualifierWords.length <= maxQualifiers) continue\n\n if (seenRanges.some(range => occurrence.start < range.end && occurrence.end > range.start)) continue\n seenRanges.push({ start: occurrence.start, end: occurrence.end })\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-qualifier-creep',\n severity: 'warn',\n message: `remove stacked qualifiers in \"${occurrence.text}\" — use one strong word or a concrete detail`,\n range,\n help: noQualifierCreep.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoRedundantPairsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'first and foremost',\n 'each and every',\n 'various different',\n 'end result',\n 'final outcome',\n 'past history',\n 'future plans',\n 'unexpected surprise',\n 'advance planning',\n]\n\nexport const noRedundantPairs: Rule<NoRedundantPairsOptions> = {\n id: 'no-redundant-pairs',\n description: 'Flag redundant word pairs and padded fixed phrases',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Redundant pairs repeat the same idea twice. Keep the stronger word or replace the phrase with a more specific claim.',\n\n check({ text, sourceMap, options }: RuleInput<NoRedundantPairsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-redundant-pairs',\n severity: 'warn',\n message: `tighten redundant phrase \"${matchedPhrase}\"`,\n range: { start, end: end + 1 },\n help: noRedundantPairs.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoStackedAdjectivesOptions {\n allowedPhrases?: string[]\n}\n\nexport const noStackedAdjectives: Rule<NoStackedAdjectivesOptions> = {\n id: 'no-stacked-adjectives',\n description: 'Flag noun phrases with multiple adjectives before the noun',\n defaults: { allowedPhrases: [] },\n help: 'Stacked adjectives make copy feel generic. Keep the one descriptor that earns its place or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoStackedAdjectivesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const allowedPhrases = new Set((options.allowedPhrases ?? []).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Adjective #Adjective+ #Noun')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n if (allowedPhrases.has(occurrence.text.toLowerCase())) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-stacked-adjectives',\n severity: 'warn',\n message: `cut stacked adjectives in \"${occurrence.text}\"`,\n range,\n help: noStackedAdjectives.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoSuperlativeClaimsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'best',\n 'leading',\n 'world-class',\n 'top',\n 'premier',\n 'ultimate',\n 'industry-leading',\n 'cutting-edge',\n 'state-of-the-art',\n]\n\nexport const noSuperlativeClaims: Rule<NoSuperlativeClaimsOptions> = {\n id: 'no-superlative-claims',\n description: 'Flag unproven superlatives and market-positioning claims',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Superlative claims need proof. Replace broad market-positioning words with evidence, scope, or a concrete product behavior.',\n\n check({ text, sourceMap, options }: RuleInput<NoSuperlativeClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = getLongestPhrasesFirst(\n options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n )\n const claimedRanges: Array<{ start: number; end: number }> = []\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const matchStart = match.index\n const matchEnd = match.index + matchedPhrase.length\n if (claimedRanges.some(range => rangesOverlap(range, matchStart, matchEnd))) continue\n\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n claimedRanges.push({ start: matchStart, end: matchEnd })\n\n diagnostics.push({\n ruleId: 'no-superlative-claims',\n severity: 'warn',\n message: `prove or remove superlative claim \"${matchedPhrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noSuperlativeClaims.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction getLongestPhrasesFirst(phrases: string[]): string[] {\n return [...phrases].sort((left, right) => right.length - left.length)\n}\n\nfunction rangesOverlap(range: { start: number; end: number }, start: number, end: number): boolean {\n return start < range.end && end > range.start\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport nlp from 'compromise'\nimport { getMatchOccurrences, getOccurrenceRange } from './utils.js'\nimport type { JsonOffsetEntry } from './types.js'\n\nexport interface NoVagueComparativesOptions {\n /** Comparative words or phrases that require a baseline. */\n comparatives?: string[]\n /** Whether to require \"than\" in the same sentence to avoid flagging. */\n requireThan?: boolean\n}\n\nconst DEFAULT_OPTIONS: Required<NoVagueComparativesOptions> = {\n comparatives: [\n 'better',\n 'worse',\n 'more',\n 'less',\n 'faster',\n 'slower',\n 'easier',\n 'harder',\n 'stronger',\n 'weaker',\n 'higher',\n 'lower',\n 'bigger',\n 'smaller',\n 'greater',\n ],\n requireThan: true,\n}\n\nexport const noVagueComparatives: Rule<NoVagueComparativesOptions> = {\n id: 'no-vague-comparatives',\n description: 'Flag comparative claims that omit a clear baseline',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Comparative words like \"better\" or \"faster\" only persuade when the reader knows what is being compared. ' +\n 'Add \"than\" and a concrete baseline, or rephrase with a specific metric.',\n\n check({ text, sourceMap, options }: RuleInput<NoVagueComparativesOptions>): Diagnostic[] {\n const comparatives = new Set(\n (options.comparatives ?? DEFAULT_OPTIONS.comparatives).map(word => word.toLowerCase())\n )\n const requireThan = options.requireThan ?? DEFAULT_OPTIONS.requireThan\n\n if (comparatives.size === 0) return []\n\n const doc = nlp(text)\n const diagnostics: Diagnostic[] = []\n const seenRanges: Array<{ start: number; end: number }> = []\n\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, text: true } }) as JsonOffsetEntry[]\n\n for (const sentence of sentences) {\n const sentenceStart = sentence.offset?.start ?? sentence.terms?.[0]?.offset?.start\n const sentenceLength = sentence.offset?.length ?? sentence.terms?.reduce((sum, term) => sum + (term.offset?.length ?? 0), 0)\n if (typeof sentenceStart !== 'number' || typeof sentenceLength !== 'number') continue\n const sentenceEnd = sentenceStart + sentenceLength\n\n const sentenceText = sentence.text ?? text.slice(sentenceStart, sentenceEnd)\n const sentenceDoc = nlp(sentenceText)\n\n // Build patterns that capture both compromise-tagged comparatives and explicit user-configured words.\n const explicitComparatives = Array.from(comparatives).join('|')\n const patterns = [\n '#Comparative',\n `(more|less) #Adjective`,\n `(more|less) #Adverb`,\n `(${explicitComparatives})`,\n ]\n\n for (const pattern of patterns) {\n const matches = sentenceDoc.match(pattern)\n\n for (const occurrence of getMatchOccurrences(sentenceText, matches)) {\n const words = occurrence.text.trim().toLowerCase().split(/\\s+/)\n const matchedComparative = words.find(word => comparatives.has(word.replace(/[^a-zA-Z]+$/, '')))\n if (!matchedComparative) continue\n\n // Occurrence offsets are relative to the sentence; convert to full-text offsets.\n const trailingPunctuation = occurrence.text.match(/[^a-zA-Z\\s]+$/)?.[0].length ?? 0\n const occurrenceStart = sentenceStart + occurrence.start\n const occurrenceEnd = sentenceStart + occurrence.end - trailingPunctuation\n\n if (seenRanges.some(range => occurrenceStart < range.end && occurrenceEnd > range.start)) continue\n seenRanges.push({ start: occurrenceStart, end: occurrenceEnd })\n\n if (requireThan && sentenceDoc.has('than')) continue\n\n const range = getOccurrenceRange(sourceMap, { text: occurrence.text, start: occurrenceStart, end: occurrenceEnd })\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-vague-comparatives',\n severity: 'warn',\n message: `comparative \"${occurrence.text.trim().replace(/[^a-zA-Z\\s]+$/, '')}\" needs a baseline — add \"than\" or a concrete comparison`,\n range,\n help: noVagueComparatives.help,\n })\n }\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoVagueQuantifiersOptions {\n quantifiers?: string[]\n}\n\nconst DEFAULT_QUANTIFIERS = [\n 'many',\n 'several',\n 'various',\n 'numerous',\n 'multiple',\n 'some',\n 'a number of',\n 'a variety of',\n 'a range of',\n 'a host of',\n 'countless',\n 'tons of',\n 'lots of',\n]\n\nexport const noVagueQuantifiers: Rule<NoVagueQuantifiersOptions> = {\n id: 'no-vague-quantifiers',\n description: 'Flag bare quantifiers without numeric anchors',\n defaults: { quantifiers: DEFAULT_QUANTIFIERS },\n help:\n 'Vague quantifiers make claims hard to evaluate. Replace them with a number, range, or concrete scope when precision matters.',\n\n check({ text, sourceMap, options }: RuleInput<NoVagueQuantifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const quantifiers = options.quantifiers?.length ? options.quantifiers : DEFAULT_QUANTIFIERS\n\n for (const quantifier of quantifiers) {\n const re = new RegExp(\n `\\\\b${escapeRegExp(quantifier).replace(/\\\\s+/g, '\\\\s+')}\\\\b`,\n 'gi'\n )\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedQuantifier = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedQuantifier.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-vague-quantifiers',\n severity: 'warn',\n message: `replace vague quantifier \"${matchedQuantifier}\" with a number or concrete scope`,\n range: { start, end: end + 1 },\n help: noVagueQuantifiers.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoWeakModalsOptions {\n modals?: string[]\n verbs?: string[]\n}\n\nconst DEFAULT_MODALS = ['can', 'could', 'may', 'might']\nconst DEFAULT_VERBS = [\n 'boost',\n 'drive',\n 'enable',\n 'help',\n 'improve',\n 'increase',\n 'make',\n 'reduce',\n 'support',\n 'transform',\n 'unlock',\n]\n\nexport const noWeakModals: Rule<NoWeakModalsOptions> = {\n id: 'no-weak-modals',\n description: 'Flag hedged modal claims like \"can help\" and \"might improve\"',\n defaults: { modals: DEFAULT_MODALS, verbs: DEFAULT_VERBS },\n help: 'Hedged modal claims sound tentative. Replace them with the outcome, capability, or proof you can stand behind.',\n\n check({ text, sourceMap, options }: RuleInput<NoWeakModalsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const modals = new Set((options.modals?.length ? options.modals : DEFAULT_MODALS).map(value => value.toLowerCase()))\n const verbs = new Set((options.verbs?.length ? options.verbs : DEFAULT_VERBS).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Modal #Adverb? #Verb')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n const modal = words[0]?.toLowerCase()\n const verb = words[words.length - 1]?.toLowerCase()\n if (!modal || !verb || !modals.has(modal) || !verbs.has(verb)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-weak-modals',\n severity: 'warn',\n message: `replace \"${occurrence.text.toLowerCase()}\" with a direct claim`,\n range,\n help: noWeakModals.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc } from './utils.js'\n\nexport interface SentenceComplexityOptions {\n /** Maximum number of words allowed in a single sentence. */\n maxWordCount?: number\n /** Maximum number of finite-verb clauses allowed in a single sentence. */\n maxClauseCount?: number\n}\n\nconst DEFAULT_OPTIONS: Required<SentenceComplexityOptions> = {\n maxWordCount: 25,\n maxClauseCount: 3,\n}\n\nexport const sentenceComplexity: Rule<SentenceComplexityOptions> = {\n id: 'sentence-complexity',\n description: 'Flag sentences that exceed a word or clause threshold',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Long, clause-heavy sentences are harder to read. Split them into shorter sentences that each make one point.',\n\n check({ text, sourceMap, options }: RuleInput<SentenceComplexityOptions>): Diagnostic[] {\n const maxWordCount = options.maxWordCount ?? DEFAULT_OPTIONS.maxWordCount\n const maxClauseCount = options.maxClauseCount ?? DEFAULT_OPTIONS.maxClauseCount\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.offset || !sentence.terms) continue\n\n const terms = sentence.terms.filter(term => /[a-zA-Z0-9]/.test(term.text ?? ''))\n const wordCount = terms.length\n\n const clauseCount = terms.filter((term) => {\n const tags = term.tags ?? []\n return tags.includes('Verb') && !tags.includes('Gerund') && !tags.includes('Infinitive') && !tags.includes('Particle')\n }).length\n\n if (wordCount <= maxWordCount && clauseCount <= maxClauseCount) continue\n\n const start = sentence.offset.start ?? 0\n const length = sentence.offset.length ?? 0\n const end = start + length\n\n const sourceStart = sourceMap[start]\n const sourceEnd = sourceMap[end - 1]\n if (sourceStart === undefined || sourceEnd === undefined) continue\n\n const reasons: string[] = []\n if (wordCount > maxWordCount) reasons.push(`${wordCount} words (max ${maxWordCount})`)\n if (clauseCount > maxClauseCount) reasons.push(`${clauseCount} clauses (max ${maxClauseCount})`)\n\n diagnostics.push({\n ruleId: 'sentence-complexity',\n severity: 'warn',\n message: `sentence is too complex: ${reasons.join(', ')} — consider splitting it`,\n range: { start: sourceStart, end: sourceEnd + 1 },\n help: sentenceComplexity.help,\n suggest: {\n description: 'Split this sentence into shorter sentences, one idea each.',\n edits: [],\n },\n })\n }\n\n return diagnostics\n },\n}\n","import type { Rule } from '@faircopy/core'\nimport { noAbsoluteIntensifiers } from './no-absolute-intensifiers.js'\nimport { noAdverbOveruse } from './no-adverb-overuse.js'\nimport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nimport { noComplexReadability } from './no-complex-readability.js'\nimport { noExpletiveOpeners } from './no-expletive-openers.js'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nimport { noFuturePromises } from './no-future-promises.js'\nimport { noHedgeWords } from './no-hedge-words.js'\nimport { noJargon } from './no-jargon.js'\nimport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noOverlyComplexSentences } from './no-overly-complex-sentences.js'\nimport { noOverusedAdverbs } from './no-overused-adverbs.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\nimport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nimport { noQualifierCreep } from './no-qualifier-creep.js'\nimport { noRedundantPairs } from './no-redundant-pairs.js'\nimport { noStackedAdjectives } from './no-stacked-adjectives.js'\nimport { noSuperlativeClaims } from './no-superlative-claims.js'\nimport { noVagueComparatives } from './no-vague-comparatives.js'\nimport { noVagueQuantifiers } from './no-vague-quantifiers.js'\nimport { noWeakModals } from './no-weak-modals.js'\nimport { sentenceComplexity } from './sentence-complexity.js'\n\nexport { noAbsoluteIntensifiers } from './no-absolute-intensifiers.js'\nexport { noAdverbOveruse } from './no-adverb-overuse.js'\nexport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nexport { noComplexReadability } from './no-complex-readability.js'\nexport { noExpletiveOpeners } from './no-expletive-openers.js'\nexport { noFilterWords } from './no-filter-words.js'\nexport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nexport { noFuturePromises } from './no-future-promises.js'\nexport { noHedgeWords } from './no-hedge-words.js'\nexport { noJargon } from './no-jargon.js'\nexport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noOverlyComplexSentences } from './no-overly-complex-sentences.js'\nexport { noOverusedAdverbs } from './no-overused-adverbs.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nexport { noQualifierCreep } from './no-qualifier-creep.js'\nexport { noRedundantPairs } from './no-redundant-pairs.js'\nexport { noStackedAdjectives } from './no-stacked-adjectives.js'\nexport { noSuperlativeClaims } from './no-superlative-claims.js'\nexport { noVagueComparatives } from './no-vague-comparatives.js'\nexport { noVagueQuantifiers } from './no-vague-quantifiers.js'\nexport { noWeakModals } from './no-weak-modals.js'\nexport { sentenceComplexity } from './sentence-complexity.js'\nexport type { NoAbsoluteIntensifiersOptions } from './no-absolute-intensifiers.js'\nexport type { NoAdverbOveruseOptions } from './no-adverb-overuse.js'\nexport type { NoBuzzwordStacksOptions } from './no-buzzword-stacks.js'\nexport type { NoComplexReadabilityOptions } from './no-complex-readability.js'\nexport type { NoExpletiveOpenersOptions } from './no-expletive-openers.js'\nexport type { NoFilterWordsOptions } from './no-filter-words.js'\nexport type { NoEmptyTransformationClaimsOptions } from './no-empty-transformation-claims.js'\nexport type { NoFuturePromisesOptions } from './no-future-promises.js'\nexport type { NoHedgeWordsOptions } from './no-hedge-words.js'\nexport type { NoJargonOptions } from './no-jargon.js'\nexport type { NoMeaninglessModifiersOptions } from './no-meaningless-modifiers.js'\nexport type { NoNominalizedPhrasesOptions } from './no-nominalized-phrases.js'\nexport type { NoOverlyComplexSentencesOptions } from './no-overly-complex-sentences.js'\nexport type { NoOverusedAdverbsOptions } from './no-overused-adverbs.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\nexport type { NoPronounLedClaimsOptions } from './no-pronoun-led-claims.js'\nexport type { NoQualifierCreepOptions } from './no-qualifier-creep.js'\nexport type { NoRedundantPairsOptions } from './no-redundant-pairs.js'\nexport type { NoStackedAdjectivesOptions } from './no-stacked-adjectives.js'\nexport type { NoSuperlativeClaimsOptions } from './no-superlative-claims.js'\nexport type { NoVagueComparativesOptions } from './no-vague-comparatives.js'\nexport type { NoVagueQuantifiersOptions } from './no-vague-quantifiers.js'\nexport type { NoWeakModalsOptions } from './no-weak-modals.js'\nexport type { SentenceComplexityOptions } from './sentence-complexity.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-absolute-intensifiers', noAbsoluteIntensifiers as Rule],\n ['no-adverb-overuse', noAdverbOveruse as Rule],\n ['no-buzzword-stacks', noBuzzwordStacks as Rule],\n ['no-complex-readability', noComplexReadability as Rule],\n ['no-empty-transformation-claims', noEmptyTransformationClaims as Rule],\n ['no-expletive-openers', noExpletiveOpeners as Rule],\n ['no-filter-words', noFilterWords as Rule],\n ['no-future-promises', noFuturePromises as Rule],\n ['no-hedge-words', noHedgeWords as Rule],\n ['no-jargon', noJargon as Rule],\n ['no-meaningless-modifiers', noMeaninglessModifiers as Rule],\n ['no-nominalized-phrases', noNominalizedPhrases as Rule],\n ['no-overly-complex-sentences', noOverlyComplexSentences as Rule],\n ['no-overused-adverbs', noOverusedAdverbs as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n ['no-pronoun-led-claims', noPronounLedClaims as Rule],\n ['no-qualifier-creep', noQualifierCreep as Rule],\n ['no-redundant-pairs', noRedundantPairs as Rule],\n ['no-stacked-adjectives', noStackedAdjectives as Rule],\n ['no-superlative-claims', noSuperlativeClaims as Rule],\n ['no-vague-comparatives', noVagueComparatives as Rule],\n ['no-vague-quantifiers', noVagueQuantifiers as Rule],\n ['no-weak-modals', noWeakModals as Rule],\n ['sentence-complexity', sentenceComplexity as Rule],\n])\n"],"mappings":"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","names":["DEFAULT_PHRASES","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","escapeRegex","DEFAULT_ALLOWED_ADVERBS","escapeRegex","range","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","nlp","DEFAULT_OPTIONS","nlp","range","escapeRegExp","DEFAULT_VERBS","DEFAULT_OPTIONS"]}
1
+ {"version":3,"sources":["../src/no-absolute-intensifiers.ts","../src/utils.ts","../src/no-adverb-overuse.ts","../src/no-buzzword-stacks.ts","../src/no-complex-readability.ts","../src/no-expletive-openers.ts","../src/no-filter-words.ts","../src/no-empty-transformation-claims.ts","../src/no-future-promises.ts","../src/no-hedge-words.ts","../src/no-jargon.ts","../src/no-meaningless-modifiers.ts","../src/no-non-inclusive-language.ts","../src/no-nominalized-phrases.ts","../src/no-overly-complex-sentences.ts","../src/no-overused-adverbs.ts","../src/no-passive-voice.ts","../src/no-pronoun-led-claims.ts","../src/no-qualifier-creep.ts","../src/no-redundant-pairs.ts","../src/no-stacked-adjectives.ts","../src/no-superlative-claims.ts","../src/no-vague-comparatives.ts","../src/no-vague-quantifiers.ts","../src/no-weak-modals.ts","../src/sentence-complexity.ts","../src/index.ts"],"sourcesContent":["import type { Diagnostic, Rule, RuleInput, Suggestion } from '@faircopy/core'\n\nexport interface NoAbsoluteIntensifiersOptions {\n intensifiers?: string[]\n absolutes?: string[]\n}\n\nconst DEFAULT_INTENSIFIERS = [\n 'very',\n 'really',\n 'completely',\n 'totally',\n 'absolutely',\n 'utterly',\n 'quite',\n 'extremely',\n 'perfectly',\n 'entirely',\n]\n\nconst DEFAULT_ABSOLUTES = [\n 'unique',\n 'finished',\n 'destroyed',\n 'essential',\n 'perfect',\n 'impossible',\n 'dead',\n 'empty',\n 'full',\n 'silent',\n 'unanimous',\n 'infinite',\n 'eternal',\n 'flawless',\n 'ultimate',\n 'final',\n 'complete',\n 'total',\n 'absolute',\n]\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction longestFirst(values: string[]): string[] {\n return [...values].sort((left, right) => right.length - left.length)\n}\n\nfunction buildPattern(intensifiers: string[], absolutes: string[]): RegExp {\n const intensifierPattern = longestFirst(intensifiers)\n .map(phrase => escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+'))\n .join('|')\n const absolutePattern = longestFirst(absolutes)\n .map(phrase => escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+'))\n .join('|')\n\n return new RegExp(\n `\\\\b(${intensifierPattern})\\\\s+\\\\b(${absolutePattern})(?![\\\\w'-])`,\n 'gi',\n )\n}\n\nexport const noAbsoluteIntensifiers: Rule<NoAbsoluteIntensifiersOptions> = {\n id: 'no-absolute-intensifiers',\n description: 'Flag intensifiers before absolute adjectives',\n defaults: {\n intensifiers: DEFAULT_INTENSIFIERS,\n absolutes: DEFAULT_ABSOLUTES,\n },\n help: 'Absolute adjectives already express an extreme. Intensifiers like \"very\" ' +\n 'before them are redundant and weaken the claim. Remove the intensifier ' +\n 'or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoAbsoluteIntensifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const intensifiers = options.intensifiers?.length ? options.intensifiers : DEFAULT_INTENSIFIERS\n const absolutes = options.absolutes?.length ? options.absolutes : DEFAULT_ABSOLUTES\n\n if (!intensifiers.length || !absolutes.length) return diagnostics\n\n const re = buildPattern(intensifiers, absolutes)\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedText = match[0]\n const intensifier = match[1]!\n const absolute = match[2]!\n const matchStart = match.index\n const matchEnd = matchStart + matchedText.length\n\n const start = sourceMap[matchStart]\n const end = sourceMap[matchEnd - 1]\n if (start === undefined || end === undefined) continue\n\n const suggest: Suggestion = {\n description: `remove \"${intensifier}\" before \"${absolute}\"`,\n edits: [{ range: { start, end: end + 1 }, replacement: absolute }],\n }\n\n diagnostics.push({\n ruleId: 'no-absolute-intensifiers',\n severity: 'warn',\n message: `\"${matchedText}\" is redundant — \"${absolute}\" is already absolute`,\n range: { start, end: end + 1 },\n help: noAbsoluteIntensifiers.help,\n suggest,\n })\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import nlp from 'compromise'\nimport type { Diagnostic } from '@faircopy/core'\nimport type { DocView, JsonOffsetEntry, JsonOffsetTerm, MatchView } from './types.js'\n\nexport interface MatchOccurrence {\n text: string\n start: number\n end: number\n}\n\nexport function createDoc(text: string): DocView {\n return nlp(text) as unknown as DocView\n}\n\nexport function getMatchOccurrences(text: string, matches: MatchView): MatchOccurrence[] {\n const json = matches.json({ offset: true, text: true, terms: { offset: true } }) as JsonOffsetEntry[]\n\n return json.flatMap((entry) => {\n const start = entry.offset?.start ?? entry.terms?.[0]?.offset?.start\n const length = entry.offset?.length ?? sumTermLengths(entry.terms)\n\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) {\n return []\n }\n\n return [{\n text: entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n }]\n })\n}\n\nexport function getOccurrenceRange(sourceMap: number[], occurrence: MatchOccurrence): Diagnostic['range'] | null {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) return null\n\n return { start, end: end + 1 }\n}\n\nfunction sumTermLengths(terms: JsonOffsetTerm[] | undefined): number | undefined {\n if (!terms?.length) return undefined\n\n let total = 0\n for (const term of terms) {\n const length = term.offset?.length\n if (typeof length !== 'number') return undefined\n total += length\n }\n\n return total\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getOccurrenceRange } from './utils.js'\n\nexport interface NoAdverbOveruseOptions {\n /** Maximum number of -ly adverbs allowed per sentence before the rest are flagged. */\n maxAdverbs?: number\n /** Adverbs that are allowed and do not count toward the threshold. */\n allowedAdverbs?: string[]\n}\n\nconst DEFAULT_MAX_ADVERBS = 2\nconst DEFAULT_ALLOWED_ADVERBS = ['only']\n\nexport const noAdverbOveruse: Rule<NoAdverbOveruseOptions> = {\n id: 'no-adverb-overuse',\n description: 'Flag sentences with more than two -ly adverbs',\n defaults: { maxAdverbs: DEFAULT_MAX_ADVERBS, allowedAdverbs: DEFAULT_ALLOWED_ADVERBS },\n help: 'Too many -ly adverbs in one sentence make copy feel padded. Remove the adverb, replace it with a stronger verb or adjective, or add it to allowedAdverbs if it is essential.',\n\n check({ text, sourceMap, options }: RuleInput<NoAdverbOveruseOptions>): Diagnostic[] {\n const maxAdverbs = options.maxAdverbs ?? DEFAULT_MAX_ADVERBS\n const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS).map(value => value.toLowerCase()))\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.terms) continue\n\n const occurrences = sentence.terms\n .filter((term) => term.tags?.includes('Adverb'))\n .map((term) => {\n const start = term.offset?.start\n const length = term.offset?.length\n return {\n text: term.text?.replace(/[^a-zA-Z]+$/, '') ?? '',\n start: typeof start === 'number' ? start : 0,\n end: typeof start === 'number' && typeof length === 'number' ? start + length : 0,\n }\n })\n .filter((occurrence) => /ly$/i.test(occurrence.text))\n .filter((occurrence) => !allowed.has(occurrence.text.toLowerCase()))\n .sort((left, right) => left.start - right.start)\n\n for (let index = maxAdverbs; index < occurrences.length; index += 1) {\n const occurrence = occurrences[index]!\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-adverb-overuse',\n severity: 'warn',\n message: `reduce adverb overuse: \"${occurrence.text}\" exceeds the limit of ${maxAdverbs} -ly adverb${maxAdverbs === 1 ? '' : 's'} per sentence`,\n range,\n help: noAdverbOveruse.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoBuzzwordStacksOptions {\n terms?: string[]\n maxTermsPerSentence?: number\n}\n\nconst DEFAULT_TERMS = [\n 'alignment',\n 'automation',\n 'collaboration',\n 'efficiency',\n 'engagement',\n 'experience',\n 'growth',\n 'impact',\n 'innovation',\n 'intelligence',\n 'optimization',\n 'platform',\n 'productivity',\n 'solution',\n 'strategy',\n 'transformation',\n 'value',\n 'velocity',\n 'workflow',\n]\n\nexport const noBuzzwordStacks: Rule<NoBuzzwordStacksOptions> = {\n id: 'no-buzzword-stacks',\n description: 'Flag sentences overloaded with abstract benefit nouns',\n defaults: { terms: DEFAULT_TERMS, maxTermsPerSentence: 2 },\n help: 'Buzzword-heavy sentences make copy sound interchangeable. Replace abstract benefit nouns with the specific product behavior, customer outcome, or proof point.',\n\n check({ text, sourceMap, options }: RuleInput<NoBuzzwordStacksOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const terms = options.terms?.length ? options.terms : DEFAULT_TERMS\n const maxTermsPerSentence = options.maxTermsPerSentence ?? 2\n if (maxTermsPerSentence < 1) return diagnostics\n\n for (const sentence of getSentenceRanges(text)) {\n const hits = getTermHits(sentence.text, terms)\n if (hits.length <= maxTermsPerSentence) continue\n\n const startOffset = sentence.start + hits[0]!.start\n const endOffset = sentence.start + hits[hits.length - 1]!.end\n const start = sourceMap[startOffset]\n const end = sourceMap[endOffset - 1]\n if (start === undefined || end === undefined) continue\n\n const words = hits.map(hit => hit.text.toLowerCase()).join(', ')\n diagnostics.push({\n ruleId: 'no-buzzword-stacks',\n severity: 'warn',\n message: `replace buzzword stack: ${words}`,\n range: { start, end: end + 1 },\n help: noBuzzwordStacks.help,\n })\n }\n\n return diagnostics\n },\n}\n\ninterface SentenceRange {\n text: string\n start: number\n}\n\ninterface TermHit {\n text: string\n start: number\n end: number\n}\n\nfunction getSentenceRanges(text: string): SentenceRange[] {\n const ranges: SentenceRange[] = []\n const re = /[^.!?]+[.!?]?/g\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const leadingWhitespaceLength = match[0].match(/^\\s*/)?.[0].length ?? 0\n const sentence = match[0].trim()\n if (!sentence) continue\n ranges.push({ text: sentence, start: match.index + leadingWhitespaceLength })\n }\n return ranges\n}\n\nfunction getTermHits(text: string, terms: string[]): TermHit[] {\n const termPattern = terms.map(escapeRegex).join('|')\n if (!termPattern) return []\n\n const hits: TermHit[] = []\n const re = new RegExp(`\\\\b(${termPattern})\\\\b`, 'gi')\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n hits.push({ text: match[0], start: match.index, end: match.index + match[0].length })\n }\n return hits\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoComplexReadabilityOptions {\n /** Target Flesch-Kincaid grade level. Text scoring above this is flagged. */\n maxGradeLevel?: number\n /** Minimum sentence count before scoring. Shorter passages are too noisy. */\n minSentences?: number\n /** Minimum word count before scoring. */\n minWords?: number\n}\n\nconst DEFAULT_OPTIONS: Required<NoComplexReadabilityOptions> = {\n maxGradeLevel: 12,\n minSentences: 3,\n minWords: 30,\n}\n\nexport const noComplexReadability: Rule<NoComplexReadabilityOptions> = {\n id: 'no-complex-readability',\n description: 'Flag prose whose Flesch-Kincaid grade level exceeds a target',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Landing-page copy should be readable by a broad audience. Break long sentences, replace jargon, and front-load the point until the grade level drops.',\n\n check({ text, sourceMap, options }: RuleInput<NoComplexReadabilityOptions>): Diagnostic[] {\n const maxGradeLevel = options.maxGradeLevel ?? DEFAULT_OPTIONS.maxGradeLevel\n const minSentences = options.minSentences ?? DEFAULT_OPTIONS.minSentences\n const minWords = options.minWords ?? DEFAULT_OPTIONS.minWords\n\n const sentences = getSentences(text)\n const words = getWords(text)\n\n if (sentences.length < minSentences || words.length < minWords) {\n return []\n }\n\n const syllables = words.reduce((sum, word) => sum + countSyllables(word), 0)\n const grade = fleschKincaidGrade(words.length, sentences.length, syllables)\n\n if (grade <= maxGradeLevel) {\n return []\n }\n\n const start = sourceMap[0]\n const end = sourceMap[sourceMap.length - 1]\n if (start === undefined || end === undefined) return []\n\n return [{\n ruleId: 'no-complex-readability',\n severity: 'warn',\n message: `readability is grade ${grade.toFixed(1)} — simplify to ${maxGradeLevel} or below`,\n range: { start, end: end + 1 },\n help: noComplexReadability.help,\n }]\n },\n}\n\nfunction getSentences(text: string): string[] {\n // Split on sentence terminators, keeping the delimiter so trailing spaces are preserved.\n const parts = text.split(/([.!?]+)/)\n const sentences: string[] = []\n for (let i = 0; i < parts.length; i += 2) {\n const sentence = parts[i]\n const terminator = parts[i + 1] ?? ''\n const combined = (sentence ?? '') + terminator\n const trimmed = combined.trim()\n if (trimmed) sentences.push(trimmed)\n }\n return sentences\n}\n\nfunction getWords(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s'-]/g, ' ')\n .split(/\\s+/)\n .filter(word => word.length > 0 && /[a-z0-9]/.test(word))\n}\n\nfunction countSyllables(word: string): number {\n const cleaned = word.toLowerCase().replace(/[^a-z]/g, '')\n if (!cleaned) return 0\n if (cleaned.length <= 3) return 1\n\n const vowels = cleaned.match(/[aeiouy]+/g)\n if (!vowels) return 1\n\n let count = vowels.length\n if (cleaned.endsWith('e')) count--\n if (cleaned.endsWith('le') && cleaned.length > 2 && !/[aeiouy]$/.test(cleaned[cleaned.length - 3] ?? '')) {\n count++\n }\n return Math.max(1, count)\n}\n\nfunction fleschKincaidGrade(words: number, sentences: number, syllables: number): number {\n if (sentences === 0 || words === 0) return 0\n return 0.39 * (words / sentences) + 11.8 * (syllables / words) - 15.59\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoExpletiveOpenersOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'there is',\n 'there are',\n 'there was',\n 'there were',\n 'there will be',\n]\n\nexport const noExpletiveOpeners: Rule<NoExpletiveOpenersOptions> = {\n id: 'no-expletive-openers',\n description: 'Flag sentence openings that delay the real subject',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Expletive openers like \"there are\" make copy indirect. Start with the actor, product, or benefit so the sentence lands faster.',\n\n check({ text, sourceMap, options }: RuleInput<NoExpletiveOpenersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n if (!startsSentence(text, occurrence.start)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-expletive-openers',\n severity: 'warn',\n message: `start with the real subject instead of \"${occurrence.text.toLowerCase()}\"`,\n range,\n help: noExpletiveOpeners.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction startsSentence(text: string, start: number): boolean {\n let index = start - 1\n while (index >= 0 && /\\s/.test(text[index]!)) index--\n return index < 0 || /[.!?]/.test(text[index]!)\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoFilterWordsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'I think',\n 'it seems',\n 'basically',\n 'in order to',\n]\n\nexport const noFilterWords: Rule<NoFilterWordsOptions> = {\n id: 'no-filter-words',\n description: 'Ban filter phrases that distance the claim from the reader',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Filter phrases announce a perspective or pad the sentence instead of making the point. Delete the phrase or rewrite the sentence so the claim stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoFilterWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const doc = createDoc(text)\n\n for (const phrase of phrases) {\n const matches = doc.match(phrase)\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-filter-words',\n severity: 'error',\n message: `remove \"${occurrence.text.toLowerCase()}\" — state the claim directly`,\n range: { start, end: end + 1 },\n help: noFilterWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoEmptyTransformationClaimsOptions {\n allowedPhrases?: string[]\n}\n\ninterface Pattern {\n re: RegExp\n message: string\n}\n\nconst PATTERNS: Pattern[] = [\n {\n re: /\\b(?:transform(?:s|ed|ing)?|chang(?:e|es|ed|ing)|reimagin(?:e|es|ed|ing)|revolutioniz(?:e|es|ed|ing))\\s+the\\s+way\\s+(?:you|your\\s+team|teams|companies|businesses|people)\\s+(?:work|build|sell|operate|collaborate|communicate|create|grow|ship|scale|learn|manage)\\b/gi,\n message: 'replace empty transformation claim with a concrete outcome',\n },\n {\n re: /\\bunlock\\s+(?:your|their|team|teams'|your\\s+team's|the\\s+team's|the\\s+full)\\s+(?:potential|productivity|growth|creativity|efficiency)\\b/gi,\n message: 'replace empty unlock claim with the specific benefit',\n },\n {\n re: /\\btake\\s+(?:your|their|team|teams'|your\\s+team's|the\\s+team's)?\\s*(?:productivity|workflow|workflows|growth|collaboration|business|operations|process|processes)\\s+to\\s+the\\s+next\\s+level\\b/gi,\n message: 'replace next-level claim with measurable value',\n },\n]\n\nexport const noEmptyTransformationClaims: Rule<NoEmptyTransformationClaimsOptions> = {\n id: 'no-empty-transformation-claims',\n description: 'Flag broad transformation claims that do not name a concrete outcome',\n defaults: { allowedPhrases: [] },\n help: 'Transformation cliches promise a feeling instead of a result. Replace them with the specific workflow, metric, or customer outcome the product changes.',\n\n check({ text, sourceMap, options }: RuleInput<NoEmptyTransformationClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const allowedPhrases = new Set((options.allowedPhrases ?? []).map(value => normalize(value)))\n\n for (const pattern of PATTERNS) {\n const re = new RegExp(pattern.re.source, pattern.re.flags)\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const phrase = match[0]\n if (allowedPhrases.has(normalize(phrase))) continue\n\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-empty-transformation-claims',\n severity: 'warn',\n message: `${pattern.message}: \"${phrase}\"`,\n range: { start, end: end + 1 },\n help: noEmptyTransformationClaims.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction normalize(value: string): string {\n return value.toLowerCase().replace(/\\s+/g, ' ').trim()\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoFuturePromisesOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'will help you',\n 'will enable',\n 'will transform',\n 'will allow you to',\n 'will empower',\n]\n\nexport const noFuturePromises: Rule<NoFuturePromisesOptions> = {\n id: 'no-future-promises',\n description: 'Flag future-tense promises without present evidence',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Future promises defer proof. Rewrite the claim around current behavior, concrete evidence, or a specific outcome already available.',\n\n check({ text, sourceMap, options }: RuleInput<NoFuturePromisesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-future-promises',\n severity: 'warn',\n message: `replace future promise \"${matchedPhrase}\" with present evidence`,\n range: { start, end: end + 1 },\n help: noFuturePromises.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoHedgeWordsOptions {\n hedges?: string[]\n}\n\nconst DEFAULT_HEDGES = [\n 'kind of',\n 'sort of',\n 'somewhat',\n 'fairly',\n 'pretty',\n 'rather',\n 'quite',\n 'arguably',\n 'relatively',\n 'more or less',\n]\n\nexport const noHedgeWords: Rule<NoHedgeWordsOptions> = {\n id: 'no-hedge-words',\n description: 'Flag hedge words that soften claims',\n defaults: { hedges: DEFAULT_HEDGES },\n help: 'Hedge words make claims sound uncertain. Remove the hedge or replace the sentence with a specific proof point. Words like \"pretty\" and \"quite\" can be intentional adjectives in some contexts; override hedges when that trade-off is too noisy for your copy.',\n\n check({ text, sourceMap, options }: RuleInput<NoHedgeWordsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const hedges = options.hedges?.length ? options.hedges : DEFAULT_HEDGES\n\n for (const hedge of hedges) {\n const re = new RegExp(`\\\\b${escapeRegExp(hedge).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const phrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-hedge-words',\n severity: 'warn',\n message: `remove hedge \"${phrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noHedgeWords.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoJargonOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'leverage',\n 'synergy',\n 'ideate',\n 'circle back',\n 'deep dive',\n 'drill down',\n 'move the needle',\n 'best of breed',\n 'best in class',\n]\n\nexport const noJargon: Rule<NoJargonOptions> = {\n id: 'no-jargon',\n description: 'Flag business jargon and vague workplace idioms',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Jargon makes copy sound generic. Replace it with the specific action, technical behavior, or customer outcome, or tune the phrase list for terms that are legitimate in your context.',\n\n check({ text, sourceMap, options }: RuleInput<NoJargonOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-jargon',\n severity: 'warn',\n message: `replace jargon phrase \"${matchedPhrase}\"`,\n range: { start, end: end + 1 },\n help: noJargon.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoMeaninglessModifiersOptions {\n modifiers?: string[]\n}\n\nconst DEFAULT_MODIFIERS = [\n 'very',\n 'really',\n 'actually',\n 'basically',\n 'literally',\n 'essentially',\n 'truly',\n 'definitely',\n 'clearly',\n 'obviously',\n]\n\nexport const noMeaninglessModifiers: Rule<NoMeaninglessModifiersOptions> = {\n id: 'no-meaningless-modifiers',\n description: 'Flag intensifiers that add no information',\n defaults: { modifiers: DEFAULT_MODIFIERS },\n help: 'Meaningless modifiers inflate a claim without adding evidence. Delete the modifier, replace it with a concrete detail, or opt out specific words such as \"clearly\" and \"obviously\" when they are part of your voice.',\n\n check({ text, sourceMap, options }: RuleInput<NoMeaninglessModifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const modifiers = options.modifiers?.length ? options.modifiers : DEFAULT_MODIFIERS\n\n for (const modifier of modifiers) {\n const re = new RegExp(`\\\\b${escapeRegExp(modifier).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedModifier = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedModifier.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-meaningless-modifiers',\n severity: 'warn',\n message: `remove meaningless modifier \"${matchedModifier}\"`,\n range: { start, end: end + 1 },\n help: noMeaninglessModifiers.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getOccurrenceRange, type MatchOccurrence } from './utils.js'\n\nexport interface NonInclusiveTerm {\n term: string\n alternatives: string[]\n /** Require the whole phrase to match with word boundaries. Default false. */\n exact?: boolean\n}\n\nexport interface NoNonInclusiveLanguageOptions {\n /** Terms to flag with suggested alternatives. */\n terms?: NonInclusiveTerm[]\n /** Terms to allow and skip. */\n allowedTerms?: string[]\n}\n\nconst DEFAULT_TERMS: NonInclusiveTerm[] = [\n { term: 'guys', alternatives: ['everyone', 'team', 'folks'] },\n { term: 'manpower', alternatives: ['workforce', 'staffing', 'personnel'] },\n { term: 'whitelist', alternatives: ['allowlist'] },\n { term: 'blacklist', alternatives: ['denylist', 'blocklist'] },\n { term: 'master', alternatives: ['primary', 'main', 'leader'] },\n { term: 'slave', alternatives: ['secondary', 'replica', 'follower'] },\n { term: 'crazy', alternatives: ['unexpected', 'intense', 'extreme'] },\n { term: 'insane', alternatives: ['extreme', 'unbelievable', 'remarkable'] },\n { term: 'dumb', alternatives: ['unhelpful', 'poor', 'uninformed'] },\n { term: 'lame', alternatives: ['unimpressive', 'inadequate', 'weak'] },\n { term: 'sanity check', alternatives: ['quick check', 'confidence check', 'verification'], exact: true },\n { term: 'blind spot', alternatives: ['unaware area', 'gap', 'oversight'], exact: true },\n { term: 'grandfathered', alternatives: ['legacy status', 'exempted'] },\n { term: 'mankind', alternatives: ['humanity', 'humankind', 'people'] },\n]\n\n/** Terms that can also be verbs; only flag when used as nouns or adjectives. */\nconst VERB_AMBIGUOUS_TERMS = new Set(['master', 'slave'])\n\ninterface JsonTerm {\n text?: string\n normal?: string\n tags?: string[]\n offset?: { start?: number; length?: number }\n}\n\ninterface JsonMatch {\n text?: string\n terms?: JsonTerm[]\n offset?: { start?: number; length?: number }\n}\n\nfunction toMatchPattern(rawTerm: string): string {\n return rawTerm\n}\n\nfunction isProblematicUsage(term: NonInclusiveTerm, terms: JsonTerm[]): boolean {\n if (!VERB_AMBIGUOUS_TERMS.has(term.term.toLowerCase())) return true\n if (terms.length !== 1) return true\n const tags = terms[0]!.tags ?? []\n const isVerb = tags.includes('Verb')\n const isNounOrAdjective = tags.includes('Noun') || tags.includes('Adjective')\n // Skip verb-only usages of ambiguous terms (e.g., \"master the skill\").\n return !isVerb || isNounOrAdjective\n}\n\nfunction getOccurrenceFromMatch(entry: JsonMatch, originalText: string): MatchOccurrence | null {\n const terms = entry.terms\n if (!terms?.length) return null\n\n const firstOffset = terms[0]!.offset\n const lastOffset = terms[terms.length - 1]!.offset\n if (\n typeof firstOffset?.start !== 'number' ||\n typeof firstOffset.length !== 'number' ||\n typeof lastOffset?.start !== 'number' ||\n typeof lastOffset.length !== 'number'\n ) {\n return null\n }\n\n const start = firstOffset.start\n const end = lastOffset.start + lastOffset.length\n if (end <= start) return null\n\n return {\n text: originalText.slice(start, end),\n start,\n end,\n }\n}\n\nexport const noNonInclusiveLanguage: Rule<NoNonInclusiveLanguageOptions> = {\n id: 'no-non-inclusive-language-nlp',\n description: 'Flag non-inclusive terms and suggest neutral alternatives using NLP-aware matching',\n defaults: { terms: DEFAULT_TERMS, allowedTerms: [] },\n help: 'Non-inclusive terms can alienate readers. Replace them with neutral alternatives that name the same idea without relying on identity, ability, or historical power metaphors.',\n\n check({ text, sourceMap, options }: RuleInput<NoNonInclusiveLanguageOptions>): Diagnostic[] {\n const terms = options.terms?.length ? options.terms : DEFAULT_TERMS\n const allowed = new Set((options.allowedTerms ?? []).map(term => term.toLowerCase()))\n\n const doc = createDoc(text)\n const diagnostics: Diagnostic[] = []\n\n for (const term of terms) {\n if (allowed.has(term.term.toLowerCase())) continue\n\n const pattern = toMatchPattern(term.term)\n const matches = doc.match(pattern)\n const json = matches.json({ offset: true, text: true, terms: { offset: true, text: true, tags: true } }) as JsonMatch[]\n\n for (const entry of json) {\n if (!isProblematicUsage(term, entry.terms ?? [])) continue\n\n const occurrence = getOccurrenceFromMatch(entry, text)\n if (!occurrence) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n const suggestion = term.alternatives.join(', ')\n diagnostics.push({\n ruleId: 'no-non-inclusive-language-nlp',\n severity: 'error',\n message: `replace \"${occurrence.text}\" with a neutral alternative such as \"${suggestion}\"`,\n range,\n help: noNonInclusiveLanguage.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoNominalizedPhrasesOptions {\n suffixes?: string[]\n allowedWords?: string[]\n}\n\nconst DEFAULT_SUFFIXES = ['tion', 'sion', 'ment', 'ance', 'ence', 'ity']\nconst DEFAULT_ALLOWED_WORDS = [\n 'accessibility',\n 'availability',\n 'capacity',\n 'community',\n 'identity',\n 'opportunity',\n 'privacy',\n 'quality',\n 'reliability',\n 'security',\n]\n\nexport const noNominalizedPhrases: Rule<NoNominalizedPhrasesOptions> = {\n id: 'no-nominalized-phrases',\n description: 'Flag nominalized \"X of Y\" phrases that hide the action in a noun',\n defaults: { suffixes: DEFAULT_SUFFIXES, allowedWords: DEFAULT_ALLOWED_WORDS },\n help: 'Nominalized phrases bury the action. Rewrite the phrase with a verb so the sentence says who does what.',\n\n check({ text, sourceMap, options }: RuleInput<NoNominalizedPhrasesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const suffixes = options.suffixes?.length ? options.suffixes : DEFAULT_SUFFIXES\n const allowedWords = new Set((options.allowedWords?.length ? options.allowedWords : DEFAULT_ALLOWED_WORDS).map(value => value.toLowerCase()))\n const suffixPattern = suffixes.map(escapeRegex).join('|')\n if (!suffixPattern) return diagnostics\n\n const doc = createDoc(text)\n const matches = doc.match(`/[a-z]+(${suffixPattern})/ of .`)\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const nominalization = occurrence.text.trim().split(/\\s+/)[0]?.toLowerCase()\n if (!nominalization || allowedWords.has(nominalization)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-nominalized-phrases',\n severity: 'warn',\n message: `rewrite \"${occurrence.text}\" with a verb`,\n range,\n help: noNominalizedPhrases.help,\n })\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc } from './utils.js'\n\nexport interface NoOverlyComplexSentencesOptions {\n /** Maximum total conjunctions allowed in a single sentence. */\n maxConjunctions?: number\n /** Maximum coordinating conjunctions allowed in a single sentence. */\n maxCoordinating?: number\n /** Maximum subordinating conjunctions allowed in a single sentence. */\n maxSubordinating?: number\n /** Words or phrases to treat as coordinating conjunctions. */\n coordinating?: string[]\n /** Words or phrases to treat as subordinating conjunctions. */\n subordinating?: string[]\n /** Words or phrases to ignore when counting. */\n allowList?: string[]\n}\n\nconst DEFAULT_MAX_CONJUNCTIONS = 4\nconst DEFAULT_MAX_COORDINATING = 3\nconst DEFAULT_MAX_SUBORDINATING = 2\n\nconst DEFAULT_COORDINATING = ['and', 'but', 'or', 'nor', 'yet', 'so']\nconst DEFAULT_SUBORDINATING = [\n 'because',\n 'although',\n 'though',\n 'while',\n 'since',\n 'unless',\n 'if',\n 'when',\n 'after',\n 'before',\n 'until',\n 'whether',\n 'once',\n]\n\ninterface ConjunctionPattern {\n phrase: string\n category: 'coordinating' | 'subordinating'\n regex: RegExp\n}\n\nexport const noOverlyComplexSentences: Rule<NoOverlyComplexSentencesOptions> = {\n id: 'no-overly-complex-sentences',\n description: 'Flag sentences with too many coordinating or subordinating conjunctions',\n defaults: {\n maxConjunctions: DEFAULT_MAX_CONJUNCTIONS,\n maxCoordinating: DEFAULT_MAX_COORDINATING,\n maxSubordinating: DEFAULT_MAX_SUBORDINATING,\n coordinating: DEFAULT_COORDINATING,\n subordinating: DEFAULT_SUBORDINATING,\n allowList: [],\n },\n help: 'Sentences packed with conjunctions are often run-ons or nested too deeply. Break them into shorter sentences so each point stands on its own.',\n\n check({ text, sourceMap, options }: RuleInput<NoOverlyComplexSentencesOptions>): Diagnostic[] {\n const maxConjunctions = options.maxConjunctions ?? DEFAULT_MAX_CONJUNCTIONS\n const maxCoordinating = options.maxCoordinating ?? DEFAULT_MAX_COORDINATING\n const maxSubordinating = options.maxSubordinating ?? DEFAULT_MAX_SUBORDINATING\n\n const coordinating = (options.coordinating ?? DEFAULT_COORDINATING).map(value =>\n value.toLowerCase().trim(),\n )\n const subordinating = (options.subordinating ?? DEFAULT_SUBORDINATING).map(value =>\n value.toLowerCase().trim(),\n )\n const allowList = new Set(\n (options.allowList ?? []).map(value => value.toLowerCase().trim()),\n )\n\n const patterns: ConjunctionPattern[] = [\n ...coordinating.map(phrase => ({\n phrase,\n category: 'coordinating' as const,\n regex: phraseToRegex(phrase),\n })),\n ...subordinating.map(phrase => ({\n phrase,\n category: 'subordinating' as const,\n regex: phraseToRegex(phrase),\n })),\n ].sort((a, b) => b.phrase.length - a.phrase.length)\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.offset || !sentence.terms) continue\n\n const sentenceStart = sentence.offset.start ?? 0\n const sentenceText = sentence.text ?? ''\n const matchedRanges: Array<{ start: number; end: number }> = []\n\n let coordinatingCount = 0\n let subordinatingCount = 0\n\n for (const { phrase, category, regex } of patterns) {\n if (allowList.has(phrase)) continue\n\n let match: RegExpExecArray | null\n while ((match = regex.exec(sentenceText)) !== null) {\n const matchStart = match.index\n const matchEnd = match.index + match[0].length\n\n // Avoid counting the same tokens twice when shorter phrases overlap.\n if (matchedRanges.some(range => matchStart < range.end && matchEnd > range.start)) {\n continue\n }\n\n matchedRanges.push({ start: matchStart, end: matchEnd })\n\n if (category === 'coordinating') {\n coordinatingCount += 1\n } else {\n subordinatingCount += 1\n }\n }\n }\n\n const total = coordinatingCount + subordinatingCount\n if (\n total <= maxConjunctions &&\n coordinatingCount <= maxCoordinating &&\n subordinatingCount <= maxSubordinating\n ) {\n continue\n }\n\n const length = sentence.offset.length ?? 0\n const sentenceEnd = sentenceStart + length\n\n const sourceStart = sourceMap[sentenceStart]\n const sourceEnd = sourceMap[sentenceEnd - 1]\n if (sourceStart === undefined || sourceEnd === undefined) continue\n\n const reasons: string[] = []\n if (total > maxConjunctions) {\n reasons.push(`${total} conjunctions (max ${maxConjunctions})`)\n }\n if (coordinatingCount > maxCoordinating) {\n reasons.push(`${coordinatingCount} coordinating (max ${maxCoordinating})`)\n }\n if (subordinatingCount > maxSubordinating) {\n reasons.push(`${subordinatingCount} subordinating (max ${maxSubordinating})`)\n }\n\n diagnostics.push({\n ruleId: 'no-overly-complex-sentences',\n severity: 'warn',\n message: `sentence is overly complex: ${reasons.join(', ')} — consider splitting it`,\n range: { start: sourceStart, end: sourceEnd + 1 },\n help: noOverlyComplexSentences.help,\n suggest: {\n description: 'Split this sentence into shorter sentences, one idea each.',\n edits: [],\n },\n })\n }\n\n return diagnostics\n },\n}\n\nfunction phraseToRegex(phrase: string): RegExp {\n const escaped = phrase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const spaced = escaped.replace(/\\s+/g, '\\\\s+')\n const source = spaced.includes('\\\\s+') ? spaced : `\\\\b${spaced}\\\\b`\n return new RegExp(source, 'gi')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getOccurrenceRange } from './utils.js'\n\nexport interface NoOverusedAdverbsOptions {\n /** Maximum occurrences of a single adverb allowed before later ones are flagged. */\n threshold?: number\n /** Minimum character length for an adverb to be considered. */\n minLength?: number\n /** Adverbs that are allowed and do not count toward the threshold. */\n allowedAdverbs?: string[]\n /** If provided, only these adverbs are checked. */\n adverbs?: string[]\n}\n\nconst DEFAULT_THRESHOLD = 3\nconst DEFAULT_MIN_LENGTH = 3\nconst DEFAULT_ALLOWED_ADVERBS = ['only', 'not']\n\nexport const noOverusedAdverbs: Rule<NoOverusedAdverbsOptions> = {\n id: 'no-overused-adverbs',\n description: 'Flag adverbs that appear excessively across the text',\n defaults: {\n threshold: DEFAULT_THRESHOLD,\n minLength: DEFAULT_MIN_LENGTH,\n allowedAdverbs: DEFAULT_ALLOWED_ADVERBS,\n },\n help: 'Repeating the same adverb throughout a passage weakens copy and reads as filler. Replace the adverb with a stronger verb or adjective, cut it, or add it to allowedAdverbs if it is essential.',\n\n check({ text, sourceMap, options }: RuleInput<NoOverusedAdverbsOptions>): Diagnostic[] {\n const threshold = options.threshold ?? DEFAULT_THRESHOLD\n const minLength = options.minLength ?? DEFAULT_MIN_LENGTH\n const allowed = new Set((options.allowedAdverbs ?? DEFAULT_ALLOWED_ADVERBS).map(value => value.toLowerCase()))\n const adverbFilter = options.adverbs ? new Set(options.adverbs.map(value => value.toLowerCase())) : null\n\n const doc = createDoc(text)\n const sentences = doc.json({ offset: true, terms: { offset: true, tags: true } })\n\n const occurrencesByAdverb = new Map<string, Array<{ text: string; start: number; end: number }>>()\n\n for (const sentence of sentences) {\n for (const term of sentence.terms ?? []) {\n if (!term.tags?.includes('Adverb')) continue\n\n const rawText = term.text?.replace(/[^a-zA-Z]+$/, '') ?? ''\n const normalized = rawText.toLowerCase()\n if (!normalized || normalized.length < minLength) continue\n if (allowed.has(normalized)) continue\n if (adverbFilter && !adverbFilter.has(normalized)) continue\n\n const start = term.offset?.start\n const length = term.offset?.length\n if (typeof start !== 'number' || typeof length !== 'number') continue\n\n const occurrence = { text: rawText, start, end: start + length }\n const existing = occurrencesByAdverb.get(normalized)\n if (existing) {\n existing.push(occurrence)\n } else {\n occurrencesByAdverb.set(normalized, [occurrence])\n }\n }\n }\n\n const diagnostics: Diagnostic[] = []\n\n for (const [, occurrences] of occurrencesByAdverb) {\n if (occurrences.length <= threshold) continue\n\n for (let index = threshold; index < occurrences.length; index += 1) {\n const occurrence = occurrences[index]!\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-overused-adverbs',\n severity: 'warn',\n message: `reduce overused adverb: \"${occurrence.text}\" appears ${occurrences.length} times`,\n range,\n help: noOverusedAdverbs.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences } from './utils.js'\n\nexport interface NoPassiveVoiceOptions {\n allowedAuxiliaries?: string[]\n}\n\nconst DEFAULT_ALLOWED_AUXILIARIES = ['is', 'are', 'was', 'were', 'be', 'been', 'being']\n\nexport const noPassiveVoice: Rule<NoPassiveVoiceOptions> = {\n id: 'no-passive-voice',\n description: 'Flag likely passive-voice constructions using POS tagging patterns',\n defaults: { allowedAuxiliaries: DEFAULT_ALLOWED_AUXILIARIES },\n help: 'Passive voice often hides the actor and adds drag. Prefer naming who did the action unless the actor genuinely does not matter.',\n\n check({ text, sourceMap, options }: RuleInput<NoPassiveVoiceOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const auxiliaries = new Set((options.allowedAuxiliaries?.length ? options.allowedAuxiliaries : DEFAULT_ALLOWED_AUXILIARIES).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('(#Copula|#Auxiliary) #PastTense')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n if (words.length < 2) continue\n if (!auxiliaries.has(words[0]!.toLowerCase())) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-passive-voice',\n severity: 'warn',\n message: `rewrite passive construction \"${occurrence.text}\" with a named actor`,\n range: { start, end: end + 1 },\n help: noPassiveVoice.help,\n })\n }\n\n return dedupeDiagnostics(diagnostics)\n },\n}\n\nfunction dedupeDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {\n const seen = new Set<string>()\n return diagnostics.filter((diagnostic) => {\n const key = `${diagnostic.range.start}:${diagnostic.range.end}`\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoPronounLedClaimsOptions {\n pronouns?: string[]\n verbs?: string[]\n}\n\nconst DEFAULT_PRONOUNS = ['it', 'this', 'that', 'these', 'those']\nconst DEFAULT_VERBS = [\n 'brings',\n 'delivers',\n 'enables',\n 'gives',\n 'helps',\n 'keeps',\n 'lets',\n 'makes',\n 'turns',\n 'unlocks',\n]\n\nexport const noPronounLedClaims: Rule<NoPronounLedClaimsOptions> = {\n id: 'no-pronoun-led-claims',\n description: 'Flag vague claims that start with it, this, that, these, or those',\n defaults: { pronouns: DEFAULT_PRONOUNS, verbs: DEFAULT_VERBS },\n help: 'Pronoun-led claims make the reader infer the subject. Name the feature, product, or user action that creates the outcome.',\n\n check({ text, sourceMap, options }: RuleInput<NoPronounLedClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const pronouns = options.pronouns?.length ? options.pronouns : DEFAULT_PRONOUNS\n const verbs = options.verbs?.length ? options.verbs : DEFAULT_VERBS\n if (!pronouns.length || !verbs.length) return diagnostics\n\n const re = new RegExp(\n `(?:^|[.!?]\\\\s+)(${pronouns.map(escapeRegex).join('|')})\\\\s+(${verbs.map(escapeRegex).join('|')})\\\\b`,\n 'gi'\n )\n let match: RegExpExecArray | null\n while ((match = re.exec(text)) !== null) {\n const phrase = `${match[1]} ${match[2]}`\n const phraseStart = match.index + match[0].indexOf(phrase)\n const start = sourceMap[phraseStart]\n const end = sourceMap[phraseStart + phrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-pronoun-led-claims',\n severity: 'warn',\n message: `name the subject instead of \"${phrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noPronounLedClaims.help,\n })\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoQualifierCreepOptions {\n /** Qualifier words that count toward a stacked-qualifier pattern. */\n qualifiers?: string[]\n /** Maximum qualifiers allowed in a row before the excess are flagged. */\n maxQualifiers?: number\n}\n\nconst DEFAULT_QUALIFIERS = [\n 'very',\n 'really',\n 'quite',\n 'fairly',\n 'somewhat',\n 'rather',\n 'pretty',\n 'basically',\n 'actually',\n 'literally',\n 'essentially',\n 'truly',\n 'definitely',\n 'clearly',\n 'obviously',\n 'absolutely',\n 'completely',\n 'totally',\n 'utterly',\n]\n\nconst DEFAULT_MAX_QUALIFIERS = 1\n\nexport const noQualifierCreep: Rule<NoQualifierCreepOptions> = {\n id: 'no-qualifier-creep',\n description: 'Flag stacked qualifiers or intensifiers before an adjective or adverb',\n defaults: { qualifiers: DEFAULT_QUALIFIERS, maxQualifiers: DEFAULT_MAX_QUALIFIERS },\n help: 'Stacked qualifiers dilute the claim and feel hedged. Keep the strongest qualifier or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoQualifierCreepOptions>): Diagnostic[] {\n const qualifiers = new Set((options.qualifiers?.length ? options.qualifiers : DEFAULT_QUALIFIERS).map(value => value.toLowerCase()))\n const maxQualifiers = options.maxQualifiers ?? DEFAULT_MAX_QUALIFIERS\n if (maxQualifiers < 1) return []\n\n const doc = createDoc(text)\n const diagnostics: Diagnostic[] = []\n const seenRanges: Array<{ start: number; end: number }> = []\n\n const patterns = ['#Adverb #Adverb+ #Adjective', '#Adverb #Adverb+ #Adverb']\n for (const pattern of patterns) {\n const matches = doc.match(pattern)\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n const qualifierWords = words.filter(word => qualifiers.has(word.toLowerCase().replace(/[^a-zA-Z]+$/, '')))\n if (qualifierWords.length <= maxQualifiers) continue\n\n if (seenRanges.some(range => occurrence.start < range.end && occurrence.end > range.start)) continue\n seenRanges.push({ start: occurrence.start, end: occurrence.end })\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-qualifier-creep',\n severity: 'warn',\n message: `remove stacked qualifiers in \"${occurrence.text}\" — use one strong word or a concrete detail`,\n range,\n help: noQualifierCreep.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoRedundantPairsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'first and foremost',\n 'each and every',\n 'various different',\n 'end result',\n 'final outcome',\n 'past history',\n 'future plans',\n 'unexpected surprise',\n 'advance planning',\n]\n\nexport const noRedundantPairs: Rule<NoRedundantPairsOptions> = {\n id: 'no-redundant-pairs',\n description: 'Flag redundant word pairs and padded fixed phrases',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Redundant pairs repeat the same idea twice. Keep the stronger word or replace the phrase with a more specific claim.',\n\n check({ text, sourceMap, options }: RuleInput<NoRedundantPairsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-redundant-pairs',\n severity: 'warn',\n message: `tighten redundant phrase \"${matchedPhrase}\"`,\n range: { start, end: end + 1 },\n help: noRedundantPairs.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoStackedAdjectivesOptions {\n allowedPhrases?: string[]\n}\n\nexport const noStackedAdjectives: Rule<NoStackedAdjectivesOptions> = {\n id: 'no-stacked-adjectives',\n description: 'Flag noun phrases with multiple adjectives before the noun',\n defaults: { allowedPhrases: [] },\n help: 'Stacked adjectives make copy feel generic. Keep the one descriptor that earns its place or replace the phrase with concrete evidence.',\n\n check({ text, sourceMap, options }: RuleInput<NoStackedAdjectivesOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const allowedPhrases = new Set((options.allowedPhrases ?? []).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Adjective #Adjective+ #Noun')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n if (allowedPhrases.has(occurrence.text.toLowerCase())) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-stacked-adjectives',\n severity: 'warn',\n message: `cut stacked adjectives in \"${occurrence.text}\"`,\n range,\n help: noStackedAdjectives.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoSuperlativeClaimsOptions {\n phrases?: string[]\n}\n\nconst DEFAULT_PHRASES = [\n 'best',\n 'leading',\n 'world-class',\n 'top',\n 'premier',\n 'ultimate',\n 'industry-leading',\n 'cutting-edge',\n 'state-of-the-art',\n]\n\nexport const noSuperlativeClaims: Rule<NoSuperlativeClaimsOptions> = {\n id: 'no-superlative-claims',\n description: 'Flag unproven superlatives and market-positioning claims',\n defaults: { phrases: DEFAULT_PHRASES },\n help: 'Superlative claims need proof. Replace broad market-positioning words with evidence, scope, or a concrete product behavior.',\n\n check({ text, sourceMap, options }: RuleInput<NoSuperlativeClaimsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = getLongestPhrasesFirst(\n options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n )\n const claimedRanges: Array<{ start: number; end: number }> = []\n\n for (const phrase of phrases) {\n const re = new RegExp(`\\\\b${escapeRegExp(phrase).replace(/\\\\s+/g, '\\\\s+')}\\\\b`, 'gi')\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedPhrase = match[0]\n const matchStart = match.index\n const matchEnd = match.index + matchedPhrase.length\n if (claimedRanges.some(range => rangesOverlap(range, matchStart, matchEnd))) continue\n\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedPhrase.length - 1]\n if (start === undefined || end === undefined) continue\n claimedRanges.push({ start: matchStart, end: matchEnd })\n\n diagnostics.push({\n ruleId: 'no-superlative-claims',\n severity: 'warn',\n message: `prove or remove superlative claim \"${matchedPhrase.toLowerCase()}\"`,\n range: { start, end: end + 1 },\n help: noSuperlativeClaims.help,\n })\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n\nfunction getLongestPhrasesFirst(phrases: string[]): string[] {\n return [...phrases].sort((left, right) => right.length - left.length)\n}\n\nfunction rangesOverlap(range: { start: number; end: number }, start: number, end: number): boolean {\n return start < range.end && end > range.start\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport nlp from 'compromise'\nimport { getMatchOccurrences, getOccurrenceRange } from './utils.js'\nimport type { JsonOffsetEntry } from './types.js'\n\nexport interface NoVagueComparativesOptions {\n /** Comparative words or phrases that require a baseline. */\n comparatives?: string[]\n /** Whether to require \"than\" in the same sentence to avoid flagging. */\n requireThan?: boolean\n}\n\nconst DEFAULT_OPTIONS: Required<NoVagueComparativesOptions> = {\n comparatives: [\n 'better',\n 'worse',\n 'more',\n 'less',\n 'faster',\n 'slower',\n 'easier',\n 'harder',\n 'stronger',\n 'weaker',\n 'higher',\n 'lower',\n 'bigger',\n 'smaller',\n 'greater',\n ],\n requireThan: true,\n}\n\nexport const noVagueComparatives: Rule<NoVagueComparativesOptions> = {\n id: 'no-vague-comparatives',\n description: 'Flag comparative claims that omit a clear baseline',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Comparative words like \"better\" or \"faster\" only persuade when the reader knows what is being compared. ' +\n 'Add \"than\" and a concrete baseline, or rephrase with a specific metric.',\n\n check({ text, sourceMap, options }: RuleInput<NoVagueComparativesOptions>): Diagnostic[] {\n const comparatives = new Set(\n (options.comparatives ?? DEFAULT_OPTIONS.comparatives).map(word => word.toLowerCase())\n )\n const requireThan = options.requireThan ?? DEFAULT_OPTIONS.requireThan\n\n if (comparatives.size === 0) return []\n\n const doc = nlp(text)\n const diagnostics: Diagnostic[] = []\n const seenRanges: Array<{ start: number; end: number }> = []\n\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, text: true } }) as JsonOffsetEntry[]\n\n for (const sentence of sentences) {\n const sentenceStart = sentence.offset?.start ?? sentence.terms?.[0]?.offset?.start\n const sentenceLength = sentence.offset?.length ?? sentence.terms?.reduce((sum, term) => sum + (term.offset?.length ?? 0), 0)\n if (typeof sentenceStart !== 'number' || typeof sentenceLength !== 'number') continue\n const sentenceEnd = sentenceStart + sentenceLength\n\n const sentenceText = sentence.text ?? text.slice(sentenceStart, sentenceEnd)\n const sentenceDoc = nlp(sentenceText)\n\n // Build patterns that capture both compromise-tagged comparatives and explicit user-configured words.\n const explicitComparatives = Array.from(comparatives).join('|')\n const patterns = [\n '#Comparative',\n `(more|less) #Adjective`,\n `(more|less) #Adverb`,\n `(${explicitComparatives})`,\n ]\n\n for (const pattern of patterns) {\n const matches = sentenceDoc.match(pattern)\n\n for (const occurrence of getMatchOccurrences(sentenceText, matches)) {\n const words = occurrence.text.trim().toLowerCase().split(/\\s+/)\n const matchedComparative = words.find(word => comparatives.has(word.replace(/[^a-zA-Z]+$/, '')))\n if (!matchedComparative) continue\n\n // Occurrence offsets are relative to the sentence; convert to full-text offsets.\n const trailingPunctuation = occurrence.text.match(/[^a-zA-Z\\s]+$/)?.[0].length ?? 0\n const occurrenceStart = sentenceStart + occurrence.start\n const occurrenceEnd = sentenceStart + occurrence.end - trailingPunctuation\n\n if (seenRanges.some(range => occurrenceStart < range.end && occurrenceEnd > range.start)) continue\n seenRanges.push({ start: occurrenceStart, end: occurrenceEnd })\n\n if (requireThan && sentenceDoc.has('than')) continue\n\n const range = getOccurrenceRange(sourceMap, { text: occurrence.text, start: occurrenceStart, end: occurrenceEnd })\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-vague-comparatives',\n severity: 'warn',\n message: `comparative \"${occurrence.text.trim().replace(/[^a-zA-Z\\s]+$/, '')}\" needs a baseline — add \"than\" or a concrete comparison`,\n range,\n help: noVagueComparatives.help,\n })\n }\n }\n }\n\n return diagnostics.sort((left, right) => left.range.start - right.range.start)\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\n\nexport interface NoVagueQuantifiersOptions {\n quantifiers?: string[]\n}\n\nconst DEFAULT_QUANTIFIERS = [\n 'many',\n 'several',\n 'various',\n 'numerous',\n 'multiple',\n 'some',\n 'a number of',\n 'a variety of',\n 'a range of',\n 'a host of',\n 'countless',\n 'tons of',\n 'lots of',\n]\n\nexport const noVagueQuantifiers: Rule<NoVagueQuantifiersOptions> = {\n id: 'no-vague-quantifiers',\n description: 'Flag bare quantifiers without numeric anchors',\n defaults: { quantifiers: DEFAULT_QUANTIFIERS },\n help:\n 'Vague quantifiers make claims hard to evaluate. Replace them with a number, range, or concrete scope when precision matters.',\n\n check({ text, sourceMap, options }: RuleInput<NoVagueQuantifiersOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const quantifiers = options.quantifiers?.length ? options.quantifiers : DEFAULT_QUANTIFIERS\n\n for (const quantifier of quantifiers) {\n const re = new RegExp(\n `\\\\b${escapeRegExp(quantifier).replace(/\\\\s+/g, '\\\\s+')}\\\\b`,\n 'gi'\n )\n let match: RegExpExecArray | null\n\n while ((match = re.exec(text)) !== null) {\n const matchedQuantifier = match[0]\n const start = sourceMap[match.index]\n const end = sourceMap[match.index + matchedQuantifier.length - 1]\n if (start === undefined || end === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-vague-quantifiers',\n severity: 'warn',\n message: `replace vague quantifier \"${matchedQuantifier}\" with a number or concrete scope`,\n range: { start, end: end + 1 },\n help: noVagueQuantifiers.help,\n })\n }\n }\n\n return diagnostics\n },\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc, getMatchOccurrences, getOccurrenceRange } from './utils.js'\n\nexport interface NoWeakModalsOptions {\n modals?: string[]\n verbs?: string[]\n}\n\nconst DEFAULT_MODALS = ['can', 'could', 'may', 'might']\nconst DEFAULT_VERBS = [\n 'boost',\n 'drive',\n 'enable',\n 'help',\n 'improve',\n 'increase',\n 'make',\n 'reduce',\n 'support',\n 'transform',\n 'unlock',\n]\n\nexport const noWeakModals: Rule<NoWeakModalsOptions> = {\n id: 'no-weak-modals',\n description: 'Flag hedged modal claims like \"can help\" and \"might improve\"',\n defaults: { modals: DEFAULT_MODALS, verbs: DEFAULT_VERBS },\n help: 'Hedged modal claims sound tentative. Replace them with the outcome, capability, or proof you can stand behind.',\n\n check({ text, sourceMap, options }: RuleInput<NoWeakModalsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const modals = new Set((options.modals?.length ? options.modals : DEFAULT_MODALS).map(value => value.toLowerCase()))\n const verbs = new Set((options.verbs?.length ? options.verbs : DEFAULT_VERBS).map(value => value.toLowerCase()))\n const doc = createDoc(text)\n const matches = doc.match('#Modal #Adverb? #Verb')\n\n for (const occurrence of getMatchOccurrences(text, matches)) {\n const words = occurrence.text.trim().split(/\\s+/)\n const modal = words[0]?.toLowerCase()\n const verb = words[words.length - 1]?.toLowerCase()\n if (!modal || !verb || !modals.has(modal) || !verbs.has(verb)) continue\n\n const range = getOccurrenceRange(sourceMap, occurrence)\n if (!range) continue\n\n diagnostics.push({\n ruleId: 'no-weak-modals',\n severity: 'warn',\n message: `replace \"${occurrence.text.toLowerCase()}\" with a direct claim`,\n range,\n help: noWeakModals.help,\n })\n }\n\n return diagnostics\n },\n}\n","import type { Diagnostic, Rule, RuleInput } from '@faircopy/core'\nimport { createDoc } from './utils.js'\n\nexport interface SentenceComplexityOptions {\n /** Maximum number of words allowed in a single sentence. */\n maxWordCount?: number\n /** Maximum number of finite-verb clauses allowed in a single sentence. */\n maxClauseCount?: number\n}\n\nconst DEFAULT_OPTIONS: Required<SentenceComplexityOptions> = {\n maxWordCount: 25,\n maxClauseCount: 3,\n}\n\nexport const sentenceComplexity: Rule<SentenceComplexityOptions> = {\n id: 'sentence-complexity',\n description: 'Flag sentences that exceed a word or clause threshold',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Long, clause-heavy sentences are harder to read. Split them into shorter sentences that each make one point.',\n\n check({ text, sourceMap, options }: RuleInput<SentenceComplexityOptions>): Diagnostic[] {\n const maxWordCount = options.maxWordCount ?? DEFAULT_OPTIONS.maxWordCount\n const maxClauseCount = options.maxClauseCount ?? DEFAULT_OPTIONS.maxClauseCount\n\n const doc = createDoc(text)\n const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } })\n\n const diagnostics: Diagnostic[] = []\n\n for (const sentence of sentences) {\n if (!sentence.offset || !sentence.terms) continue\n\n const terms = sentence.terms.filter(term => /[a-zA-Z0-9]/.test(term.text ?? ''))\n const wordCount = terms.length\n\n const clauseCount = terms.filter((term) => {\n const tags = term.tags ?? []\n return tags.includes('Verb') && !tags.includes('Gerund') && !tags.includes('Infinitive') && !tags.includes('Particle')\n }).length\n\n if (wordCount <= maxWordCount && clauseCount <= maxClauseCount) continue\n\n const start = sentence.offset.start ?? 0\n const length = sentence.offset.length ?? 0\n const end = start + length\n\n const sourceStart = sourceMap[start]\n const sourceEnd = sourceMap[end - 1]\n if (sourceStart === undefined || sourceEnd === undefined) continue\n\n const reasons: string[] = []\n if (wordCount > maxWordCount) reasons.push(`${wordCount} words (max ${maxWordCount})`)\n if (clauseCount > maxClauseCount) reasons.push(`${clauseCount} clauses (max ${maxClauseCount})`)\n\n diagnostics.push({\n ruleId: 'sentence-complexity',\n severity: 'warn',\n message: `sentence is too complex: ${reasons.join(', ')} — consider splitting it`,\n range: { start: sourceStart, end: sourceEnd + 1 },\n help: sentenceComplexity.help,\n suggest: {\n description: 'Split this sentence into shorter sentences, one idea each.',\n edits: [],\n },\n })\n }\n\n return diagnostics\n },\n}\n","import type { Rule } from '@faircopy/core'\nimport { noAbsoluteIntensifiers } from './no-absolute-intensifiers.js'\nimport { noAdverbOveruse } from './no-adverb-overuse.js'\nimport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nimport { noComplexReadability } from './no-complex-readability.js'\nimport { noExpletiveOpeners } from './no-expletive-openers.js'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nimport { noFuturePromises } from './no-future-promises.js'\nimport { noHedgeWords } from './no-hedge-words.js'\nimport { noJargon } from './no-jargon.js'\nimport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nimport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noOverlyComplexSentences } from './no-overly-complex-sentences.js'\nimport { noOverusedAdverbs } from './no-overused-adverbs.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\nimport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nimport { noQualifierCreep } from './no-qualifier-creep.js'\nimport { noRedundantPairs } from './no-redundant-pairs.js'\nimport { noStackedAdjectives } from './no-stacked-adjectives.js'\nimport { noSuperlativeClaims } from './no-superlative-claims.js'\nimport { noVagueComparatives } from './no-vague-comparatives.js'\nimport { noVagueQuantifiers } from './no-vague-quantifiers.js'\nimport { noWeakModals } from './no-weak-modals.js'\nimport { sentenceComplexity } from './sentence-complexity.js'\n\nexport { noAbsoluteIntensifiers } from './no-absolute-intensifiers.js'\nexport { noAdverbOveruse } from './no-adverb-overuse.js'\nexport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nexport { noComplexReadability } from './no-complex-readability.js'\nexport { noExpletiveOpeners } from './no-expletive-openers.js'\nexport { noFilterWords } from './no-filter-words.js'\nexport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nexport { noFuturePromises } from './no-future-promises.js'\nexport { noHedgeWords } from './no-hedge-words.js'\nexport { noJargon } from './no-jargon.js'\nexport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nexport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noOverlyComplexSentences } from './no-overly-complex-sentences.js'\nexport { noOverusedAdverbs } from './no-overused-adverbs.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nexport { noQualifierCreep } from './no-qualifier-creep.js'\nexport { noRedundantPairs } from './no-redundant-pairs.js'\nexport { noStackedAdjectives } from './no-stacked-adjectives.js'\nexport { noSuperlativeClaims } from './no-superlative-claims.js'\nexport { noVagueComparatives } from './no-vague-comparatives.js'\nexport { noVagueQuantifiers } from './no-vague-quantifiers.js'\nexport { noWeakModals } from './no-weak-modals.js'\nexport { sentenceComplexity } from './sentence-complexity.js'\nexport type { NoAbsoluteIntensifiersOptions } from './no-absolute-intensifiers.js'\nexport type { NoAdverbOveruseOptions } from './no-adverb-overuse.js'\nexport type { NoBuzzwordStacksOptions } from './no-buzzword-stacks.js'\nexport type { NoComplexReadabilityOptions } from './no-complex-readability.js'\nexport type { NoExpletiveOpenersOptions } from './no-expletive-openers.js'\nexport type { NoFilterWordsOptions } from './no-filter-words.js'\nexport type { NoEmptyTransformationClaimsOptions } from './no-empty-transformation-claims.js'\nexport type { NoFuturePromisesOptions } from './no-future-promises.js'\nexport type { NoHedgeWordsOptions } from './no-hedge-words.js'\nexport type { NoJargonOptions } from './no-jargon.js'\nexport type { NoMeaninglessModifiersOptions } from './no-meaningless-modifiers.js'\nexport type { NoNonInclusiveLanguageOptions } from './no-non-inclusive-language.js'\nexport type { NoNominalizedPhrasesOptions } from './no-nominalized-phrases.js'\nexport type { NoOverlyComplexSentencesOptions } from './no-overly-complex-sentences.js'\nexport type { NoOverusedAdverbsOptions } from './no-overused-adverbs.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\nexport type { NoPronounLedClaimsOptions } from './no-pronoun-led-claims.js'\nexport type { NoQualifierCreepOptions } from './no-qualifier-creep.js'\nexport type { NoRedundantPairsOptions } from './no-redundant-pairs.js'\nexport type { NoStackedAdjectivesOptions } from './no-stacked-adjectives.js'\nexport type { NoSuperlativeClaimsOptions } from './no-superlative-claims.js'\nexport type { NoVagueComparativesOptions } from './no-vague-comparatives.js'\nexport type { NoVagueQuantifiersOptions } from './no-vague-quantifiers.js'\nexport type { NoWeakModalsOptions } from './no-weak-modals.js'\nexport type { SentenceComplexityOptions } from './sentence-complexity.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-absolute-intensifiers', noAbsoluteIntensifiers as Rule],\n ['no-adverb-overuse', noAdverbOveruse as Rule],\n ['no-buzzword-stacks', noBuzzwordStacks as Rule],\n ['no-complex-readability', noComplexReadability as Rule],\n ['no-empty-transformation-claims', noEmptyTransformationClaims as Rule],\n ['no-expletive-openers', noExpletiveOpeners as Rule],\n ['no-filter-words', noFilterWords as Rule],\n ['no-future-promises', noFuturePromises as Rule],\n ['no-hedge-words', noHedgeWords as Rule],\n ['no-jargon', noJargon as Rule],\n ['no-meaningless-modifiers', noMeaninglessModifiers as Rule],\n ['no-non-inclusive-language-nlp', noNonInclusiveLanguage as Rule],\n ['no-nominalized-phrases', noNominalizedPhrases as Rule],\n ['no-overly-complex-sentences', noOverlyComplexSentences as Rule],\n ['no-overused-adverbs', noOverusedAdverbs as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n ['no-pronoun-led-claims', noPronounLedClaims as Rule],\n ['no-qualifier-creep', noQualifierCreep as Rule],\n ['no-redundant-pairs', noRedundantPairs as Rule],\n ['no-stacked-adjectives', noStackedAdjectives as Rule],\n ['no-superlative-claims', noSuperlativeClaims as Rule],\n ['no-vague-comparatives', noVagueComparatives as Rule],\n ['no-vague-quantifiers', noVagueQuantifiers as Rule],\n ['no-weak-modals', noWeakModals as Rule],\n ['sentence-complexity', sentenceComplexity as Rule],\n])\n"],"mappings":"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","names":["DEFAULT_PHRASES","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","DEFAULT_TERMS","escapeRegex","DEFAULT_ALLOWED_ADVERBS","escapeRegex","range","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","nlp","DEFAULT_OPTIONS","nlp","range","escapeRegExp","DEFAULT_VERBS","DEFAULT_OPTIONS"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faircopy/rules-nlp",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Optional NLP-powered ruleset for faircopy using compromise",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,7 +19,7 @@
19
19
  "prepublishOnly": "pnpm run build"
20
20
  },
21
21
  "dependencies": {
22
- "@faircopy/core": "1.16.0",
22
+ "@faircopy/core": "1.17.0",
23
23
  "compromise": "^14.15.0"
24
24
  },
25
25
  "devDependencies": {