@faircopy/rules-nlp 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +187 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,12 +13,15 @@ rulesets: ['@faircopy/rules-nlp'],
|
|
|
13
13
|
rules: {
|
|
14
14
|
'no-expletive-openers': 'warn',
|
|
15
15
|
'no-filter-words': 'warn',
|
|
16
|
+
'no-hedge-words': 'warn',
|
|
16
17
|
'no-empty-transformation-claims': 'warn',
|
|
17
18
|
'no-passive-voice': 'warn',
|
|
18
19
|
'no-redundant-pairs': 'warn',
|
|
19
20
|
'no-weak-modals': 'warn',
|
|
20
21
|
'no-stacked-adjectives': 'warn',
|
|
21
22
|
'no-nominalized-phrases': 'warn',
|
|
23
|
+
'no-pronoun-led-claims': 'warn',
|
|
24
|
+
'no-buzzword-stacks': 'warn',
|
|
22
25
|
}
|
|
23
26
|
```
|
|
24
27
|
|
|
@@ -31,8 +34,11 @@ Package-qualified IDs like `@faircopy/rules-nlp/no-passive-voice` still work and
|
|
|
31
34
|
| `no-empty-transformation-claims` | Flag broad transformation cliches like `transform the way teams work` |
|
|
32
35
|
| `no-expletive-openers` | Flag sentence openings like `There are` |
|
|
33
36
|
| `no-filter-words` | Ban filter phrases like `I think` and `it seems` |
|
|
37
|
+
| `no-hedge-words` | Flag hedge words like `kind of` and `somewhat` |
|
|
34
38
|
| `no-passive-voice` | Flag likely passive-voice constructions |
|
|
35
39
|
| `no-redundant-pairs` | Flag redundant fixed phrases like `first and foremost` |
|
|
36
40
|
| `no-weak-modals` | Flag hedged modal claims like `can help` and `might improve` |
|
|
37
41
|
| `no-stacked-adjectives` | Flag noun phrases with multiple adjectives before the noun |
|
|
38
42
|
| `no-nominalized-phrases` | Flag nominalized `X of Y` phrases like `optimization of onboarding` |
|
|
43
|
+
| `no-pronoun-led-claims` | Flag vague sentence openers like `This helps` and `It enables` |
|
|
44
|
+
| `no-buzzword-stacks` | Flag sentences overloaded with abstract benefit nouns |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Rule } from '@faircopy/core';
|
|
2
2
|
|
|
3
|
+
interface NoBuzzwordStacksOptions {
|
|
4
|
+
terms?: string[];
|
|
5
|
+
maxTermsPerSentence?: number;
|
|
6
|
+
}
|
|
7
|
+
declare const noBuzzwordStacks: Rule<NoBuzzwordStacksOptions>;
|
|
8
|
+
|
|
3
9
|
interface NoExpletiveOpenersOptions {
|
|
4
10
|
phrases?: string[];
|
|
5
11
|
}
|
|
@@ -15,6 +21,11 @@ interface NoEmptyTransformationClaimsOptions {
|
|
|
15
21
|
}
|
|
16
22
|
declare const noEmptyTransformationClaims: Rule<NoEmptyTransformationClaimsOptions>;
|
|
17
23
|
|
|
24
|
+
interface NoHedgeWordsOptions {
|
|
25
|
+
hedges?: string[];
|
|
26
|
+
}
|
|
27
|
+
declare const noHedgeWords: Rule<NoHedgeWordsOptions>;
|
|
28
|
+
|
|
18
29
|
interface NoNominalizedPhrasesOptions {
|
|
19
30
|
suffixes?: string[];
|
|
20
31
|
allowedWords?: string[];
|
|
@@ -26,6 +37,12 @@ interface NoPassiveVoiceOptions {
|
|
|
26
37
|
}
|
|
27
38
|
declare const noPassiveVoice: Rule<NoPassiveVoiceOptions>;
|
|
28
39
|
|
|
40
|
+
interface NoPronounLedClaimsOptions {
|
|
41
|
+
pronouns?: string[];
|
|
42
|
+
verbs?: string[];
|
|
43
|
+
}
|
|
44
|
+
declare const noPronounLedClaims: Rule<NoPronounLedClaimsOptions>;
|
|
45
|
+
|
|
29
46
|
interface NoRedundantPairsOptions {
|
|
30
47
|
phrases?: string[];
|
|
31
48
|
}
|
|
@@ -45,4 +62,4 @@ declare const noWeakModals: Rule<NoWeakModalsOptions>;
|
|
|
45
62
|
/** All NLP rules keyed by their rule ID. */
|
|
46
63
|
declare const ruleRegistry: Map<string, Rule>;
|
|
47
64
|
|
|
48
|
-
export { type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoNominalizedPhrasesOptions, type NoPassiveVoiceOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoWeakModalsOptions, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noNominalizedPhrases, noPassiveVoice, noRedundantPairs, noStackedAdjectives, noWeakModals, ruleRegistry };
|
|
65
|
+
export { type NoBuzzwordStacksOptions, type NoEmptyTransformationClaimsOptions, type NoExpletiveOpenersOptions, type NoFilterWordsOptions, type NoHedgeWordsOptions, type NoNominalizedPhrasesOptions, type NoPassiveVoiceOptions, type NoPronounLedClaimsOptions, type NoRedundantPairsOptions, type NoStackedAdjectivesOptions, type NoWeakModalsOptions, noBuzzwordStacks, noEmptyTransformationClaims, noExpletiveOpeners, noFilterWords, noHedgeWords, noNominalizedPhrases, noPassiveVoice, noPronounLedClaims, noRedundantPairs, noStackedAdjectives, noWeakModals, ruleRegistry };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
+
// src/no-buzzword-stacks.ts
|
|
2
|
+
var DEFAULT_TERMS = [
|
|
3
|
+
"alignment",
|
|
4
|
+
"automation",
|
|
5
|
+
"collaboration",
|
|
6
|
+
"efficiency",
|
|
7
|
+
"engagement",
|
|
8
|
+
"experience",
|
|
9
|
+
"growth",
|
|
10
|
+
"impact",
|
|
11
|
+
"innovation",
|
|
12
|
+
"intelligence",
|
|
13
|
+
"optimization",
|
|
14
|
+
"platform",
|
|
15
|
+
"productivity",
|
|
16
|
+
"solution",
|
|
17
|
+
"strategy",
|
|
18
|
+
"transformation",
|
|
19
|
+
"value",
|
|
20
|
+
"velocity",
|
|
21
|
+
"workflow"
|
|
22
|
+
];
|
|
23
|
+
var noBuzzwordStacks = {
|
|
24
|
+
id: "no-buzzword-stacks",
|
|
25
|
+
description: "Flag sentences overloaded with abstract benefit nouns",
|
|
26
|
+
defaults: { terms: DEFAULT_TERMS, maxTermsPerSentence: 2 },
|
|
27
|
+
help: "Buzzword-heavy sentences make copy sound interchangeable. Replace abstract benefit nouns with the specific product behavior, customer outcome, or proof point.",
|
|
28
|
+
check({ text, sourceMap, options }) {
|
|
29
|
+
const diagnostics = [];
|
|
30
|
+
const terms = options.terms?.length ? options.terms : DEFAULT_TERMS;
|
|
31
|
+
const maxTermsPerSentence = options.maxTermsPerSentence ?? 2;
|
|
32
|
+
if (maxTermsPerSentence < 1) return diagnostics;
|
|
33
|
+
for (const sentence of getSentenceRanges(text)) {
|
|
34
|
+
const hits = getTermHits(sentence.text, terms);
|
|
35
|
+
if (hits.length <= maxTermsPerSentence) continue;
|
|
36
|
+
const startOffset = sentence.start + hits[0].start;
|
|
37
|
+
const endOffset = sentence.start + hits[hits.length - 1].end;
|
|
38
|
+
const start = sourceMap[startOffset];
|
|
39
|
+
const end = sourceMap[endOffset - 1];
|
|
40
|
+
if (start === void 0 || end === void 0) continue;
|
|
41
|
+
const words = hits.map((hit) => hit.text.toLowerCase()).join(", ");
|
|
42
|
+
diagnostics.push({
|
|
43
|
+
ruleId: "no-buzzword-stacks",
|
|
44
|
+
severity: "warn",
|
|
45
|
+
message: `replace buzzword stack: ${words}`,
|
|
46
|
+
range: { start, end: end + 1 },
|
|
47
|
+
help: noBuzzwordStacks.help
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return diagnostics;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function getSentenceRanges(text) {
|
|
54
|
+
const ranges = [];
|
|
55
|
+
const re = /[^.!?]+[.!?]?/g;
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = re.exec(text)) !== null) {
|
|
58
|
+
const leadingWhitespaceLength = match[0].match(/^\s*/)?.[0].length ?? 0;
|
|
59
|
+
const sentence = match[0].trim();
|
|
60
|
+
if (!sentence) continue;
|
|
61
|
+
ranges.push({ text: sentence, start: match.index + leadingWhitespaceLength });
|
|
62
|
+
}
|
|
63
|
+
return ranges;
|
|
64
|
+
}
|
|
65
|
+
function getTermHits(text, terms) {
|
|
66
|
+
const termPattern = terms.map(escapeRegex).join("|");
|
|
67
|
+
if (!termPattern) return [];
|
|
68
|
+
const hits = [];
|
|
69
|
+
const re = new RegExp(`\\b(${termPattern})\\b`, "gi");
|
|
70
|
+
let match;
|
|
71
|
+
while ((match = re.exec(text)) !== null) {
|
|
72
|
+
hits.push({ text: match[0], start: match.index, end: match.index + match[0].length });
|
|
73
|
+
}
|
|
74
|
+
return hits;
|
|
75
|
+
}
|
|
76
|
+
function escapeRegex(value) {
|
|
77
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
|
+
}
|
|
79
|
+
|
|
1
80
|
// src/utils.ts
|
|
2
81
|
import nlp from "compromise";
|
|
3
82
|
function createDoc(text) {
|
|
@@ -159,6 +238,51 @@ function normalize(value) {
|
|
|
159
238
|
return value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
160
239
|
}
|
|
161
240
|
|
|
241
|
+
// src/no-hedge-words.ts
|
|
242
|
+
var DEFAULT_HEDGES = [
|
|
243
|
+
"kind of",
|
|
244
|
+
"sort of",
|
|
245
|
+
"somewhat",
|
|
246
|
+
"fairly",
|
|
247
|
+
"pretty",
|
|
248
|
+
"rather",
|
|
249
|
+
"quite",
|
|
250
|
+
"arguably",
|
|
251
|
+
"relatively",
|
|
252
|
+
"more or less"
|
|
253
|
+
];
|
|
254
|
+
var noHedgeWords = {
|
|
255
|
+
id: "no-hedge-words",
|
|
256
|
+
description: "Flag hedge words that soften claims",
|
|
257
|
+
defaults: { hedges: DEFAULT_HEDGES },
|
|
258
|
+
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.',
|
|
259
|
+
check({ text, sourceMap, options }) {
|
|
260
|
+
const diagnostics = [];
|
|
261
|
+
const hedges = options.hedges?.length ? options.hedges : DEFAULT_HEDGES;
|
|
262
|
+
for (const hedge of hedges) {
|
|
263
|
+
const re = new RegExp(`\\b${escapeRegExp(hedge).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
264
|
+
let match;
|
|
265
|
+
while ((match = re.exec(text)) !== null) {
|
|
266
|
+
const phrase = match[0];
|
|
267
|
+
const start = sourceMap[match.index];
|
|
268
|
+
const end = sourceMap[match.index + phrase.length - 1];
|
|
269
|
+
if (start === void 0 || end === void 0) continue;
|
|
270
|
+
diagnostics.push({
|
|
271
|
+
ruleId: "no-hedge-words",
|
|
272
|
+
severity: "warn",
|
|
273
|
+
message: `remove hedge "${phrase.toLowerCase()}"`,
|
|
274
|
+
range: { start, end: end + 1 },
|
|
275
|
+
help: noHedgeWords.help
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return diagnostics;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
function escapeRegExp(value) {
|
|
283
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
284
|
+
}
|
|
285
|
+
|
|
162
286
|
// src/no-nominalized-phrases.ts
|
|
163
287
|
var DEFAULT_SUFFIXES = ["tion", "sion", "ment", "ance", "ence", "ity"];
|
|
164
288
|
var DEFAULT_ALLOWED_WORDS = [
|
|
@@ -182,7 +306,7 @@ var noNominalizedPhrases = {
|
|
|
182
306
|
const diagnostics = [];
|
|
183
307
|
const suffixes = options.suffixes?.length ? options.suffixes : DEFAULT_SUFFIXES;
|
|
184
308
|
const allowedWords = new Set((options.allowedWords?.length ? options.allowedWords : DEFAULT_ALLOWED_WORDS).map((value) => value.toLowerCase()));
|
|
185
|
-
const suffixPattern = suffixes.map(
|
|
309
|
+
const suffixPattern = suffixes.map(escapeRegex2).join("|");
|
|
186
310
|
if (!suffixPattern) return diagnostics;
|
|
187
311
|
const doc = createDoc(text);
|
|
188
312
|
const matches = doc.match(`/[a-z]+(${suffixPattern})/ of .`);
|
|
@@ -202,7 +326,7 @@ var noNominalizedPhrases = {
|
|
|
202
326
|
return diagnostics;
|
|
203
327
|
}
|
|
204
328
|
};
|
|
205
|
-
function
|
|
329
|
+
function escapeRegex2(value) {
|
|
206
330
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
331
|
}
|
|
208
332
|
|
|
@@ -246,6 +370,56 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
246
370
|
});
|
|
247
371
|
}
|
|
248
372
|
|
|
373
|
+
// src/no-pronoun-led-claims.ts
|
|
374
|
+
var DEFAULT_PRONOUNS = ["it", "this", "that", "these", "those"];
|
|
375
|
+
var DEFAULT_VERBS = [
|
|
376
|
+
"brings",
|
|
377
|
+
"delivers",
|
|
378
|
+
"enables",
|
|
379
|
+
"gives",
|
|
380
|
+
"helps",
|
|
381
|
+
"keeps",
|
|
382
|
+
"lets",
|
|
383
|
+
"makes",
|
|
384
|
+
"turns",
|
|
385
|
+
"unlocks"
|
|
386
|
+
];
|
|
387
|
+
var noPronounLedClaims = {
|
|
388
|
+
id: "no-pronoun-led-claims",
|
|
389
|
+
description: "Flag vague claims that start with it, this, that, these, or those",
|
|
390
|
+
defaults: { pronouns: DEFAULT_PRONOUNS, verbs: DEFAULT_VERBS },
|
|
391
|
+
help: "Pronoun-led claims make the reader infer the subject. Name the feature, product, or user action that creates the outcome.",
|
|
392
|
+
check({ text, sourceMap, options }) {
|
|
393
|
+
const diagnostics = [];
|
|
394
|
+
const pronouns = options.pronouns?.length ? options.pronouns : DEFAULT_PRONOUNS;
|
|
395
|
+
const verbs = options.verbs?.length ? options.verbs : DEFAULT_VERBS;
|
|
396
|
+
if (!pronouns.length || !verbs.length) return diagnostics;
|
|
397
|
+
const re = new RegExp(
|
|
398
|
+
`(?:^|[.!?]\\s+)(${pronouns.map(escapeRegex3).join("|")})\\s+(${verbs.map(escapeRegex3).join("|")})\\b`,
|
|
399
|
+
"gi"
|
|
400
|
+
);
|
|
401
|
+
let match;
|
|
402
|
+
while ((match = re.exec(text)) !== null) {
|
|
403
|
+
const phrase = `${match[1]} ${match[2]}`;
|
|
404
|
+
const phraseStart = match.index + match[0].indexOf(phrase);
|
|
405
|
+
const start = sourceMap[phraseStart];
|
|
406
|
+
const end = sourceMap[phraseStart + phrase.length - 1];
|
|
407
|
+
if (start === void 0 || end === void 0) continue;
|
|
408
|
+
diagnostics.push({
|
|
409
|
+
ruleId: "no-pronoun-led-claims",
|
|
410
|
+
severity: "warn",
|
|
411
|
+
message: `name the subject instead of "${phrase.toLowerCase()}"`,
|
|
412
|
+
range: { start, end: end + 1 },
|
|
413
|
+
help: noPronounLedClaims.help
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return diagnostics;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
function escapeRegex3(value) {
|
|
420
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
421
|
+
}
|
|
422
|
+
|
|
249
423
|
// src/no-redundant-pairs.ts
|
|
250
424
|
var DEFAULT_PHRASES3 = [
|
|
251
425
|
"first and foremost",
|
|
@@ -267,7 +441,7 @@ var noRedundantPairs = {
|
|
|
267
441
|
const diagnostics = [];
|
|
268
442
|
const phrases = options.phrases?.length ? options.phrases : DEFAULT_PHRASES3;
|
|
269
443
|
for (const phrase of phrases) {
|
|
270
|
-
const re = new RegExp(`\\b${
|
|
444
|
+
const re = new RegExp(`\\b${escapeRegExp2(phrase).replace(/\\s+/g, "\\s+")}\\b`, "gi");
|
|
271
445
|
let match;
|
|
272
446
|
while ((match = re.exec(text)) !== null) {
|
|
273
447
|
const matchedPhrase = match[0];
|
|
@@ -286,7 +460,7 @@ var noRedundantPairs = {
|
|
|
286
460
|
return diagnostics;
|
|
287
461
|
}
|
|
288
462
|
};
|
|
289
|
-
function
|
|
463
|
+
function escapeRegExp2(value) {
|
|
290
464
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
291
465
|
}
|
|
292
466
|
|
|
@@ -319,7 +493,7 @@ var noStackedAdjectives = {
|
|
|
319
493
|
|
|
320
494
|
// src/no-weak-modals.ts
|
|
321
495
|
var DEFAULT_MODALS = ["can", "could", "may", "might"];
|
|
322
|
-
var
|
|
496
|
+
var DEFAULT_VERBS2 = [
|
|
323
497
|
"boost",
|
|
324
498
|
"drive",
|
|
325
499
|
"enable",
|
|
@@ -335,12 +509,12 @@ var DEFAULT_VERBS = [
|
|
|
335
509
|
var noWeakModals = {
|
|
336
510
|
id: "no-weak-modals",
|
|
337
511
|
description: 'Flag hedged modal claims like "can help" and "might improve"',
|
|
338
|
-
defaults: { modals: DEFAULT_MODALS, verbs:
|
|
512
|
+
defaults: { modals: DEFAULT_MODALS, verbs: DEFAULT_VERBS2 },
|
|
339
513
|
help: "Hedged modal claims sound tentative. Replace them with the outcome, capability, or proof you can stand behind.",
|
|
340
514
|
check({ text, sourceMap, options }) {
|
|
341
515
|
const diagnostics = [];
|
|
342
516
|
const modals = new Set((options.modals?.length ? options.modals : DEFAULT_MODALS).map((value) => value.toLowerCase()));
|
|
343
|
-
const verbs = new Set((options.verbs?.length ? options.verbs :
|
|
517
|
+
const verbs = new Set((options.verbs?.length ? options.verbs : DEFAULT_VERBS2).map((value) => value.toLowerCase()));
|
|
344
518
|
const doc = createDoc(text);
|
|
345
519
|
const matches = doc.match("#Modal #Adverb? #Verb");
|
|
346
520
|
for (const occurrence of getMatchOccurrences(text, matches)) {
|
|
@@ -364,21 +538,27 @@ var noWeakModals = {
|
|
|
364
538
|
|
|
365
539
|
// src/index.ts
|
|
366
540
|
var ruleRegistry = /* @__PURE__ */ new Map([
|
|
541
|
+
["no-buzzword-stacks", noBuzzwordStacks],
|
|
367
542
|
["no-empty-transformation-claims", noEmptyTransformationClaims],
|
|
368
543
|
["no-expletive-openers", noExpletiveOpeners],
|
|
369
544
|
["no-filter-words", noFilterWords],
|
|
545
|
+
["no-hedge-words", noHedgeWords],
|
|
370
546
|
["no-nominalized-phrases", noNominalizedPhrases],
|
|
371
547
|
["no-passive-voice", noPassiveVoice],
|
|
548
|
+
["no-pronoun-led-claims", noPronounLedClaims],
|
|
372
549
|
["no-redundant-pairs", noRedundantPairs],
|
|
373
550
|
["no-stacked-adjectives", noStackedAdjectives],
|
|
374
551
|
["no-weak-modals", noWeakModals]
|
|
375
552
|
]);
|
|
376
553
|
export {
|
|
554
|
+
noBuzzwordStacks,
|
|
377
555
|
noEmptyTransformationClaims,
|
|
378
556
|
noExpletiveOpeners,
|
|
379
557
|
noFilterWords,
|
|
558
|
+
noHedgeWords,
|
|
380
559
|
noNominalizedPhrases,
|
|
381
560
|
noPassiveVoice,
|
|
561
|
+
noPronounLedClaims,
|
|
382
562
|
noRedundantPairs,
|
|
383
563
|
noStackedAdjectives,
|
|
384
564
|
noWeakModals,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils.ts","../src/no-expletive-openers.ts","../src/no-filter-words.ts","../src/no-empty-transformation-claims.ts","../src/no-nominalized-phrases.ts","../src/no-passive-voice.ts","../src/no-redundant-pairs.ts","../src/no-stacked-adjectives.ts","../src/no-weak-modals.ts","../src/index.ts"],"sourcesContent":["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, 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'\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, 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 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'\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 { Rule } from '@faircopy/core'\nimport { noExpletiveOpeners } from './no-expletive-openers.js'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\nimport { noRedundantPairs } from './no-redundant-pairs.js'\nimport { noStackedAdjectives } from './no-stacked-adjectives.js'\nimport { noWeakModals } from './no-weak-modals.js'\n\nexport { noExpletiveOpeners } from './no-expletive-openers.js'\nexport { noFilterWords } from './no-filter-words.js'\nexport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport { noRedundantPairs } from './no-redundant-pairs.js'\nexport { noStackedAdjectives } from './no-stacked-adjectives.js'\nexport { noWeakModals } from './no-weak-modals.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 { NoNominalizedPhrasesOptions } from './no-nominalized-phrases.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\nexport type { NoRedundantPairsOptions } from './no-redundant-pairs.js'\nexport type { NoStackedAdjectivesOptions } from './no-stacked-adjectives.js'\nexport type { NoWeakModalsOptions } from './no-weak-modals.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-empty-transformation-claims', noEmptyTransformationClaims as Rule],\n ['no-expletive-openers', noExpletiveOpeners as Rule],\n ['no-filter-words', noFilterWords as Rule],\n ['no-nominalized-phrases', noNominalizedPhrases as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n ['no-redundant-pairs', noRedundantPairs as Rule],\n ['no-stacked-adjectives', noStackedAdjectives as Rule],\n ['no-weak-modals', noWeakModals as Rule],\n])\n"],"mappings":"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","names":["DEFAULT_PHRASES","DEFAULT_PHRASES"]}
|
|
1
|
+
{"version":3,"sources":["../src/no-buzzword-stacks.ts","../src/utils.ts","../src/no-expletive-openers.ts","../src/no-filter-words.ts","../src/no-empty-transformation-claims.ts","../src/no-hedge-words.ts","../src/no-nominalized-phrases.ts","../src/no-passive-voice.ts","../src/no-pronoun-led-claims.ts","../src/no-redundant-pairs.ts","../src/no-stacked-adjectives.ts","../src/no-weak-modals.ts","../src/index.ts"],"sourcesContent":["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 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, 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 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'\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, 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'\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'\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 { Rule } from '@faircopy/core'\nimport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nimport { noExpletiveOpeners } from './no-expletive-openers.js'\nimport { noFilterWords } from './no-filter-words.js'\nimport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nimport { noHedgeWords } from './no-hedge-words.js'\nimport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nimport { noPassiveVoice } from './no-passive-voice.js'\nimport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nimport { noRedundantPairs } from './no-redundant-pairs.js'\nimport { noStackedAdjectives } from './no-stacked-adjectives.js'\nimport { noWeakModals } from './no-weak-modals.js'\n\nexport { noBuzzwordStacks } from './no-buzzword-stacks.js'\nexport { noExpletiveOpeners } from './no-expletive-openers.js'\nexport { noFilterWords } from './no-filter-words.js'\nexport { noEmptyTransformationClaims } from './no-empty-transformation-claims.js'\nexport { noHedgeWords } from './no-hedge-words.js'\nexport { noNominalizedPhrases } from './no-nominalized-phrases.js'\nexport { noPassiveVoice } from './no-passive-voice.js'\nexport { noPronounLedClaims } from './no-pronoun-led-claims.js'\nexport { noRedundantPairs } from './no-redundant-pairs.js'\nexport { noStackedAdjectives } from './no-stacked-adjectives.js'\nexport { noWeakModals } from './no-weak-modals.js'\nexport type { NoBuzzwordStacksOptions } from './no-buzzword-stacks.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 { NoHedgeWordsOptions } from './no-hedge-words.js'\nexport type { NoNominalizedPhrasesOptions } from './no-nominalized-phrases.js'\nexport type { NoPassiveVoiceOptions } from './no-passive-voice.js'\nexport type { NoPronounLedClaimsOptions } from './no-pronoun-led-claims.js'\nexport type { NoRedundantPairsOptions } from './no-redundant-pairs.js'\nexport type { NoStackedAdjectivesOptions } from './no-stacked-adjectives.js'\nexport type { NoWeakModalsOptions } from './no-weak-modals.js'\n\n/** All NLP rules keyed by their rule ID. */\nexport const ruleRegistry: Map<string, Rule> = new Map([\n ['no-buzzword-stacks', noBuzzwordStacks 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-hedge-words', noHedgeWords as Rule],\n ['no-nominalized-phrases', noNominalizedPhrases as Rule],\n ['no-passive-voice', noPassiveVoice as Rule],\n ['no-pronoun-led-claims', noPronounLedClaims as Rule],\n ['no-redundant-pairs', noRedundantPairs as Rule],\n ['no-stacked-adjectives', noStackedAdjectives as Rule],\n ['no-weak-modals', noWeakModals as Rule],\n])\n"],"mappings":"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","names":["DEFAULT_PHRASES","escapeRegex","escapeRegex","DEFAULT_PHRASES","escapeRegExp","DEFAULT_VERBS"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faircopy/rules-nlp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Optional NLP-powered ruleset for faircopy using compromise",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"prepublishOnly": "pnpm run build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@faircopy/core": "1.
|
|
22
|
+
"@faircopy/core": "1.9.0",
|
|
23
23
|
"compromise": "^14.15.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|