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/README.md +7 -1
- package/SOURCES.md +8 -3
- package/dist/cli.js +279 -37
- package/dist/index.cjs +255 -24
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +255 -24
- package/dist/node.cjs +261 -30
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +263 -32
- package/dist/{types-BC0TjlU0.d.cts → types-B6cMXIul.d.cts} +4 -1
- package/dist/{types-BC0TjlU0.d.ts → types-B6cMXIul.d.ts} +4 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ Node.js 20+ is required. The package ships dual **ESM and CommonJS** builds with
|
|
|
23
23
|
| **is-this-ai-slop** | Clichés, buzzwords, em-dash spam, antithesis, sycophancy |
|
|
24
24
|
| **veldica** | Stock LLM transitions and AI-style markers |
|
|
25
25
|
| **clean-writing** | Banned constructions, marketing adjectives, filler, intensifiers |
|
|
26
|
+
| **ai-slop-linter** | Wikipedia _Signs of AI writing_ rules with line/column findings |
|
|
27
|
+
| **slop-detector** | EQBench SLOP score (vocabulary, contrast patterns, trigrams, tropes) |
|
|
28
|
+
| **prose-slop** | Vocabulary, punctuation, and document-cadence tells (English) |
|
|
26
29
|
| **vale** | Vendored styles: write-good, Google, AiTextCheck, ai-tells, signs-of-ai-writing |
|
|
27
30
|
|
|
28
31
|
Safe auto-fixes can strip filler openers and collapse extra whitespace without rewriting code fences.
|
|
@@ -146,6 +149,9 @@ Exit code `1` when issues meet the fail threshold.
|
|
|
146
149
|
"isThisAiSlop": true,
|
|
147
150
|
"veldica": true,
|
|
148
151
|
"cleanWriting": true,
|
|
152
|
+
"aiSlopLinter": true,
|
|
153
|
+
"slopDetector": true,
|
|
154
|
+
"proseSlop": true,
|
|
149
155
|
"vale": true
|
|
150
156
|
}
|
|
151
157
|
}
|
|
@@ -155,7 +161,7 @@ Presets:
|
|
|
155
161
|
|
|
156
162
|
- `default`: every check and every engine
|
|
157
163
|
- `strict`: default plus extra slop-gate punctuation
|
|
158
|
-
- `ai`: AI tells + typography (polish, slop-gate, is-this-ai-slop, Veldica, clean-writing, Vale)
|
|
164
|
+
- `ai`: AI tells + typography (polish, slop-gate, is-this-ai-slop, Veldica, clean-writing, ai-slop-linter, slop-detector, prose-slop, Vale)
|
|
159
165
|
- `grammar`: grammar + readability (write-good, retext, clean-writing, Vale)
|
|
160
166
|
|
|
161
167
|
## Scripts
|
package/SOURCES.md
CHANGED
|
@@ -18,17 +18,22 @@ API key. Vale (`@vvago/vale`) is the prose engine the user referred to as Vale
|
|
|
18
18
|
| is-this-ai-slop | `is-this-ai-slop` (`analyze()`) |
|
|
19
19
|
| Veldica AI markers | `@veldica/prose-linter` (`inventoryMarkers()`) |
|
|
20
20
|
| Clean Writing / STE subset | `clean-writing-lint` (`lint()`), with contraction and 20-word sentence rules off |
|
|
21
|
+
| ai-slop-linter | `ai-slop-linter` (`lintText()`) — Wikipedia Signs of AI writing |
|
|
22
|
+
| slop-detector (EQBench) | `slop-detector` scoring assets + sync adapter (`runSlopDetector`) |
|
|
23
|
+
| prose-slop | `prose-slop` (`check()`) — EN vocabulary / cadence |
|
|
21
24
|
|
|
22
25
|
Skipped after research (not a fit for Node 20 in-process checks):
|
|
23
26
|
|
|
24
27
|
- `slopsift` / WritingLint — requires Node 24+
|
|
25
|
-
- `slopless` — textlint-only, async
|
|
28
|
+
- `slopless` — textlint-only, async, Node ≥22.13
|
|
26
29
|
- `wsc-lint` / `tacheles` — CLI-only, no stable in-process API
|
|
30
|
+
- `smellcheck` (npm) — agent skill / Markdown rules only; does not scan text
|
|
31
|
+
- `llm-slop-detector` — VS Code–centric, awkward as a library dependency
|
|
27
32
|
- Hosted classifiers (GPTZero, Originality, etc.) — need secrets / upload text
|
|
28
33
|
|
|
29
34
|
Default `checkFiles` order: polish + slop-gate JSON + write-good + retext +
|
|
30
|
-
is-this-ai-slop + Veldica + clean-writing
|
|
31
|
-
report.
|
|
35
|
+
is-this-ai-slop + Veldica + clean-writing + ai-slop-linter + slop-detector +
|
|
36
|
+
prose-slop, then Vale JSON merged onto the same report.
|
|
32
37
|
|
|
33
38
|
Licenses for this package and the vendored Vale styles are in [LICENSE](./LICENSE)
|
|
34
39
|
and [NOTICE](./NOTICE).
|
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) {
|
|
@@ -941,6 +956,30 @@ function isStrictPreset(config) {
|
|
|
941
956
|
// src/node/check-files.ts
|
|
942
957
|
import fs3 from "fs";
|
|
943
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
|
+
|
|
944
983
|
// node_modules/clean-writing-lint/src/mask.js
|
|
945
984
|
function blank(text) {
|
|
946
985
|
return text.replace(/[^\n]/g, " ");
|
|
@@ -1957,7 +1996,7 @@ var AI_RULES = /* @__PURE__ */ new Set([
|
|
|
1957
1996
|
"banned-construction",
|
|
1958
1997
|
"em-dash"
|
|
1959
1998
|
]);
|
|
1960
|
-
function
|
|
1999
|
+
function toSeverity2(value) {
|
|
1961
2000
|
if (value === "error") return "error";
|
|
1962
2001
|
if (value === "warn") return "warn";
|
|
1963
2002
|
return "info";
|
|
@@ -1977,7 +2016,7 @@ function runCleanWriting(text) {
|
|
|
1977
2016
|
rule: `cws-${violation.rule}`,
|
|
1978
2017
|
engine: "clean-writing",
|
|
1979
2018
|
category: AI_RULES.has(violation.rule) ? "ai" : "grammar",
|
|
1980
|
-
severity:
|
|
2019
|
+
severity: toSeverity2(violation.severity),
|
|
1981
2020
|
message: violation.message,
|
|
1982
2021
|
line: violation.line,
|
|
1983
2022
|
column: violation.column,
|
|
@@ -2008,6 +2047,36 @@ function runIsThisAiSlop(text) {
|
|
|
2008
2047
|
});
|
|
2009
2048
|
}
|
|
2010
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
|
+
|
|
2011
2080
|
// node_modules/nlcst-to-string/lib/index.js
|
|
2012
2081
|
var emptyNodes = [];
|
|
2013
2082
|
function toString(value) {
|
|
@@ -2968,10 +3037,10 @@ function anyFactory(tests) {
|
|
|
2968
3037
|
return false;
|
|
2969
3038
|
}
|
|
2970
3039
|
}
|
|
2971
|
-
function propertiesFactory(
|
|
3040
|
+
function propertiesFactory(check2) {
|
|
2972
3041
|
const checkAsRecord = (
|
|
2973
3042
|
/** @type {Record<string, unknown>} */
|
|
2974
|
-
|
|
3043
|
+
check2
|
|
2975
3044
|
);
|
|
2976
3045
|
return castFactory(all);
|
|
2977
3046
|
function all(node) {
|
|
@@ -2981,21 +3050,21 @@ function propertiesFactory(check) {
|
|
|
2981
3050
|
node
|
|
2982
3051
|
);
|
|
2983
3052
|
let key;
|
|
2984
|
-
for (key in
|
|
3053
|
+
for (key in check2) {
|
|
2985
3054
|
if (nodeAsRecord[key] !== checkAsRecord[key]) return false;
|
|
2986
3055
|
}
|
|
2987
3056
|
return true;
|
|
2988
3057
|
}
|
|
2989
3058
|
}
|
|
2990
|
-
function typeFactory(
|
|
3059
|
+
function typeFactory(check2) {
|
|
2991
3060
|
return castFactory(type);
|
|
2992
3061
|
function type(node) {
|
|
2993
|
-
return node && node.type ===
|
|
3062
|
+
return node && node.type === check2;
|
|
2994
3063
|
}
|
|
2995
3064
|
}
|
|
2996
3065
|
function castFactory(testFunction) {
|
|
2997
|
-
return
|
|
2998
|
-
function
|
|
3066
|
+
return check2;
|
|
3067
|
+
function check2(value, index2, parent) {
|
|
2999
3068
|
return Boolean(
|
|
3000
3069
|
looksLikeANode(value) && testFunction.call(
|
|
3001
3070
|
this,
|
|
@@ -3024,14 +3093,14 @@ var CONTINUE = true;
|
|
|
3024
3093
|
var EXIT = false;
|
|
3025
3094
|
var SKIP = "skip";
|
|
3026
3095
|
function visitParents(tree, test, visitor, reverse) {
|
|
3027
|
-
let
|
|
3096
|
+
let check2;
|
|
3028
3097
|
if (typeof test === "function" && typeof visitor !== "function") {
|
|
3029
3098
|
reverse = visitor;
|
|
3030
3099
|
visitor = test;
|
|
3031
3100
|
} else {
|
|
3032
|
-
|
|
3101
|
+
check2 = test;
|
|
3033
3102
|
}
|
|
3034
|
-
const is2 = convert(
|
|
3103
|
+
const is2 = convert(check2);
|
|
3035
3104
|
const step = reverse ? -1 : 1;
|
|
3036
3105
|
factory(tree, void 0, [])();
|
|
3037
3106
|
function factory(node, index2, parents) {
|
|
@@ -9676,9 +9745,9 @@ var VFile = class {
|
|
|
9676
9745
|
* @returns {undefined}
|
|
9677
9746
|
* Nothing.
|
|
9678
9747
|
*/
|
|
9679
|
-
set dirname(
|
|
9748
|
+
set dirname(dirname3) {
|
|
9680
9749
|
assertPath(this.basename, "dirname");
|
|
9681
|
-
this.path = default2.join(
|
|
9750
|
+
this.path = default2.join(dirname3 || "", this.basename);
|
|
9682
9751
|
}
|
|
9683
9752
|
/**
|
|
9684
9753
|
* Get the extname (including dot) (example: `'.js'`).
|
|
@@ -10712,6 +10781,165 @@ function runRetext(text) {
|
|
|
10712
10781
|
});
|
|
10713
10782
|
}
|
|
10714
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
|
+
|
|
10715
10943
|
// src/engines/tag.ts
|
|
10716
10944
|
function tagEngine(issues, engine) {
|
|
10717
10945
|
return issues.map((issue) => ({ ...issue, engine }));
|
|
@@ -11082,7 +11310,7 @@ function countListItems(input) {
|
|
|
11082
11310
|
function countBlockquotes(input) {
|
|
11083
11311
|
return getLineMetadata(input).blockquote_count;
|
|
11084
11312
|
}
|
|
11085
|
-
function
|
|
11313
|
+
function tokenize2(text) {
|
|
11086
11314
|
const blocks = splitParagraphBlocks(text);
|
|
11087
11315
|
const paragraphs2 = blocks.map((block) => block.text);
|
|
11088
11316
|
const sentences2 = splitSentencesFromBlocks(blocks);
|
|
@@ -11104,7 +11332,7 @@ function tokenize(text) {
|
|
|
11104
11332
|
}
|
|
11105
11333
|
};
|
|
11106
11334
|
}
|
|
11107
|
-
var tokenizeProse =
|
|
11335
|
+
var tokenizeProse = tokenize2;
|
|
11108
11336
|
|
|
11109
11337
|
// node_modules/@veldica/prose-analyzer/dist/utils.js
|
|
11110
11338
|
function round(value, decimals = 4) {
|
|
@@ -11848,7 +12076,7 @@ function inventoryMarkers(text, options = {}) {
|
|
|
11848
12076
|
}
|
|
11849
12077
|
|
|
11850
12078
|
// src/engines/veldica.ts
|
|
11851
|
-
function
|
|
12079
|
+
function toSeverity4(value) {
|
|
11852
12080
|
if (value === "high") return "warn";
|
|
11853
12081
|
return "info";
|
|
11854
12082
|
}
|
|
@@ -11863,7 +12091,7 @@ function runVeldica(text) {
|
|
|
11863
12091
|
rule: `veldica-${match.category || "marker"}`,
|
|
11864
12092
|
engine: "veldica",
|
|
11865
12093
|
category: "ai",
|
|
11866
|
-
severity:
|
|
12094
|
+
severity: toSeverity4(match.severity),
|
|
11867
12095
|
message: `AI-style marker (${match.category}): \u201C${match.matched_text}\u201D`,
|
|
11868
12096
|
excerpt: excerptAround(text, offset),
|
|
11869
12097
|
match: match.matched_text
|
|
@@ -11878,9 +12106,9 @@ function runVeldica(text) {
|
|
|
11878
12106
|
}
|
|
11879
12107
|
|
|
11880
12108
|
// src/engines/write-good.ts
|
|
11881
|
-
import { createRequire } from "module";
|
|
11882
|
-
var
|
|
11883
|
-
var writeGood =
|
|
12109
|
+
import { createRequire as createRequire3 } from "module";
|
|
12110
|
+
var require4 = createRequire3(import.meta.url);
|
|
12111
|
+
var writeGood = require4("write-good");
|
|
11884
12112
|
function runWriteGood(text) {
|
|
11885
12113
|
const suggestions = writeGood(text, {
|
|
11886
12114
|
so: false,
|
|
@@ -12976,7 +13204,7 @@ function computeStats(body, titleLength, descriptionLength, issues) {
|
|
|
12976
13204
|
descriptionLength
|
|
12977
13205
|
};
|
|
12978
13206
|
}
|
|
12979
|
-
function collectIssues(body, frontmatter, format, config) {
|
|
13207
|
+
function collectIssues(body, frontmatter, format, config, source2) {
|
|
12980
13208
|
const ignoreRules = new Set(config.ignoreRules);
|
|
12981
13209
|
const issues = [];
|
|
12982
13210
|
const enginesRun = [];
|
|
@@ -13049,6 +13277,18 @@ function collectIssues(body, frontmatter, format, config) {
|
|
|
13049
13277
|
enginesRun.push("clean-writing");
|
|
13050
13278
|
issues.push(...runCleanWriting(body));
|
|
13051
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
|
+
}
|
|
13052
13292
|
return {
|
|
13053
13293
|
issues: issues.filter((issue) => !ignoreRules.has(issue.rule)),
|
|
13054
13294
|
enginesRun: uniqueEngines(enginesRun)
|
|
@@ -13064,7 +13304,8 @@ function checkText(text, options = {}) {
|
|
|
13064
13304
|
body,
|
|
13065
13305
|
parsed.frontmatter,
|
|
13066
13306
|
format,
|
|
13067
|
-
config
|
|
13307
|
+
config,
|
|
13308
|
+
source2
|
|
13068
13309
|
);
|
|
13069
13310
|
const title = String(parsed.frontmatter.title ?? pathBasename(source2));
|
|
13070
13311
|
const report = {
|
|
@@ -13097,11 +13338,11 @@ import path2 from "path";
|
|
|
13097
13338
|
|
|
13098
13339
|
// src/node/vale.ts
|
|
13099
13340
|
import { spawnSync } from "child_process";
|
|
13100
|
-
import { createRequire as
|
|
13341
|
+
import { createRequire as createRequire4 } from "module";
|
|
13101
13342
|
import fs from "fs";
|
|
13102
13343
|
import path from "path";
|
|
13103
13344
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13104
|
-
var
|
|
13345
|
+
var require5 = createRequire4(import.meta.url);
|
|
13105
13346
|
function packageRoot() {
|
|
13106
13347
|
let current = path.dirname(fileURLToPath2(import.meta.url));
|
|
13107
13348
|
while (true) {
|
|
@@ -13140,7 +13381,7 @@ function findValeBinary(cwd = process.cwd()) {
|
|
|
13140
13381
|
const fromPackage = firstExisting(valeNativeCandidates(packageRoot()));
|
|
13141
13382
|
if (fromPackage) return fromPackage;
|
|
13142
13383
|
try {
|
|
13143
|
-
const pkg =
|
|
13384
|
+
const pkg = require5.resolve("@vvago/vale/package.json");
|
|
13144
13385
|
const dir = path.dirname(pkg);
|
|
13145
13386
|
const resolved = firstExisting([
|
|
13146
13387
|
path.join(
|
|
@@ -13197,8 +13438,8 @@ function valeSeverity(value) {
|
|
|
13197
13438
|
if (value === "suggestion") return "info";
|
|
13198
13439
|
return "warn";
|
|
13199
13440
|
}
|
|
13200
|
-
function valeCategory(
|
|
13201
|
-
if (
|
|
13441
|
+
function valeCategory(check2) {
|
|
13442
|
+
if (check2.startsWith("write-good.") || check2.startsWith("Google.")) {
|
|
13202
13443
|
return "grammar";
|
|
13203
13444
|
}
|
|
13204
13445
|
return "ai";
|
|
@@ -13224,13 +13465,13 @@ function collectValeIssues(files, options = {}) {
|
|
|
13224
13465
|
const issues = [];
|
|
13225
13466
|
for (const raw of alerts) {
|
|
13226
13467
|
const alert = raw;
|
|
13227
|
-
const
|
|
13468
|
+
const check2 = alert.Check ?? "vale";
|
|
13228
13469
|
const issue = {
|
|
13229
|
-
rule:
|
|
13470
|
+
rule: check2,
|
|
13230
13471
|
engine: "vale",
|
|
13231
|
-
category: valeCategory(
|
|
13472
|
+
category: valeCategory(check2),
|
|
13232
13473
|
severity: valeSeverity(alert.Severity),
|
|
13233
|
-
message: alert.Message ??
|
|
13474
|
+
message: alert.Message ?? check2
|
|
13234
13475
|
};
|
|
13235
13476
|
if (alert.Line) issue.line = alert.Line;
|
|
13236
13477
|
if (alert.Span?.[0]) issue.column = alert.Span[0];
|
|
@@ -13467,8 +13708,9 @@ Options:
|
|
|
13467
13708
|
-- Extra flags after -- are passed to Vale
|
|
13468
13709
|
|
|
13469
13710
|
By default every local engine runs: polish, slop-gate, write-good, retext,
|
|
13470
|
-
is-this-ai-slop, Veldica, clean-writing,
|
|
13471
|
-
|
|
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.
|
|
13472
13714
|
|
|
13473
13715
|
Examples:
|
|
13474
13716
|
ai-text-check content
|
|
@@ -13634,9 +13876,9 @@ function toUserConfig(options) {
|
|
|
13634
13876
|
// src/cli/init.ts
|
|
13635
13877
|
import fs5 from "fs";
|
|
13636
13878
|
import path5 from "path";
|
|
13637
|
-
import { createRequire as
|
|
13879
|
+
import { createRequire as createRequire5 } from "module";
|
|
13638
13880
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13639
|
-
var
|
|
13881
|
+
var require6 = createRequire5(import.meta.url);
|
|
13640
13882
|
function packageVersion() {
|
|
13641
13883
|
let current = path5.dirname(fileURLToPath3(import.meta.url));
|
|
13642
13884
|
while (true) {
|
|
@@ -13649,7 +13891,7 @@ function packageVersion() {
|
|
|
13649
13891
|
if (parent === current) break;
|
|
13650
13892
|
current = parent;
|
|
13651
13893
|
}
|
|
13652
|
-
return
|
|
13894
|
+
return require6("../../package.json").version;
|
|
13653
13895
|
}
|
|
13654
13896
|
function findPackageRoot() {
|
|
13655
13897
|
let current = path5.dirname(fileURLToPath3(import.meta.url));
|