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/cli.js CHANGED
@@ -567,6 +567,9 @@ var ENGINE_PRESETS = {
567
567
  isThisAiSlop: true,
568
568
  veldica: true,
569
569
  cleanWriting: true,
570
+ aiSlopLinter: true,
571
+ slopDetector: true,
572
+ proseSlop: true,
570
573
  vale: true
571
574
  },
572
575
  strict: {
@@ -577,6 +580,9 @@ var ENGINE_PRESETS = {
577
580
  isThisAiSlop: true,
578
581
  veldica: true,
579
582
  cleanWriting: true,
583
+ aiSlopLinter: true,
584
+ slopDetector: true,
585
+ proseSlop: true,
580
586
  vale: true
581
587
  },
582
588
  ai: {
@@ -587,6 +593,9 @@ var ENGINE_PRESETS = {
587
593
  isThisAiSlop: true,
588
594
  veldica: true,
589
595
  cleanWriting: true,
596
+ aiSlopLinter: true,
597
+ slopDetector: true,
598
+ proseSlop: true,
590
599
  vale: true
591
600
  },
592
601
  grammar: {
@@ -597,6 +606,9 @@ var ENGINE_PRESETS = {
597
606
  isThisAiSlop: false,
598
607
  veldica: false,
599
608
  cleanWriting: true,
609
+ aiSlopLinter: false,
610
+ slopDetector: false,
611
+ proseSlop: false,
600
612
  vale: true
601
613
  }
602
614
  };
@@ -617,6 +629,9 @@ var ENGINE_KEYS = [
617
629
  "isThisAiSlop",
618
630
  "veldica",
619
631
  "cleanWriting",
632
+ "aiSlopLinter",
633
+ "slopDetector",
634
+ "proseSlop",
620
635
  "vale"
621
636
  ];
622
637
  function isObject(value) {
@@ -736,6 +751,14 @@ function validateUserConfig(value) {
736
751
  config.ignoreRules = value.ignoreRules;
737
752
  }
738
753
  }
754
+ const ignoreEmojis = expectBoolean(
755
+ value.ignoreEmojis,
756
+ "ignoreEmojis",
757
+ errors
758
+ );
759
+ if (ignoreEmojis !== void 0) {
760
+ config.ignoreEmojis = ignoreEmojis;
761
+ }
739
762
  if (value.include !== void 0) {
740
763
  if (!isStringArray(value.include)) {
741
764
  errors.push("include must be an array of strings");
@@ -869,6 +892,7 @@ function validateUserConfig(value) {
869
892
  "failOn",
870
893
  "minScore",
871
894
  "ignoreRules",
895
+ "ignoreEmojis",
872
896
  "include",
873
897
  "exclude",
874
898
  "extensions",
@@ -892,7 +916,14 @@ function resolveConfig(user = {}) {
892
916
  format: user.format ?? "auto",
893
917
  failOn: user.failOn ?? "warn",
894
918
  minScore: user.minScore ?? 0,
895
- ignoreRules: user.ignoreRules ?? [],
919
+ ignoreRules: (() => {
920
+ const rules = [...user.ignoreRules ?? []];
921
+ if (user.ignoreEmojis ?? false) {
922
+ rules.push("ai-emojis");
923
+ }
924
+ return rules;
925
+ })(),
926
+ ignoreEmojis: user.ignoreEmojis ?? false,
896
927
  include: user.include ?? [],
897
928
  exclude: user.exclude ?? [],
898
929
  extensions: (user.extensions ?? DEFAULT_EXTENSIONS).map(
@@ -925,6 +956,30 @@ function isStrictPreset(config) {
925
956
  // src/node/check-files.ts
926
957
  import fs3 from "fs";
927
958
 
959
+ // src/engines/ai-slop-linter.ts
960
+ import { lintText } from "ai-slop-linter";
961
+ function toSeverity(value) {
962
+ if (value === "error") return "error";
963
+ if (value === "warning" || value === "warn") return "warn";
964
+ return "info";
965
+ }
966
+ function runAiSlopLinter(text, options = {}) {
967
+ const result = lintText(options.source ?? "<text>", text, { floor: 0 });
968
+ return result.findings.map((finding) => {
969
+ const issue = {
970
+ rule: `asl-${finding.rule}`,
971
+ engine: "ai-slop-linter",
972
+ category: "ai",
973
+ severity: toSeverity(finding.severity),
974
+ message: finding.message,
975
+ line: finding.line,
976
+ column: finding.column,
977
+ excerpt: finding.excerpt
978
+ };
979
+ return issue;
980
+ });
981
+ }
982
+
928
983
  // node_modules/clean-writing-lint/src/mask.js
929
984
  function blank(text) {
930
985
  return text.replace(/[^\n]/g, " ");
@@ -1941,7 +1996,7 @@ var AI_RULES = /* @__PURE__ */ new Set([
1941
1996
  "banned-construction",
1942
1997
  "em-dash"
1943
1998
  ]);
1944
- function toSeverity(value) {
1999
+ function toSeverity2(value) {
1945
2000
  if (value === "error") return "error";
1946
2001
  if (value === "warn") return "warn";
1947
2002
  return "info";
@@ -1961,7 +2016,7 @@ function runCleanWriting(text) {
1961
2016
  rule: `cws-${violation.rule}`,
1962
2017
  engine: "clean-writing",
1963
2018
  category: AI_RULES.has(violation.rule) ? "ai" : "grammar",
1964
- severity: toSeverity(violation.severity),
2019
+ severity: toSeverity2(violation.severity),
1965
2020
  message: violation.message,
1966
2021
  line: violation.line,
1967
2022
  column: violation.column,
@@ -1992,6 +2047,36 @@ function runIsThisAiSlop(text) {
1992
2047
  });
1993
2048
  }
1994
2049
 
2050
+ // src/engines/prose-slop.ts
2051
+ import { createRequire } from "module";
2052
+ var require2 = createRequire(import.meta.url);
2053
+ var { check } = require2("prose-slop");
2054
+ function toSeverity3(value) {
2055
+ if (value === "error") return "error";
2056
+ if (value === "warning" || value === "warn") return "warn";
2057
+ return "info";
2058
+ }
2059
+ function runProseSlop(text, options = {}) {
2060
+ const findings = check(text, {
2061
+ format: options.format ?? "plain",
2062
+ lang: "en"
2063
+ });
2064
+ return findings.map((finding) => {
2065
+ const issue = {
2066
+ rule: `prose-${finding.ruleId}`,
2067
+ engine: "prose-slop",
2068
+ category: "ai",
2069
+ severity: toSeverity3(finding.severity),
2070
+ message: finding.message,
2071
+ line: finding.line,
2072
+ column: finding.column,
2073
+ excerpt: excerptAround(text, finding.index),
2074
+ match: finding.match
2075
+ };
2076
+ return issue;
2077
+ });
2078
+ }
2079
+
1995
2080
  // node_modules/nlcst-to-string/lib/index.js
1996
2081
  var emptyNodes = [];
1997
2082
  function toString(value) {
@@ -2952,10 +3037,10 @@ function anyFactory(tests) {
2952
3037
  return false;
2953
3038
  }
2954
3039
  }
2955
- function propertiesFactory(check) {
3040
+ function propertiesFactory(check2) {
2956
3041
  const checkAsRecord = (
2957
3042
  /** @type {Record<string, unknown>} */
2958
- check
3043
+ check2
2959
3044
  );
2960
3045
  return castFactory(all);
2961
3046
  function all(node) {
@@ -2965,21 +3050,21 @@ function propertiesFactory(check) {
2965
3050
  node
2966
3051
  );
2967
3052
  let key;
2968
- for (key in check) {
3053
+ for (key in check2) {
2969
3054
  if (nodeAsRecord[key] !== checkAsRecord[key]) return false;
2970
3055
  }
2971
3056
  return true;
2972
3057
  }
2973
3058
  }
2974
- function typeFactory(check) {
3059
+ function typeFactory(check2) {
2975
3060
  return castFactory(type);
2976
3061
  function type(node) {
2977
- return node && node.type === check;
3062
+ return node && node.type === check2;
2978
3063
  }
2979
3064
  }
2980
3065
  function castFactory(testFunction) {
2981
- return check;
2982
- function check(value, index2, parent) {
3066
+ return check2;
3067
+ function check2(value, index2, parent) {
2983
3068
  return Boolean(
2984
3069
  looksLikeANode(value) && testFunction.call(
2985
3070
  this,
@@ -3008,14 +3093,14 @@ var CONTINUE = true;
3008
3093
  var EXIT = false;
3009
3094
  var SKIP = "skip";
3010
3095
  function visitParents(tree, test, visitor, reverse) {
3011
- let check;
3096
+ let check2;
3012
3097
  if (typeof test === "function" && typeof visitor !== "function") {
3013
3098
  reverse = visitor;
3014
3099
  visitor = test;
3015
3100
  } else {
3016
- check = test;
3101
+ check2 = test;
3017
3102
  }
3018
- const is2 = convert(check);
3103
+ const is2 = convert(check2);
3019
3104
  const step = reverse ? -1 : 1;
3020
3105
  factory(tree, void 0, [])();
3021
3106
  function factory(node, index2, parents) {
@@ -9660,9 +9745,9 @@ var VFile = class {
9660
9745
  * @returns {undefined}
9661
9746
  * Nothing.
9662
9747
  */
9663
- set dirname(dirname2) {
9748
+ set dirname(dirname3) {
9664
9749
  assertPath(this.basename, "dirname");
9665
- this.path = default2.join(dirname2 || "", this.basename);
9750
+ this.path = default2.join(dirname3 || "", this.basename);
9666
9751
  }
9667
9752
  /**
9668
9753
  * Get the extname (including dot) (example: `'.js'`).
@@ -10696,6 +10781,165 @@ function runRetext(text) {
10696
10781
  });
10697
10782
  }
10698
10783
 
10784
+ // src/engines/slop-detector.ts
10785
+ import { readFileSync as readFileSync3 } from "fs";
10786
+ import { createRequire as createRequire2 } from "module";
10787
+ import { dirname as dirname2, join as join3 } from "path";
10788
+ var WEIGHT_WORD = 0.5;
10789
+ var WEIGHT_PATTERN = 0.2;
10790
+ var WEIGHT_TRIGRAM = 0.12;
10791
+ var WEIGHT_TROPE = 0.18;
10792
+ var WORD_MULTIPLIER = 1e3;
10793
+ var TRIGRAM_MULTIPLIER = 1e3;
10794
+ 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;
10795
+ var NOT_BUT_PATTERN = /\bnot\b[\s\S]{0,60}?\bbut\b/gi;
10796
+ var require3 = createRequire2(import.meta.url);
10797
+ var assetsDir = join3(
10798
+ dirname2(require3.resolve("slop-detector/package.json")),
10799
+ "dist",
10800
+ "assets"
10801
+ );
10802
+ var cachedAssets = null;
10803
+ function computeRange(values) {
10804
+ if (values.length === 0) return { min: 0, max: 1 };
10805
+ const min = Math.min(...values);
10806
+ const max = Math.max(...values);
10807
+ const range = max - min || 1;
10808
+ return { min: min - range * 0.1, max: max + range * 0.1 };
10809
+ }
10810
+ function loadAssets() {
10811
+ if (cachedAssets) return cachedAssets;
10812
+ const wordsRaw = JSON.parse(
10813
+ readFileSync3(join3(assetsDir, "slop_list.json"), "utf8")
10814
+ );
10815
+ const trigramsRaw = JSON.parse(
10816
+ readFileSync3(join3(assetsDir, "slop_list_trigrams.json"), "utf8")
10817
+ );
10818
+ const leaderboard = JSON.parse(
10819
+ readFileSync3(join3(assetsDir, "leaderboard_results.json"), "utf8")
10820
+ );
10821
+ const words = /* @__PURE__ */ new Set();
10822
+ for (const item of wordsRaw) {
10823
+ if (Array.isArray(item) && item[0]) words.add(item[0].toLowerCase());
10824
+ }
10825
+ const trigrams = /* @__PURE__ */ new Set();
10826
+ for (const item of trigramsRaw) {
10827
+ if (Array.isArray(item) && item[0]) trigrams.add(item[0].toLowerCase());
10828
+ }
10829
+ const metrics = {
10830
+ slop_words: [],
10831
+ slop_trigrams: [],
10832
+ contrast: []
10833
+ };
10834
+ for (const res of leaderboard.results ?? []) {
10835
+ if (!res.metrics) continue;
10836
+ metrics.slop_words.push(res.metrics.slop_list_matches_per_1k_words || 0);
10837
+ metrics.slop_trigrams.push(
10838
+ res.metrics.slop_trigram_matches_per_1k_words || 0
10839
+ );
10840
+ metrics.contrast.push(res.metrics.not_x_but_y_per_1k_chars || 0);
10841
+ }
10842
+ cachedAssets = {
10843
+ words,
10844
+ trigrams,
10845
+ normalizationRanges: {
10846
+ slop_words: computeRange(metrics.slop_words),
10847
+ slop_trigrams: computeRange(metrics.slop_trigrams),
10848
+ contrast: computeRange(metrics.contrast)
10849
+ }
10850
+ };
10851
+ return cachedAssets;
10852
+ }
10853
+ function normalizeValue(value, range) {
10854
+ const normalized = (value - range.min) / (range.max - range.min);
10855
+ return Math.max(0, Math.min(1, normalized));
10856
+ }
10857
+ function toPlainText(text) {
10858
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`\n]+`/g, " ").replace(/<[^>]+>/g, " ").replace(/!\[[^\]]*]\([^)]*\)/g, " ").replace(/\[[^\]]*]\([^)]*\)/g, " ").replace(/^#{1,6}\s+/gm, "").replace(/[*_~>#-]+/g, " ");
10859
+ }
10860
+ function tokenize(text) {
10861
+ return text.toLowerCase().match(/[a-z]+(?:'[a-z]+)?/g) ?? [];
10862
+ }
10863
+ function collectMatches(pattern, text) {
10864
+ return [...text.matchAll(pattern)].map((match) => match[0] ?? "");
10865
+ }
10866
+ function runSlopDetector(text) {
10867
+ if (!text.trim()) return [];
10868
+ const assets = loadAssets();
10869
+ const plain = toPlainText(text);
10870
+ const tokens = tokenize(plain);
10871
+ const nTokens = tokens.length || 1;
10872
+ let wordHitCount = 0;
10873
+ const wordHitMap = /* @__PURE__ */ new Map();
10874
+ for (const token of tokens) {
10875
+ if (!assets.words.has(token)) continue;
10876
+ wordHitCount++;
10877
+ wordHitMap.set(token, (wordHitMap.get(token) || 0) + 1);
10878
+ }
10879
+ let trigramHitCount = 0;
10880
+ if (tokens.length >= 3) {
10881
+ for (let i = 0; i < tokens.length - 2; i++) {
10882
+ const tg = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`;
10883
+ if (assets.trigrams.has(tg)) trigramHitCount++;
10884
+ }
10885
+ }
10886
+ const contrastMatches = [
10887
+ .../* @__PURE__ */ new Set([
10888
+ ...collectMatches(CONTRAST_PATTERN, plain),
10889
+ ...collectMatches(NOT_BUT_PATTERN, plain)
10890
+ ])
10891
+ ];
10892
+ const emDashCount = (text.match(/—/g) ?? []).length;
10893
+ const tropeRate = emDashCount / Math.max(plain.length, 1) * 1e3;
10894
+ const wordScore = wordHitCount / nTokens * WORD_MULTIPLIER;
10895
+ const trigramScore = trigramHitCount / nTokens * TRIGRAM_MULTIPLIER;
10896
+ const contrastScore = contrastMatches.length / Math.max(plain.length, 1) * 1e3;
10897
+ 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;
10898
+ const slopScore = Math.round(rawSlopScore * 10) / 10;
10899
+ const issues = [];
10900
+ if (slopScore >= 40) {
10901
+ issues.push({
10902
+ rule: "eqbench-slop-score",
10903
+ engine: "slop-detector",
10904
+ category: "ai",
10905
+ severity: slopScore >= 60 ? "warn" : "info",
10906
+ message: `EQBench SLOP score ${slopScore}/100 (higher = more AI-like).`,
10907
+ suggestion: slopScore >= 60 ? "Rewrite stock phrasing and contrast constructions." : "Review flagged vocabulary and contrast patterns."
10908
+ });
10909
+ }
10910
+ for (const [word2, count] of [...wordHitMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8)) {
10911
+ issues.push({
10912
+ rule: `eqbench-word-${word2.replace(/\s+/g, "-")}`,
10913
+ engine: "slop-detector",
10914
+ category: "ai",
10915
+ severity: "info",
10916
+ message: `EQBench slop vocabulary: \u201C${word2}\u201D (${count}\xD7).`,
10917
+ match: word2
10918
+ });
10919
+ }
10920
+ for (const match of contrastMatches.slice(0, 5)) {
10921
+ issues.push({
10922
+ rule: "eqbench-contrast",
10923
+ engine: "slop-detector",
10924
+ category: "ai",
10925
+ severity: "info",
10926
+ message: `EQBench contrast pattern: \u201C${match.trim()}\u201D`,
10927
+ match
10928
+ });
10929
+ }
10930
+ if (emDashCount >= 3) {
10931
+ issues.push({
10932
+ rule: "eqbench-trope-em-dash",
10933
+ engine: "slop-detector",
10934
+ category: "ai",
10935
+ severity: "info",
10936
+ message: `EQBench typography trope: ${emDashCount} em dash(es).`,
10937
+ match: "\u2014"
10938
+ });
10939
+ }
10940
+ return issues;
10941
+ }
10942
+
10699
10943
  // src/engines/tag.ts
10700
10944
  function tagEngine(issues, engine) {
10701
10945
  return issues.map((issue) => ({ ...issue, engine }));
@@ -11066,7 +11310,7 @@ function countListItems(input) {
11066
11310
  function countBlockquotes(input) {
11067
11311
  return getLineMetadata(input).blockquote_count;
11068
11312
  }
11069
- function tokenize(text) {
11313
+ function tokenize2(text) {
11070
11314
  const blocks = splitParagraphBlocks(text);
11071
11315
  const paragraphs2 = blocks.map((block) => block.text);
11072
11316
  const sentences2 = splitSentencesFromBlocks(blocks);
@@ -11088,7 +11332,7 @@ function tokenize(text) {
11088
11332
  }
11089
11333
  };
11090
11334
  }
11091
- var tokenizeProse = tokenize;
11335
+ var tokenizeProse = tokenize2;
11092
11336
 
11093
11337
  // node_modules/@veldica/prose-analyzer/dist/utils.js
11094
11338
  function round(value, decimals = 4) {
@@ -11832,7 +12076,7 @@ function inventoryMarkers(text, options = {}) {
11832
12076
  }
11833
12077
 
11834
12078
  // src/engines/veldica.ts
11835
- function toSeverity2(value) {
12079
+ function toSeverity4(value) {
11836
12080
  if (value === "high") return "warn";
11837
12081
  return "info";
11838
12082
  }
@@ -11847,7 +12091,7 @@ function runVeldica(text) {
11847
12091
  rule: `veldica-${match.category || "marker"}`,
11848
12092
  engine: "veldica",
11849
12093
  category: "ai",
11850
- severity: toSeverity2(match.severity),
12094
+ severity: toSeverity4(match.severity),
11851
12095
  message: `AI-style marker (${match.category}): \u201C${match.matched_text}\u201D`,
11852
12096
  excerpt: excerptAround(text, offset),
11853
12097
  match: match.matched_text
@@ -11862,9 +12106,9 @@ function runVeldica(text) {
11862
12106
  }
11863
12107
 
11864
12108
  // src/engines/write-good.ts
11865
- import { createRequire } from "module";
11866
- var require2 = createRequire(import.meta.url);
11867
- var writeGood = require2("write-good");
12109
+ import { createRequire as createRequire3 } from "module";
12110
+ var require4 = createRequire3(import.meta.url);
12111
+ var writeGood = require4("write-good");
11868
12112
  function runWriteGood(text) {
11869
12113
  const suggestions = writeGood(text, {
11870
12114
  so: false,
@@ -12088,6 +12332,14 @@ var AI_PATTERN_RULES = [
12088
12332
  severity: "info",
12089
12333
  minCount: ({ words }) => Math.max(6, words / 250)
12090
12334
  },
12335
+ {
12336
+ id: "ai-emojis",
12337
+ category: "ai",
12338
+ pattern: new RegExp("\\p{Extended_Pictographic}", "gu"),
12339
+ message: "Emoji can read as chatty or AI-styled in formal prose.",
12340
+ severity: "info",
12341
+ suggestion: "Prefer plain prose, or set ignoreEmojis: true to allow them."
12342
+ },
12091
12343
  {
12092
12344
  id: "double-space",
12093
12345
  category: "typography",
@@ -12952,7 +13204,7 @@ function computeStats(body, titleLength, descriptionLength, issues) {
12952
13204
  descriptionLength
12953
13205
  };
12954
13206
  }
12955
- function collectIssues(body, frontmatter, format, config) {
13207
+ function collectIssues(body, frontmatter, format, config, source2) {
12956
13208
  const ignoreRules = new Set(config.ignoreRules);
12957
13209
  const issues = [];
12958
13210
  const enginesRun = [];
@@ -13025,6 +13277,18 @@ function collectIssues(body, frontmatter, format, config) {
13025
13277
  enginesRun.push("clean-writing");
13026
13278
  issues.push(...runCleanWriting(body));
13027
13279
  }
13280
+ if (config.engines.aiSlopLinter && config.checks.ai) {
13281
+ enginesRun.push("ai-slop-linter");
13282
+ issues.push(...runAiSlopLinter(body, { source: source2 }));
13283
+ }
13284
+ if (config.engines.slopDetector && config.checks.ai) {
13285
+ enginesRun.push("slop-detector");
13286
+ issues.push(...runSlopDetector(body));
13287
+ }
13288
+ if (config.engines.proseSlop && config.checks.ai) {
13289
+ enginesRun.push("prose-slop");
13290
+ issues.push(...runProseSlop(body, { format }));
13291
+ }
13028
13292
  return {
13029
13293
  issues: issues.filter((issue) => !ignoreRules.has(issue.rule)),
13030
13294
  enginesRun: uniqueEngines(enginesRun)
@@ -13040,7 +13304,8 @@ function checkText(text, options = {}) {
13040
13304
  body,
13041
13305
  parsed.frontmatter,
13042
13306
  format,
13043
- config
13307
+ config,
13308
+ source2
13044
13309
  );
13045
13310
  const title = String(parsed.frontmatter.title ?? pathBasename(source2));
13046
13311
  const report = {
@@ -13073,11 +13338,11 @@ import path2 from "path";
13073
13338
 
13074
13339
  // src/node/vale.ts
13075
13340
  import { spawnSync } from "child_process";
13076
- import { createRequire as createRequire2 } from "module";
13341
+ import { createRequire as createRequire4 } from "module";
13077
13342
  import fs from "fs";
13078
13343
  import path from "path";
13079
13344
  import { fileURLToPath as fileURLToPath2 } from "url";
13080
- var require3 = createRequire2(import.meta.url);
13345
+ var require5 = createRequire4(import.meta.url);
13081
13346
  function packageRoot() {
13082
13347
  let current = path.dirname(fileURLToPath2(import.meta.url));
13083
13348
  while (true) {
@@ -13116,7 +13381,7 @@ function findValeBinary(cwd = process.cwd()) {
13116
13381
  const fromPackage = firstExisting(valeNativeCandidates(packageRoot()));
13117
13382
  if (fromPackage) return fromPackage;
13118
13383
  try {
13119
- const pkg = require3.resolve("@vvago/vale/package.json");
13384
+ const pkg = require5.resolve("@vvago/vale/package.json");
13120
13385
  const dir = path.dirname(pkg);
13121
13386
  const resolved = firstExisting([
13122
13387
  path.join(
@@ -13173,8 +13438,8 @@ function valeSeverity(value) {
13173
13438
  if (value === "suggestion") return "info";
13174
13439
  return "warn";
13175
13440
  }
13176
- function valeCategory(check) {
13177
- if (check.startsWith("write-good.") || check.startsWith("Google.")) {
13441
+ function valeCategory(check2) {
13442
+ if (check2.startsWith("write-good.") || check2.startsWith("Google.")) {
13178
13443
  return "grammar";
13179
13444
  }
13180
13445
  return "ai";
@@ -13200,13 +13465,13 @@ function collectValeIssues(files, options = {}) {
13200
13465
  const issues = [];
13201
13466
  for (const raw of alerts) {
13202
13467
  const alert = raw;
13203
- const check = alert.Check ?? "vale";
13468
+ const check2 = alert.Check ?? "vale";
13204
13469
  const issue = {
13205
- rule: check,
13470
+ rule: check2,
13206
13471
  engine: "vale",
13207
- category: valeCategory(check),
13472
+ category: valeCategory(check2),
13208
13473
  severity: valeSeverity(alert.Severity),
13209
- message: alert.Message ?? check
13474
+ message: alert.Message ?? check2
13210
13475
  };
13211
13476
  if (alert.Line) issue.line = alert.Line;
13212
13477
  if (alert.Span?.[0]) issue.column = alert.Span[0];
@@ -13435,6 +13700,7 @@ Options:
13435
13700
  --no-slop Skip slop-gate vocabulary and is-this-ai-slop
13436
13701
  --no-write-good Skip the write-good grammar engine
13437
13702
  --no-retext Skip retext (passive, readability, simplify, repeats)
13703
+ --ignore-emojis Allow emojis (skip the ai-emojis rule; off by default)
13438
13704
  --ignore-rule ID Ignore a rule (repeatable)
13439
13705
  --config PATH Path to ai-text-check.config.json
13440
13706
  --help, -h Show this help
@@ -13442,7 +13708,9 @@ Options:
13442
13708
  -- Extra flags after -- are passed to Vale
13443
13709
 
13444
13710
  By default every local engine runs: polish, slop-gate, write-good, retext,
13445
- is-this-ai-slop, Veldica, clean-writing, and Vale.
13711
+ is-this-ai-slop, Veldica, clean-writing, ai-slop-linter, slop-detector,
13712
+ prose-slop, and Vale. Emoji in prose is flagged as an AI tell unless
13713
+ --ignore-emojis (or ignoreEmojis: true) is set.
13446
13714
 
13447
13715
  Examples:
13448
13716
  ai-text-check content
@@ -13463,6 +13731,7 @@ function parseArgs(argv) {
13463
13731
  slop: true,
13464
13732
  writeGood: true,
13465
13733
  retext: true,
13734
+ ignoreEmojis: false,
13466
13735
  ignoreRules: [],
13467
13736
  valeArgs: []
13468
13737
  };
@@ -13528,6 +13797,9 @@ function parseArgs(argv) {
13528
13797
  case "--no-retext":
13529
13798
  options.retext = false;
13530
13799
  break;
13800
+ case "--ignore-emojis":
13801
+ options.ignoreEmojis = true;
13802
+ break;
13531
13803
  case "--min-score": {
13532
13804
  const value = Number(args[++i]);
13533
13805
  if (!Number.isFinite(value)) {
@@ -13588,6 +13860,7 @@ function toUserConfig(options) {
13588
13860
  if (options.failOn) config.failOn = options.failOn;
13589
13861
  if (options.minScore !== void 0) config.minScore = options.minScore;
13590
13862
  if (options.ignoreRules.length > 0) config.ignoreRules = options.ignoreRules;
13863
+ if (options.ignoreEmojis) config.ignoreEmojis = true;
13591
13864
  const engines = {};
13592
13865
  if (!options.vale) engines.vale = false;
13593
13866
  if (!options.slop) {
@@ -13603,9 +13876,9 @@ function toUserConfig(options) {
13603
13876
  // src/cli/init.ts
13604
13877
  import fs5 from "fs";
13605
13878
  import path5 from "path";
13606
- import { createRequire as createRequire3 } from "module";
13879
+ import { createRequire as createRequire5 } from "module";
13607
13880
  import { fileURLToPath as fileURLToPath3 } from "url";
13608
- var require4 = createRequire3(import.meta.url);
13881
+ var require6 = createRequire5(import.meta.url);
13609
13882
  function packageVersion() {
13610
13883
  let current = path5.dirname(fileURLToPath3(import.meta.url));
13611
13884
  while (true) {
@@ -13618,7 +13891,7 @@ function packageVersion() {
13618
13891
  if (parent === current) break;
13619
13892
  current = parent;
13620
13893
  }
13621
- return require4("../../package.json").version;
13894
+ return require6("../../package.json").version;
13622
13895
  }
13623
13896
  function findPackageRoot() {
13624
13897
  let current = path5.dirname(fileURLToPath3(import.meta.url));
@@ -13648,7 +13921,8 @@ var SAMPLE_CONFIG = `{
13648
13921
  "failOn": "warn",
13649
13922
  "minScore": 0,
13650
13923
  "extensions": [".md", ".mdx", ".markdown", ".txt"],
13651
- "ignoreRules": []
13924
+ "ignoreRules": [],
13925
+ "ignoreEmojis": false
13652
13926
  }
13653
13927
  `;
13654
13928
  function initProject(cwd, withVale) {