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.
Files changed (53) hide show
  1. package/README.npm.md +95 -0
  2. package/bin/opengrammar-server.js +111 -0
  3. package/dist/server.js +48639 -0
  4. package/package.json +80 -0
  5. package/server-node.ts +159 -0
  6. package/server.ts +15 -0
  7. package/src/analyzer.ts +542 -0
  8. package/src/dictionary.ts +1973 -0
  9. package/src/index.ts +978 -0
  10. package/src/nlp/nlp-engine.ts +17 -0
  11. package/src/nlp/tone-analyzer.ts +269 -0
  12. package/src/rephraser.ts +146 -0
  13. package/src/rules/categories/academic-writing.ts +182 -0
  14. package/src/rules/categories/adjectives-adverbs.ts +152 -0
  15. package/src/rules/categories/articles.ts +160 -0
  16. package/src/rules/categories/business-writing.ts +250 -0
  17. package/src/rules/categories/capitalization.ts +79 -0
  18. package/src/rules/categories/clarity.ts +117 -0
  19. package/src/rules/categories/common-errors.ts +601 -0
  20. package/src/rules/categories/confused-words.ts +219 -0
  21. package/src/rules/categories/conjunctions.ts +176 -0
  22. package/src/rules/categories/dangling-modifiers.ts +123 -0
  23. package/src/rules/categories/formality.ts +274 -0
  24. package/src/rules/categories/formatting-idioms.ts +323 -0
  25. package/src/rules/categories/gerund-infinitive.ts +274 -0
  26. package/src/rules/categories/grammar-advanced.ts +294 -0
  27. package/src/rules/categories/grammar.ts +286 -0
  28. package/src/rules/categories/inclusive-language.ts +280 -0
  29. package/src/rules/categories/nouns-pronouns.ts +233 -0
  30. package/src/rules/categories/prepositions-extended.ts +217 -0
  31. package/src/rules/categories/prepositions.ts +159 -0
  32. package/src/rules/categories/punctuation.ts +347 -0
  33. package/src/rules/categories/quantity-agreement.ts +200 -0
  34. package/src/rules/categories/readability.ts +293 -0
  35. package/src/rules/categories/sentence-structure.ts +100 -0
  36. package/src/rules/categories/spelling-advanced.ts +164 -0
  37. package/src/rules/categories/spelling.ts +119 -0
  38. package/src/rules/categories/style-tone.ts +511 -0
  39. package/src/rules/categories/style.ts +78 -0
  40. package/src/rules/categories/subject-verb-agreement.ts +201 -0
  41. package/src/rules/categories/tone-rules.ts +206 -0
  42. package/src/rules/categories/verb-tense.ts +582 -0
  43. package/src/rules/context-filter.ts +446 -0
  44. package/src/rules/index.ts +96 -0
  45. package/src/rules/ruleset-part1-cj-pu-sp.json +657 -0
  46. package/src/rules/ruleset-part1-np-ad-aa-pr.json +831 -0
  47. package/src/rules/ruleset-part1-ss-vt.json +907 -0
  48. package/src/rules/ruleset-part2-cw-st-nf.json +318 -0
  49. package/src/rules/ruleset-part3-aw-bw-il-rd.json +161 -0
  50. package/src/rules/types.ts +79 -0
  51. package/src/shared-types.ts +152 -0
  52. package/src/spellchecker.ts +418 -0
  53. package/tsconfig.json +25 -0
@@ -0,0 +1,152 @@
1
+ import type { Issue } from '../../shared-types.js';
2
+ import { createRegexRule, type Rule } from '../types.js';
3
+
4
+ export const adjectivesAdverbsRules: Rule[] = [
5
+ // ═══ Good/Well Confusion (AA_GDW) ═══
6
+ createRegexRule({
7
+ id: 'AA_GDW_plays',
8
+ category: 'grammar',
9
+ pattern:
10
+ /\b(plays|writes|performs|works|functions|runs|speaks|sings|dances|cooks|drives|reads|does|handles|communicates|operates|manages)\s+good\b/i,
11
+ suggestion: (m) => `${m[1]} well`,
12
+ reason: '"Good" is an adjective. Use the adverb "well" after action verbs.',
13
+ }),
14
+
15
+ // ═══ Adjective vs Adverb Confusion (AA_ADV) ═══
16
+ createRegexRule({
17
+ id: 'AA_ADV_beautiful',
18
+ category: 'grammar',
19
+ pattern: /\b(sings|plays|dances|writes|speaks|performs)\s+beautiful\b/i,
20
+ suggestion: (m) => `${m[1]} beautifully`,
21
+ reason: 'Use the adverb "beautifully" to modify a verb, not the adjective "beautiful".',
22
+ }),
23
+ createRegexRule({
24
+ id: 'AA_ADV_quick',
25
+ category: 'grammar',
26
+ pattern: /\b(runs|moves|walks|types|responds|reacts|acts|thinks|learns|grows)\s+quick\b/i,
27
+ suggestion: (m) => `${m[1]} quickly`,
28
+ reason: 'Use the adverb "quickly" to modify a verb, not the adjective "quick".',
29
+ }),
30
+ createRegexRule({
31
+ id: 'AA_ADV_bad',
32
+ category: 'grammar',
33
+ pattern: /\b(performs|writes|speaks|plays|behaves|acts|handles)\s+bad\b/i,
34
+ suggestion: (m) => `${m[1]} badly`,
35
+ reason: 'Use the adverb "badly" to modify a verb. "Bad" is an adjective.',
36
+ }),
37
+ createRegexRule({
38
+ id: 'AA_ADV_real',
39
+ category: 'grammar',
40
+ pattern:
41
+ /\b(real)\s+(good|nice|fast|quick|bad|hard|easy|big|small|important|interesting|beautiful|difficult)\b/i,
42
+ suggestion: (m) => `really ${m[2]}`,
43
+ reason: '"Real" is an adjective. Use the adverb "really" to modify another adjective.',
44
+ }),
45
+
46
+ // ═══ Double Comparatives (AA_CMP) ═══
47
+ createRegexRule({
48
+ id: 'AA_CMP_more_er',
49
+ category: 'grammar',
50
+ pattern:
51
+ /\bmore\s+(bigger|smaller|taller|shorter|faster|slower|older|younger|easier|harder|simpler|wider|narrower|louder|quieter|nicer|safer|cleaner|closer|newer|darker|brighter|stronger|weaker|smarter|cheaper|richer|poorer|thinner|thicker|longer|lighter|heavier|cooler|warmer|hotter|colder)\b/i,
52
+ suggestion: (m) => m[1] || '',
53
+ reason: 'Do not combine "more" with the -er comparative form. Use one or the other.',
54
+ }),
55
+ createRegexRule({
56
+ id: 'AA_CMP_most_est',
57
+ category: 'grammar',
58
+ pattern:
59
+ /\bmost\s+(biggest|smallest|tallest|shortest|fastest|slowest|oldest|youngest|easiest|hardest|simplest|widest|narrowest|loudest|quietest|nicest|safest|cleanest|closest|newest|darkest|brightest|strongest|weakest|smartest|cheapest|richest|poorest)\b/i,
60
+ suggestion: (m) => m[1] || '',
61
+ reason: 'Do not combine "most" with the -est superlative form. Use one or the other.',
62
+ }),
63
+
64
+ // ═══ Irregular Comparatives (AA_IRR) ═══
65
+ createRegexRule({
66
+ id: 'AA_IRR_gooder',
67
+ category: 'grammar',
68
+ pattern: /\bgooder\b/i,
69
+ suggestion: 'better',
70
+ reason: 'The comparative of "good" is "better", not "gooder".',
71
+ }),
72
+ createRegexRule({
73
+ id: 'AA_IRR_goodest',
74
+ category: 'grammar',
75
+ pattern: /\bgoodest\b/i,
76
+ suggestion: 'best',
77
+ reason: 'The superlative of "good" is "best", not "goodest".',
78
+ }),
79
+ createRegexRule({
80
+ id: 'AA_IRR_badder',
81
+ category: 'grammar',
82
+ pattern: /\bbadder\b/i,
83
+ suggestion: 'worse',
84
+ reason: 'The comparative of "bad" is "worse", not "badder".',
85
+ }),
86
+ createRegexRule({
87
+ id: 'AA_IRR_baddest',
88
+ category: 'grammar',
89
+ pattern: /\bbaddest\b/i,
90
+ suggestion: 'worst',
91
+ reason: 'The superlative of "bad" is "worst", not "baddest".',
92
+ }),
93
+ createRegexRule({
94
+ id: 'AA_IRR_more_good',
95
+ category: 'grammar',
96
+ pattern: /\bmore\s+good\b/i,
97
+ suggestion: 'better',
98
+ reason: 'The comparative of "good" is "better", not "more good".',
99
+ }),
100
+ createRegexRule({
101
+ id: 'AA_IRR_most_good',
102
+ category: 'grammar',
103
+ pattern: /\bmost\s+good\b/i,
104
+ suggestion: 'best',
105
+ reason: 'The superlative of "good" is "best", not "most good".',
106
+ }),
107
+ createRegexRule({
108
+ id: 'AA_IRR_more_bad',
109
+ category: 'grammar',
110
+ pattern: /\bmore\s+bad\b/i,
111
+ suggestion: 'worse',
112
+ reason: 'The comparative of "bad" is "worse", not "more bad".',
113
+ }),
114
+ createRegexRule({
115
+ id: 'AA_IRR_most_bad',
116
+ category: 'grammar',
117
+ pattern: /\bmost\s+bad\b/i,
118
+ suggestion: 'worst',
119
+ reason: 'The superlative of "bad" is "worst", not "most bad".',
120
+ }),
121
+
122
+ // ═══ Absolute Adjectives (AA_ABS) ═══
123
+ createRegexRule({
124
+ id: 'AA_ABS_unique',
125
+ category: 'style',
126
+ pattern:
127
+ /\b(very|more|most|extremely|really|somewhat|rather|slightly|fairly)\s+(unique|perfect|complete|dead|infinite|impossible|universal|absolute|essential|fatal|final|total|eternal|entire|supreme|unanimous|empty|equal|round|square|pregnant)\b/i,
128
+ suggestion: (m: RegExpExecArray) => m[2] || '',
129
+ reason:
130
+ 'This is an absolute adjective and cannot logically be compared or intensified. Remove the modifier.',
131
+ }),
132
+
133
+ // ═══ Participial Adjectives: -ed vs -ing (AA_PAR) ═══
134
+ createRegexRule({
135
+ id: 'AA_PAR_boring',
136
+ category: 'grammar',
137
+ pattern:
138
+ /\b(I|he|she|we|they|you)\s+(am|is|are|was|were|feel|felt)\s+(boring|interesting|exciting|confusing|tiring|surprising|amazing|amusing|annoying|disappointing|embarrassing|exhausting|frightening|frustrating|inspiring|overwhelming|relaxing|satisfying|shocking|terrifying|thrilling|worrying)\b/i,
139
+ suggestion: (m) => `${m[1]} ${m[2]} ${(m[3] || '').replace(/ing$/, 'ed')}`,
140
+ reason:
141
+ 'Use the -ed form when describing how a person FEELS. The -ing form describes what CAUSES the feeling.',
142
+ }),
143
+ createRegexRule({
144
+ id: 'AA_PAR_bored',
145
+ category: 'grammar',
146
+ pattern:
147
+ /\b(the\s+)?(movie|book|film|show|game|class|lesson|lecture|speech|presentation|talk|story|news|weather|trip|journey|experience|event|meeting|conversation|discussion|debate|task|job|work|project|assignment)\s+(is|are|was|were)\s+(bored|interested|excited|confused|tired|surprised|amazed|amused|annoyed|disappointed|embarrassed|exhausted|frightened|frustrated|inspired|overwhelmed|relaxed|satisfied|shocked|terrified|thrilled|worried)\b/i,
148
+ suggestion: (m) => `${m[1] || ''}${m[2]} ${m[3]} ${(m[4] || '').replace(/ed$/, 'ing')}`,
149
+ reason:
150
+ 'Use the -ing form when describing something that CAUSES a feeling. The -ed form describes how people feel.',
151
+ }),
152
+ ];
@@ -0,0 +1,160 @@
1
+ import { createRegexRule, type Rule } from '../types.js';
2
+
3
+ /**
4
+ * ════════════════════════════════════════════════════════
5
+ * Article Rules — ART Module
6
+ * a/an distinction + missing articles + extra articles
7
+ * ════════════════════════════════════════════════════════
8
+ */
9
+ export const articleRules: Rule[] = [
10
+
11
+ // ─── A vs AN (vowel sound rule) ───
12
+ createRegexRule({
13
+ id: 'ART_a_vowel',
14
+ category: 'grammar',
15
+ // a before words starting with vowel SOUNDS (silent h)
16
+ pattern: /\ba\s+(honest|hour|heir|honor|honour|honorable|honourable|hourly|honesty|herb(\s|$))/i,
17
+ suggestion: (m) => `an ${m[1]}`,
18
+ reason: 'Use "an" before words with a vowel sound, including silent "h": "an honest person".',
19
+ }),
20
+ createRegexRule({
21
+ id: 'ART_an_consonant',
22
+ category: 'grammar',
23
+ pattern: /\ban\s+(hero|historic|historical|hotel|hospital|holiday|horror|horrible|hundred|huge|human|habit|hat|head|heart|help|hill|home|hope|house|hustle|hand|horse|hard|heavy|high|happy|hot|hole|hire)\b/i,
24
+ suggestion: (m) => `a ${m[1]}`,
25
+ reason: 'Use "a" before words with a consonant sound (including aspirated "h"): "a hero", "a hotel".',
26
+ }),
27
+ createRegexRule({
28
+ id: 'ART_an_unique',
29
+ category: 'grammar',
30
+ pattern: /\ban\s+(unique|user|union|uniform|united|unit|university|universe|usage|usual|utility|euro|European|euphemism|eulogy|ewe|one-)\b/i,
31
+ suggestion: (m) => `a ${m[1]}`,
32
+ reason: 'Use "a" before words starting with a "y" or "w" sound: "a unique", "a user".',
33
+ }),
34
+
35
+ // ─── DOUBLE ARTICLE ───
36
+ createRegexRule({
37
+ id: 'ART_double_the',
38
+ category: 'grammar',
39
+ pattern: /\bthe\s+the\b/i,
40
+ suggestion: 'the',
41
+ reason: 'Do not use "the" twice in a row.',
42
+ }),
43
+ createRegexRule({
44
+ id: 'ART_double_a',
45
+ category: 'grammar',
46
+ pattern: /\ba\s+a\b/i,
47
+ suggestion: 'a',
48
+ reason: 'Do not repeat the article "a".',
49
+ }),
50
+
51
+ // ─── CONFUSED ITS / IT'S ───
52
+ createRegexRule({
53
+ id: 'ART_its_contraction',
54
+ category: 'grammar',
55
+ pattern: /\bIts\s+(a|an|the|going|been|been|time|not|never|just|only|still|already|been)\b/,
56
+ suggestion: (m) => `It's ${m[1]}`,
57
+ reason: '"It\'s" (with apostrophe) is a contraction of "it is". "Its" is the possessive.',
58
+ }),
59
+ createRegexRule({
60
+ id: 'ART_its_contraction_lower',
61
+ category: 'grammar',
62
+ pattern: /\bits\s+(a|an|going|been|not|never|just|only|still|already|impossible|possible|clear|obvious|true|false|fine|okay|great|amazing|terrible|wonderful|awful|strange|weird|funny)\b/i,
63
+ suggestion: (m) => `it's ${m[1]}`,
64
+ reason: '"It\'s" (with apostrophe) is "it is". "Its" without apostrophe is the possessive.',
65
+ }),
66
+
67
+ // ─── THERE vs THEY'RE ───
68
+ createRegexRule({
69
+ id: 'ART_there_theyre',
70
+ category: 'grammar',
71
+ pattern: /\bThere\s+(going|coming|trying|waiting|planning|hoping|thinking|running|standing|sitting|looking|moving|working|playing|staying|leaving|arriving|calling|asking|telling|showing|helping|taking|making|doing)\b/,
72
+ suggestion: (m) => `They're ${m[1]}`,
73
+ reason: '"They\'re" means "they are". "There" refers to a place.',
74
+ }),
75
+ createRegexRule({
76
+ id: 'ART_there_theyre_lower',
77
+ category: 'grammar',
78
+ pattern: /\bthere\s+(going\s+to|coming\s+to|trying\s+to|waiting\s+for|planning\s+to|hoping\s+to)\b/i,
79
+ suggestion: (m) => `they're ${m[1]}`,
80
+ reason: '"They\'re" (they are) is different from "there" (a place) and "their" (possessive).',
81
+ }),
82
+
83
+ // ─── THEIR vs THERE ───
84
+ createRegexRule({
85
+ id: 'ART_their_there',
86
+ category: 'grammar',
87
+ pattern: /\bTheir\s+(were|was|is|are|has|have|had|will|would|could|should|might|may|must|can)\s+/,
88
+ suggestion: (m) => `There ${m[1]} `,
89
+ reason: '"There" refers to a location or introduces sentences. "Their" shows possession.',
90
+ }),
91
+ createRegexRule({
92
+ id: 'ART_their_there_lower',
93
+ category: 'grammar',
94
+ pattern: /\btheir\s+were\b/i,
95
+ suggestion: 'there were',
96
+ reason: '"There were" uses "there" (place/existence), not "their" (possessive).',
97
+ }),
98
+
99
+ // ─── PASSED vs PAST ───
100
+ createRegexRule({
101
+ id: 'ART_past_vs_passed',
102
+ category: 'grammar',
103
+ pattern: /\bshe\s+past\s+the\b/i,
104
+ suggestion: 'she passed the',
105
+ reason: '"Passed" is the verb (past tense of "pass"). "Past" is used as a preposition or noun.',
106
+ }),
107
+ createRegexRule({
108
+ id: 'ART_past_exam',
109
+ category: 'grammar',
110
+ pattern: /\b(he|she|I|they|we|you)\s+past\s+(the\s+)?(exam|test|interview|inspection|review|check)\b/i,
111
+ suggestion: (m) => `${m[1]} passed ${m[2] || ''}${m[3]}`,
112
+ reason: '"Passed" (verb) is the past tense of "pass". "Past" is not a verb.',
113
+ }),
114
+
115
+ // ─── DOUBLE COMPARATIVE ───
116
+ createRegexRule({
117
+ id: 'ART_most_worst',
118
+ category: 'grammar',
119
+ pattern: /\bthe\s+most\s+worst\b/i,
120
+ suggestion: 'the worst',
121
+ reason: '"Worst" is already a superlative. Do not add "most".',
122
+ }),
123
+ createRegexRule({
124
+ id: 'ART_more_prettier',
125
+ category: 'grammar',
126
+ pattern: /\bmore\s+(prettier|cleverer|wittier|funnier|happier|sadder|madder|thinner|fatter|taller|shorter|softer|harder|faster|slower|louder|quieter|richer|poorer|larger|smaller|bigger|younger|older|smarter|wiser|braver|kinder|nicer|sweeter)\b/i,
127
+ suggestion: (m) => m[1],
128
+ reason: `"${'"more prettier"'}" is a double comparative. "${'"Prettier"'}" is already the comparative form.`,
129
+ }),
130
+
131
+ // ─── REDUNDANT WORDS ───
132
+ createRegexRule({
133
+ id: 'ART_currently_now',
134
+ category: 'clarity',
135
+ pattern: /\bcurrently\s+now\b/i,
136
+ suggestion: 'currently',
137
+ reason: '"Currently" and "now" are redundant together. Use one.',
138
+ }),
139
+ createRegexRule({
140
+ id: 'ART_rsvp_please',
141
+ category: 'clarity',
142
+ pattern: /\bplease\s+RSVP\b/i,
143
+ suggestion: 'RSVP',
144
+ reason: '"RSVP" already means "please respond" (répondez s\'il vous plaît). "Please RSVP" is redundant.',
145
+ }),
146
+ createRegexRule({
147
+ id: 'ART_rsvp_to',
148
+ category: 'clarity',
149
+ pattern: /\bRSVP\s+to\s+(?:this|the)\s+(?:event|invitation|party|meeting)\b/i,
150
+ suggestion: (m) => m[0].replace(/RSVP\s+to/, 'RSVP for'),
151
+ reason: 'RSVP does not need "to" in formal usage.',
152
+ }),
153
+ createRegexRule({
154
+ id: 'ART_nodded_head',
155
+ category: 'clarity',
156
+ pattern: /\bnodded\s+(?:his|her|their|my|your|our)\s+head\b/i,
157
+ suggestion: 'nodded',
158
+ reason: '"Nodded" implies a head movement. "Nodded his head" is redundant.',
159
+ }),
160
+ ];
@@ -0,0 +1,250 @@
1
+ import type { Issue } from '../../shared-types.js';
2
+ import { createRegexRule, type Rule } from '../types.js';
3
+
4
+ /**
5
+ * ═══════════════════════════════════════════════════
6
+ * Business & Professional Writing (BW)
7
+ * Empty openers, corporate bloat, weak verbs
8
+ * ═══════════════════════════════════════════════════
9
+ */
10
+ export const businessWritingRules: Rule[] = [
11
+ // ═══ Empty Email Openers (BW_002) ═══
12
+ createRegexRule({
13
+ id: 'BW_writing_inform',
14
+ category: 'clarity',
15
+ pattern: /\bI\s+am\s+writing\s+to\s+inform\s+you\b/i,
16
+ suggestion: '(State the information directly)',
17
+ reason: '"I am writing to inform you" is an empty opener. Get to the point.',
18
+ }),
19
+ createRegexRule({
20
+ id: 'BW_email_let_know',
21
+ category: 'clarity',
22
+ pattern: /\bthis\s+email\s+is\s+to\s+let\s+you\s+know\b/i,
23
+ suggestion: '(State the information directly)',
24
+ reason: '"This email is to let you know" is filler. State the information directly.',
25
+ }),
26
+ createRegexRule({
27
+ id: 'BW_wanted_reach',
28
+ category: 'clarity',
29
+ pattern: /\bI\s+wanted\s+to\s+reach\s+out\b/i,
30
+ suggestion: '(State your purpose directly)',
31
+ reason: '"I wanted to reach out" is an empty opener. State your purpose directly.',
32
+ }),
33
+ createRegexRule({
34
+ id: 'BW_just_checking',
35
+ category: 'clarity',
36
+ pattern: /\bI\s+(am\s+)?just\s+checking\s+in\b/i,
37
+ suggestion: '(State your question directly)',
38
+ reason: '"Just checking in" is filler. Ask your question or state your purpose.',
39
+ }),
40
+ createRegexRule({
41
+ id: 'BW_touching_base',
42
+ category: 'clarity',
43
+ pattern: /\bI\s+(am\s+)?(just\s+)?touching\s+base\b/i,
44
+ suggestion: '(State your purpose)',
45
+ reason: '"Touching base" is vague corporate jargon. State your purpose clearly.',
46
+ }),
47
+ createRegexRule({
48
+ id: 'BW_hope_finds',
49
+ category: 'clarity',
50
+ pattern: /\bI\s+hope\s+this\s+(email|message)\s+finds\s+you\s+well\b/i,
51
+ suggestion: '(Skip or use a specific greeting)',
52
+ reason:
53
+ '"I hope this email finds you well" is a cliché opener. Get to the point or use a specific greeting.',
54
+ }),
55
+ createRegexRule({
56
+ id: 'BW_per_our',
57
+ category: 'clarity',
58
+ pattern: /\bper\s+our\s+(conversation|discussion|meeting|call|agreement|email)\b/i,
59
+ suggestion: (m) => `As we discussed in our ${m[1]}`,
60
+ reason: '"Per our..." is stiff. Try "As we discussed" or "Following our...".',
61
+ }),
62
+ createRegexRule({
63
+ id: 'BW_please_advise',
64
+ category: 'clarity',
65
+ pattern: /\bplease\s+advise\b/i,
66
+ suggestion: '(Ask a specific question)',
67
+ reason: '"Please advise" is vague. Ask a specific question instead.',
68
+ }),
69
+ createRegexRule({
70
+ id: 'BW_kindly_note',
71
+ category: 'clarity',
72
+ pattern: /\bkindly\s+note\s+that\b/i,
73
+ suggestion: 'Please note that',
74
+ reason: '"Kindly note" sounds overly formal. Use "Please note" or state the point directly.',
75
+ }),
76
+
77
+ // ═══ Weak/Corporate Verbs (BW_003) ═══
78
+ createRegexRule({
79
+ id: 'BW_utilize',
80
+ category: 'style',
81
+ pattern: /\butilize\b/i,
82
+ suggestion: 'use',
83
+ reason: '"Utilize" is unnecessarily complex. Use "use" — it means the same thing.',
84
+ }),
85
+ createRegexRule({
86
+ id: 'BW_implement',
87
+ category: 'style',
88
+ pattern:
89
+ /\bimplement\s+(a|an|the|this|that|our|your|new)\s+(solution|plan|strategy|system|process|policy|change)\b/i,
90
+ suggestion: (m) => `start ${m[1]} ${m[2]}`,
91
+ reason: '"Implement" is corporate-speak. Try "start", "begin", "put in place", or "carry out".',
92
+ }),
93
+ createRegexRule({
94
+ id: 'BW_facilitate',
95
+ category: 'style',
96
+ pattern: /\bfacilitate\b/i,
97
+ suggestion: 'help',
98
+ reason: '"Facilitate" is jargon. Use "help", "enable", "support", or "lead".',
99
+ }),
100
+ createRegexRule({
101
+ id: 'BW_commence',
102
+ category: 'style',
103
+ pattern: /\bcommence\b/i,
104
+ suggestion: 'begin',
105
+ reason: '"Commence" is unnecessarily formal. Use "begin" or "start".',
106
+ }),
107
+ createRegexRule({
108
+ id: 'BW_terminate',
109
+ category: 'style',
110
+ pattern: /\bterminate\s+(the|this|that|a|an|our|your|his|her|their)\b/i,
111
+ suggestion: (m) => `end ${m[1]}`,
112
+ reason: '"Terminate" is bureaucratic. Use "end", "stop", or "cancel".',
113
+ }),
114
+ createRegexRule({
115
+ id: 'BW_endeavor',
116
+ category: 'style',
117
+ pattern: /\bendeavou?r\b/i,
118
+ suggestion: 'try',
119
+ reason: '"Endeavor" is unnecessarily formal. Use "try" or "attempt".',
120
+ }),
121
+ createRegexRule({
122
+ id: 'BW_ascertain',
123
+ category: 'style',
124
+ pattern: /\bascertain\b/i,
125
+ suggestion: 'find out',
126
+ reason: '"Ascertain" is unnecessarily complex. Use "find out" or "determine".',
127
+ }),
128
+ createRegexRule({
129
+ id: 'BW_ameliorate',
130
+ category: 'style',
131
+ pattern: /\bameliorate\b/i,
132
+ suggestion: 'improve',
133
+ reason: '"Ameliorate" is unnecessarily complex. Use "improve".',
134
+ }),
135
+ createRegexRule({
136
+ id: 'BW_cognizant',
137
+ category: 'style',
138
+ pattern: /\bcognizant\b/i,
139
+ suggestion: 'aware',
140
+ reason: '"Cognizant" is unnecessarily complex. Use "aware".',
141
+ }),
142
+ createRegexRule({
143
+ id: 'BW_elucidate',
144
+ category: 'style',
145
+ pattern: /\belucidate\b/i,
146
+ suggestion: 'explain',
147
+ reason: '"Elucidate" is unnecessarily complex. Use "explain" or "clarify".',
148
+ }),
149
+ createRegexRule({
150
+ id: 'BW_expedite',
151
+ category: 'style',
152
+ pattern: /\bexpedite\b/i,
153
+ suggestion: 'speed up',
154
+ reason: '"Expedite" is corporate jargon. Use "speed up" or "accelerate".',
155
+ }),
156
+ createRegexRule({
157
+ id: 'BW_remuneration',
158
+ category: 'style',
159
+ pattern: /\bremuneration\b/i,
160
+ suggestion: 'pay',
161
+ reason: '"Remuneration" is unnecessarily formal. Use "pay", "salary", or "compensation".',
162
+ }),
163
+ createRegexRule({
164
+ id: 'BW_disseminate',
165
+ category: 'style',
166
+ pattern: /\bdisseminate\b/i,
167
+ suggestion: 'share',
168
+ reason: '"Disseminate" is unnecessarily complex. Use "share", "distribute", or "spread".',
169
+ }),
170
+ createRegexRule({
171
+ id: 'BW_promulgate',
172
+ category: 'style',
173
+ pattern: /\bpromulgate\b/i,
174
+ suggestion: 'announce',
175
+ reason: '"Promulgate" is legal/bureaucratic. Use "announce", "publish", or "declare".',
176
+ }),
177
+ createRegexRule({
178
+ id: 'BW_effectuate',
179
+ category: 'style',
180
+ pattern: /\beffectuate\b/i,
181
+ suggestion: 'carry out',
182
+ reason: '"Effectuate" is unnecessarily complex. Use "carry out" or "bring about".',
183
+ }),
184
+ createRegexRule({
185
+ id: 'BW_incentivize',
186
+ category: 'style',
187
+ pattern: /\bincentivize\b/i,
188
+ suggestion: 'encourage',
189
+ reason: '"Incentivize" is corporate jargon. Use "encourage", "motivate", or "reward".',
190
+ }),
191
+ createRegexRule({
192
+ id: 'BW_operationalize',
193
+ category: 'style',
194
+ pattern: /\boperationalize\b/i,
195
+ suggestion: 'put into practice',
196
+ reason: '"Operationalize" is corporate jargon. Use "put into practice" or "implement".',
197
+ }),
198
+ createRegexRule({
199
+ id: 'BW_onboard',
200
+ category: 'style',
201
+ pattern:
202
+ /\bonboard\s+(a|an|the|new|our)\s+(employee|team\s+member|hire|staff|colleague|person|candidate)\b/i,
203
+ suggestion: (m) => `welcome ${m[1]} ${m[2]}`,
204
+ reason: '"Onboard" as a verb is HR jargon. Try "welcome", "train", or "introduce".',
205
+ }),
206
+
207
+ // ═══ Polite Double Negatives (BW_004) ═══
208
+ createRegexRule({
209
+ id: 'BW_not_uncommon',
210
+ category: 'clarity',
211
+ pattern: /\bnot\s+uncommon\b/i,
212
+ suggestion: 'common',
213
+ reason: '"Not uncommon" is a double negative. Say "common" for clarity.',
214
+ }),
215
+ createRegexRule({
216
+ id: 'BW_not_unlike',
217
+ category: 'clarity',
218
+ pattern: /\bnot\s+unlike\b/i,
219
+ suggestion: 'similar to',
220
+ reason: '"Not unlike" is a double negative. Say "similar to" for clarity.',
221
+ }),
222
+ createRegexRule({
223
+ id: 'BW_not_insignificant',
224
+ category: 'clarity',
225
+ pattern: /\bnot\s+insignificant\b/i,
226
+ suggestion: 'significant',
227
+ reason: '"Not insignificant" is a double negative. Say "significant" directly.',
228
+ }),
229
+ createRegexRule({
230
+ id: 'BW_not_unreasonable',
231
+ category: 'clarity',
232
+ pattern: /\bnot\s+unreasonable\b/i,
233
+ suggestion: 'reasonable',
234
+ reason: '"Not unreasonable" is a double negative. Say "reasonable" directly.',
235
+ }),
236
+ createRegexRule({
237
+ id: 'BW_not_unimportant',
238
+ category: 'clarity',
239
+ pattern: /\bnot\s+unimportant\b/i,
240
+ suggestion: 'important',
241
+ reason: '"Not unimportant" is a double negative. Say "important" directly.',
242
+ }),
243
+ createRegexRule({
244
+ id: 'BW_not_infrequent',
245
+ category: 'clarity',
246
+ pattern: /\bnot\s+infrequent(ly)?\b/i,
247
+ suggestion: (m) => (m[1] ? 'frequently' : 'frequent'),
248
+ reason: '"Not infrequent" is a double negative. Say "frequent" directly.',
249
+ }),
250
+ ];
@@ -0,0 +1,79 @@
1
+ import type { Issue } from '../../shared-types.js';
2
+ import { createRegexRule, type Rule } from '../types.js';
3
+
4
+ export const capitalizationRules: Rule[] = [
5
+ // Standalone I
6
+ {
7
+ id: 'cap-i',
8
+ type: 'regex',
9
+ category: 'grammar',
10
+ pattern: /(?:^|\s)(i)(?=\s|['’]m|[.,!?]|$)/g,
11
+ reason: 'The pronoun "I" should always be capitalized.',
12
+ suggestion: 'I',
13
+ check: (text: string): Issue[] => {
14
+ const issues: Issue[] = [];
15
+ const iRegex = /(?:^|\s)(i)(?=\s|['’]m|[.,!?]|$)/g;
16
+ let match: RegExpExecArray | null;
17
+ while ((match = iRegex.exec(text)) !== null) {
18
+ const matchIndex = match.index + match[0].indexOf('i');
19
+ issues.push({
20
+ id: `cap-i-${matchIndex}`,
21
+ type: 'grammar',
22
+ original: 'i',
23
+ suggestion: 'I',
24
+ reason: 'The pronoun "I" should always be capitalized.',
25
+ offset: matchIndex,
26
+ length: 1,
27
+ });
28
+ }
29
+ return issues;
30
+ },
31
+ },
32
+
33
+ // Days and Months
34
+ createRegexRule({
35
+ id: 'cap-date',
36
+ category: 'grammar',
37
+ pattern:
38
+ /\b(monday|tuesday|wednesday|thursday|friday|saturday|sunday|january|february|march|april|may|june|july|august|september|october|november|december)\b/g,
39
+ suggestion: (m) => m[1].charAt(0).toUpperCase() + m[1].slice(1),
40
+ reason: 'Days of the week and months should be capitalized.',
41
+ }),
42
+
43
+ // Common Proper Nouns
44
+ createRegexRule({
45
+ id: 'cap-proper-noun',
46
+ category: 'grammar',
47
+ pattern:
48
+ /\b(swadhin|dhaka|john|mary|london|paris|america|india|bangladesh|google|microsoft|apple|facebook)\b/g,
49
+ suggestion: (m) => m[1].charAt(0).toUpperCase() + m[1].slice(1),
50
+ reason: 'Proper nouns, names, and places should be capitalized.',
51
+ }),
52
+
53
+ // First letter of sentences
54
+ {
55
+ id: 'cap-sentence-start',
56
+ type: 'regex',
57
+ category: 'grammar',
58
+ pattern: /(?:^|[.!?]\s+)([a-z])/g,
59
+ reason: 'The first word of a sentence should be capitalized.',
60
+ suggestion: '',
61
+ check: (text: string): Issue[] => {
62
+ const issues: Issue[] = [];
63
+ const sentenceStartRegex = /(?:^|[.!?]\s+)([a-z])/g;
64
+ let match: RegExpExecArray | null;
65
+ while ((match = sentenceStartRegex.exec(text)) !== null) {
66
+ issues.push({
67
+ id: `cap-start-${match.index}`,
68
+ type: 'grammar',
69
+ original: match[1],
70
+ suggestion: match[1].toUpperCase(),
71
+ reason: 'The first word of a sentence should be capitalized.',
72
+ offset: match.index + match[0].lastIndexOf(match[1]),
73
+ length: 1,
74
+ });
75
+ }
76
+ return issues;
77
+ },
78
+ },
79
+ ];