ai-text-check 1.1.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/index.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- require('fs');
4
- require('path');
3
+ var aiSlopLinter = require('ai-slop-linter');
4
+ var fs = require('fs');
5
+ var path = require('path');
5
6
  require('url');
6
7
  var isThisAiSlop = require('is-this-ai-slop');
7
8
  var module$1 = require('module');
@@ -568,6 +569,9 @@ var ENGINE_PRESETS = {
568
569
  isThisAiSlop: true,
569
570
  veldica: true,
570
571
  cleanWriting: true,
572
+ aiSlopLinter: true,
573
+ slopDetector: true,
574
+ proseSlop: true,
571
575
  vale: true
572
576
  },
573
577
  strict: {
@@ -578,6 +582,9 @@ var ENGINE_PRESETS = {
578
582
  isThisAiSlop: true,
579
583
  veldica: true,
580
584
  cleanWriting: true,
585
+ aiSlopLinter: true,
586
+ slopDetector: true,
587
+ proseSlop: true,
581
588
  vale: true
582
589
  },
583
590
  ai: {
@@ -588,6 +595,9 @@ var ENGINE_PRESETS = {
588
595
  isThisAiSlop: true,
589
596
  veldica: true,
590
597
  cleanWriting: true,
598
+ aiSlopLinter: true,
599
+ slopDetector: true,
600
+ proseSlop: true,
591
601
  vale: true
592
602
  },
593
603
  grammar: {
@@ -598,6 +608,9 @@ var ENGINE_PRESETS = {
598
608
  isThisAiSlop: false,
599
609
  veldica: false,
600
610
  cleanWriting: true,
611
+ aiSlopLinter: false,
612
+ slopDetector: false,
613
+ proseSlop: false,
601
614
  vale: true
602
615
  }
603
616
  };
@@ -618,6 +631,9 @@ var ENGINE_KEYS = [
618
631
  "isThisAiSlop",
619
632
  "veldica",
620
633
  "cleanWriting",
634
+ "aiSlopLinter",
635
+ "slopDetector",
636
+ "proseSlop",
621
637
  "vale"
622
638
  ];
623
639
  function isObject(value) {
@@ -946,6 +962,27 @@ function defineConfig(config) {
946
962
  function isStrictPreset(config) {
947
963
  return config.preset === "strict";
948
964
  }
965
+ function toSeverity(value) {
966
+ if (value === "error") return "error";
967
+ if (value === "warning" || value === "warn") return "warn";
968
+ return "info";
969
+ }
970
+ function runAiSlopLinter(text, options = {}) {
971
+ const result = aiSlopLinter.lintText(options.source ?? "<text>", text, { floor: 0 });
972
+ return result.findings.map((finding) => {
973
+ const issue = {
974
+ rule: `asl-${finding.rule}`,
975
+ engine: "ai-slop-linter",
976
+ category: "ai",
977
+ severity: toSeverity(finding.severity),
978
+ message: finding.message,
979
+ line: finding.line,
980
+ column: finding.column,
981
+ excerpt: finding.excerpt
982
+ };
983
+ return issue;
984
+ });
985
+ }
949
986
 
950
987
  // node_modules/clean-writing-lint/src/mask.js
951
988
  function blank(text) {
@@ -1915,7 +1952,7 @@ var AI_RULES = /* @__PURE__ */ new Set([
1915
1952
  "banned-construction",
1916
1953
  "em-dash"
1917
1954
  ]);
1918
- function toSeverity(value) {
1955
+ function toSeverity2(value) {
1919
1956
  if (value === "error") return "error";
1920
1957
  if (value === "warn") return "warn";
1921
1958
  return "info";
@@ -1935,7 +1972,7 @@ function runCleanWriting(text) {
1935
1972
  rule: `cws-${violation.rule}`,
1936
1973
  engine: "clean-writing",
1937
1974
  category: AI_RULES.has(violation.rule) ? "ai" : "grammar",
1938
- severity: toSeverity(violation.severity),
1975
+ severity: toSeverity2(violation.severity),
1939
1976
  message: violation.message,
1940
1977
  line: violation.line,
1941
1978
  column: violation.column,
@@ -1962,6 +1999,33 @@ function runIsThisAiSlop(text) {
1962
1999
  return issue;
1963
2000
  });
1964
2001
  }
2002
+ var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
2003
+ var { check } = require2("prose-slop");
2004
+ function toSeverity3(value) {
2005
+ if (value === "error") return "error";
2006
+ if (value === "warning" || value === "warn") return "warn";
2007
+ return "info";
2008
+ }
2009
+ function runProseSlop(text, options = {}) {
2010
+ const findings = check(text, {
2011
+ format: options.format ?? "plain",
2012
+ lang: "en"
2013
+ });
2014
+ return findings.map((finding) => {
2015
+ const issue = {
2016
+ rule: `prose-${finding.ruleId}`,
2017
+ engine: "prose-slop",
2018
+ category: "ai",
2019
+ severity: toSeverity3(finding.severity),
2020
+ message: finding.message,
2021
+ line: finding.line,
2022
+ column: finding.column,
2023
+ excerpt: excerptAround(text, finding.index),
2024
+ match: finding.match
2025
+ };
2026
+ return issue;
2027
+ });
2028
+ }
1965
2029
 
1966
2030
  // node_modules/nlcst-to-string/lib/index.js
1967
2031
  var emptyNodes = [];
@@ -2923,10 +2987,10 @@ function anyFactory(tests) {
2923
2987
  return false;
2924
2988
  }
2925
2989
  }
2926
- function propertiesFactory(check) {
2990
+ function propertiesFactory(check2) {
2927
2991
  const checkAsRecord = (
2928
2992
  /** @type {Record<string, unknown>} */
2929
- check
2993
+ check2
2930
2994
  );
2931
2995
  return castFactory(all);
2932
2996
  function all(node) {
@@ -2936,21 +3000,21 @@ function propertiesFactory(check) {
2936
3000
  node
2937
3001
  );
2938
3002
  let key;
2939
- for (key in check) {
3003
+ for (key in check2) {
2940
3004
  if (nodeAsRecord[key] !== checkAsRecord[key]) return false;
2941
3005
  }
2942
3006
  return true;
2943
3007
  }
2944
3008
  }
2945
- function typeFactory(check) {
3009
+ function typeFactory(check2) {
2946
3010
  return castFactory(type);
2947
3011
  function type(node) {
2948
- return node && node.type === check;
3012
+ return node && node.type === check2;
2949
3013
  }
2950
3014
  }
2951
3015
  function castFactory(testFunction) {
2952
- return check;
2953
- function check(value, index2, parent) {
3016
+ return check2;
3017
+ function check2(value, index2, parent) {
2954
3018
  return Boolean(
2955
3019
  looksLikeANode(value) && testFunction.call(
2956
3020
  this,
@@ -2979,14 +3043,14 @@ var CONTINUE = true;
2979
3043
  var EXIT = false;
2980
3044
  var SKIP = "skip";
2981
3045
  function visitParents(tree, test, visitor, reverse) {
2982
- let check;
3046
+ let check2;
2983
3047
  if (typeof test === "function" && typeof visitor !== "function") {
2984
3048
  reverse = visitor;
2985
3049
  visitor = test;
2986
3050
  } else {
2987
- check = test;
3051
+ check2 = test;
2988
3052
  }
2989
- const is2 = convert(check);
3053
+ const is2 = convert(check2);
2990
3054
  const step = reverse ? -1 : 1;
2991
3055
  factory(tree, void 0, [])();
2992
3056
  function factory(node, index2, parents) {
@@ -9879,9 +9943,9 @@ var VFile = class {
9879
9943
  * @returns {undefined}
9880
9944
  * Nothing.
9881
9945
  */
9882
- set dirname(dirname3) {
9946
+ set dirname(dirname4) {
9883
9947
  assertPath2(this.basename, "dirname");
9884
- this.path = minpath.join(dirname3 || "", this.basename);
9948
+ this.path = minpath.join(dirname4 || "", this.basename);
9885
9949
  }
9886
9950
  /**
9887
9951
  * Get the extname (including dot) (example: `'.js'`).
@@ -10904,6 +10968,160 @@ function runRetext(text) {
10904
10968
  return issue;
10905
10969
  });
10906
10970
  }
10971
+ var WEIGHT_WORD = 0.5;
10972
+ var WEIGHT_PATTERN = 0.2;
10973
+ var WEIGHT_TRIGRAM = 0.12;
10974
+ var WEIGHT_TROPE = 0.18;
10975
+ var WORD_MULTIPLIER = 1e3;
10976
+ var TRIGRAM_MULTIPLIER = 1e3;
10977
+ 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;
10978
+ var NOT_BUT_PATTERN = /\bnot\b[\s\S]{0,60}?\bbut\b/gi;
10979
+ var require3 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
10980
+ var assetsDir = path.join(
10981
+ path.dirname(require3.resolve("slop-detector/package.json")),
10982
+ "dist",
10983
+ "assets"
10984
+ );
10985
+ var cachedAssets = null;
10986
+ function computeRange(values) {
10987
+ if (values.length === 0) return { min: 0, max: 1 };
10988
+ const min = Math.min(...values);
10989
+ const max = Math.max(...values);
10990
+ const range = max - min || 1;
10991
+ return { min: min - range * 0.1, max: max + range * 0.1 };
10992
+ }
10993
+ function loadAssets() {
10994
+ if (cachedAssets) return cachedAssets;
10995
+ const wordsRaw = JSON.parse(
10996
+ fs.readFileSync(path.join(assetsDir, "slop_list.json"), "utf8")
10997
+ );
10998
+ const trigramsRaw = JSON.parse(
10999
+ fs.readFileSync(path.join(assetsDir, "slop_list_trigrams.json"), "utf8")
11000
+ );
11001
+ const leaderboard = JSON.parse(
11002
+ fs.readFileSync(path.join(assetsDir, "leaderboard_results.json"), "utf8")
11003
+ );
11004
+ const words = /* @__PURE__ */ new Set();
11005
+ for (const item of wordsRaw) {
11006
+ if (Array.isArray(item) && item[0]) words.add(item[0].toLowerCase());
11007
+ }
11008
+ const trigrams = /* @__PURE__ */ new Set();
11009
+ for (const item of trigramsRaw) {
11010
+ if (Array.isArray(item) && item[0]) trigrams.add(item[0].toLowerCase());
11011
+ }
11012
+ const metrics = {
11013
+ slop_words: [],
11014
+ slop_trigrams: [],
11015
+ contrast: []
11016
+ };
11017
+ for (const res of leaderboard.results ?? []) {
11018
+ if (!res.metrics) continue;
11019
+ metrics.slop_words.push(res.metrics.slop_list_matches_per_1k_words || 0);
11020
+ metrics.slop_trigrams.push(
11021
+ res.metrics.slop_trigram_matches_per_1k_words || 0
11022
+ );
11023
+ metrics.contrast.push(res.metrics.not_x_but_y_per_1k_chars || 0);
11024
+ }
11025
+ cachedAssets = {
11026
+ words,
11027
+ trigrams,
11028
+ normalizationRanges: {
11029
+ slop_words: computeRange(metrics.slop_words),
11030
+ slop_trigrams: computeRange(metrics.slop_trigrams),
11031
+ contrast: computeRange(metrics.contrast)
11032
+ }
11033
+ };
11034
+ return cachedAssets;
11035
+ }
11036
+ function normalizeValue(value, range) {
11037
+ const normalized = (value - range.min) / (range.max - range.min);
11038
+ return Math.max(0, Math.min(1, normalized));
11039
+ }
11040
+ function toPlainText(text) {
11041
+ return text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`\n]+`/g, " ").replace(/<[^>]+>/g, " ").replace(/!\[[^\]]*]\([^)]*\)/g, " ").replace(/\[[^\]]*]\([^)]*\)/g, " ").replace(/^#{1,6}\s+/gm, "").replace(/[*_~>#-]+/g, " ");
11042
+ }
11043
+ function tokenize(text) {
11044
+ return text.toLowerCase().match(/[a-z]+(?:'[a-z]+)?/g) ?? [];
11045
+ }
11046
+ function collectMatches(pattern, text) {
11047
+ return [...text.matchAll(pattern)].map((match) => match[0] ?? "");
11048
+ }
11049
+ function runSlopDetector(text) {
11050
+ if (!text.trim()) return [];
11051
+ const assets = loadAssets();
11052
+ const plain = toPlainText(text);
11053
+ const tokens = tokenize(plain);
11054
+ const nTokens = tokens.length || 1;
11055
+ let wordHitCount = 0;
11056
+ const wordHitMap = /* @__PURE__ */ new Map();
11057
+ for (const token of tokens) {
11058
+ if (!assets.words.has(token)) continue;
11059
+ wordHitCount++;
11060
+ wordHitMap.set(token, (wordHitMap.get(token) || 0) + 1);
11061
+ }
11062
+ let trigramHitCount = 0;
11063
+ if (tokens.length >= 3) {
11064
+ for (let i = 0; i < tokens.length - 2; i++) {
11065
+ const tg = `${tokens[i]} ${tokens[i + 1]} ${tokens[i + 2]}`;
11066
+ if (assets.trigrams.has(tg)) trigramHitCount++;
11067
+ }
11068
+ }
11069
+ const contrastMatches = [
11070
+ .../* @__PURE__ */ new Set([
11071
+ ...collectMatches(CONTRAST_PATTERN, plain),
11072
+ ...collectMatches(NOT_BUT_PATTERN, plain)
11073
+ ])
11074
+ ];
11075
+ const emDashCount = (text.match(/—/g) ?? []).length;
11076
+ const tropeRate = emDashCount / Math.max(plain.length, 1) * 1e3;
11077
+ const wordScore = wordHitCount / nTokens * WORD_MULTIPLIER;
11078
+ const trigramScore = trigramHitCount / nTokens * TRIGRAM_MULTIPLIER;
11079
+ const contrastScore = contrastMatches.length / Math.max(plain.length, 1) * 1e3;
11080
+ 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;
11081
+ const slopScore = Math.round(rawSlopScore * 10) / 10;
11082
+ const issues = [];
11083
+ if (slopScore >= 40) {
11084
+ issues.push({
11085
+ rule: "eqbench-slop-score",
11086
+ engine: "slop-detector",
11087
+ category: "ai",
11088
+ severity: slopScore >= 60 ? "warn" : "info",
11089
+ message: `EQBench SLOP score ${slopScore}/100 (higher = more AI-like).`,
11090
+ suggestion: slopScore >= 60 ? "Rewrite stock phrasing and contrast constructions." : "Review flagged vocabulary and contrast patterns."
11091
+ });
11092
+ }
11093
+ for (const [word2, count] of [...wordHitMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8)) {
11094
+ issues.push({
11095
+ rule: `eqbench-word-${word2.replace(/\s+/g, "-")}`,
11096
+ engine: "slop-detector",
11097
+ category: "ai",
11098
+ severity: "info",
11099
+ message: `EQBench slop vocabulary: \u201C${word2}\u201D (${count}\xD7).`,
11100
+ match: word2
11101
+ });
11102
+ }
11103
+ for (const match of contrastMatches.slice(0, 5)) {
11104
+ issues.push({
11105
+ rule: "eqbench-contrast",
11106
+ engine: "slop-detector",
11107
+ category: "ai",
11108
+ severity: "info",
11109
+ message: `EQBench contrast pattern: \u201C${match.trim()}\u201D`,
11110
+ match
11111
+ });
11112
+ }
11113
+ if (emDashCount >= 3) {
11114
+ issues.push({
11115
+ rule: "eqbench-trope-em-dash",
11116
+ engine: "slop-detector",
11117
+ category: "ai",
11118
+ severity: "info",
11119
+ message: `EQBench typography trope: ${emDashCount} em dash(es).`,
11120
+ match: "\u2014"
11121
+ });
11122
+ }
11123
+ return issues;
11124
+ }
10907
11125
 
10908
11126
  // src/engines/tag.ts
10909
11127
  function tagEngine(issues, engine) {
@@ -11275,7 +11493,7 @@ function countListItems(input) {
11275
11493
  function countBlockquotes(input) {
11276
11494
  return getLineMetadata(input).blockquote_count;
11277
11495
  }
11278
- function tokenize(text) {
11496
+ function tokenize2(text) {
11279
11497
  const blocks = splitParagraphBlocks(text);
11280
11498
  const paragraphs2 = blocks.map((block) => block.text);
11281
11499
  const sentences2 = splitSentencesFromBlocks(blocks);
@@ -11297,7 +11515,7 @@ function tokenize(text) {
11297
11515
  }
11298
11516
  };
11299
11517
  }
11300
- var tokenizeProse = tokenize;
11518
+ var tokenizeProse = tokenize2;
11301
11519
 
11302
11520
  // node_modules/@veldica/prose-analyzer/dist/utils.js
11303
11521
  function round(value, decimals = 4) {
@@ -12041,7 +12259,7 @@ function inventoryMarkers(text, options = {}) {
12041
12259
  }
12042
12260
 
12043
12261
  // src/engines/veldica.ts
12044
- function toSeverity2(value) {
12262
+ function toSeverity4(value) {
12045
12263
  if (value === "high") return "warn";
12046
12264
  return "info";
12047
12265
  }
@@ -12056,7 +12274,7 @@ function runVeldica(text) {
12056
12274
  rule: `veldica-${match.category || "marker"}`,
12057
12275
  engine: "veldica",
12058
12276
  category: "ai",
12059
- severity: toSeverity2(match.severity),
12277
+ severity: toSeverity4(match.severity),
12060
12278
  message: `AI-style marker (${match.category}): \u201C${match.matched_text}\u201D`,
12061
12279
  excerpt: excerptAround(text, offset),
12062
12280
  match: match.matched_text
@@ -12069,8 +12287,8 @@ function runVeldica(text) {
12069
12287
  return issue;
12070
12288
  });
12071
12289
  }
12072
- var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
12073
- var writeGood = require2("write-good");
12290
+ var require4 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
12291
+ var writeGood = require4("write-good");
12074
12292
  function runWriteGood(text) {
12075
12293
  const suggestions = writeGood(text, {
12076
12294
  so: false,
@@ -13170,7 +13388,7 @@ function computeStats(body, titleLength, descriptionLength, issues) {
13170
13388
  descriptionLength
13171
13389
  };
13172
13390
  }
13173
- function collectIssues(body, frontmatter, format, config) {
13391
+ function collectIssues(body, frontmatter, format, config, source2) {
13174
13392
  const ignoreRules = new Set(config.ignoreRules);
13175
13393
  const issues = [];
13176
13394
  const enginesRun = [];
@@ -13243,6 +13461,18 @@ function collectIssues(body, frontmatter, format, config) {
13243
13461
  enginesRun.push("clean-writing");
13244
13462
  issues.push(...runCleanWriting(body));
13245
13463
  }
13464
+ if (config.engines.aiSlopLinter && config.checks.ai) {
13465
+ enginesRun.push("ai-slop-linter");
13466
+ issues.push(...runAiSlopLinter(body, { source: source2 }));
13467
+ }
13468
+ if (config.engines.slopDetector && config.checks.ai) {
13469
+ enginesRun.push("slop-detector");
13470
+ issues.push(...runSlopDetector(body));
13471
+ }
13472
+ if (config.engines.proseSlop && config.checks.ai) {
13473
+ enginesRun.push("prose-slop");
13474
+ issues.push(...runProseSlop(body, { format }));
13475
+ }
13246
13476
  return {
13247
13477
  issues: issues.filter((issue) => !ignoreRules.has(issue.rule)),
13248
13478
  enginesRun: uniqueEngines(enginesRun)
@@ -13258,7 +13488,8 @@ function checkText(text, options = {}) {
13258
13488
  body,
13259
13489
  parsed.frontmatter,
13260
13490
  format,
13261
- config
13491
+ config,
13492
+ source2
13262
13493
  );
13263
13494
  const title = String(parsed.frontmatter.title ?? pathBasename(source2));
13264
13495
  const report = {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CheckOptions, a as CheckReport, U as UserConfig, R as ResolvedConfig, b as ReplacementStats, S as SanitizeOptions, c as Severity, I as Issue } from './types-BC0TjlU0.cjs';
2
- export { d as Category, e as CheckName, f as CheckSummary, g as ChecksConfig, E as EngineName, h as EnginesConfig, M as MarkdownConfig, P as PatternRule, i as PresetName, j as ReadabilityThresholds, k as SeoThresholds, T as TextFormat, l as TextStats } from './types-BC0TjlU0.cjs';
1
+ import { C as CheckOptions, a as CheckReport, U as UserConfig, R as ResolvedConfig, b as ReplacementStats, S as SanitizeOptions, c as Severity, I as Issue } from './types-B6cMXIul.cjs';
2
+ export { d as Category, e as CheckName, f as CheckSummary, g as ChecksConfig, E as EngineName, h as EnginesConfig, M as MarkdownConfig, P as PatternRule, i as PresetName, j as ReadabilityThresholds, k as SeoThresholds, T as TextFormat, l as TextStats } from './types-B6cMXIul.cjs';
3
3
 
4
4
  declare function checkText(text: string, options?: CheckOptions): CheckReport;
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CheckOptions, a as CheckReport, U as UserConfig, R as ResolvedConfig, b as ReplacementStats, S as SanitizeOptions, c as Severity, I as Issue } from './types-BC0TjlU0.js';
2
- export { d as Category, e as CheckName, f as CheckSummary, g as ChecksConfig, E as EngineName, h as EnginesConfig, M as MarkdownConfig, P as PatternRule, i as PresetName, j as ReadabilityThresholds, k as SeoThresholds, T as TextFormat, l as TextStats } from './types-BC0TjlU0.js';
1
+ import { C as CheckOptions, a as CheckReport, U as UserConfig, R as ResolvedConfig, b as ReplacementStats, S as SanitizeOptions, c as Severity, I as Issue } from './types-B6cMXIul.js';
2
+ export { d as Category, e as CheckName, f as CheckSummary, g as ChecksConfig, E as EngineName, h as EnginesConfig, M as MarkdownConfig, P as PatternRule, i as PresetName, j as ReadabilityThresholds, k as SeoThresholds, T as TextFormat, l as TextStats } from './types-B6cMXIul.js';
3
3
 
4
4
  declare function checkText(text: string, options?: CheckOptions): CheckReport;
5
5