@faircopy/rules-nlp 1.20.0 → 1.21.1
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 +43 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +67 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ rules: {
|
|
|
34
34
|
'no-meaningless-modifiers': 'warn',
|
|
35
35
|
'no-superlative-claims': 'warn',
|
|
36
36
|
'no-llm-speak': 'warn',
|
|
37
|
+
'no-n-things': 'warn',
|
|
37
38
|
'no-weak-verbs': 'warn',
|
|
38
39
|
}
|
|
39
40
|
```
|
|
@@ -69,6 +70,7 @@ Package-qualified IDs like `@faircopy/rules-nlp/no-passive-voice` still work and
|
|
|
69
70
|
| `no-superlative-claims` | Flag unproven superlatives like `best` and `world-class` |
|
|
70
71
|
| `no-non-inclusive-language-nlp` | Flag non-inclusive terms and suggest neutral alternatives |
|
|
71
72
|
| `no-llm-speak` | Flag LLM clichés like `delve into` and `robust` and suggest concrete alternatives |
|
|
73
|
+
| `no-n-things` | Flag the counted placeholder frame like `Three things.` and `5 things to know` |
|
|
72
74
|
| `no-weak-verbs` | Flag vague action verbs like `make` and `perform` and suggest stronger alternatives |
|
|
73
75
|
| `sentence-complexity` | Flag sentences that exceed a word or clause threshold |
|
|
74
76
|
|
|
@@ -425,3 +427,44 @@ The process for designing the user experience is slow.
|
|
|
425
427
|
```
|
|
426
428
|
|
|
427
429
|
The rule uses `compromise` for part-of-speech tagging and skips proper nouns such as place names.
|
|
430
|
+
|
|
431
|
+
### `no-n-things`
|
|
432
|
+
|
|
433
|
+
A count plus a placeholder noun — `Three things.`, `5 things to know before launch` — borrows its number for rhythm instead of meaning. The frame is a documented AI-writing tell: the count is chosen because enumerated lists feel punchy, not because the content demanded that many items. Name the items in the heading, or drop the count and state the claim.
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
rules: {
|
|
437
|
+
'no-n-things': 'warn',
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Options:
|
|
442
|
+
|
|
443
|
+
| Option | Type | Default | Description |
|
|
444
|
+
|---|---|---|---|
|
|
445
|
+
| `minCount` | `number` | `2` | Minimum cardinal count before flagging; keeps `one thing` idioms alone |
|
|
446
|
+
| `nouns` | `string[]` | `['thing', 'things']` | Placeholder nouns that complete the `N ___` frame |
|
|
447
|
+
| `allowedPhrases` | `string[]` | `[]` | Phrases to allow and skip |
|
|
448
|
+
|
|
449
|
+
Example with custom settings:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
'no-n-things': ['warn', {
|
|
453
|
+
minCount: 3,
|
|
454
|
+
nouns: ['thing', 'things', 'tips', 'takeaways'],
|
|
455
|
+
}]
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Flagged example:
|
|
459
|
+
|
|
460
|
+
```text
|
|
461
|
+
Three things you should know about memory.
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Suggested fix:
|
|
465
|
+
|
|
466
|
+
```text
|
|
467
|
+
How Ditto remembers your conversations.
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The rule uses `compromise` for part-of-speech tagging. Only cardinal counts trigger it — idioms like `First things first`, `All things considered`, and `a few things` are left alone, as are concrete counted nouns like `three tabs`.
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,21 @@ interface NoNounStringsOptions {
|
|
|
103
103
|
}
|
|
104
104
|
declare const noNounStrings: Rule<NoNounStringsOptions>;
|
|
105
105
|
|
|
106
|
+
interface NoNThingsOptions {
|
|
107
|
+
/** Minimum cardinal count before the frame is flagged. Default 2, which leaves "one thing" idioms alone. */
|
|
108
|
+
minCount?: number;
|
|
109
|
+
/** Placeholder nouns that complete the "N ___" frame. */
|
|
110
|
+
nouns?: string[];
|
|
111
|
+
/** Phrases to ignore entirely. */
|
|
112
|
+
allowedPhrases?: string[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* "Three things." / "5 things to know" borrows its number for rhythm, not
|
|
116
|
+
* content — the count-noun cousin of the rule of three, and a widely
|
|
117
|
+
* documented AI-writing tell.
|
|
118
|
+
*/
|
|
119
|
+
declare const noNThings: Rule<NoNThingsOptions>;
|
|
120
|
+
|
|
106
121
|
interface NoOverlyComplexSentencesOptions {
|
|
107
122
|
/** Maximum total conjunctions allowed in a single sentence. */
|
|
108
123
|
maxConjunctions?: number;
|
|
@@ -207,4 +222,4 @@ declare const sentenceComplexity: Rule<SentenceComplexityOptions>;
|
|
|
207
222
|
/** All NLP rules keyed by their rule ID. */
|
|
208
223
|
declare const ruleRegistry: Map<string, Rule>;
|
|
209
224
|
|
|
210
|
-
export { type NoAbsoluteIntensifiersOptions, type NoAdverbOveruseOptions, type NoBuzzwordStacksOptions, type NoComplexReadabilityOptions, type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoFuturePromisesOptions, type NoHedgeWordsOptions, type NoJargonOptions, type NoLlmSpeakOptions, type NoMeaninglessModifiersOptions, type NoNominalizedPhrasesOptions, type NoNonInclusiveLanguageOptions, type NoNounStringsOptions, type NoOverlyComplexSentencesOptions, type NoOverusedAdverbsOptions, type NoPassiveVoiceOptions, type NoPronounLedClaimsOptions, type NoQualifierCreepOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoSuperlativeClaimsOptions, type NoVagueComparativesOptions, type NoVagueQuantifiersOptions, type NoWeakModalsOptions, type NoWeakVerbsOptions, type SentenceComplexityOptions, noAbsoluteIntensifiers, noAdverbOveruse, noBuzzwordStacks, noComplexReadability, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noFuturePromises, noHedgeWords, noJargon, noLlmSpeak, noMeaninglessModifiers, noNominalizedPhrases, noNonInclusiveLanguage, noNounStrings, noOverlyComplexSentences, noOverusedAdverbs, noPassiveVoice, noPronounLedClaims, noQualifierCreep, noRedundantPairs, noStackedAdjectives, noSuperlativeClaims, noVagueComparatives, noVagueQuantifiers, noWeakModals, noWeakVerbs, ruleRegistry, sentenceComplexity };
|
|
225
|
+
export { type NoAbsoluteIntensifiersOptions, type NoAdverbOveruseOptions, type NoBuzzwordStacksOptions, type NoComplexReadabilityOptions, type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoFuturePromisesOptions, type NoHedgeWordsOptions, type NoJargonOptions, type NoLlmSpeakOptions, type NoMeaninglessModifiersOptions, type NoNThingsOptions, type NoNominalizedPhrasesOptions, type NoNonInclusiveLanguageOptions, type NoNounStringsOptions, type NoOverlyComplexSentencesOptions, type NoOverusedAdverbsOptions, type NoPassiveVoiceOptions, type NoPronounLedClaimsOptions, type NoQualifierCreepOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoSuperlativeClaimsOptions, type NoVagueComparativesOptions, type NoVagueQuantifiersOptions, type NoWeakModalsOptions, type NoWeakVerbsOptions, type SentenceComplexityOptions, noAbsoluteIntensifiers, noAdverbOveruse, noBuzzwordStacks, noComplexReadability, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noFuturePromises, noHedgeWords, noJargon, noLlmSpeak, noMeaninglessModifiers, noNThings, noNominalizedPhrases, noNonInclusiveLanguage, noNounStrings, noOverlyComplexSentences, noOverusedAdverbs, noPassiveVoice, noPronounLedClaims, noQualifierCreep, noRedundantPairs, noStackedAdjectives, noSuperlativeClaims, noVagueComparatives, noVagueQuantifiers, noWeakModals, noWeakVerbs, ruleRegistry, sentenceComplexity };
|
package/dist/index.js
CHANGED
|
@@ -905,6 +905,63 @@ var noNounStrings = {
|
|
|
905
905
|
}
|
|
906
906
|
};
|
|
907
907
|
|
|
908
|
+
// src/no-n-things.ts
|
|
909
|
+
var DEFAULT_OPTIONS3 = {
|
|
910
|
+
minCount: 2,
|
|
911
|
+
nouns: ["thing", "things"],
|
|
912
|
+
allowedPhrases: []
|
|
913
|
+
};
|
|
914
|
+
function normalizePhrase2(phrase) {
|
|
915
|
+
return phrase.toLowerCase().replace(/\s+/g, " ").trim();
|
|
916
|
+
}
|
|
917
|
+
function escapeMatchWord(word) {
|
|
918
|
+
return word.replace(/[^\w-]/g, "");
|
|
919
|
+
}
|
|
920
|
+
var noNThings = {
|
|
921
|
+
id: "no-n-things",
|
|
922
|
+
description: 'Flag the enumerated "N things" frame common in AI-generated copy',
|
|
923
|
+
defaults: { ...DEFAULT_OPTIONS3 },
|
|
924
|
+
help: 'A count plus a placeholder noun ("three things", "5 things to know") spends its number on rhythm instead of meaning. Name the items in the heading, or drop the count and state the claim.',
|
|
925
|
+
check({ text, sourceMap, options }) {
|
|
926
|
+
const diagnostics = [];
|
|
927
|
+
const minCount = options.minCount ?? DEFAULT_OPTIONS3.minCount;
|
|
928
|
+
const allowed = new Set((options.allowedPhrases ?? []).map(normalizePhrase2));
|
|
929
|
+
const nouns = (options.nouns?.length ? options.nouns : DEFAULT_OPTIONS3.nouns).map(escapeMatchWord).filter(Boolean);
|
|
930
|
+
if (!nouns.length || text.length === 0) return [];
|
|
931
|
+
const doc = createDoc(text);
|
|
932
|
+
const matches = doc.match(`#Value+ (${nouns.join("|")})`).json({ offset: true, text: true, terms: { offset: true, text: true, tags: true } });
|
|
933
|
+
for (const entry of matches) {
|
|
934
|
+
const terms = entry.terms ?? [];
|
|
935
|
+
if (terms.length < 2) continue;
|
|
936
|
+
const firstOffset = terms[0].offset;
|
|
937
|
+
const lastOffset = terms[terms.length - 1].offset;
|
|
938
|
+
if (typeof firstOffset?.start !== "number" || typeof firstOffset.length !== "number" || typeof lastOffset?.start !== "number" || typeof lastOffset.length !== "number") {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const start = firstOffset.start;
|
|
942
|
+
const end = lastOffset.start + lastOffset.length;
|
|
943
|
+
if (end <= start) continue;
|
|
944
|
+
const matchedText = text.slice(start, end);
|
|
945
|
+
if (allowed.has(normalizePhrase2(matchedText))) continue;
|
|
946
|
+
const countOut = doc.match(matchedText).values?.().toNumber().out("array") ?? [];
|
|
947
|
+
const countText = countOut[0];
|
|
948
|
+
const count = typeof countText === "string" ? Number(countText) : Number.NaN;
|
|
949
|
+
if (!Number.isFinite(count) || count < minCount) continue;
|
|
950
|
+
const sourceStart = sourceMap[start];
|
|
951
|
+
const sourceEnd = sourceMap[end - 1];
|
|
952
|
+
if (sourceStart === void 0 || sourceEnd === void 0) continue;
|
|
953
|
+
diagnostics.push({
|
|
954
|
+
ruleId: "no-n-things",
|
|
955
|
+
severity: "warn",
|
|
956
|
+
message: `rewrite "${matchedText}" with the concrete subject \u2014 a counted placeholder is an AI-writing tell`,
|
|
957
|
+
range: { start: sourceStart, end: sourceEnd + 1 },
|
|
958
|
+
help: noNThings.help
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
return diagnostics.sort((left, right) => left.range.start - right.range.start);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
908
965
|
// src/no-overly-complex-sentences.ts
|
|
909
966
|
var DEFAULT_MAX_CONJUNCTIONS = 4;
|
|
910
967
|
var DEFAULT_MAX_COORDINATING = 3;
|
|
@@ -1370,7 +1427,7 @@ function escapeRegExp7(value) {
|
|
|
1370
1427
|
|
|
1371
1428
|
// src/no-vague-comparatives.ts
|
|
1372
1429
|
import nlp2 from "compromise";
|
|
1373
|
-
var
|
|
1430
|
+
var DEFAULT_OPTIONS4 = {
|
|
1374
1431
|
comparatives: [
|
|
1375
1432
|
"better",
|
|
1376
1433
|
"worse",
|
|
@@ -1393,13 +1450,13 @@ var DEFAULT_OPTIONS3 = {
|
|
|
1393
1450
|
var noVagueComparatives = {
|
|
1394
1451
|
id: "no-vague-comparatives",
|
|
1395
1452
|
description: "Flag comparative claims that omit a clear baseline",
|
|
1396
|
-
defaults: { ...
|
|
1453
|
+
defaults: { ...DEFAULT_OPTIONS4 },
|
|
1397
1454
|
help: 'Comparative words like "better" or "faster" only persuade when the reader knows what is being compared. Add "than" and a concrete baseline, or rephrase with a specific metric.',
|
|
1398
1455
|
check({ text, sourceMap, options }) {
|
|
1399
1456
|
const comparatives = new Set(
|
|
1400
|
-
(options.comparatives ??
|
|
1457
|
+
(options.comparatives ?? DEFAULT_OPTIONS4.comparatives).map((word) => word.toLowerCase())
|
|
1401
1458
|
);
|
|
1402
|
-
const requireThan = options.requireThan ??
|
|
1459
|
+
const requireThan = options.requireThan ?? DEFAULT_OPTIONS4.requireThan;
|
|
1403
1460
|
if (comparatives.size === 0) return [];
|
|
1404
1461
|
const doc = nlp2(text);
|
|
1405
1462
|
const diagnostics = [];
|
|
@@ -1610,18 +1667,18 @@ var noWeakVerbs = {
|
|
|
1610
1667
|
};
|
|
1611
1668
|
|
|
1612
1669
|
// src/sentence-complexity.ts
|
|
1613
|
-
var
|
|
1670
|
+
var DEFAULT_OPTIONS5 = {
|
|
1614
1671
|
maxWordCount: 25,
|
|
1615
1672
|
maxClauseCount: 3
|
|
1616
1673
|
};
|
|
1617
1674
|
var sentenceComplexity = {
|
|
1618
1675
|
id: "sentence-complexity",
|
|
1619
1676
|
description: "Flag sentences that exceed a word or clause threshold",
|
|
1620
|
-
defaults: { ...
|
|
1677
|
+
defaults: { ...DEFAULT_OPTIONS5 },
|
|
1621
1678
|
help: "Long, clause-heavy sentences are harder to read. Split them into shorter sentences that each make one point.",
|
|
1622
1679
|
check({ text, sourceMap, options }) {
|
|
1623
|
-
const maxWordCount = options.maxWordCount ??
|
|
1624
|
-
const maxClauseCount = options.maxClauseCount ??
|
|
1680
|
+
const maxWordCount = options.maxWordCount ?? DEFAULT_OPTIONS5.maxWordCount;
|
|
1681
|
+
const maxClauseCount = options.maxClauseCount ?? DEFAULT_OPTIONS5.maxClauseCount;
|
|
1625
1682
|
const doc = createDoc(text);
|
|
1626
1683
|
const sentences = doc.sentences().json({ offset: true, terms: { offset: true, tags: true } });
|
|
1627
1684
|
const diagnostics = [];
|
|
@@ -1676,6 +1733,7 @@ var ruleRegistry = /* @__PURE__ */ new Map([
|
|
|
1676
1733
|
["no-non-inclusive-language-nlp", noNonInclusiveLanguage],
|
|
1677
1734
|
["no-nominalized-phrases", noNominalizedPhrases],
|
|
1678
1735
|
["no-noun-strings", noNounStrings],
|
|
1736
|
+
["no-n-things", noNThings],
|
|
1679
1737
|
["no-overly-complex-sentences", noOverlyComplexSentences],
|
|
1680
1738
|
["no-overused-adverbs", noOverusedAdverbs],
|
|
1681
1739
|
["no-passive-voice", noPassiveVoice],
|
|
@@ -1703,6 +1761,7 @@ export {
|
|
|
1703
1761
|
noJargon,
|
|
1704
1762
|
noLlmSpeak,
|
|
1705
1763
|
noMeaninglessModifiers,
|
|
1764
|
+
noNThings,
|
|
1706
1765
|
noNominalizedPhrases,
|
|
1707
1766
|
noNonInclusiveLanguage,
|
|
1708
1767
|
noNounStrings,
|
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-llm-speak.ts","../src/no-meaningless-modifiers.ts","../src/no-non-inclusive-language.ts","../src/no-nominalized-phrases.ts","../src/no-noun-strings.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/no-weak-verbs.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'\nimport { createDoc } from './utils.js'\n\nexport interface LlmSpeakPhrase {\n phrase: string\n alternatives: string[]\n}\n\nexport interface NoLlmSpeakOptions {\n phrases?: LlmSpeakPhrase[]\n allowedPhrases?: string[]\n}\n\nconst DEFAULT_PHRASES: LlmSpeakPhrase[] = [\n { phrase: 'delve into', alternatives: ['explore', 'examine'] },\n { phrase: \"it's important to note\", alternatives: ['note', 'remember'] },\n { phrase: \"it's worth noting\", alternatives: ['note'] },\n { phrase: 'in the ever-evolving landscape', alternatives: ['today', 'now'] },\n { phrase: \"in today's digital age\", alternatives: ['today'] },\n { phrase: 'a myriad of', alternatives: ['many'] },\n { phrase: 'a wealth of', alternatives: ['rich', 'deep'] },\n { phrase: 'in the realm of', alternatives: ['in'] },\n { phrase: 'at the forefront of', alternatives: ['leading'] },\n { phrase: 'navigate the complexities of', alternatives: ['handle'] },\n { phrase: 'sheds light on', alternatives: ['explains', 'clarifies'] },\n { phrase: 'underscores', alternatives: ['shows', 'emphasizes'] },\n { phrase: 'furthermore', alternatives: ['also'] },\n { phrase: 'moreover', alternatives: ['also'] },\n { phrase: 'in conclusion', alternatives: ['so', 'finally'] },\n { phrase: 'it is interesting to note', alternatives: ['note'] },\n { phrase: 'it should be noted', alternatives: ['note'] },\n { phrase: 'it is worth noting', alternatives: ['note'] },\n { phrase: 'as mentioned earlier', alternatives: [] },\n { phrase: 'as noted above', alternatives: [] },\n { phrase: 'going forward', alternatives: ['next', 'later'] },\n { phrase: 'moving forward', alternatives: ['next'] },\n { phrase: 'tapestry', alternatives: ['pattern', 'mix'] },\n { phrase: 'multifaceted', alternatives: ['complex', 'many-sided'] },\n { phrase: 'intricate', alternatives: ['complex', 'detailed'] },\n { phrase: 'meticulous', alternatives: ['careful', 'precise'] },\n { phrase: 'pivotal', alternatives: ['key', 'critical'] },\n { phrase: 'crucial', alternatives: ['critical', 'key'] },\n { phrase: 'robust', alternatives: ['strong', 'resilient'] },\n]\n\ninterface JsonTerm {\n text?: string\n normal?: 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 getOccurrenceFromMatch(\n entry: JsonMatch,\n originalText: string\n): { text: string; start: number; end: number } | 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 noLlmSpeak: Rule<NoLlmSpeakOptions> = {\n id: 'no-llm-speak',\n description: 'Flag phrases commonly overused by LLMs and suggest concrete alternatives',\n defaults: { phrases: DEFAULT_PHRASES, allowedPhrases: [] },\n help: 'LLM-speak uses padded, abstract phrases that sound authoritative without saying much. Replace them with direct verbs, concrete nouns, or delete them entirely.',\n\n check({ text, sourceMap, options }: RuleInput<NoLlmSpeakOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const allowed = new Set((options.allowedPhrases ?? []).map(phrase => phrase.toLowerCase()))\n const doc = createDoc(text)\n\n for (const { phrase, alternatives } of phrases) {\n if (allowed.has(phrase.toLowerCase())) continue\n\n const matches = doc.match(phrase)\n const json = matches.json({\n offset: true,\n text: true,\n terms: { offset: true, text: true },\n }) as JsonMatch[]\n\n for (const entry of json) {\n const occurrence = getOccurrenceFromMatch(entry, text)\n if (!occurrence) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n const suggestion = alternatives.length\n ? `try \"${alternatives.join('\", \"')}\"`\n : 'remove this filler phrase'\n\n diagnostics.push({\n ruleId: 'no-llm-speak',\n severity: 'warn',\n message: `replace LLM-speak phrase \"${occurrence.text}\" — ${suggestion}`,\n range: { start, end: end + 1 },\n help: noLlmSpeak.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 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, getOccurrenceRange } from './utils.js'\nimport type { JsonOffsetTerm } from './types.js'\n\nexport interface NoNounStringsOptions {\n /** Maximum consecutive nouns allowed before flagging. */\n maxConsecutiveNouns?: number\n /** Phrases to allow and skip. */\n allowedPhrases?: string[]\n}\n\nconst DEFAULT_OPTIONS: Required<NoNounStringsOptions> = {\n maxConsecutiveNouns: 3,\n allowedPhrases: [],\n}\n\ninterface TermEntry {\n text: string\n start: number\n end: number\n tags: string[]\n}\n\nfunction isCountableNoun(entry: TermEntry): boolean {\n return entry.tags.includes('Noun') && !entry.tags.includes('ProperNoun')\n}\n\nfunction normalizePhrase(phrase: string): string {\n return phrase.toLowerCase().replace(/\\s+/g, ' ').trim()\n}\n\nexport const noNounStrings: Rule<NoNounStringsOptions> = {\n id: 'no-noun-strings',\n description: 'Flag dense strings of consecutive nouns',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Three or more nouns in a row create dense, hard-to-read compounds. Rewrite them with prepositions or verbs.',\n\n check({ text, sourceMap, options }: RuleInput<NoNounStringsOptions>): Diagnostic[] {\n const maxConsecutiveNouns = options.maxConsecutiveNouns ?? DEFAULT_OPTIONS.maxConsecutiveNouns\n const allowed = new Set((options.allowedPhrases ?? []).map(normalizePhrase))\n\n if (maxConsecutiveNouns <= 1 || text.length === 0) return []\n\n const doc = createDoc(text)\n const rawTerms = doc.terms().json({\n offset: true,\n terms: { offset: true, text: true, tags: true },\n }) as Array<{\n text?: string\n offset?: { start?: number; length?: number }\n terms?: JsonOffsetTerm[]\n }>\n\n const terms: TermEntry[] = []\n for (const entry of rawTerms) {\n const inner = entry.terms?.[0]\n if (!inner) continue\n const offset = inner.offset ?? entry.offset\n const start = offset?.start\n const length = offset?.length\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) continue\n\n terms.push({\n text: inner.text ?? entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n tags: inner.tags ?? [],\n })\n }\n\n const diagnostics: Diagnostic[] = []\n let runStart = -1\n let runEnd = -1\n const runWords: string[] = []\n\n function flushRun(): void {\n if (runWords.length < maxConsecutiveNouns) return\n\n const phrase = runWords.join(' ')\n if (allowed.has(normalizePhrase(phrase))) return\n\n const range = getOccurrenceRange(sourceMap, { start: runStart, end: runEnd, text: phrase })\n if (!range) return\n\n diagnostics.push({\n ruleId: 'no-noun-strings',\n severity: 'warn',\n message: `noun string \"${phrase}\" is dense — rewrite with verbs or prepositions`,\n range,\n help: noNounStrings.help,\n })\n }\n\n for (const term of terms) {\n if (isCountableNoun(term)) {\n if (runWords.length === 0) {\n runStart = term.start\n }\n runEnd = term.end\n runWords.push(term.text)\n } else {\n flushRun()\n runWords.length = 0\n runStart = -1\n runEnd = -1\n }\n }\n\n flushRun()\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 } 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 WeakVerb {\n verb: string\n alternatives: string[]\n}\n\nexport interface NoWeakVerbsOptions {\n /** Weak verbs to flag, each with suggested stronger alternatives. */\n verbs?: WeakVerb[]\n /** Verbs or phrases to allow and skip. */\n allowList?: string[]\n}\n\nconst DEFAULT_VERBS: WeakVerb[] = [\n { verb: 'make', alternatives: ['create', 'build', 'produce'] },\n { verb: 'perform', alternatives: ['execute', 'run', 'complete'] },\n { verb: 'conduct', alternatives: ['lead', 'run', 'manage'] },\n { verb: 'carry out', alternatives: ['execute', 'complete', 'implement'] },\n]\n\ninterface JsonTerm {\n text?: string\n offset?: { start?: number; length?: number }\n}\n\ninterface JsonVerb {\n text?: string\n terms?: JsonTerm[]\n offset?: { start?: number; length?: number }\n verb?: {\n infinitive?: string\n }\n}\n\nfunction getOccurrenceFromVerb(entry: JsonVerb, originalText: string): { text: string; start: number; end: number } | 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 noWeakVerbs: Rule<NoWeakVerbsOptions> = {\n id: 'no-weak-verbs',\n description: 'Flag vague action verbs and suggest stronger alternatives',\n defaults: { verbs: DEFAULT_VERBS, allowList: [] },\n help: 'Weak verbs like \"make\" and \"do\" hide the real action. Replace them with specific verbs that tell the reader exactly what happens.',\n\n check({ text, sourceMap, options }: RuleInput<NoWeakVerbsOptions>): Diagnostic[] {\n const verbs = options.verbs?.length ? options.verbs : DEFAULT_VERBS\n const allowed = new Set((options.allowList ?? []).map(verb => verb.toLowerCase()))\n const weakByInfinitive = new Map<string, WeakVerb>()\n for (const entry of verbs) {\n weakByInfinitive.set(entry.verb.toLowerCase(), entry)\n }\n\n const doc = createDoc(text)\n const diagnostics: Diagnostic[] = []\n\n const verbMatches = doc.verbs().json({\n offset: true,\n text: true,\n terms: { offset: true, text: true },\n }) as JsonVerb[]\n\n for (const entry of verbMatches) {\n const infinitive = entry.verb?.infinitive?.toLowerCase()\n if (!infinitive) continue\n if (allowed.has(infinitive)) continue\n\n const weakVerb = weakByInfinitive.get(infinitive)\n if (!weakVerb) continue\n\n const occurrence = getOccurrenceFromVerb(entry, text)\n if (!occurrence) 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-weak-verbs',\n severity: 'warn',\n message: `replace weak verb \"${occurrence.text}\" with a stronger alternative such as \"${weakVerb.alternatives.join('\", \"')}\"`,\n range: { start, end: end + 1 },\n help: noWeakVerbs.help,\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 } 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 { noLlmSpeak } from './no-llm-speak.js'\nimport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nimport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noNounStrings } from './no-noun-strings.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 { noWeakVerbs } from './no-weak-verbs.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 { noLlmSpeak } from './no-llm-speak.js'\nexport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nexport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noNounStrings } from './no-noun-strings.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 { noWeakVerbs } from './no-weak-verbs.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 { NoLlmSpeakOptions } from './no-llm-speak.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 { NoNounStringsOptions } from './no-noun-strings.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 { NoWeakVerbsOptions } from './no-weak-verbs.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-llm-speak', noLlmSpeak 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-noun-strings', noNounStrings 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 ['no-weak-verbs', noWeakVerbs as Rule],\n ['sentence-complexity', sentenceComplexity as Rule],\n])\n"],"mappings":";AAOA,IAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,aAAa,QAA4B;AAChD,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;AACrE;AAEA,SAAS,aAAa,cAAwB,WAA6B;AACzE,QAAM,qBAAqB,aAAa,YAAY,EACjD,IAAI,YAAU,aAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,EAC3D,KAAK,GAAG;AACX,QAAM,kBAAkB,aAAa,SAAS,EAC3C,IAAI,YAAU,aAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,EAC3D,KAAK,GAAG;AAEX,SAAO,IAAI;AAAA,IACT,OAAO,kBAAkB,YAAY,eAAe;AAAA,IACpD;AAAA,EACF;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA,EACA,MAAM;AAAA,EAIN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,cAA4B,CAAC;AACnC,UAAM,eAAe,QAAQ,cAAc,SAAS,QAAQ,eAAe;AAC3E,UAAM,YAAY,QAAQ,WAAW,SAAS,QAAQ,YAAY;AAElE,QAAI,CAAC,aAAa,UAAU,CAAC,UAAU,OAAQ,QAAO;AAEtD,UAAM,KAAK,aAAa,cAAc,SAAS;AAC/C,QAAI;AAEJ,YAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,YAAM,cAAc,MAAM,CAAC;AAC3B,YAAM,cAAc,MAAM,CAAC;AAC3B,YAAM,WAAW,MAAM,CAAC;AACxB,YAAM,aAAa,MAAM;AACzB,YAAM,WAAW,aAAa,YAAY;AAE1C,YAAM,QAAQ,UAAU,UAAU;AAClC,YAAM,MAAM,UAAU,WAAW,CAAC;AAClC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,YAAM,UAAsB;AAAA,QAC1B,aAAa,WAAW,WAAW,aAAa,QAAQ;AAAA,QACxD,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE,GAAG,aAAa,SAAS,CAAC;AAAA,MACnE;AAEA,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,IAAI,WAAW,0BAAqB,QAAQ;AAAA,QACrD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,uBAAuB;AAAA,QAC7B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACjHA,OAAO,SAAS;AAUT,SAAS,UAAU,MAAuB;AAC/C,SAAO,IAAI,IAAI;AACjB;AAEO,SAAS,oBAAoB,MAAc,SAAuC;AACvF,QAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,QAAQ,KAAK,EAAE,CAAC;AAE/E,SAAO,KAAK,QAAQ,CAAC,UAAU;AAC7B,UAAM,QAAQ,MAAM,QAAQ,SAAS,MAAM,QAAQ,CAAC,GAAG,QAAQ;AAC/D,UAAM,SAAS,MAAM,QAAQ,UAAU,eAAe,MAAM,KAAK;AAEjE,QAAI,OAAO,UAAU,YAAY,OAAO,WAAW,YAAY,UAAU,GAAG;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,WAAO,CAAC;AAAA,MACN,MAAM,MAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,MAAM;AAAA,MACpD;AAAA,MACA,KAAK,QAAQ;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,mBAAmB,WAAqB,YAAyD;AAC/G,QAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,QAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,MAAI,UAAU,UAAa,QAAQ,OAAW,QAAO;AAErD,SAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAC/B;AAEA,SAAS,eAAe,OAAyD;AAC/E,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,KAAK,QAAQ;AAC5B,QAAI,OAAO,WAAW,SAAU,QAAO;AACvC,aAAS;AAAA,EACX;AAEA,SAAO;AACT;;;AC1CA,IAAM,sBAAsB;AAC5B,IAAM,0BAA0B,CAAC,MAAM;AAEhC,IAAM,kBAAgD;AAAA,EAC3D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,YAAY,qBAAqB,gBAAgB,wBAAwB;AAAA,EACrF,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAoD;AACnF,UAAM,aAAa,QAAQ,cAAc;AACzC,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,yBAAyB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAE7G,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,MAAO;AAErB,YAAM,cAAc,SAAS,MAC1B,OAAO,CAAC,SAAS,KAAK,MAAM,SAAS,QAAQ,CAAC,EAC9C,IAAI,CAAC,SAAS;AACb,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,SAAS,KAAK,QAAQ;AAC5B,eAAO;AAAA,UACL,MAAM,KAAK,MAAM,QAAQ,eAAe,EAAE,KAAK;AAAA,UAC/C,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,UAC3C,KAAK,OAAO,UAAU,YAAY,OAAO,WAAW,WAAW,QAAQ,SAAS;AAAA,QAClF;AAAA,MACF,CAAC,EACA,OAAO,CAAC,eAAe,OAAO,KAAK,WAAW,IAAI,CAAC,EACnD,OAAO,CAAC,eAAe,CAAC,QAAQ,IAAI,WAAW,KAAK,YAAY,CAAC,CAAC,EAClE,KAAK,CAAC,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK;AAEjD,eAAS,QAAQ,YAAY,QAAQ,YAAY,QAAQ,SAAS,GAAG;AACnE,cAAM,aAAa,YAAY,KAAK;AACpC,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2BAA2B,WAAW,IAAI,0BAA0B,UAAU,cAAc,eAAe,IAAI,KAAK,GAAG;AAAA,UAChI;AAAA,UACA,MAAM,gBAAgB;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACxDA,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAO,eAAe,qBAAqB,EAAE;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQ;AACtD,UAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAI,sBAAsB,EAAG,QAAO;AAEpC,eAAW,YAAY,kBAAkB,IAAI,GAAG;AAC9C,YAAM,OAAO,YAAY,SAAS,MAAM,KAAK;AAC7C,UAAI,KAAK,UAAU,oBAAqB;AAExC,YAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,EAAG;AAC9C,YAAM,YAAY,SAAS,QAAQ,KAAK,KAAK,SAAS,CAAC,EAAG;AAC1D,YAAM,QAAQ,UAAU,WAAW;AACnC,YAAM,MAAM,UAAU,YAAY,CAAC;AACnC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,YAAM,QAAQ,KAAK,IAAI,SAAO,IAAI,KAAK,YAAY,CAAC,EAAE,KAAK,IAAI;AAC/D,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,2BAA2B,KAAK;AAAA,QACzC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,iBAAiB;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAaA,SAAS,kBAAkB,MAA+B;AACxD,QAAM,SAA0B,CAAC;AACjC,QAAM,KAAK;AACX,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,UAAM,0BAA0B,MAAM,CAAC,EAAE,MAAM,MAAM,IAAI,CAAC,EAAE,UAAU;AACtE,UAAM,WAAW,MAAM,CAAC,EAAE,KAAK;AAC/B,QAAI,CAAC,SAAU;AACf,WAAO,KAAK,EAAE,MAAM,UAAU,OAAO,MAAM,QAAQ,wBAAwB,CAAC;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAc,OAA4B;AAC7D,QAAM,cAAc,MAAM,IAAI,WAAW,EAAE,KAAK,GAAG;AACnD,MAAI,CAAC,YAAa,QAAO,CAAC;AAE1B,QAAM,OAAkB,CAAC;AACzB,QAAM,KAAK,IAAI,OAAO,OAAO,WAAW,QAAQ,IAAI;AACpD,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,SAAK,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,OAAO,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,OAAO,CAAC;AAAA,EACtF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC7FA,IAAM,kBAAyD;AAAA,EAC7D,eAAe;AAAA,EACf,cAAc;AAAA,EACd,UAAU;AACZ;AAEO,IAAM,uBAA0D;AAAA,EACrE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAG,gBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAyD;AACxF,UAAM,gBAAgB,QAAQ,iBAAiB,gBAAgB;AAC/D,UAAM,eAAe,QAAQ,gBAAgB,gBAAgB;AAC7D,UAAM,WAAW,QAAQ,YAAY,gBAAgB;AAErD,UAAM,YAAY,aAAa,IAAI;AACnC,UAAM,QAAQ,SAAS,IAAI;AAE3B,QAAI,UAAU,SAAS,gBAAgB,MAAM,SAAS,UAAU;AAC9D,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAAY,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,eAAe,IAAI,GAAG,CAAC;AAC3E,UAAM,QAAQ,mBAAmB,MAAM,QAAQ,UAAU,QAAQ,SAAS;AAE1E,QAAI,SAAS,eAAe;AAC1B,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAAQ,UAAU,CAAC;AACzB,UAAM,MAAM,UAAU,UAAU,SAAS,CAAC;AAC1C,QAAI,UAAU,UAAa,QAAQ,OAAW,QAAO,CAAC;AAEtD,WAAO,CAAC;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,wBAAwB,MAAM,QAAQ,CAAC,CAAC,uBAAkB,aAAa;AAAA,MAChF,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,MAC7B,MAAM,qBAAqB;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,aAAa,MAAwB;AAE5C,QAAM,QAAQ,KAAK,MAAM,UAAU;AACnC,QAAM,YAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,WAAW,MAAM,CAAC;AACxB,UAAM,aAAa,MAAM,IAAI,CAAC,KAAK;AACnC,UAAM,YAAY,YAAY,MAAM;AACpC,UAAM,UAAU,SAAS,KAAK;AAC9B,QAAI,QAAS,WAAU,KAAK,OAAO;AAAA,EACrC;AACA,SAAO;AACT;AAEA,SAAS,SAAS,MAAwB;AACxC,SAAO,KACJ,YAAY,EACZ,QAAQ,kBAAkB,GAAG,EAC7B,MAAM,KAAK,EACX,OAAO,UAAQ,KAAK,SAAS,KAAK,WAAW,KAAK,IAAI,CAAC;AAC5D;AAEA,SAAS,eAAe,MAAsB;AAC5C,QAAM,UAAU,KAAK,YAAY,EAAE,QAAQ,WAAW,EAAE;AACxD,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,UAAU,EAAG,QAAO;AAEhC,QAAM,SAAS,QAAQ,MAAM,YAAY;AACzC,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,QAAQ,OAAO;AACnB,MAAI,QAAQ,SAAS,GAAG,EAAG;AAC3B,MAAI,QAAQ,SAAS,IAAI,KAAK,QAAQ,SAAS,KAAK,CAAC,YAAY,KAAK,QAAQ,QAAQ,SAAS,CAAC,KAAK,EAAE,GAAG;AACxG;AAAA,EACF;AACA,SAAO,KAAK,IAAI,GAAG,KAAK;AAC1B;AAEA,SAAS,mBAAmB,OAAe,WAAmB,WAA2B;AACvF,MAAI,cAAc,KAAK,UAAU,EAAG,QAAO;AAC3C,SAAO,QAAQ,QAAQ,aAAa,QAAQ,YAAY,SAAS;AACnE;;;AC1FA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAAS,gBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAU;AAC5D,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAI,CAAC,eAAe,MAAM,WAAW,KAAK,EAAG;AAE7C,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2CAA2C,WAAW,KAAK,YAAY,CAAC;AAAA,UACjF;AAAA,UACA,MAAM,mBAAmB;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAAS,eAAe,MAAc,OAAwB;AAC5D,MAAI,QAAQ,QAAQ;AACpB,SAAO,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,CAAE,EAAG;AAC9C,SAAO,QAAQ,KAAK,QAAQ,KAAK,KAAK,KAAK,CAAE;AAC/C;;;AC7CA,IAAMA,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,gBAA4C;AAAA,EACvD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAkD;AACjF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAC5D,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,cAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,cAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,WAAW,WAAW,KAAK,YAAY,CAAC;AAAA,UACjD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,cAAc;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACjCA,IAAM,WAAsB;AAAA,EAC1B;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AACF;AAEO,IAAM,8BAAwE;AAAA,EACnF,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,gBAAgB,CAAC,EAAE;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAgE;AAC/F,UAAM,cAA4B,CAAC;AACnC,UAAM,iBAAiB,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,WAAS,UAAU,KAAK,CAAC,CAAC;AAE5F,eAAW,WAAW,UAAU;AAC9B,YAAM,KAAK,IAAI,OAAO,QAAQ,GAAG,QAAQ,QAAQ,GAAG,KAAK;AACzD,UAAI;AACJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,SAAS,MAAM,CAAC;AACtB,YAAI,eAAe,IAAI,UAAU,MAAM,CAAC,EAAG;AAE3C,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,OAAO,SAAS,CAAC;AACrD,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,GAAG,QAAQ,OAAO,MAAM,MAAM;AAAA,UACvC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,4BAA4B;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,MAAM,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACvD;;;ACzDA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2BAA2B,aAAa;AAAA,UACjD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC5CA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,eAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,QAAQ,eAAe;AAAA,EACnC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAiD;AAChF,UAAM,cAA4B,CAAC;AACnC,UAAM,SAAS,QAAQ,QAAQ,SAAS,QAAQ,SAAS;AAEzD,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,KAAK,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACnF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,SAAS,MAAM,CAAC;AACtB,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,OAAO,SAAS,CAAC;AACrD,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,iBAAiB,OAAO,YAAY,CAAC;AAAA,UAC9C,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,aAAa;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACjDA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,WAAkC;AAAA,EAC7C,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA6C;AAC5E,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,0BAA0B,aAAa;AAAA,UAChD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,SAAS;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACzCA,IAAMC,mBAAoC;AAAA,EACxC,EAAE,QAAQ,cAAc,cAAc,CAAC,WAAW,SAAS,EAAE;AAAA,EAC7D,EAAE,QAAQ,0BAA0B,cAAc,CAAC,QAAQ,UAAU,EAAE;AAAA,EACvE,EAAE,QAAQ,qBAAqB,cAAc,CAAC,MAAM,EAAE;AAAA,EACtD,EAAE,QAAQ,kCAAkC,cAAc,CAAC,SAAS,KAAK,EAAE;AAAA,EAC3E,EAAE,QAAQ,0BAA0B,cAAc,CAAC,OAAO,EAAE;AAAA,EAC5D,EAAE,QAAQ,eAAe,cAAc,CAAC,MAAM,EAAE;AAAA,EAChD,EAAE,QAAQ,eAAe,cAAc,CAAC,QAAQ,MAAM,EAAE;AAAA,EACxD,EAAE,QAAQ,mBAAmB,cAAc,CAAC,IAAI,EAAE;AAAA,EAClD,EAAE,QAAQ,uBAAuB,cAAc,CAAC,SAAS,EAAE;AAAA,EAC3D,EAAE,QAAQ,gCAAgC,cAAc,CAAC,QAAQ,EAAE;AAAA,EACnE,EAAE,QAAQ,kBAAkB,cAAc,CAAC,YAAY,WAAW,EAAE;AAAA,EACpE,EAAE,QAAQ,eAAe,cAAc,CAAC,SAAS,YAAY,EAAE;AAAA,EAC/D,EAAE,QAAQ,eAAe,cAAc,CAAC,MAAM,EAAE;AAAA,EAChD,EAAE,QAAQ,YAAY,cAAc,CAAC,MAAM,EAAE;AAAA,EAC7C,EAAE,QAAQ,iBAAiB,cAAc,CAAC,MAAM,SAAS,EAAE;AAAA,EAC3D,EAAE,QAAQ,6BAA6B,cAAc,CAAC,MAAM,EAAE;AAAA,EAC9D,EAAE,QAAQ,sBAAsB,cAAc,CAAC,MAAM,EAAE;AAAA,EACvD,EAAE,QAAQ,sBAAsB,cAAc,CAAC,MAAM,EAAE;AAAA,EACvD,EAAE,QAAQ,wBAAwB,cAAc,CAAC,EAAE;AAAA,EACnD,EAAE,QAAQ,kBAAkB,cAAc,CAAC,EAAE;AAAA,EAC7C,EAAE,QAAQ,iBAAiB,cAAc,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC3D,EAAE,QAAQ,kBAAkB,cAAc,CAAC,MAAM,EAAE;AAAA,EACnD,EAAE,QAAQ,YAAY,cAAc,CAAC,WAAW,KAAK,EAAE;AAAA,EACvD,EAAE,QAAQ,gBAAgB,cAAc,CAAC,WAAW,YAAY,EAAE;AAAA,EAClE,EAAE,QAAQ,aAAa,cAAc,CAAC,WAAW,UAAU,EAAE;AAAA,EAC7D,EAAE,QAAQ,cAAc,cAAc,CAAC,WAAW,SAAS,EAAE;AAAA,EAC7D,EAAE,QAAQ,WAAW,cAAc,CAAC,OAAO,UAAU,EAAE;AAAA,EACvD,EAAE,QAAQ,WAAW,cAAc,CAAC,YAAY,KAAK,EAAE;AAAA,EACvD,EAAE,QAAQ,UAAU,cAAc,CAAC,UAAU,WAAW,EAAE;AAC5D;AAcA,SAAS,uBACP,OACA,cACqD;AACrD,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,aAAsC;AAAA,EACjD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,kBAAiB,gBAAgB,CAAC,EAAE;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA+C;AAC9E,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAC5D,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,YAAU,OAAO,YAAY,CAAC,CAAC;AAC1F,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,EAAE,QAAQ,aAAa,KAAK,SAAS;AAC9C,UAAI,QAAQ,IAAI,OAAO,YAAY,CAAC,EAAG;AAEvC,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,YAAM,OAAO,QAAQ,KAAK;AAAA,QACxB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK;AAAA,MACpC,CAAC;AAED,iBAAW,SAAS,MAAM;AACxB,cAAM,aAAa,uBAAuB,OAAO,IAAI;AACrD,YAAI,CAAC,WAAY;AAEjB,cAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,cAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,cAAM,aAAa,aAAa,SAC5B,QAAQ,aAAa,KAAK,MAAM,CAAC,MACjC;AAEJ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,WAAW,IAAI,YAAO,UAAU;AAAA,UACtE,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,WAAW;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC9HA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,WAAW,kBAAkB;AAAA,EACzC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,cAA4B,CAAC;AACnC,UAAM,YAAY,QAAQ,WAAW,SAAS,QAAQ,YAAY;AAElE,eAAW,YAAY,WAAW;AAChC,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,QAAQ,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACtF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,kBAAkB,MAAM,CAAC;AAC/B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,gBAAgB,SAAS,CAAC;AAC9D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,gCAAgC,eAAe;AAAA,UACxD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,uBAAuB;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACtCA,IAAMC,iBAAoC;AAAA,EACxC,EAAE,MAAM,QAAQ,cAAc,CAAC,YAAY,QAAQ,OAAO,EAAE;AAAA,EAC5D,EAAE,MAAM,YAAY,cAAc,CAAC,aAAa,YAAY,WAAW,EAAE;AAAA,EACzE,EAAE,MAAM,aAAa,cAAc,CAAC,WAAW,EAAE;AAAA,EACjD,EAAE,MAAM,aAAa,cAAc,CAAC,YAAY,WAAW,EAAE;AAAA,EAC7D,EAAE,MAAM,UAAU,cAAc,CAAC,WAAW,QAAQ,QAAQ,EAAE;AAAA,EAC9D,EAAE,MAAM,SAAS,cAAc,CAAC,aAAa,WAAW,UAAU,EAAE;AAAA,EACpE,EAAE,MAAM,SAAS,cAAc,CAAC,cAAc,WAAW,SAAS,EAAE;AAAA,EACpE,EAAE,MAAM,UAAU,cAAc,CAAC,WAAW,gBAAgB,YAAY,EAAE;AAAA,EAC1E,EAAE,MAAM,QAAQ,cAAc,CAAC,aAAa,QAAQ,YAAY,EAAE;AAAA,EAClE,EAAE,MAAM,QAAQ,cAAc,CAAC,gBAAgB,cAAc,MAAM,EAAE;AAAA,EACrE,EAAE,MAAM,gBAAgB,cAAc,CAAC,eAAe,oBAAoB,cAAc,GAAG,OAAO,KAAK;AAAA,EACvG,EAAE,MAAM,cAAc,cAAc,CAAC,gBAAgB,OAAO,WAAW,GAAG,OAAO,KAAK;AAAA,EACtF,EAAE,MAAM,iBAAiB,cAAc,CAAC,iBAAiB,UAAU,EAAE;AAAA,EACrE,EAAE,MAAM,WAAW,cAAc,CAAC,YAAY,aAAa,QAAQ,EAAE;AACvE;AAGA,IAAM,uBAAuB,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC;AAexD,SAAS,eAAe,SAAyB;AAC/C,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAwB,OAA4B;AAC9E,MAAI,CAAC,qBAAqB,IAAI,KAAK,KAAK,YAAY,CAAC,EAAG,QAAO;AAC/D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,MAAM,CAAC,EAAG,QAAQ,CAAC;AAChC,QAAM,SAAS,KAAK,SAAS,MAAM;AACnC,QAAM,oBAAoB,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,WAAW;AAE5E,SAAO,CAAC,UAAU;AACpB;AAEA,SAASC,wBAAuB,OAAkB,cAA8C;AAC9F,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAOD,gBAAe,cAAc,CAAC,EAAE;AAAA,EACnD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQA;AACtD,UAAM,UAAU,IAAI,KAAK,QAAQ,gBAAgB,CAAC,GAAG,IAAI,UAAQ,KAAK,YAAY,CAAC,CAAC;AAEpF,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AAEnC,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,IAAI,KAAK,KAAK,YAAY,CAAC,EAAG;AAE1C,YAAM,UAAU,eAAe,KAAK,IAAI;AACxC,YAAM,UAAU,IAAI,MAAM,OAAO;AACjC,YAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,CAAC;AAEvG,iBAAW,SAAS,MAAM;AACxB,YAAI,CAAC,mBAAmB,MAAM,MAAM,SAAS,CAAC,CAAC,EAAG;AAElD,cAAM,aAAaC,wBAAuB,OAAO,IAAI;AACrD,YAAI,CAAC,WAAY;AAEjB,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,cAAM,aAAa,KAAK,aAAa,KAAK,IAAI;AAC9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,YAAY,WAAW,IAAI,yCAAyC,UAAU;AAAA,UACvF;AAAA,UACA,MAAM,uBAAuB;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC5HA,IAAM,mBAAmB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,KAAK;AACvE,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAA0D;AAAA,EACrE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,UAAU,kBAAkB,cAAc,sBAAsB;AAAA,EAC5E,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAyD;AACxF,UAAM,cAA4B,CAAC;AACnC,UAAM,WAAW,QAAQ,UAAU,SAAS,QAAQ,WAAW;AAC/D,UAAM,eAAe,IAAI,KAAK,QAAQ,cAAc,SAAS,QAAQ,eAAe,uBAAuB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC5I,UAAM,gBAAgB,SAAS,IAAIC,YAAW,EAAE,KAAK,GAAG;AACxD,QAAI,CAAC,cAAe,QAAO;AAE3B,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,WAAW,aAAa,SAAS;AAE3D,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,iBAAiB,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,GAAG,YAAY;AAC3E,UAAI,CAAC,kBAAkB,aAAa,IAAI,cAAc,EAAG;AAEzD,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,YAAY,WAAW,IAAI;AAAA,QACpC;AAAA,QACA,MAAM,qBAAqB;AAAA,MAC7B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,aAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACjDA,IAAMC,mBAAkD;AAAA,EACtD,qBAAqB;AAAA,EACrB,gBAAgB,CAAC;AACnB;AASA,SAAS,gBAAgB,OAA2B;AAClD,SAAO,MAAM,KAAK,SAAS,MAAM,KAAK,CAAC,MAAM,KAAK,SAAS,YAAY;AACzE;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACxD;AAEO,IAAM,gBAA4C;AAAA,EACvD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAkD;AACjF,UAAM,sBAAsB,QAAQ,uBAAuBA,iBAAgB;AAC3E,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,eAAe,CAAC;AAE3E,QAAI,uBAAuB,KAAK,KAAK,WAAW,EAAG,QAAO,CAAC;AAE3D,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,WAAW,IAAI,MAAM,EAAE,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,OAAO,EAAE,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAChD,CAAC;AAMD,UAAM,QAAqB,CAAC;AAC5B,eAAW,SAAS,UAAU;AAC5B,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,MAAM,UAAU,MAAM;AACrC,YAAM,QAAQ,QAAQ;AACtB,YAAM,SAAS,QAAQ;AACvB,UAAI,OAAO,UAAU,YAAY,OAAO,WAAW,YAAY,UAAU,EAAG;AAE5E,YAAM,KAAK;AAAA,QACT,MAAM,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,MAAM;AAAA,QAClE;AAAA,QACA,KAAK,QAAQ;AAAA,QACb,MAAM,MAAM,QAAQ,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,UAAM,cAA4B,CAAC;AACnC,QAAI,WAAW;AACf,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,aAAS,WAAiB;AACxB,UAAI,SAAS,SAAS,oBAAqB;AAE3C,YAAM,SAAS,SAAS,KAAK,GAAG;AAChC,UAAI,QAAQ,IAAI,gBAAgB,MAAM,CAAC,EAAG;AAE1C,YAAM,QAAQ,mBAAmB,WAAW,EAAE,OAAO,UAAU,KAAK,QAAQ,MAAM,OAAO,CAAC;AAC1F,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,gBAAgB,MAAM;AAAA,QAC/B;AAAA,QACA,MAAM,cAAc;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,eAAW,QAAQ,OAAO;AACxB,UAAI,gBAAgB,IAAI,GAAG;AACzB,YAAI,SAAS,WAAW,GAAG;AACzB,qBAAW,KAAK;AAAA,QAClB;AACA,iBAAS,KAAK;AACd,iBAAS,KAAK,KAAK,IAAI;AAAA,MACzB,OAAO;AACL,iBAAS;AACT,iBAAS,SAAS;AAClB,mBAAW;AACX,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,aAAS;AAET,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC9FA,IAAM,2BAA2B;AACjC,IAAM,2BAA2B;AACjC,IAAM,4BAA4B;AAElC,IAAM,uBAAuB,CAAC,OAAO,OAAO,MAAM,OAAO,OAAO,IAAI;AACpE,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,2BAAkE;AAAA,EAC7E,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW,CAAC;AAAA,EACd;AAAA,EACA,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA6D;AAC5F,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,mBAAmB,QAAQ,oBAAoB;AAErD,UAAM,gBAAgB,QAAQ,gBAAgB,sBAAsB;AAAA,MAAI,WACtE,MAAM,YAAY,EAAE,KAAK;AAAA,IAC3B;AACA,UAAM,iBAAiB,QAAQ,iBAAiB,uBAAuB;AAAA,MAAI,WACzE,MAAM,YAAY,EAAE,KAAK;AAAA,IAC3B;AACA,UAAM,YAAY,IAAI;AAAA,OACnB,QAAQ,aAAa,CAAC,GAAG,IAAI,WAAS,MAAM,YAAY,EAAE,KAAK,CAAC;AAAA,IACnE;AAEA,UAAM,WAAiC;AAAA,MACrC,GAAG,aAAa,IAAI,aAAW;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,QACV,OAAO,cAAc,MAAM;AAAA,MAC7B,EAAE;AAAA,MACF,GAAG,cAAc,IAAI,aAAW;AAAA,QAC9B;AAAA,QACA,UAAU;AAAA,QACV,OAAO,cAAc,MAAM;AAAA,MAC7B,EAAE;AAAA,IACJ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,MAAM;AAElD,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,KAAK,EAAE,CAAC;AAEhF,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,UAAU,CAAC,SAAS,MAAO;AAEzC,YAAM,gBAAgB,SAAS,OAAO,SAAS;AAC/C,YAAM,eAAe,SAAS,QAAQ;AACtC,YAAM,gBAAuD,CAAC;AAE9D,UAAI,oBAAoB;AACxB,UAAI,qBAAqB;AAEzB,iBAAW,EAAE,QAAQ,UAAU,MAAM,KAAK,UAAU;AAClD,YAAI,UAAU,IAAI,MAAM,EAAG;AAE3B,YAAI;AACJ,gBAAQ,QAAQ,MAAM,KAAK,YAAY,OAAO,MAAM;AAClD,gBAAM,aAAa,MAAM;AACzB,gBAAM,WAAW,MAAM,QAAQ,MAAM,CAAC,EAAE;AAGxC,cAAI,cAAc,KAAK,WAAS,aAAa,MAAM,OAAO,WAAW,MAAM,KAAK,GAAG;AACjF;AAAA,UACF;AAEA,wBAAc,KAAK,EAAE,OAAO,YAAY,KAAK,SAAS,CAAC;AAEvD,cAAI,aAAa,gBAAgB;AAC/B,iCAAqB;AAAA,UACvB,OAAO;AACL,kCAAsB;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,oBAAoB;AAClC,UACE,SAAS,mBACT,qBAAqB,mBACrB,sBAAsB,kBACtB;AACA;AAAA,MACF;AAEA,YAAM,SAAS,SAAS,OAAO,UAAU;AACzC,YAAM,cAAc,gBAAgB;AAEpC,YAAM,cAAc,UAAU,aAAa;AAC3C,YAAM,YAAY,UAAU,cAAc,CAAC;AAC3C,UAAI,gBAAgB,UAAa,cAAc,OAAW;AAE1D,YAAM,UAAoB,CAAC;AAC3B,UAAI,QAAQ,iBAAiB;AAC3B,gBAAQ,KAAK,GAAG,KAAK,sBAAsB,eAAe,GAAG;AAAA,MAC/D;AACA,UAAI,oBAAoB,iBAAiB;AACvC,gBAAQ,KAAK,GAAG,iBAAiB,sBAAsB,eAAe,GAAG;AAAA,MAC3E;AACA,UAAI,qBAAqB,kBAAkB;AACzC,gBAAQ,KAAK,GAAG,kBAAkB,uBAAuB,gBAAgB,GAAG;AAAA,MAC9E;AAEA,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,+BAA+B,QAAQ,KAAK,IAAI,CAAC;AAAA,QAC1D,OAAO,EAAE,OAAO,aAAa,KAAK,YAAY,EAAE;AAAA,QAChD,MAAM,yBAAyB;AAAA,QAC/B,SAAS;AAAA,UACP,aAAa;AAAA,UACb,OAAO,CAAC;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,QAAwB;AAC7C,QAAM,UAAU,OAAO,QAAQ,uBAAuB,MAAM;AAC5D,QAAM,SAAS,QAAQ,QAAQ,QAAQ,MAAM;AAC7C,QAAM,SAAS,OAAO,SAAS,MAAM,IAAI,SAAS,MAAM,MAAM;AAC9D,SAAO,IAAI,OAAO,QAAQ,IAAI;AAChC;;;AC/JA,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAC3B,IAAMC,2BAA0B,CAAC,QAAQ,KAAK;AAEvC,IAAM,oBAAoD;AAAA,EAC/D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,WAAW;AAAA,IACX,WAAW;AAAA,IACX,gBAAgBA;AAAA,EAClB;AAAA,EACA,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAsD;AACrF,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkBA,0BAAyB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC7G,UAAM,eAAe,QAAQ,UAAU,IAAI,IAAI,QAAQ,QAAQ,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC,IAAI;AAEpG,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAEhF,UAAM,sBAAsB,oBAAI,IAAiE;AAEjG,eAAW,YAAY,WAAW;AAChC,iBAAW,QAAQ,SAAS,SAAS,CAAC,GAAG;AACvC,YAAI,CAAC,KAAK,MAAM,SAAS,QAAQ,EAAG;AAEpC,cAAM,UAAU,KAAK,MAAM,QAAQ,eAAe,EAAE,KAAK;AACzD,cAAM,aAAa,QAAQ,YAAY;AACvC,YAAI,CAAC,cAAc,WAAW,SAAS,UAAW;AAClD,YAAI,QAAQ,IAAI,UAAU,EAAG;AAC7B,YAAI,gBAAgB,CAAC,aAAa,IAAI,UAAU,EAAG;AAEnD,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,SAAS,KAAK,QAAQ;AAC5B,YAAI,OAAO,UAAU,YAAY,OAAO,WAAW,SAAU;AAE7D,cAAM,aAAa,EAAE,MAAM,SAAS,OAAO,KAAK,QAAQ,OAAO;AAC/D,cAAM,WAAW,oBAAoB,IAAI,UAAU;AACnD,YAAI,UAAU;AACZ,mBAAS,KAAK,UAAU;AAAA,QAC1B,OAAO;AACL,8BAAoB,IAAI,YAAY,CAAC,UAAU,CAAC;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAA4B,CAAC;AAEnC,eAAW,CAAC,EAAE,WAAW,KAAK,qBAAqB;AACjD,UAAI,YAAY,UAAU,UAAW;AAErC,eAAS,QAAQ,WAAW,QAAQ,YAAY,QAAQ,SAAS,GAAG;AAClE,cAAM,aAAa,YAAY,KAAK;AACpC,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,4BAA4B,WAAW,IAAI,aAAa,YAAY,MAAM;AAAA,UACnF;AAAA,UACA,MAAM,kBAAkB;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC9EA,IAAM,8BAA8B,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAE/E,IAAM,iBAA8C;AAAA,EACzD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,oBAAoB,4BAA4B;AAAA,EAC5D,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAmD;AAClF,UAAM,cAA4B,CAAC;AACnC,UAAM,cAAc,IAAI,KAAK,QAAQ,oBAAoB,SAAS,QAAQ,qBAAqB,6BAA6B,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC7J,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,iCAAiC;AAE3D,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,UAAI,MAAM,SAAS,EAAG;AACtB,UAAI,CAAC,YAAY,IAAI,MAAM,CAAC,EAAG,YAAY,CAAC,EAAG;AAE/C,YAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,YAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,iCAAiC,WAAW,IAAI;AAAA,QACzD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,eAAe;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,kBAAkB,WAAW;AAAA,EACtC;AACF;AAEA,SAAS,kBAAkB,aAAyC;AAClE,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,YAAY,OAAO,CAAC,eAAe;AACxC,UAAM,MAAM,GAAG,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM,GAAG;AAC7D,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC;AACH;;;AC5CA,IAAM,mBAAmB,CAAC,MAAM,QAAQ,QAAQ,SAAS,OAAO;AAChE,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,UAAU,kBAAkB,OAAO,cAAc;AAAA,EAC7D,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,WAAW,QAAQ,UAAU,SAAS,QAAQ,WAAW;AAC/D,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQ;AACtD,QAAI,CAAC,SAAS,UAAU,CAAC,MAAM,OAAQ,QAAO;AAE9C,UAAM,KAAK,IAAI;AAAA,MACb,mBAAmB,SAAS,IAAIC,YAAW,EAAE,KAAK,GAAG,CAAC,SAAS,MAAM,IAAIA,YAAW,EAAE,KAAK,GAAG,CAAC;AAAA,MAC/F;AAAA,IACF;AACA,QAAI;AACJ,YAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,YAAM,SAAS,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACtC,YAAM,cAAc,MAAM,QAAQ,MAAM,CAAC,EAAE,QAAQ,MAAM;AACzD,YAAM,QAAQ,UAAU,WAAW;AACnC,YAAM,MAAM,UAAU,cAAc,OAAO,SAAS,CAAC;AACrD,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,gCAAgC,OAAO,YAAY,CAAC;AAAA,QAC7D,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,mBAAmB;AAAA,MAC3B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,aAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AClDA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,yBAAyB;AAExB,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,YAAY,oBAAoB,eAAe,uBAAuB;AAAA,EAClF,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,aAAa,IAAI,KAAK,QAAQ,YAAY,SAAS,QAAQ,aAAa,oBAAoB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AACnI,UAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAI,gBAAgB,EAAG,QAAO,CAAC;AAE/B,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AACnC,UAAM,aAAoD,CAAC;AAE3D,UAAM,WAAW,CAAC,+BAA+B,0BAA0B;AAC3E,eAAW,WAAW,UAAU;AAC9B,YAAM,UAAU,IAAI,MAAM,OAAO;AAEjC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,cAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,cAAM,iBAAiB,MAAM,OAAO,UAAQ,WAAW,IAAI,KAAK,YAAY,EAAE,QAAQ,eAAe,EAAE,CAAC,CAAC;AACzG,YAAI,eAAe,UAAU,cAAe;AAE5C,YAAI,WAAW,KAAK,CAAAC,WAAS,WAAW,QAAQA,OAAM,OAAO,WAAW,MAAMA,OAAM,KAAK,EAAG;AAC5F,mBAAW,KAAK,EAAE,OAAO,WAAW,OAAO,KAAK,WAAW,IAAI,CAAC;AAEhE,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,iCAAiC,WAAW,IAAI;AAAA,UACzD;AAAA,UACA,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACtEA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,aAAa;AAAA,UACnD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC/CO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,gBAAgB,CAAC,EAAE;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,cAA4B,CAAC;AACnC,UAAM,iBAAiB,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC/F,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,8BAA8B;AAExD,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,UAAI,eAAe,IAAI,WAAW,KAAK,YAAY,CAAC,EAAG;AAEvD,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,8BAA8B,WAAW,IAAI;AAAA,QACtD;AAAA,QACA,MAAM,oBAAoB;AAAA,MAC5B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;AC9BA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU;AAAA,MACd,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAAA,IAC9C;AACA,UAAM,gBAAuD,CAAC;AAE9D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,aAAa,MAAM;AACzB,cAAM,WAAW,MAAM,QAAQ,cAAc;AAC7C,YAAI,cAAc,KAAK,WAAS,cAAc,OAAO,YAAY,QAAQ,CAAC,EAAG;AAE7E,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAC9C,sBAAc,KAAK,EAAE,OAAO,YAAY,KAAK,SAAS,CAAC;AAEvD,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,sCAAsC,cAAc,YAAY,CAAC;AAAA,UAC1E,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,oBAAoB;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAAS,uBAAuB,SAA6B;AAC3D,SAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;AACtE;AAEA,SAAS,cAAc,OAAuC,OAAe,KAAsB;AACjG,SAAO,QAAQ,MAAM,OAAO,MAAM,MAAM;AAC1C;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACrEA,OAAOC,UAAS;AAWhB,IAAMC,mBAAwD;AAAA,EAC5D,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,aAAa;AACf;AAEO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAGN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,eAAe,IAAI;AAAA,OACtB,QAAQ,gBAAgBA,iBAAgB,cAAc,IAAI,UAAQ,KAAK,YAAY,CAAC;AAAA,IACvF;AACA,UAAM,cAAc,QAAQ,eAAeA,iBAAgB;AAE3D,QAAI,aAAa,SAAS,EAAG,QAAO,CAAC;AAErC,UAAM,MAAMC,KAAI,IAAI;AACpB,UAAM,cAA4B,CAAC;AACnC,UAAM,aAAoD,CAAC;AAE3D,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,eAAW,YAAY,WAAW;AAChC,YAAM,gBAAgB,SAAS,QAAQ,SAAS,SAAS,QAAQ,CAAC,GAAG,QAAQ;AAC7E,YAAM,iBAAiB,SAAS,QAAQ,UAAU,SAAS,OAAO,OAAO,CAAC,KAAK,SAAS,OAAO,KAAK,QAAQ,UAAU,IAAI,CAAC;AAC3H,UAAI,OAAO,kBAAkB,YAAY,OAAO,mBAAmB,SAAU;AAC7E,YAAM,cAAc,gBAAgB;AAEpC,YAAM,eAAe,SAAS,QAAQ,KAAK,MAAM,eAAe,WAAW;AAC3E,YAAM,cAAcA,KAAI,YAAY;AAGpC,YAAM,uBAAuB,MAAM,KAAK,YAAY,EAAE,KAAK,GAAG;AAC9D,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI,oBAAoB;AAAA,MAC1B;AAEA,iBAAW,WAAW,UAAU;AAC9B,cAAM,UAAU,YAAY,MAAM,OAAO;AAEzC,mBAAW,cAAc,oBAAoB,cAAc,OAAO,GAAG;AACnE,gBAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK;AAC9D,gBAAM,qBAAqB,MAAM,KAAK,UAAQ,aAAa,IAAI,KAAK,QAAQ,eAAe,EAAE,CAAC,CAAC;AAC/F,cAAI,CAAC,mBAAoB;AAGzB,gBAAM,sBAAsB,WAAW,KAAK,MAAM,eAAe,IAAI,CAAC,EAAE,UAAU;AAClF,gBAAM,kBAAkB,gBAAgB,WAAW;AACnD,gBAAM,gBAAgB,gBAAgB,WAAW,MAAM;AAEvD,cAAI,WAAW,KAAK,CAAAC,WAAS,kBAAkBA,OAAM,OAAO,gBAAgBA,OAAM,KAAK,EAAG;AAC1F,qBAAW,KAAK,EAAE,OAAO,iBAAiB,KAAK,cAAc,CAAC;AAE9D,cAAI,eAAe,YAAY,IAAI,MAAM,EAAG;AAE5C,gBAAM,QAAQ,mBAAmB,WAAW,EAAE,MAAM,WAAW,MAAM,OAAO,iBAAiB,KAAK,cAAc,CAAC;AACjH,cAAI,CAAC,MAAO;AAEZ,sBAAY,KAAK;AAAA,YACf,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,SAAS,gBAAgB,WAAW,KAAK,KAAK,EAAE,QAAQ,iBAAiB,EAAE,CAAC;AAAA,YAC5E;AAAA,YACA,MAAM,oBAAoB;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACpGA,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,aAAa,oBAAoB;AAAA,EAC7C,MACE;AAAA,EAEF,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,cAAc,QAAQ,aAAa,SAAS,QAAQ,cAAc;AAExE,eAAW,cAAc,aAAa;AACpC,YAAM,KAAK,IAAI;AAAA,QACb,MAAMC,cAAa,UAAU,EAAE,QAAQ,SAAS,MAAM,CAAC;AAAA,QACvD;AAAA,MACF;AACA,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,oBAAoB,MAAM,CAAC;AACjC,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,kBAAkB,SAAS,CAAC;AAChE,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,iBAAiB;AAAA,UACvD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,mBAAmB;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACtDA,IAAM,iBAAiB,CAAC,OAAO,SAAS,OAAO,OAAO;AACtD,IAAMC,iBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,eAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,QAAQ,gBAAgB,OAAOA,eAAc;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAiD;AAChF,UAAM,cAA4B,CAAC;AACnC,UAAM,SAAS,IAAI,KAAK,QAAQ,QAAQ,SAAS,QAAQ,SAAS,gBAAgB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AACnH,UAAM,QAAQ,IAAI,KAAK,QAAQ,OAAO,SAAS,QAAQ,QAAQA,gBAAe,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC/G,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,uBAAuB;AAEjD,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,YAAM,QAAQ,MAAM,CAAC,GAAG,YAAY;AACpC,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC,GAAG,YAAY;AAClD,UAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,KAAK,KAAK,CAAC,MAAM,IAAI,IAAI,EAAG;AAE/D,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,YAAY,WAAW,KAAK,YAAY,CAAC;AAAA,QAClD;AAAA,QACA,MAAM,aAAa;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;ACzCA,IAAMC,iBAA4B;AAAA,EAChC,EAAE,MAAM,QAAQ,cAAc,CAAC,UAAU,SAAS,SAAS,EAAE;AAAA,EAC7D,EAAE,MAAM,WAAW,cAAc,CAAC,WAAW,OAAO,UAAU,EAAE;AAAA,EAChE,EAAE,MAAM,WAAW,cAAc,CAAC,QAAQ,OAAO,QAAQ,EAAE;AAAA,EAC3D,EAAE,MAAM,aAAa,cAAc,CAAC,WAAW,YAAY,WAAW,EAAE;AAC1E;AAgBA,SAAS,sBAAsB,OAAiB,cAA2E;AACzH,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,cAAwC;AAAA,EACnD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAOA,gBAAe,WAAW,CAAC,EAAE;AAAA,EAChD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAgD;AAC/E,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQA;AACtD,UAAM,UAAU,IAAI,KAAK,QAAQ,aAAa,CAAC,GAAG,IAAI,UAAQ,KAAK,YAAY,CAAC,CAAC;AACjF,UAAM,mBAAmB,oBAAI,IAAsB;AACnD,eAAW,SAAS,OAAO;AACzB,uBAAiB,IAAI,MAAM,KAAK,YAAY,GAAG,KAAK;AAAA,IACtD;AAEA,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AAEnC,UAAM,cAAc,IAAI,MAAM,EAAE,KAAK;AAAA,MACnC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK;AAAA,IACpC,CAAC;AAED,eAAW,SAAS,aAAa;AAC/B,YAAM,aAAa,MAAM,MAAM,YAAY,YAAY;AACvD,UAAI,CAAC,WAAY;AACjB,UAAI,QAAQ,IAAI,UAAU,EAAG;AAE7B,YAAM,WAAW,iBAAiB,IAAI,UAAU;AAChD,UAAI,CAAC,SAAU;AAEf,YAAM,aAAa,sBAAsB,OAAO,IAAI;AACpD,UAAI,CAAC,WAAY;AAEjB,YAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,YAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,sBAAsB,WAAW,IAAI,0CAA0C,SAAS,aAAa,KAAK,MAAM,CAAC;AAAA,QAC1H,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,YAAY;AAAA,MACpB,CAAC;AAAA,IACH;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACrGA,IAAMC,mBAAuD;AAAA,EAC3D,cAAc;AAAA,EACd,gBAAgB;AAClB;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,eAAe,QAAQ,gBAAgBA,iBAAgB;AAC7D,UAAM,iBAAiB,QAAQ,kBAAkBA,iBAAgB;AAEjE,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,UAAU,CAAC,SAAS,MAAO;AAEzC,YAAM,QAAQ,SAAS,MAAM,OAAO,UAAQ,cAAc,KAAK,KAAK,QAAQ,EAAE,CAAC;AAC/E,YAAM,YAAY,MAAM;AAExB,YAAM,cAAc,MAAM,OAAO,CAAC,SAAS;AACzC,cAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,eAAO,KAAK,SAAS,MAAM,KAAK,CAAC,KAAK,SAAS,QAAQ,KAAK,CAAC,KAAK,SAAS,YAAY,KAAK,CAAC,KAAK,SAAS,UAAU;AAAA,MACvH,CAAC,EAAE;AAEH,UAAI,aAAa,gBAAgB,eAAe,eAAgB;AAEhE,YAAM,QAAQ,SAAS,OAAO,SAAS;AACvC,YAAM,SAAS,SAAS,OAAO,UAAU;AACzC,YAAM,MAAM,QAAQ;AAEpB,YAAM,cAAc,UAAU,KAAK;AACnC,YAAM,YAAY,UAAU,MAAM,CAAC;AACnC,UAAI,gBAAgB,UAAa,cAAc,OAAW;AAE1D,YAAM,UAAoB,CAAC;AAC3B,UAAI,YAAY,aAAc,SAAQ,KAAK,GAAG,SAAS,eAAe,YAAY,GAAG;AACrF,UAAI,cAAc,eAAgB,SAAQ,KAAK,GAAG,WAAW,iBAAiB,cAAc,GAAG;AAE/F,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,4BAA4B,QAAQ,KAAK,IAAI,CAAC;AAAA,QACvD,OAAO,EAAE,OAAO,aAAa,KAAK,YAAY,EAAE;AAAA,QAChD,MAAM,mBAAmB;AAAA,QACzB,SAAS;AAAA,UACP,aAAa;AAAA,UACb,OAAO,CAAC;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;ACkBO,IAAM,eAAkC,oBAAI,IAAI;AAAA,EACrD,CAAC,4BAA4B,sBAA8B;AAAA,EAC3D,CAAC,qBAAqB,eAAuB;AAAA,EAC7C,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,0BAA0B,oBAA4B;AAAA,EACvD,CAAC,kCAAkC,2BAAmC;AAAA,EACtE,CAAC,wBAAwB,kBAA0B;AAAA,EACnD,CAAC,mBAAmB,aAAqB;AAAA,EACzC,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,kBAAkB,YAAoB;AAAA,EACvC,CAAC,aAAa,QAAgB;AAAA,EAC9B,CAAC,gBAAgB,UAAkB;AAAA,EACnC,CAAC,4BAA4B,sBAA8B;AAAA,EAC3D,CAAC,iCAAiC,sBAA8B;AAAA,EAChE,CAAC,0BAA0B,oBAA4B;AAAA,EACvD,CAAC,mBAAmB,aAAqB;AAAA,EACzC,CAAC,+BAA+B,wBAAgC;AAAA,EAChE,CAAC,uBAAuB,iBAAyB;AAAA,EACjD,CAAC,oBAAoB,cAAsB;AAAA,EAC3C,CAAC,yBAAyB,kBAA0B;AAAA,EACpD,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,wBAAwB,kBAA0B;AAAA,EACnD,CAAC,kBAAkB,YAAoB;AAAA,EACvC,CAAC,iBAAiB,WAAmB;AAAA,EACrC,CAAC,uBAAuB,kBAA0B;AACpD,CAAC;","names":["DEFAULT_PHRASES","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","DEFAULT_TERMS","getOccurrenceFromMatch","escapeRegex","DEFAULT_OPTIONS","DEFAULT_ALLOWED_ADVERBS","escapeRegex","range","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","nlp","DEFAULT_OPTIONS","nlp","range","escapeRegExp","DEFAULT_VERBS","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-llm-speak.ts","../src/no-meaningless-modifiers.ts","../src/no-non-inclusive-language.ts","../src/no-nominalized-phrases.ts","../src/no-noun-strings.ts","../src/no-n-things.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/no-weak-verbs.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'\nimport { createDoc } from './utils.js'\n\nexport interface LlmSpeakPhrase {\n phrase: string\n alternatives: string[]\n}\n\nexport interface NoLlmSpeakOptions {\n phrases?: LlmSpeakPhrase[]\n allowedPhrases?: string[]\n}\n\nconst DEFAULT_PHRASES: LlmSpeakPhrase[] = [\n { phrase: 'delve into', alternatives: ['explore', 'examine'] },\n { phrase: \"it's important to note\", alternatives: ['note', 'remember'] },\n { phrase: \"it's worth noting\", alternatives: ['note'] },\n { phrase: 'in the ever-evolving landscape', alternatives: ['today', 'now'] },\n { phrase: \"in today's digital age\", alternatives: ['today'] },\n { phrase: 'a myriad of', alternatives: ['many'] },\n { phrase: 'a wealth of', alternatives: ['rich', 'deep'] },\n { phrase: 'in the realm of', alternatives: ['in'] },\n { phrase: 'at the forefront of', alternatives: ['leading'] },\n { phrase: 'navigate the complexities of', alternatives: ['handle'] },\n { phrase: 'sheds light on', alternatives: ['explains', 'clarifies'] },\n { phrase: 'underscores', alternatives: ['shows', 'emphasizes'] },\n { phrase: 'furthermore', alternatives: ['also'] },\n { phrase: 'moreover', alternatives: ['also'] },\n { phrase: 'in conclusion', alternatives: ['so', 'finally'] },\n { phrase: 'it is interesting to note', alternatives: ['note'] },\n { phrase: 'it should be noted', alternatives: ['note'] },\n { phrase: 'it is worth noting', alternatives: ['note'] },\n { phrase: 'as mentioned earlier', alternatives: [] },\n { phrase: 'as noted above', alternatives: [] },\n { phrase: 'going forward', alternatives: ['next', 'later'] },\n { phrase: 'moving forward', alternatives: ['next'] },\n { phrase: 'tapestry', alternatives: ['pattern', 'mix'] },\n { phrase: 'multifaceted', alternatives: ['complex', 'many-sided'] },\n { phrase: 'intricate', alternatives: ['complex', 'detailed'] },\n { phrase: 'meticulous', alternatives: ['careful', 'precise'] },\n { phrase: 'pivotal', alternatives: ['key', 'critical'] },\n { phrase: 'crucial', alternatives: ['critical', 'key'] },\n { phrase: 'robust', alternatives: ['strong', 'resilient'] },\n]\n\ninterface JsonTerm {\n text?: string\n normal?: 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 getOccurrenceFromMatch(\n entry: JsonMatch,\n originalText: string\n): { text: string; start: number; end: number } | 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 noLlmSpeak: Rule<NoLlmSpeakOptions> = {\n id: 'no-llm-speak',\n description: 'Flag phrases commonly overused by LLMs and suggest concrete alternatives',\n defaults: { phrases: DEFAULT_PHRASES, allowedPhrases: [] },\n help: 'LLM-speak uses padded, abstract phrases that sound authoritative without saying much. Replace them with direct verbs, concrete nouns, or delete them entirely.',\n\n check({ text, sourceMap, options }: RuleInput<NoLlmSpeakOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES\n const allowed = new Set((options.allowedPhrases ?? []).map(phrase => phrase.toLowerCase()))\n const doc = createDoc(text)\n\n for (const { phrase, alternatives } of phrases) {\n if (allowed.has(phrase.toLowerCase())) continue\n\n const matches = doc.match(phrase)\n const json = matches.json({\n offset: true,\n text: true,\n terms: { offset: true, text: true },\n }) as JsonMatch[]\n\n for (const entry of json) {\n const occurrence = getOccurrenceFromMatch(entry, text)\n if (!occurrence) continue\n\n const start = sourceMap[occurrence.start]\n const end = sourceMap[occurrence.end - 1]\n if (start === undefined || end === undefined) continue\n\n const suggestion = alternatives.length\n ? `try \"${alternatives.join('\", \"')}\"`\n : 'remove this filler phrase'\n\n diagnostics.push({\n ruleId: 'no-llm-speak',\n severity: 'warn',\n message: `replace LLM-speak phrase \"${occurrence.text}\" — ${suggestion}`,\n range: { start, end: end + 1 },\n help: noLlmSpeak.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 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, getOccurrenceRange } from './utils.js'\nimport type { JsonOffsetTerm } from './types.js'\n\nexport interface NoNounStringsOptions {\n /** Maximum consecutive nouns allowed before flagging. */\n maxConsecutiveNouns?: number\n /** Phrases to allow and skip. */\n allowedPhrases?: string[]\n}\n\nconst DEFAULT_OPTIONS: Required<NoNounStringsOptions> = {\n maxConsecutiveNouns: 3,\n allowedPhrases: [],\n}\n\ninterface TermEntry {\n text: string\n start: number\n end: number\n tags: string[]\n}\n\nfunction isCountableNoun(entry: TermEntry): boolean {\n return entry.tags.includes('Noun') && !entry.tags.includes('ProperNoun')\n}\n\nfunction normalizePhrase(phrase: string): string {\n return phrase.toLowerCase().replace(/\\s+/g, ' ').trim()\n}\n\nexport const noNounStrings: Rule<NoNounStringsOptions> = {\n id: 'no-noun-strings',\n description: 'Flag dense strings of consecutive nouns',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'Three or more nouns in a row create dense, hard-to-read compounds. Rewrite them with prepositions or verbs.',\n\n check({ text, sourceMap, options }: RuleInput<NoNounStringsOptions>): Diagnostic[] {\n const maxConsecutiveNouns = options.maxConsecutiveNouns ?? DEFAULT_OPTIONS.maxConsecutiveNouns\n const allowed = new Set((options.allowedPhrases ?? []).map(normalizePhrase))\n\n if (maxConsecutiveNouns <= 1 || text.length === 0) return []\n\n const doc = createDoc(text)\n const rawTerms = doc.terms().json({\n offset: true,\n terms: { offset: true, text: true, tags: true },\n }) as Array<{\n text?: string\n offset?: { start?: number; length?: number }\n terms?: JsonOffsetTerm[]\n }>\n\n const terms: TermEntry[] = []\n for (const entry of rawTerms) {\n const inner = entry.terms?.[0]\n if (!inner) continue\n const offset = inner.offset ?? entry.offset\n const start = offset?.start\n const length = offset?.length\n if (typeof start !== 'number' || typeof length !== 'number' || length <= 0) continue\n\n terms.push({\n text: inner.text ?? entry.text ?? text.slice(start, start + length),\n start,\n end: start + length,\n tags: inner.tags ?? [],\n })\n }\n\n const diagnostics: Diagnostic[] = []\n let runStart = -1\n let runEnd = -1\n const runWords: string[] = []\n\n function flushRun(): void {\n if (runWords.length < maxConsecutiveNouns) return\n\n const phrase = runWords.join(' ')\n if (allowed.has(normalizePhrase(phrase))) return\n\n const range = getOccurrenceRange(sourceMap, { start: runStart, end: runEnd, text: phrase })\n if (!range) return\n\n diagnostics.push({\n ruleId: 'no-noun-strings',\n severity: 'warn',\n message: `noun string \"${phrase}\" is dense — rewrite with verbs or prepositions`,\n range,\n help: noNounStrings.help,\n })\n }\n\n for (const term of terms) {\n if (isCountableNoun(term)) {\n if (runWords.length === 0) {\n runStart = term.start\n }\n runEnd = term.end\n runWords.push(term.text)\n } else {\n flushRun()\n runWords.length = 0\n runStart = -1\n runEnd = -1\n }\n }\n\n flushRun()\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 } from './utils.js'\n\nexport interface NoNThingsOptions {\n /** Minimum cardinal count before the frame is flagged. Default 2, which leaves \"one thing\" idioms alone. */\n minCount?: number\n /** Placeholder nouns that complete the \"N ___\" frame. */\n nouns?: string[]\n /** Phrases to ignore entirely. */\n allowedPhrases?: string[]\n}\n\nconst DEFAULT_OPTIONS: Required<NoNThingsOptions> = {\n minCount: 2,\n nouns: ['thing', 'things'],\n allowedPhrases: [],\n}\n\ninterface JsonTerm {\n text?: string\n offset?: { start?: number; length?: number }\n tags?: string[]\n}\n\ninterface JsonMatch {\n text?: string\n terms?: JsonTerm[]\n}\n\nfunction normalizePhrase(phrase: string): string {\n return phrase.toLowerCase().replace(/\\s+/g, ' ').trim()\n}\n\nfunction escapeMatchWord(word: string): string {\n return word.replace(/[^\\w-]/g, '')\n}\n\n/**\n * \"Three things.\" / \"5 things to know\" borrows its number for rhythm, not\n * content — the count-noun cousin of the rule of three, and a widely\n * documented AI-writing tell.\n */\nexport const noNThings: Rule<NoNThingsOptions> = {\n id: 'no-n-things',\n description: 'Flag the enumerated \"N things\" frame common in AI-generated copy',\n defaults: { ...DEFAULT_OPTIONS },\n help: 'A count plus a placeholder noun (\"three things\", \"5 things to know\") spends its number on rhythm instead of meaning. ' +\n 'Name the items in the heading, or drop the count and state the claim.',\n\n check({ text, sourceMap, options }: RuleInput<NoNThingsOptions>): Diagnostic[] {\n const diagnostics: Diagnostic[] = []\n const minCount = options.minCount ?? DEFAULT_OPTIONS.minCount\n const allowed = new Set((options.allowedPhrases ?? []).map(normalizePhrase))\n const nouns = (options.nouns?.length ? options.nouns : DEFAULT_OPTIONS.nouns)\n .map(escapeMatchWord)\n .filter(Boolean)\n if (!nouns.length || text.length === 0) return []\n\n const doc = createDoc(text)\n const matches = doc\n .match(`#Value+ (${nouns.join('|')})`)\n .json({ offset: true, text: true, terms: { offset: true, text: true, tags: true } }) as JsonMatch[]\n\n for (const entry of matches) {\n const terms = entry.terms ?? []\n if (terms.length < 2) continue\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 continue\n }\n\n const start = firstOffset.start\n const end = lastOffset.start + lastOffset.length\n if (end <= start) continue\n\n const matchedText = text.slice(start, end)\n if (allowed.has(normalizePhrase(matchedText))) continue\n\n // Only a cardinal count frames the trope. Ordinals (\"first things\n // first\") and vague values (\"a few things\") parse to non-numbers here.\n const countOut = doc.match(matchedText).values?.().toNumber().out('array') ?? []\n const countText = countOut[0]\n const count = typeof countText === 'string' ? Number(countText) : Number.NaN\n if (!Number.isFinite(count) || count < minCount) continue\n\n const sourceStart = sourceMap[start]\n const sourceEnd = sourceMap[end - 1]\n if (sourceStart === undefined || sourceEnd === undefined) continue\n\n diagnostics.push({\n ruleId: 'no-n-things',\n severity: 'warn',\n message: `rewrite \"${matchedText}\" with the concrete subject — a counted placeholder is an AI-writing tell`,\n range: { start: sourceStart, end: sourceEnd + 1 },\n help: noNThings.help,\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 } 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 WeakVerb {\n verb: string\n alternatives: string[]\n}\n\nexport interface NoWeakVerbsOptions {\n /** Weak verbs to flag, each with suggested stronger alternatives. */\n verbs?: WeakVerb[]\n /** Verbs or phrases to allow and skip. */\n allowList?: string[]\n}\n\nconst DEFAULT_VERBS: WeakVerb[] = [\n { verb: 'make', alternatives: ['create', 'build', 'produce'] },\n { verb: 'perform', alternatives: ['execute', 'run', 'complete'] },\n { verb: 'conduct', alternatives: ['lead', 'run', 'manage'] },\n { verb: 'carry out', alternatives: ['execute', 'complete', 'implement'] },\n]\n\ninterface JsonTerm {\n text?: string\n offset?: { start?: number; length?: number }\n}\n\ninterface JsonVerb {\n text?: string\n terms?: JsonTerm[]\n offset?: { start?: number; length?: number }\n verb?: {\n infinitive?: string\n }\n}\n\nfunction getOccurrenceFromVerb(entry: JsonVerb, originalText: string): { text: string; start: number; end: number } | 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 noWeakVerbs: Rule<NoWeakVerbsOptions> = {\n id: 'no-weak-verbs',\n description: 'Flag vague action verbs and suggest stronger alternatives',\n defaults: { verbs: DEFAULT_VERBS, allowList: [] },\n help: 'Weak verbs like \"make\" and \"do\" hide the real action. Replace them with specific verbs that tell the reader exactly what happens.',\n\n check({ text, sourceMap, options }: RuleInput<NoWeakVerbsOptions>): Diagnostic[] {\n const verbs = options.verbs?.length ? options.verbs : DEFAULT_VERBS\n const allowed = new Set((options.allowList ?? []).map(verb => verb.toLowerCase()))\n const weakByInfinitive = new Map<string, WeakVerb>()\n for (const entry of verbs) {\n weakByInfinitive.set(entry.verb.toLowerCase(), entry)\n }\n\n const doc = createDoc(text)\n const diagnostics: Diagnostic[] = []\n\n const verbMatches = doc.verbs().json({\n offset: true,\n text: true,\n terms: { offset: true, text: true },\n }) as JsonVerb[]\n\n for (const entry of verbMatches) {\n const infinitive = entry.verb?.infinitive?.toLowerCase()\n if (!infinitive) continue\n if (allowed.has(infinitive)) continue\n\n const weakVerb = weakByInfinitive.get(infinitive)\n if (!weakVerb) continue\n\n const occurrence = getOccurrenceFromVerb(entry, text)\n if (!occurrence) 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-weak-verbs',\n severity: 'warn',\n message: `replace weak verb \"${occurrence.text}\" with a stronger alternative such as \"${weakVerb.alternatives.join('\", \"')}\"`,\n range: { start, end: end + 1 },\n help: noWeakVerbs.help,\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 } 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 { noLlmSpeak } from './no-llm-speak.js'\nimport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nimport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noNounStrings } from './no-noun-strings.js'\nimport { noNThings } from './no-n-things.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 { noWeakVerbs } from './no-weak-verbs.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 { noLlmSpeak } from './no-llm-speak.js'\nexport { noMeaninglessModifiers } from './no-meaningless-modifiers.js'\nexport { noNonInclusiveLanguage } from './no-non-inclusive-language.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noNounStrings } from './no-noun-strings.js'\nexport { noNThings } from './no-n-things.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 { noWeakVerbs } from './no-weak-verbs.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 { NoLlmSpeakOptions } from './no-llm-speak.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 { NoNounStringsOptions } from './no-noun-strings.js'\nexport type { NoNThingsOptions } from './no-n-things.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 { NoWeakVerbsOptions } from './no-weak-verbs.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-llm-speak', noLlmSpeak 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-noun-strings', noNounStrings as Rule],\n ['no-n-things', noNThings 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 ['no-weak-verbs', noWeakVerbs as Rule],\n ['sentence-complexity', sentenceComplexity as Rule],\n])\n"],"mappings":";AAOA,IAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,aAAa,QAA4B;AAChD,SAAO,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;AACrE;AAEA,SAAS,aAAa,cAAwB,WAA6B;AACzE,QAAM,qBAAqB,aAAa,YAAY,EACjD,IAAI,YAAU,aAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,EAC3D,KAAK,GAAG;AACX,QAAM,kBAAkB,aAAa,SAAS,EAC3C,IAAI,YAAU,aAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,EAC3D,KAAK,GAAG;AAEX,SAAO,IAAI;AAAA,IACT,OAAO,kBAAkB,YAAY,eAAe;AAAA,IACpD;AAAA,EACF;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAAA,EACA,MAAM;AAAA,EAIN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,cAA4B,CAAC;AACnC,UAAM,eAAe,QAAQ,cAAc,SAAS,QAAQ,eAAe;AAC3E,UAAM,YAAY,QAAQ,WAAW,SAAS,QAAQ,YAAY;AAElE,QAAI,CAAC,aAAa,UAAU,CAAC,UAAU,OAAQ,QAAO;AAEtD,UAAM,KAAK,aAAa,cAAc,SAAS;AAC/C,QAAI;AAEJ,YAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,YAAM,cAAc,MAAM,CAAC;AAC3B,YAAM,cAAc,MAAM,CAAC;AAC3B,YAAM,WAAW,MAAM,CAAC;AACxB,YAAM,aAAa,MAAM;AACzB,YAAM,WAAW,aAAa,YAAY;AAE1C,YAAM,QAAQ,UAAU,UAAU;AAClC,YAAM,MAAM,UAAU,WAAW,CAAC;AAClC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,YAAM,UAAsB;AAAA,QAC1B,aAAa,WAAW,WAAW,aAAa,QAAQ;AAAA,QACxD,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE,GAAG,aAAa,SAAS,CAAC;AAAA,MACnE;AAEA,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,IAAI,WAAW,0BAAqB,QAAQ;AAAA,QACrD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,uBAAuB;AAAA,QAC7B;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACjHA,OAAO,SAAS;AAUT,SAAS,UAAU,MAAuB;AAC/C,SAAO,IAAI,IAAI;AACjB;AAEO,SAAS,oBAAoB,MAAc,SAAuC;AACvF,QAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,QAAQ,KAAK,EAAE,CAAC;AAE/E,SAAO,KAAK,QAAQ,CAAC,UAAU;AAC7B,UAAM,QAAQ,MAAM,QAAQ,SAAS,MAAM,QAAQ,CAAC,GAAG,QAAQ;AAC/D,UAAM,SAAS,MAAM,QAAQ,UAAU,eAAe,MAAM,KAAK;AAEjE,QAAI,OAAO,UAAU,YAAY,OAAO,WAAW,YAAY,UAAU,GAAG;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,WAAO,CAAC;AAAA,MACN,MAAM,MAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,MAAM;AAAA,MACpD;AAAA,MACA,KAAK,QAAQ;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,mBAAmB,WAAqB,YAAyD;AAC/G,QAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,QAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,MAAI,UAAU,UAAa,QAAQ,OAAW,QAAO;AAErD,SAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAC/B;AAEA,SAAS,eAAe,OAAyD;AAC/E,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,KAAK,QAAQ;AAC5B,QAAI,OAAO,WAAW,SAAU,QAAO;AACvC,aAAS;AAAA,EACX;AAEA,SAAO;AACT;;;AC1CA,IAAM,sBAAsB;AAC5B,IAAM,0BAA0B,CAAC,MAAM;AAEhC,IAAM,kBAAgD;AAAA,EAC3D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,YAAY,qBAAqB,gBAAgB,wBAAwB;AAAA,EACrF,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAoD;AACnF,UAAM,aAAa,QAAQ,cAAc;AACzC,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,yBAAyB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAE7G,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,MAAO;AAErB,YAAM,cAAc,SAAS,MAC1B,OAAO,CAAC,SAAS,KAAK,MAAM,SAAS,QAAQ,CAAC,EAC9C,IAAI,CAAC,SAAS;AACb,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,SAAS,KAAK,QAAQ;AAC5B,eAAO;AAAA,UACL,MAAM,KAAK,MAAM,QAAQ,eAAe,EAAE,KAAK;AAAA,UAC/C,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,UAC3C,KAAK,OAAO,UAAU,YAAY,OAAO,WAAW,WAAW,QAAQ,SAAS;AAAA,QAClF;AAAA,MACF,CAAC,EACA,OAAO,CAAC,eAAe,OAAO,KAAK,WAAW,IAAI,CAAC,EACnD,OAAO,CAAC,eAAe,CAAC,QAAQ,IAAI,WAAW,KAAK,YAAY,CAAC,CAAC,EAClE,KAAK,CAAC,MAAM,UAAU,KAAK,QAAQ,MAAM,KAAK;AAEjD,eAAS,QAAQ,YAAY,QAAQ,YAAY,QAAQ,SAAS,GAAG;AACnE,cAAM,aAAa,YAAY,KAAK;AACpC,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2BAA2B,WAAW,IAAI,0BAA0B,UAAU,cAAc,eAAe,IAAI,KAAK,GAAG;AAAA,UAChI;AAAA,UACA,MAAM,gBAAgB;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACxDA,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAO,eAAe,qBAAqB,EAAE;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQ;AACtD,UAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAI,sBAAsB,EAAG,QAAO;AAEpC,eAAW,YAAY,kBAAkB,IAAI,GAAG;AAC9C,YAAM,OAAO,YAAY,SAAS,MAAM,KAAK;AAC7C,UAAI,KAAK,UAAU,oBAAqB;AAExC,YAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,EAAG;AAC9C,YAAM,YAAY,SAAS,QAAQ,KAAK,KAAK,SAAS,CAAC,EAAG;AAC1D,YAAM,QAAQ,UAAU,WAAW;AACnC,YAAM,MAAM,UAAU,YAAY,CAAC;AACnC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,YAAM,QAAQ,KAAK,IAAI,SAAO,IAAI,KAAK,YAAY,CAAC,EAAE,KAAK,IAAI;AAC/D,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,2BAA2B,KAAK;AAAA,QACzC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,iBAAiB;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAaA,SAAS,kBAAkB,MAA+B;AACxD,QAAM,SAA0B,CAAC;AACjC,QAAM,KAAK;AACX,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,UAAM,0BAA0B,MAAM,CAAC,EAAE,MAAM,MAAM,IAAI,CAAC,EAAE,UAAU;AACtE,UAAM,WAAW,MAAM,CAAC,EAAE,KAAK;AAC/B,QAAI,CAAC,SAAU;AACf,WAAO,KAAK,EAAE,MAAM,UAAU,OAAO,MAAM,QAAQ,wBAAwB,CAAC;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,YAAY,MAAc,OAA4B;AAC7D,QAAM,cAAc,MAAM,IAAI,WAAW,EAAE,KAAK,GAAG;AACnD,MAAI,CAAC,YAAa,QAAO,CAAC;AAE1B,QAAM,OAAkB,CAAC;AACzB,QAAM,KAAK,IAAI,OAAO,OAAO,WAAW,QAAQ,IAAI;AACpD,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,SAAK,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,OAAO,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,OAAO,CAAC;AAAA,EACtF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC7FA,IAAM,kBAAyD;AAAA,EAC7D,eAAe;AAAA,EACf,cAAc;AAAA,EACd,UAAU;AACZ;AAEO,IAAM,uBAA0D;AAAA,EACrE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAG,gBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAyD;AACxF,UAAM,gBAAgB,QAAQ,iBAAiB,gBAAgB;AAC/D,UAAM,eAAe,QAAQ,gBAAgB,gBAAgB;AAC7D,UAAM,WAAW,QAAQ,YAAY,gBAAgB;AAErD,UAAM,YAAY,aAAa,IAAI;AACnC,UAAM,QAAQ,SAAS,IAAI;AAE3B,QAAI,UAAU,SAAS,gBAAgB,MAAM,SAAS,UAAU;AAC9D,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,YAAY,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,eAAe,IAAI,GAAG,CAAC;AAC3E,UAAM,QAAQ,mBAAmB,MAAM,QAAQ,UAAU,QAAQ,SAAS;AAE1E,QAAI,SAAS,eAAe;AAC1B,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAAQ,UAAU,CAAC;AACzB,UAAM,MAAM,UAAU,UAAU,SAAS,CAAC;AAC1C,QAAI,UAAU,UAAa,QAAQ,OAAW,QAAO,CAAC;AAEtD,WAAO,CAAC;AAAA,MACN,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,wBAAwB,MAAM,QAAQ,CAAC,CAAC,uBAAkB,aAAa;AAAA,MAChF,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,MAC7B,MAAM,qBAAqB;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;AAEA,SAAS,aAAa,MAAwB;AAE5C,QAAM,QAAQ,KAAK,MAAM,UAAU;AACnC,QAAM,YAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,WAAW,MAAM,CAAC;AACxB,UAAM,aAAa,MAAM,IAAI,CAAC,KAAK;AACnC,UAAM,YAAY,YAAY,MAAM;AACpC,UAAM,UAAU,SAAS,KAAK;AAC9B,QAAI,QAAS,WAAU,KAAK,OAAO;AAAA,EACrC;AACA,SAAO;AACT;AAEA,SAAS,SAAS,MAAwB;AACxC,SAAO,KACJ,YAAY,EACZ,QAAQ,kBAAkB,GAAG,EAC7B,MAAM,KAAK,EACX,OAAO,UAAQ,KAAK,SAAS,KAAK,WAAW,KAAK,IAAI,CAAC;AAC5D;AAEA,SAAS,eAAe,MAAsB;AAC5C,QAAM,UAAU,KAAK,YAAY,EAAE,QAAQ,WAAW,EAAE;AACxD,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,UAAU,EAAG,QAAO;AAEhC,QAAM,SAAS,QAAQ,MAAM,YAAY;AACzC,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,QAAQ,OAAO;AACnB,MAAI,QAAQ,SAAS,GAAG,EAAG;AAC3B,MAAI,QAAQ,SAAS,IAAI,KAAK,QAAQ,SAAS,KAAK,CAAC,YAAY,KAAK,QAAQ,QAAQ,SAAS,CAAC,KAAK,EAAE,GAAG;AACxG;AAAA,EACF;AACA,SAAO,KAAK,IAAI,GAAG,KAAK;AAC1B;AAEA,SAAS,mBAAmB,OAAe,WAAmB,WAA2B;AACvF,MAAI,cAAc,KAAK,UAAU,EAAG,QAAO;AAC3C,SAAO,QAAQ,QAAQ,aAAa,QAAQ,YAAY,SAAS;AACnE;;;AC1FA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAAS,gBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAU;AAC5D,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAI,CAAC,eAAe,MAAM,WAAW,KAAK,EAAG;AAE7C,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2CAA2C,WAAW,KAAK,YAAY,CAAC;AAAA,UACjF;AAAA,UACA,MAAM,mBAAmB;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAAS,eAAe,MAAc,OAAwB;AAC5D,MAAI,QAAQ,QAAQ;AACpB,SAAO,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,CAAE,EAAG;AAC9C,SAAO,QAAQ,KAAK,QAAQ,KAAK,KAAK,KAAK,CAAE;AAC/C;;;AC7CA,IAAMA,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,gBAA4C;AAAA,EACvD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAkD;AACjF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAC5D,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,cAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,cAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,WAAW,WAAW,KAAK,YAAY,CAAC;AAAA,UACjD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,cAAc;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACjCA,IAAM,WAAsB;AAAA,EAC1B;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,SAAS;AAAA,EACX;AACF;AAEO,IAAM,8BAAwE;AAAA,EACnF,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,gBAAgB,CAAC,EAAE;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAgE;AAC/F,UAAM,cAA4B,CAAC;AACnC,UAAM,iBAAiB,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,WAAS,UAAU,KAAK,CAAC,CAAC;AAE5F,eAAW,WAAW,UAAU;AAC9B,YAAM,KAAK,IAAI,OAAO,QAAQ,GAAG,QAAQ,QAAQ,GAAG,KAAK;AACzD,UAAI;AACJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,SAAS,MAAM,CAAC;AACtB,YAAI,eAAe,IAAI,UAAU,MAAM,CAAC,EAAG;AAE3C,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,OAAO,SAAS,CAAC;AACrD,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,GAAG,QAAQ,OAAO,MAAM,MAAM;AAAA,UACvC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,4BAA4B;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,MAAM,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACvD;;;ACzDA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,2BAA2B,aAAa;AAAA,UACjD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC5CA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,eAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,QAAQ,eAAe;AAAA,EACnC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAiD;AAChF,UAAM,cAA4B,CAAC;AACnC,UAAM,SAAS,QAAQ,QAAQ,SAAS,QAAQ,SAAS;AAEzD,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,KAAK,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACnF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,SAAS,MAAM,CAAC;AACtB,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,OAAO,SAAS,CAAC;AACrD,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,iBAAiB,OAAO,YAAY,CAAC;AAAA,UAC9C,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,aAAa;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACjDA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,WAAkC;AAAA,EAC7C,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA6C;AAC5E,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,0BAA0B,aAAa;AAAA,UAChD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,SAAS;AAAA,QACjB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACzCA,IAAMC,mBAAoC;AAAA,EACxC,EAAE,QAAQ,cAAc,cAAc,CAAC,WAAW,SAAS,EAAE;AAAA,EAC7D,EAAE,QAAQ,0BAA0B,cAAc,CAAC,QAAQ,UAAU,EAAE;AAAA,EACvE,EAAE,QAAQ,qBAAqB,cAAc,CAAC,MAAM,EAAE;AAAA,EACtD,EAAE,QAAQ,kCAAkC,cAAc,CAAC,SAAS,KAAK,EAAE;AAAA,EAC3E,EAAE,QAAQ,0BAA0B,cAAc,CAAC,OAAO,EAAE;AAAA,EAC5D,EAAE,QAAQ,eAAe,cAAc,CAAC,MAAM,EAAE;AAAA,EAChD,EAAE,QAAQ,eAAe,cAAc,CAAC,QAAQ,MAAM,EAAE;AAAA,EACxD,EAAE,QAAQ,mBAAmB,cAAc,CAAC,IAAI,EAAE;AAAA,EAClD,EAAE,QAAQ,uBAAuB,cAAc,CAAC,SAAS,EAAE;AAAA,EAC3D,EAAE,QAAQ,gCAAgC,cAAc,CAAC,QAAQ,EAAE;AAAA,EACnE,EAAE,QAAQ,kBAAkB,cAAc,CAAC,YAAY,WAAW,EAAE;AAAA,EACpE,EAAE,QAAQ,eAAe,cAAc,CAAC,SAAS,YAAY,EAAE;AAAA,EAC/D,EAAE,QAAQ,eAAe,cAAc,CAAC,MAAM,EAAE;AAAA,EAChD,EAAE,QAAQ,YAAY,cAAc,CAAC,MAAM,EAAE;AAAA,EAC7C,EAAE,QAAQ,iBAAiB,cAAc,CAAC,MAAM,SAAS,EAAE;AAAA,EAC3D,EAAE,QAAQ,6BAA6B,cAAc,CAAC,MAAM,EAAE;AAAA,EAC9D,EAAE,QAAQ,sBAAsB,cAAc,CAAC,MAAM,EAAE;AAAA,EACvD,EAAE,QAAQ,sBAAsB,cAAc,CAAC,MAAM,EAAE;AAAA,EACvD,EAAE,QAAQ,wBAAwB,cAAc,CAAC,EAAE;AAAA,EACnD,EAAE,QAAQ,kBAAkB,cAAc,CAAC,EAAE;AAAA,EAC7C,EAAE,QAAQ,iBAAiB,cAAc,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC3D,EAAE,QAAQ,kBAAkB,cAAc,CAAC,MAAM,EAAE;AAAA,EACnD,EAAE,QAAQ,YAAY,cAAc,CAAC,WAAW,KAAK,EAAE;AAAA,EACvD,EAAE,QAAQ,gBAAgB,cAAc,CAAC,WAAW,YAAY,EAAE;AAAA,EAClE,EAAE,QAAQ,aAAa,cAAc,CAAC,WAAW,UAAU,EAAE;AAAA,EAC7D,EAAE,QAAQ,cAAc,cAAc,CAAC,WAAW,SAAS,EAAE;AAAA,EAC7D,EAAE,QAAQ,WAAW,cAAc,CAAC,OAAO,UAAU,EAAE;AAAA,EACvD,EAAE,QAAQ,WAAW,cAAc,CAAC,YAAY,KAAK,EAAE;AAAA,EACvD,EAAE,QAAQ,UAAU,cAAc,CAAC,UAAU,WAAW,EAAE;AAC5D;AAcA,SAAS,uBACP,OACA,cACqD;AACrD,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,aAAsC;AAAA,EACjD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,kBAAiB,gBAAgB,CAAC,EAAE;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA+C;AAC9E,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAC5D,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,YAAU,OAAO,YAAY,CAAC,CAAC;AAC1F,UAAM,MAAM,UAAU,IAAI;AAE1B,eAAW,EAAE,QAAQ,aAAa,KAAK,SAAS;AAC9C,UAAI,QAAQ,IAAI,OAAO,YAAY,CAAC,EAAG;AAEvC,YAAM,UAAU,IAAI,MAAM,MAAM;AAChC,YAAM,OAAO,QAAQ,KAAK;AAAA,QACxB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK;AAAA,MACpC,CAAC;AAED,iBAAW,SAAS,MAAM;AACxB,cAAM,aAAa,uBAAuB,OAAO,IAAI;AACrD,YAAI,CAAC,WAAY;AAEjB,cAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,cAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,cAAM,aAAa,aAAa,SAC5B,QAAQ,aAAa,KAAK,MAAM,CAAC,MACjC;AAEJ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,WAAW,IAAI,YAAO,UAAU;AAAA,UACtE,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,WAAW;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC9HA,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,WAAW,kBAAkB;AAAA,EACzC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,cAA4B,CAAC;AACnC,UAAM,YAAY,QAAQ,WAAW,SAAS,QAAQ,YAAY;AAElE,eAAW,YAAY,WAAW;AAChC,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,QAAQ,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACtF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,kBAAkB,MAAM,CAAC;AAC/B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,gBAAgB,SAAS,CAAC;AAC9D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,gCAAgC,eAAe;AAAA,UACxD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,uBAAuB;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACtCA,IAAMC,iBAAoC;AAAA,EACxC,EAAE,MAAM,QAAQ,cAAc,CAAC,YAAY,QAAQ,OAAO,EAAE;AAAA,EAC5D,EAAE,MAAM,YAAY,cAAc,CAAC,aAAa,YAAY,WAAW,EAAE;AAAA,EACzE,EAAE,MAAM,aAAa,cAAc,CAAC,WAAW,EAAE;AAAA,EACjD,EAAE,MAAM,aAAa,cAAc,CAAC,YAAY,WAAW,EAAE;AAAA,EAC7D,EAAE,MAAM,UAAU,cAAc,CAAC,WAAW,QAAQ,QAAQ,EAAE;AAAA,EAC9D,EAAE,MAAM,SAAS,cAAc,CAAC,aAAa,WAAW,UAAU,EAAE;AAAA,EACpE,EAAE,MAAM,SAAS,cAAc,CAAC,cAAc,WAAW,SAAS,EAAE;AAAA,EACpE,EAAE,MAAM,UAAU,cAAc,CAAC,WAAW,gBAAgB,YAAY,EAAE;AAAA,EAC1E,EAAE,MAAM,QAAQ,cAAc,CAAC,aAAa,QAAQ,YAAY,EAAE;AAAA,EAClE,EAAE,MAAM,QAAQ,cAAc,CAAC,gBAAgB,cAAc,MAAM,EAAE;AAAA,EACrE,EAAE,MAAM,gBAAgB,cAAc,CAAC,eAAe,oBAAoB,cAAc,GAAG,OAAO,KAAK;AAAA,EACvG,EAAE,MAAM,cAAc,cAAc,CAAC,gBAAgB,OAAO,WAAW,GAAG,OAAO,KAAK;AAAA,EACtF,EAAE,MAAM,iBAAiB,cAAc,CAAC,iBAAiB,UAAU,EAAE;AAAA,EACrE,EAAE,MAAM,WAAW,cAAc,CAAC,YAAY,aAAa,QAAQ,EAAE;AACvE;AAGA,IAAM,uBAAuB,oBAAI,IAAI,CAAC,UAAU,OAAO,CAAC;AAexD,SAAS,eAAe,SAAyB;AAC/C,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAwB,OAA4B;AAC9E,MAAI,CAAC,qBAAqB,IAAI,KAAK,KAAK,YAAY,CAAC,EAAG,QAAO;AAC/D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,MAAM,CAAC,EAAG,QAAQ,CAAC;AAChC,QAAM,SAAS,KAAK,SAAS,MAAM;AACnC,QAAM,oBAAoB,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,WAAW;AAE5E,SAAO,CAAC,UAAU;AACpB;AAEA,SAASC,wBAAuB,OAAkB,cAA8C;AAC9F,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,yBAA8D;AAAA,EACzE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAOD,gBAAe,cAAc,CAAC,EAAE;AAAA,EACnD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA2D;AAC1F,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQA;AACtD,UAAM,UAAU,IAAI,KAAK,QAAQ,gBAAgB,CAAC,GAAG,IAAI,UAAQ,KAAK,YAAY,CAAC,CAAC;AAEpF,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AAEnC,eAAW,QAAQ,OAAO;AACxB,UAAI,QAAQ,IAAI,KAAK,KAAK,YAAY,CAAC,EAAG;AAE1C,YAAM,UAAU,eAAe,KAAK,IAAI;AACxC,YAAM,UAAU,IAAI,MAAM,OAAO;AACjC,YAAM,OAAO,QAAQ,KAAK,EAAE,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,CAAC;AAEvG,iBAAW,SAAS,MAAM;AACxB,YAAI,CAAC,mBAAmB,MAAM,MAAM,SAAS,CAAC,CAAC,EAAG;AAElD,cAAM,aAAaC,wBAAuB,OAAO,IAAI;AACrD,YAAI,CAAC,WAAY;AAEjB,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,cAAM,aAAa,KAAK,aAAa,KAAK,IAAI;AAC9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,YAAY,WAAW,IAAI,yCAAyC,UAAU;AAAA,UACvF;AAAA,UACA,MAAM,uBAAuB;AAAA,QAC/B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC5HA,IAAM,mBAAmB,CAAC,QAAQ,QAAQ,QAAQ,QAAQ,QAAQ,KAAK;AACvE,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAA0D;AAAA,EACrE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,UAAU,kBAAkB,cAAc,sBAAsB;AAAA,EAC5E,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAyD;AACxF,UAAM,cAA4B,CAAC;AACnC,UAAM,WAAW,QAAQ,UAAU,SAAS,QAAQ,WAAW;AAC/D,UAAM,eAAe,IAAI,KAAK,QAAQ,cAAc,SAAS,QAAQ,eAAe,uBAAuB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC5I,UAAM,gBAAgB,SAAS,IAAIC,YAAW,EAAE,KAAK,GAAG;AACxD,QAAI,CAAC,cAAe,QAAO;AAE3B,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,WAAW,aAAa,SAAS;AAE3D,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,iBAAiB,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,CAAC,GAAG,YAAY;AAC3E,UAAI,CAAC,kBAAkB,aAAa,IAAI,cAAc,EAAG;AAEzD,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,YAAY,WAAW,IAAI;AAAA,QACpC;AAAA,QACA,MAAM,qBAAqB;AAAA,MAC7B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,aAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACjDA,IAAMC,mBAAkD;AAAA,EACtD,qBAAqB;AAAA,EACrB,gBAAgB,CAAC;AACnB;AASA,SAAS,gBAAgB,OAA2B;AAClD,SAAO,MAAM,KAAK,SAAS,MAAM,KAAK,CAAC,MAAM,KAAK,SAAS,YAAY;AACzE;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACxD;AAEO,IAAM,gBAA4C;AAAA,EACvD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAkD;AACjF,UAAM,sBAAsB,QAAQ,uBAAuBA,iBAAgB;AAC3E,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,eAAe,CAAC;AAE3E,QAAI,uBAAuB,KAAK,KAAK,WAAW,EAAG,QAAO,CAAC;AAE3D,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,WAAW,IAAI,MAAM,EAAE,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,OAAO,EAAE,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAChD,CAAC;AAMD,UAAM,QAAqB,CAAC;AAC5B,eAAW,SAAS,UAAU;AAC5B,YAAM,QAAQ,MAAM,QAAQ,CAAC;AAC7B,UAAI,CAAC,MAAO;AACZ,YAAM,SAAS,MAAM,UAAU,MAAM;AACrC,YAAM,QAAQ,QAAQ;AACtB,YAAM,SAAS,QAAQ;AACvB,UAAI,OAAO,UAAU,YAAY,OAAO,WAAW,YAAY,UAAU,EAAG;AAE5E,YAAM,KAAK;AAAA,QACT,MAAM,MAAM,QAAQ,MAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,MAAM;AAAA,QAClE;AAAA,QACA,KAAK,QAAQ;AAAA,QACb,MAAM,MAAM,QAAQ,CAAC;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,UAAM,cAA4B,CAAC;AACnC,QAAI,WAAW;AACf,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,aAAS,WAAiB;AACxB,UAAI,SAAS,SAAS,oBAAqB;AAE3C,YAAM,SAAS,SAAS,KAAK,GAAG;AAChC,UAAI,QAAQ,IAAI,gBAAgB,MAAM,CAAC,EAAG;AAE1C,YAAM,QAAQ,mBAAmB,WAAW,EAAE,OAAO,UAAU,KAAK,QAAQ,MAAM,OAAO,CAAC;AAC1F,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,gBAAgB,MAAM;AAAA,QAC/B;AAAA,QACA,MAAM,cAAc;AAAA,MACtB,CAAC;AAAA,IACH;AAEA,eAAW,QAAQ,OAAO;AACxB,UAAI,gBAAgB,IAAI,GAAG;AACzB,YAAI,SAAS,WAAW,GAAG;AACzB,qBAAW,KAAK;AAAA,QAClB;AACA,iBAAS,KAAK;AACd,iBAAS,KAAK,KAAK,IAAI;AAAA,MACzB,OAAO;AACL,iBAAS;AACT,iBAAS,SAAS;AAClB,mBAAW;AACX,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,aAAS;AAET,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACpGA,IAAMC,mBAA8C;AAAA,EAClD,UAAU;AAAA,EACV,OAAO,CAAC,SAAS,QAAQ;AAAA,EACzB,gBAAgB,CAAC;AACnB;AAaA,SAASC,iBAAgB,QAAwB;AAC/C,SAAO,OAAO,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACxD;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO,KAAK,QAAQ,WAAW,EAAE;AACnC;AAOO,IAAM,YAAoC;AAAA,EAC/C,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGD,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAGN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA8C;AAC7E,UAAM,cAA4B,CAAC;AACnC,UAAM,WAAW,QAAQ,YAAYA,iBAAgB;AACrD,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAIC,gBAAe,CAAC;AAC3E,UAAM,SAAS,QAAQ,OAAO,SAAS,QAAQ,QAAQD,iBAAgB,OACpE,IAAI,eAAe,EACnB,OAAO,OAAO;AACjB,QAAI,CAAC,MAAM,UAAU,KAAK,WAAW,EAAG,QAAO,CAAC;AAEhD,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IACb,MAAM,YAAY,MAAM,KAAK,GAAG,CAAC,GAAG,EACpC,KAAK,EAAE,QAAQ,MAAM,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,CAAC;AAErF,eAAW,SAAS,SAAS;AAC3B,YAAM,QAAQ,MAAM,SAAS,CAAC;AAC9B,UAAI,MAAM,SAAS,EAAG;AAEtB,YAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,YAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,UACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA;AAAA,MACF;AAEA,YAAM,QAAQ,YAAY;AAC1B,YAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,UAAI,OAAO,MAAO;AAElB,YAAM,cAAc,KAAK,MAAM,OAAO,GAAG;AACzC,UAAI,QAAQ,IAAIC,iBAAgB,WAAW,CAAC,EAAG;AAI/C,YAAM,WAAW,IAAI,MAAM,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,OAAO,KAAK,CAAC;AAC/E,YAAM,YAAY,SAAS,CAAC;AAC5B,YAAM,QAAQ,OAAO,cAAc,WAAW,OAAO,SAAS,IAAI,OAAO;AACzE,UAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,SAAU;AAEjD,YAAM,cAAc,UAAU,KAAK;AACnC,YAAM,YAAY,UAAU,MAAM,CAAC;AACnC,UAAI,gBAAgB,UAAa,cAAc,OAAW;AAE1D,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,YAAY,WAAW;AAAA,QAChC,OAAO,EAAE,OAAO,aAAa,KAAK,YAAY,EAAE;AAAA,QAChD,MAAM,UAAU;AAAA,MAClB,CAAC;AAAA,IACH;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACzFA,IAAM,2BAA2B;AACjC,IAAM,2BAA2B;AACjC,IAAM,4BAA4B;AAElC,IAAM,uBAAuB,CAAC,OAAO,OAAO,MAAM,OAAO,OAAO,IAAI;AACpE,IAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,2BAAkE;AAAA,EAC7E,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW,CAAC;AAAA,EACd;AAAA,EACA,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAA6D;AAC5F,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,kBAAkB,QAAQ,mBAAmB;AACnD,UAAM,mBAAmB,QAAQ,oBAAoB;AAErD,UAAM,gBAAgB,QAAQ,gBAAgB,sBAAsB;AAAA,MAAI,WACtE,MAAM,YAAY,EAAE,KAAK;AAAA,IAC3B;AACA,UAAM,iBAAiB,QAAQ,iBAAiB,uBAAuB;AAAA,MAAI,WACzE,MAAM,YAAY,EAAE,KAAK;AAAA,IAC3B;AACA,UAAM,YAAY,IAAI;AAAA,OACnB,QAAQ,aAAa,CAAC,GAAG,IAAI,WAAS,MAAM,YAAY,EAAE,KAAK,CAAC;AAAA,IACnE;AAEA,UAAM,WAAiC;AAAA,MACrC,GAAG,aAAa,IAAI,aAAW;AAAA,QAC7B;AAAA,QACA,UAAU;AAAA,QACV,OAAO,cAAc,MAAM;AAAA,MAC7B,EAAE;AAAA,MACF,GAAG,cAAc,IAAI,aAAW;AAAA,QAC9B;AAAA,QACA,UAAU;AAAA,QACV,OAAO,cAAc,MAAM;AAAA,MAC7B,EAAE;AAAA,IACJ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,MAAM;AAElD,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,KAAK,EAAE,CAAC;AAEhF,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,UAAU,CAAC,SAAS,MAAO;AAEzC,YAAM,gBAAgB,SAAS,OAAO,SAAS;AAC/C,YAAM,eAAe,SAAS,QAAQ;AACtC,YAAM,gBAAuD,CAAC;AAE9D,UAAI,oBAAoB;AACxB,UAAI,qBAAqB;AAEzB,iBAAW,EAAE,QAAQ,UAAU,MAAM,KAAK,UAAU;AAClD,YAAI,UAAU,IAAI,MAAM,EAAG;AAE3B,YAAI;AACJ,gBAAQ,QAAQ,MAAM,KAAK,YAAY,OAAO,MAAM;AAClD,gBAAM,aAAa,MAAM;AACzB,gBAAM,WAAW,MAAM,QAAQ,MAAM,CAAC,EAAE;AAGxC,cAAI,cAAc,KAAK,WAAS,aAAa,MAAM,OAAO,WAAW,MAAM,KAAK,GAAG;AACjF;AAAA,UACF;AAEA,wBAAc,KAAK,EAAE,OAAO,YAAY,KAAK,SAAS,CAAC;AAEvD,cAAI,aAAa,gBAAgB;AAC/B,iCAAqB;AAAA,UACvB,OAAO;AACL,kCAAsB;AAAA,UACxB;AAAA,QACF;AAAA,MACF;AAEA,YAAM,QAAQ,oBAAoB;AAClC,UACE,SAAS,mBACT,qBAAqB,mBACrB,sBAAsB,kBACtB;AACA;AAAA,MACF;AAEA,YAAM,SAAS,SAAS,OAAO,UAAU;AACzC,YAAM,cAAc,gBAAgB;AAEpC,YAAM,cAAc,UAAU,aAAa;AAC3C,YAAM,YAAY,UAAU,cAAc,CAAC;AAC3C,UAAI,gBAAgB,UAAa,cAAc,OAAW;AAE1D,YAAM,UAAoB,CAAC;AAC3B,UAAI,QAAQ,iBAAiB;AAC3B,gBAAQ,KAAK,GAAG,KAAK,sBAAsB,eAAe,GAAG;AAAA,MAC/D;AACA,UAAI,oBAAoB,iBAAiB;AACvC,gBAAQ,KAAK,GAAG,iBAAiB,sBAAsB,eAAe,GAAG;AAAA,MAC3E;AACA,UAAI,qBAAqB,kBAAkB;AACzC,gBAAQ,KAAK,GAAG,kBAAkB,uBAAuB,gBAAgB,GAAG;AAAA,MAC9E;AAEA,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,+BAA+B,QAAQ,KAAK,IAAI,CAAC;AAAA,QAC1D,OAAO,EAAE,OAAO,aAAa,KAAK,YAAY,EAAE;AAAA,QAChD,MAAM,yBAAyB;AAAA,QAC/B,SAAS;AAAA,UACP,aAAa;AAAA,UACb,OAAO,CAAC;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc,QAAwB;AAC7C,QAAM,UAAU,OAAO,QAAQ,uBAAuB,MAAM;AAC5D,QAAM,SAAS,QAAQ,QAAQ,QAAQ,MAAM;AAC7C,QAAM,SAAS,OAAO,SAAS,MAAM,IAAI,SAAS,MAAM,MAAM;AAC9D,SAAO,IAAI,OAAO,QAAQ,IAAI;AAChC;;;AC/JA,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAC3B,IAAMC,2BAA0B,CAAC,QAAQ,KAAK;AAEvC,IAAM,oBAAoD;AAAA,EAC/D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU;AAAA,IACR,WAAW;AAAA,IACX,WAAW;AAAA,IACX,gBAAgBA;AAAA,EAClB;AAAA,EACA,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAsD;AACrF,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,YAAY,QAAQ,aAAa;AACvC,UAAM,UAAU,IAAI,KAAK,QAAQ,kBAAkBA,0BAAyB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC7G,UAAM,eAAe,QAAQ,UAAU,IAAI,IAAI,QAAQ,QAAQ,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC,IAAI;AAEpG,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAEhF,UAAM,sBAAsB,oBAAI,IAAiE;AAEjG,eAAW,YAAY,WAAW;AAChC,iBAAW,QAAQ,SAAS,SAAS,CAAC,GAAG;AACvC,YAAI,CAAC,KAAK,MAAM,SAAS,QAAQ,EAAG;AAEpC,cAAM,UAAU,KAAK,MAAM,QAAQ,eAAe,EAAE,KAAK;AACzD,cAAM,aAAa,QAAQ,YAAY;AACvC,YAAI,CAAC,cAAc,WAAW,SAAS,UAAW;AAClD,YAAI,QAAQ,IAAI,UAAU,EAAG;AAC7B,YAAI,gBAAgB,CAAC,aAAa,IAAI,UAAU,EAAG;AAEnD,cAAM,QAAQ,KAAK,QAAQ;AAC3B,cAAM,SAAS,KAAK,QAAQ;AAC5B,YAAI,OAAO,UAAU,YAAY,OAAO,WAAW,SAAU;AAE7D,cAAM,aAAa,EAAE,MAAM,SAAS,OAAO,KAAK,QAAQ,OAAO;AAC/D,cAAM,WAAW,oBAAoB,IAAI,UAAU;AACnD,YAAI,UAAU;AACZ,mBAAS,KAAK,UAAU;AAAA,QAC1B,OAAO;AACL,8BAAoB,IAAI,YAAY,CAAC,UAAU,CAAC;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAA4B,CAAC;AAEnC,eAAW,CAAC,EAAE,WAAW,KAAK,qBAAqB;AACjD,UAAI,YAAY,UAAU,UAAW;AAErC,eAAS,QAAQ,WAAW,QAAQ,YAAY,QAAQ,SAAS,GAAG;AAClE,cAAM,aAAa,YAAY,KAAK;AACpC,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,4BAA4B,WAAW,IAAI,aAAa,YAAY,MAAM;AAAA,UACnF;AAAA,UACA,MAAM,kBAAkB;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;AC9EA,IAAM,8BAA8B,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,QAAQ,OAAO;AAE/E,IAAM,iBAA8C;AAAA,EACzD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,oBAAoB,4BAA4B;AAAA,EAC5D,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAmD;AAClF,UAAM,cAA4B,CAAC;AACnC,UAAM,cAAc,IAAI,KAAK,QAAQ,oBAAoB,SAAS,QAAQ,qBAAqB,6BAA6B,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC7J,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,iCAAiC;AAE3D,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,UAAI,MAAM,SAAS,EAAG;AACtB,UAAI,CAAC,YAAY,IAAI,MAAM,CAAC,EAAG,YAAY,CAAC,EAAG;AAE/C,YAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,YAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,iCAAiC,WAAW,IAAI;AAAA,QACzD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,eAAe;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,kBAAkB,WAAW;AAAA,EACtC;AACF;AAEA,SAAS,kBAAkB,aAAyC;AAClE,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,YAAY,OAAO,CAAC,eAAe;AACxC,UAAM,MAAM,GAAG,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM,GAAG;AAC7D,QAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,SAAK,IAAI,GAAG;AACZ,WAAO;AAAA,EACT,CAAC;AACH;;;AC5CA,IAAM,mBAAmB,CAAC,MAAM,QAAQ,QAAQ,SAAS,OAAO;AAChE,IAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,UAAU,kBAAkB,OAAO,cAAc;AAAA,EAC7D,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,WAAW,QAAQ,UAAU,SAAS,QAAQ,WAAW;AAC/D,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQ;AACtD,QAAI,CAAC,SAAS,UAAU,CAAC,MAAM,OAAQ,QAAO;AAE9C,UAAM,KAAK,IAAI;AAAA,MACb,mBAAmB,SAAS,IAAIC,YAAW,EAAE,KAAK,GAAG,CAAC,SAAS,MAAM,IAAIA,YAAW,EAAE,KAAK,GAAG,CAAC;AAAA,MAC/F;AAAA,IACF;AACA,QAAI;AACJ,YAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,YAAM,SAAS,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACtC,YAAM,cAAc,MAAM,QAAQ,MAAM,CAAC,EAAE,QAAQ,MAAM;AACzD,YAAM,QAAQ,UAAU,WAAW;AACnC,YAAM,MAAM,UAAU,cAAc,OAAO,SAAS,CAAC;AACrD,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,gCAAgC,OAAO,YAAY,CAAC;AAAA,QAC7D,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,mBAAmB;AAAA,MAC3B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,aAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AClDA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,yBAAyB;AAExB,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,YAAY,oBAAoB,eAAe,uBAAuB;AAAA,EAClF,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,aAAa,IAAI,KAAK,QAAQ,YAAY,SAAS,QAAQ,aAAa,oBAAoB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AACnI,UAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAI,gBAAgB,EAAG,QAAO,CAAC;AAE/B,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AACnC,UAAM,aAAoD,CAAC;AAE3D,UAAM,WAAW,CAAC,+BAA+B,0BAA0B;AAC3E,eAAW,WAAW,UAAU;AAC9B,YAAM,UAAU,IAAI,MAAM,OAAO;AAEjC,iBAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,cAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,cAAM,iBAAiB,MAAM,OAAO,UAAQ,WAAW,IAAI,KAAK,YAAY,EAAE,QAAQ,eAAe,EAAE,CAAC,CAAC;AACzG,YAAI,eAAe,UAAU,cAAe;AAE5C,YAAI,WAAW,KAAK,CAAAC,WAAS,WAAW,QAAQA,OAAM,OAAO,WAAW,MAAMA,OAAM,KAAK,EAAG;AAC5F,mBAAW,KAAK,EAAE,OAAO,WAAW,OAAO,KAAK,WAAW,IAAI,CAAC;AAEhE,cAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,YAAI,CAAC,MAAO;AAEZ,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,iCAAiC,WAAW,IAAI;AAAA,UACzD;AAAA,UACA,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACtEA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAqD;AACpF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAE5D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,aAAa;AAAA,UACnD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,iBAAiB;AAAA,QACzB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;AC/CO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,gBAAgB,CAAC,EAAE;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,cAA4B,CAAC;AACnC,UAAM,iBAAiB,IAAI,KAAK,QAAQ,kBAAkB,CAAC,GAAG,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC/F,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,8BAA8B;AAExD,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,UAAI,eAAe,IAAI,WAAW,KAAK,YAAY,CAAC,EAAG;AAEvD,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,8BAA8B,WAAW,IAAI;AAAA,QACtD;AAAA,QACA,MAAM,oBAAoB;AAAA,MAC5B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;AC9BA,IAAMC,mBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,SAASA,iBAAgB;AAAA,EACrC,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,cAA4B,CAAC;AACnC,UAAM,UAAU;AAAA,MACd,QAAQ,SAAS,SAAS,QAAQ,UAAUA;AAAA,IAC9C;AACA,UAAM,gBAAuD,CAAC;AAE9D,eAAW,UAAU,SAAS;AAC5B,YAAM,KAAK,IAAI,OAAO,MAAMC,cAAa,MAAM,EAAE,QAAQ,SAAS,MAAM,CAAC,OAAO,IAAI;AACpF,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,gBAAgB,MAAM,CAAC;AAC7B,cAAM,aAAa,MAAM;AACzB,cAAM,WAAW,MAAM,QAAQ,cAAc;AAC7C,YAAI,cAAc,KAAK,WAAS,cAAc,OAAO,YAAY,QAAQ,CAAC,EAAG;AAE7E,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,cAAc,SAAS,CAAC;AAC5D,YAAI,UAAU,UAAa,QAAQ,OAAW;AAC9C,sBAAc,KAAK,EAAE,OAAO,YAAY,KAAK,SAAS,CAAC;AAEvD,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,sCAAsC,cAAc,YAAY,CAAC;AAAA,UAC1E,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,oBAAoB;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;AAEA,SAAS,uBAAuB,SAA6B;AAC3D,SAAO,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;AACtE;AAEA,SAAS,cAAc,OAAuC,OAAe,KAAsB;AACjG,SAAO,QAAQ,MAAM,OAAO,MAAM,MAAM;AAC1C;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACrEA,OAAOC,UAAS;AAWhB,IAAMC,mBAAwD;AAAA,EAC5D,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,aAAa;AACf;AAEO,IAAM,sBAAwD;AAAA,EACnE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAGN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAwD;AACvF,UAAM,eAAe,IAAI;AAAA,OACtB,QAAQ,gBAAgBA,iBAAgB,cAAc,IAAI,UAAQ,KAAK,YAAY,CAAC;AAAA,IACvF;AACA,UAAM,cAAc,QAAQ,eAAeA,iBAAgB;AAE3D,QAAI,aAAa,SAAS,EAAG,QAAO,CAAC;AAErC,UAAM,MAAMC,KAAI,IAAI;AACpB,UAAM,cAA4B,CAAC;AACnC,UAAM,aAAoD,CAAC;AAE3D,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,eAAW,YAAY,WAAW;AAChC,YAAM,gBAAgB,SAAS,QAAQ,SAAS,SAAS,QAAQ,CAAC,GAAG,QAAQ;AAC7E,YAAM,iBAAiB,SAAS,QAAQ,UAAU,SAAS,OAAO,OAAO,CAAC,KAAK,SAAS,OAAO,KAAK,QAAQ,UAAU,IAAI,CAAC;AAC3H,UAAI,OAAO,kBAAkB,YAAY,OAAO,mBAAmB,SAAU;AAC7E,YAAM,cAAc,gBAAgB;AAEpC,YAAM,eAAe,SAAS,QAAQ,KAAK,MAAM,eAAe,WAAW;AAC3E,YAAM,cAAcA,KAAI,YAAY;AAGpC,YAAM,uBAAuB,MAAM,KAAK,YAAY,EAAE,KAAK,GAAG;AAC9D,YAAM,WAAW;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI,oBAAoB;AAAA,MAC1B;AAEA,iBAAW,WAAW,UAAU;AAC9B,cAAM,UAAU,YAAY,MAAM,OAAO;AAEzC,mBAAW,cAAc,oBAAoB,cAAc,OAAO,GAAG;AACnE,gBAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK;AAC9D,gBAAM,qBAAqB,MAAM,KAAK,UAAQ,aAAa,IAAI,KAAK,QAAQ,eAAe,EAAE,CAAC,CAAC;AAC/F,cAAI,CAAC,mBAAoB;AAGzB,gBAAM,sBAAsB,WAAW,KAAK,MAAM,eAAe,IAAI,CAAC,EAAE,UAAU;AAClF,gBAAM,kBAAkB,gBAAgB,WAAW;AACnD,gBAAM,gBAAgB,gBAAgB,WAAW,MAAM;AAEvD,cAAI,WAAW,KAAK,CAAAC,WAAS,kBAAkBA,OAAM,OAAO,gBAAgBA,OAAM,KAAK,EAAG;AAC1F,qBAAW,KAAK,EAAE,OAAO,iBAAiB,KAAK,cAAc,CAAC;AAE9D,cAAI,eAAe,YAAY,IAAI,MAAM,EAAG;AAE5C,gBAAM,QAAQ,mBAAmB,WAAW,EAAE,MAAM,WAAW,MAAM,OAAO,iBAAiB,KAAK,cAAc,CAAC;AACjH,cAAI,CAAC,MAAO;AAEZ,sBAAY,KAAK;AAAA,YACf,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,SAAS,gBAAgB,WAAW,KAAK,KAAK,EAAE,QAAQ,iBAAiB,EAAE,CAAC;AAAA,YAC5E;AAAA,YACA,MAAM,oBAAoB;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACpGA,IAAM,sBAAsB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,aAAa,oBAAoB;AAAA,EAC7C,MACE;AAAA,EAEF,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,cAA4B,CAAC;AACnC,UAAM,cAAc,QAAQ,aAAa,SAAS,QAAQ,cAAc;AAExE,eAAW,cAAc,aAAa;AACpC,YAAM,KAAK,IAAI;AAAA,QACb,MAAMC,cAAa,UAAU,EAAE,QAAQ,SAAS,MAAM,CAAC;AAAA,QACvD;AAAA,MACF;AACA,UAAI;AAEJ,cAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,cAAM,oBAAoB,MAAM,CAAC;AACjC,cAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,cAAM,MAAM,UAAU,MAAM,QAAQ,kBAAkB,SAAS,CAAC;AAChE,YAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,oBAAY,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,SAAS,6BAA6B,iBAAiB;AAAA,UACvD,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,UAC7B,MAAM,mBAAmB;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAEA,SAASA,cAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;;;ACtDA,IAAM,iBAAiB,CAAC,OAAO,SAAS,OAAO,OAAO;AACtD,IAAMC,iBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,eAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,QAAQ,gBAAgB,OAAOA,eAAc;AAAA,EACzD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAiD;AAChF,UAAM,cAA4B,CAAC;AACnC,UAAM,SAAS,IAAI,KAAK,QAAQ,QAAQ,SAAS,QAAQ,SAAS,gBAAgB,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AACnH,UAAM,QAAQ,IAAI,KAAK,QAAQ,OAAO,SAAS,QAAQ,QAAQA,gBAAe,IAAI,WAAS,MAAM,YAAY,CAAC,CAAC;AAC/G,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,UAAU,IAAI,MAAM,uBAAuB;AAEjD,eAAW,cAAc,oBAAoB,MAAM,OAAO,GAAG;AAC3D,YAAM,QAAQ,WAAW,KAAK,KAAK,EAAE,MAAM,KAAK;AAChD,YAAM,QAAQ,MAAM,CAAC,GAAG,YAAY;AACpC,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC,GAAG,YAAY;AAClD,UAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,IAAI,KAAK,KAAK,CAAC,MAAM,IAAI,IAAI,EAAG;AAE/D,YAAM,QAAQ,mBAAmB,WAAW,UAAU;AACtD,UAAI,CAAC,MAAO;AAEZ,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,YAAY,WAAW,KAAK,YAAY,CAAC;AAAA,QAClD;AAAA,QACA,MAAM,aAAa;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;ACzCA,IAAMC,iBAA4B;AAAA,EAChC,EAAE,MAAM,QAAQ,cAAc,CAAC,UAAU,SAAS,SAAS,EAAE;AAAA,EAC7D,EAAE,MAAM,WAAW,cAAc,CAAC,WAAW,OAAO,UAAU,EAAE;AAAA,EAChE,EAAE,MAAM,WAAW,cAAc,CAAC,QAAQ,OAAO,QAAQ,EAAE;AAAA,EAC3D,EAAE,MAAM,aAAa,cAAc,CAAC,WAAW,YAAY,WAAW,EAAE;AAC1E;AAgBA,SAAS,sBAAsB,OAAiB,cAA2E;AACzH,QAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,cAAc,MAAM,CAAC,EAAG;AAC9B,QAAM,aAAa,MAAM,MAAM,SAAS,CAAC,EAAG;AAC5C,MACE,OAAO,aAAa,UAAU,YAC9B,OAAO,YAAY,WAAW,YAC9B,OAAO,YAAY,UAAU,YAC7B,OAAO,WAAW,WAAW,UAC7B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY;AAC1B,QAAM,MAAM,WAAW,QAAQ,WAAW;AAC1C,MAAI,OAAO,MAAO,QAAO;AAEzB,SAAO;AAAA,IACL,MAAM,aAAa,MAAM,OAAO,GAAG;AAAA,IACnC;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,cAAwC;AAAA,EACnD,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,OAAOA,gBAAe,WAAW,CAAC,EAAE;AAAA,EAChD,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAgD;AAC/E,UAAM,QAAQ,QAAQ,OAAO,SAAS,QAAQ,QAAQA;AACtD,UAAM,UAAU,IAAI,KAAK,QAAQ,aAAa,CAAC,GAAG,IAAI,UAAQ,KAAK,YAAY,CAAC,CAAC;AACjF,UAAM,mBAAmB,oBAAI,IAAsB;AACnD,eAAW,SAAS,OAAO;AACzB,uBAAiB,IAAI,MAAM,KAAK,YAAY,GAAG,KAAK;AAAA,IACtD;AAEA,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,cAA4B,CAAC;AAEnC,UAAM,cAAc,IAAI,MAAM,EAAE,KAAK;AAAA,MACnC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK;AAAA,IACpC,CAAC;AAED,eAAW,SAAS,aAAa;AAC/B,YAAM,aAAa,MAAM,MAAM,YAAY,YAAY;AACvD,UAAI,CAAC,WAAY;AACjB,UAAI,QAAQ,IAAI,UAAU,EAAG;AAE7B,YAAM,WAAW,iBAAiB,IAAI,UAAU;AAChD,UAAI,CAAC,SAAU;AAEf,YAAM,aAAa,sBAAsB,OAAO,IAAI;AACpD,UAAI,CAAC,WAAY;AAEjB,YAAM,QAAQ,UAAU,WAAW,KAAK;AACxC,YAAM,MAAM,UAAU,WAAW,MAAM,CAAC;AACxC,UAAI,UAAU,UAAa,QAAQ,OAAW;AAE9C,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,sBAAsB,WAAW,IAAI,0CAA0C,SAAS,aAAa,KAAK,MAAM,CAAC;AAAA,QAC1H,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE;AAAA,QAC7B,MAAM,YAAY;AAAA,MACpB,CAAC;AAAA,IACH;AAEA,WAAO,YAAY,KAAK,CAAC,MAAM,UAAU,KAAK,MAAM,QAAQ,MAAM,MAAM,KAAK;AAAA,EAC/E;AACF;;;ACrGA,IAAMC,mBAAuD;AAAA,EAC3D,cAAc;AAAA,EACd,gBAAgB;AAClB;AAEO,IAAM,qBAAsD;AAAA,EACjE,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,UAAU,EAAE,GAAGA,iBAAgB;AAAA,EAC/B,MAAM;AAAA,EAEN,MAAM,EAAE,MAAM,WAAW,QAAQ,GAAuD;AACtF,UAAM,eAAe,QAAQ,gBAAgBA,iBAAgB;AAC7D,UAAM,iBAAiB,QAAQ,kBAAkBA,iBAAgB;AAEjE,UAAM,MAAM,UAAU,IAAI;AAC1B,UAAM,YAAY,IAAI,UAAU,EAAE,KAAK,EAAE,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,EAAE,CAAC;AAE5F,UAAM,cAA4B,CAAC;AAEnC,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,SAAS,UAAU,CAAC,SAAS,MAAO;AAEzC,YAAM,QAAQ,SAAS,MAAM,OAAO,UAAQ,cAAc,KAAK,KAAK,QAAQ,EAAE,CAAC;AAC/E,YAAM,YAAY,MAAM;AAExB,YAAM,cAAc,MAAM,OAAO,CAAC,SAAS;AACzC,cAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,eAAO,KAAK,SAAS,MAAM,KAAK,CAAC,KAAK,SAAS,QAAQ,KAAK,CAAC,KAAK,SAAS,YAAY,KAAK,CAAC,KAAK,SAAS,UAAU;AAAA,MACvH,CAAC,EAAE;AAEH,UAAI,aAAa,gBAAgB,eAAe,eAAgB;AAEhE,YAAM,QAAQ,SAAS,OAAO,SAAS;AACvC,YAAM,SAAS,SAAS,OAAO,UAAU;AACzC,YAAM,MAAM,QAAQ;AAEpB,YAAM,cAAc,UAAU,KAAK;AACnC,YAAM,YAAY,UAAU,MAAM,CAAC;AACnC,UAAI,gBAAgB,UAAa,cAAc,OAAW;AAE1D,YAAM,UAAoB,CAAC;AAC3B,UAAI,YAAY,aAAc,SAAQ,KAAK,GAAG,SAAS,eAAe,YAAY,GAAG;AACrF,UAAI,cAAc,eAAgB,SAAQ,KAAK,GAAG,WAAW,iBAAiB,cAAc,GAAG;AAE/F,kBAAY,KAAK;AAAA,QACf,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,4BAA4B,QAAQ,KAAK,IAAI,CAAC;AAAA,QACvD,OAAO,EAAE,OAAO,aAAa,KAAK,YAAY,EAAE;AAAA,QAChD,MAAM,mBAAmB;AAAA,QACzB,SAAS;AAAA,UACP,aAAa;AAAA,UACb,OAAO,CAAC;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;ACqBO,IAAM,eAAkC,oBAAI,IAAI;AAAA,EACrD,CAAC,4BAA4B,sBAA8B;AAAA,EAC3D,CAAC,qBAAqB,eAAuB;AAAA,EAC7C,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,0BAA0B,oBAA4B;AAAA,EACvD,CAAC,kCAAkC,2BAAmC;AAAA,EACtE,CAAC,wBAAwB,kBAA0B;AAAA,EACnD,CAAC,mBAAmB,aAAqB;AAAA,EACzC,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,kBAAkB,YAAoB;AAAA,EACvC,CAAC,aAAa,QAAgB;AAAA,EAC9B,CAAC,gBAAgB,UAAkB;AAAA,EACnC,CAAC,4BAA4B,sBAA8B;AAAA,EAC3D,CAAC,iCAAiC,sBAA8B;AAAA,EAChE,CAAC,0BAA0B,oBAA4B;AAAA,EACvD,CAAC,mBAAmB,aAAqB;AAAA,EACzC,CAAC,eAAe,SAAiB;AAAA,EACjC,CAAC,+BAA+B,wBAAgC;AAAA,EAChE,CAAC,uBAAuB,iBAAyB;AAAA,EACjD,CAAC,oBAAoB,cAAsB;AAAA,EAC3C,CAAC,yBAAyB,kBAA0B;AAAA,EACpD,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,sBAAsB,gBAAwB;AAAA,EAC/C,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,yBAAyB,mBAA2B;AAAA,EACrD,CAAC,wBAAwB,kBAA0B;AAAA,EACnD,CAAC,kBAAkB,YAAoB;AAAA,EACvC,CAAC,iBAAiB,WAAmB;AAAA,EACrC,CAAC,uBAAuB,kBAA0B;AACpD,CAAC;","names":["DEFAULT_PHRASES","DEFAULT_PHRASES","escapeRegExp","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","DEFAULT_TERMS","getOccurrenceFromMatch","escapeRegex","DEFAULT_OPTIONS","DEFAULT_OPTIONS","normalizePhrase","DEFAULT_ALLOWED_ADVERBS","escapeRegex","range","DEFAULT_PHRASES","escapeRegExp","DEFAULT_PHRASES","escapeRegExp","nlp","DEFAULT_OPTIONS","nlp","range","escapeRegExp","DEFAULT_VERBS","DEFAULT_VERBS","DEFAULT_OPTIONS"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faircopy/rules-nlp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.1",
|
|
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.
|
|
22
|
+
"@faircopy/core": "1.21.1",
|
|
23
23
|
"compromise": "^14.15.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|