@saulwade/swl-ses 1.6.1 → 1.6.3

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 (50) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +4 -4
  3. package/agentes/_intent-spec.md +73 -0
  4. package/agentes/auto-evolucion-swl.md +24 -0
  5. package/agentes/cloud-infra-swl.md +25 -0
  6. package/agentes/datos-swl.md +23 -0
  7. package/agentes/devops-ci-swl.md +24 -0
  8. package/agentes/migrador-swl.md +22 -0
  9. package/agentes/pagos-swl.md +25 -0
  10. package/agentes/release-manager-swl.md +24 -0
  11. package/agentes/sre-swl.md +24 -0
  12. package/comandos/swl/planear-fase.md +16 -0
  13. package/habilidades/aprender-de-git-diff/SKILL.md +288 -0
  14. package/habilidades/diseno-herramientas-agente/SKILL.md +17 -1
  15. package/habilidades/meta-skills-estandar/SKILL.md +6 -0
  16. package/habilidades/meta-skills-estandar/recursos/skill-judge-rubrica.md +281 -0
  17. package/habilidades/proceso-autoverificacion-evidencias/SKILL.md +258 -0
  18. package/habilidades/proceso-confianza-pre-implementacion/SKILL.md +246 -0
  19. package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -0
  20. package/habilidades/proceso-ddia-streaming/SKILL.md +231 -0
  21. package/habilidades/proceso-intent-engineering/SKILL.md +269 -0
  22. package/habilidades/reducir-entropia/SKILL.md +219 -0
  23. package/hooks/lib/task-budget.js +218 -0
  24. package/hooks/validar-intent-spec.js +222 -0
  25. package/manifiestos/hooks-config.json +9 -0
  26. package/manifiestos/modulos.json +11 -2
  27. package/manifiestos/skills-lock.json +90 -41
  28. package/package.json +2 -2
  29. package/plugin.json +9 -2
  30. package/reglas/fragmentos-compartidos.md +26 -0
  31. package/reglas/intent-engineering.md +214 -0
  32. package/reglas/registro-componentes-nuevos.md +38 -0
  33. package/schemas/agent-frontmatter.schema.json +294 -167
  34. package/schemas/agent-message.schema.json +73 -53
  35. package/schemas/agent-output-implementacion.schema.json +114 -85
  36. package/schemas/agent-output-planificacion.schema.json +150 -113
  37. package/schemas/agent-output-review.schema.json +98 -78
  38. package/schemas/diary-entry.schema.json +42 -10
  39. package/schemas/hook-profiles.schema.json +54 -39
  40. package/schemas/hooks-config.schema.json +89 -74
  41. package/schemas/instinct.schema.json +152 -115
  42. package/schemas/modulos.schema.json +38 -29
  43. package/schemas/perfiles.schema.json +36 -28
  44. package/schemas/plugin.schema.json +77 -64
  45. package/schemas/skill-evals.schema.json +119 -95
  46. package/schemas/skill-frontmatter.schema.json +245 -170
  47. package/scripts/generar-inventario.js +3 -1
  48. package/scripts/lib/schema-version.js +164 -0
  49. package/scripts/validar-manifest.js +1 -1
  50. package/scripts/validar.js +3 -2
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * task-budget.js — presupuesto SEMÁNTICO de tokens por tarea.
5
+ *
6
+ * Adaptación de SuperClaude_Framework `pm_agent/token_budget.py` (MIT) a Node
7
+ * zero-deps. Provee un contrato de presupuesto por COMPLEJIDAD de tarea que
8
+ * agentes y orquestador pueden consultar para gating, NO un contador exacto
9
+ * de tokens consumidos (el modelo no expone tokens en runtime).
10
+ *
11
+ * IMPORTANTE: no confundir con `hooks/lib/token-budget.js`, que distribuye
12
+ * presupuesto entre BLOQUES DE CONTEXTO (memoria/observaciones/reglas) con
13
+ * scoring de recencia+importancia. Aquel es para inyección de contexto;
14
+ * éste es para clasificación de TAREAS.
15
+ *
16
+ * Niveles de complejidad y presupuesto recomendado:
17
+ * - simple: 200 tokens — typo fix, rename, comentario, formato.
18
+ * - medium: 1000 tokens — bugfix acotado, feature pequeña.
19
+ * - complex: 2500 tokens — feature grande, refactor cross-módulo.
20
+ *
21
+ * Uso típico:
22
+ * const { TaskBudget } = require('./task-budget');
23
+ * const budget = new TaskBudget('medium');
24
+ * if (budget.tryUse(150)) {
25
+ * // proceder con la sub-tarea
26
+ * } else {
27
+ * // presupuesto agotado — pausar, reportar, escalar
28
+ * }
29
+ *
30
+ * Semántica de "rebasado": si un agente declara tarea "simple" y consume
31
+ * 800 tokens, hay señal de que la clasificación fue incorrecta o el scope
32
+ * creció. El orquestador puede actuar (re-clasificar, pausar, alertar).
33
+ *
34
+ * @module hooks/lib/task-budget
35
+ */
36
+
37
+ /** @typedef {'simple' | 'medium' | 'complex'} ComplexityLevel */
38
+
39
+ /**
40
+ * Límites por defecto. Defaults recomendados por el material fuente
41
+ * (SuperClaude pm_agent). Modificables vía constructor para escenarios
42
+ * especiales.
43
+ * @type {Readonly<Record<ComplexityLevel, number>>}
44
+ */
45
+ const DEFAULT_LIMITS = Object.freeze({
46
+ simple: 200,
47
+ medium: 1000,
48
+ complex: 2500,
49
+ });
50
+
51
+ /**
52
+ * Gestor de presupuesto semántico por tarea.
53
+ *
54
+ * Inmutable en `limit` y `complexity` tras la construcción; mutable solo
55
+ * en `used` vía `tryUse()` / `forceUse()` / `reset()`.
56
+ */
57
+ class TaskBudget {
58
+ /**
59
+ * @param {ComplexityLevel} [complexity='medium'] - Complejidad declarada.
60
+ * @param {Partial<Record<ComplexityLevel, number>>} [overrides] - Límites custom.
61
+ */
62
+ constructor(complexity = 'medium', overrides = undefined) {
63
+ const limits = overrides
64
+ ? Object.freeze({ ...DEFAULT_LIMITS, ...overrides })
65
+ : DEFAULT_LIMITS;
66
+
67
+ // Default defensivo a 'medium' si llega un nivel desconocido — el
68
+ // material fuente hace lo mismo. Evita excepciones por typo.
69
+ const normalized = Object.prototype.hasOwnProperty.call(limits, complexity)
70
+ ? complexity
71
+ : 'medium';
72
+
73
+ /** @type {ComplexityLevel} */
74
+ this.complexity = /** @type {ComplexityLevel} */ (normalized);
75
+ /** @type {number} */
76
+ this.limit = limits[normalized];
77
+ /** @type {number} */
78
+ this.used = 0;
79
+ }
80
+
81
+ /**
82
+ * Intenta consumir `amount` tokens. Si excede el presupuesto, NO consume
83
+ * y retorna false. Útil cuando el caller debe decidir entre proceder o
84
+ * escalar.
85
+ *
86
+ * @param {number} amount - Cantidad a consumir. Debe ser número finito >= 0.
87
+ * @returns {boolean} true si se consumió, false si excedería el presupuesto.
88
+ * @throws {TypeError} si amount no es número finito >= 0.
89
+ */
90
+ tryUse(amount) {
91
+ if (!Number.isFinite(amount) || amount < 0) {
92
+ throw new TypeError(`TaskBudget.tryUse: amount debe ser número finito >= 0, recibido: ${amount}`);
93
+ }
94
+ if (this.used + amount > this.limit) {
95
+ return false;
96
+ }
97
+ this.used += amount;
98
+ return true;
99
+ }
100
+
101
+ /**
102
+ * Alias semántico de `tryUse` para callers que prefieren `allocate`.
103
+ * Mantiene paridad con el API original (pm_agent.token_budget.allocate).
104
+ *
105
+ * @param {number} amount
106
+ * @returns {boolean}
107
+ */
108
+ allocate(amount) {
109
+ return this.tryUse(amount);
110
+ }
111
+
112
+ /**
113
+ * Consume `amount` tokens FORZOSAMENTE, incluso si excede el presupuesto.
114
+ * Útil cuando el consumo ya ocurrió y se quiere registrar para auditoría.
115
+ * Retorna true si quedó dentro del presupuesto, false si lo rebasó.
116
+ *
117
+ * @param {number} amount
118
+ * @returns {boolean} true si dentro del presupuesto, false si lo rebasó.
119
+ * @throws {TypeError} si amount no es número finito >= 0.
120
+ */
121
+ forceUse(amount) {
122
+ if (!Number.isFinite(amount) || amount < 0) {
123
+ throw new TypeError(`TaskBudget.forceUse: amount debe ser número finito >= 0, recibido: ${amount}`);
124
+ }
125
+ this.used += amount;
126
+ return this.used <= this.limit;
127
+ }
128
+
129
+ /**
130
+ * Tokens disponibles restantes. Puede ser negativo si se usó `forceUse`
131
+ * con consumo que rebasó el límite.
132
+ * @returns {number}
133
+ */
134
+ get remaining() {
135
+ return this.limit - this.used;
136
+ }
137
+
138
+ /**
139
+ * Fracción del presupuesto consumida en [0, ∞). 1.0 = exactamente lleno;
140
+ * >1.0 = rebasado. Útil para alertas "65% consumido".
141
+ * @returns {number}
142
+ */
143
+ get usedFraction() {
144
+ return this.limit === 0 ? 0 : this.used / this.limit;
145
+ }
146
+
147
+ /**
148
+ * true si el presupuesto fue rebasado (used > limit). Señal operacional
149
+ * de mala clasificación de complejidad o scope creep.
150
+ * @returns {boolean}
151
+ */
152
+ get isOver() {
153
+ return this.used > this.limit;
154
+ }
155
+
156
+ /**
157
+ * Resetea el contador de usados. NO cambia el límite ni la complejidad.
158
+ */
159
+ reset() {
160
+ this.used = 0;
161
+ }
162
+
163
+ /**
164
+ * Representación para logs / debugging.
165
+ * @returns {string}
166
+ */
167
+ toString() {
168
+ return `TaskBudget(complexity=${this.complexity}, limit=${this.limit}, used=${this.used})`;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Sugiere nivel de complejidad a partir de heurística simple sobre el
174
+ * descriptor de la tarea. NO es clasificación inteligente — es atajo para
175
+ * callers que no quieren razonar el nivel manualmente.
176
+ *
177
+ * Heurística:
178
+ * - menciona "typo", "rename", "format", "comment" → simple
179
+ * - menciona "feature", "refactor", "migration", "arquitectura" → complex
180
+ * - resto → medium
181
+ *
182
+ * @param {string} description - Descripción libre de la tarea.
183
+ * @returns {ComplexityLevel}
184
+ */
185
+ function suggestComplexity(description) {
186
+ if (typeof description !== 'string' || description.length === 0) {
187
+ return 'medium';
188
+ }
189
+ const lower = description.toLowerCase();
190
+
191
+ const simpleHints = ['typo', 'rename', 'format', 'comment', 'whitespace', 'comentario', 'formato'];
192
+ if (simpleHints.some((hint) => lower.includes(hint))) {
193
+ return 'simple';
194
+ }
195
+
196
+ const complexHints = [
197
+ 'refactor',
198
+ 'migration',
199
+ 'migración',
200
+ 'feature nueva',
201
+ 'cross-module',
202
+ 'cross-modulo',
203
+ 'arquitectura',
204
+ 'rewrite',
205
+ 'reescritura',
206
+ ];
207
+ if (complexHints.some((hint) => lower.includes(hint))) {
208
+ return 'complex';
209
+ }
210
+
211
+ return 'medium';
212
+ }
213
+
214
+ module.exports = {
215
+ TaskBudget,
216
+ DEFAULT_LIMITS,
217
+ suggestComplexity,
218
+ };
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: validar-intent-spec.js
6
+ * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit sobre agentes/*.md)
7
+ *
8
+ * Verifica que los agentes con `nivelRiesgo: ALTO` declaren las 6 partes
9
+ * del framework Intent Engineering exigidas por `reglas/intent-engineering.md`:
10
+ * - strategy (Parte 1)
11
+ * - healthMetrics (Parte 4)
12
+ * - steering (Parte 6 — prompt-level)
13
+ * - hardGuardrails (Parte 6 — orchestration-level)
14
+ * - fragmentos: [_intent-spec]
15
+ * - maxTurnos (Parte 8 — Stop Rules)
16
+ *
17
+ * Comportamiento:
18
+ * - NUNCA bloquea (exit code 0 siempre — blocking:false en hooks-config)
19
+ * - Solo emite nudge cuando faltan campos en agente ALTO — ruido mínimo
20
+ * - Solo se dispara con archivos `agentes/*.md` (no fragmentos `_*.md`)
21
+ * - Respeta exclusiones: temp/, node_modules/, respositorios-git/, _userland/
22
+ *
23
+ * Opt-out: SWL_INTENT_SPEC=0 desactiva completamente el hook.
24
+ *
25
+ * Formato del nudge:
26
+ * {
27
+ * id: string,
28
+ * kind: "intent-spec-incomplete",
29
+ * target: "auto-evolucion-swl",
30
+ * source: "hooks/validar-intent-spec.js",
31
+ * message: "...",
32
+ * data: { archivo, agente, faltan: [...] },
33
+ * ts: ISO,
34
+ * accionado: false
35
+ * }
36
+ *
37
+ * Origen: ADR-0027 (F3 Opción B integración Lead Agents + DDIA).
38
+ * Plan de promoción a `blocking:true`: tras 2 semanas sin falsos positivos
39
+ * documentados, con nudges accionados, considerar bloqueo activo.
40
+ */
41
+
42
+ const fs = require('fs');
43
+ const path = require('path');
44
+ const crypto = require('crypto');
45
+
46
+ // ─── Opt-out global ───────────────────────────────────────────────────────
47
+ if (process.env.SWL_INTENT_SPEC === '0') {
48
+ process.exit(0);
49
+ }
50
+
51
+ let hookInput = '';
52
+ try {
53
+ hookInput = fs.readFileSync(0, 'utf-8');
54
+ } catch (_) {
55
+ process.exit(0);
56
+ }
57
+
58
+ let evento;
59
+ try {
60
+ evento = JSON.parse(hookInput);
61
+ } catch (_) {
62
+ process.exit(0);
63
+ }
64
+
65
+ const toolName = evento?.tool_name;
66
+ const toolInput = evento?.tool_input;
67
+
68
+ if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
69
+ process.exit(0);
70
+ }
71
+
72
+ const filePath = toolInput?.file_path;
73
+ if (!filePath) {
74
+ process.exit(0);
75
+ }
76
+
77
+ const pathNormalized = filePath.replace(/\\/g, '/');
78
+
79
+ // Solo agentes/*.md (NO fragmentos _*.md)
80
+ if (!/\/agentes\/[^_][^/]*\.md$/.test(pathNormalized)) {
81
+ process.exit(0);
82
+ }
83
+
84
+ const RUTAS_EXCLUIDAS = [
85
+ '/temp/',
86
+ '/node_modules/',
87
+ '/respositorios-git/',
88
+ '/_userland/',
89
+ '/.planning/',
90
+ ];
91
+ if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
92
+ process.exit(0);
93
+ }
94
+
95
+ if (!fs.existsSync(filePath)) {
96
+ process.exit(0);
97
+ }
98
+
99
+ // ─── Parsear frontmatter YAML ─────────────────────────────────────────────
100
+ let contenido;
101
+ try {
102
+ contenido = fs.readFileSync(filePath, 'utf-8');
103
+ } catch (_) {
104
+ process.exit(0);
105
+ }
106
+
107
+ // Normalizar line endings (CRLF Windows → LF) antes del regex.
108
+ // Sin esto el hook nunca detectaba frontmatter en archivos guardados desde
109
+ // Git for Windows con autocrlf=true (bug reportado 2026-05-18).
110
+ contenido = contenido.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
111
+
112
+ const matchFM = contenido.match(/^---\n([\s\S]+?)\n---/);
113
+ if (!matchFM) {
114
+ // Sin frontmatter — no es un agente conforme. Otros validadores lo detectan.
115
+ process.exit(0);
116
+ }
117
+
118
+ const yamlBlob = matchFM[1];
119
+
120
+ // Parser mínimo de frontmatter (sin dependencias). Solo lee:
121
+ // - nivelRiesgo: VALOR
122
+ // - presencia de claves de primer nivel
123
+ const lineas = yamlBlob.split('\n');
124
+ const claves = new Set();
125
+ let nivelRiesgo = null;
126
+ let fragmentosTieneIntentSpec = false;
127
+ let dentroDeFragmentos = false;
128
+
129
+ // Strip inline YAML comments (' # ...' o '\t# ...') y comillas wrapping.
130
+ // Sin esto, `nivelRiesgo: ALTO # ejemplo` se parsearía como "ALTO # ejemplo"
131
+ // y la comparación con 'ALTO' fallaría silenciosamente (bug latente 2026-05-18).
132
+ function limpiarValorYaml(crudo) {
133
+ let v = crudo;
134
+ // Comentario inline: '#' precedido de whitespace (no '#' dentro de strings comillados,
135
+ // que en este parser mínimo no soportamos pero sí intentamos respetar).
136
+ const m = v.match(/^(.*?)(?:\s+#.*)?$/);
137
+ if (m) v = m[1];
138
+ v = v.trim();
139
+ // Strip comillas wrapping si las hay
140
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
141
+ v = v.slice(1, -1);
142
+ }
143
+ return v;
144
+ }
145
+
146
+ for (const linea of lineas) {
147
+ // Detectar clave de primer nivel (sin indent)
148
+ const m = linea.match(/^([a-zA-Z_][a-zA-Z0-9_]*):/);
149
+ if (m) {
150
+ claves.add(m[1]);
151
+ if (m[1] === 'nivelRiesgo') {
152
+ const crudo = linea.split(':').slice(1).join(':').trim();
153
+ nivelRiesgo = limpiarValorYaml(crudo);
154
+ dentroDeFragmentos = false;
155
+ } else if (m[1] === 'fragmentos') {
156
+ dentroDeFragmentos = true;
157
+ // Caso fragmentos: [_intent-spec] inline
158
+ if (linea.includes('_intent-spec')) {
159
+ fragmentosTieneIntentSpec = true;
160
+ }
161
+ } else {
162
+ dentroDeFragmentos = false;
163
+ }
164
+ } else if (dentroDeFragmentos && linea.includes('_intent-spec')) {
165
+ fragmentosTieneIntentSpec = true;
166
+ }
167
+ }
168
+
169
+ // Solo evaluar agentes ALTO
170
+ if (nivelRiesgo !== 'ALTO') {
171
+ process.exit(0);
172
+ }
173
+
174
+ // Verificar las 6 partes obligatorias
175
+ const CAMPOS_OBLIGATORIOS = ['strategy', 'healthMetrics', 'steering', 'hardGuardrails', 'maxTurnos'];
176
+ const faltan = CAMPOS_OBLIGATORIOS.filter((c) => !claves.has(c));
177
+
178
+ if (!fragmentosTieneIntentSpec) {
179
+ faltan.push('fragmentos:[_intent-spec]');
180
+ }
181
+
182
+ if (faltan.length === 0) {
183
+ process.exit(0);
184
+ }
185
+
186
+ // ─── Emitir nudge ─────────────────────────────────────────────────────────
187
+ const CWD = process.cwd();
188
+ const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
189
+ const nombreAgente = path.basename(filePath, '.md');
190
+
191
+ const nudge = {
192
+ id: crypto.randomBytes(8).toString('hex'),
193
+ kind: 'intent-spec-incomplete',
194
+ target: 'auto-evolucion-swl',
195
+ source: 'hooks/validar-intent-spec.js',
196
+ message:
197
+ `Agente ALTO riesgo ${nombreAgente} no declara intent spec completo. ` +
198
+ `Faltan: ${faltan.join(', ')}. Cargar Skill("proceso-intent-engineering") + ` +
199
+ `regla intent-engineering.md y completar frontmatter.`,
200
+ data: {
201
+ archivo: rutaRelativa,
202
+ agente: nombreAgente,
203
+ nivelRiesgo: 'ALTO',
204
+ faltan,
205
+ },
206
+ ts: new Date().toISOString(),
207
+ accionado: false,
208
+ };
209
+
210
+ const nudgesDir = path.join(CWD, '.planning', 'evolucion');
211
+ const nudgesFile = path.join(nudgesDir, 'nudges.jsonl');
212
+
213
+ try {
214
+ if (!fs.existsSync(nudgesDir)) {
215
+ fs.mkdirSync(nudgesDir, { recursive: true });
216
+ }
217
+ fs.appendFileSync(nudgesFile, JSON.stringify(nudge) + '\n');
218
+ } catch (_) {
219
+ // Si falla persistir, no romper el flujo del usuario
220
+ }
221
+
222
+ process.exit(0);
@@ -356,6 +356,15 @@
356
356
  "maxConsecutiveFailures": 5,
357
357
  "degradeOnFailure": "skip"
358
358
  },
359
+ "validar-intent-spec.js": {
360
+ "event": "PostToolUse",
361
+ "matcher": "Write|Edit|MultiEdit",
362
+ "description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolucion/nudges.jsonl si faltan campos. No bloquea (modo warn-only inicial; promocion a blocking:true tras 2 semanas estables). Excluye temp/, node_modules/, respositorios-git/, _userland/, .planning/. Opt-out: SWL_INTENT_SPEC=0. ADR-0027.",
363
+ "blocking": false,
364
+ "async": true,
365
+ "maxConsecutiveFailures": 5,
366
+ "degradeOnFailure": "skip"
367
+ },
359
368
  "notificacion-telegram.js": {
360
369
  "event": "Stop",
361
370
  "matcher": "",
@@ -234,7 +234,14 @@
234
234
  "habilidades/tracing-processor",
235
235
  "habilidades/guardrail-semantico",
236
236
  "habilidades/legacy-code-rescue",
237
- "habilidades/harness-claude-code"
237
+ "habilidades/harness-claude-code",
238
+ "habilidades/proceso-confianza-pre-implementacion",
239
+ "habilidades/proceso-autoverificacion-evidencias",
240
+ "habilidades/aprender-de-git-diff",
241
+ "habilidades/reducir-entropia",
242
+ "habilidades/proceso-intent-engineering",
243
+ "habilidades/proceso-ddia-fundamentos",
244
+ "habilidades/proceso-ddia-streaming"
238
245
  ],
239
246
  "targets": [
240
247
  "claude",
@@ -887,7 +894,8 @@
887
894
  "reglas/arreglar-al-detectar.md",
888
895
  "reglas/analisis-previo-tareas-grandes.md",
889
896
  "reglas/registro-componentes-nuevos.md",
890
- "reglas/auditorias-documentales-estructurales.md"
897
+ "reglas/auditorias-documentales-estructurales.md",
898
+ "reglas/intent-engineering.md"
891
899
  ],
892
900
  "targets": [
893
901
  "claude",
@@ -1006,6 +1014,7 @@
1006
1014
  "hooks/inbox-aviso.js",
1007
1015
  "hooks/aiisms-detector.js",
1008
1016
  "hooks/claudemd-bloat-detector.js",
1017
+ "hooks/validar-intent-spec.js",
1009
1018
  "hooks/sugerir-regenerar-inventario.js",
1010
1019
  "hooks/sugerir-contribuir.js",
1011
1020
  "hooks/_run-hook.sh",