@saulwade/swl-ses 1.4.0 → 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.
- package/CLAUDE.md +4 -3
- package/README.md +15 -14
- package/agentes/nemesis-auditor-swl.md +161 -0
- package/bin/swl-mcp-server.js +187 -187
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +122 -0
- package/comandos/swl/salud.md +34 -0
- package/comandos/swl/verificar.md +45 -0
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/web-fetcher-routing/SKILL.md +75 -0
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -0
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +41 -6
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +30 -9
- package/package.json +2 -2
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +10 -2
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/audit-tools/audit-history.js +330 -0
- package/scripts/audit-tools/bundle-tracker.js +290 -0
- package/scripts/audit-tools/canary-monitor.js +352 -0
- package/scripts/audit-tools/code-profiler.js +605 -0
- package/scripts/audit-tools/dep-doctor.js +320 -0
- package/scripts/audit-tools/env-validator.js +206 -0
- package/scripts/audit-tools/lib/fs-walk.js +48 -0
- package/scripts/audit-tools/lib/output.js +23 -0
- package/scripts/audit-tools/migration-checker.js +392 -0
- package/scripts/audit-tools/pentest-scanner.js +1436 -0
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,307 +1,307 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* detectar-stack-detallado.js
|
|
5
|
-
*
|
|
6
|
-
* Detecta stack del proyecto destino con información rica para generación de
|
|
7
|
-
* CLAUDE.md (no solo presencia, sino también: framework principal, scripts
|
|
8
|
-
* disponibles, package manager, deps clave).
|
|
9
|
-
*
|
|
10
|
-
* Diferencia con `detectar-stack.js`: ese módulo es para FILTRAR reglas de
|
|
11
|
-
* lenguaje (presence/absence). Este módulo es para POBLAR el bloque CLAUDE.md
|
|
12
|
-
* inyectado por el installer con info accionable del proyecto destino.
|
|
13
|
-
*
|
|
14
|
-
* Zero-dependencies (solo fs/path nativos). Defensivo: cualquier read failure
|
|
15
|
-
* devuelve null/undefined, nunca lanza.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const fs = require('fs');
|
|
19
|
-
const path = require('path');
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Lee package.json del proyecto destino.
|
|
23
|
-
* @returns {{ name?: string, version?: string, scripts?: object, dependencies?: object, devDependencies?: object } | null}
|
|
24
|
-
*/
|
|
25
|
-
function leerPackageJson(dirProyecto) {
|
|
26
|
-
return _leerJsonSeguro(path.join(dirProyecto, 'package.json'));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Detecta el package manager Node usado (basado en lockfile).
|
|
31
|
-
* @returns {'pnpm' | 'yarn' | 'bun' | 'npm' | null}
|
|
32
|
-
*/
|
|
33
|
-
function detectarPackageManagerNode(dirProyecto) {
|
|
34
|
-
if (fs.existsSync(path.join(dirProyecto, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
35
|
-
if (fs.existsSync(path.join(dirProyecto, 'yarn.lock'))) return 'yarn';
|
|
36
|
-
if (fs.existsSync(path.join(dirProyecto, 'bun.lockb'))) return 'bun';
|
|
37
|
-
if (fs.existsSync(path.join(dirProyecto, 'package-lock.json'))) return 'npm';
|
|
38
|
-
if (fs.existsSync(path.join(dirProyecto, 'package.json'))) return 'npm';
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Detecta el package manager Python.
|
|
44
|
-
* @returns {'poetry' | 'uv' | 'pdm' | 'pip' | null}
|
|
45
|
-
*/
|
|
46
|
-
function detectarPackageManagerPython(dirProyecto) {
|
|
47
|
-
const pyproject = path.join(dirProyecto, 'pyproject.toml');
|
|
48
|
-
if (fs.existsSync(pyproject)) {
|
|
49
|
-
const contenido = _leerArchivoSeguro(pyproject) || '';
|
|
50
|
-
if (contenido.includes('[tool.poetry]')) return 'poetry';
|
|
51
|
-
if (contenido.includes('[tool.uv]')) return 'uv';
|
|
52
|
-
if (contenido.includes('[tool.pdm]')) return 'pdm';
|
|
53
|
-
return 'pip'; // pyproject genérico
|
|
54
|
-
}
|
|
55
|
-
if (fs.existsSync(path.join(dirProyecto, 'requirements.txt'))) return 'pip';
|
|
56
|
-
if (fs.existsSync(path.join(dirProyecto, 'Pipfile'))) return 'pip';
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Detecta framework Node a partir de dependencies.
|
|
62
|
-
* @returns {string | null}
|
|
63
|
-
*/
|
|
64
|
-
function detectarFrameworkNode(packageJson) {
|
|
65
|
-
if (!packageJson) return null;
|
|
66
|
-
const deps = { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) };
|
|
67
|
-
|
|
68
|
-
if (deps.next) return `Next.js ${_limpiarVersion(deps.next)}`;
|
|
69
|
-
if (deps['@angular/core']) return `Angular ${_limpiarVersion(deps['@angular/core'])}`;
|
|
70
|
-
if (deps['@nestjs/core']) return `NestJS ${_limpiarVersion(deps['@nestjs/core'])}`;
|
|
71
|
-
if (deps.svelte) return `Svelte ${_limpiarVersion(deps.svelte)}`;
|
|
72
|
-
if (deps.vue) return `Vue ${_limpiarVersion(deps.vue)}`;
|
|
73
|
-
if (deps.remix || deps['@remix-run/node']) return 'Remix';
|
|
74
|
-
if (deps.astro) return `Astro ${_limpiarVersion(deps.astro)}`;
|
|
75
|
-
if (deps.nuxt) return `Nuxt ${_limpiarVersion(deps.nuxt)}`;
|
|
76
|
-
if (deps.fastify) return `Fastify ${_limpiarVersion(deps.fastify)}`;
|
|
77
|
-
if (deps.express) return `Express ${_limpiarVersion(deps.express)}`;
|
|
78
|
-
if (deps.react) return `React ${_limpiarVersion(deps.react)} (sin framework)`;
|
|
79
|
-
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Detecta framework Python leyendo deps en pyproject.toml o requirements.txt.
|
|
85
|
-
* @returns {string | null}
|
|
86
|
-
*/
|
|
87
|
-
function detectarFrameworkPython(dirProyecto) {
|
|
88
|
-
const pyproject = _leerArchivoSeguro(path.join(dirProyecto, 'pyproject.toml')) || '';
|
|
89
|
-
const requirements = _leerArchivoSeguro(path.join(dirProyecto, 'requirements.txt')) || '';
|
|
90
|
-
const deps = (pyproject + '\n' + requirements).toLowerCase();
|
|
91
|
-
|
|
92
|
-
if (deps.includes('fastapi')) return 'FastAPI';
|
|
93
|
-
if (deps.includes('django')) return 'Django';
|
|
94
|
-
if (deps.includes('flask')) return 'Flask';
|
|
95
|
-
if (deps.includes('starlette')) return 'Starlette';
|
|
96
|
-
if (deps.includes('aiohttp')) return 'aiohttp';
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Detecta el ORM/driver de BD principal.
|
|
102
|
-
* @returns {string | null}
|
|
103
|
-
*/
|
|
104
|
-
function detectarORM(packageJson, dirProyecto) {
|
|
105
|
-
const depsNode = packageJson
|
|
106
|
-
? { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) }
|
|
107
|
-
: {};
|
|
108
|
-
|
|
109
|
-
if (depsNode.prisma || depsNode['@prisma/client']) return 'Prisma';
|
|
110
|
-
if (depsNode['drizzle-orm']) return 'Drizzle';
|
|
111
|
-
if (depsNode.typeorm) return 'TypeORM';
|
|
112
|
-
if (depsNode.sequelize) return 'Sequelize';
|
|
113
|
-
if (depsNode.mongoose) return 'Mongoose';
|
|
114
|
-
|
|
115
|
-
// Python
|
|
116
|
-
const pyproject = _leerArchivoSeguro(path.join(dirProyecto, 'pyproject.toml')) || '';
|
|
117
|
-
const requirements = _leerArchivoSeguro(path.join(dirProyecto, 'requirements.txt')) || '';
|
|
118
|
-
const depsPy = (pyproject + '\n' + requirements).toLowerCase();
|
|
119
|
-
if (depsPy.includes('sqlalchemy')) return 'SQLAlchemy';
|
|
120
|
-
if (depsPy.includes('peewee')) return 'Peewee';
|
|
121
|
-
if (depsPy.includes('tortoise-orm')) return 'Tortoise ORM';
|
|
122
|
-
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Detecta lenguaje principal con confianza (devuelve el más probable).
|
|
128
|
-
* @returns {{ lenguaje: string, runtime?: string } | null}
|
|
129
|
-
*/
|
|
130
|
-
function detectarLenguajePrincipal(dirProyecto) {
|
|
131
|
-
// Node.js / TypeScript
|
|
132
|
-
if (fs.existsSync(path.join(dirProyecto, 'package.json'))) {
|
|
133
|
-
if (fs.existsSync(path.join(dirProyecto, 'tsconfig.json'))) {
|
|
134
|
-
return { lenguaje: 'TypeScript', runtime: 'Node.js' };
|
|
135
|
-
}
|
|
136
|
-
return { lenguaje: 'JavaScript', runtime: 'Node.js' };
|
|
137
|
-
}
|
|
138
|
-
// Python
|
|
139
|
-
if (
|
|
140
|
-
fs.existsSync(path.join(dirProyecto, 'pyproject.toml')) ||
|
|
141
|
-
fs.existsSync(path.join(dirProyecto, 'requirements.txt')) ||
|
|
142
|
-
fs.existsSync(path.join(dirProyecto, 'setup.py'))
|
|
143
|
-
) {
|
|
144
|
-
return { lenguaje: 'Python' };
|
|
145
|
-
}
|
|
146
|
-
// Go
|
|
147
|
-
if (fs.existsSync(path.join(dirProyecto, 'go.mod'))) return { lenguaje: 'Go' };
|
|
148
|
-
// Rust
|
|
149
|
-
if (fs.existsSync(path.join(dirProyecto, 'Cargo.toml'))) return { lenguaje: 'Rust' };
|
|
150
|
-
// Java / Kotlin
|
|
151
|
-
if (fs.existsSync(path.join(dirProyecto, 'pom.xml'))) return { lenguaje: 'Java', runtime: 'Maven' };
|
|
152
|
-
if (
|
|
153
|
-
fs.existsSync(path.join(dirProyecto, 'build.gradle')) ||
|
|
154
|
-
fs.existsSync(path.join(dirProyecto, 'build.gradle.kts'))
|
|
155
|
-
) {
|
|
156
|
-
return { lenguaje: 'Java/Kotlin', runtime: 'Gradle' };
|
|
157
|
-
}
|
|
158
|
-
// PHP
|
|
159
|
-
if (fs.existsSync(path.join(dirProyecto, 'composer.json'))) return { lenguaje: 'PHP' };
|
|
160
|
-
// Swift
|
|
161
|
-
if (fs.existsSync(path.join(dirProyecto, 'Package.swift'))) return { lenguaje: 'Swift' };
|
|
162
|
-
// Ruby
|
|
163
|
-
if (fs.existsSync(path.join(dirProyecto, 'Gemfile'))) return { lenguaje: 'Ruby' };
|
|
164
|
-
// C# / .NET
|
|
165
|
-
if (fs.existsSync(path.join(dirProyecto, 'global.json'))) return { lenguaje: 'C#', runtime: '.NET' };
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Detecta scripts comunes del proyecto (test, dev, build, lint).
|
|
172
|
-
* Devuelve los scripts npm si existen + comandos típicos del lenguaje.
|
|
173
|
-
*
|
|
174
|
-
* @returns {Array<{ comando: string, proposito: string }>}
|
|
175
|
-
*/
|
|
176
|
-
function detectarComandos(dirProyecto) {
|
|
177
|
-
const comandos = [];
|
|
178
|
-
const pkg = leerPackageJson(dirProyecto);
|
|
179
|
-
const pm = detectarPackageManagerNode(dirProyecto);
|
|
180
|
-
|
|
181
|
-
if (pkg && pkg.scripts) {
|
|
182
|
-
const interesantes = ['dev', 'start', 'build', 'test', 'lint', 'typecheck', 'format'];
|
|
183
|
-
for (const script of interesantes) {
|
|
184
|
-
if (pkg.scripts[script]) {
|
|
185
|
-
comandos.push({
|
|
186
|
-
comando: `${pm || 'npm'} run ${script}`,
|
|
187
|
-
proposito: _propositoScript(script),
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// Si tiene "test" como entrypoint puro
|
|
192
|
-
if (pkg.scripts.test && !comandos.some(c => c.comando.includes('test'))) {
|
|
193
|
-
comandos.push({ comando: `${pm || 'npm'} test`, proposito: 'Tests' });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Python
|
|
198
|
-
const pmPy = detectarPackageManagerPython(dirProyecto);
|
|
199
|
-
if (pmPy === 'poetry') {
|
|
200
|
-
comandos.push({ comando: 'poetry install', proposito: 'Instalar dependencias' });
|
|
201
|
-
comandos.push({ comando: 'poetry run pytest', proposito: 'Tests' });
|
|
202
|
-
} else if (pmPy === 'uv') {
|
|
203
|
-
comandos.push({ comando: 'uv sync', proposito: 'Instalar dependencias' });
|
|
204
|
-
comandos.push({ comando: 'uv run pytest', proposito: 'Tests' });
|
|
205
|
-
} else if (pmPy === 'pip') {
|
|
206
|
-
comandos.push({ comando: 'pip install -r requirements.txt', proposito: 'Instalar dependencias' });
|
|
207
|
-
comandos.push({ comando: 'pytest', proposito: 'Tests' });
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Go
|
|
211
|
-
if (fs.existsSync(path.join(dirProyecto, 'go.mod'))) {
|
|
212
|
-
comandos.push({ comando: 'go test ./...', proposito: 'Tests' });
|
|
213
|
-
comandos.push({ comando: 'go build ./...', proposito: 'Build' });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Rust
|
|
217
|
-
if (fs.existsSync(path.join(dirProyecto, 'Cargo.toml'))) {
|
|
218
|
-
comandos.push({ comando: 'cargo test', proposito: 'Tests' });
|
|
219
|
-
comandos.push({ comando: 'cargo build', proposito: 'Build' });
|
|
220
|
-
comandos.push({ comando: 'cargo clippy', proposito: 'Lint' });
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return comandos;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Detección integral: devuelve resumen del stack del proyecto destino.
|
|
228
|
-
*
|
|
229
|
-
* @returns {{
|
|
230
|
-
* lenguajePrincipal: { lenguaje: string, runtime?: string } | null,
|
|
231
|
-
* framework: string | null,
|
|
232
|
-
* orm: string | null,
|
|
233
|
-
* packageManager: string | null,
|
|
234
|
-
* comandos: Array<{ comando: string, proposito: string }>,
|
|
235
|
-
* detectado: boolean
|
|
236
|
-
* }}
|
|
237
|
-
*/
|
|
238
|
-
function detectarStackDetallado(dirProyecto) {
|
|
239
|
-
const lenguajePrincipal = detectarLenguajePrincipal(dirProyecto);
|
|
240
|
-
const pkg = leerPackageJson(dirProyecto);
|
|
241
|
-
const framework = detectarFrameworkNode(pkg) || detectarFrameworkPython(dirProyecto);
|
|
242
|
-
const orm = detectarORM(pkg, dirProyecto);
|
|
243
|
-
const packageManager =
|
|
244
|
-
detectarPackageManagerNode(dirProyecto) || detectarPackageManagerPython(dirProyecto);
|
|
245
|
-
const comandos = detectarComandos(dirProyecto);
|
|
246
|
-
|
|
247
|
-
return {
|
|
248
|
-
lenguajePrincipal,
|
|
249
|
-
framework,
|
|
250
|
-
orm,
|
|
251
|
-
packageManager,
|
|
252
|
-
comandos,
|
|
253
|
-
detectado: lenguajePrincipal !== null,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ─── Helpers privados ────────────────────────────────────────────────────────
|
|
258
|
-
|
|
259
|
-
function _leerJsonSeguro(ruta) {
|
|
260
|
-
try {
|
|
261
|
-
if (!fs.existsSync(ruta)) return null;
|
|
262
|
-
return JSON.parse(fs.readFileSync(ruta, 'utf8'));
|
|
263
|
-
} catch {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function _leerArchivoSeguro(ruta) {
|
|
269
|
-
try {
|
|
270
|
-
if (!fs.existsSync(ruta)) return null;
|
|
271
|
-
return fs.readFileSync(ruta, 'utf8');
|
|
272
|
-
} catch {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function _limpiarVersion(rango) {
|
|
278
|
-
// "^15.0.0" → "15", "~3.4.1" → "3"
|
|
279
|
-
if (typeof rango !== 'string') return '';
|
|
280
|
-
const m = rango.match(/(\d+)/);
|
|
281
|
-
return m ? m[1] : '';
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function _propositoScript(nombre) {
|
|
285
|
-
const map = {
|
|
286
|
-
dev: 'Servidor de desarrollo',
|
|
287
|
-
start: 'Iniciar app',
|
|
288
|
-
build: 'Build de producción',
|
|
289
|
-
test: 'Tests',
|
|
290
|
-
lint: 'Lint',
|
|
291
|
-
typecheck: 'Verificar tipos',
|
|
292
|
-
format: 'Formatear código',
|
|
293
|
-
};
|
|
294
|
-
return map[nombre] || nombre;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
module.exports = {
|
|
298
|
-
detectarStackDetallado,
|
|
299
|
-
detectarLenguajePrincipal,
|
|
300
|
-
detectarFrameworkNode,
|
|
301
|
-
detectarFrameworkPython,
|
|
302
|
-
detectarORM,
|
|
303
|
-
detectarPackageManagerNode,
|
|
304
|
-
detectarPackageManagerPython,
|
|
305
|
-
detectarComandos,
|
|
306
|
-
leerPackageJson,
|
|
307
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* detectar-stack-detallado.js
|
|
5
|
+
*
|
|
6
|
+
* Detecta stack del proyecto destino con información rica para generación de
|
|
7
|
+
* CLAUDE.md (no solo presencia, sino también: framework principal, scripts
|
|
8
|
+
* disponibles, package manager, deps clave).
|
|
9
|
+
*
|
|
10
|
+
* Diferencia con `detectar-stack.js`: ese módulo es para FILTRAR reglas de
|
|
11
|
+
* lenguaje (presence/absence). Este módulo es para POBLAR el bloque CLAUDE.md
|
|
12
|
+
* inyectado por el installer con info accionable del proyecto destino.
|
|
13
|
+
*
|
|
14
|
+
* Zero-dependencies (solo fs/path nativos). Defensivo: cualquier read failure
|
|
15
|
+
* devuelve null/undefined, nunca lanza.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Lee package.json del proyecto destino.
|
|
23
|
+
* @returns {{ name?: string, version?: string, scripts?: object, dependencies?: object, devDependencies?: object } | null}
|
|
24
|
+
*/
|
|
25
|
+
function leerPackageJson(dirProyecto) {
|
|
26
|
+
return _leerJsonSeguro(path.join(dirProyecto, 'package.json'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detecta el package manager Node usado (basado en lockfile).
|
|
31
|
+
* @returns {'pnpm' | 'yarn' | 'bun' | 'npm' | null}
|
|
32
|
+
*/
|
|
33
|
+
function detectarPackageManagerNode(dirProyecto) {
|
|
34
|
+
if (fs.existsSync(path.join(dirProyecto, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
35
|
+
if (fs.existsSync(path.join(dirProyecto, 'yarn.lock'))) return 'yarn';
|
|
36
|
+
if (fs.existsSync(path.join(dirProyecto, 'bun.lockb'))) return 'bun';
|
|
37
|
+
if (fs.existsSync(path.join(dirProyecto, 'package-lock.json'))) return 'npm';
|
|
38
|
+
if (fs.existsSync(path.join(dirProyecto, 'package.json'))) return 'npm';
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detecta el package manager Python.
|
|
44
|
+
* @returns {'poetry' | 'uv' | 'pdm' | 'pip' | null}
|
|
45
|
+
*/
|
|
46
|
+
function detectarPackageManagerPython(dirProyecto) {
|
|
47
|
+
const pyproject = path.join(dirProyecto, 'pyproject.toml');
|
|
48
|
+
if (fs.existsSync(pyproject)) {
|
|
49
|
+
const contenido = _leerArchivoSeguro(pyproject) || '';
|
|
50
|
+
if (contenido.includes('[tool.poetry]')) return 'poetry';
|
|
51
|
+
if (contenido.includes('[tool.uv]')) return 'uv';
|
|
52
|
+
if (contenido.includes('[tool.pdm]')) return 'pdm';
|
|
53
|
+
return 'pip'; // pyproject genérico
|
|
54
|
+
}
|
|
55
|
+
if (fs.existsSync(path.join(dirProyecto, 'requirements.txt'))) return 'pip';
|
|
56
|
+
if (fs.existsSync(path.join(dirProyecto, 'Pipfile'))) return 'pip';
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detecta framework Node a partir de dependencies.
|
|
62
|
+
* @returns {string | null}
|
|
63
|
+
*/
|
|
64
|
+
function detectarFrameworkNode(packageJson) {
|
|
65
|
+
if (!packageJson) return null;
|
|
66
|
+
const deps = { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) };
|
|
67
|
+
|
|
68
|
+
if (deps.next) return `Next.js ${_limpiarVersion(deps.next)}`;
|
|
69
|
+
if (deps['@angular/core']) return `Angular ${_limpiarVersion(deps['@angular/core'])}`;
|
|
70
|
+
if (deps['@nestjs/core']) return `NestJS ${_limpiarVersion(deps['@nestjs/core'])}`;
|
|
71
|
+
if (deps.svelte) return `Svelte ${_limpiarVersion(deps.svelte)}`;
|
|
72
|
+
if (deps.vue) return `Vue ${_limpiarVersion(deps.vue)}`;
|
|
73
|
+
if (deps.remix || deps['@remix-run/node']) return 'Remix';
|
|
74
|
+
if (deps.astro) return `Astro ${_limpiarVersion(deps.astro)}`;
|
|
75
|
+
if (deps.nuxt) return `Nuxt ${_limpiarVersion(deps.nuxt)}`;
|
|
76
|
+
if (deps.fastify) return `Fastify ${_limpiarVersion(deps.fastify)}`;
|
|
77
|
+
if (deps.express) return `Express ${_limpiarVersion(deps.express)}`;
|
|
78
|
+
if (deps.react) return `React ${_limpiarVersion(deps.react)} (sin framework)`;
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Detecta framework Python leyendo deps en pyproject.toml o requirements.txt.
|
|
85
|
+
* @returns {string | null}
|
|
86
|
+
*/
|
|
87
|
+
function detectarFrameworkPython(dirProyecto) {
|
|
88
|
+
const pyproject = _leerArchivoSeguro(path.join(dirProyecto, 'pyproject.toml')) || '';
|
|
89
|
+
const requirements = _leerArchivoSeguro(path.join(dirProyecto, 'requirements.txt')) || '';
|
|
90
|
+
const deps = (pyproject + '\n' + requirements).toLowerCase();
|
|
91
|
+
|
|
92
|
+
if (deps.includes('fastapi')) return 'FastAPI';
|
|
93
|
+
if (deps.includes('django')) return 'Django';
|
|
94
|
+
if (deps.includes('flask')) return 'Flask';
|
|
95
|
+
if (deps.includes('starlette')) return 'Starlette';
|
|
96
|
+
if (deps.includes('aiohttp')) return 'aiohttp';
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Detecta el ORM/driver de BD principal.
|
|
102
|
+
* @returns {string | null}
|
|
103
|
+
*/
|
|
104
|
+
function detectarORM(packageJson, dirProyecto) {
|
|
105
|
+
const depsNode = packageJson
|
|
106
|
+
? { ...(packageJson.dependencies || {}), ...(packageJson.devDependencies || {}) }
|
|
107
|
+
: {};
|
|
108
|
+
|
|
109
|
+
if (depsNode.prisma || depsNode['@prisma/client']) return 'Prisma';
|
|
110
|
+
if (depsNode['drizzle-orm']) return 'Drizzle';
|
|
111
|
+
if (depsNode.typeorm) return 'TypeORM';
|
|
112
|
+
if (depsNode.sequelize) return 'Sequelize';
|
|
113
|
+
if (depsNode.mongoose) return 'Mongoose';
|
|
114
|
+
|
|
115
|
+
// Python
|
|
116
|
+
const pyproject = _leerArchivoSeguro(path.join(dirProyecto, 'pyproject.toml')) || '';
|
|
117
|
+
const requirements = _leerArchivoSeguro(path.join(dirProyecto, 'requirements.txt')) || '';
|
|
118
|
+
const depsPy = (pyproject + '\n' + requirements).toLowerCase();
|
|
119
|
+
if (depsPy.includes('sqlalchemy')) return 'SQLAlchemy';
|
|
120
|
+
if (depsPy.includes('peewee')) return 'Peewee';
|
|
121
|
+
if (depsPy.includes('tortoise-orm')) return 'Tortoise ORM';
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Detecta lenguaje principal con confianza (devuelve el más probable).
|
|
128
|
+
* @returns {{ lenguaje: string, runtime?: string } | null}
|
|
129
|
+
*/
|
|
130
|
+
function detectarLenguajePrincipal(dirProyecto) {
|
|
131
|
+
// Node.js / TypeScript
|
|
132
|
+
if (fs.existsSync(path.join(dirProyecto, 'package.json'))) {
|
|
133
|
+
if (fs.existsSync(path.join(dirProyecto, 'tsconfig.json'))) {
|
|
134
|
+
return { lenguaje: 'TypeScript', runtime: 'Node.js' };
|
|
135
|
+
}
|
|
136
|
+
return { lenguaje: 'JavaScript', runtime: 'Node.js' };
|
|
137
|
+
}
|
|
138
|
+
// Python
|
|
139
|
+
if (
|
|
140
|
+
fs.existsSync(path.join(dirProyecto, 'pyproject.toml')) ||
|
|
141
|
+
fs.existsSync(path.join(dirProyecto, 'requirements.txt')) ||
|
|
142
|
+
fs.existsSync(path.join(dirProyecto, 'setup.py'))
|
|
143
|
+
) {
|
|
144
|
+
return { lenguaje: 'Python' };
|
|
145
|
+
}
|
|
146
|
+
// Go
|
|
147
|
+
if (fs.existsSync(path.join(dirProyecto, 'go.mod'))) return { lenguaje: 'Go' };
|
|
148
|
+
// Rust
|
|
149
|
+
if (fs.existsSync(path.join(dirProyecto, 'Cargo.toml'))) return { lenguaje: 'Rust' };
|
|
150
|
+
// Java / Kotlin
|
|
151
|
+
if (fs.existsSync(path.join(dirProyecto, 'pom.xml'))) return { lenguaje: 'Java', runtime: 'Maven' };
|
|
152
|
+
if (
|
|
153
|
+
fs.existsSync(path.join(dirProyecto, 'build.gradle')) ||
|
|
154
|
+
fs.existsSync(path.join(dirProyecto, 'build.gradle.kts'))
|
|
155
|
+
) {
|
|
156
|
+
return { lenguaje: 'Java/Kotlin', runtime: 'Gradle' };
|
|
157
|
+
}
|
|
158
|
+
// PHP
|
|
159
|
+
if (fs.existsSync(path.join(dirProyecto, 'composer.json'))) return { lenguaje: 'PHP' };
|
|
160
|
+
// Swift
|
|
161
|
+
if (fs.existsSync(path.join(dirProyecto, 'Package.swift'))) return { lenguaje: 'Swift' };
|
|
162
|
+
// Ruby
|
|
163
|
+
if (fs.existsSync(path.join(dirProyecto, 'Gemfile'))) return { lenguaje: 'Ruby' };
|
|
164
|
+
// C# / .NET
|
|
165
|
+
if (fs.existsSync(path.join(dirProyecto, 'global.json'))) return { lenguaje: 'C#', runtime: '.NET' };
|
|
166
|
+
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detecta scripts comunes del proyecto (test, dev, build, lint).
|
|
172
|
+
* Devuelve los scripts npm si existen + comandos típicos del lenguaje.
|
|
173
|
+
*
|
|
174
|
+
* @returns {Array<{ comando: string, proposito: string }>}
|
|
175
|
+
*/
|
|
176
|
+
function detectarComandos(dirProyecto) {
|
|
177
|
+
const comandos = [];
|
|
178
|
+
const pkg = leerPackageJson(dirProyecto);
|
|
179
|
+
const pm = detectarPackageManagerNode(dirProyecto);
|
|
180
|
+
|
|
181
|
+
if (pkg && pkg.scripts) {
|
|
182
|
+
const interesantes = ['dev', 'start', 'build', 'test', 'lint', 'typecheck', 'format'];
|
|
183
|
+
for (const script of interesantes) {
|
|
184
|
+
if (pkg.scripts[script]) {
|
|
185
|
+
comandos.push({
|
|
186
|
+
comando: `${pm || 'npm'} run ${script}`,
|
|
187
|
+
proposito: _propositoScript(script),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Si tiene "test" como entrypoint puro
|
|
192
|
+
if (pkg.scripts.test && !comandos.some(c => c.comando.includes('test'))) {
|
|
193
|
+
comandos.push({ comando: `${pm || 'npm'} test`, proposito: 'Tests' });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Python
|
|
198
|
+
const pmPy = detectarPackageManagerPython(dirProyecto);
|
|
199
|
+
if (pmPy === 'poetry') {
|
|
200
|
+
comandos.push({ comando: 'poetry install', proposito: 'Instalar dependencias' });
|
|
201
|
+
comandos.push({ comando: 'poetry run pytest', proposito: 'Tests' });
|
|
202
|
+
} else if (pmPy === 'uv') {
|
|
203
|
+
comandos.push({ comando: 'uv sync', proposito: 'Instalar dependencias' });
|
|
204
|
+
comandos.push({ comando: 'uv run pytest', proposito: 'Tests' });
|
|
205
|
+
} else if (pmPy === 'pip') {
|
|
206
|
+
comandos.push({ comando: 'pip install -r requirements.txt', proposito: 'Instalar dependencias' });
|
|
207
|
+
comandos.push({ comando: 'pytest', proposito: 'Tests' });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Go
|
|
211
|
+
if (fs.existsSync(path.join(dirProyecto, 'go.mod'))) {
|
|
212
|
+
comandos.push({ comando: 'go test ./...', proposito: 'Tests' });
|
|
213
|
+
comandos.push({ comando: 'go build ./...', proposito: 'Build' });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Rust
|
|
217
|
+
if (fs.existsSync(path.join(dirProyecto, 'Cargo.toml'))) {
|
|
218
|
+
comandos.push({ comando: 'cargo test', proposito: 'Tests' });
|
|
219
|
+
comandos.push({ comando: 'cargo build', proposito: 'Build' });
|
|
220
|
+
comandos.push({ comando: 'cargo clippy', proposito: 'Lint' });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return comandos;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Detección integral: devuelve resumen del stack del proyecto destino.
|
|
228
|
+
*
|
|
229
|
+
* @returns {{
|
|
230
|
+
* lenguajePrincipal: { lenguaje: string, runtime?: string } | null,
|
|
231
|
+
* framework: string | null,
|
|
232
|
+
* orm: string | null,
|
|
233
|
+
* packageManager: string | null,
|
|
234
|
+
* comandos: Array<{ comando: string, proposito: string }>,
|
|
235
|
+
* detectado: boolean
|
|
236
|
+
* }}
|
|
237
|
+
*/
|
|
238
|
+
function detectarStackDetallado(dirProyecto) {
|
|
239
|
+
const lenguajePrincipal = detectarLenguajePrincipal(dirProyecto);
|
|
240
|
+
const pkg = leerPackageJson(dirProyecto);
|
|
241
|
+
const framework = detectarFrameworkNode(pkg) || detectarFrameworkPython(dirProyecto);
|
|
242
|
+
const orm = detectarORM(pkg, dirProyecto);
|
|
243
|
+
const packageManager =
|
|
244
|
+
detectarPackageManagerNode(dirProyecto) || detectarPackageManagerPython(dirProyecto);
|
|
245
|
+
const comandos = detectarComandos(dirProyecto);
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
lenguajePrincipal,
|
|
249
|
+
framework,
|
|
250
|
+
orm,
|
|
251
|
+
packageManager,
|
|
252
|
+
comandos,
|
|
253
|
+
detectado: lenguajePrincipal !== null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── Helpers privados ────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
function _leerJsonSeguro(ruta) {
|
|
260
|
+
try {
|
|
261
|
+
if (!fs.existsSync(ruta)) return null;
|
|
262
|
+
return JSON.parse(fs.readFileSync(ruta, 'utf8'));
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _leerArchivoSeguro(ruta) {
|
|
269
|
+
try {
|
|
270
|
+
if (!fs.existsSync(ruta)) return null;
|
|
271
|
+
return fs.readFileSync(ruta, 'utf8');
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function _limpiarVersion(rango) {
|
|
278
|
+
// "^15.0.0" → "15", "~3.4.1" → "3"
|
|
279
|
+
if (typeof rango !== 'string') return '';
|
|
280
|
+
const m = rango.match(/(\d+)/);
|
|
281
|
+
return m ? m[1] : '';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function _propositoScript(nombre) {
|
|
285
|
+
const map = {
|
|
286
|
+
dev: 'Servidor de desarrollo',
|
|
287
|
+
start: 'Iniciar app',
|
|
288
|
+
build: 'Build de producción',
|
|
289
|
+
test: 'Tests',
|
|
290
|
+
lint: 'Lint',
|
|
291
|
+
typecheck: 'Verificar tipos',
|
|
292
|
+
format: 'Formatear código',
|
|
293
|
+
};
|
|
294
|
+
return map[nombre] || nombre;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
detectarStackDetallado,
|
|
299
|
+
detectarLenguajePrincipal,
|
|
300
|
+
detectarFrameworkNode,
|
|
301
|
+
detectarFrameworkPython,
|
|
302
|
+
detectarORM,
|
|
303
|
+
detectarPackageManagerNode,
|
|
304
|
+
detectarPackageManagerPython,
|
|
305
|
+
detectarComandos,
|
|
306
|
+
leerPackageJson,
|
|
307
|
+
};
|