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/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:
|
|
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
|
|
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:
|
|
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(
|
|
3040
|
+
function propertiesFactory(check2) {
|
|
2956
3041
|
const checkAsRecord = (
|
|
2957
3042
|
/** @type {Record<string, unknown>} */
|
|
2958
|
-
|
|
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
|
|
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(
|
|
3059
|
+
function typeFactory(check2) {
|
|
2975
3060
|
return castFactory(type);
|
|
2976
3061
|
function type(node) {
|
|
2977
|
-
return node && node.type ===
|
|
3062
|
+
return node && node.type === check2;
|
|
2978
3063
|
}
|
|
2979
3064
|
}
|
|
2980
3065
|
function castFactory(testFunction) {
|
|
2981
|
-
return
|
|
2982
|
-
function
|
|
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
|
|
3096
|
+
let check2;
|
|
3012
3097
|
if (typeof test === "function" && typeof visitor !== "function") {
|
|
3013
3098
|
reverse = visitor;
|
|
3014
3099
|
visitor = test;
|
|
3015
3100
|
} else {
|
|
3016
|
-
|
|
3101
|
+
check2 = test;
|
|
3017
3102
|
}
|
|
3018
|
-
const is2 = convert(
|
|
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(
|
|
9748
|
+
set dirname(dirname3) {
|
|
9664
9749
|
assertPath(this.basename, "dirname");
|
|
9665
|
-
this.path = default2.join(
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
11867
|
-
var writeGood =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
13177
|
-
if (
|
|
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
|
|
13468
|
+
const check2 = alert.Check ?? "vale";
|
|
13204
13469
|
const issue = {
|
|
13205
|
-
rule:
|
|
13470
|
+
rule: check2,
|
|
13206
13471
|
engine: "vale",
|
|
13207
|
-
category: valeCategory(
|
|
13472
|
+
category: valeCategory(check2),
|
|
13208
13473
|
severity: valeSeverity(alert.Severity),
|
|
13209
|
-
message: alert.Message ??
|
|
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,
|
|
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
|
|
13879
|
+
import { createRequire as createRequire5 } from "module";
|
|
13607
13880
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13608
|
-
var
|
|
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
|
|
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) {
|