@saulwade/swl-ses 1.4.1 → 1.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.
Files changed (114) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +1 -1
  3. package/agentes/nemesis-auditor-swl.md +161 -161
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/comandos/swl/.evolved.json +22 -22
  6. package/comandos/swl/contribuir.md +233 -233
  7. package/comandos/swl/nemesis.md +122 -122
  8. package/gateway/lib/event-channel.js +191 -191
  9. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  10. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  11. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  12. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  13. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  14. package/habilidades/eval-framework/SKILL.md +212 -212
  15. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  16. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  17. package/habilidades/harness-claude-code/SKILL.md +299 -299
  18. package/habilidades/infra-github-actions/SKILL.md +166 -166
  19. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  20. package/habilidades/manejo-errores/.evolved.json +8 -8
  21. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  22. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  23. package/habilidades/patrones-python/SKILL.md +229 -229
  24. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  25. package/habilidades/planear-fase/SKILL.md +319 -319
  26. package/habilidades/release-semver/.evolved.json +8 -8
  27. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  28. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  29. package/habilidades/testing-python/SKILL.md +340 -340
  30. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  31. package/hooks/claudemd-bloat-detector.js +161 -161
  32. package/hooks/lib/agent-routing.js +107 -107
  33. package/hooks/lib/auto-consolidator.js +335 -335
  34. package/hooks/lib/error-classifier.js +308 -308
  35. package/hooks/lib/merkle-audit.js +96 -96
  36. package/hooks/lib/provenance-tracker.js +191 -191
  37. package/hooks/lib/rate-limit-tracker.js +253 -253
  38. package/hooks/lib/resource-quota.js +122 -122
  39. package/hooks/lib/retry-jitter.js +165 -165
  40. package/hooks/lib/security-net.js +201 -201
  41. package/hooks/lib/skill-auditor.js +588 -588
  42. package/hooks/lib/sync-status.js +228 -228
  43. package/hooks/lib/taint-tracker.js +107 -107
  44. package/hooks/lib/text-similarity.js +241 -241
  45. package/hooks/lib/toon-compressor.js +245 -245
  46. package/hooks/registro-turnos.js +209 -209
  47. package/hooks/sugerir-regenerar-inventario.js +170 -170
  48. package/hooks/validar-formato-post-subagente.js +140 -140
  49. package/hooks/validar-memoria-hook.js +218 -218
  50. package/instintos/prompt-appendices.yaml +57 -57
  51. package/manifiestos/agent-output-schemas.json +57 -57
  52. package/manifiestos/modulos.json +11 -6
  53. package/manifiestos/perfiles.json +2 -1
  54. package/manifiestos/skills-lock.json +1114 -1114
  55. package/package.json +1 -1
  56. package/plantillas/auditor-veto-template.md +105 -105
  57. package/plantillas/github-workflows/README.md +47 -47
  58. package/plantillas/github-workflows/release-please.yml +44 -44
  59. package/plantillas/github-workflows/swl-ci.yml +107 -107
  60. package/plantillas/github-workflows/swl-security.yml +51 -51
  61. package/plugin.json +9 -1
  62. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  63. package/reglas/arreglar-al-detectar.md +147 -147
  64. package/reglas/fragmentos-compartidos.md +152 -152
  65. package/reglas/harness-claude-code.md +213 -213
  66. package/reglas/usar-context7.md +226 -226
  67. package/schemas/diary-entry.schema.json +80 -80
  68. package/scripts/audit-tools/audit-history.js +330 -330
  69. package/scripts/audit-tools/bundle-tracker.js +290 -290
  70. package/scripts/audit-tools/canary-monitor.js +352 -352
  71. package/scripts/audit-tools/code-profiler.js +605 -605
  72. package/scripts/audit-tools/dep-doctor.js +320 -320
  73. package/scripts/audit-tools/env-validator.js +206 -206
  74. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  75. package/scripts/audit-tools/lib/output.js +23 -23
  76. package/scripts/audit-tools/migration-checker.js +392 -392
  77. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  78. package/scripts/benchmark-memoria.js +167 -167
  79. package/scripts/configurar-branch-protection.js +418 -418
  80. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  81. package/scripts/field-report.js +199 -199
  82. package/scripts/generar-checklists-consolidados.js +273 -273
  83. package/scripts/generar-inventario.js +420 -420
  84. package/scripts/generar-matriz-lenguajes.js +271 -271
  85. package/scripts/lib/artefactos-python.js +43 -43
  86. package/scripts/lib/benchmark-metrics.js +160 -160
  87. package/scripts/lib/budget-enforcer.js +252 -252
  88. package/scripts/lib/configurar-ci.js +380 -380
  89. package/scripts/lib/contadores-inventario.js +217 -217
  90. package/scripts/lib/detectar-stack-detallado.js +307 -307
  91. package/scripts/lib/diary-entry.js +234 -234
  92. package/scripts/lib/eval-metrics-store.js +218 -218
  93. package/scripts/lib/eval-quality.js +171 -171
  94. package/scripts/lib/eval-schemas.js +144 -144
  95. package/scripts/lib/eval-self-correct.js +106 -106
  96. package/scripts/lib/eval-validator.js +185 -185
  97. package/scripts/lib/jaccard-similarity.js +98 -98
  98. package/scripts/lib/longmemeval-runner.js +125 -125
  99. package/scripts/lib/manifiestos.js +42 -1
  100. package/scripts/lib/npm-version.js +261 -261
  101. package/scripts/lib/paquetes-conocidos.js +50 -50
  102. package/scripts/lib/prompt-builder.js +264 -264
  103. package/scripts/lib/rrf-fusion.js +175 -175
  104. package/scripts/lib/scoring-instintos.js +277 -277
  105. package/scripts/lib/semantic-search.js +252 -252
  106. package/scripts/limpiar-artefactos-python.js +131 -131
  107. package/scripts/mcp-server/README.md +128 -128
  108. package/scripts/mcp-server/handlers.js +206 -206
  109. package/scripts/migrar-csv-a-array.js +168 -168
  110. package/scripts/migrar-fase-dominio.js +201 -201
  111. package/scripts/publicar.js +511 -511
  112. package/scripts/run-eval.js +141 -141
  113. package/scripts/validar-manifest.js +231 -195
  114. package/scripts/validar-userland-vacio.js +110 -110
@@ -1,320 +1,320 @@
1
- // Adaptado de temp/ultraship-main/tools/dep-doctor.mjs bajo MIT License
2
- // Fuente: Houseofmvps/ultraship (https://github.com/Houseofmvps/ultraship)
3
- 'use strict';
4
-
5
- const { readFileSync, existsSync, readdirSync, statSync } = require('fs');
6
- const { join, relative, extname } = require('path');
7
- const { outputJSON, outputError } = require('./lib/output');
8
-
9
- const SKIP_DIRS = new Set([
10
- 'node_modules', '.git', 'dist', 'build', '.next', 'coverage',
11
- '__pycache__', '.cache', 'venv', '.venv', 'target', 'vendor',
12
- '.tox', 'eggs', '.eggs', 'htmlcov', '.mypy_cache', '.pytest_cache',
13
- ]);
14
-
15
- const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.vue', '.svelte']);
16
-
17
- /**
18
- * Recorre un directorio buscando archivos de código.
19
- * @param {string} dir
20
- * @returns {string[]}
21
- */
22
- function findCodeFiles(dir) {
23
- const files = [];
24
- function walk(d) {
25
- try {
26
- for (const entry of readdirSync(d)) {
27
- if (entry.startsWith('.') || SKIP_DIRS.has(entry)) continue;
28
- const p = join(d, entry);
29
- try {
30
- const s = statSync(p);
31
- if (s.isDirectory()) walk(p);
32
- else if (CODE_EXTS.has(extname(entry).toLowerCase())) files.push(p);
33
- } catch { /* skip */ }
34
- }
35
- } catch { /* skip */ }
36
- }
37
- walk(dir);
38
- return files;
39
- }
40
-
41
- // Dependencias usadas implícitamente (herramientas de build, definiciones de tipos)
42
- const IMPLICIT_DEPS = new Set([
43
- 'typescript', '@types/node', '@types/react', '@types/react-dom',
44
- 'eslint', 'prettier', 'vitest', 'jest', 'mocha',
45
- 'tailwindcss', 'autoprefixer', 'postcss',
46
- 'drizzle-kit', 'prisma',
47
- '@vitejs/plugin-react', 'vite',
48
- 'tsx', 'ts-node', 'nodemon',
49
- 'husky', 'lint-staged', 'commitlint',
50
- 'dotenv', 'cross-env',
51
- ]);
52
-
53
- // Paquetes cuyo nombre de import difiere del nombre en package.json
54
- const IMPORT_ALIASES = {
55
- 'next': ['next', 'next/'],
56
- '@hono/node-server': ['@hono/node-server'],
57
- 'drizzle-orm': ['drizzle-orm'],
58
- '@neondatabase/serverless': ['@neondatabase/serverless'],
59
- 'better-auth': ['better-auth'],
60
- '@anthropic-ai/sdk': ['@anthropic-ai/sdk', 'anthropic'],
61
- '@clerk/nextjs': ['@clerk/nextjs'],
62
- };
63
-
64
- /**
65
- * Detecta dependencias no utilizadas en el directorio dado.
66
- * @param {string} dir
67
- * @returns {{ unused: object[], total_deps: number, total_dev_deps: number, error?: string }}
68
- */
69
- function detectUnusedDeps(dir) {
70
- const pkgPath = join(dir, 'package.json');
71
- if (!existsSync(pkgPath)) return { unused: [], error: 'No se encontró package.json', total_deps: 0, total_dev_deps: 0 };
72
-
73
- let pkg;
74
- try {
75
- pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
76
- } catch (e) {
77
- return { unused: [], error: `package.json inválido: ${e.message}`, total_deps: 0, total_dev_deps: 0 };
78
- }
79
-
80
- const prodDeps = Object.keys(pkg.dependencies || {});
81
- const devDeps = Object.keys(pkg.devDependencies || {});
82
-
83
- const codeFiles = findCodeFiles(dir);
84
- const fileContents = new Map();
85
- let allCode = '';
86
-
87
- for (const file of codeFiles) {
88
- try {
89
- const code = readFileSync(file, 'utf8');
90
- fileContents.set(file, code);
91
- allCode += code + '\n';
92
- } catch { /* skip */ }
93
- }
94
-
95
- // Incluir archivos de configuración en la raíz
96
- const configFiles = [
97
- 'vite.config.ts', 'vite.config.js', 'next.config.js', 'next.config.mjs',
98
- 'tailwind.config.js', 'tailwind.config.ts', 'postcss.config.js', 'postcss.config.cjs',
99
- 'drizzle.config.ts', 'drizzle.config.js', '.eslintrc.js', '.eslintrc.json',
100
- 'tsconfig.json', 'jest.config.js', 'vitest.config.ts',
101
- ];
102
- for (const cf of configFiles) {
103
- const p = join(dir, cf);
104
- if (existsSync(p)) {
105
- try {
106
- const code = readFileSync(p, 'utf8');
107
- fileContents.set(p, code);
108
- allCode += code + '\n';
109
- } catch { /* skip */ }
110
- }
111
- }
112
-
113
- // Construir grafo de imports: objetivos de import locales normalizados
114
- const allImportTargets = new Set();
115
- for (const [, code] of fileContents) {
116
- const importRegex = /(?:from\s+|require\s*\(\s*)['"]([^'"]+)['"]/g;
117
- let m;
118
- while ((m = importRegex.exec(code)) !== null) {
119
- const target = m[1];
120
- if (target.startsWith('.') || target.startsWith('@/') || target.startsWith('~/')) {
121
- const clean = target.replace(/^(?:\.\/|@\/|~\/)/, '').replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
122
- allImportTargets.add(clean);
123
- allImportTargets.add(clean.replace(/\/index$/, ''));
124
- }
125
- }
126
- }
127
-
128
- function isFileReachable(filePath) {
129
- const rel = relative(dir, filePath).replace(/\\/g, '/');
130
- const noExt = rel.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
131
- const noIndex = noExt.replace(/\/index$/, '');
132
- const basename = filePath.split(/[/\\]/).pop();
133
- if (['page.tsx', 'page.ts', 'page.jsx', 'layout.tsx', 'layout.ts',
134
- 'main.tsx', 'main.ts', 'App.tsx', 'App.ts', 'index.tsx', 'index.ts',
135
- 'index.js', 'main.js'].includes(basename)) return true;
136
- if (rel.includes('/routes/') || rel.includes('/api/') || rel.includes('/pages/')) return true;
137
- if (rel.includes('config')) return true;
138
- for (const target of allImportTargets) {
139
- if (target === noExt || target === noIndex ||
140
- target === `src/${noExt}` || target === `src/${noIndex}` ||
141
- noExt.endsWith(`/${target}`) || noIndex.endsWith(`/${target}`)) return true;
142
- }
143
- return false;
144
- }
145
-
146
- function findDepImportFiles(dep) {
147
- const aliases = IMPORT_ALIASES[dep] || [dep];
148
- const files = [];
149
- for (const [filePath, code] of fileContents) {
150
- for (const alias of aliases) {
151
- if (code.includes(`'${alias}'`) || code.includes(`"${alias}"`) ||
152
- code.includes(`'${alias}/`) || code.includes(`"${alias}/`)) {
153
- files.push(filePath);
154
- break;
155
- }
156
- }
157
- if (dep.startsWith('@') && !files.includes(filePath)) {
158
- if (code.includes(`'${dep}'`) || code.includes(`"${dep}"`) ||
159
- code.includes(`'${dep}/`) || code.includes(`"${dep}/`)) {
160
- files.push(filePath);
161
- }
162
- }
163
- }
164
- return files;
165
- }
166
-
167
- const unused = [];
168
-
169
- function isUsed(dep) {
170
- if (IMPLICIT_DEPS.has(dep)) return true;
171
- if (dep.startsWith('@types/')) return true;
172
- const aliases = IMPORT_ALIASES[dep] || [dep];
173
- for (const alias of aliases) {
174
- if (allCode.includes(`'${alias}'`) || allCode.includes(`"${alias}"`)) return true;
175
- if (allCode.includes(`'${alias}/`) || allCode.includes(`"${alias}/`)) return true;
176
- if (allCode.includes(`require('${alias}')`) || allCode.includes(`require("${alias}")`)) return true;
177
- }
178
- if (dep.startsWith('@')) {
179
- if (allCode.includes(`'${dep}'`) || allCode.includes(`"${dep}"`)) return true;
180
- if (allCode.includes(`'${dep}/`) || allCode.includes(`"${dep}/`)) return true;
181
- }
182
- return false;
183
- }
184
-
185
- function isDeadCode(dep) {
186
- const importFiles = findDepImportFiles(dep);
187
- if (importFiles.length === 0) return true;
188
- return importFiles.every(f => !isFileReachable(f));
189
- }
190
-
191
- for (const dep of prodDeps) {
192
- if (!isUsed(dep)) {
193
- unused.push({ name: dep, type: 'production', severity: 'high', message: `"${dep}" está en dependencies pero no se importa en ningún lugar — eliminar para reducir el tamaño de instalación` });
194
- } else if (isDeadCode(dep)) {
195
- unused.push({ name: dep, type: 'production', severity: 'medium', message: `"${dep}" solo se importa en archivos no alcanzables — eliminar si esos componentes no se necesitan` });
196
- }
197
- }
198
-
199
- for (const dep of devDeps) {
200
- if (!isUsed(dep)) {
201
- unused.push({ name: dep, type: 'devDependency', severity: 'low', message: `"${dep}" está en devDependencies pero no se referencia — puede eliminarse` });
202
- }
203
- }
204
-
205
- return { unused, total_deps: prodDeps.length, total_dev_deps: devDeps.length };
206
- }
207
-
208
- /**
209
- * Detecta dependencias posiblemente desactualizadas.
210
- * @param {string} dir
211
- * @returns {{ outdated: object[] }}
212
- */
213
- function detectOutdated(dir) {
214
- const pkgPath = join(dir, 'package.json');
215
- if (!existsSync(pkgPath)) return { outdated: [] };
216
-
217
- let pkg;
218
- try {
219
- pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
220
- } catch {
221
- return { outdated: [] };
222
- }
223
-
224
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
225
- const findings = [];
226
-
227
- for (const [name, version] of Object.entries(allDeps)) {
228
- if (typeof version !== 'string') continue;
229
- const v = version.trim();
230
- if (v.startsWith('file:') || v.startsWith('link:') || v.startsWith('workspace:') || v === '*' || v === 'latest') continue;
231
-
232
- // Versión anclada exacta (sin ^ ni ~)
233
- if (/^\d/.test(v)) {
234
- findings.push({
235
- name,
236
- version: v,
237
- severity: 'low',
238
- issue: 'anclada',
239
- message: `"${name}@${v}" está anclado a versión exacta — usar ^${v} para recibir actualizaciones de parches`,
240
- });
241
- }
242
-
243
- // Versiones mayores muy antiguas de paquetes conocidos
244
- const majorMatch = v.match(/\d+/);
245
- if (majorMatch) {
246
- const major = parseInt(majorMatch[0], 10);
247
- const knownOld = {
248
- 'react': 18, 'next': 14, 'vue': 3, 'express': 4, 'hono': 4,
249
- 'typescript': 5, 'vite': 5, 'tailwindcss': 3, 'eslint': 9,
250
- 'drizzle-orm': 0, 'prisma': 5, 'zod': 3,
251
- };
252
- if (knownOld[name] !== undefined && major < knownOld[name] - 1) {
253
- findings.push({
254
- name,
255
- version: v,
256
- severity: 'medium',
257
- issue: 'mayor_desactualizado',
258
- message: `"${name}@${v}" tiene ${knownOld[name] - major}+ versiones mayores de retraso — considerar actualización`,
259
- });
260
- }
261
- }
262
- }
263
-
264
- return { outdated: findings };
265
- }
266
-
267
- function main() {
268
- const dir = process.argv[2];
269
- if (!dir) {
270
- outputError('Uso: node dep-doctor.js <directorio-proyecto>');
271
- process.exit(0);
272
- }
273
-
274
- if (!existsSync(dir)) {
275
- outputError(`Ruta no encontrada: ${dir}`);
276
- process.exit(0);
277
- }
278
-
279
- const unusedResult = detectUnusedDeps(dir);
280
- const outdatedResult = detectOutdated(dir);
281
-
282
- outputJSON({
283
- success: true,
284
- packages_scanned: 1,
285
- total_production_deps: unusedResult.total_deps || 0,
286
- total_dev_deps: unusedResult.total_dev_deps || 0,
287
- unused_count: unusedResult.unused.length,
288
- outdated_count: outdatedResult.outdated.length,
289
- total_findings: unusedResult.unused.length + outdatedResult.outdated.length,
290
- unused: unusedResult.unused,
291
- outdated: outdatedResult.outdated,
292
- });
293
- }
294
-
295
- main();
296
-
297
- module.exports = { detectUnusedDeps, detectOutdated, findCodeFiles, IMPLICIT_DEPS };
298
-
299
- /**
300
- * @complemento Skill("dependencias-auditoria")
301
- *
302
- * dep-doctor.js realiza **análisis estático** de dependencias:
303
- * - Detecta dependencias declaradas pero nunca importadas en el código fuente.
304
- * - Detecta versiones ancladas sin `^`/`~` y versiones mayores muy desactualizadas.
305
- * - Sin ejecución de shell, sin red. Seguro para usar en cualquier entorno.
306
- * - Rápido (solo lectura de archivos locales).
307
- *
308
- * Skill("dependencias-auditoria") realiza **auditoría de seguridad profunda**:
309
- * - Consulta bases de datos CVE reales (pip-audit, npm audit, trivy, grype).
310
- * - Detecta licencias incompatibles (pip-licenses, license-checker).
311
- * - Identifica dependencias abandonadas con fecha de último commit.
312
- * - Requiere: red (acceso a advisories), shell (pip-audit, npm, trivy instalados).
313
- * - Más lento pero definitivo en vulnerabilidades conocidas.
314
- *
315
- * Flujo recomendado:
316
- * 1. Ejecutar `dep-doctor.js` primero (rápido, sin dependencias externas).
317
- * → Eliminar dependencias no usadas reduce la superficie de ataque.
318
- * 2. Luego invocar `Skill("dependencias-auditoria")` para CVEs y licencias.
319
- * → Asegura que las dependencias restantes no tienen vulnerabilidades conocidas.
320
- */
1
+ // Adaptado de temp/ultraship-main/tools/dep-doctor.mjs bajo MIT License
2
+ // Fuente: Houseofmvps/ultraship (https://github.com/Houseofmvps/ultraship)
3
+ 'use strict';
4
+
5
+ const { readFileSync, existsSync, readdirSync, statSync } = require('fs');
6
+ const { join, relative, extname } = require('path');
7
+ const { outputJSON, outputError } = require('./lib/output');
8
+
9
+ const SKIP_DIRS = new Set([
10
+ 'node_modules', '.git', 'dist', 'build', '.next', 'coverage',
11
+ '__pycache__', '.cache', 'venv', '.venv', 'target', 'vendor',
12
+ '.tox', 'eggs', '.eggs', 'htmlcov', '.mypy_cache', '.pytest_cache',
13
+ ]);
14
+
15
+ const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.vue', '.svelte']);
16
+
17
+ /**
18
+ * Recorre un directorio buscando archivos de código.
19
+ * @param {string} dir
20
+ * @returns {string[]}
21
+ */
22
+ function findCodeFiles(dir) {
23
+ const files = [];
24
+ function walk(d) {
25
+ try {
26
+ for (const entry of readdirSync(d)) {
27
+ if (entry.startsWith('.') || SKIP_DIRS.has(entry)) continue;
28
+ const p = join(d, entry);
29
+ try {
30
+ const s = statSync(p);
31
+ if (s.isDirectory()) walk(p);
32
+ else if (CODE_EXTS.has(extname(entry).toLowerCase())) files.push(p);
33
+ } catch { /* skip */ }
34
+ }
35
+ } catch { /* skip */ }
36
+ }
37
+ walk(dir);
38
+ return files;
39
+ }
40
+
41
+ // Dependencias usadas implícitamente (herramientas de build, definiciones de tipos)
42
+ const IMPLICIT_DEPS = new Set([
43
+ 'typescript', '@types/node', '@types/react', '@types/react-dom',
44
+ 'eslint', 'prettier', 'vitest', 'jest', 'mocha',
45
+ 'tailwindcss', 'autoprefixer', 'postcss',
46
+ 'drizzle-kit', 'prisma',
47
+ '@vitejs/plugin-react', 'vite',
48
+ 'tsx', 'ts-node', 'nodemon',
49
+ 'husky', 'lint-staged', 'commitlint',
50
+ 'dotenv', 'cross-env',
51
+ ]);
52
+
53
+ // Paquetes cuyo nombre de import difiere del nombre en package.json
54
+ const IMPORT_ALIASES = {
55
+ 'next': ['next', 'next/'],
56
+ '@hono/node-server': ['@hono/node-server'],
57
+ 'drizzle-orm': ['drizzle-orm'],
58
+ '@neondatabase/serverless': ['@neondatabase/serverless'],
59
+ 'better-auth': ['better-auth'],
60
+ '@anthropic-ai/sdk': ['@anthropic-ai/sdk', 'anthropic'],
61
+ '@clerk/nextjs': ['@clerk/nextjs'],
62
+ };
63
+
64
+ /**
65
+ * Detecta dependencias no utilizadas en el directorio dado.
66
+ * @param {string} dir
67
+ * @returns {{ unused: object[], total_deps: number, total_dev_deps: number, error?: string }}
68
+ */
69
+ function detectUnusedDeps(dir) {
70
+ const pkgPath = join(dir, 'package.json');
71
+ if (!existsSync(pkgPath)) return { unused: [], error: 'No se encontró package.json', total_deps: 0, total_dev_deps: 0 };
72
+
73
+ let pkg;
74
+ try {
75
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
76
+ } catch (e) {
77
+ return { unused: [], error: `package.json inválido: ${e.message}`, total_deps: 0, total_dev_deps: 0 };
78
+ }
79
+
80
+ const prodDeps = Object.keys(pkg.dependencies || {});
81
+ const devDeps = Object.keys(pkg.devDependencies || {});
82
+
83
+ const codeFiles = findCodeFiles(dir);
84
+ const fileContents = new Map();
85
+ let allCode = '';
86
+
87
+ for (const file of codeFiles) {
88
+ try {
89
+ const code = readFileSync(file, 'utf8');
90
+ fileContents.set(file, code);
91
+ allCode += code + '\n';
92
+ } catch { /* skip */ }
93
+ }
94
+
95
+ // Incluir archivos de configuración en la raíz
96
+ const configFiles = [
97
+ 'vite.config.ts', 'vite.config.js', 'next.config.js', 'next.config.mjs',
98
+ 'tailwind.config.js', 'tailwind.config.ts', 'postcss.config.js', 'postcss.config.cjs',
99
+ 'drizzle.config.ts', 'drizzle.config.js', '.eslintrc.js', '.eslintrc.json',
100
+ 'tsconfig.json', 'jest.config.js', 'vitest.config.ts',
101
+ ];
102
+ for (const cf of configFiles) {
103
+ const p = join(dir, cf);
104
+ if (existsSync(p)) {
105
+ try {
106
+ const code = readFileSync(p, 'utf8');
107
+ fileContents.set(p, code);
108
+ allCode += code + '\n';
109
+ } catch { /* skip */ }
110
+ }
111
+ }
112
+
113
+ // Construir grafo de imports: objetivos de import locales normalizados
114
+ const allImportTargets = new Set();
115
+ for (const [, code] of fileContents) {
116
+ const importRegex = /(?:from\s+|require\s*\(\s*)['"]([^'"]+)['"]/g;
117
+ let m;
118
+ while ((m = importRegex.exec(code)) !== null) {
119
+ const target = m[1];
120
+ if (target.startsWith('.') || target.startsWith('@/') || target.startsWith('~/')) {
121
+ const clean = target.replace(/^(?:\.\/|@\/|~\/)/, '').replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
122
+ allImportTargets.add(clean);
123
+ allImportTargets.add(clean.replace(/\/index$/, ''));
124
+ }
125
+ }
126
+ }
127
+
128
+ function isFileReachable(filePath) {
129
+ const rel = relative(dir, filePath).replace(/\\/g, '/');
130
+ const noExt = rel.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
131
+ const noIndex = noExt.replace(/\/index$/, '');
132
+ const basename = filePath.split(/[/\\]/).pop();
133
+ if (['page.tsx', 'page.ts', 'page.jsx', 'layout.tsx', 'layout.ts',
134
+ 'main.tsx', 'main.ts', 'App.tsx', 'App.ts', 'index.tsx', 'index.ts',
135
+ 'index.js', 'main.js'].includes(basename)) return true;
136
+ if (rel.includes('/routes/') || rel.includes('/api/') || rel.includes('/pages/')) return true;
137
+ if (rel.includes('config')) return true;
138
+ for (const target of allImportTargets) {
139
+ if (target === noExt || target === noIndex ||
140
+ target === `src/${noExt}` || target === `src/${noIndex}` ||
141
+ noExt.endsWith(`/${target}`) || noIndex.endsWith(`/${target}`)) return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ function findDepImportFiles(dep) {
147
+ const aliases = IMPORT_ALIASES[dep] || [dep];
148
+ const files = [];
149
+ for (const [filePath, code] of fileContents) {
150
+ for (const alias of aliases) {
151
+ if (code.includes(`'${alias}'`) || code.includes(`"${alias}"`) ||
152
+ code.includes(`'${alias}/`) || code.includes(`"${alias}/`)) {
153
+ files.push(filePath);
154
+ break;
155
+ }
156
+ }
157
+ if (dep.startsWith('@') && !files.includes(filePath)) {
158
+ if (code.includes(`'${dep}'`) || code.includes(`"${dep}"`) ||
159
+ code.includes(`'${dep}/`) || code.includes(`"${dep}/`)) {
160
+ files.push(filePath);
161
+ }
162
+ }
163
+ }
164
+ return files;
165
+ }
166
+
167
+ const unused = [];
168
+
169
+ function isUsed(dep) {
170
+ if (IMPLICIT_DEPS.has(dep)) return true;
171
+ if (dep.startsWith('@types/')) return true;
172
+ const aliases = IMPORT_ALIASES[dep] || [dep];
173
+ for (const alias of aliases) {
174
+ if (allCode.includes(`'${alias}'`) || allCode.includes(`"${alias}"`)) return true;
175
+ if (allCode.includes(`'${alias}/`) || allCode.includes(`"${alias}/`)) return true;
176
+ if (allCode.includes(`require('${alias}')`) || allCode.includes(`require("${alias}")`)) return true;
177
+ }
178
+ if (dep.startsWith('@')) {
179
+ if (allCode.includes(`'${dep}'`) || allCode.includes(`"${dep}"`)) return true;
180
+ if (allCode.includes(`'${dep}/`) || allCode.includes(`"${dep}/`)) return true;
181
+ }
182
+ return false;
183
+ }
184
+
185
+ function isDeadCode(dep) {
186
+ const importFiles = findDepImportFiles(dep);
187
+ if (importFiles.length === 0) return true;
188
+ return importFiles.every(f => !isFileReachable(f));
189
+ }
190
+
191
+ for (const dep of prodDeps) {
192
+ if (!isUsed(dep)) {
193
+ unused.push({ name: dep, type: 'production', severity: 'high', message: `"${dep}" está en dependencies pero no se importa en ningún lugar — eliminar para reducir el tamaño de instalación` });
194
+ } else if (isDeadCode(dep)) {
195
+ unused.push({ name: dep, type: 'production', severity: 'medium', message: `"${dep}" solo se importa en archivos no alcanzables — eliminar si esos componentes no se necesitan` });
196
+ }
197
+ }
198
+
199
+ for (const dep of devDeps) {
200
+ if (!isUsed(dep)) {
201
+ unused.push({ name: dep, type: 'devDependency', severity: 'low', message: `"${dep}" está en devDependencies pero no se referencia — puede eliminarse` });
202
+ }
203
+ }
204
+
205
+ return { unused, total_deps: prodDeps.length, total_dev_deps: devDeps.length };
206
+ }
207
+
208
+ /**
209
+ * Detecta dependencias posiblemente desactualizadas.
210
+ * @param {string} dir
211
+ * @returns {{ outdated: object[] }}
212
+ */
213
+ function detectOutdated(dir) {
214
+ const pkgPath = join(dir, 'package.json');
215
+ if (!existsSync(pkgPath)) return { outdated: [] };
216
+
217
+ let pkg;
218
+ try {
219
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
220
+ } catch {
221
+ return { outdated: [] };
222
+ }
223
+
224
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
225
+ const findings = [];
226
+
227
+ for (const [name, version] of Object.entries(allDeps)) {
228
+ if (typeof version !== 'string') continue;
229
+ const v = version.trim();
230
+ if (v.startsWith('file:') || v.startsWith('link:') || v.startsWith('workspace:') || v === '*' || v === 'latest') continue;
231
+
232
+ // Versión anclada exacta (sin ^ ni ~)
233
+ if (/^\d/.test(v)) {
234
+ findings.push({
235
+ name,
236
+ version: v,
237
+ severity: 'low',
238
+ issue: 'anclada',
239
+ message: `"${name}@${v}" está anclado a versión exacta — usar ^${v} para recibir actualizaciones de parches`,
240
+ });
241
+ }
242
+
243
+ // Versiones mayores muy antiguas de paquetes conocidos
244
+ const majorMatch = v.match(/\d+/);
245
+ if (majorMatch) {
246
+ const major = parseInt(majorMatch[0], 10);
247
+ const knownOld = {
248
+ 'react': 18, 'next': 14, 'vue': 3, 'express': 4, 'hono': 4,
249
+ 'typescript': 5, 'vite': 5, 'tailwindcss': 3, 'eslint': 9,
250
+ 'drizzle-orm': 0, 'prisma': 5, 'zod': 3,
251
+ };
252
+ if (knownOld[name] !== undefined && major < knownOld[name] - 1) {
253
+ findings.push({
254
+ name,
255
+ version: v,
256
+ severity: 'medium',
257
+ issue: 'mayor_desactualizado',
258
+ message: `"${name}@${v}" tiene ${knownOld[name] - major}+ versiones mayores de retraso — considerar actualización`,
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ return { outdated: findings };
265
+ }
266
+
267
+ function main() {
268
+ const dir = process.argv[2];
269
+ if (!dir) {
270
+ outputError('Uso: node dep-doctor.js <directorio-proyecto>');
271
+ process.exit(0);
272
+ }
273
+
274
+ if (!existsSync(dir)) {
275
+ outputError(`Ruta no encontrada: ${dir}`);
276
+ process.exit(0);
277
+ }
278
+
279
+ const unusedResult = detectUnusedDeps(dir);
280
+ const outdatedResult = detectOutdated(dir);
281
+
282
+ outputJSON({
283
+ success: true,
284
+ packages_scanned: 1,
285
+ total_production_deps: unusedResult.total_deps || 0,
286
+ total_dev_deps: unusedResult.total_dev_deps || 0,
287
+ unused_count: unusedResult.unused.length,
288
+ outdated_count: outdatedResult.outdated.length,
289
+ total_findings: unusedResult.unused.length + outdatedResult.outdated.length,
290
+ unused: unusedResult.unused,
291
+ outdated: outdatedResult.outdated,
292
+ });
293
+ }
294
+
295
+ main();
296
+
297
+ module.exports = { detectUnusedDeps, detectOutdated, findCodeFiles, IMPLICIT_DEPS };
298
+
299
+ /**
300
+ * @complemento Skill("dependencias-auditoria")
301
+ *
302
+ * dep-doctor.js realiza **análisis estático** de dependencias:
303
+ * - Detecta dependencias declaradas pero nunca importadas en el código fuente.
304
+ * - Detecta versiones ancladas sin `^`/`~` y versiones mayores muy desactualizadas.
305
+ * - Sin ejecución de shell, sin red. Seguro para usar en cualquier entorno.
306
+ * - Rápido (solo lectura de archivos locales).
307
+ *
308
+ * Skill("dependencias-auditoria") realiza **auditoría de seguridad profunda**:
309
+ * - Consulta bases de datos CVE reales (pip-audit, npm audit, trivy, grype).
310
+ * - Detecta licencias incompatibles (pip-licenses, license-checker).
311
+ * - Identifica dependencias abandonadas con fecha de último commit.
312
+ * - Requiere: red (acceso a advisories), shell (pip-audit, npm, trivy instalados).
313
+ * - Más lento pero definitivo en vulnerabilidades conocidas.
314
+ *
315
+ * Flujo recomendado:
316
+ * 1. Ejecutar `dep-doctor.js` primero (rápido, sin dependencias externas).
317
+ * → Eliminar dependencias no usadas reduce la superficie de ataque.
318
+ * 2. Luego invocar `Skill("dependencias-auditoria")` para CVEs y licencias.
319
+ * → Asegura que las dependencias restantes no tienen vulnerabilidades conocidas.
320
+ */