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/README.md +21 -11
- package/SOURCES.md +8 -3
- package/dist/cli.js +312 -38
- package/dist/index.cjs +280 -25
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +280 -25
- package/dist/node.cjs +286 -31
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +288 -33
- package/dist/{types-BlvvRFG5.d.cts → types-B6cMXIul.d.cts} +7 -1
- package/dist/{types-BlvvRFG5.d.ts → types-B6cMXIul.d.ts} +7 -1
- package/package.json +4 -1
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:
|
|
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
|
|
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:
|
|
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(
|
|
3077
|
+
function propertiesFactory(check2) {
|
|
2998
3078
|
const checkAsRecord = (
|
|
2999
3079
|
/** @type {Record<string, unknown>} */
|
|
3000
|
-
|
|
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
|
|
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(
|
|
3096
|
+
function typeFactory(check2) {
|
|
3017
3097
|
return castFactory(type);
|
|
3018
3098
|
function type(node) {
|
|
3019
|
-
return node && node.type ===
|
|
3099
|
+
return node && node.type === check2;
|
|
3020
3100
|
}
|
|
3021
3101
|
}
|
|
3022
3102
|
function castFactory(testFunction) {
|
|
3023
|
-
return
|
|
3024
|
-
function
|
|
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
|
|
3133
|
+
let check2;
|
|
3054
3134
|
if (typeof test === "function" && typeof visitor !== "function") {
|
|
3055
3135
|
reverse = visitor;
|
|
3056
3136
|
visitor = test;
|
|
3057
3137
|
} else {
|
|
3058
|
-
|
|
3138
|
+
check2 = test;
|
|
3059
3139
|
}
|
|
3060
|
-
const is2 = convert(
|
|
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(
|
|
10033
|
+
set dirname(dirname4) {
|
|
9954
10034
|
assertPath2(this.basename, "dirname");
|
|
9955
|
-
this.path = minpath.join(
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
12144
|
-
var writeGood =
|
|
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
|
|
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 =
|
|
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(
|
|
13390
|
-
if (
|
|
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
|
|
13671
|
+
const check2 = alert.Check ?? "vale";
|
|
13417
13672
|
const issue = {
|
|
13418
|
-
rule:
|
|
13673
|
+
rule: check2,
|
|
13419
13674
|
engine: "vale",
|
|
13420
|
-
category: valeCategory(
|
|
13675
|
+
category: valeCategory(check2),
|
|
13421
13676
|
severity: valeSeverity(alert.Severity),
|
|
13422
|
-
message: alert.Message ??
|
|
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.
|
|
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"
|