opengrammar-server 2.0.644277

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,446 @@
1
+ import type { Rule } from './types.js';
2
+
3
+ /**
4
+ * ╔══════════════════════════════════════════════════════════════╗
5
+ * ║ Context-Aware Rule Filtering ║
6
+ * ║ Auto-detect document type and enable/disable rule modules ║
7
+ * ╚══════════════════════════════════════════════════════════════╝
8
+ *
9
+ * In casual chat, users don't want 814 rules nagging about "gonna".
10
+ * In academic papers, they want weasel word detection.
11
+ * This module maps writing context → active rule module prefixes.
12
+ */
13
+
14
+ export type WritingContext =
15
+ | 'email' // Gmail, Outlook, Yahoo Mail
16
+ | 'document' // Google Docs, Notion, Overleaf, Word
17
+ | 'technical' // GitHub, StackOverflow, GitLab
18
+ | 'social' // Twitter/X, Reddit, Facebook, LinkedIn
19
+ | 'chat' // Slack, Discord, Teams, WhatsApp Web
20
+ | 'academic' // Academic papers (detected by content or domain like Overleaf)
21
+ | 'general'; // Default — full rule set
22
+
23
+ /**
24
+ * Rule module prefixes — these are the first segment of each rule ID
25
+ * or the export name pattern from each category file.
26
+ * We use rule ID prefixes to classify which module a rule belongs to.
27
+ */
28
+ const MODULE_PREFIXES: Record<string, string[]> = {
29
+ // Grammar core
30
+ grammar: [
31
+ 'SVA_',
32
+ 'VT_',
33
+ 'SS_',
34
+ 'PU_',
35
+ 'buyed',
36
+ 'drived',
37
+ 'catched',
38
+ 'finded',
39
+ 'goed',
40
+ 'runned',
41
+ 'swimmed',
42
+ 'thinked',
43
+ 'writed',
44
+ 'speaked',
45
+ 'breaked',
46
+ 'choosed',
47
+ 'drinked',
48
+ 'eated',
49
+ 'falled',
50
+ 'feeled',
51
+ 'flyed',
52
+ 'growed',
53
+ 'hided',
54
+ 'holded',
55
+ 'keeped',
56
+ 'knowed',
57
+ 'leaved',
58
+ 'meaned',
59
+ 'maked',
60
+ 'payed',
61
+ 'putted',
62
+ 'readed',
63
+ 'sayed',
64
+ 'seed',
65
+ 'selled',
66
+ 'sended',
67
+ 'singed',
68
+ 'sitted',
69
+ 'sleeped',
70
+ 'spended',
71
+ 'standed',
72
+ 'stealed',
73
+ 'taked',
74
+ 'teached',
75
+ 'telled',
76
+ 'understanded',
77
+ 'weared',
78
+ 'winned',
79
+ 'GR_',
80
+ // Common errors — grammar group
81
+ 'CE_GRT_', 'CE_REF_', 'CE_CPD_', 'CE_ART_', 'CE_CON_', 'QA_',
82
+ // New grammar modules
83
+ 'SVA_', 'MV_', 'GI_', 'PRX_', 'ART_', 'DM_',
84
+ ],
85
+ // Spelling
86
+ spelling: ['SP_', 'CW_'],
87
+ // Sentence structure
88
+ 'sentence-structure': ['SS_'],
89
+ // Verb tense
90
+ 'verb-tense': [
91
+ 'VT_',
92
+ 'have_went',
93
+ 'have_broke',
94
+ 'have_ate',
95
+ 'should_have_',
96
+ 'could_have_',
97
+ 'would_have_',
98
+ 'must_have_',
99
+ 'might_have_',
100
+ 'may_have_',
101
+ ],
102
+ // Adjectives & adverbs
103
+ 'adjectives-adverbs': ['AA_'],
104
+ // Nouns & pronouns
105
+ 'nouns-pronouns': ['NP_'],
106
+ // Prepositions
107
+ prepositions: ['PR_'],
108
+ // Conjunctions / wordy
109
+ conjunctions: ['CJ_'],
110
+ // Punctuation
111
+ punctuation: [
112
+ 'PU_',
113
+ 'dont',
114
+ 'doesnt',
115
+ 'didnt',
116
+ 'cant',
117
+ 'wont',
118
+ 'isnt',
119
+ 'arent',
120
+ 'wasnt',
121
+ 'werent',
122
+ 'shouldnt',
123
+ 'couldnt',
124
+ 'wouldnt',
125
+ 'hasnt',
126
+ 'havent',
127
+ 'hadnt',
128
+ 'its_vs_',
129
+ 'HYPH_',
130
+ 'CE_COM_',
131
+ 'PU_QMK_',
132
+ ],
133
+ // Confused words
134
+ 'confused-words': ['CW_'],
135
+ // Style & tone
136
+ 'style-tone': ['ST_'],
137
+ // Formality (informal speech — suppressed in chat; CE_PHR_ = gonna/wanna/etc.)
138
+ formality: ['FR_', 'CE_PHR_'],
139
+ // Formatting & idioms
140
+ 'formatting-idioms': ['IE_', 'NF_'],
141
+ // Capitalization
142
+ capitalization: ['CAP_'],
143
+ // Academic writing
144
+ 'academic-writing': ['AW_'],
145
+ // Business writing
146
+ 'business-writing': ['BW_'],
147
+ // Inclusive language
148
+ 'inclusive-language': ['IL_'],
149
+ // Readability
150
+ readability: ['RD_'],
151
+ // Clarity
152
+ clarity: ['CLR_'],
153
+ // Style base (TNR_ = tone rules — jargon, hedging, aggression)
154
+ style: ['STY_', 'TNR_'],
155
+ // Grammar advanced
156
+ 'grammar-advanced': ['GR_'],
157
+ // Spelling advanced
158
+ 'spelling-advanced': [
159
+ 'intents-purposes',
160
+ 'its-vs-its',
161
+ 'their-vs-there',
162
+ 'your-vs-youre',
163
+ 'who-vs-whom',
164
+ ],
165
+ };
166
+
167
+ /**
168
+ * Which module groups are ENABLED per writing context.
169
+ * Modules not listed are DISABLED for that context.
170
+ */
171
+ const CONTEXT_MODULES: Record<WritingContext, Set<string>> = {
172
+ chat: new Set(['grammar', 'spelling', 'confused-words', 'punctuation', 'spelling-advanced']),
173
+
174
+ social: new Set([
175
+ 'grammar',
176
+ 'spelling',
177
+ 'confused-words',
178
+ 'punctuation',
179
+ 'nouns-pronouns',
180
+ 'verb-tense',
181
+ 'spelling-advanced',
182
+ ]),
183
+
184
+ email: new Set([
185
+ 'grammar',
186
+ 'spelling',
187
+ 'confused-words',
188
+ 'punctuation',
189
+ 'sentence-structure',
190
+ 'verb-tense',
191
+ 'nouns-pronouns',
192
+ 'adjectives-adverbs',
193
+ 'prepositions',
194
+ 'capitalization',
195
+ 'business-writing',
196
+ 'formality',
197
+ 'clarity',
198
+ 'style-tone',
199
+ 'spelling-advanced',
200
+ 'grammar-advanced',
201
+ 'formatting-idioms',
202
+ ]),
203
+
204
+ technical: new Set([
205
+ 'grammar',
206
+ 'spelling',
207
+ 'confused-words',
208
+ 'punctuation',
209
+ 'sentence-structure',
210
+ 'verb-tense',
211
+ 'nouns-pronouns',
212
+ 'prepositions',
213
+ 'capitalization',
214
+ 'readability',
215
+ 'clarity',
216
+ 'spelling-advanced',
217
+ 'grammar-advanced',
218
+ ]),
219
+
220
+ document: new Set(Object.keys(MODULE_PREFIXES)),
221
+
222
+ academic: new Set([
223
+ 'grammar',
224
+ 'spelling',
225
+ 'confused-words',
226
+ 'punctuation',
227
+ 'sentence-structure',
228
+ 'verb-tense',
229
+ 'nouns-pronouns',
230
+ 'adjectives-adverbs',
231
+ 'prepositions',
232
+ 'conjunctions',
233
+ 'capitalization',
234
+ 'academic-writing',
235
+ 'readability',
236
+ 'style-tone',
237
+ 'clarity',
238
+ 'inclusive-language',
239
+ 'formatting-idioms',
240
+ 'spelling-advanced',
241
+ 'grammar-advanced',
242
+ ]),
243
+
244
+ general: new Set(Object.keys(MODULE_PREFIXES)),
245
+ };
246
+
247
+ /**
248
+ * Determine the module a rule belongs to based on its ID prefix.
249
+ */
250
+ function getRuleModule(ruleId: string): string {
251
+ for (const [moduleName, prefixes] of Object.entries(MODULE_PREFIXES)) {
252
+ for (const prefix of prefixes) {
253
+ if (ruleId.startsWith(prefix)) {
254
+ return moduleName;
255
+ }
256
+ }
257
+ }
258
+ // Fallback: rules without a recognized prefix are always included
259
+ return 'general';
260
+ }
261
+
262
+ /**
263
+ * Filter rules based on writing context and user-defined disabled modules.
264
+ * Returns only the rules that are relevant for the given context and not manually disabled.
265
+ */
266
+ export function filterRulesByContext(
267
+ rules: Rule[],
268
+ context: WritingContext,
269
+ disabledModules?: string[],
270
+ ): Rule[] {
271
+ // 1. First, apply manual user overrides (stripping out entire categories if disabled)
272
+ // We map the UI categories (Grammar, Spelling, Punctuation, Style, Clarity)
273
+ // to the internal MODULE_PREFIXES groups.
274
+ const disabledPrefixGroups = new Set<string>();
275
+
276
+ if (disabledModules && disabledModules.length > 0) {
277
+ const dLower = disabledModules.map((d) => d.toLowerCase());
278
+ if (dLower.includes('grammar')) {
279
+ [
280
+ 'grammar',
281
+ 'grammar-advanced',
282
+ 'sentence-structure',
283
+ 'verb-tense',
284
+ 'nouns-pronouns',
285
+ 'adjectives-adverbs',
286
+ 'prepositions',
287
+ 'conjunctions',
288
+ ].forEach((m) => disabledPrefixGroups.add(m));
289
+ }
290
+ if (dLower.includes('spelling')) {
291
+ ['spelling', 'spelling-advanced', 'confused-words'].forEach((m) =>
292
+ disabledPrefixGroups.add(m),
293
+ );
294
+ }
295
+ if (dLower.includes('punctuation')) {
296
+ ['punctuation', 'capitalization'].forEach((m) => disabledPrefixGroups.add(m));
297
+ }
298
+ if (dLower.includes('style')) {
299
+ [
300
+ 'style',
301
+ 'style-tone',
302
+ 'formality',
303
+ 'academic-writing',
304
+ 'business-writing',
305
+ 'inclusive-language',
306
+ 'formatting-idioms',
307
+ ].forEach((m) => disabledPrefixGroups.add(m));
308
+ }
309
+ if (dLower.includes('clarity')) {
310
+ ['clarity', 'readability'].forEach((m) => disabledPrefixGroups.add(m));
311
+ }
312
+ }
313
+
314
+ // 2. Map standard context
315
+ const enabledContextModules = CONTEXT_MODULES[context];
316
+
317
+ return rules.filter((rule) => {
318
+ const module = getRuleModule(rule.id);
319
+
320
+ // Manual override check: if the user disabled this entire module group, strip it out.
321
+ if (disabledPrefixGroups.has(module)) {
322
+ return false;
323
+ }
324
+
325
+ // Context filter check
326
+ if (context === 'general' || context === 'document') {
327
+ return true; // No context filtering
328
+ }
329
+
330
+ return module === 'general' || (enabledContextModules && enabledContextModules.has(module));
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Detect writing context from domain URL.
336
+ * This mirrors the detection in index.ts but returns our WritingContext type.
337
+ */
338
+ export function detectWritingContext(domain?: string): WritingContext {
339
+ if (!domain) return 'general';
340
+ const d = domain.toLowerCase();
341
+
342
+ // Chat
343
+ if (
344
+ d.includes('slack') ||
345
+ d.includes('discord') ||
346
+ d.includes('teams') ||
347
+ d.includes('whatsapp') ||
348
+ d.includes('telegram') ||
349
+ d.includes('messenger')
350
+ ) {
351
+ return 'chat';
352
+ }
353
+
354
+ // Social
355
+ if (
356
+ d.includes('twitter') ||
357
+ d.includes('x.com') ||
358
+ d.includes('reddit') ||
359
+ d.includes('facebook') ||
360
+ d.includes('linkedin') ||
361
+ d.includes('instagram') ||
362
+ d.includes('threads') ||
363
+ d.includes('mastodon') ||
364
+ d.includes('bluesky')
365
+ ) {
366
+ return 'social';
367
+ }
368
+
369
+ // Email
370
+ if (
371
+ d.includes('mail.google') ||
372
+ d.includes('outlook') ||
373
+ d.includes('yahoo') ||
374
+ d.includes('proton') ||
375
+ d.includes('zoho') ||
376
+ d.includes('fastmail')
377
+ ) {
378
+ return 'email';
379
+ }
380
+
381
+ // Academic
382
+ if (
383
+ d.includes('overleaf') ||
384
+ d.includes('arxiv') ||
385
+ d.includes('scholar.google') ||
386
+ d.includes('academia.edu') ||
387
+ d.includes('researchgate')
388
+ ) {
389
+ return 'academic';
390
+ }
391
+
392
+ // Technical
393
+ if (
394
+ d.includes('github') ||
395
+ d.includes('gitlab') ||
396
+ d.includes('stackoverflow') ||
397
+ d.includes('stackexchange') ||
398
+ d.includes('bitbucket') ||
399
+ d.includes('codepen') ||
400
+ d.includes('replit') ||
401
+ d.includes('jsfiddle') ||
402
+ d.includes('codesandbox')
403
+ ) {
404
+ return 'technical';
405
+ }
406
+
407
+ // Document
408
+ if (
409
+ d.includes('docs.google') ||
410
+ d.includes('notion') ||
411
+ d.includes('coda.io') ||
412
+ d.includes('dropbox') ||
413
+ d.includes('quip') ||
414
+ d.includes('confluence') ||
415
+ d.includes('sharepoint') ||
416
+ d.includes('office.com') ||
417
+ d.includes('word')
418
+ ) {
419
+ return 'document';
420
+ }
421
+
422
+ return 'general';
423
+ }
424
+
425
+ /**
426
+ * Get a summary of how many rules are active per context.
427
+ * Useful for debugging and the API response.
428
+ */
429
+ export function getContextSummary(rules: Rule[]): Record<WritingContext, number> {
430
+ const contexts: WritingContext[] = [
431
+ 'chat',
432
+ 'social',
433
+ 'email',
434
+ 'technical',
435
+ 'document',
436
+ 'academic',
437
+ 'general',
438
+ ];
439
+ const summary: Record<string, number> = {};
440
+
441
+ for (const ctx of contexts) {
442
+ summary[ctx] = filterRulesByContext(rules, ctx).length;
443
+ }
444
+
445
+ return summary as Record<WritingContext, number>;
446
+ }
@@ -0,0 +1,96 @@
1
+ // ═══ Phase 4 — Domain, Inclusivity, Readability (Part 3 JSON) ═══
2
+ import { academicWritingRules } from './categories/academic-writing.js';
3
+ import { adjectivesAdverbsRules } from './categories/adjectives-adverbs.js';
4
+ import { businessWritingRules } from './categories/business-writing.js';
5
+ import { capitalizationRules } from './categories/capitalization.js';
6
+ import { clarityRules } from './categories/clarity.js';
7
+ // ═══ Phase 3 — Style, Words, Formality (Part 2 JSON) ═══
8
+ import { confusedWordsRules } from './categories/confused-words.js';
9
+ import { conjunctionRules } from './categories/conjunctions.js';
10
+ import { formalityRules } from './categories/formality.js';
11
+ import { formattingIdiomRules } from './categories/formatting-idioms.js';
12
+ // ═══ Phase 1 — Foundation ═══
13
+ import { basicGrammarRules } from './categories/grammar.js';
14
+ import { commonErrorRules } from './categories/common-errors.js';
15
+ import { advancedGrammarRules } from './categories/grammar-advanced.js';
16
+ import { inclusiveLanguageRules } from './categories/inclusive-language.js';
17
+ import { nounsPronouns } from './categories/nouns-pronouns.js';
18
+ import { prepositionRules } from './categories/prepositions.js';
19
+ import { punctuationRules } from './categories/punctuation.js';
20
+ import { readabilityRules } from './categories/readability.js';
21
+ // ═══ Phase 2 — Structural Grammar (Part 1 JSON) ═══
22
+ import { sentenceStructureRules } from './categories/sentence-structure.js';
23
+ import { spellingRules } from './categories/spelling.js';
24
+ import { spellingAdvancedRules } from './categories/spelling-advanced.js';
25
+ import { styleRules } from './categories/style.js';
26
+ import { styleToneRules } from './categories/style-tone.js';
27
+ import { verbTenseRules } from './categories/verb-tense.js';
28
+ import { subjectVerbAgreementRules } from './categories/subject-verb-agreement.js';
29
+ import { gerundInfinitiveRules } from './categories/gerund-infinitive.js';
30
+ import { prepositionExtendedRules } from './categories/prepositions-extended.js';
31
+ import { articleRules } from './categories/articles.js';
32
+ import { danglingModifierRules } from './categories/dangling-modifiers.js';
33
+ import { toneRules } from './categories/tone-rules.js';
34
+ import { quantityAgreementRules } from './categories/quantity-agreement.js';
35
+ import type { Rule } from './types.js';
36
+
37
+ /**
38
+ * ╔══════════════════════════════════════════════════════════════╗
39
+ * ║ OpenGrammar — Giant Grammarly Rule Engine ║
40
+ * ║ 22 Category Modules · 850+ Rules ║
41
+ * ║ ║
42
+ * ║ Categories: ║
43
+ * ║ • Grammar (basic, advanced, sentence structure, verbs) ║
44
+ * ║ • Spelling (common, advanced, confused words) ║
45
+ * ║ • Style (tone, clichés, formality, nominalizations) ║
46
+ * ║ • Clarity (redundancy, wordiness, readability) ║
47
+ * ║ • Punctuation (apostrophes, hyphens, commas) ║
48
+ * ║ • Capitalization (I, days, proper nouns, sentences) ║
49
+ * ║ • Prepositions, Conjunctions, Adjectives/Adverbs ║
50
+ * ║ • Nouns/Pronouns (countability, plurals, determiners) ║
51
+ * ║ • Idioms & Formatting (malapropisms, tautologies) ║
52
+ * ║ • Academic Writing (weasel words, hedging, citations) ║
53
+ * ║ • Business Writing (corporate bloat, email openers) ║
54
+ * ║ • Inclusive Language (gendered, ableist, person-first) ║
55
+ * ║ • Readability (complex words, long sentences) ║
56
+ * ╚══════════════════════════════════════════════════════════════╝
57
+ */
58
+ export const CORE_RULES: Rule[] = [
59
+ // Phase 1 — Foundation (highest priority)
60
+ ...commonErrorRules,
61
+ ...basicGrammarRules,
62
+ ...spellingRules,
63
+ ...styleRules,
64
+ ...clarityRules,
65
+ ...advancedGrammarRules,
66
+ ...spellingAdvancedRules,
67
+ ...capitalizationRules,
68
+
69
+ // Phase 2 — Structural Grammar
70
+ ...sentenceStructureRules,
71
+ ...verbTenseRules,
72
+ ...subjectVerbAgreementRules,
73
+ ...gerundInfinitiveRules,
74
+ ...nounsPronouns,
75
+ ...adjectivesAdverbsRules,
76
+ ...prepositionRules,
77
+ ...prepositionExtendedRules,
78
+ ...conjunctionRules,
79
+ ...punctuationRules,
80
+ ...quantityAgreementRules,
81
+ ...articleRules,
82
+ ...danglingModifierRules,
83
+
84
+ // Phase 3 — Style, Confused Words, Formality, Idioms
85
+ ...confusedWordsRules,
86
+ ...styleToneRules,
87
+ ...formalityRules,
88
+ ...formattingIdiomRules,
89
+ ...toneRules,
90
+
91
+ // Phase 4 — Domain Writing, Inclusivity, Readability
92
+ ...academicWritingRules,
93
+ ...businessWritingRules,
94
+ ...inclusiveLanguageRules,
95
+ ...readabilityRules,
96
+ ];