ai-text-check 1.0.0 → 1.2.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/dist/node.js CHANGED
@@ -1,6 +1,7 @@
1
- import fs3 from 'fs';
2
- import path3 from 'path';
1
+ import fs3, { readFileSync } from 'fs';
2
+ import path3, { join, dirname } from 'path';
3
3
  import { pathToFileURL, fileURLToPath } from 'url';
4
+ import { lintText } from 'ai-slop-linter';
4
5
  import { analyze } from 'is-this-ai-slop';
5
6
  import { createRequire } from 'module';
6
7
  import { spawnSync } from 'child_process';
@@ -565,6 +566,9 @@ var ENGINE_PRESETS = {
565
566
  isThisAiSlop: true,
566
567
  veldica: true,
567
568
  cleanWriting: true,
569
+ aiSlopLinter: true,
570
+ slopDetector: true,
571
+ proseSlop: true,
568
572
  vale: true
569
573
  },
570
574
  strict: {
@@ -575,6 +579,9 @@ var ENGINE_PRESETS = {
575
579
  isThisAiSlop: true,
576
580
  veldica: true,
577
581
  cleanWriting: true,
582
+ aiSlopLinter: true,
583
+ slopDetector: true,
584
+ proseSlop: true,
578
585
  vale: true
579
586
  },
580
587
  ai: {
@@ -585,6 +592,9 @@ var ENGINE_PRESETS = {
585
592
  isThisAiSlop: true,
586
593
  veldica: true,
587
594
  cleanWriting: true,
595
+ aiSlopLinter: true,
596
+ slopDetector: true,
597
+ proseSlop: true,
588
598
  vale: true
589
599
  },
590
600
  grammar: {
@@ -595,6 +605,9 @@ var ENGINE_PRESETS = {
595
605
  isThisAiSlop: false,
596
606
  veldica: false,
597
607
  cleanWriting: true,
608
+ aiSlopLinter: false,
609
+ slopDetector: false,
610
+ proseSlop: false,
598
611
  vale: true
599
612
  }
600
613
  };
@@ -615,6 +628,9 @@ var ENGINE_KEYS = [
615
628
  "isThisAiSlop",
616
629
  "veldica",
617
630
  "cleanWriting",
631
+ "aiSlopLinter",
632
+ "slopDetector",
633
+ "proseSlop",
618
634
  "vale"
619
635
  ];
620
636
  function isObject(value) {
@@ -734,6 +750,14 @@ function validateUserConfig(value) {
734
750
  config.ignoreRules = value.ignoreRules;
735
751
  }
736
752
  }
753
+ const ignoreEmojis = expectBoolean(
754
+ value.ignoreEmojis,
755
+ "ignoreEmojis",
756
+ errors
757
+ );
758
+ if (ignoreEmojis !== void 0) {
759
+ config.ignoreEmojis = ignoreEmojis;
760
+ }
737
761
  if (value.include !== void 0) {
738
762
  if (!isStringArray(value.include)) {
739
763
  errors.push("include must be an array of strings");
@@ -867,6 +891,7 @@ function validateUserConfig(value) {
867
891
  "failOn",
868
892
  "minScore",
869
893
  "ignoreRules",
894
+ "ignoreEmojis",
870
895
  "include",
871
896
  "exclude",
872
897
  "extensions",
@@ -890,7 +915,14 @@ function resolveConfig(user = {}) {
890
915
  format: user.format ?? "auto",
891
916
  failOn: user.failOn ?? "warn",
892
917
  minScore: user.minScore ?? 0,
893
- ignoreRules: user.ignoreRules ?? [],
918
+ ignoreRules: (() => {
919
+ const rules = [...user.ignoreRules ?? []];
920
+ if (user.ignoreEmojis ?? false) {
921
+ rules.push("ai-emojis");
922
+ }
923
+ return rules;
924
+ })(),
925
+ ignoreEmojis: user.ignoreEmojis ?? false,
894
926
  include: user.include ?? [],
895
927
  exclude: user.exclude ?? [],
896
928
  extensions: (user.extensions ?? DEFAULT_EXTENSIONS).map(
@@ -1017,6 +1049,27 @@ function collectFiles(roots, config) {
1017
1049
  }
1018
1050
  return [...files].sort();
1019
1051
  }
1052
+ function toSeverity(value) {
1053
+ if (value === "error") return "error";
1054
+ if (value === "warning" || value === "warn") return "warn";
1055
+ return "info";
1056
+ }
1057
+ function runAiSlopLinter(text, options = {}) {
1058
+ const result = lintText(options.source ?? "<text>", text, { floor: 0 });
1059
+ return result.findings.map((finding) => {
1060
+ const issue = {
1061
+ rule: `asl-${finding.rule}`,
1062
+ engine: "ai-slop-linter",
1063
+ category: "ai",
1064
+ severity: toSeverity(finding.severity),
1065
+ message: finding.message,
1066
+ line: finding.line,
1067
+ column: finding.column,
1068
+ excerpt: finding.excerpt
1069
+ };
1070
+ return issue;
1071
+ });
1072
+ }
1020
1073
 
1021
1074
  // node_modules/clean-writing-lint/src/mask.js
1022
1075
  function blank(text) {
@@ -1986,7 +2039,7 @@ var AI_RULES = /* @__PURE__ */ new Set([
1986
2039
  "banned-construction",
1987
2040
  "em-dash"
1988
2041
  ]);
1989
- function toSeverity(value) {
2042
+ function toSeverity2(value) {
1990
2043
  if (value === "error") return "error";
1991
2044
  if (value === "warn") return "warn";
1992
2045
  return "info";
@@ -2006,7 +2059,7 @@ function runCleanWriting(text) {
2006
2059
  rule: `cws-${violation.rule}`,
2007
2060
  engine: "clean-writing",
2008
2061
  category: AI_RULES.has(violation.rule) ? "ai" : "grammar",
2009
- severity: toSeverity(violation.severity),
2062
+ severity: toSeverity2(violation.severity),
2010
2063
  message: violation.message,
2011
2064
  line: violation.line,
2012
2065
  column: violation.column,
@@ -2033,6 +2086,33 @@ function runIsThisAiSlop(text) {
2033
2086
  return issue;
2034
2087
  });
2035
2088
  }
2089
+ var require2 = createRequire(import.meta.url);
2090
+ var { check } = require2("prose-slop");
2091
+ function toSeverity3(value) {
2092
+ if (value === "error") return "error";
2093
+ if (value === "warning" || value === "warn") return "warn";
2094
+ return "info";
2095
+ }
2096
+ function runProseSlop(text, options = {}) {
2097
+ const findings = check(text, {
2098
+ format: options.format ?? "plain",
2099
+ lang: "en"
2100
+ });
2101
+ return findings.map((finding) => {
2102
+ const issue = {
2103
+ rule: `prose-${finding.ruleId}`,
2104
+ engine: "prose-slop",
2105
+ category: "ai",
2106
+ severity: toSeverity3(finding.severity),
2107
+ message: finding.message,
2108
+ line: finding.line,
2109
+ column: finding.column,
2110
+ excerpt: excerptAround(text, finding.index),
2111
+ match: finding.match
2112
+ };
2113
+ return issue;
2114
+ });
2115
+ }
2036
2116
 
2037
2117
  // node_modules/nlcst-to-string/lib/index.js
2038
2118
  var emptyNodes = [];
@@ -2994,10 +3074,10 @@ function anyFactory(tests) {
2994
3074
  return false;
2995
3075
  }
2996
3076
  }
2997
- function propertiesFactory(check) {
3077
+ function propertiesFactory(check2) {
2998
3078
  const checkAsRecord = (
2999
3079
  /** @type {Record<string, unknown>} */
3000
- check
3080
+ check2
3001
3081
  );
3002
3082
  return castFactory(all);
3003
3083
  function all(node) {
@@ -3007,21 +3087,21 @@ function propertiesFactory(check) {
3007
3087
  node
3008
3088
  );
3009
3089
  let key;
3010
- for (key in check) {
3090
+ for (key in check2) {
3011
3091
  if (nodeAsRecord[key] !== checkAsRecord[key]) return false;
3012
3092
  }
3013
3093
  return true;
3014
3094
  }
3015
3095
  }
3016
- function typeFactory(check) {
3096
+ function typeFactory(check2) {
3017
3097
  return castFactory(type);
3018
3098
  function type(node) {
3019
- return node && node.type === check;
3099
+ return node && node.type === check2;
3020
3100
  }
3021
3101
  }
3022
3102
  function castFactory(testFunction) {
3023
- return check;
3024
- function check(value, index2, parent) {
3103
+ return check2;
3104
+ function check2(value, index2, parent) {
3025
3105
  return Boolean(
3026
3106
  looksLikeANode(value) && testFunction.call(
3027
3107
  this,
@@ -3050,14 +3130,14 @@ var CONTINUE = true;
3050
3130
  var EXIT = false;
3051
3131
  var SKIP = "skip";
3052
3132
  function visitParents(tree, test, visitor, reverse) {
3053
- let check;
3133
+ let check2;
3054
3134
  if (typeof test === "function" && typeof visitor !== "function") {
3055
3135
  reverse = visitor;
3056
3136
  visitor = test;
3057
3137
  } else {
3058
- check = test;
3138
+ check2 = test;
3059
3139
  }
3060
- const is2 = convert(check);
3140
+ const is2 = convert(check2);
3061
3141
  const step = reverse ? -1 : 1;
3062
3142
  factory(tree, void 0, [])();
3063
3143
  function factory(node, index2, parents) {
@@ -9950,9 +10030,9 @@ var VFile = class {
9950
10030
  * @returns {undefined}
9951
10031
  * Nothing.
9952
10032
  */
9953
- set dirname(dirname3) {
10033
+ set dirname(dirname4) {
9954
10034
  assertPath2(this.basename, "dirname");
9955
- this.path = minpath.join(dirname3 || "", this.basename);
10035
+ this.path = minpath.join(dirname4 || "", this.basename);
9956
10036
  }
9957
10037
  /**
9958
10038
  * Get the extname (including dot) (example: `'.js'`).
@@ -10975,6 +11055,160 @@ function runRetext(text) {
10975
11055
  return issue;
10976
11056
  });
10977
11057
  }
11058
+ var WEIGHT_WORD = 0.5;
11059
+ var WEIGHT_PATTERN = 0.2;
11060
+ var WEIGHT_TRIGRAM = 0.12;
11061
+ var WEIGHT_TROPE = 0.18;
11062
+ var WORD_MULTIPLIER = 1e3;
11063
+ var TRIGRAM_MULTIPLIER = 1e3;
11064
+ var CONTRAST_PATTERN = /\b(?:it(?:'s| is)|this is|that(?:'s| is)|(?:it|this|that) isn'?t)\s+(?:just |only |merely |simply )?[a-z][\s\S]{0,80}?\b(?:it(?:'s| is)|this is|that(?:'s| is)|but)\b/gi;
11065
+ var NOT_BUT_PATTERN = /\bnot\b[\s\S]{0,60}?\bbut\b/gi;
11066
+ var require3 = createRequire(import.meta.url);
11067
+ var assetsDir = join(
11068
+ dirname(require3.resolve("slop-detector/package.json")),
11069
+ "dist",
11070
+ "assets"
11071
+ );
11072
+ var cachedAssets = null;
11073
+ function computeRange(values) {
11074
+ if (values.length === 0) return { min: 0, max: 1 };
11075
+ const min = Math.min(...values);
11076
+ const max = Math.max(...values);
11077
+ const range = max - min || 1;
11078
+ return { min: min - range * 0.1, max: max + range * 0.1 };
11079
+ }
11080
+ function loadAssets() {
11081
+ if (cachedAssets) return cachedAssets;
11082
+ const wordsRaw = JSON.parse(
11083
+ readFileSync(join(assetsDir, "slop_list.json"), "utf8")
11084
+ );
11085
+ const trigramsRaw = JSON.parse(
11086
+ readFileSync(join(assetsDir, "slop_list_trigrams.json"), "utf8")
11087
+ );
11088
+ const leaderboard = JSON.parse(
11089
+ readFileSync(join(assetsDir, "leaderboard_results.json"), "utf8")
11090
+ );
11091
+ const words = /* @__PURE__ */ new Set();
11092
+ for (const item of wordsRaw) {
11093
+ if (Array.isArray(item) && item[0]) words.add(item[0].toLowerCase());
11094
+ }
11095
+ const trigrams = /* @__PURE__ */ new Set();
11096
+ for (const item of trigramsRaw) {
11097
+ if (Array.isArray(item) && item[0]) trigrams.add(item[0].toLowerCase());
11098
+ }
11099
+ const metrics = {
11100
+ slop_words: [],
11101
+ slop_trigrams: [],
11102
+ contrast: []
11103
+ };
11104
+ for (const res of leaderboard.results ?? []) {
11105
+ if (!res.metrics) continue;
11106
+ metrics.slop_words.push(res.metrics.slop_list_matches_per_1k_words || 0);
11107
+ metrics.slop_trigrams.push(
11108
+ res.metrics.slop_trigram_matches_per_1k_words || 0
11109
+ );
11110
+ metrics.contrast.push(res.metrics.not_x_but_y_per_1k_chars || 0);
11111
+ }
11112
+ cachedAssets = {
11113
+ words,
11114
+ trigrams,
11115
+ normalizationRanges: {
11116
+ slop_words: computeRange(metrics.slop_words),
11117
+ slop_trigrams: computeRange(metrics.slop_trigrams),
11118
+ contrast: computeRange(metrics.contrast)
11119
+ }
11120
+ };
11121
+ return cachedAssets;
11122
+ }
11123
+ function normalizeValue(value, range) {
11124
+ const normalized = (value - range.min) / (range.max - range.min);
11125
+ return Math.max(0, Math.min(1, normalized));
11126
+ }
11127
+ function toPlainText(text) {
11128
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`\n]+`/g, " ").replace(/<[^>]+>/g, " ").replace(/!\[[^\]]*]\([^)]*\)/g, " ").replace(/\[[^\]]*]\([^)]*\)/g, " ").replace(/^#{1,6}\s+/gm, "").replace(/[*_~>#-]+/g, " ");
11129
+ }
11130
+ function tokenize(text) {
11131
+ return text.toLowerCase().match(/[a-z]+(?:'[a-z]+)?/g) ?? [];
11132
+ }
11133
+ function collectMatches(pattern, text) {
11134
+ return [...text.matchAll(pattern)].map((match) => match[0] ?? "");
11135
+ }
11136
+ function runSlopDetector(text) {
11137
+ if (!text.trim()) return [];
11138
+ const assets = loadAssets();
11139
+ const plain = toPlainText(text);
11140
+ const tokens = tokenize(plain);
11141
+ const nTokens = tokens.length || 1;
11142
+ let wordHitCount = 0;
11143
+ const wordHitMap = /* @__PURE__ */ new Map();
11144
+ for (const token of tokens) {
11145
+ if (!assets.words.has(token)) continue;
11146
+ wordHitCount++;
11147
+ wordHitMap.set(token, (wordHitMap.get(token) || 0) + 1);
11148
+ }
11149
+ let trigramHitCount = 0;
11150
+ if (tokens.length >= 3) {
11151
+ for (let i = 0; i < tokens.length - 2; i++) {
11152
+ const tg = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`;
11153
+ if (assets.trigrams.has(tg)) trigramHitCount++;
11154
+ }
11155
+ }
11156
+ const contrastMatches = [
11157
+ .../* @__PURE__ */ new Set([
11158
+ ...collectMatches(CONTRAST_PATTERN, plain),
11159
+ ...collectMatches(NOT_BUT_PATTERN, plain)
11160
+ ])
11161
+ ];
11162
+ const emDashCount = (text.match(/—/g) ?? []).length;
11163
+ const tropeRate = emDashCount / Math.max(plain.length, 1) * 1e3;
11164
+ const wordScore = wordHitCount / nTokens * WORD_MULTIPLIER;
11165
+ const trigramScore = trigramHitCount / nTokens * TRIGRAM_MULTIPLIER;
11166
+ const contrastScore = contrastMatches.length / Math.max(plain.length, 1) * 1e3;
11167
+ const rawSlopScore = (normalizeValue(wordScore, assets.normalizationRanges.slop_words) * WEIGHT_WORD + normalizeValue(contrastScore, assets.normalizationRanges.contrast) * WEIGHT_PATTERN + normalizeValue(trigramScore, assets.normalizationRanges.slop_trigrams) * WEIGHT_TRIGRAM + normalizeValue(tropeRate, { min: 0, max: 5 }) * WEIGHT_TROPE) * 100;
11168
+ const slopScore = Math.round(rawSlopScore * 10) / 10;
11169
+ const issues = [];
11170
+ if (slopScore >= 40) {
11171
+ issues.push({
11172
+ rule: "eqbench-slop-score",
11173
+ engine: "slop-detector",
11174
+ category: "ai",
11175
+ severity: slopScore >= 60 ? "warn" : "info",
11176
+ message: `EQBench SLOP score ${slopScore}/100 (higher = more AI-like).`,
11177
+ suggestion: slopScore >= 60 ? "Rewrite stock phrasing and contrast constructions." : "Review flagged vocabulary and contrast patterns."
11178
+ });
11179
+ }
11180
+ for (const [word2, count] of [...wordHitMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8)) {
11181
+ issues.push({
11182
+ rule: `eqbench-word-${word2.replace(/\s+/g, "-")}`,
11183
+ engine: "slop-detector",
11184
+ category: "ai",
11185
+ severity: "info",
11186
+ message: `EQBench slop vocabulary: \u201C${word2}\u201D (${count}\xD7).`,
11187
+ match: word2
11188
+ });
11189
+ }
11190
+ for (const match of contrastMatches.slice(0, 5)) {
11191
+ issues.push({
11192
+ rule: "eqbench-contrast",
11193
+ engine: "slop-detector",
11194
+ category: "ai",
11195
+ severity: "info",
11196
+ message: `EQBench contrast pattern: \u201C${match.trim()}\u201D`,
11197
+ match
11198
+ });
11199
+ }
11200
+ if (emDashCount >= 3) {
11201
+ issues.push({
11202
+ rule: "eqbench-trope-em-dash",
11203
+ engine: "slop-detector",
11204
+ category: "ai",
11205
+ severity: "info",
11206
+ message: `EQBench typography trope: ${emDashCount} em dash(es).`,
11207
+ match: "\u2014"
11208
+ });
11209
+ }
11210
+ return issues;
11211
+ }
10978
11212
 
10979
11213
  // src/engines/tag.ts
10980
11214
  function tagEngine(issues, engine) {
@@ -11346,7 +11580,7 @@ function countListItems(input) {
11346
11580
  function countBlockquotes(input) {
11347
11581
  return getLineMetadata(input).blockquote_count;
11348
11582
  }
11349
- function tokenize(text) {
11583
+ function tokenize2(text) {
11350
11584
  const blocks = splitParagraphBlocks(text);
11351
11585
  const paragraphs2 = blocks.map((block) => block.text);
11352
11586
  const sentences2 = splitSentencesFromBlocks(blocks);
@@ -11368,7 +11602,7 @@ function tokenize(text) {
11368
11602
  }
11369
11603
  };
11370
11604
  }
11371
- var tokenizeProse = tokenize;
11605
+ var tokenizeProse = tokenize2;
11372
11606
 
11373
11607
  // node_modules/@veldica/prose-analyzer/dist/utils.js
11374
11608
  function round(value, decimals = 4) {
@@ -12112,7 +12346,7 @@ function inventoryMarkers(text, options = {}) {
12112
12346
  }
12113
12347
 
12114
12348
  // src/engines/veldica.ts
12115
- function toSeverity2(value) {
12349
+ function toSeverity4(value) {
12116
12350
  if (value === "high") return "warn";
12117
12351
  return "info";
12118
12352
  }
@@ -12127,7 +12361,7 @@ function runVeldica(text) {
12127
12361
  rule: `veldica-${match.category || "marker"}`,
12128
12362
  engine: "veldica",
12129
12363
  category: "ai",
12130
- severity: toSeverity2(match.severity),
12364
+ severity: toSeverity4(match.severity),
12131
12365
  message: `AI-style marker (${match.category}): \u201C${match.matched_text}\u201D`,
12132
12366
  excerpt: excerptAround(text, offset),
12133
12367
  match: match.matched_text
@@ -12140,8 +12374,8 @@ function runVeldica(text) {
12140
12374
  return issue;
12141
12375
  });
12142
12376
  }
12143
- var require2 = createRequire(import.meta.url);
12144
- var writeGood = require2("write-good");
12377
+ var require4 = createRequire(import.meta.url);
12378
+ var writeGood = require4("write-good");
12145
12379
  function runWriteGood(text) {
12146
12380
  const suggestions = writeGood(text, {
12147
12381
  so: false,
@@ -12365,6 +12599,14 @@ var AI_PATTERN_RULES = [
12365
12599
  severity: "info",
12366
12600
  minCount: ({ words }) => Math.max(6, words / 250)
12367
12601
  },
12602
+ {
12603
+ id: "ai-emojis",
12604
+ category: "ai",
12605
+ pattern: new RegExp("\\p{Extended_Pictographic}", "gu"),
12606
+ message: "Emoji can read as chatty or AI-styled in formal prose.",
12607
+ severity: "info",
12608
+ suggestion: "Prefer plain prose, or set ignoreEmojis: true to allow them."
12609
+ },
12368
12610
  {
12369
12611
  id: "double-space",
12370
12612
  category: "typography",
@@ -13175,7 +13417,7 @@ function computeStats(body, titleLength, descriptionLength, issues) {
13175
13417
  descriptionLength
13176
13418
  };
13177
13419
  }
13178
- function collectIssues(body, frontmatter, format, config) {
13420
+ function collectIssues(body, frontmatter, format, config, source2) {
13179
13421
  const ignoreRules = new Set(config.ignoreRules);
13180
13422
  const issues = [];
13181
13423
  const enginesRun = [];
@@ -13248,6 +13490,18 @@ function collectIssues(body, frontmatter, format, config) {
13248
13490
  enginesRun.push("clean-writing");
13249
13491
  issues.push(...runCleanWriting(body));
13250
13492
  }
13493
+ if (config.engines.aiSlopLinter && config.checks.ai) {
13494
+ enginesRun.push("ai-slop-linter");
13495
+ issues.push(...runAiSlopLinter(body, { source: source2 }));
13496
+ }
13497
+ if (config.engines.slopDetector && config.checks.ai) {
13498
+ enginesRun.push("slop-detector");
13499
+ issues.push(...runSlopDetector(body));
13500
+ }
13501
+ if (config.engines.proseSlop && config.checks.ai) {
13502
+ enginesRun.push("prose-slop");
13503
+ issues.push(...runProseSlop(body, { format }));
13504
+ }
13251
13505
  return {
13252
13506
  issues: issues.filter((issue) => !ignoreRules.has(issue.rule)),
13253
13507
  enginesRun: uniqueEngines(enginesRun)
@@ -13263,7 +13517,8 @@ function checkText(text, options = {}) {
13263
13517
  body,
13264
13518
  parsed.frontmatter,
13265
13519
  format,
13266
- config
13520
+ config,
13521
+ source2
13267
13522
  );
13268
13523
  const title = String(parsed.frontmatter.title ?? pathBasename(source2));
13269
13524
  const report = {
@@ -13290,7 +13545,7 @@ function checkText(text, options = {}) {
13290
13545
  }
13291
13546
  return report;
13292
13547
  }
13293
- var require3 = createRequire(import.meta.url);
13548
+ var require5 = createRequire(import.meta.url);
13294
13549
  function packageRoot() {
13295
13550
  let current = path3.dirname(fileURLToPath(import.meta.url));
13296
13551
  while (true) {
@@ -13329,7 +13584,7 @@ function findValeBinary(cwd2 = process.cwd()) {
13329
13584
  const fromPackage = firstExisting(valeNativeCandidates(packageRoot()));
13330
13585
  if (fromPackage) return fromPackage;
13331
13586
  try {
13332
- const pkg = require3.resolve("@vvago/vale/package.json");
13587
+ const pkg = require5.resolve("@vvago/vale/package.json");
13333
13588
  const dir = path3.dirname(pkg);
13334
13589
  const resolved = firstExisting([
13335
13590
  path3.join(
@@ -13386,8 +13641,8 @@ function valeSeverity(value) {
13386
13641
  if (value === "suggestion") return "info";
13387
13642
  return "warn";
13388
13643
  }
13389
- function valeCategory(check) {
13390
- if (check.startsWith("write-good.") || check.startsWith("Google.")) {
13644
+ function valeCategory(check2) {
13645
+ if (check2.startsWith("write-good.") || check2.startsWith("Google.")) {
13391
13646
  return "grammar";
13392
13647
  }
13393
13648
  return "ai";
@@ -13413,13 +13668,13 @@ function collectValeIssues(files, options = {}) {
13413
13668
  const issues = [];
13414
13669
  for (const raw of alerts) {
13415
13670
  const alert = raw;
13416
- const check = alert.Check ?? "vale";
13671
+ const check2 = alert.Check ?? "vale";
13417
13672
  const issue = {
13418
- rule: check,
13673
+ rule: check2,
13419
13674
  engine: "vale",
13420
- category: valeCategory(check),
13675
+ category: valeCategory(check2),
13421
13676
  severity: valeSeverity(alert.Severity),
13422
- message: alert.Message ?? check
13677
+ message: alert.Message ?? check2
13423
13678
  };
13424
13679
  if (alert.Line) issue.line = alert.Line;
13425
13680
  if (alert.Span?.[0]) issue.column = alert.Span[0];
@@ -1,6 +1,6 @@
1
1
  type Severity = "error" | "warn" | "info";
2
2
  type Category = "ai" | "grammar" | "readability" | "structure" | "seo" | "typography";
3
- type EngineName = "polish" | "slop-gate" | "write-good" | "retext" | "is-this-ai-slop" | "veldica" | "clean-writing" | "vale";
3
+ type EngineName = "polish" | "slop-gate" | "write-good" | "retext" | "is-this-ai-slop" | "veldica" | "clean-writing" | "ai-slop-linter" | "slop-detector" | "prose-slop" | "vale";
4
4
  type TextFormat = "auto" | "plain" | "markdown";
5
5
  type PresetName = "default" | "strict" | "ai" | "grammar";
6
6
  type CheckName = "ai" | "grammar" | "readability" | "structure" | "seo" | "typography";
@@ -62,6 +62,9 @@ type EnginesConfig = {
62
62
  isThisAiSlop?: boolean;
63
63
  veldica?: boolean;
64
64
  cleanWriting?: boolean;
65
+ aiSlopLinter?: boolean;
66
+ slopDetector?: boolean;
67
+ proseSlop?: boolean;
65
68
  vale?: boolean;
66
69
  };
67
70
  type MarkdownConfig = {
@@ -90,6 +93,8 @@ type UserConfig = {
90
93
  failOn?: Severity;
91
94
  minScore?: number;
92
95
  ignoreRules?: string[];
96
+ /** When true, skip the ai-emojis rule. Default false (emojis are flagged). */
97
+ ignoreEmojis?: boolean;
93
98
  include?: string[];
94
99
  exclude?: string[];
95
100
  extensions?: string[];
@@ -105,6 +110,7 @@ type ResolvedConfig = {
105
110
  failOn: Severity;
106
111
  minScore: number;
107
112
  ignoreRules: string[];
113
+ ignoreEmojis: boolean;
108
114
  include: string[];
109
115
  exclude: string[];
110
116
  extensions: string[];
@@ -1,6 +1,6 @@
1
1
  type Severity = "error" | "warn" | "info";
2
2
  type Category = "ai" | "grammar" | "readability" | "structure" | "seo" | "typography";
3
- type EngineName = "polish" | "slop-gate" | "write-good" | "retext" | "is-this-ai-slop" | "veldica" | "clean-writing" | "vale";
3
+ type EngineName = "polish" | "slop-gate" | "write-good" | "retext" | "is-this-ai-slop" | "veldica" | "clean-writing" | "ai-slop-linter" | "slop-detector" | "prose-slop" | "vale";
4
4
  type TextFormat = "auto" | "plain" | "markdown";
5
5
  type PresetName = "default" | "strict" | "ai" | "grammar";
6
6
  type CheckName = "ai" | "grammar" | "readability" | "structure" | "seo" | "typography";
@@ -62,6 +62,9 @@ type EnginesConfig = {
62
62
  isThisAiSlop?: boolean;
63
63
  veldica?: boolean;
64
64
  cleanWriting?: boolean;
65
+ aiSlopLinter?: boolean;
66
+ slopDetector?: boolean;
67
+ proseSlop?: boolean;
65
68
  vale?: boolean;
66
69
  };
67
70
  type MarkdownConfig = {
@@ -90,6 +93,8 @@ type UserConfig = {
90
93
  failOn?: Severity;
91
94
  minScore?: number;
92
95
  ignoreRules?: string[];
96
+ /** When true, skip the ai-emojis rule. Default false (emojis are flagged). */
97
+ ignoreEmojis?: boolean;
93
98
  include?: string[];
94
99
  exclude?: string[];
95
100
  extensions?: string[];
@@ -105,6 +110,7 @@ type ResolvedConfig = {
105
110
  failOn: Severity;
106
111
  minScore: number;
107
112
  ignoreRules: string[];
113
+ ignoreEmojis: boolean;
108
114
  include: string[];
109
115
  exclude: string[];
110
116
  extensions: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-text-check",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Detect AI-generated writing tells, grammar issues, and synthetic typography in JavaScript and TypeScript projects.",
5
5
  "license": "MIT",
6
6
  "author": "Pritam Ghosh <contact.meghoshpritam@gmail.com>",
@@ -99,15 +99,18 @@
99
99
  "dependencies": {
100
100
  "@veldica/prose-linter": "^1.1.3",
101
101
  "@vvago/vale": "^3.22.0",
102
+ "ai-slop-linter": "^0.1.5",
102
103
  "clean-writing-lint": "^0.1.0",
103
104
  "is-this-ai-slop": "^0.1.0",
104
105
  "marked": "^15.0.12",
106
+ "prose-slop": "^0.4.0",
105
107
  "retext-english": "^5.0.0",
106
108
  "retext-passive": "^5.0.0",
107
109
  "retext-readability": "^8.0.0",
108
110
  "retext-repeated-words": "^5.0.0",
109
111
  "retext-simplify": "^8.0.0",
110
112
  "retext-stringify": "^4.0.0",
113
+ "slop-detector": "^1.2.0",
111
114
  "slop-gate": "^0.5.0",
112
115
  "unified": "^11.0.5",
113
116
  "write-good": "^1.0.8"