@ingcrea/aumic-sanitizer 0.0.0-stage → 5.4.2

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.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MojibakeEngine = void 0;
4
+ const mojibake_vectors_1 = require("../1-atoms/mojibake-vectors");
5
+ class MojibakeEngine {
6
+ /**
7
+ * Motor Heurístico de Sanitización de Nivel 7.
8
+ * Evalúa la presencia de firmas de corrupción antes de disparar mutaciones.
9
+ */
10
+ static cure(raw, allowedVectors) {
11
+ let fixed = raw;
12
+ let totalCount = 0;
13
+ const scores = {
14
+ LATIN1: 0,
15
+ CP1252_PUNCTUATION: 0,
16
+ HTML_ENTITIES: 0,
17
+ DOUBLE_UTF8: 0
18
+ };
19
+ // 1. Barrido Forense (Scoring)
20
+ for (const vector of allowedVectors) {
21
+ const dict = mojibake_vectors_1.VECTORS[vector];
22
+ for (const bad of Object.keys(dict)) {
23
+ const count = fixed.split(bad).length - 1;
24
+ if (count > 0)
25
+ scores[vector] += count;
26
+ }
27
+ }
28
+ // 2. Ejecución Jerárquica (Para evitar colisiones entre encodings anidados)
29
+ const hierarchy = [
30
+ 'DOUBLE_UTF8', // Jerarquía Máxima (Contiene LATIN1 anidado)
31
+ 'HTML_ENTITIES', // Jerarquía Alta (Sintaxis explícita)
32
+ 'LATIN1', // Jerarquía Media (Mapeo directo)
33
+ 'CP1252_PUNCTUATION' // Jerarquía Estándar
34
+ ];
35
+ for (const vector of hierarchy) {
36
+ if (allowedVectors.includes(vector) && scores[vector] > 0) {
37
+ const { res, c } = this.applyVector(fixed, vector);
38
+ fixed = res;
39
+ totalCount += c;
40
+ }
41
+ }
42
+ return { fixed, count: totalCount };
43
+ }
44
+ static applyVector(raw, vector) {
45
+ let res = raw;
46
+ let c = 0;
47
+ for (const [bad, good] of Object.entries(mojibake_vectors_1.VECTORS[vector])) {
48
+ const count = res.split(bad).length - 1;
49
+ if (count > 0) {
50
+ res = res.replaceAll(bad, good);
51
+ c += count;
52
+ }
53
+ }
54
+ return { res, c };
55
+ }
56
+ }
57
+ exports.MojibakeEngine = MojibakeEngine;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_CONFIG = void 0;
7
+ exports.loadConfig = loadConfig;
8
+ exports.initConfig = initConfig;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ exports.DEFAULT_CONFIG = {
12
+ gitMode: 'block',
13
+ rules: {
14
+ mojibake: true,
15
+ emojis: true,
16
+ crlf: true,
17
+ smartQuotes: true,
18
+ zeroWidth: true,
19
+ eradicator: false
20
+ }
21
+ };
22
+ function loadConfig(targetDir) {
23
+ const configPath = path_1.default.join(targetDir, 'aumic.config.json');
24
+ if (fs_1.default.existsSync(configPath)) {
25
+ try {
26
+ const parsed = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
27
+ return {
28
+ gitMode: parsed.gitMode || exports.DEFAULT_CONFIG.gitMode,
29
+ rules: { ...exports.DEFAULT_CONFIG.rules, ...(parsed.rules || {}) }
30
+ };
31
+ }
32
+ catch {
33
+ return exports.DEFAULT_CONFIG;
34
+ }
35
+ }
36
+ return exports.DEFAULT_CONFIG;
37
+ }
38
+ function initConfig(targetDir) {
39
+ const configPath = path_1.default.join(targetDir, 'aumic.config.json');
40
+ fs_1.default.writeFileSync(configPath, JSON.stringify(exports.DEFAULT_CONFIG, null, 4), 'utf8');
41
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getTargetFiles = getTargetFiles;
7
+ const fast_glob_1 = __importDefault(require("fast-glob"));
8
+ const child_process_1 = require("child_process");
9
+ const path_1 = __importDefault(require("path"));
10
+ function getTargetFiles(targetDir, deltaOnly = false) {
11
+ const fsLib = require('fs');
12
+ if (!fsLib.existsSync(targetDir))
13
+ return [];
14
+ if (fsLib.statSync(targetDir).isFile()) {
15
+ return [path_1.default.resolve(targetDir)];
16
+ }
17
+ targetDir = path_1.default.resolve(targetDir);
18
+ const extensions = ['md', 'js', 'ts', 'jsx', 'tsx', 'json', 'yml', 'yaml', 'html', 'css', 'scss', 'astro'];
19
+ const extGlob = `**/*.{${extensions.join(',')}}`;
20
+ const rulesGlob = '**/*rules';
21
+ try {
22
+ // Si estamos en un repo Git, respetamos el .gitignore de forma estricta
23
+ (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', { cwd: targetDir, stdio: 'ignore' });
24
+ let tracked = [];
25
+ let untracked = [];
26
+ if (deltaOnly) {
27
+ try {
28
+ tracked = (0, child_process_1.execSync)('git diff --name-only HEAD', { cwd: targetDir, encoding: 'utf-8' }).split('\n').filter(Boolean);
29
+ }
30
+ catch (e) {
31
+ tracked = (0, child_process_1.execSync)('git ls-files', { cwd: targetDir, encoding: 'utf-8' }).split('\n').filter(Boolean);
32
+ }
33
+ }
34
+ else {
35
+ tracked = (0, child_process_1.execSync)('git ls-files', { cwd: targetDir, encoding: 'utf-8' }).split('\n').filter(Boolean);
36
+ }
37
+ untracked = (0, child_process_1.execSync)('git ls-files --others --exclude-standard', { cwd: targetDir, encoding: 'utf-8' }).split('\n').filter(Boolean);
38
+ const allFiles = [...tracked, ...untracked];
39
+ return allFiles
40
+ .filter(f => extensions.some(ext => f.endsWith('.' + ext)) || f.endsWith('rules') || path_1.default.basename(f).startsWith('LICENSE'))
41
+ .map(f => path_1.default.join(targetDir, f));
42
+ }
43
+ catch (e) {
44
+ // Fallback: fast-glob puro ignorando node_modules
45
+ return fast_glob_1.default.sync([extGlob, rulesGlob, '**/LICENSE*'], {
46
+ cwd: targetDir,
47
+ ignore: ['**/node_modules/**', '**/dist/**', '**.git/**'],
48
+ absolute: true
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installGitHook = installGitHook;
7
+ exports.installGlobalGitHook = installGlobalGitHook;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const picocolors_1 = __importDefault(require("picocolors"));
12
+ const child_process_1 = require("child_process");
13
+ const HOOK_SCRIPT_LOCAL = '#!/usr/bin/env bash\n# AUM-IC Self-Healing Hook\n\naumic-sanitizer scan --from-hook --delta\nexit $?\n';
14
+ // Hook Global Inteligente (Corre AUM-IC y luego encadena con Husky/Lefthook si existen)
15
+ const HOOK_SCRIPT_GLOBAL = `#!/usr/bin/env bash
16
+ # AUM-IC Sentinel (Global)
17
+
18
+ aumic-sanitizer scan --from-hook --delta
19
+ AUM_EXIT=$?
20
+ if [ $AUM_EXIT -ne 0 ]; then
21
+ exit $AUM_EXIT
22
+ fi
23
+
24
+ # Delegar a hook local si existe (Respetar Husky / Lefthook)
25
+ if [ -x ".git/hooks/pre-commit" ]; then
26
+ .git/hooks/pre-commit "$@"
27
+ exit $?
28
+ fi
29
+ `;
30
+ // ─── Hook LOCAL ─────────────────────────────────────────────────────────────
31
+ function installGitHook(targetDir) {
32
+ const gitDir = path_1.default.join(targetDir, '.git');
33
+ if (!fs_1.default.existsSync(gitDir)) {
34
+ console.log(picocolors_1.default.red('[!] No se detectó un repositorio Git (.git) en esta ruta.'));
35
+ return;
36
+ }
37
+ const hooksDir = path_1.default.join(gitDir, 'hooks');
38
+ if (!fs_1.default.existsSync(hooksDir))
39
+ fs_1.default.mkdirSync(hooksDir, { recursive: true });
40
+ const hookFilePath = path_1.default.join(hooksDir, 'pre-commit');
41
+ fs_1.default.writeFileSync(hookFilePath, HOOK_SCRIPT_LOCAL, { encoding: 'utf8', mode: 0o755 });
42
+ console.log(picocolors_1.default.green('✔ Hook pre-commit LOCAL instalado exitosamente.'));
43
+ console.log(picocolors_1.default.cyan(' Ámbito: Solo este repositorio → ' + targetDir));
44
+ }
45
+ // ─── Hook GLOBAL (Verdadero: core.hooksPath) ──────────────────────────────────
46
+ function installGlobalGitHook() {
47
+ const globalHooksDir = path_1.default.join(os_1.default.homedir(), '.aumic-hooks');
48
+ fs_1.default.mkdirSync(globalHooksDir, { recursive: true });
49
+ const hookFilePath = path_1.default.join(globalHooksDir, 'pre-commit');
50
+ fs_1.default.writeFileSync(hookFilePath, HOOK_SCRIPT_GLOBAL, { encoding: 'utf8', mode: 0o755 });
51
+ try {
52
+ (0, child_process_1.execSync)(`git config --global core.hooksPath "${globalHooksDir}"`, { encoding: 'utf8' });
53
+ console.log(picocolors_1.default.green('✔ Hook pre-commit GLOBAL Absoluto instalado exitosamente.'));
54
+ console.log(picocolors_1.default.cyan(' Ruta central: ' + globalHooksDir));
55
+ console.log(picocolors_1.default.magenta(' ¡Atención! AUM-IC interceptará absolutamente todos los commits'));
56
+ console.log(picocolors_1.default.magenta(' en esta computadora de ahora en adelante (sin necesidad de init).'));
57
+ console.log(picocolors_1.default.gray(' (El diseño es seguro: no romperá repositorios que usen Husky).'));
58
+ }
59
+ catch (e) {
60
+ console.log(picocolors_1.default.red('[!] Error configurando core.hooksPath global.'));
61
+ }
62
+ // ─── Auto-Gitignore Global ───
63
+ try {
64
+ let globalIgnorePath = '';
65
+ try {
66
+ globalIgnorePath = (0, child_process_1.execSync)('git config --global core.excludesfile', { encoding: 'utf8' }).trim();
67
+ }
68
+ catch (_) { } // Aún no existe la configuración
69
+ if (!globalIgnorePath) {
70
+ globalIgnorePath = path_1.default.join(os_1.default.homedir(), '.gitignore_global');
71
+ (0, child_process_1.execSync)(`git config --global core.excludesfile "${globalIgnorePath}"`, { encoding: 'utf8' });
72
+ }
73
+ else if (globalIgnorePath.startsWith('~')) {
74
+ globalIgnorePath = globalIgnorePath.replace(/^~/, os_1.default.homedir());
75
+ }
76
+ if (fs_1.default.existsSync(globalIgnorePath)) {
77
+ const current = fs_1.default.readFileSync(globalIgnorePath, 'utf8');
78
+ if (!current.includes('aumic-history.json')) {
79
+ fs_1.default.appendFileSync(globalIgnorePath, '\n# AUM-IC Forensic Log\naumic-history.json\n', 'utf8');
80
+ }
81
+ }
82
+ else {
83
+ fs_1.default.writeFileSync(globalIgnorePath, '# AUM-IC Forensic Log\naumic-history.json\n', 'utf8');
84
+ }
85
+ console.log(picocolors_1.default.green('✔ AUM-IC adherido al .gitignore global del sistema.'));
86
+ }
87
+ catch (e) {
88
+ console.log(picocolors_1.default.yellow('⚠ No se pudo configurar el .gitignore global automáticamente.'));
89
+ }
90
+ }
@@ -0,0 +1,364 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.processFile = processFile;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const child_process_1 = require("child_process");
9
+ const path_1 = __importDefault(require("path"));
10
+ const parser_1 = require("@babel/parser");
11
+ const traverse = require('@babel/traverse').default || require('@babel/traverse');
12
+ const magic_string_1 = __importDefault(require("magic-string"));
13
+ const constants_1 = require("../1-atoms/constants");
14
+ const MojibakeEngine_1 = require("../2-molecules/MojibakeEngine");
15
+ const LanguageProfiler_1 = require("../2-molecules/LanguageProfiler");
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ // KERNEL DE REGLAS DE ENTROPIA
18
+ // Recibe un fragmento de texto YA AISLADO y aplica las transformaciones.
19
+ // El modo de smart quotes se decide fuera de esta funcion por el perfil.
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ function applyEntropyRules(raw, rules, profile, insideString = false // true = llamado desde un nodo AST o string-regex
22
+ ) {
23
+ let fixed = raw;
24
+ let count = 0;
25
+ if (rules.mojibake && profile.allowMojibake) {
26
+ const { fixed: f, count: c } = MojibakeEngine_1.MojibakeEngine.cure(fixed, profile.allowedMojibakeVectors);
27
+ if (c > 0) {
28
+ fixed = f;
29
+ count += c;
30
+ }
31
+ }
32
+ if (rules.emojis) {
33
+ fixed = fixed.replace(constants_1.EMOJI_PATTERN, (match) => {
34
+ const r = (0, constants_1.reverseEmojiMojibake)(match);
35
+ if (r !== match) {
36
+ count++;
37
+ return r;
38
+ }
39
+ return match;
40
+ });
41
+ }
42
+ if (rules.smartQuotes) {
43
+ const mode = profile.smartQuoteMode;
44
+ const canTouch = mode === 'global' || (insideString && (mode === 'ast-only' || mode === 'string-regex'));
45
+ if (canTouch) {
46
+ // Comillas dobles tipograficas — siempre seguras en cualquier contexto
47
+ const dq = (fixed.match(/[\u201C\u201D]/g) || []).length;
48
+ if (dq > 0) {
49
+ fixed = fixed.replace(/[\u201C\u201D]/g, '\x22');
50
+ count += dq;
51
+ }
52
+ // Comillas simples / apostrofes — SOLO si estamos dentro de un string aislado
53
+ // o en modo global (archivos planos donde no hay delimitadores de lenguaje)
54
+ if (insideString || mode === 'global') {
55
+ const sq = (fixed.match(/[\u2018\u2019]/g) || []).length;
56
+ if (sq > 0) {
57
+ fixed = fixed.replace(/[\u2018\u2019]/g, '\x27');
58
+ count += sq;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ if (rules.zeroWidth && profile.allowZeroWidth) {
64
+ const inv = /[\u200B\u200C\u200D\u202A-\u202E\u2066-\u2069]/g;
65
+ const c = (fixed.match(inv) || []).length;
66
+ if (c > 0) {
67
+ fixed = fixed.replace(inv, '');
68
+ count += c;
69
+ }
70
+ }
71
+ return { fixed, count };
72
+ }
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+ // PROCESADOR AST (JS / TS)
75
+ // Usa Babel para parsear el arbol sintactico y operar SOLO sobre nodos
76
+ // de tipo StringLiteral, TemplateLiteral.quasis y Comment.
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ async function processWithAST(filepath, content, rules, profile, audit, gitStage) {
79
+ let corruptions = 0;
80
+ try {
81
+ const ms = new magic_string_1.default(content);
82
+ const ast = (0, parser_1.parse)(content, {
83
+ sourceType: 'unambiguous',
84
+ errorRecovery: true,
85
+ plugins: ['jsx', 'typescript', 'decorators-legacy', 'classProperties'],
86
+ });
87
+ traverse(ast, {
88
+ StringLiteral({ node }) {
89
+ const inner = content.slice(node.start + 1, node.end - 1);
90
+ const { fixed, count } = applyEntropyRules(inner, rules, profile, true);
91
+ if (count > 0 && fixed !== inner) {
92
+ ms.overwrite(node.start + 1, node.end - 1, fixed);
93
+ corruptions += count;
94
+ }
95
+ },
96
+ TemplateLiteral({ node }) {
97
+ for (const quasi of node.quasis) {
98
+ const raw = quasi.value.raw;
99
+ const { fixed, count } = applyEntropyRules(raw, rules, profile, true);
100
+ if (count > 0 && fixed !== raw) {
101
+ const s = quasi.start + 1;
102
+ const e = quasi.end - 1;
103
+ if (s < e) {
104
+ ms.overwrite(s, e, fixed);
105
+ corruptions += count;
106
+ }
107
+ }
108
+ }
109
+ },
110
+ });
111
+ // Comentarios (en ast.comments, fuera del traversal estandar)
112
+ for (const comment of (ast.comments || [])) {
113
+ const raw = comment.value;
114
+ const { fixed, count } = applyEntropyRules(raw, rules, profile, true);
115
+ if (count > 0 && fixed !== raw) {
116
+ const s = comment.start + 2;
117
+ const e = comment.end - (comment.type === 'CommentBlock' ? 2 : 0);
118
+ if (s < e) {
119
+ ms.overwrite(s, e, fixed);
120
+ corruptions += count;
121
+ }
122
+ }
123
+ }
124
+ // Eradicador (solo si el perfil lo permite)
125
+ let finalContent = ms.toString();
126
+ if (rules.eradicator && profile.allowEradicator) {
127
+ const consoleRx = /^[ \t]*console\.(log|info|debug|warn)\(.*?\);?[ \t]*\r?\n?/gm;
128
+ const debugRx = /^[ \t]*debugger;[ \t]*\r?\n?/gm;
129
+ const todoRx = /^[ \t]*\/\/ ?TODO:.*?\r?\n?/gmi;
130
+ const cc = (finalContent.match(consoleRx) || []).length;
131
+ if (cc > 0) {
132
+ finalContent = finalContent.replace(consoleRx, '');
133
+ corruptions += cc;
134
+ }
135
+ const dc = (finalContent.match(debugRx) || []).length;
136
+ if (dc > 0) {
137
+ finalContent = finalContent.replace(debugRx, '');
138
+ corruptions += dc;
139
+ }
140
+ const tc = (finalContent.match(todoRx) || []).length;
141
+ if (tc > 0) {
142
+ finalContent = finalContent.replace(todoRx, '');
143
+ corruptions += tc;
144
+ }
145
+ }
146
+ // CRLF (nivel de archivo, no de nodo)
147
+ if (rules.crlf && profile.allowCRLF) {
148
+ const c = (finalContent.match(/\r\n/g) || []).length;
149
+ if (c > 0) {
150
+ finalContent = finalContent.replace(/\r\n/g, '\n');
151
+ corruptions += c;
152
+ }
153
+ }
154
+ if (corruptions > 0) {
155
+ if (!audit) {
156
+ await promises_1.default.writeFile(filepath, finalContent, 'utf8');
157
+ if (gitStage) {
158
+ try {
159
+ (0, child_process_1.execSync)(`git add "${filepath}"`, { cwd: path_1.default.dirname(filepath), stdio: 'ignore' });
160
+ }
161
+ catch (_) { }
162
+ }
163
+ }
164
+ return { filepath, corruptions, modified: true, originalContent: content };
165
+ }
166
+ return { filepath, corruptions: 0, modified: false };
167
+ }
168
+ catch (_parseError) {
169
+ // Fallback: el archivo no pudo parsearse — modo regex conservador
170
+ return processWithRegex(filepath, content, rules, profile, audit, gitStage);
171
+ }
172
+ }
173
+ // ─────────────────────────────────────────────────────────────────────────────
174
+ // PROCESADOR STRING-REGEX
175
+ // Para lenguajes sin AST disponible (PHP, Python, Ruby, C#, Java, Kotlin...).
176
+ // Usa regex para aislar el interior de strings segun la sintaxis del lenguaje
177
+ // y aplica las reglas solo en esos fragmentos.
178
+ // ─────────────────────────────────────────────────────────────────────────────
179
+ // Patterns por categoria de lenguaje para extraer el interior de strings
180
+ const STRING_PATTERNS = {
181
+ PHP: /(?<=")((?:[^"\\]|\\.)*?)(?=")|(?<=')((?:[^'\\]|\\.)*?)(?=')/g,
182
+ PYTHON: /"""([sS]*?)"""|'''([sS]*?)'''|"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'/g,
183
+ RUBY: /"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'/g,
184
+ CSHARP: /@"((?:[^"]|"")*)"|"((?:[^"\\]|\\.)*)"/g,
185
+ JAVA: /"((?:[^"\\]|\\.)*)"/g,
186
+ KOTLIN: /"""([sS]*?)"""|"((?:[^"\\]|\\.)*)"/g,
187
+ DART: /"""([sS]*?)"""|'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)"/g,
188
+ MARKUP: /(?<=[a-zA-Z-]\s*=\s*)"([^"]*)"/g,
189
+ DATA_XML: /(?<=[a-zA-Z-]\s*=\s*)"([^"]*)"/g,
190
+ VUE_SVELTE: /(?<=")((?:[^"\\]|\\.)*?)(?=")|(?<=')((?:[^'\\]|\\.)*?)(?=')/g,
191
+ };
192
+ async function processWithStringRegex(filepath, content, rules, profile, audit, gitStage) {
193
+ let modified = content;
194
+ let corruptions = 0;
195
+ const pat = STRING_PATTERNS[profile.category];
196
+ if (pat && rules.smartQuotes) {
197
+ // Reemplazar solo dentro de strings reconocidos por el pattern
198
+ modified = modified.replace(pat, (match) => {
199
+ const { fixed, count } = applyEntropyRules(match, rules, profile, true);
200
+ corruptions += count;
201
+ return fixed;
202
+ });
203
+ }
204
+ // Resto de reglas (mojibake, zeroWidth, CRLF) son byte-level y siempre seguros
205
+ if (rules.mojibake && profile.allowMojibake) {
206
+ const { fixed: f, count: c } = MojibakeEngine_1.MojibakeEngine.cure(modified, profile.allowedMojibakeVectors);
207
+ if (c > 0) {
208
+ modified = f;
209
+ corruptions += c;
210
+ }
211
+ }
212
+ // BOM estandar y fantasma
213
+ if (modified.charCodeAt(0) === 0xFEFF) {
214
+ modified = modified.slice(1);
215
+ corruptions++;
216
+ }
217
+ const ghostBom = (modified.match(/\u00EF\u00BB\u00BF/g) || []).length;
218
+ if (ghostBom > 0) {
219
+ modified = modified.replace(/\u00EF\u00BB\u00BF/g, '');
220
+ corruptions += ghostBom;
221
+ }
222
+ if (rules.zeroWidth && profile.allowZeroWidth) {
223
+ const inv = /[\u200B\u200C\u200D\u202A-\u202E\u2066-\u2069]/g;
224
+ const c = (modified.match(inv) || []).length;
225
+ if (c > 0) {
226
+ modified = modified.replace(inv, '');
227
+ corruptions += c;
228
+ }
229
+ if (profile.allowNBSP) {
230
+ const nb = (modified.match(/\u00A0/g) || []).length;
231
+ if (nb > 0) {
232
+ modified = modified.replace(/\u00A0/g, ' ');
233
+ corruptions += nb;
234
+ }
235
+ }
236
+ }
237
+ if (rules.crlf && profile.allowCRLF) {
238
+ const c = (modified.match(/\r\n/g) || []).length;
239
+ if (c > 0) {
240
+ modified = modified.replace(/\r\n/g, '\n');
241
+ corruptions += c;
242
+ }
243
+ }
244
+ if (modified !== content) {
245
+ if (!audit) {
246
+ await promises_1.default.writeFile(filepath, modified, 'utf8');
247
+ if (gitStage) {
248
+ try {
249
+ (0, child_process_1.execSync)(`git add "${filepath}"`, { cwd: path_1.default.dirname(filepath), stdio: 'ignore' });
250
+ }
251
+ catch (_) { }
252
+ }
253
+ }
254
+ return { filepath, corruptions, modified: true, originalContent: content };
255
+ }
256
+ return { filepath, corruptions: 0, modified: false };
257
+ }
258
+ // ─────────────────────────────────────────────────────────────────────────────
259
+ // PROCESADOR GLOBAL (Regex sin restricciones)
260
+ // Para archivos planos: Markdown, YAML, TOML, CSS, ENV.
261
+ // No hay sintaxis de lenguaje que pueda romperse.
262
+ // ─────────────────────────────────────────────────────────────────────────────
263
+ async function processWithRegex(filepath, content, rules, profile, audit, gitStage) {
264
+ let modified = content;
265
+ let corruptions = 0;
266
+ if (rules.mojibake && profile.allowMojibake) {
267
+ const { fixed: f, count: c } = MojibakeEngine_1.MojibakeEngine.cure(modified, profile.allowedMojibakeVectors);
268
+ if (c > 0) {
269
+ modified = f;
270
+ corruptions += c;
271
+ }
272
+ }
273
+ if (rules.emojis) {
274
+ modified = modified.replace(constants_1.EMOJI_PATTERN, (match) => {
275
+ const r = (0, constants_1.reverseEmojiMojibake)(match);
276
+ if (r !== match) {
277
+ corruptions++;
278
+ return r;
279
+ }
280
+ return match;
281
+ });
282
+ }
283
+ if (modified.charCodeAt(0) === 0xFEFF) {
284
+ modified = modified.slice(1);
285
+ corruptions++;
286
+ }
287
+ const ghostBom = (modified.match(/\u00EF\u00BB\u00BF/g) || []).length;
288
+ if (ghostBom > 0) {
289
+ modified = modified.replace(/\u00EF\u00BB\u00BF/g, '');
290
+ corruptions += ghostBom;
291
+ }
292
+ if (rules.smartQuotes && profile.smartQuoteMode === 'global') {
293
+ const { fixed, count } = applyEntropyRules(modified, rules, profile, true);
294
+ if (count > 0) {
295
+ modified = fixed;
296
+ corruptions += count;
297
+ }
298
+ }
299
+ if (rules.zeroWidth && profile.allowZeroWidth) {
300
+ const inv = /[\u200B\u200C\u200D\u202A-\u202E\u2066-\u2069]/g;
301
+ const c = (modified.match(inv) || []).length;
302
+ if (c > 0) {
303
+ modified = modified.replace(inv, '');
304
+ corruptions += c;
305
+ }
306
+ if (profile.allowNBSP) {
307
+ const nb = (modified.match(/\u00A0/g) || []).length;
308
+ if (nb > 0) {
309
+ modified = modified.replace(/\u00A0/g, ' ');
310
+ corruptions += nb;
311
+ }
312
+ }
313
+ }
314
+ if (rules.crlf && profile.allowCRLF) {
315
+ const c = (modified.match(/\r\n/g) || []).length;
316
+ if (c > 0) {
317
+ modified = modified.replace(/\r\n/g, '\n');
318
+ corruptions += c;
319
+ }
320
+ }
321
+ if (modified !== content) {
322
+ if (!audit) {
323
+ await promises_1.default.writeFile(filepath, modified, 'utf8');
324
+ if (gitStage) {
325
+ try {
326
+ (0, child_process_1.execSync)(`git add "${filepath}"`, { cwd: path_1.default.dirname(filepath), stdio: 'ignore' });
327
+ }
328
+ catch (_) { }
329
+ }
330
+ }
331
+ return { filepath, corruptions, modified: true, originalContent: content };
332
+ }
333
+ return { filepath, corruptions: 0, modified: false };
334
+ }
335
+ // ─────────────────────────────────────────────────────────────────────────────
336
+ // DISPATCHER PRINCIPAL
337
+ // Clasifica el archivo con LanguageProfiler y enruta al procesador correcto.
338
+ // ─────────────────────────────────────────────────────────────────────────────
339
+ async function processFile(filepath, options) {
340
+ if ((0, LanguageProfiler_1.isBinaryOrIgnored)(filepath)) {
341
+ return { filepath, corruptions: 0, modified: false };
342
+ }
343
+ try {
344
+ const content = await promises_1.default.readFile(filepath, 'utf8');
345
+ const rules = options.config.rules;
346
+ const profile = (0, LanguageProfiler_1.detectProfile)(filepath, content);
347
+ const audit = options.audit;
348
+ const git = options.gitStage ?? false;
349
+ // Archivos minificados: solo operaciones byte-level (mojibake + zeroWidth)
350
+ // Las reglas semanticas (smartQuotes, eradicator) quedan bloqueadas por el perfil.
351
+ if (profile.useAST) {
352
+ return processWithAST(filepath, content, rules, profile, audit, git);
353
+ }
354
+ else if (profile.smartQuoteMode === 'string-regex') {
355
+ return processWithStringRegex(filepath, content, rules, profile, audit, git);
356
+ }
357
+ else {
358
+ return processWithRegex(filepath, content, rules, profile, audit, git);
359
+ }
360
+ }
361
+ catch (_) {
362
+ return { filepath, corruptions: 0, modified: false };
363
+ }
364
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Sanitizer_1 = require("./Sanitizer");
4
+ module.exports = async function (args) {
5
+ return (0, Sanitizer_1.processFile)(args.filepath, args.options);
6
+ };