opengrammar-server 2.0.615350
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.npm.md +95 -0
- package/bin/opengrammar-server.js +111 -0
- package/dist/server.js +48639 -0
- package/package.json +80 -0
- package/server-node.ts +159 -0
- package/server.ts +15 -0
- package/src/analyzer.ts +542 -0
- package/src/dictionary.ts +1973 -0
- package/src/index.ts +978 -0
- package/src/nlp/nlp-engine.ts +17 -0
- package/src/nlp/tone-analyzer.ts +269 -0
- package/src/rephraser.ts +146 -0
- package/src/rules/categories/academic-writing.ts +182 -0
- package/src/rules/categories/adjectives-adverbs.ts +152 -0
- package/src/rules/categories/articles.ts +160 -0
- package/src/rules/categories/business-writing.ts +250 -0
- package/src/rules/categories/capitalization.ts +79 -0
- package/src/rules/categories/clarity.ts +117 -0
- package/src/rules/categories/common-errors.ts +601 -0
- package/src/rules/categories/confused-words.ts +219 -0
- package/src/rules/categories/conjunctions.ts +176 -0
- package/src/rules/categories/dangling-modifiers.ts +123 -0
- package/src/rules/categories/formality.ts +274 -0
- package/src/rules/categories/formatting-idioms.ts +323 -0
- package/src/rules/categories/gerund-infinitive.ts +274 -0
- package/src/rules/categories/grammar-advanced.ts +294 -0
- package/src/rules/categories/grammar.ts +286 -0
- package/src/rules/categories/inclusive-language.ts +280 -0
- package/src/rules/categories/nouns-pronouns.ts +233 -0
- package/src/rules/categories/prepositions-extended.ts +217 -0
- package/src/rules/categories/prepositions.ts +159 -0
- package/src/rules/categories/punctuation.ts +347 -0
- package/src/rules/categories/quantity-agreement.ts +200 -0
- package/src/rules/categories/readability.ts +293 -0
- package/src/rules/categories/sentence-structure.ts +100 -0
- package/src/rules/categories/spelling-advanced.ts +164 -0
- package/src/rules/categories/spelling.ts +119 -0
- package/src/rules/categories/style-tone.ts +511 -0
- package/src/rules/categories/style.ts +78 -0
- package/src/rules/categories/subject-verb-agreement.ts +201 -0
- package/src/rules/categories/tone-rules.ts +206 -0
- package/src/rules/categories/verb-tense.ts +582 -0
- package/src/rules/context-filter.ts +446 -0
- package/src/rules/index.ts +96 -0
- package/src/rules/ruleset-part1-cj-pu-sp.json +657 -0
- package/src/rules/ruleset-part1-np-ad-aa-pr.json +831 -0
- package/src/rules/ruleset-part1-ss-vt.json +907 -0
- package/src/rules/ruleset-part2-cw-st-nf.json +318 -0
- package/src/rules/ruleset-part3-aw-bw-il-rd.json +161 -0
- package/src/rules/types.ts +79 -0
- package/src/shared-types.ts +152 -0
- package/src/spellchecker.ts +418 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"generated_for": "OpenGrammar Engine",
|
|
4
|
+
"part": "2A_of_3",
|
|
5
|
+
"categories": [
|
|
6
|
+
{
|
|
7
|
+
"code": "CW",
|
|
8
|
+
"name": "Commonly Confused Words",
|
|
9
|
+
"rules": [
|
|
10
|
+
{
|
|
11
|
+
"rule_id": "CW_001",
|
|
12
|
+
"subcategory": "Homophones",
|
|
13
|
+
"title": "affect vs effect",
|
|
14
|
+
"pattern_description": "affect (verb: to influence) vs effect (noun: result; verb: to bring about)",
|
|
15
|
+
"examples": {
|
|
16
|
+
"incorrect": ["The weather will effect my mood.", "The new law had a great affect."],
|
|
17
|
+
"correct": ["The weather will affect my mood.", "The new law had a great effect."]
|
|
18
|
+
},
|
|
19
|
+
"severity": "error"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"rule_id": "CW_002",
|
|
23
|
+
"subcategory": "Homophones",
|
|
24
|
+
"title": "accept vs except",
|
|
25
|
+
"pattern_description": "accept (verb: receive) vs except (prep: excluding)",
|
|
26
|
+
"examples": {
|
|
27
|
+
"incorrect": ["I will except your offer.", "Everyone accept John came."],
|
|
28
|
+
"correct": ["I will accept your offer.", "Everyone except John came."]
|
|
29
|
+
},
|
|
30
|
+
"severity": "error"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"rule_id": "CW_003",
|
|
34
|
+
"subcategory": "Homophones",
|
|
35
|
+
"title": "complement vs compliment",
|
|
36
|
+
"pattern_description": "complement (complete/enhance) vs compliment (praise)",
|
|
37
|
+
"severity": "warning"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"rule_id": "CW_004",
|
|
41
|
+
"subcategory": "Homophones",
|
|
42
|
+
"title": "principal vs principle",
|
|
43
|
+
"pattern_description": "principal (main/head) vs principle (rule/belief)",
|
|
44
|
+
"severity": "warning"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"rule_id": "CW_005",
|
|
48
|
+
"subcategory": "Homophones",
|
|
49
|
+
"title": "stationary vs stationery",
|
|
50
|
+
"pattern_description": "stationary (not moving) vs stationery (writing materials)",
|
|
51
|
+
"severity": "warning"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"rule_id": "CW_006",
|
|
55
|
+
"subcategory": "Homophones",
|
|
56
|
+
"title": "loose vs lose",
|
|
57
|
+
"pattern_description": "loose (adjective: not tight) vs lose (verb: to misplace/fail)",
|
|
58
|
+
"severity": "error"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"rule_id": "CW_007",
|
|
62
|
+
"subcategory": "Homophones",
|
|
63
|
+
"title": "advise vs advice",
|
|
64
|
+
"pattern_description": "advise (verb) vs advice (noun)",
|
|
65
|
+
"severity": "error"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"rule_id": "CW_008",
|
|
69
|
+
"subcategory": "Homophones",
|
|
70
|
+
"title": "practise vs practice",
|
|
71
|
+
"pattern_description": "BrE: practise (verb) / practice (noun). AmE: practice for both.",
|
|
72
|
+
"severity": "warning"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"rule_id": "CW_009",
|
|
76
|
+
"subcategory": "Near-Homophones",
|
|
77
|
+
"title": "ensure vs insure vs assure",
|
|
78
|
+
"pattern_description": "ensure (make certain), insure (financial protection), assure (remove doubt from a person)",
|
|
79
|
+
"severity": "warning"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"rule_id": "CW_010",
|
|
83
|
+
"subcategory": "Near-Homophones",
|
|
84
|
+
"title": "farther vs further",
|
|
85
|
+
"pattern_description": "farther (physical distance), further (degree/metaphorical)",
|
|
86
|
+
"severity": "suggestion"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"rule_id": "CW_011",
|
|
90
|
+
"subcategory": "Semantic",
|
|
91
|
+
"title": "imply vs infer",
|
|
92
|
+
"pattern_description": "imply (speaker suggests), infer (listener deduces)",
|
|
93
|
+
"severity": "warning"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"rule_id": "CW_012",
|
|
97
|
+
"subcategory": "Semantic",
|
|
98
|
+
"title": "emigrate vs immigrate",
|
|
99
|
+
"pattern_description": "emigrate (leave a country), immigrate (enter a country)",
|
|
100
|
+
"severity": "warning"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"rule_id": "CW_013",
|
|
104
|
+
"subcategory": "Semantic",
|
|
105
|
+
"title": "continual vs continuous",
|
|
106
|
+
"pattern_description": "continual (repeated with breaks), continuous (uninterrupted)",
|
|
107
|
+
"severity": "suggestion"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"rule_id": "CW_014",
|
|
111
|
+
"subcategory": "Semantic",
|
|
112
|
+
"title": "disinterested vs uninterested",
|
|
113
|
+
"pattern_description": "disinterested (impartial), uninterested (bored/not interested)",
|
|
114
|
+
"severity": "suggestion"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"rule_id": "CW_015",
|
|
118
|
+
"subcategory": "Semantic",
|
|
119
|
+
"title": "elicit vs illicit",
|
|
120
|
+
"pattern_description": "elicit (draw out), illicit (illegal)",
|
|
121
|
+
"severity": "error"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"rule_id": "CW_016",
|
|
125
|
+
"subcategory": "Semantic",
|
|
126
|
+
"title": "precede vs proceed",
|
|
127
|
+
"pattern_description": "precede (come before), proceed (move forward)",
|
|
128
|
+
"severity": "error"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"rule_id": "CW_017",
|
|
132
|
+
"subcategory": "Semantic",
|
|
133
|
+
"title": "adverse vs averse",
|
|
134
|
+
"pattern_description": "adverse (harmful/unfavorable), averse (reluctant/opposed)",
|
|
135
|
+
"severity": "warning"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"rule_id": "CW_018",
|
|
139
|
+
"subcategory": "Semantic",
|
|
140
|
+
"title": "allusion vs illusion",
|
|
141
|
+
"pattern_description": "allusion (indirect reference), illusion (false perception)",
|
|
142
|
+
"severity": "warning"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"rule_id": "CW_019",
|
|
146
|
+
"subcategory": "Semantic",
|
|
147
|
+
"title": "cite vs site vs sight",
|
|
148
|
+
"pattern_description": "cite (quote/reference), site (location), sight (vision/view)",
|
|
149
|
+
"severity": "error"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"rule_id": "CW_020",
|
|
153
|
+
"subcategory": "Semantic",
|
|
154
|
+
"title": "conscious vs conscience",
|
|
155
|
+
"pattern_description": "conscious (aware), conscience (moral sense)",
|
|
156
|
+
"severity": "warning"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"rule_id": "CW_021",
|
|
160
|
+
"subcategory": "Semantic",
|
|
161
|
+
"title": "dessert vs desert",
|
|
162
|
+
"pattern_description": "dessert (sweet food), desert (arid land / abandon)",
|
|
163
|
+
"severity": "error"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"rule_id": "CW_022",
|
|
167
|
+
"subcategory": "Semantic",
|
|
168
|
+
"title": "whose vs who's",
|
|
169
|
+
"pattern_description": "whose (possessive), who's (who is/who has)",
|
|
170
|
+
"severity": "error"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"rule_id": "CW_023",
|
|
174
|
+
"subcategory": "Semantic",
|
|
175
|
+
"title": "weather vs whether",
|
|
176
|
+
"pattern_description": "weather (climate), whether (if)",
|
|
177
|
+
"severity": "error"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"rule_id": "CW_024",
|
|
181
|
+
"subcategory": "Semantic",
|
|
182
|
+
"title": "than vs then",
|
|
183
|
+
"pattern_description": "than (comparison), then (time/sequence)",
|
|
184
|
+
"severity": "error"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"rule_id": "CW_025",
|
|
188
|
+
"subcategory": "Semantic",
|
|
189
|
+
"title": "lie vs lay",
|
|
190
|
+
"pattern_description": "lie (recline, intransitive: lie/lay/lain), lay (put down, transitive: lay/laid/laid)",
|
|
191
|
+
"severity": "error"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"rule_id": "CW_026",
|
|
195
|
+
"subcategory": "Semantic",
|
|
196
|
+
"title": "hanged vs hung",
|
|
197
|
+
"pattern_description": "hanged (execution), hung (suspended an object)",
|
|
198
|
+
"severity": "warning"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"rule_id": "CW_027",
|
|
202
|
+
"subcategory": "Semantic",
|
|
203
|
+
"title": "historic vs historical",
|
|
204
|
+
"pattern_description": "historic (important/famous), historical (relating to history)",
|
|
205
|
+
"severity": "suggestion"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"rule_id": "CW_028",
|
|
209
|
+
"subcategory": "Semantic",
|
|
210
|
+
"title": "literal vs figurative",
|
|
211
|
+
"pattern_description": "Detect misuse of 'literally' for emphasis when meaning is figurative",
|
|
212
|
+
"severity": "suggestion"
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"code": "ST",
|
|
218
|
+
"name": "Style & Tone",
|
|
219
|
+
"rules": [
|
|
220
|
+
{
|
|
221
|
+
"rule_id": "ST_001",
|
|
222
|
+
"subcategory": "Wordiness",
|
|
223
|
+
"title": "Redundant pairs",
|
|
224
|
+
"pattern_description": "each and every, first and foremost, null and void, true and accurate, any and all, various and sundry, aid and abet, cease and desist, full and complete",
|
|
225
|
+
"severity": "suggestion"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"rule_id": "ST_002",
|
|
229
|
+
"subcategory": "Wordiness",
|
|
230
|
+
"title": "Inflated phrases",
|
|
231
|
+
"pattern_description": "at this point in time → now, in the final analysis → finally, it goes without saying → (delete), needless to say → (delete), it is worth noting that → (delete), as a matter of fact → in fact, for all intents and purposes → (simplify)",
|
|
232
|
+
"severity": "suggestion"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"rule_id": "ST_003",
|
|
236
|
+
"subcategory": "Hedging",
|
|
237
|
+
"title": "Excessive hedging language",
|
|
238
|
+
"pattern_description": "Detect overuse: sort of, kind of, somewhat, rather, fairly, quite, perhaps, maybe, possibly, seemingly, appears to, tends to, in some cases, to some extent, it could be argued",
|
|
239
|
+
"severity": "suggestion"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"rule_id": "ST_004",
|
|
243
|
+
"subcategory": "Filler",
|
|
244
|
+
"title": "Filler words and phrases",
|
|
245
|
+
"pattern_description": "basically, actually, honestly, literally, essentially, definitely, absolutely, totally, completely, really, very, just, quite, rather, somewhat, pretty (as intensifier)",
|
|
246
|
+
"severity": "suggestion"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"rule_id": "ST_005",
|
|
250
|
+
"subcategory": "Nominalizations",
|
|
251
|
+
"title": "Nominalization (hidden verb)",
|
|
252
|
+
"pattern_description": "make a decision → decide, give consideration to → consider, make an improvement → improve, provide assistance → assist, conduct an investigation → investigate, perform an analysis → analyze, take into consideration → consider, have a discussion → discuss, make a recommendation → recommend, give an indication → indicate, provide an explanation → explain, make a determination → determine",
|
|
253
|
+
"severity": "suggestion"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"rule_id": "ST_006",
|
|
257
|
+
"subcategory": "Clichés",
|
|
258
|
+
"title": "Overused clichés",
|
|
259
|
+
"pattern_description": "at the end of the day, think outside the box, low-hanging fruit, move the needle, synergy, paradigm shift, take it to the next level, game changer, deep dive, circle back, touch base, on the same page, push the envelope, best practices, going forward, leverage (as verb), bandwidth (non-technical), drill down, scalable, actionable, value-add, stakeholders, streamline, optimize",
|
|
260
|
+
"severity": "suggestion"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"code": "NF",
|
|
266
|
+
"name": "Numbers & Formatting",
|
|
267
|
+
"rules": [
|
|
268
|
+
{
|
|
269
|
+
"rule_id": "NF_001",
|
|
270
|
+
"subcategory": "Number Spelling",
|
|
271
|
+
"title": "Spell out numbers under 10",
|
|
272
|
+
"pattern_description": "In formal prose, spell out single-digit numbers (1→one, 2→two ... 9→nine)",
|
|
273
|
+
"severity": "suggestion"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"rule_id": "NF_002",
|
|
277
|
+
"subcategory": "Number Spelling",
|
|
278
|
+
"title": "Spell out numbers at sentence start",
|
|
279
|
+
"pattern_description": "Never start a sentence with a numeral: '100 people came' → 'One hundred people came' or restructure",
|
|
280
|
+
"severity": "warning"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"rule_id": "NF_003",
|
|
284
|
+
"subcategory": "Number Consistency",
|
|
285
|
+
"title": "Mixed numeral and word forms",
|
|
286
|
+
"pattern_description": "Don't mix: 'We need 3 chairs and twelve tables' → be consistent",
|
|
287
|
+
"severity": "suggestion"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"rule_id": "NF_004",
|
|
291
|
+
"subcategory": "Units",
|
|
292
|
+
"title": "Missing space before unit",
|
|
293
|
+
"pattern_description": "10kg → 10 kg, 5cm → 5 cm (but 5% is acceptable without space)",
|
|
294
|
+
"severity": "suggestion"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"rule_id": "NF_005",
|
|
298
|
+
"subcategory": "Date Format",
|
|
299
|
+
"title": "Ambiguous date formats",
|
|
300
|
+
"pattern_description": "Warn on MM/DD/YYYY vs DD/MM/YYYY ambiguity when day ≤ 12",
|
|
301
|
+
"severity": "suggestion"
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"rule_id": "NF_006",
|
|
305
|
+
"subcategory": "Abbreviations",
|
|
306
|
+
"title": "Inconsistent abbreviation use",
|
|
307
|
+
"pattern_description": "First use should spell out: 'Use Artificial Intelligence (AI) first, then AI thereafter'",
|
|
308
|
+
"severity": "suggestion"
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
"implementation_notes": {
|
|
314
|
+
"CW": "Confused word detection benefits from POS tagging. affect/effect requires knowing if verb or noun is expected. Many can be regex with contextual hints.",
|
|
315
|
+
"ST": "Style rules should be severity:suggestion and respect language_register. Nominalization detection benefits from NLP verb-noun mapping.",
|
|
316
|
+
"NF": "Number rules need sentence-boundary awareness. Unit detection is regex-friendly."
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"generated_for": "OpenGrammar Engine",
|
|
4
|
+
"part": "3_of_3",
|
|
5
|
+
"categories": [
|
|
6
|
+
{
|
|
7
|
+
"code": "AW",
|
|
8
|
+
"name": "Academic Writing",
|
|
9
|
+
"description": "Rules specific to scholarly, research, and academic prose.",
|
|
10
|
+
"rules": [
|
|
11
|
+
{
|
|
12
|
+
"rule_id": "AW_001",
|
|
13
|
+
"title": "First person in academic writing",
|
|
14
|
+
"pattern_description": "Flag 'I think', 'I believe', 'I feel', 'In my opinion' in academic register",
|
|
15
|
+
"severity": "suggestion"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"rule_id": "AW_002",
|
|
19
|
+
"title": "Contractions in academic writing",
|
|
20
|
+
"pattern_description": "Flag don't, can't, won't, it's, they're etc. in formal academic text",
|
|
21
|
+
"severity": "suggestion"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"rule_id": "AW_003",
|
|
25
|
+
"title": "Vague quantifiers",
|
|
26
|
+
"pattern_description": "Flag 'a lot of', 'lots of', 'tons of', 'a bunch of' — prefer precise data",
|
|
27
|
+
"severity": "suggestion"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"rule_id": "AW_004",
|
|
31
|
+
"title": "Weasel words",
|
|
32
|
+
"pattern_description": "some people say, it is believed, studies show, experts say, research suggests — cite specifically",
|
|
33
|
+
"severity": "suggestion"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"rule_id": "AW_005",
|
|
37
|
+
"title": "Rhetorical questions in papers",
|
|
38
|
+
"pattern_description": "Questions in academic papers should generally be avoided",
|
|
39
|
+
"severity": "suggestion"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"rule_id": "AW_006",
|
|
43
|
+
"title": "Informal transitions",
|
|
44
|
+
"pattern_description": "Plus, Also at sentence start, So, Anyway, Well — use Moreover, Furthermore, Additionally",
|
|
45
|
+
"severity": "suggestion"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"code": "BW",
|
|
51
|
+
"name": "Business Writing",
|
|
52
|
+
"description": "Rules for professional emails, reports, memos.",
|
|
53
|
+
"rules": [
|
|
54
|
+
{
|
|
55
|
+
"rule_id": "BW_001",
|
|
56
|
+
"title": "Passive in instructions",
|
|
57
|
+
"pattern_description": "In instructions, prefer active: 'Submit the report' not 'The report should be submitted'",
|
|
58
|
+
"severity": "suggestion"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"rule_id": "BW_002",
|
|
62
|
+
"title": "Empty openers",
|
|
63
|
+
"pattern_description": "I am writing to inform you, This email is to let you know, I wanted to reach out — get to the point",
|
|
64
|
+
"severity": "suggestion"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"rule_id": "BW_003",
|
|
68
|
+
"title": "Weak verbs",
|
|
69
|
+
"pattern_description": "utilize→use, implement→do/start, facilitate→help, commence→begin/start, terminate→end, endeavor→try",
|
|
70
|
+
"severity": "suggestion"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"rule_id": "BW_004",
|
|
74
|
+
"title": "Double negatives for politeness",
|
|
75
|
+
"pattern_description": "'not uncommon'→'common', 'not unlike'→'similar to', 'not insignificant'→'significant'",
|
|
76
|
+
"severity": "suggestion"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"rule_id": "BW_005",
|
|
80
|
+
"title": "Overly complex words",
|
|
81
|
+
"pattern_description": "ameliorate→improve, ascertain→find out, cognizant→aware, elucidate→explain, expedite→speed up, juxtapose→compare, paradigm→model, remuneration→pay, ubiquitous→widespread, veracious→truthful",
|
|
82
|
+
"severity": "suggestion"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"code": "IL",
|
|
88
|
+
"name": "Inclusive Language",
|
|
89
|
+
"description": "Gender-neutral, disability-aware, culturally sensitive language.",
|
|
90
|
+
"rules": [
|
|
91
|
+
{
|
|
92
|
+
"rule_id": "IL_001",
|
|
93
|
+
"title": "Gendered job titles",
|
|
94
|
+
"pattern_description": "chairman→chairperson/chair, fireman→firefighter, policeman→police officer, stewardess→flight attendant, mailman→mail carrier, mankind→humankind, manpower→workforce, man-made→artificial/synthetic",
|
|
95
|
+
"severity": "suggestion"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"rule_id": "IL_002",
|
|
99
|
+
"title": "Generic 'he'",
|
|
100
|
+
"pattern_description": "'When a student submits his work' → 'their work' or 'his or her work'",
|
|
101
|
+
"severity": "suggestion"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"rule_id": "IL_003",
|
|
105
|
+
"title": "Person-first language",
|
|
106
|
+
"pattern_description": "'disabled person'→'person with a disability', 'autistic child'→'child with autism', 'blind person'→'person who is blind'",
|
|
107
|
+
"severity": "suggestion"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"rule_id": "IL_004",
|
|
111
|
+
"title": "Ableist language",
|
|
112
|
+
"pattern_description": "'falling on deaf ears', 'blind spot' (metaphorical), 'lame excuse', 'crippled by', 'suffers from', 'confined to a wheelchair'→'uses a wheelchair'",
|
|
113
|
+
"severity": "suggestion"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"rule_id": "IL_005",
|
|
117
|
+
"title": "Age-related language",
|
|
118
|
+
"pattern_description": "'elderly'→'older adults', 'senior citizens'→'older adults'",
|
|
119
|
+
"severity": "suggestion"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"code": "RD",
|
|
125
|
+
"name": "Readability",
|
|
126
|
+
"description": "Sentence complexity, reading level, clarity metrics.",
|
|
127
|
+
"rules": [
|
|
128
|
+
{
|
|
129
|
+
"rule_id": "RD_001",
|
|
130
|
+
"title": "Very long sentences",
|
|
131
|
+
"pattern_description": "Flag sentences over 40 words — suggest breaking up",
|
|
132
|
+
"severity": "warning"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"rule_id": "RD_002",
|
|
136
|
+
"title": "Long paragraphs",
|
|
137
|
+
"pattern_description": "Flag paragraphs over 150 words",
|
|
138
|
+
"severity": "suggestion"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"rule_id": "RD_003",
|
|
142
|
+
"title": "Complex word alternatives",
|
|
143
|
+
"pattern_description": "Suggest simpler synonyms: utilize→use, demonstrate→show, approximately→about, subsequent→next, prior→before, commence→start, terminate→end, sufficient→enough, endeavor→try, nevertheless→still, notwithstanding→despite, heretofore→previously, aforementioned→previous, inasmuch→since, henceforth→from now on, thereby→by this, whereby→by which, therein→in that",
|
|
144
|
+
"severity": "suggestion"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"rule_id": "RD_004",
|
|
148
|
+
"title": "Consecutive prepositional phrases",
|
|
149
|
+
"pattern_description": "Flag 3+ prepositional phrases in a row — indicates complexity",
|
|
150
|
+
"severity": "suggestion"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"rule_id": "RD_005",
|
|
154
|
+
"title": "Repeated sentence beginnings",
|
|
155
|
+
"pattern_description": "Flag when 3+ consecutive sentences start with the same word",
|
|
156
|
+
"severity": "suggestion"
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Issue } from '../shared-types.js';
|
|
2
|
+
|
|
3
|
+
export type RuleCategory = 'spelling' | 'grammar' | 'style' | 'clarity';
|
|
4
|
+
|
|
5
|
+
export interface BaseRule {
|
|
6
|
+
id: string;
|
|
7
|
+
category: RuleCategory;
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RegexRule extends BaseRule {
|
|
12
|
+
type: 'regex';
|
|
13
|
+
pattern: RegExp;
|
|
14
|
+
suggestion: string | ((match: RegExpExecArray) => string);
|
|
15
|
+
/**
|
|
16
|
+
* Execute regex on text and return any matching issues.
|
|
17
|
+
*/
|
|
18
|
+
check: (text: string) => Issue[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface NLPRule extends BaseRule {
|
|
22
|
+
type: 'nlp';
|
|
23
|
+
/**
|
|
24
|
+
* Returns a suggestion based on the NLP match.
|
|
25
|
+
*/
|
|
26
|
+
suggestion: string;
|
|
27
|
+
/**
|
|
28
|
+
* Execute compromise match logic on the parsed document.
|
|
29
|
+
*/
|
|
30
|
+
check: (text: string, doc: any) => Issue[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Rule = RegexRule | NLPRule;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Helper to easily create Regex-based rules.
|
|
37
|
+
*/
|
|
38
|
+
export function createRegexRule({
|
|
39
|
+
id,
|
|
40
|
+
category,
|
|
41
|
+
reason,
|
|
42
|
+
pattern,
|
|
43
|
+
suggestion,
|
|
44
|
+
}: Omit<RegexRule, 'type' | 'check'>): RegexRule {
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
type: 'regex',
|
|
48
|
+
category,
|
|
49
|
+
reason,
|
|
50
|
+
pattern,
|
|
51
|
+
suggestion,
|
|
52
|
+
check: (text: string): Issue[] => {
|
|
53
|
+
const issues: Issue[] = [];
|
|
54
|
+
let match: RegExpExecArray | null;
|
|
55
|
+
|
|
56
|
+
// Ensure the regex has the 'g' flag if we want all matches
|
|
57
|
+
const checkPattern = new RegExp(
|
|
58
|
+
pattern.source,
|
|
59
|
+
pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
while ((match = checkPattern.exec(text)) !== null) {
|
|
63
|
+
issues.push({
|
|
64
|
+
type: category,
|
|
65
|
+
original: match[0],
|
|
66
|
+
suggestion:
|
|
67
|
+
typeof suggestion === 'function'
|
|
68
|
+
? suggestion(match)
|
|
69
|
+
: match[0].replace(pattern, suggestion),
|
|
70
|
+
reason,
|
|
71
|
+
offset: match.index,
|
|
72
|
+
length: match[0].length,
|
|
73
|
+
id: `${id}-${match.index}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return issues;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|