@saulwade/swl-ses 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -1
- package/bin/swl-ses.js +27 -6
- package/manifiestos/modulos.json +2 -1
- package/package.json +4 -6
- package/plugin.json +1 -1
- package/reglas/usar-context7.md +226 -0
- package/scripts/lib/semantic-search.js +10 -0
- package/scripts/validar-userland-vacio.js +110 -0
package/CLAUDE.md
CHANGED
|
@@ -87,7 +87,7 @@ scripts/ bin/ _userland/ .claude/ .planning/
|
|
|
87
87
|
| `/swl:contribuir` | Contribuir evoluciones de _userland/ al core vía PR (filtro dominio + PluginEval ≥80) |
|
|
88
88
|
| `/swl:ayuda` | Ayuda interactiva: catálogo, detalle de comando, búsqueda por keyword |
|
|
89
89
|
|
|
90
|
-
## Reglas obligatorias (
|
|
90
|
+
## Reglas obligatorias (21 base + 40 por lenguaje)
|
|
91
91
|
|
|
92
92
|
| Regla | Carga cuando |
|
|
93
93
|
|-------|-------------|
|
|
@@ -109,6 +109,7 @@ scripts/ bin/ _userland/ .claude/ .planning/
|
|
|
109
109
|
| `markitdown.md` | Lectura de archivos Office/Jupyter |
|
|
110
110
|
| `patrones.md` | Patrones de diseño y arquitectura |
|
|
111
111
|
| `testing.md` | test*/, *.test.*, *.spec.* |
|
|
112
|
+
| `usar-context7.md` | OBLIGATORIA al generar código que importe librerías/frameworks externos. Aplica a `backend-*-swl`, `frontend-*-swl`, `mobile-*-swl`, `implementador-swl`, `llm-apps-swl`, `pagos-swl`, `datos-swl`. Verifica versión actual y deprecaciones via Context7 MCP antes de agregar deps o generar imports. |
|
|
112
113
|
|
|
113
114
|
Reglas por lenguaje (5 por lenguaje × 8 lenguajes): Java, Go, Rust, C#, Kotlin, Swift, PHP, Next.js — en `reglas/` con subdirectorios.
|
|
114
115
|
|
package/bin/swl-ses.js
CHANGED
|
@@ -64,13 +64,34 @@ function verificarCoherenciaVersion() {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if (versionMax && compararSemver(versionMax, VERSION) === 1) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const localPS = parseSemver(versionMax);
|
|
68
|
+
const cliPS = parseSemver(VERSION);
|
|
69
|
+
// Tras el rebrand 2026-04-30, el CLI nuevo @saulwade/swl-ses
|
|
70
|
+
// arrancó en v1.0.0 mientras el paquete legacy (@saulwadeleon/...
|
|
71
|
+
// o swl-ses sin scope) estaba en 5.13.1. Si la diferencia de
|
|
72
|
+
// major es ≥3 hacia abajo del CLI nuevo, NO es "desactualizado":
|
|
73
|
+
// el state apunta a una instalación previa al rebrand. En ese
|
|
74
|
+
// caso emitimos un aviso DIFERENTE: "reinstala para sincronizar".
|
|
75
|
+
const esDriftDePaqueteLegacy = (
|
|
76
|
+
localPS && cliPS &&
|
|
77
|
+
localPS[0] >= 5 && cliPS[0] === 1 &&
|
|
78
|
+
(localPS[0] - cliPS[0]) >= 3
|
|
73
79
|
);
|
|
80
|
+
if (esDriftDePaqueteLegacy) {
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`\n[swl-ses] Detectada instalación previa del paquete legacy (v${versionMax}).\n` +
|
|
83
|
+
`[swl-ses] El sistema fue renombrado a @saulwade/swl-ses con SemVer reset (v${VERSION}).\n` +
|
|
84
|
+
`[swl-ses] Para sincronizar: npx @saulwade/swl-ses@latest install --target claude --profile <perfil> --force\n\n`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`\n[swl-ses] AVISO: CLI desactualizado — ejecutando v${VERSION}, pero tu instalación SWL es v${versionMax}.\n` +
|
|
89
|
+
`[swl-ses] npm tiene cacheada una versión vieja. Para usar la versión correcta:\n` +
|
|
90
|
+
`[swl-ses] npx @saulwade/swl-ses@latest <comando>\n` +
|
|
91
|
+
`[swl-ses] O limpia la caché de npx:\n` +
|
|
92
|
+
`[swl-ses] npx clear-npx-cache\n\n`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
} catch { /* nunca bloquear el CLI por este check */ }
|
|
76
97
|
}
|
package/manifiestos/modulos.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saulwade/swl-ses",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot con 59 agentes, 151 habilidades, 42 comandos, 60 reglas y 37 hooks. Soporta 11 lenguajes y 5 runtimes: Claude Code, Copilot, OpenCode, Codex y Gemini CLI. 100% en espanol (Mexico). Incluye gateway bidireccional con relay Telegram a Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"swl-ses": "bin/swl-ses.js",
|
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
"test:smoke": "node scripts/smoke-test.js",
|
|
32
32
|
"test:aislamiento": "node scripts/validar-tests-aislamiento.js",
|
|
33
33
|
"test:all": "npm test && node scripts/validar.js && node scripts/validar-manifest.js",
|
|
34
|
-
"test:
|
|
34
|
+
"test:userland": "node scripts/validar-userland-vacio.js",
|
|
35
|
+
"test:release": "npm run test:all && npm run test:userland && npm run test:smoke",
|
|
35
36
|
"doctor": "node scripts/doctor.js",
|
|
36
|
-
"prepack": "node scripts/limpiar-artefactos-python.js",
|
|
37
|
+
"prepack": "node scripts/limpiar-artefactos-python.js && node scripts/validar-userland-vacio.js",
|
|
37
38
|
"prepublishOnly": "npm run test:release",
|
|
38
39
|
"publish:all": "node scripts/publicar.js",
|
|
39
40
|
"publish:github": "node scripts/publicar.js --solo-github",
|
|
@@ -76,9 +77,6 @@
|
|
|
76
77
|
"dependencies": {
|
|
77
78
|
"docx": "^9.6.1"
|
|
78
79
|
},
|
|
79
|
-
"optionalDependencies": {
|
|
80
|
-
"@xenova/transformers": "^2.17.2"
|
|
81
|
-
},
|
|
82
80
|
"overrides": {
|
|
83
81
|
"pako": "^2.1.0",
|
|
84
82
|
"readable-stream": "^4.7.0"
|
package/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swl-ses",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Sistema de ingenieria de software auto-evolutivo multi-runtime polyglot. 59 agentes, 151 habilidades, 42 comandos, 60 reglas y 37 hooks. 60 librerias. 11 lenguajes. Soporta Claude Code, Copilot, OpenCode, Codex y Gemini CLI.",
|
|
5
5
|
"author": "Saul Wade Leon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Regla: Consultar Context7 antes de generar código con dependencias externas
|
|
2
|
+
|
|
3
|
+
Esta regla es OBLIGATORIA para todo agente o skill del sistema SWL que
|
|
4
|
+
genere código que use librerías, frameworks, SDKs o APIs externas. La regla
|
|
5
|
+
se carga automáticamente para los agentes de stack (`backend-*-swl`,
|
|
6
|
+
`frontend-*-swl`, `mobile-*-swl`, `implementador-swl`, `llm-apps-swl`,
|
|
7
|
+
`pagos-swl`, `datos-swl`) y para skills de implementación.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Por qué existe esta regla
|
|
12
|
+
|
|
13
|
+
El conocimiento del modelo sobre librerías de terceros tiene fecha de
|
|
14
|
+
corte. Una librería que en el corte estaba en la versión X y era la
|
|
15
|
+
recomendación, hoy puede estar:
|
|
16
|
+
|
|
17
|
+
- En una versión Y mayor con cambios incompatibles
|
|
18
|
+
- Deprecada y reemplazada por otra librería
|
|
19
|
+
- Con vulnerabilidades conocidas que requieren un parche
|
|
20
|
+
- Renombrada o migrada a otra organización
|
|
21
|
+
|
|
22
|
+
Generar código sin verificar la versión actual genera tres clases de
|
|
23
|
+
defectos en producción:
|
|
24
|
+
|
|
25
|
+
1. **Imports a APIs deprecadas** que arrastran warnings o se eliminan
|
|
26
|
+
en próximas versiones (ejemplo histórico: `@xenova/transformers`
|
|
27
|
+
migrado a `@huggingface/transformers`).
|
|
28
|
+
2. **Dependencias transitivas obsoletas** que el usuario ve como
|
|
29
|
+
warnings al instalar (ejemplo: `prebuild-install@7.1.3` arrastrada
|
|
30
|
+
por una librería que el sistema eligió sin verificar la salud).
|
|
31
|
+
3. **Vulnerabilidades CVE conocidas** que están parchadas en versiones
|
|
32
|
+
más nuevas que el modelo no conoce.
|
|
33
|
+
|
|
34
|
+
Context7 (MCP `mcp__claude_ai_Context7__query-docs` y
|
|
35
|
+
`mcp__claude_ai_Context7__resolve-library-id`) provee documentación
|
|
36
|
+
actualizada de librerías y frameworks. Su uso es la primera línea de
|
|
37
|
+
defensa contra estos tres problemas.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Cuándo consultar Context7 (OBLIGATORIO)
|
|
42
|
+
|
|
43
|
+
Antes de:
|
|
44
|
+
|
|
45
|
+
1. **Agregar una nueva dependencia** a `package.json`, `requirements.txt`,
|
|
46
|
+
`pom.xml`, `Cargo.toml`, `go.mod`, `composer.json`, `Package.swift` o
|
|
47
|
+
equivalente.
|
|
48
|
+
2. **Generar código que importe o use una librería externa** que no
|
|
49
|
+
estaba ya en uso en el proyecto, aunque la librería ya esté
|
|
50
|
+
instalada.
|
|
51
|
+
3. **Actualizar la versión** de una dependencia existente.
|
|
52
|
+
4. **Migrar de una librería a otra** alternativa (ej.
|
|
53
|
+
`axios` → `fetch`, `moment` → `date-fns`, `jest` → `vitest`).
|
|
54
|
+
5. **Resolver un warning de deprecación** de una dependencia
|
|
55
|
+
transitiva.
|
|
56
|
+
6. **Implementar un patrón nuevo** del framework (ej. Server Actions
|
|
57
|
+
en Next.js, Signals en Angular, Suspense en React) — la API
|
|
58
|
+
correcta cambia rápido.
|
|
59
|
+
|
|
60
|
+
NO es necesario consultar Context7 cuando:
|
|
61
|
+
|
|
62
|
+
- La librería es interna al proyecto SWL (`scripts/lib/*`,
|
|
63
|
+
`hooks/lib/*`).
|
|
64
|
+
- Es la biblioteca estándar del lenguaje (Node `fs`, Python `os`).
|
|
65
|
+
- Es código que no usa dependencias externas (algoritmos, lógica de
|
|
66
|
+
negocio sin librerías de terceros).
|
|
67
|
+
- El cambio es trivial (renombrar variable, formatear).
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Cómo consultar Context7
|
|
72
|
+
|
|
73
|
+
El MCP de Context7 expone dos herramientas principales:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
mcp__claude_ai_Context7__resolve-library-id(libraryName)
|
|
77
|
+
→ Resuelve un nombre de librería (ej. "react", "next.js") al ID
|
|
78
|
+
canónico que Context7 usa internamente.
|
|
79
|
+
|
|
80
|
+
mcp__claude_ai_Context7__query-docs(library, topic, version?)
|
|
81
|
+
→ Devuelve documentación actualizada de la librería sobre el
|
|
82
|
+
topic específico. Acepta versión opcional.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Flujo típico:
|
|
86
|
+
|
|
87
|
+
1. Resolver el ID de la librería:
|
|
88
|
+
```
|
|
89
|
+
const id = mcp__claude_ai_Context7__resolve-library-id({
|
|
90
|
+
libraryName: "next.js"
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
2. Consultar el tema específico:
|
|
95
|
+
```
|
|
96
|
+
const docs = mcp__claude_ai_Context7__query-docs({
|
|
97
|
+
library: id,
|
|
98
|
+
topic: "Server Actions",
|
|
99
|
+
version: "16"
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
3. Verificar:
|
|
104
|
+
- ¿La librería sigue siendo la recomendada o está deprecada?
|
|
105
|
+
- ¿Hay una alternativa migrada que deberíamos usar?
|
|
106
|
+
- ¿La API que voy a generar es la actual o cambió?
|
|
107
|
+
- ¿La versión que recomienda Context7 coincide con la del
|
|
108
|
+
`package.json` del proyecto?
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Reglas estrictas
|
|
113
|
+
|
|
114
|
+
### NUNCA agregar dependencia sin verificar
|
|
115
|
+
|
|
116
|
+
Antes de escribir `npm install <paquete>`, `pip install <paquete>`,
|
|
117
|
+
`go get <paquete>` o equivalente:
|
|
118
|
+
|
|
119
|
+
- Consultar Context7 para confirmar que la librería sigue activa.
|
|
120
|
+
- Confirmar la versión más reciente compatible con el resto del
|
|
121
|
+
stack del proyecto.
|
|
122
|
+
- Si Context7 reporta deprecación, **NO la uses**. Buscar la
|
|
123
|
+
alternativa migrada.
|
|
124
|
+
|
|
125
|
+
### NUNCA generar código con API obsoleta
|
|
126
|
+
|
|
127
|
+
Si el usuario pide implementar X usando librería Y:
|
|
128
|
+
|
|
129
|
+
- Consultar Context7 sobre cómo se hace X en la versión actual de Y.
|
|
130
|
+
- Si la API que aparece en el contexto del modelo difiere de la que
|
|
131
|
+
Context7 reporta como actual, **usar la de Context7**.
|
|
132
|
+
- Documentar en un comentario corto la versión consultada y la
|
|
133
|
+
fecha si la API es notable.
|
|
134
|
+
|
|
135
|
+
### NUNCA copiar ejemplos de la web sin verificar versión
|
|
136
|
+
|
|
137
|
+
Stack Overflow, blogs y tutoriales tienen alta probabilidad de
|
|
138
|
+
mostrar código deprecado. Si el agente consulta una web (vía
|
|
139
|
+
WebSearch o WebFetch) y obtiene un ejemplo, ese ejemplo debe
|
|
140
|
+
validarse contra Context7 antes de incorporarlo.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Excepción: Context7 no responde o no tiene la librería
|
|
145
|
+
|
|
146
|
+
Si Context7 no tiene documentación disponible para una librería
|
|
147
|
+
específica:
|
|
148
|
+
|
|
149
|
+
1. **Documentar en el código** que Context7 no respondió y la API se
|
|
150
|
+
tomó de la fuente oficial (npm registry, docs del repo).
|
|
151
|
+
2. **Verificar al menos**: la librería existe, no está deprecada en
|
|
152
|
+
npm/PyPI, su última publicación es reciente (≤ 12 meses).
|
|
153
|
+
3. **NO bloquear** el flujo del usuario por este caso — proceder con
|
|
154
|
+
diligencia razonable.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Auditoría retroactiva
|
|
159
|
+
|
|
160
|
+
Esta regla aplica también al código existente del proyecto SWL:
|
|
161
|
+
|
|
162
|
+
- Si durante un refactor se detecta una dependencia que arrastra
|
|
163
|
+
warnings de deprecación, abrir un ticket para auditarla con
|
|
164
|
+
Context7 y migrar si hay alternativa actual.
|
|
165
|
+
- El comando `/swl:auditar-deps` (cuando esté disponible) debe
|
|
166
|
+
cruzar las dependencias del proyecto contra Context7 para
|
|
167
|
+
detectar deprecaciones silenciosas.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Aplicabilidad por agente
|
|
172
|
+
|
|
173
|
+
| Agente | Aplica | Cuándo |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| `backend-python-swl` | SÍ | Al generar código FastAPI/Django/SQLAlchemy |
|
|
176
|
+
| `backend-node-swl` | SÍ | Al generar código Express/Fastify/NestJS |
|
|
177
|
+
| `backend-java-swl` | SÍ | Al generar código Spring Boot/JPA |
|
|
178
|
+
| `backend-go-swl` | SÍ | Al generar código Go con frameworks |
|
|
179
|
+
| `backend-rust-swl` | SÍ | Al generar código Axum/Actix |
|
|
180
|
+
| `backend-csharp-swl` | SÍ | Al generar código ASP.NET Core |
|
|
181
|
+
| `frontend-react-swl` | SÍ | Al generar código React/Next.js |
|
|
182
|
+
| `frontend-angular-swl` | SÍ | Al generar código Angular |
|
|
183
|
+
| `frontend-css-swl` | SÍ | Al usar PostCSS/Tailwind plugins |
|
|
184
|
+
| `frontend-tailwind-swl` | SÍ | Al usar Tailwind v4+ APIs |
|
|
185
|
+
| `frontend-swl` | SÍ | Al usar Vue/Svelte/Lit |
|
|
186
|
+
| `mobile-android-swl` | SÍ | Al usar Jetpack Compose libs |
|
|
187
|
+
| `mobile-ios-swl` | SÍ | Al usar SwiftUI/Combine |
|
|
188
|
+
| `mobile-cross-swl` | SÍ | Al usar React Native/Flutter |
|
|
189
|
+
| `implementador-swl` | SÍ | Generalista — siempre |
|
|
190
|
+
| `llm-apps-swl` | SÍ | LangChain, OpenAI SDK, Anthropic SDK |
|
|
191
|
+
| `pagos-swl` | SÍ | Stripe SDK actualizado |
|
|
192
|
+
| `datos-swl` | SÍ | ORMs y drivers de BD |
|
|
193
|
+
| `arquitecto-swl` | RECOMENDADO | Al evaluar tradeoffs entre librerías |
|
|
194
|
+
| `revisor-codigo-swl` | RECOMENDADO | Al detectar deps deprecadas en review |
|
|
195
|
+
| `revisor-seguridad-swl` | OBLIGATORIO | Al verificar CVEs de dependencias |
|
|
196
|
+
| Resto de agentes | NO | No generan código con dependencias externas |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Checklist de consulta Context7
|
|
201
|
+
|
|
202
|
+
Antes de marcar una tarea como completa:
|
|
203
|
+
|
|
204
|
+
- [ ] ¿Las dependencias agregadas/usadas se verificaron contra Context7?
|
|
205
|
+
- [ ] ¿La versión usada está alineada con la que Context7 reporta como actual?
|
|
206
|
+
- [ ] ¿No hay imports de APIs deprecadas según Context7?
|
|
207
|
+
- [ ] ¿Las dependencias transitivas conocidas no son warnings de
|
|
208
|
+
deprecación al instalar?
|
|
209
|
+
- [ ] Si Context7 no tenía la librería, ¿se documentó el motivo?
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Origen de esta regla
|
|
214
|
+
|
|
215
|
+
Esta regla se formalizó tras observar en la primera instalación pública
|
|
216
|
+
de `@saulwade/swl-ses@1.0.0` (2026-04-30) un warning de
|
|
217
|
+
`prebuild-install@7.1.3 deprecated` arrastrado por
|
|
218
|
+
`@xenova/transformers` que estaba en `optionalDependencies`. La
|
|
219
|
+
dependencia se eligió en una iteración anterior sin verificar Context7;
|
|
220
|
+
la migración oficial era `@huggingface/transformers`. El warning se vio
|
|
221
|
+
en cada `npm install` del paquete distribuido. Resuelto en v1.0.1
|
|
222
|
+
eliminando la dependencia opcional y elevando esta regla a obligatoria
|
|
223
|
+
para que no se repita.
|
|
224
|
+
|
|
225
|
+
Memoria asociada: `feedback_usar_context7.md` (preferencia previa del
|
|
226
|
+
usuario, formalizada como regla).
|
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
* Backend: el módulo NO requiere Xenova. Si SWL_SEMANTIC_BACKEND=xenova y
|
|
27
27
|
* @xenova/transformers está instalado, se usa para embeddings. Si no, Jaccard.
|
|
28
28
|
*
|
|
29
|
+
* Instalación opt-in del backend de embeddings:
|
|
30
|
+
*
|
|
31
|
+
* npm install -g @xenova/transformers
|
|
32
|
+
* export SWL_SEMANTIC_BACKEND=xenova
|
|
33
|
+
*
|
|
34
|
+
* @xenova/transformers ya NO es dependencia del paquete swl-ses (v1.0.1+)
|
|
35
|
+
* porque arrastra `prebuild-install@7.1.3` deprecated y sumaba ~50 MB al
|
|
36
|
+
* install para una feature opt-in raramente usada. El usuario que quiera
|
|
37
|
+
* el backend semántico lo instala manualmente.
|
|
38
|
+
*
|
|
29
39
|
* @module scripts/lib/semantic-search
|
|
30
40
|
*/
|
|
31
41
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* validar-userland-vacio.js — guard pre-publish.
|
|
6
|
+
*
|
|
7
|
+
* Verifica que `_userland/` solo contenga archivos `.gitkeep` antes de
|
|
8
|
+
* empaquetar el tarball. Si algún contributor accidentalmente commiteó
|
|
9
|
+
* agentes, skills o credenciales experimentales en `_userland/`, este
|
|
10
|
+
* gate aborta el publish con exit 1 y lista los archivos extraños.
|
|
11
|
+
*
|
|
12
|
+
* Por qué existe (defensa en profundidad):
|
|
13
|
+
* `_userland/` es el directorio donde el USUARIO instala su contenido
|
|
14
|
+
* custom. En el repo del sistema debe permanecer vacío (solo
|
|
15
|
+
* `.gitkeep` para preservar la estructura). El campo `files` de
|
|
16
|
+
* package.json incluye `_userland/`, así que cualquier archivo real
|
|
17
|
+
* que se cuele entrará al tarball publicado a npm — exposición
|
|
18
|
+
* potencial de IP o secretos. Aunque `.gitignore` y `.npmignore` ya
|
|
19
|
+
* cubren los vectores típicos (.env, credenciales), este guard
|
|
20
|
+
* garantiza que la regla "_userland/ vacío en distribución" se cumpla
|
|
21
|
+
* determinísticamente.
|
|
22
|
+
*
|
|
23
|
+
* Variables de entorno:
|
|
24
|
+
* SWL_USERLAND_GUARD_ALLOW=archivo1,archivo2 — permite archivos extra
|
|
25
|
+
* (uso excepcional documentado en commit que active la variable).
|
|
26
|
+
*
|
|
27
|
+
* Exit codes:
|
|
28
|
+
* 0 — OK, _userland/ solo contiene .gitkeep
|
|
29
|
+
* 1 — hay archivos extraños; lista impresa a stderr
|
|
30
|
+
* 2 — error de invariante (cwd incorrecto, _userland/ no existe)
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
const USERLAND_DIR = '_userland';
|
|
37
|
+
const PERMITIDOS_BASE = new Set(['.gitkeep', '.gitignore']);
|
|
38
|
+
|
|
39
|
+
function log(msg) { process.stdout.write(`[userland-guard] ${msg}\n`); }
|
|
40
|
+
function err(msg) { process.stderr.write(`[userland-guard] ERROR: ${msg}\n`); }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normaliza una ruta relativa al separador POSIX (`/`) para que la
|
|
44
|
+
* comparación contra la allowlist funcione idéntica en Windows y POSIX.
|
|
45
|
+
*/
|
|
46
|
+
function normalizar(rel) {
|
|
47
|
+
return rel.replace(/\\/g, '/');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function obtenerExtras() {
|
|
51
|
+
const extras = process.env.SWL_USERLAND_GUARD_ALLOW;
|
|
52
|
+
if (!extras) return new Set();
|
|
53
|
+
return new Set(extras.split(',').map(s => normalizar(s.trim())).filter(Boolean));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function listarRecursivo(dir, baseDir = dir, acumulador = []) {
|
|
57
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const full = path.join(dir, entry.name);
|
|
60
|
+
const rel = normalizar(path.relative(baseDir, full));
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
listarRecursivo(full, baseDir, acumulador);
|
|
63
|
+
} else if (entry.isFile()) {
|
|
64
|
+
acumulador.push(rel);
|
|
65
|
+
} else if (entry.isSymbolicLink()) {
|
|
66
|
+
// Symlinks dentro del paquete distribuido son anti-patrón.
|
|
67
|
+
acumulador.push(`${rel} (symlink)`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return acumulador;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function main() {
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
const userland = path.join(cwd, USERLAND_DIR);
|
|
76
|
+
|
|
77
|
+
if (!fs.existsSync(userland)) {
|
|
78
|
+
err(`${USERLAND_DIR}/ no existe en ${cwd}. ¿cwd incorrecto?`);
|
|
79
|
+
process.exit(2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const extrasPermitidos = obtenerExtras();
|
|
83
|
+
const archivos = listarRecursivo(userland);
|
|
84
|
+
|
|
85
|
+
const noPermitidos = archivos.filter(rel => {
|
|
86
|
+
const nombre = path.basename(rel);
|
|
87
|
+
if (PERMITIDOS_BASE.has(nombre)) return false;
|
|
88
|
+
if (extrasPermitidos.has(rel)) return false;
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (noPermitidos.length > 0) {
|
|
93
|
+
err(`${USERLAND_DIR}/ contiene ${noPermitidos.length} archivo(s) que no deberían distribuirse en el tarball npm:`);
|
|
94
|
+
for (const rel of noPermitidos) {
|
|
95
|
+
err(` - ${USERLAND_DIR}/${rel}`);
|
|
96
|
+
}
|
|
97
|
+
err('');
|
|
98
|
+
err('Acciones posibles:');
|
|
99
|
+
err(` 1. Mover el contenido fuera de ${USERLAND_DIR}/ si es del sistema base`);
|
|
100
|
+
err(` 2. Borrarlo si era código experimental local`);
|
|
101
|
+
err(` 3. Permitir explícitamente con SWL_USERLAND_GUARD_ALLOW="ruta1,ruta2"`);
|
|
102
|
+
err(' (documentar la excepción en el mensaje de commit)');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
log(`OK: ${USERLAND_DIR}/ solo contiene ${archivos.length} archivo(s) permitido(s).`);
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|