@contentrain/mcp 1.1.2 → 1.2.1
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 +29 -20
- package/dist/{apply-manager-Cf5OWOJ7.mjs → apply-manager-BkPMfpsN.mjs} +8 -3
- package/dist/apply-manager-BkPMfpsN.mjs.map +1 -0
- package/dist/{astro-parser-RofKmKQi.mjs → astro-parser-BJ-54JGe.mjs} +3 -3
- package/dist/{astro-parser-RofKmKQi.mjs.map → astro-parser-BJ-54JGe.mjs.map} +1 -1
- package/dist/core/apply-manager.d.mts.map +1 -1
- package/dist/core/apply-manager.mjs +2 -2
- package/dist/core/scanner.d.mts +3 -9
- package/dist/core/scanner.d.mts.map +1 -1
- package/dist/core/scanner.mjs +3 -3
- package/dist/git/transaction.d.mts +6 -1
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +2 -2
- package/dist/index.mjs +5 -5
- package/dist/{scanner-DPrbU-oK.mjs → scanner-DUoXnmtI.mjs} +291 -306
- package/dist/scanner-DUoXnmtI.mjs.map +1 -0
- package/dist/{server-CjK63H6X.mjs → server-Dtq8vqbK.mjs} +172 -20
- package/dist/server-Dtq8vqbK.mjs.map +1 -0
- package/dist/server.mjs +5 -5
- package/dist/{svelte-parser-COTNG92w.mjs → svelte-parser-1bBcMx3T.mjs} +16 -6
- package/dist/{svelte-parser-COTNG92w.mjs.map → svelte-parser-1bBcMx3T.mjs.map} +1 -1
- package/dist/{transaction-B0V0_SE2.mjs → transaction-C_Pvux78.mjs} +102 -2
- package/dist/{transaction-B0V0_SE2.mjs.map → transaction-C_Pvux78.mjs.map} +1 -1
- package/dist/tsx-parser-BFLv292Y.mjs +2 -0
- package/dist/{tsx-parser-CYVyBWjn.mjs → tsx-parser-BWiMGAIR.mjs} +3 -2
- package/dist/tsx-parser-BWiMGAIR.mjs.map +1 -0
- package/dist/{vue-parser-CjS4YAoQ.mjs → vue-parser-DTPTN3Yb.mjs} +16 -5
- package/dist/{vue-parser-CjS4YAoQ.mjs.map → vue-parser-DTPTN3Yb.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/apply-manager-Cf5OWOJ7.mjs.map +0 -1
- package/dist/scanner-DPrbU-oK.mjs.map +0 -1
- package/dist/server-CjK63H6X.mjs.map +0 -1
- package/dist/tsx-parser-Bf8Firrq.mjs +0 -2
- package/dist/tsx-parser-CYVyBWjn.mjs.map +0 -1
|
@@ -1,73 +1,36 @@
|
|
|
1
1
|
import { o as readText } from "./fs-BnIQ4dKj.mjs";
|
|
2
2
|
import { i as autoDetectSourceDirs, o as discoverFiles } from "./scan-config-QgnJ1-52.mjs";
|
|
3
|
-
import { t as parseTsx } from "./tsx-parser-
|
|
3
|
+
import { t as parseTsx } from "./tsx-parser-BWiMGAIR.mjs";
|
|
4
4
|
import { extname, join } from "node:path";
|
|
5
5
|
//#region src/core/ast-scanner/pre-filter.ts
|
|
6
|
-
const PURE_NUMBER_RE =
|
|
6
|
+
const PURE_NUMBER_RE = /^-?\d+(\.\d+)?$/;
|
|
7
7
|
const HEX_COLOR_RE = /^#[0-9a-f]{3,8}$/i;
|
|
8
|
-
const FILE_EXT_RE
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
const FILE_EXT_RE = /\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i;
|
|
9
|
+
const SVG_PATH_DATA_RE = /^[Mm][\d\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/;
|
|
10
|
+
const SVG_VIEWBOX_RE = /^\d+(\.\d+)?\s+\d+(\.\d+)?\s+\d+(\.\d+)?\s+\d+(\.\d+)?$/;
|
|
11
|
+
const I18N_KEY_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
12
|
+
const TECHNICAL_IDENTIFIER_RE = /^[_a-z][a-z0-9_-]*$/;
|
|
13
|
+
const ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/;
|
|
14
|
+
const PLACEHOLDER_RE = /^\{\d+\}$|^\.{2,}$/;
|
|
15
|
+
const CAMEL_CASE_RE = /^[a-z]+[A-Z]/;
|
|
16
|
+
const LOCALE_CODE_RE = /^[a-z]{2}[-_][A-Z]{2}$/;
|
|
17
|
+
const DIMENSION_RE = /^\d+[x×]\d+$/;
|
|
18
|
+
const REPEAT_CHAR_RE = /^(.)\1{3,}$/;
|
|
19
|
+
const MIME_TYPE_RE = /^(application|text|image|audio|video|multipart|font)\/[\w.+-]+$/;
|
|
20
|
+
const PASCAL_CASE_RE = /^[A-Z][a-z]+[A-Z]/;
|
|
21
|
+
const HTML_TARGETS = new Set([
|
|
18
22
|
"_blank",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"submit",
|
|
23
|
-
"reset",
|
|
24
|
-
"text",
|
|
25
|
-
"numeric",
|
|
26
|
-
"password",
|
|
27
|
-
"email",
|
|
28
|
-
"checkbox",
|
|
29
|
-
"radio",
|
|
30
|
-
"hidden",
|
|
31
|
-
"none",
|
|
32
|
-
"auto",
|
|
33
|
-
"inherit",
|
|
34
|
-
"initial",
|
|
35
|
-
"sm",
|
|
36
|
-
"md",
|
|
37
|
-
"lg",
|
|
38
|
-
"xl",
|
|
39
|
-
"xs",
|
|
40
|
-
"2xl",
|
|
41
|
-
"3xl",
|
|
42
|
-
"icon",
|
|
43
|
-
"icon-sm",
|
|
44
|
-
"icon-lg"
|
|
45
|
-
]);
|
|
46
|
-
const SLOT_EVENT_NAMES = new Set([
|
|
47
|
-
"header",
|
|
48
|
-
"footer",
|
|
49
|
-
"default",
|
|
50
|
-
"trigger",
|
|
51
|
-
"content",
|
|
52
|
-
"sidebar",
|
|
53
|
-
"overlay",
|
|
54
|
-
"body",
|
|
55
|
-
"actions",
|
|
56
|
-
"click",
|
|
57
|
-
"change",
|
|
58
|
-
"input",
|
|
59
|
-
"focus",
|
|
60
|
-
"blur",
|
|
61
|
-
"submit",
|
|
62
|
-
"mounted",
|
|
63
|
-
"unmounted",
|
|
64
|
-
"updated"
|
|
23
|
+
"_self",
|
|
24
|
+
"_parent",
|
|
25
|
+
"_top"
|
|
65
26
|
]);
|
|
27
|
+
function isURLLike(str) {
|
|
28
|
+
if (/^(https?|ftp|file|mailto|data):/.test(str)) return true;
|
|
29
|
+
if (/^(\.\.?\/|\/|[A-Za-z]:\\)/.test(str)) return true;
|
|
30
|
+
if (/^['"]?[@a-z][\w-]*/.test(str.toLowerCase()) && !str.includes(" ") && (str.includes("/") || str.includes("."))) return true;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
66
33
|
const TAILWIND_SEGMENT_RE = /^(?:bg-|text-|border-|flex|grid|p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|rounded|shadow|w-|h-|min-|max-|gap-|space-|items-|justify-|self-|overflow-|z-|opacity-|transition|duration-|ease-|animate-|font-|leading-|tracking-|decoration-|underline|line-through|uppercase|lowercase|capitalize|truncate|whitespace-|break-|sr-only|not-sr-only|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|group-|peer-|ring-|outline-|divide-|table-|col-|row-|aspect-|object-|inset-|top-|right-|bottom-|left-|translate-|rotate-|scale-|skew-|origin-|cursor-|select-|resize-|fill-|stroke-|block|inline|absolute|relative|fixed|sticky|static|float-|clear-|isolate|visible|invisible|grow|shrink|basis-|order-|place-)/;
|
|
67
|
-
/**
|
|
68
|
-
* Returns true if the string looks like a CSS class list (Tailwind or similar).
|
|
69
|
-
* Requires 2+ space-separated segments with majority matching utility patterns.
|
|
70
|
-
*/
|
|
71
34
|
function isCssClassList(value) {
|
|
72
35
|
const segments = value.trim().split(/\s+/);
|
|
73
36
|
if (segments.length < 2) return false;
|
|
@@ -75,116 +38,223 @@ function isCssClassList(value) {
|
|
|
75
38
|
for (const seg of segments) if (TAILWIND_SEGMENT_RE.test(seg)) matched++;
|
|
76
39
|
return matched / segments.length >= .5;
|
|
77
40
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Returns true if the string is a single CSS utility token (e.g. "bg-blue-500").
|
|
80
|
-
*/
|
|
81
41
|
function isSingleCssUtility(value) {
|
|
82
42
|
const trimmed = value.trim();
|
|
83
43
|
if (trimmed.includes(" ")) return false;
|
|
84
44
|
return TAILWIND_SEGMENT_RE.test(trimmed);
|
|
85
45
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
46
|
+
const SVG_TECHNICAL_ATTRIBUTES = new Set([
|
|
47
|
+
"d",
|
|
48
|
+
"viewBox",
|
|
49
|
+
"points",
|
|
50
|
+
"transform",
|
|
51
|
+
"pathLength",
|
|
52
|
+
"xmlns",
|
|
53
|
+
"preserveAspectRatio",
|
|
54
|
+
"stroke-linecap",
|
|
55
|
+
"stroke-linejoin",
|
|
56
|
+
"stroke-width",
|
|
57
|
+
"stroke-dasharray",
|
|
58
|
+
"stroke-dashoffset",
|
|
59
|
+
"stroke-miterlimit",
|
|
60
|
+
"fill-rule",
|
|
61
|
+
"clip-rule"
|
|
62
|
+
]);
|
|
63
|
+
const SVG_GRAPHIC_ELEMENTS = new Set([
|
|
64
|
+
"svg",
|
|
65
|
+
"path",
|
|
66
|
+
"circle",
|
|
67
|
+
"rect",
|
|
68
|
+
"line",
|
|
69
|
+
"polyline",
|
|
70
|
+
"polygon",
|
|
71
|
+
"ellipse",
|
|
72
|
+
"g",
|
|
73
|
+
"defs",
|
|
74
|
+
"use",
|
|
75
|
+
"symbol",
|
|
76
|
+
"clipPath",
|
|
77
|
+
"mask",
|
|
78
|
+
"pattern",
|
|
79
|
+
"linearGradient",
|
|
80
|
+
"radialGradient",
|
|
81
|
+
"stop",
|
|
82
|
+
"marker",
|
|
83
|
+
"animate",
|
|
84
|
+
"animateTransform",
|
|
85
|
+
"image"
|
|
86
|
+
]);
|
|
87
|
+
const I18N_FUNCTIONS = new Set([
|
|
88
|
+
"t",
|
|
89
|
+
"$t",
|
|
90
|
+
"i18n",
|
|
91
|
+
"translate",
|
|
92
|
+
"formatMessage",
|
|
93
|
+
"msg"
|
|
94
|
+
]);
|
|
95
|
+
const EMIT_FUNCTIONS = new Set(["emit", "$emit"]);
|
|
96
|
+
const TRANSLATABLE_ATTRIBUTES = new Set([
|
|
97
|
+
"title",
|
|
98
|
+
"alt",
|
|
99
|
+
"placeholder",
|
|
100
|
+
"label",
|
|
101
|
+
"summary",
|
|
102
|
+
"caption",
|
|
103
|
+
"abbr",
|
|
104
|
+
"accesskey",
|
|
105
|
+
"content",
|
|
106
|
+
"description",
|
|
107
|
+
"aria-label",
|
|
108
|
+
"aria-description",
|
|
109
|
+
"aria-placeholder",
|
|
110
|
+
"aria-roledescription",
|
|
111
|
+
"aria-valuetext",
|
|
112
|
+
"accessibilityLabel",
|
|
113
|
+
"accessibilityHint",
|
|
114
|
+
"accessibilityValue",
|
|
115
|
+
"heading",
|
|
116
|
+
"subheading",
|
|
117
|
+
"message",
|
|
118
|
+
"hint",
|
|
119
|
+
"tooltip",
|
|
120
|
+
"helper-text",
|
|
121
|
+
"error-message",
|
|
122
|
+
"success-message",
|
|
123
|
+
"confirm-text",
|
|
124
|
+
"cancel-text",
|
|
125
|
+
"empty-text",
|
|
126
|
+
"loading-text",
|
|
127
|
+
"no-data-text",
|
|
128
|
+
"no-results-text"
|
|
129
|
+
]);
|
|
130
|
+
const TRANSLATABLE_PROPERTIES = new Set([
|
|
131
|
+
"label",
|
|
132
|
+
"title",
|
|
133
|
+
"description",
|
|
134
|
+
"text",
|
|
135
|
+
"message",
|
|
136
|
+
"placeholder",
|
|
137
|
+
"caption",
|
|
138
|
+
"summary",
|
|
139
|
+
"heading",
|
|
140
|
+
"subheading",
|
|
141
|
+
"subtitle",
|
|
142
|
+
"tooltip",
|
|
143
|
+
"hint",
|
|
144
|
+
"helpText",
|
|
145
|
+
"errorMessage",
|
|
146
|
+
"successMessage",
|
|
147
|
+
"name"
|
|
148
|
+
]);
|
|
149
|
+
/**
|
|
150
|
+
* Determines if a string is definitely NOT user-visible content.
|
|
151
|
+
* Returns skip reason if it should be filtered, null if it should proceed to scoring.
|
|
152
|
+
*
|
|
153
|
+
* Conservative for template_text/jsx_text (tag-between text is almost always content).
|
|
154
|
+
* Aggressive for everything else (technical tokens, config values, framework artifacts).
|
|
155
|
+
*/
|
|
156
|
+
function shouldSkip(str) {
|
|
157
|
+
if (str.context === "import_path") return "import_path";
|
|
158
|
+
if (str.context === "type_annotation") return "type_annotation";
|
|
159
|
+
if (str.context === "css_class") return "css_class";
|
|
160
|
+
if (str.context === "css_utility_call") return "css_utility_call";
|
|
161
|
+
if (str.context === "console_call") return "console_call";
|
|
162
|
+
if (str.context === "test_assertion") return "test_assertion";
|
|
163
|
+
if (str.context === "switch_case") return "switch_case";
|
|
164
|
+
const v = str.value;
|
|
165
|
+
if (v.length <= 1) return "single_char";
|
|
166
|
+
if (/^\s+$/.test(v)) return "whitespace";
|
|
167
|
+
if (PURE_NUMBER_RE.test(v)) return "pure_number";
|
|
168
|
+
if (HEX_COLOR_RE.test(v)) return "hex_color";
|
|
169
|
+
if (FILE_EXT_RE.test(v)) return "file_extension";
|
|
170
|
+
if (v.startsWith("--")) return "cli_flag";
|
|
171
|
+
if (I18N_KEY_RE.test(v)) return "i18n_key";
|
|
172
|
+
if (MIME_TYPE_RE.test(v)) return "mime_type";
|
|
173
|
+
if (isURLLike(v)) return "url_path";
|
|
174
|
+
if (isCssClassList(v)) return "css_class_list";
|
|
175
|
+
if (isSingleCssUtility(v)) return "css_utility_token";
|
|
176
|
+
if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return "svg_path_data";
|
|
177
|
+
if (SVG_VIEWBOX_RE.test(v)) return "svg_viewbox";
|
|
178
|
+
if (str.parentProperty !== void 0 && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return "svg_technical_attr";
|
|
179
|
+
if (str.context === "template_attribute" && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return "svg_element_attr";
|
|
180
|
+
if (v.startsWith("update:")) return "vue_emit_event";
|
|
181
|
+
if (PLACEHOLDER_RE.test(v)) return "placeholder";
|
|
182
|
+
if (LOCALE_CODE_RE.test(v)) return "locale_code";
|
|
183
|
+
if (DIMENSION_RE.test(v)) return "dimension";
|
|
184
|
+
if (REPEAT_CHAR_RE.test(v)) return "repeat_chars";
|
|
185
|
+
if (HTML_TARGETS.has(v)) return "html_target";
|
|
186
|
+
if (str.context === "function_argument" && I18N_FUNCTIONS.has(str.parent)) {
|
|
187
|
+
if (/^[a-z][a-z0-9_.-]*$/.test(v)) return "i18n_function_arg";
|
|
188
|
+
}
|
|
189
|
+
if (str.context === "function_argument" && EMIT_FUNCTIONS.has(str.parent)) return "emit_event_arg";
|
|
190
|
+
if (str.context !== "template_text" && str.context !== "jsx_text") {
|
|
191
|
+
if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) return "technical_identifier";
|
|
192
|
+
}
|
|
193
|
+
if (ERROR_CODE_RE.test(v) && v.includes("_") && v.length > 3) return "error_code";
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Calculates a content confidence score (0-1) for a string that passed shouldSkip.
|
|
198
|
+
* Uses AST context metadata (our advantage over offset-based tools) combined with
|
|
199
|
+
* value-based signals proven by i18next-cli.
|
|
200
|
+
*
|
|
201
|
+
* Base score: 0.5. Boosted/penalized by context and value characteristics.
|
|
202
|
+
*/
|
|
203
|
+
function calculateContentScore(str) {
|
|
204
|
+
let score = .5;
|
|
205
|
+
if (str.context === "template_text" || str.context === "jsx_text") score += .3;
|
|
206
|
+
if (str.context === "template_attribute" || str.context === "jsx_attribute") if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) score += .2;
|
|
207
|
+
else score -= .2;
|
|
208
|
+
if (str.context === "object_property") {
|
|
209
|
+
if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) score += .25;
|
|
153
210
|
}
|
|
154
|
-
|
|
211
|
+
const wordCount = str.value.split(/\s+/).length;
|
|
212
|
+
if (wordCount >= 3) score += .2;
|
|
213
|
+
else if (wordCount === 2) score += .1;
|
|
214
|
+
if (/[.!?:;]$/.test(str.value)) score += .1;
|
|
215
|
+
if (/[\u0080-\uFFFF]/.test(str.value)) score += .15;
|
|
216
|
+
if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += .1;
|
|
217
|
+
if (CAMEL_CASE_RE.test(str.value)) score -= .3;
|
|
218
|
+
if (PASCAL_CASE_RE.test(str.value) && !str.value.includes(" ")) score -= .25;
|
|
219
|
+
if (/^[A-Z]{2,5}$/.test(str.value)) score -= .15;
|
|
220
|
+
if (str.value.includes("/") && !str.value.includes(" ")) score -= .2;
|
|
221
|
+
return Math.max(0, Math.min(1, score));
|
|
222
|
+
}
|
|
155
223
|
/**
|
|
156
|
-
*
|
|
224
|
+
* Two-phase pre-filter:
|
|
225
|
+
* 1. shouldSkip(): Binary removal of definite non-content
|
|
226
|
+
* 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings
|
|
157
227
|
*
|
|
158
|
-
*
|
|
159
|
-
* Returns candidates (passed), count of filtered, and reason breakdown.
|
|
228
|
+
* Returns candidates that passed both phases, with content scores attached.
|
|
160
229
|
*/
|
|
161
|
-
function applyPreFilter(strings) {
|
|
230
|
+
function applyPreFilter(strings, minScore = .4) {
|
|
162
231
|
const candidates = [];
|
|
163
|
-
const
|
|
164
|
-
let
|
|
232
|
+
const skipReasons = {};
|
|
233
|
+
let skipped = 0;
|
|
234
|
+
let lowConfidence = 0;
|
|
165
235
|
for (const str of strings) {
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
236
|
+
const skipReason = shouldSkip(str);
|
|
237
|
+
if (skipReason) {
|
|
238
|
+
skipped++;
|
|
239
|
+
skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const contentScore = calculateContentScore(str);
|
|
243
|
+
if (contentScore < minScore) {
|
|
244
|
+
lowConfidence++;
|
|
245
|
+
skipReasons["low_confidence"] = (skipReasons["low_confidence"] ?? 0) + 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
str.contentScore = contentScore;
|
|
249
|
+
candidates.push(str);
|
|
171
250
|
}
|
|
172
251
|
return {
|
|
173
252
|
candidates,
|
|
174
|
-
|
|
175
|
-
|
|
253
|
+
skipped,
|
|
254
|
+
lowConfidence,
|
|
255
|
+
skipReasons
|
|
176
256
|
};
|
|
177
257
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Find the first pre-filter rule that matches this string.
|
|
180
|
-
* Returns the rule if matched, undefined if the string should pass through.
|
|
181
|
-
*/
|
|
182
|
-
function findMatchingRule(str) {
|
|
183
|
-
for (const rule of PRE_FILTER_RULES) {
|
|
184
|
-
if (rule.context !== void 0 && str.context === rule.context) return rule;
|
|
185
|
-
if (rule.condition !== void 0 && rule.condition(str.value)) return rule;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
258
|
//#endregion
|
|
189
259
|
//#region src/core/ast-scanner/index.ts
|
|
190
260
|
const TSX_EXTENSIONS = new Set([
|
|
@@ -203,7 +273,7 @@ const ASTRO_EXTENSIONS = new Set([".astro"]);
|
|
|
203
273
|
*/
|
|
204
274
|
async function loadVueParser() {
|
|
205
275
|
try {
|
|
206
|
-
return (await import("./vue-parser-
|
|
276
|
+
return (await import("./vue-parser-DTPTN3Yb.mjs")).parseVue;
|
|
207
277
|
} catch {
|
|
208
278
|
return;
|
|
209
279
|
}
|
|
@@ -214,7 +284,7 @@ async function loadVueParser() {
|
|
|
214
284
|
*/
|
|
215
285
|
async function loadSvelteParser() {
|
|
216
286
|
try {
|
|
217
|
-
return (await import("./svelte-parser-
|
|
287
|
+
return (await import("./svelte-parser-1bBcMx3T.mjs")).parseSvelte;
|
|
218
288
|
} catch {
|
|
219
289
|
return;
|
|
220
290
|
}
|
|
@@ -225,7 +295,7 @@ async function loadSvelteParser() {
|
|
|
225
295
|
*/
|
|
226
296
|
async function loadAstroParser() {
|
|
227
297
|
try {
|
|
228
|
-
return (await import("./astro-parser-
|
|
298
|
+
return (await import("./astro-parser-BJ-54JGe.mjs")).parseAstro;
|
|
229
299
|
} catch {
|
|
230
300
|
return;
|
|
231
301
|
}
|
|
@@ -343,6 +413,7 @@ const DEFAULT_LIMIT = 50;
|
|
|
343
413
|
const DEFAULT_OFFSET = 0;
|
|
344
414
|
const DEFAULT_MIN_LENGTH = 2;
|
|
345
415
|
const DEFAULT_MAX_LENGTH = 500;
|
|
416
|
+
const DEFAULT_MIN_SCORE = .4;
|
|
346
417
|
const SUMMARY_SAMPLE_SIZE = 10;
|
|
347
418
|
const CONTEXT_MAP = {
|
|
348
419
|
"template_text": "template_text",
|
|
@@ -364,142 +435,17 @@ const CONTEXT_MAP = {
|
|
|
364
435
|
"console_call": "other",
|
|
365
436
|
"test_assertion": "other"
|
|
366
437
|
};
|
|
367
|
-
const IMPORT_PATH_RE = /^(\.\/|\.\.\/|@\/|@[a-z]|[a-z][a-z0-9-]*\/)/;
|
|
368
|
-
const CSS_CLASS_PREFIX_RE = /^(flex|grid|block|inline|hidden|absolute|relative|fixed|sticky|p-|m-|w-|h-|bg-|text-|border-|rounded|shadow|gap-|space-|font-|leading-|tracking-|opacity-|z-|overflow-|cursor-|transition|transform|animate-|hover:|focus:|active:|dark:|sm:|md:|lg:|xl:)/;
|
|
369
|
-
const URL_ROUTE_RE = /^(https?:\/\/|\/api\/|\/v[0-9]|#\/|mailto:|tel:)/;
|
|
370
|
-
const ROUTE_LIKE_RE = /^\/[a-z0-9/:_-]*$/i;
|
|
371
|
-
const COLOR_RE = /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\(|transparent|inherit|currentColor)$/i;
|
|
372
|
-
const CAMEL_CASE_RE = /^[a-z][a-zA-Z0-9]*$/;
|
|
373
|
-
const SCREAMING_SNAKE_RE = /^[A-Z][A-Z0-9_]+$/;
|
|
374
|
-
const KEBAB_TECH_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
|
|
375
|
-
const FILE_EXT_RE = /\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i;
|
|
376
|
-
const REGEX_LIKE_RE = /^\^|^\(.*\)$|\$$|\\d|\\w|\\s|\.\*|\.\+/;
|
|
377
|
-
const CONSOLE_RE = /console\.(log|warn|error|debug|info|trace)\s*\(/;
|
|
378
|
-
const CONTENT_WORDS = new Set([
|
|
379
|
-
"sign",
|
|
380
|
-
"up",
|
|
381
|
-
"log",
|
|
382
|
-
"in",
|
|
383
|
-
"out",
|
|
384
|
-
"get",
|
|
385
|
-
"started",
|
|
386
|
-
"learn",
|
|
387
|
-
"more",
|
|
388
|
-
"read",
|
|
389
|
-
"click",
|
|
390
|
-
"here",
|
|
391
|
-
"go",
|
|
392
|
-
"back",
|
|
393
|
-
"next",
|
|
394
|
-
"prev",
|
|
395
|
-
"previous",
|
|
396
|
-
"see",
|
|
397
|
-
"all",
|
|
398
|
-
"view",
|
|
399
|
-
"show",
|
|
400
|
-
"hide",
|
|
401
|
-
"open",
|
|
402
|
-
"close",
|
|
403
|
-
"add",
|
|
404
|
-
"new",
|
|
405
|
-
"save",
|
|
406
|
-
"edit",
|
|
407
|
-
"delete",
|
|
408
|
-
"remove",
|
|
409
|
-
"submit",
|
|
410
|
-
"cancel",
|
|
411
|
-
"confirm",
|
|
412
|
-
"welcome",
|
|
413
|
-
"hello",
|
|
414
|
-
"thank",
|
|
415
|
-
"you",
|
|
416
|
-
"good",
|
|
417
|
-
"bye",
|
|
418
|
-
"hi",
|
|
419
|
-
"hey",
|
|
420
|
-
"no",
|
|
421
|
-
"yes",
|
|
422
|
-
"ok",
|
|
423
|
-
"sorry",
|
|
424
|
-
"please",
|
|
425
|
-
"help",
|
|
426
|
-
"about",
|
|
427
|
-
"contact",
|
|
428
|
-
"home",
|
|
429
|
-
"page",
|
|
430
|
-
"not",
|
|
431
|
-
"found",
|
|
432
|
-
"error",
|
|
433
|
-
"success",
|
|
434
|
-
"loading"
|
|
435
|
-
]);
|
|
436
|
-
/**
|
|
437
|
-
* Determines if a string is non-content (technical) and should be filtered out.
|
|
438
|
-
* Pure function — no side effects, no semantic decisions.
|
|
439
|
-
* Conservative: if in doubt, returns false (keeps the string).
|
|
440
|
-
*
|
|
441
|
-
* @deprecated Kept for backward compatibility. Scanner v2 uses structural pre-filter instead.
|
|
442
|
-
*/
|
|
443
|
-
function isNonContent(value, lineContext, minLength = DEFAULT_MIN_LENGTH, maxLength = DEFAULT_MAX_LENGTH) {
|
|
444
|
-
if (value.length < minLength) return true;
|
|
445
|
-
if (value.length > maxLength) return true;
|
|
446
|
-
if (IMPORT_PATH_RE.test(value) && !value.includes(" ")) return true;
|
|
447
|
-
if (CSS_CLASS_PREFIX_RE.test(value) && !value.includes(" ")) return true;
|
|
448
|
-
if (value.includes(" ") && looksLikeCssClasses(value)) return true;
|
|
449
|
-
if (URL_ROUTE_RE.test(value)) return true;
|
|
450
|
-
if (value.startsWith("/") && ROUTE_LIKE_RE.test(value)) return true;
|
|
451
|
-
if (COLOR_RE.test(value)) return true;
|
|
452
|
-
if (isTechnicalIdentifier(value)) return true;
|
|
453
|
-
if (FILE_EXT_RE.test(value)) return true;
|
|
454
|
-
if (REGEX_LIKE_RE.test(value)) return true;
|
|
455
|
-
if (CONSOLE_RE.test(lineContext)) return true;
|
|
456
|
-
return false;
|
|
457
|
-
}
|
|
458
|
-
function isTechnicalIdentifier(value) {
|
|
459
|
-
if (value.includes(" ")) return false;
|
|
460
|
-
if (CAMEL_CASE_RE.test(value) && /[A-Z]/.test(value)) return true;
|
|
461
|
-
if (SCREAMING_SNAKE_RE.test(value) && value.includes("_")) return true;
|
|
462
|
-
if (KEBAB_TECH_RE.test(value)) {
|
|
463
|
-
if (!value.split("-").some((p) => CONTENT_WORDS.has(p))) return true;
|
|
464
|
-
}
|
|
465
|
-
if (/^[a-z][a-z0-9-]*$/.test(value) && value.length < 40 && !value.includes(" ")) {
|
|
466
|
-
if (value.includes("/")) return true;
|
|
467
|
-
}
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
function looksLikeCssClasses(value) {
|
|
471
|
-
const tokens = value.split(/\s+/);
|
|
472
|
-
if (tokens.length < 2) return false;
|
|
473
|
-
return tokens.filter((t) => isCssClassToken(t)).length === tokens.length;
|
|
474
|
-
}
|
|
475
|
-
function isCssClassToken(token) {
|
|
476
|
-
if (CSS_CLASS_PREFIX_RE.test(token)) return true;
|
|
477
|
-
if (/^(flex|grid|block|inline|hidden|container|relative|absolute|fixed|sticky|static|isolate|overflow-hidden|overflow-auto|overflow-scroll|underline|italic|uppercase|lowercase|capitalize|truncate|break-words|break-all|whitespace-nowrap|sr-only|not-sr-only)$/.test(token)) return true;
|
|
478
|
-
if (/^[a-z]+-\[.+\]$/.test(token)) return true;
|
|
479
|
-
if (/^(sm|md|lg|xl|2xl|hover|focus|active|disabled|first|last|odd|even|dark|group-hover|peer-checked):/.test(token)) return true;
|
|
480
|
-
return false;
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Apply legacy isNonContent filter as a secondary pass on v2 candidates.
|
|
484
|
-
* This catches heuristic patterns (URLs, regex, camelCase identifiers, etc.)
|
|
485
|
-
* that the structural pre-filter does not cover.
|
|
486
|
-
*/
|
|
487
|
-
function applySecondaryFilter(value, _surrounding, minLength, maxLength) {
|
|
488
|
-
return isNonContent(value, "", minLength, maxLength);
|
|
489
|
-
}
|
|
490
438
|
async function scanCandidates(projectRoot, options) {
|
|
491
439
|
const limit = options?.limit ?? DEFAULT_LIMIT;
|
|
492
440
|
const offset = options?.offset ?? DEFAULT_OFFSET;
|
|
493
441
|
const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH;
|
|
494
442
|
const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH;
|
|
443
|
+
const minScore = options?.min_score ?? DEFAULT_MIN_SCORE;
|
|
495
444
|
const files = await discoverFiles(projectRoot, {
|
|
496
445
|
paths: options?.paths ?? await autoDetectSourceDirs(projectRoot),
|
|
497
446
|
include: options?.include,
|
|
498
447
|
exclude: options?.exclude
|
|
499
448
|
});
|
|
500
|
-
const allCandidates = [];
|
|
501
|
-
const dupeMap = /* @__PURE__ */ new Map();
|
|
502
|
-
let rawStringsFound = 0;
|
|
503
449
|
const filePromises = files.map(async (relPath) => {
|
|
504
450
|
const filePath = join(projectRoot, relPath);
|
|
505
451
|
const content = await readText(filePath);
|
|
@@ -513,51 +459,89 @@ async function scanCandidates(projectRoot, options) {
|
|
|
513
459
|
};
|
|
514
460
|
});
|
|
515
461
|
const fileResults = await Promise.all(filePromises);
|
|
462
|
+
let rawStringsFound = 0;
|
|
463
|
+
let skippedCount = 0;
|
|
464
|
+
let lowConfidenceCount = 0;
|
|
465
|
+
const skipReasons = {};
|
|
466
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
467
|
+
const dupeMap = /* @__PURE__ */ new Map();
|
|
516
468
|
for (const { relPath, extractions } of fileResults) {
|
|
517
469
|
rawStringsFound += extractions.length;
|
|
518
470
|
for (const extraction of extractions) {
|
|
519
|
-
if (
|
|
471
|
+
if (extraction.value.length < minLength || extraction.value.length > maxLength) {
|
|
472
|
+
skippedCount++;
|
|
473
|
+
skipReasons["length_filter"] = (skipReasons["length_filter"] ?? 0) + 1;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const contentScore = extraction.contentScore ?? calculateContentScore(extraction);
|
|
477
|
+
if (contentScore < minScore) {
|
|
478
|
+
lowConfidenceCount++;
|
|
479
|
+
skipReasons["low_confidence"] = (skipReasons["low_confidence"] ?? 0) + 1;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
520
482
|
const mappedContext = CONTEXT_MAP[extraction.context];
|
|
521
|
-
const
|
|
522
|
-
file: relPath,
|
|
523
|
-
line: extraction.line,
|
|
524
|
-
column: extraction.column,
|
|
525
|
-
value: extraction.value,
|
|
526
|
-
context: mappedContext,
|
|
527
|
-
surrounding: extraction.surrounding
|
|
528
|
-
};
|
|
529
|
-
allCandidates.push(candidate);
|
|
530
|
-
const key = extraction.value;
|
|
531
|
-
if (!dupeMap.has(key)) dupeMap.set(key, []);
|
|
532
|
-
dupeMap.get(key).push({
|
|
483
|
+
const loc = {
|
|
533
484
|
file: relPath,
|
|
534
485
|
line: extraction.line
|
|
486
|
+
};
|
|
487
|
+
if (!dupeMap.has(extraction.value)) dupeMap.set(extraction.value, []);
|
|
488
|
+
dupeMap.get(extraction.value).push(loc);
|
|
489
|
+
if (!uniqueMap.has(extraction.value)) uniqueMap.set(extraction.value, {
|
|
490
|
+
candidate: {
|
|
491
|
+
file: relPath,
|
|
492
|
+
line: extraction.line,
|
|
493
|
+
column: extraction.column,
|
|
494
|
+
value: extraction.value,
|
|
495
|
+
context: mappedContext,
|
|
496
|
+
surrounding: extraction.surrounding,
|
|
497
|
+
contentScore,
|
|
498
|
+
occurrences: [loc]
|
|
499
|
+
},
|
|
500
|
+
maxScore: contentScore
|
|
535
501
|
});
|
|
502
|
+
else {
|
|
503
|
+
const entry = uniqueMap.get(extraction.value);
|
|
504
|
+
entry.candidate.occurrences.push(loc);
|
|
505
|
+
if (contentScore > entry.maxScore) {
|
|
506
|
+
entry.maxScore = contentScore;
|
|
507
|
+
entry.candidate.contentScore = contentScore;
|
|
508
|
+
entry.candidate.file = relPath;
|
|
509
|
+
entry.candidate.line = extraction.line;
|
|
510
|
+
entry.candidate.column = extraction.column;
|
|
511
|
+
entry.candidate.context = mappedContext;
|
|
512
|
+
entry.candidate.surrounding = extraction.surrounding;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
536
515
|
}
|
|
537
516
|
}
|
|
517
|
+
const allUniqueCandidates = [...uniqueMap.values()].map((e) => e.candidate).toSorted((a, b) => b.contentScore - a.contentScore);
|
|
538
518
|
const duplicates = [...dupeMap.entries()].filter(([, occurrences]) => occurrences.length >= 2).map(([value, occurrences]) => ({
|
|
539
519
|
value,
|
|
540
520
|
count: occurrences.length,
|
|
541
521
|
occurrences
|
|
542
522
|
})).toSorted((a, b) => b.count - a.count);
|
|
543
|
-
const
|
|
544
|
-
const paginated =
|
|
545
|
-
const hasMore =
|
|
523
|
+
const uniqueCount = allUniqueCandidates.length;
|
|
524
|
+
const paginated = allUniqueCandidates.slice(offset, offset + limit);
|
|
525
|
+
const hasMore = uniqueCount > offset + limit;
|
|
546
526
|
return {
|
|
547
527
|
candidates: paginated,
|
|
548
528
|
duplicates,
|
|
549
529
|
stats: {
|
|
550
530
|
files_scanned: files.length,
|
|
551
531
|
raw_strings_found: rawStringsFound,
|
|
552
|
-
|
|
532
|
+
skipped: skippedCount,
|
|
533
|
+
low_confidence: lowConfidenceCount,
|
|
534
|
+
unique_candidates: uniqueCount,
|
|
553
535
|
candidates_returned: paginated.length,
|
|
554
|
-
has_more: hasMore
|
|
536
|
+
has_more: hasMore,
|
|
537
|
+
skip_reasons: skipReasons
|
|
555
538
|
}
|
|
556
539
|
};
|
|
557
540
|
}
|
|
558
541
|
async function scanSummary(projectRoot, options) {
|
|
559
542
|
const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH;
|
|
560
543
|
const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH;
|
|
544
|
+
const minScore = options?.min_score ?? DEFAULT_MIN_SCORE;
|
|
561
545
|
const files = await discoverFiles(projectRoot, {
|
|
562
546
|
paths: options?.paths ?? await autoDetectSourceDirs(projectRoot),
|
|
563
547
|
include: options?.include,
|
|
@@ -587,7 +571,8 @@ async function scanSummary(projectRoot, options) {
|
|
|
587
571
|
});
|
|
588
572
|
const sampleResults = await Promise.all(samplePromises);
|
|
589
573
|
for (const extractions of sampleResults) for (const extraction of extractions) {
|
|
590
|
-
if (
|
|
574
|
+
if (extraction.value.length < minLength || extraction.value.length > maxLength) continue;
|
|
575
|
+
if ((extraction.contentScore ?? calculateContentScore(extraction)) < minScore) continue;
|
|
591
576
|
sampleCandidates++;
|
|
592
577
|
const prev = freqMap.get(extraction.value) ?? 0;
|
|
593
578
|
freqMap.set(extraction.value, prev + 1);
|
|
@@ -614,6 +599,6 @@ async function scanSummary(projectRoot, options) {
|
|
|
614
599
|
};
|
|
615
600
|
}
|
|
616
601
|
//#endregion
|
|
617
|
-
export {
|
|
602
|
+
export { scanSummary as n, scanCandidates as t };
|
|
618
603
|
|
|
619
|
-
//# sourceMappingURL=scanner-
|
|
604
|
+
//# sourceMappingURL=scanner-DUoXnmtI.mjs.map
|