@contentrain/mcp 1.1.1 → 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 +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-CQOxARUU.mjs} +275 -308
- package/dist/scanner-CQOxARUU.mjs.map +1 -0
- package/dist/{server-BXguCuJb.mjs → server-DfWRpzdP.mjs} +172 -20
- package/dist/server-DfWRpzdP.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-BXguCuJb.mjs.map +0 -1
- package/dist/tsx-parser-Bf8Firrq.mjs +0 -2
- package/dist/tsx-parser-CYVyBWjn.mjs.map +0 -1
|
@@ -1,73 +1,25 @@
|
|
|
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
|
-
|
|
18
|
-
|
|
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"
|
|
65
|
-
]);
|
|
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
|
+
function isURLLike(str) {
|
|
17
|
+
if (/^(https?|ftp|file|mailto|data):/.test(str)) return true;
|
|
18
|
+
if (/^(\.\.?\/|\/|[A-Za-z]:\\)/.test(str)) return true;
|
|
19
|
+
if (/^['"]?[@a-z][\w-]*/.test(str.toLowerCase()) && !str.includes(" ") && (str.includes("/") || str.includes("."))) return true;
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
66
22
|
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
23
|
function isCssClassList(value) {
|
|
72
24
|
const segments = value.trim().split(/\s+/);
|
|
73
25
|
if (segments.length < 2) return false;
|
|
@@ -75,116 +27,216 @@ function isCssClassList(value) {
|
|
|
75
27
|
for (const seg of segments) if (TAILWIND_SEGMENT_RE.test(seg)) matched++;
|
|
76
28
|
return matched / segments.length >= .5;
|
|
77
29
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Returns true if the string is a single CSS utility token (e.g. "bg-blue-500").
|
|
80
|
-
*/
|
|
81
30
|
function isSingleCssUtility(value) {
|
|
82
31
|
const trimmed = value.trim();
|
|
83
32
|
if (trimmed.includes(" ")) return false;
|
|
84
33
|
return TAILWIND_SEGMENT_RE.test(trimmed);
|
|
85
34
|
}
|
|
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
|
-
|
|
35
|
+
const SVG_TECHNICAL_ATTRIBUTES = new Set([
|
|
36
|
+
"d",
|
|
37
|
+
"viewBox",
|
|
38
|
+
"points",
|
|
39
|
+
"transform",
|
|
40
|
+
"pathLength",
|
|
41
|
+
"xmlns",
|
|
42
|
+
"preserveAspectRatio",
|
|
43
|
+
"stroke-linecap",
|
|
44
|
+
"stroke-linejoin",
|
|
45
|
+
"stroke-width",
|
|
46
|
+
"stroke-dasharray",
|
|
47
|
+
"stroke-dashoffset",
|
|
48
|
+
"stroke-miterlimit",
|
|
49
|
+
"fill-rule",
|
|
50
|
+
"clip-rule"
|
|
51
|
+
]);
|
|
52
|
+
const SVG_GRAPHIC_ELEMENTS = new Set([
|
|
53
|
+
"svg",
|
|
54
|
+
"path",
|
|
55
|
+
"circle",
|
|
56
|
+
"rect",
|
|
57
|
+
"line",
|
|
58
|
+
"polyline",
|
|
59
|
+
"polygon",
|
|
60
|
+
"ellipse",
|
|
61
|
+
"g",
|
|
62
|
+
"defs",
|
|
63
|
+
"use",
|
|
64
|
+
"symbol",
|
|
65
|
+
"clipPath",
|
|
66
|
+
"mask",
|
|
67
|
+
"pattern",
|
|
68
|
+
"linearGradient",
|
|
69
|
+
"radialGradient",
|
|
70
|
+
"stop",
|
|
71
|
+
"marker",
|
|
72
|
+
"animate",
|
|
73
|
+
"animateTransform",
|
|
74
|
+
"image"
|
|
75
|
+
]);
|
|
76
|
+
const I18N_FUNCTIONS = new Set([
|
|
77
|
+
"t",
|
|
78
|
+
"$t",
|
|
79
|
+
"i18n",
|
|
80
|
+
"translate",
|
|
81
|
+
"formatMessage",
|
|
82
|
+
"msg"
|
|
83
|
+
]);
|
|
84
|
+
const EMIT_FUNCTIONS = new Set(["emit", "$emit"]);
|
|
85
|
+
const TRANSLATABLE_ATTRIBUTES = new Set([
|
|
86
|
+
"title",
|
|
87
|
+
"alt",
|
|
88
|
+
"placeholder",
|
|
89
|
+
"label",
|
|
90
|
+
"summary",
|
|
91
|
+
"caption",
|
|
92
|
+
"abbr",
|
|
93
|
+
"accesskey",
|
|
94
|
+
"content",
|
|
95
|
+
"description",
|
|
96
|
+
"aria-label",
|
|
97
|
+
"aria-description",
|
|
98
|
+
"aria-placeholder",
|
|
99
|
+
"aria-roledescription",
|
|
100
|
+
"aria-valuetext",
|
|
101
|
+
"accessibilityLabel",
|
|
102
|
+
"accessibilityHint",
|
|
103
|
+
"accessibilityValue",
|
|
104
|
+
"heading",
|
|
105
|
+
"subheading",
|
|
106
|
+
"message",
|
|
107
|
+
"hint",
|
|
108
|
+
"tooltip",
|
|
109
|
+
"helper-text",
|
|
110
|
+
"error-message",
|
|
111
|
+
"success-message",
|
|
112
|
+
"confirm-text",
|
|
113
|
+
"cancel-text",
|
|
114
|
+
"empty-text",
|
|
115
|
+
"loading-text",
|
|
116
|
+
"no-data-text",
|
|
117
|
+
"no-results-text"
|
|
118
|
+
]);
|
|
119
|
+
const TRANSLATABLE_PROPERTIES = new Set([
|
|
120
|
+
"label",
|
|
121
|
+
"title",
|
|
122
|
+
"description",
|
|
123
|
+
"text",
|
|
124
|
+
"message",
|
|
125
|
+
"placeholder",
|
|
126
|
+
"caption",
|
|
127
|
+
"summary",
|
|
128
|
+
"heading",
|
|
129
|
+
"subheading",
|
|
130
|
+
"subtitle",
|
|
131
|
+
"tooltip",
|
|
132
|
+
"hint",
|
|
133
|
+
"helpText",
|
|
134
|
+
"errorMessage",
|
|
135
|
+
"successMessage",
|
|
136
|
+
"name"
|
|
137
|
+
]);
|
|
138
|
+
/**
|
|
139
|
+
* Determines if a string is definitely NOT user-visible content.
|
|
140
|
+
* Returns skip reason if it should be filtered, null if it should proceed to scoring.
|
|
141
|
+
*
|
|
142
|
+
* Conservative for template_text/jsx_text (tag-between text is almost always content).
|
|
143
|
+
* Aggressive for everything else (technical tokens, config values, framework artifacts).
|
|
144
|
+
*/
|
|
145
|
+
function shouldSkip(str) {
|
|
146
|
+
if (str.context === "import_path") return "import_path";
|
|
147
|
+
if (str.context === "type_annotation") return "type_annotation";
|
|
148
|
+
if (str.context === "css_class") return "css_class";
|
|
149
|
+
if (str.context === "css_utility_call") return "css_utility_call";
|
|
150
|
+
if (str.context === "console_call") return "console_call";
|
|
151
|
+
if (str.context === "test_assertion") return "test_assertion";
|
|
152
|
+
if (str.context === "switch_case") return "switch_case";
|
|
153
|
+
const v = str.value;
|
|
154
|
+
if (v.length <= 1) return "single_char";
|
|
155
|
+
if (/^\s+$/.test(v)) return "whitespace";
|
|
156
|
+
if (PURE_NUMBER_RE.test(v)) return "pure_number";
|
|
157
|
+
if (HEX_COLOR_RE.test(v)) return "hex_color";
|
|
158
|
+
if (FILE_EXT_RE.test(v)) return "file_extension";
|
|
159
|
+
if (v.startsWith("--")) return "cli_flag";
|
|
160
|
+
if (I18N_KEY_RE.test(v)) return "i18n_key";
|
|
161
|
+
if (isURLLike(v)) return "url_path";
|
|
162
|
+
if (isCssClassList(v)) return "css_class_list";
|
|
163
|
+
if (isSingleCssUtility(v)) return "css_utility_token";
|
|
164
|
+
if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return "svg_path_data";
|
|
165
|
+
if (SVG_VIEWBOX_RE.test(v)) return "svg_viewbox";
|
|
166
|
+
if (str.parentProperty !== void 0 && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return "svg_technical_attr";
|
|
167
|
+
if (str.context === "template_attribute" && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return "svg_element_attr";
|
|
168
|
+
if (v.startsWith("update:")) return "vue_emit_event";
|
|
169
|
+
if (PLACEHOLDER_RE.test(v)) return "placeholder";
|
|
170
|
+
if (str.context === "function_argument" && I18N_FUNCTIONS.has(str.parent)) {
|
|
171
|
+
if (/^[a-z][a-z0-9_.-]*$/.test(v)) return "i18n_function_arg";
|
|
172
|
+
}
|
|
173
|
+
if (str.context === "function_argument" && EMIT_FUNCTIONS.has(str.parent)) return "emit_event_arg";
|
|
174
|
+
if (str.context !== "template_text" && str.context !== "jsx_text") {
|
|
175
|
+
if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) return "technical_identifier";
|
|
153
176
|
}
|
|
154
|
-
|
|
177
|
+
if (ERROR_CODE_RE.test(v) && v.includes("_") && v.length > 3) return "error_code";
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
155
180
|
/**
|
|
156
|
-
*
|
|
181
|
+
* Calculates a content confidence score (0-1) for a string that passed shouldSkip.
|
|
182
|
+
* Uses AST context metadata (our advantage over offset-based tools) combined with
|
|
183
|
+
* value-based signals proven by i18next-cli.
|
|
157
184
|
*
|
|
158
|
-
*
|
|
159
|
-
* Returns candidates (passed), count of filtered, and reason breakdown.
|
|
185
|
+
* Base score: 0.5. Boosted/penalized by context and value characteristics.
|
|
160
186
|
*/
|
|
161
|
-
function
|
|
187
|
+
function calculateContentScore(str) {
|
|
188
|
+
let score = .5;
|
|
189
|
+
if (str.context === "template_text" || str.context === "jsx_text") score += .3;
|
|
190
|
+
if (str.context === "template_attribute" || str.context === "jsx_attribute") if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) score += .2;
|
|
191
|
+
else score -= .2;
|
|
192
|
+
if (str.context === "object_property") {
|
|
193
|
+
if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) score += .25;
|
|
194
|
+
}
|
|
195
|
+
const wordCount = str.value.split(/\s+/).length;
|
|
196
|
+
if (wordCount >= 3) score += .2;
|
|
197
|
+
else if (wordCount === 2) score += .1;
|
|
198
|
+
if (/[.!?:;]$/.test(str.value)) score += .1;
|
|
199
|
+
if (/[\u0080-\uFFFF]/.test(str.value)) score += .15;
|
|
200
|
+
if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += .1;
|
|
201
|
+
if (CAMEL_CASE_RE.test(str.value)) score -= .3;
|
|
202
|
+
if (str.value.includes("/") && !str.value.includes(" ")) score -= .2;
|
|
203
|
+
return Math.max(0, Math.min(1, score));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Two-phase pre-filter:
|
|
207
|
+
* 1. shouldSkip(): Binary removal of definite non-content
|
|
208
|
+
* 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings
|
|
209
|
+
*
|
|
210
|
+
* Returns candidates that passed both phases, with content scores attached.
|
|
211
|
+
*/
|
|
212
|
+
function applyPreFilter(strings, minScore = .4) {
|
|
162
213
|
const candidates = [];
|
|
163
|
-
const
|
|
164
|
-
let
|
|
214
|
+
const skipReasons = {};
|
|
215
|
+
let skipped = 0;
|
|
216
|
+
let lowConfidence = 0;
|
|
165
217
|
for (const str of strings) {
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
218
|
+
const skipReason = shouldSkip(str);
|
|
219
|
+
if (skipReason) {
|
|
220
|
+
skipped++;
|
|
221
|
+
skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const contentScore = calculateContentScore(str);
|
|
225
|
+
if (contentScore < minScore) {
|
|
226
|
+
lowConfidence++;
|
|
227
|
+
skipReasons["low_confidence"] = (skipReasons["low_confidence"] ?? 0) + 1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
str.contentScore = contentScore;
|
|
231
|
+
candidates.push(str);
|
|
171
232
|
}
|
|
172
233
|
return {
|
|
173
234
|
candidates,
|
|
174
|
-
|
|
175
|
-
|
|
235
|
+
skipped,
|
|
236
|
+
lowConfidence,
|
|
237
|
+
skipReasons
|
|
176
238
|
};
|
|
177
239
|
}
|
|
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
240
|
//#endregion
|
|
189
241
|
//#region src/core/ast-scanner/index.ts
|
|
190
242
|
const TSX_EXTENSIONS = new Set([
|
|
@@ -203,7 +255,7 @@ const ASTRO_EXTENSIONS = new Set([".astro"]);
|
|
|
203
255
|
*/
|
|
204
256
|
async function loadVueParser() {
|
|
205
257
|
try {
|
|
206
|
-
return (await import("./vue-parser-
|
|
258
|
+
return (await import("./vue-parser-DTPTN3Yb.mjs")).parseVue;
|
|
207
259
|
} catch {
|
|
208
260
|
return;
|
|
209
261
|
}
|
|
@@ -214,7 +266,7 @@ async function loadVueParser() {
|
|
|
214
266
|
*/
|
|
215
267
|
async function loadSvelteParser() {
|
|
216
268
|
try {
|
|
217
|
-
return (await import("./svelte-parser-
|
|
269
|
+
return (await import("./svelte-parser-1bBcMx3T.mjs")).parseSvelte;
|
|
218
270
|
} catch {
|
|
219
271
|
return;
|
|
220
272
|
}
|
|
@@ -225,7 +277,7 @@ async function loadSvelteParser() {
|
|
|
225
277
|
*/
|
|
226
278
|
async function loadAstroParser() {
|
|
227
279
|
try {
|
|
228
|
-
return (await import("./astro-parser-
|
|
280
|
+
return (await import("./astro-parser-BJ-54JGe.mjs")).parseAstro;
|
|
229
281
|
} catch {
|
|
230
282
|
return;
|
|
231
283
|
}
|
|
@@ -343,6 +395,7 @@ const DEFAULT_LIMIT = 50;
|
|
|
343
395
|
const DEFAULT_OFFSET = 0;
|
|
344
396
|
const DEFAULT_MIN_LENGTH = 2;
|
|
345
397
|
const DEFAULT_MAX_LENGTH = 500;
|
|
398
|
+
const DEFAULT_MIN_SCORE = .4;
|
|
346
399
|
const SUMMARY_SAMPLE_SIZE = 10;
|
|
347
400
|
const CONTEXT_MAP = {
|
|
348
401
|
"template_text": "template_text",
|
|
@@ -364,142 +417,17 @@ const CONTEXT_MAP = {
|
|
|
364
417
|
"console_call": "other",
|
|
365
418
|
"test_assertion": "other"
|
|
366
419
|
};
|
|
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
420
|
async function scanCandidates(projectRoot, options) {
|
|
491
421
|
const limit = options?.limit ?? DEFAULT_LIMIT;
|
|
492
422
|
const offset = options?.offset ?? DEFAULT_OFFSET;
|
|
493
423
|
const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH;
|
|
494
424
|
const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH;
|
|
425
|
+
const minScore = options?.min_score ?? DEFAULT_MIN_SCORE;
|
|
495
426
|
const files = await discoverFiles(projectRoot, {
|
|
496
427
|
paths: options?.paths ?? await autoDetectSourceDirs(projectRoot),
|
|
497
428
|
include: options?.include,
|
|
498
429
|
exclude: options?.exclude
|
|
499
430
|
});
|
|
500
|
-
const allCandidates = [];
|
|
501
|
-
const dupeMap = /* @__PURE__ */ new Map();
|
|
502
|
-
let rawStringsFound = 0;
|
|
503
431
|
const filePromises = files.map(async (relPath) => {
|
|
504
432
|
const filePath = join(projectRoot, relPath);
|
|
505
433
|
const content = await readText(filePath);
|
|
@@ -513,51 +441,89 @@ async function scanCandidates(projectRoot, options) {
|
|
|
513
441
|
};
|
|
514
442
|
});
|
|
515
443
|
const fileResults = await Promise.all(filePromises);
|
|
444
|
+
let rawStringsFound = 0;
|
|
445
|
+
let skippedCount = 0;
|
|
446
|
+
let lowConfidenceCount = 0;
|
|
447
|
+
const skipReasons = {};
|
|
448
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
449
|
+
const dupeMap = /* @__PURE__ */ new Map();
|
|
516
450
|
for (const { relPath, extractions } of fileResults) {
|
|
517
451
|
rawStringsFound += extractions.length;
|
|
518
452
|
for (const extraction of extractions) {
|
|
519
|
-
if (
|
|
453
|
+
if (extraction.value.length < minLength || extraction.value.length > maxLength) {
|
|
454
|
+
skippedCount++;
|
|
455
|
+
skipReasons["length_filter"] = (skipReasons["length_filter"] ?? 0) + 1;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const contentScore = extraction.contentScore ?? calculateContentScore(extraction);
|
|
459
|
+
if (contentScore < minScore) {
|
|
460
|
+
lowConfidenceCount++;
|
|
461
|
+
skipReasons["low_confidence"] = (skipReasons["low_confidence"] ?? 0) + 1;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
520
464
|
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({
|
|
465
|
+
const loc = {
|
|
533
466
|
file: relPath,
|
|
534
467
|
line: extraction.line
|
|
468
|
+
};
|
|
469
|
+
if (!dupeMap.has(extraction.value)) dupeMap.set(extraction.value, []);
|
|
470
|
+
dupeMap.get(extraction.value).push(loc);
|
|
471
|
+
if (!uniqueMap.has(extraction.value)) uniqueMap.set(extraction.value, {
|
|
472
|
+
candidate: {
|
|
473
|
+
file: relPath,
|
|
474
|
+
line: extraction.line,
|
|
475
|
+
column: extraction.column,
|
|
476
|
+
value: extraction.value,
|
|
477
|
+
context: mappedContext,
|
|
478
|
+
surrounding: extraction.surrounding,
|
|
479
|
+
contentScore,
|
|
480
|
+
occurrences: [loc]
|
|
481
|
+
},
|
|
482
|
+
maxScore: contentScore
|
|
535
483
|
});
|
|
484
|
+
else {
|
|
485
|
+
const entry = uniqueMap.get(extraction.value);
|
|
486
|
+
entry.candidate.occurrences.push(loc);
|
|
487
|
+
if (contentScore > entry.maxScore) {
|
|
488
|
+
entry.maxScore = contentScore;
|
|
489
|
+
entry.candidate.contentScore = contentScore;
|
|
490
|
+
entry.candidate.file = relPath;
|
|
491
|
+
entry.candidate.line = extraction.line;
|
|
492
|
+
entry.candidate.column = extraction.column;
|
|
493
|
+
entry.candidate.context = mappedContext;
|
|
494
|
+
entry.candidate.surrounding = extraction.surrounding;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
536
497
|
}
|
|
537
498
|
}
|
|
499
|
+
const allUniqueCandidates = [...uniqueMap.values()].map((e) => e.candidate).toSorted((a, b) => b.contentScore - a.contentScore);
|
|
538
500
|
const duplicates = [...dupeMap.entries()].filter(([, occurrences]) => occurrences.length >= 2).map(([value, occurrences]) => ({
|
|
539
501
|
value,
|
|
540
502
|
count: occurrences.length,
|
|
541
503
|
occurrences
|
|
542
504
|
})).toSorted((a, b) => b.count - a.count);
|
|
543
|
-
const
|
|
544
|
-
const paginated =
|
|
545
|
-
const hasMore =
|
|
505
|
+
const uniqueCount = allUniqueCandidates.length;
|
|
506
|
+
const paginated = allUniqueCandidates.slice(offset, offset + limit);
|
|
507
|
+
const hasMore = uniqueCount > offset + limit;
|
|
546
508
|
return {
|
|
547
509
|
candidates: paginated,
|
|
548
510
|
duplicates,
|
|
549
511
|
stats: {
|
|
550
512
|
files_scanned: files.length,
|
|
551
513
|
raw_strings_found: rawStringsFound,
|
|
552
|
-
|
|
514
|
+
skipped: skippedCount,
|
|
515
|
+
low_confidence: lowConfidenceCount,
|
|
516
|
+
unique_candidates: uniqueCount,
|
|
553
517
|
candidates_returned: paginated.length,
|
|
554
|
-
has_more: hasMore
|
|
518
|
+
has_more: hasMore,
|
|
519
|
+
skip_reasons: skipReasons
|
|
555
520
|
}
|
|
556
521
|
};
|
|
557
522
|
}
|
|
558
523
|
async function scanSummary(projectRoot, options) {
|
|
559
524
|
const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH;
|
|
560
525
|
const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH;
|
|
526
|
+
const minScore = options?.min_score ?? DEFAULT_MIN_SCORE;
|
|
561
527
|
const files = await discoverFiles(projectRoot, {
|
|
562
528
|
paths: options?.paths ?? await autoDetectSourceDirs(projectRoot),
|
|
563
529
|
include: options?.include,
|
|
@@ -587,7 +553,8 @@ async function scanSummary(projectRoot, options) {
|
|
|
587
553
|
});
|
|
588
554
|
const sampleResults = await Promise.all(samplePromises);
|
|
589
555
|
for (const extractions of sampleResults) for (const extraction of extractions) {
|
|
590
|
-
if (
|
|
556
|
+
if (extraction.value.length < minLength || extraction.value.length > maxLength) continue;
|
|
557
|
+
if ((extraction.contentScore ?? calculateContentScore(extraction)) < minScore) continue;
|
|
591
558
|
sampleCandidates++;
|
|
592
559
|
const prev = freqMap.get(extraction.value) ?? 0;
|
|
593
560
|
freqMap.set(extraction.value, prev + 1);
|
|
@@ -614,6 +581,6 @@ async function scanSummary(projectRoot, options) {
|
|
|
614
581
|
};
|
|
615
582
|
}
|
|
616
583
|
//#endregion
|
|
617
|
-
export {
|
|
584
|
+
export { scanSummary as n, scanCandidates as t };
|
|
618
585
|
|
|
619
|
-
//# sourceMappingURL=scanner-
|
|
586
|
+
//# sourceMappingURL=scanner-CQOxARUU.mjs.map
|