@saulwade/swl-ses 2.3.0 → 2.3.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 +47 -6
- package/README.md +1 -1
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +472 -472
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +479 -479
- package/agentes/backend-python-swl.md +605 -605
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +605 -605
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +2 -2
- package/agentes/implementador-swl.md +328 -328
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +278 -278
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +918 -918
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/orquestador-swl.md +1026 -1026
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +1 -1
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +245 -245
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +505 -505
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +291 -291
- package/agentes/tdd-qa-swl.md +393 -393
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/predecir.md +139 -139
- package/comandos/swl/sesiones.md +1 -1
- package/gateway/agent-executor.js +1 -1
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/agent-deep-links/SKILL.md +148 -148
- package/habilidades/agentes-como-servicio/SKILL.md +2 -2
- package/habilidades/angular-moderno/SKILL.md +20 -1
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
- package/habilidades/backend-async-postgres-testing/SKILL.md +215 -215
- package/habilidades/backend-error-design/SKILL.md +221 -221
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/calidad-anti-patrones-universales/SKILL.md +368 -368
- package/habilidades/calidad-contract-testing/SKILL.md +165 -165
- package/habilidades/calidad-mutation-testing/SKILL.md +170 -170
- package/habilidades/changelog-generator/scripts/parse-commits.js +367 -367
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/drift-detection/SKILL.md +179 -179
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +1 -1
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +252 -252
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/perfil-usuario/SKILL.md +200 -200
- package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -255
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -105
- package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
- package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -138
- package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
- package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
- package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
- package/habilidades/proceso-modular-split/SKILL.md +256 -256
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +2 -2
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/structured-outputs/SKILL.md +2 -2
- package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
- package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
- package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
- package/habilidades/swl-dashboard/SKILL.md +2 -2
- package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +1 -1
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +1 -1
- package/hooks/ciclo-evolucion-subagente.js +26 -26
- package/hooks/ciclo-evolucion.js +26 -26
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/claudemd-duplicacion-detector.js +170 -170
- package/hooks/contexto-iteracion.js +144 -144
- package/hooks/guardrail-modelo.js +1 -1
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/autonomia.js +208 -208
- package/hooks/lib/ciclo-evolucion.js +47 -47
- package/hooks/lib/deep-links.js +185 -185
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/etapa-auto-evolucion.js +701 -701
- package/hooks/lib/etapa-metricas.js +388 -388
- package/hooks/lib/etapa-perfil-usuario.js +376 -376
- package/hooks/lib/loop-telemetry.js +321 -321
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/model-router.js +2 -2
- package/hooks/lib/propose-step.js +358 -358
- 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 -201
- 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/token-estimator.js +1 -0
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/usage-model.js +1 -1
- package/hooks/linea-estado.js +2 -0
- package/hooks/registro-turnos.js +209 -209
- package/hooks/session-briefing.js +98 -98
- package/hooks/spec-gate.js +211 -211
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/tdd-gate.js +241 -241
- package/hooks/telemetria-skill-routing.js +100 -100
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-intent-spec.js +242 -242
- package/hooks/validar-memoria-hook.js +218 -218
- package/hooks/validar-planning-paths.js +134 -134
- package/instintos/autonomia.yaml +27 -27
- package/instintos/prompt-appendices.yaml +57 -57
- package/llms.txt +29 -29
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +2291 -1964
- package/manifiestos/planning-paths.json +44 -44
- package/manifiestos/skills-lock.json +1282 -1282
- package/package.json +1 -1
- 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 +1 -1
- package/reglas/accesibilidad.md +10 -10
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/api-diseno.md +9 -9
- package/reglas/arreglar-al-detectar.md +240 -240
- package/reglas/auditorias-documentales-estructurales.md +7 -7
- package/reglas/cloud-infra.md +8 -8
- package/reglas/consultar-vault-primero.md +195 -195
- package/reglas/debatir-antes-de-aceptar.md +158 -158
- package/reglas/fragmentos-compartidos.md +183 -183
- package/reglas/hooks.md +6 -6
- package/reglas/intent-engineering.md +218 -218
- package/reglas/markitdown.md +8 -8
- package/reglas/monitor-ci.md +309 -309
- package/reglas/patrones.md +6 -6
- package/reglas/sesiones-paralelas.md +180 -180
- package/reglas/sin-duplicacion-reglas-globales.md +182 -182
- package/reglas/skills-estandar.md +6 -6
- package/reglas/testing.md +7 -7
- package/reglas/tests-cleanup.md +224 -224
- package/reglas/usar-code-review-graph.md +155 -155
- package/reglas/usar-context7.md +226 -226
- package/reglas/verificar-citas-normativas.md +548 -548
- package/schemas/agent-frontmatter.schema.json +3 -3
- package/schemas/agent-message.schema.json +73 -73
- package/schemas/agent-output-implementacion.schema.json +114 -114
- package/schemas/agent-output-planificacion.schema.json +150 -150
- package/schemas/agent-output-review.schema.json +98 -98
- package/schemas/diary-entry.schema.json +112 -112
- package/schemas/hook-profiles.schema.json +54 -54
- package/schemas/hooks-config.schema.json +89 -89
- package/schemas/modulos.schema.json +38 -38
- package/schemas/perfiles.schema.json +36 -36
- package/schemas/plugin.schema.json +77 -77
- package/schemas/skill-evals.schema.json +119 -119
- package/schemas/skill-frontmatter.schema.json +245 -245
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/cli/aprobar-plan.js +73 -73
- package/scripts/cli/briefing.js +23 -23
- package/scripts/cli/ciclo-evolucion.js +26 -26
- package/scripts/cli/configurar-ci.js +40 -40
- package/scripts/cli/derivar-feature-list.js +25 -25
- package/scripts/cli/detectar-host.js +27 -27
- package/scripts/cli/diary-entry.js +69 -69
- package/scripts/cli/execution-state.js +18 -18
- package/scripts/cli/gateway-notify.js +41 -41
- package/scripts/cli/liberar-fase.js +42 -42
- package/scripts/cli/loop-telemetry.js +125 -125
- package/scripts/cli/mark-evolved.js +56 -56
- package/scripts/cli/metricas-dora.js +26 -26
- package/scripts/cli/near-duplicate.js +55 -55
- package/scripts/cli/notificaciones.js +123 -123
- package/scripts/cli/propose-step.js +29 -29
- package/scripts/cli/schedule-parse.js +19 -19
- package/scripts/cli/sugerir-modelo.js +20 -20
- package/scripts/cli/verificar-plan.js +36 -36
- package/scripts/cli/verificar-trazabilidad.js +35 -35
- 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-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/auditar-invocaciones-comandos.js +104 -104
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/ci-reader.js +193 -193
- package/scripts/lib/claude-sessions.js +1 -0
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-host-swl.js +175 -175
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
- package/scripts/lib/detector-reglas-duplicadas.js +220 -220
- 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/evidencia-release.js +322 -322
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/gate-hooks-requires.js +249 -249
- package/scripts/lib/gate-licencias.js +212 -212
- package/scripts/lib/git-metricas.js +257 -257
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/metricas-dora.js +204 -204
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/plan-lock.js +275 -275
- package/scripts/lib/pr-analyzer.js +399 -399
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/reglas-globales-conocidas.json +180 -180
- package/scripts/lib/resolver-plan-fase.js +37 -37
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/schema-version.js +164 -164
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/lib/tool-cost-analyzer.js +1 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +200 -200
- package/scripts/publicar.js +497 -497
- package/scripts/run-eval.js +141 -141
- package/scripts/tui/componentes/selector-multi.js +189 -189
- package/scripts/tui/componentes/selector-unico.js +158 -158
- package/scripts/tui/ejecutores.js +375 -375
- package/scripts/tui/index.js +162 -162
- package/scripts/tui/lib/colores.js +129 -129
- package/scripts/tui/lib/render.js +264 -264
- package/scripts/tui/lib/teclas.js +113 -113
- package/scripts/tui/pantallas/inspect.js +173 -173
- package/scripts/tui/pantallas/install-wizard.js +334 -334
- package/scripts/tui/pantallas/menu-principal.js +52 -52
- package/scripts/tui/pantallas/progreso.js +274 -274
- package/scripts/tui/pantallas/resumen.js +132 -132
- package/scripts/tui/pantallas/uninstall-wizard.js +208 -208
- package/scripts/tui/pantallas/update-wizard.js +232 -232
- package/scripts/tui/pantallas/welcome.js +187 -187
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,367 +1,367 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* parse-commits.js — Parser determinista de Conventional Commits para
|
|
5
|
-
* generación automática de CHANGELOG en /swl:release.
|
|
6
|
-
*
|
|
7
|
-
* Lee commits desde `git log` (entre dos refs o desde la última tag) y los
|
|
8
|
-
* clasifica según el spec de Conventional Commits 1.0.0:
|
|
9
|
-
* https://www.conventionalcommits.org/en/v1.0.0/
|
|
10
|
-
*
|
|
11
|
-
* Categorías generadas (formato Keep a Changelog):
|
|
12
|
-
* - feat -> "Nuevas funcionalidades"
|
|
13
|
-
* - fix -> "Correcciones"
|
|
14
|
-
* - perf -> "Mejoras de rendimiento"
|
|
15
|
-
* - refactor -> "Cambios internos"
|
|
16
|
-
* - docs / style / test / ci / build / chore -> "Mantenimiento" (opcional)
|
|
17
|
-
* - BREAKING CHANGE / ! -> "Breaking changes" (siempre top de cada release)
|
|
18
|
-
* - Sin prefijo CC válido -> "Otros" (con bandera de baja conformidad)
|
|
19
|
-
*
|
|
20
|
-
* Fallback: si la conformidad de commits es <80%, el script retorna
|
|
21
|
-
* `conformidad: <ratio>` para que el caller decida si abortar o seguir.
|
|
22
|
-
*
|
|
23
|
-
* Uso desde CLI:
|
|
24
|
-
* node parse-commits.js [--from <ref>] [--to <ref>] [--format json|markdown]
|
|
25
|
-
*
|
|
26
|
-
* Uso programático:
|
|
27
|
-
* const { parsearCommits, generarChangelog } = require('./parse-commits');
|
|
28
|
-
* const { categorias, conformidad } = parsearCommits(commits);
|
|
29
|
-
* const md = generarChangelog(categorias, { version: '1.6.5', fecha: '...' });
|
|
30
|
-
*
|
|
31
|
-
* Zero-deps. Cargado por skill `changelog-generator` y por
|
|
32
|
-
* `/swl:release` paso 7.
|
|
33
|
-
*
|
|
34
|
-
* @module habilidades/changelog-generator/scripts/parse-commits
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
const { execSync } = require('node:child_process');
|
|
38
|
-
|
|
39
|
-
// -----------------------------------------------------------------------------
|
|
40
|
-
// Spec Conventional Commits
|
|
41
|
-
// -----------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Regex que matchea una línea de commit Conventional Commits.
|
|
45
|
-
* Grupos: [1]=tipo, [2]=scope?, [3]=!?, [4]=descripcion
|
|
46
|
-
*
|
|
47
|
-
* Ejemplos válidos:
|
|
48
|
-
* feat(auth): agregar validación PKCE
|
|
49
|
-
* fix: corregir cálculo IEPS
|
|
50
|
-
* feat!: breaking change inline
|
|
51
|
-
* refactor(hooks/lib): split evolution-tracker
|
|
52
|
-
* chore(release): bump version
|
|
53
|
-
* [T-01] feat(hooks): prefijo de trazabilidad de tarea (ejecutar-fase)
|
|
54
|
-
* [T-07][T-08] refactor(hooks): varios prefijos consecutivos
|
|
55
|
-
* [REQ-08] feat(scripts): prefijo de requisito
|
|
56
|
-
* [F12·T-01] feat(hooks): prefijo namespaceado por fase (DT-IDS-NAMESPACE)
|
|
57
|
-
*
|
|
58
|
-
* El prefijo opcional `[T-NN]` / `[REQ-NN]` / `[F<fase>·T-NN]` (uno o más,
|
|
59
|
-
* consecutivos) proviene de la convención de commits de
|
|
60
|
-
* `habilidades/ejecutar-fase/SKILL.md` (trazabilidad de tarea/requisito; desde
|
|
61
|
-
* la Fase 12 los IDs se namespacean por fase). No altera la semántica CC: el
|
|
62
|
-
* tipo+scope+descripción siguen presentes después del/los prefijo(s), por lo
|
|
63
|
-
* que el commit cuenta como conforme. Grupo non-capturing para no desplazar
|
|
64
|
-
* los índices [1]=tipo [2]=scope [3]=! [4]=descripcion.
|
|
65
|
-
*/
|
|
66
|
-
const RE_CONVENTIONAL = /^(?:\[(?:F\d+·)?(?:T|REQ)-\d+(?:-\d+)?\]\s*)*(feat|fix|perf|refactor|docs|style|test|ci|build|chore|revert|evolucion|evolve)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
|
|
67
|
-
|
|
68
|
-
/** Mapa tipo CC → categoría Keep a Changelog en es-MX. */
|
|
69
|
-
const CATEGORIAS = Object.freeze({
|
|
70
|
-
feat: 'Nuevas funcionalidades',
|
|
71
|
-
fix: 'Correcciones',
|
|
72
|
-
perf: 'Mejoras de rendimiento',
|
|
73
|
-
refactor: 'Cambios internos',
|
|
74
|
-
revert: 'Reversiones',
|
|
75
|
-
evolucion: 'Evoluciones de skills/agentes',
|
|
76
|
-
evolve: 'Evoluciones de skills/agentes',
|
|
77
|
-
docs: 'Mantenimiento',
|
|
78
|
-
style: 'Mantenimiento',
|
|
79
|
-
test: 'Mantenimiento',
|
|
80
|
-
ci: 'Mantenimiento',
|
|
81
|
-
build: 'Mantenimiento',
|
|
82
|
-
chore: 'Mantenimiento',
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
/** Orden de salida en CHANGELOG (Breaking siempre primero). */
|
|
86
|
-
const ORDEN_CATEGORIAS = Object.freeze([
|
|
87
|
-
'Breaking changes',
|
|
88
|
-
'Nuevas funcionalidades',
|
|
89
|
-
'Correcciones',
|
|
90
|
-
'Mejoras de rendimiento',
|
|
91
|
-
'Cambios internos',
|
|
92
|
-
'Reversiones',
|
|
93
|
-
'Evoluciones de skills/agentes',
|
|
94
|
-
'Mantenimiento',
|
|
95
|
-
'Otros',
|
|
96
|
-
]);
|
|
97
|
-
|
|
98
|
-
// -----------------------------------------------------------------------------
|
|
99
|
-
// Transformación técnica → user-facing
|
|
100
|
-
// -----------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Aplica heurísticas de transformación para hacer la línea más legible al
|
|
104
|
-
* usuario final sin contradecir lo que dice el commit.
|
|
105
|
-
*
|
|
106
|
-
* Reglas conservadoras: solo elimina prefijos técnicos y normaliza la primera
|
|
107
|
-
* letra. NO inventa contenido; si la descripción es críptica, queda críptica.
|
|
108
|
-
*
|
|
109
|
-
* @param {string} descripcion - Descripción cruda del commit.
|
|
110
|
-
* @returns {string} Versión legible.
|
|
111
|
-
*/
|
|
112
|
-
function transformarLegible(descripcion) {
|
|
113
|
-
let texto = descripcion.trim();
|
|
114
|
-
|
|
115
|
-
// Eliminar referencias a issues al final si son ruido (#123, refs #...).
|
|
116
|
-
texto = texto.replace(/\s*\(#\d+\)\s*$/, '');
|
|
117
|
-
texto = texto.replace(/\s*[Rr]efs?:?\s*#\d+\s*$/, '');
|
|
118
|
-
|
|
119
|
-
// Capitalizar primera letra.
|
|
120
|
-
if (texto.length > 0) {
|
|
121
|
-
texto = texto[0].toUpperCase() + texto.slice(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Eliminar punto final si existe (Keep a Changelog usa bullets sin punto).
|
|
125
|
-
texto = texto.replace(/\.$/, '');
|
|
126
|
-
|
|
127
|
-
return texto;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// -----------------------------------------------------------------------------
|
|
131
|
-
// Parser principal
|
|
132
|
-
// -----------------------------------------------------------------------------
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Detecta si el commit tiene marca de breaking change según el spec
|
|
136
|
-
* Conventional Commits 1.0.0:
|
|
137
|
-
* - `!` después del tipo/scope en el subject, o
|
|
138
|
-
* - Trailer `BREAKING CHANGE:` o `BREAKING-CHANGE:` al INICIO de una línea
|
|
139
|
-
* del body (no en medio de prosa). El spec requiere este formato literal
|
|
140
|
-
* como trailer; una mención de la palabra dentro de la descripción NO
|
|
141
|
-
* dispara breaking change.
|
|
142
|
-
*
|
|
143
|
-
* Bug histórico (fix v1.6.5): la regex previa `/BREAKING\s+CHANGE/i` generaba
|
|
144
|
-
* falsos positivos cuando el commit describía features que mencionaban
|
|
145
|
-
* "breaking changes" en su prosa sin ser un breaking change ellos mismos.
|
|
146
|
-
*
|
|
147
|
-
* @param {string} mensajeCompleto - Mensaje completo del commit (subject + body).
|
|
148
|
-
* @param {string} marcaExclamacion - Match del grupo `!` del regex.
|
|
149
|
-
* @returns {boolean}
|
|
150
|
-
*/
|
|
151
|
-
function esBreakingChange(mensajeCompleto, marcaExclamacion) {
|
|
152
|
-
if (marcaExclamacion === '!') return true;
|
|
153
|
-
// Trailer al inicio de línea, mayúsculas obligatorias, dos puntos al final.
|
|
154
|
-
return /^BREAKING[-\s]CHANGE:/m.test(mensajeCompleto);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Parsea una lista de commits y los clasifica.
|
|
159
|
-
*
|
|
160
|
-
* @param {Array<{hash: string, subject: string, body?: string}>} commits
|
|
161
|
-
* @returns {{
|
|
162
|
-
* categorias: Map<string, Array<{hash: string, descripcion: string, scope?: string}>>,
|
|
163
|
-
* conformidad: number,
|
|
164
|
-
* totalCommits: number,
|
|
165
|
-
* conformes: number,
|
|
166
|
-
* }}
|
|
167
|
-
*/
|
|
168
|
-
function parsearCommits(commits) {
|
|
169
|
-
const categorias = new Map();
|
|
170
|
-
for (const cat of ORDEN_CATEGORIAS) categorias.set(cat, []);
|
|
171
|
-
|
|
172
|
-
let conformes = 0;
|
|
173
|
-
|
|
174
|
-
for (const commit of commits) {
|
|
175
|
-
const { hash, subject = '', body = '' } = commit;
|
|
176
|
-
const completo = `${subject}\n${body}`;
|
|
177
|
-
const match = subject.match(RE_CONVENTIONAL);
|
|
178
|
-
|
|
179
|
-
if (!match) {
|
|
180
|
-
categorias.get('Otros').push({
|
|
181
|
-
hash,
|
|
182
|
-
descripcion: transformarLegible(subject),
|
|
183
|
-
});
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
conformes += 1;
|
|
188
|
-
const [, tipo, scope, marca, descripcion] = match;
|
|
189
|
-
const entrada = {
|
|
190
|
-
hash,
|
|
191
|
-
descripcion: transformarLegible(descripcion),
|
|
192
|
-
scope: scope || null,
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
// Breaking change tiene precedencia sobre la categoría natural.
|
|
196
|
-
if (esBreakingChange(completo, marca)) {
|
|
197
|
-
categorias.get('Breaking changes').push(entrada);
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const categoria = CATEGORIAS[tipo] || 'Otros';
|
|
202
|
-
categorias.get(categoria).push(entrada);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
categorias,
|
|
207
|
-
conformidad: commits.length === 0 ? 1 : conformes / commits.length,
|
|
208
|
-
totalCommits: commits.length,
|
|
209
|
-
conformes,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// -----------------------------------------------------------------------------
|
|
214
|
-
// Lectura desde git
|
|
215
|
-
// -----------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Obtiene commits desde git en formato estructurado.
|
|
219
|
-
*
|
|
220
|
-
* @param {object} opts
|
|
221
|
-
* @param {string} [opts.from] - Ref inicial (default: última tag).
|
|
222
|
-
* @param {string} [opts.to='HEAD'] - Ref final.
|
|
223
|
-
* @param {string} [opts.cwd] - Directorio del repo.
|
|
224
|
-
* @returns {Array<{hash: string, subject: string, body: string}>}
|
|
225
|
-
*/
|
|
226
|
-
function leerCommitsGit(opts = {}) {
|
|
227
|
-
const { from, to = 'HEAD', cwd = process.cwd() } = opts;
|
|
228
|
-
|
|
229
|
-
let rango;
|
|
230
|
-
if (from) {
|
|
231
|
-
rango = `${from}..${to}`;
|
|
232
|
-
} else {
|
|
233
|
-
// Default: desde la última tag (si existe) o desde el initial commit.
|
|
234
|
-
try {
|
|
235
|
-
const ultimaTag = execSync('git describe --tags --abbrev=0', {
|
|
236
|
-
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'],
|
|
237
|
-
}).trim();
|
|
238
|
-
rango = `${ultimaTag}..${to}`;
|
|
239
|
-
} catch {
|
|
240
|
-
rango = to;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Separador único para parsear subject + body (que puede ser multi-línea).
|
|
245
|
-
const SEP = '<<<COMMIT-SWL-SEP>>>';
|
|
246
|
-
const FIELD = '<<<FIELD-SEP>>>';
|
|
247
|
-
const formato = `%H${FIELD}%s${FIELD}%b${SEP}`;
|
|
248
|
-
|
|
249
|
-
let salida;
|
|
250
|
-
try {
|
|
251
|
-
salida = execSync(`git log ${rango} --format=${JSON.stringify(formato)}`, {
|
|
252
|
-
cwd, encoding: 'utf8',
|
|
253
|
-
});
|
|
254
|
-
} catch {
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return salida
|
|
259
|
-
.split(SEP)
|
|
260
|
-
.map(s => s.trim())
|
|
261
|
-
.filter(Boolean)
|
|
262
|
-
.map(linea => {
|
|
263
|
-
const [hash, subject, body] = linea.split(FIELD);
|
|
264
|
-
return {
|
|
265
|
-
hash: (hash || '').trim(),
|
|
266
|
-
subject: (subject || '').trim(),
|
|
267
|
-
body: (body || '').trim(),
|
|
268
|
-
};
|
|
269
|
-
})
|
|
270
|
-
.filter(c => c.hash);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// -----------------------------------------------------------------------------
|
|
274
|
-
// Generación de markdown
|
|
275
|
-
// -----------------------------------------------------------------------------
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Genera la entrada de CHANGELOG en formato Keep a Changelog.
|
|
279
|
-
*
|
|
280
|
-
* @param {Map<string, Array>} categorias
|
|
281
|
-
* @param {object} opts
|
|
282
|
-
* @param {string} opts.version - Ej: '1.6.5'
|
|
283
|
-
* @param {string} [opts.fecha] - Default: hoy en formato YYYY-MM-DD
|
|
284
|
-
* @param {boolean} [opts.incluirHash=true] - Mostrar hash corto al inicio de cada bullet.
|
|
285
|
-
* @returns {string}
|
|
286
|
-
*/
|
|
287
|
-
function generarChangelog(categorias, opts) {
|
|
288
|
-
const { version, fecha, incluirHash = true } = opts || {};
|
|
289
|
-
if (!version) throw new Error('generarChangelog: version es obligatoria');
|
|
290
|
-
|
|
291
|
-
const fechaUsada = fecha || new Date().toISOString().slice(0, 10);
|
|
292
|
-
const lineas = [`## [${version}] - ${fechaUsada}`, ''];
|
|
293
|
-
|
|
294
|
-
for (const categoria of ORDEN_CATEGORIAS) {
|
|
295
|
-
const entradas = categorias.get(categoria) || [];
|
|
296
|
-
if (entradas.length === 0) continue;
|
|
297
|
-
|
|
298
|
-
lineas.push(`### ${categoria}`);
|
|
299
|
-
lineas.push('');
|
|
300
|
-
for (const entrada of entradas) {
|
|
301
|
-
const hashCorto = incluirHash && entrada.hash
|
|
302
|
-
? `${entrada.hash.slice(0, 7)} `
|
|
303
|
-
: '';
|
|
304
|
-
const scope = entrada.scope ? `**${entrada.scope}**: ` : '';
|
|
305
|
-
lineas.push(`- ${hashCorto}${scope}${entrada.descripcion}`);
|
|
306
|
-
}
|
|
307
|
-
lineas.push('');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return lineas.join('\n');
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// -----------------------------------------------------------------------------
|
|
314
|
-
// CLI
|
|
315
|
-
// -----------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
function _parseFlags(argv) {
|
|
318
|
-
const flags = { format: 'markdown' };
|
|
319
|
-
for (let i = 2; i < argv.length; i++) {
|
|
320
|
-
if (argv[i] === '--from' && argv[i + 1]) { flags.from = argv[++i]; continue; }
|
|
321
|
-
if (argv[i] === '--to' && argv[i + 1]) { flags.to = argv[++i]; continue; }
|
|
322
|
-
if (argv[i] === '--format' && argv[i + 1]) { flags.format = argv[++i]; continue; }
|
|
323
|
-
if (argv[i] === '--version' && argv[i + 1]) { flags.version = argv[++i]; continue; }
|
|
324
|
-
}
|
|
325
|
-
return flags;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (require.main === module) {
|
|
329
|
-
const flags = _parseFlags(process.argv);
|
|
330
|
-
const commits = leerCommitsGit({ from: flags.from, to: flags.to });
|
|
331
|
-
const resultado = parsearCommits(commits);
|
|
332
|
-
|
|
333
|
-
if (flags.format === 'json') {
|
|
334
|
-
process.stdout.write(JSON.stringify({
|
|
335
|
-
...resultado,
|
|
336
|
-
categorias: Object.fromEntries(resultado.categorias),
|
|
337
|
-
}, null, 2));
|
|
338
|
-
} else {
|
|
339
|
-
if (!flags.version) {
|
|
340
|
-
process.stderr.write('ERROR: --version requerido para format=markdown\n');
|
|
341
|
-
process.exit(1);
|
|
342
|
-
}
|
|
343
|
-
process.stdout.write(generarChangelog(resultado.categorias, {
|
|
344
|
-
version: flags.version,
|
|
345
|
-
}));
|
|
346
|
-
}
|
|
347
|
-
process.stdout.write('\n');
|
|
348
|
-
|
|
349
|
-
if (resultado.conformidad < 0.8) {
|
|
350
|
-
process.stderr.write(
|
|
351
|
-
`\nADVERTENCIA: conformidad Conventional Commits = ${(resultado.conformidad * 100).toFixed(1)}% ` +
|
|
352
|
-
`(${resultado.conformes}/${resultado.totalCommits}). ` +
|
|
353
|
-
`Revisar bloque "Otros" antes de publicar.\n`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
module.exports = {
|
|
359
|
-
parsearCommits,
|
|
360
|
-
leerCommitsGit,
|
|
361
|
-
generarChangelog,
|
|
362
|
-
transformarLegible,
|
|
363
|
-
esBreakingChange,
|
|
364
|
-
RE_CONVENTIONAL,
|
|
365
|
-
CATEGORIAS,
|
|
366
|
-
ORDEN_CATEGORIAS,
|
|
367
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* parse-commits.js — Parser determinista de Conventional Commits para
|
|
5
|
+
* generación automática de CHANGELOG en /swl:release.
|
|
6
|
+
*
|
|
7
|
+
* Lee commits desde `git log` (entre dos refs o desde la última tag) y los
|
|
8
|
+
* clasifica según el spec de Conventional Commits 1.0.0:
|
|
9
|
+
* https://www.conventionalcommits.org/en/v1.0.0/
|
|
10
|
+
*
|
|
11
|
+
* Categorías generadas (formato Keep a Changelog):
|
|
12
|
+
* - feat -> "Nuevas funcionalidades"
|
|
13
|
+
* - fix -> "Correcciones"
|
|
14
|
+
* - perf -> "Mejoras de rendimiento"
|
|
15
|
+
* - refactor -> "Cambios internos"
|
|
16
|
+
* - docs / style / test / ci / build / chore -> "Mantenimiento" (opcional)
|
|
17
|
+
* - BREAKING CHANGE / ! -> "Breaking changes" (siempre top de cada release)
|
|
18
|
+
* - Sin prefijo CC válido -> "Otros" (con bandera de baja conformidad)
|
|
19
|
+
*
|
|
20
|
+
* Fallback: si la conformidad de commits es <80%, el script retorna
|
|
21
|
+
* `conformidad: <ratio>` para que el caller decida si abortar o seguir.
|
|
22
|
+
*
|
|
23
|
+
* Uso desde CLI:
|
|
24
|
+
* node parse-commits.js [--from <ref>] [--to <ref>] [--format json|markdown]
|
|
25
|
+
*
|
|
26
|
+
* Uso programático:
|
|
27
|
+
* const { parsearCommits, generarChangelog } = require('./parse-commits');
|
|
28
|
+
* const { categorias, conformidad } = parsearCommits(commits);
|
|
29
|
+
* const md = generarChangelog(categorias, { version: '1.6.5', fecha: '...' });
|
|
30
|
+
*
|
|
31
|
+
* Zero-deps. Cargado por skill `changelog-generator` y por
|
|
32
|
+
* `/swl:release` paso 7.
|
|
33
|
+
*
|
|
34
|
+
* @module habilidades/changelog-generator/scripts/parse-commits
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const { execSync } = require('node:child_process');
|
|
38
|
+
|
|
39
|
+
// -----------------------------------------------------------------------------
|
|
40
|
+
// Spec Conventional Commits
|
|
41
|
+
// -----------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Regex que matchea una línea de commit Conventional Commits.
|
|
45
|
+
* Grupos: [1]=tipo, [2]=scope?, [3]=!?, [4]=descripcion
|
|
46
|
+
*
|
|
47
|
+
* Ejemplos válidos:
|
|
48
|
+
* feat(auth): agregar validación PKCE
|
|
49
|
+
* fix: corregir cálculo IEPS
|
|
50
|
+
* feat!: breaking change inline
|
|
51
|
+
* refactor(hooks/lib): split evolution-tracker
|
|
52
|
+
* chore(release): bump version
|
|
53
|
+
* [T-01] feat(hooks): prefijo de trazabilidad de tarea (ejecutar-fase)
|
|
54
|
+
* [T-07][T-08] refactor(hooks): varios prefijos consecutivos
|
|
55
|
+
* [REQ-08] feat(scripts): prefijo de requisito
|
|
56
|
+
* [F12·T-01] feat(hooks): prefijo namespaceado por fase (DT-IDS-NAMESPACE)
|
|
57
|
+
*
|
|
58
|
+
* El prefijo opcional `[T-NN]` / `[REQ-NN]` / `[F<fase>·T-NN]` (uno o más,
|
|
59
|
+
* consecutivos) proviene de la convención de commits de
|
|
60
|
+
* `habilidades/ejecutar-fase/SKILL.md` (trazabilidad de tarea/requisito; desde
|
|
61
|
+
* la Fase 12 los IDs se namespacean por fase). No altera la semántica CC: el
|
|
62
|
+
* tipo+scope+descripción siguen presentes después del/los prefijo(s), por lo
|
|
63
|
+
* que el commit cuenta como conforme. Grupo non-capturing para no desplazar
|
|
64
|
+
* los índices [1]=tipo [2]=scope [3]=! [4]=descripcion.
|
|
65
|
+
*/
|
|
66
|
+
const RE_CONVENTIONAL = /^(?:\[(?:F\d+·)?(?:T|REQ)-\d+(?:-\d+)?\]\s*)*(feat|fix|perf|refactor|docs|style|test|ci|build|chore|revert|evolucion|evolve)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
|
|
67
|
+
|
|
68
|
+
/** Mapa tipo CC → categoría Keep a Changelog en es-MX. */
|
|
69
|
+
const CATEGORIAS = Object.freeze({
|
|
70
|
+
feat: 'Nuevas funcionalidades',
|
|
71
|
+
fix: 'Correcciones',
|
|
72
|
+
perf: 'Mejoras de rendimiento',
|
|
73
|
+
refactor: 'Cambios internos',
|
|
74
|
+
revert: 'Reversiones',
|
|
75
|
+
evolucion: 'Evoluciones de skills/agentes',
|
|
76
|
+
evolve: 'Evoluciones de skills/agentes',
|
|
77
|
+
docs: 'Mantenimiento',
|
|
78
|
+
style: 'Mantenimiento',
|
|
79
|
+
test: 'Mantenimiento',
|
|
80
|
+
ci: 'Mantenimiento',
|
|
81
|
+
build: 'Mantenimiento',
|
|
82
|
+
chore: 'Mantenimiento',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/** Orden de salida en CHANGELOG (Breaking siempre primero). */
|
|
86
|
+
const ORDEN_CATEGORIAS = Object.freeze([
|
|
87
|
+
'Breaking changes',
|
|
88
|
+
'Nuevas funcionalidades',
|
|
89
|
+
'Correcciones',
|
|
90
|
+
'Mejoras de rendimiento',
|
|
91
|
+
'Cambios internos',
|
|
92
|
+
'Reversiones',
|
|
93
|
+
'Evoluciones de skills/agentes',
|
|
94
|
+
'Mantenimiento',
|
|
95
|
+
'Otros',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// -----------------------------------------------------------------------------
|
|
99
|
+
// Transformación técnica → user-facing
|
|
100
|
+
// -----------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Aplica heurísticas de transformación para hacer la línea más legible al
|
|
104
|
+
* usuario final sin contradecir lo que dice el commit.
|
|
105
|
+
*
|
|
106
|
+
* Reglas conservadoras: solo elimina prefijos técnicos y normaliza la primera
|
|
107
|
+
* letra. NO inventa contenido; si la descripción es críptica, queda críptica.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} descripcion - Descripción cruda del commit.
|
|
110
|
+
* @returns {string} Versión legible.
|
|
111
|
+
*/
|
|
112
|
+
function transformarLegible(descripcion) {
|
|
113
|
+
let texto = descripcion.trim();
|
|
114
|
+
|
|
115
|
+
// Eliminar referencias a issues al final si son ruido (#123, refs #...).
|
|
116
|
+
texto = texto.replace(/\s*\(#\d+\)\s*$/, '');
|
|
117
|
+
texto = texto.replace(/\s*[Rr]efs?:?\s*#\d+\s*$/, '');
|
|
118
|
+
|
|
119
|
+
// Capitalizar primera letra.
|
|
120
|
+
if (texto.length > 0) {
|
|
121
|
+
texto = texto[0].toUpperCase() + texto.slice(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Eliminar punto final si existe (Keep a Changelog usa bullets sin punto).
|
|
125
|
+
texto = texto.replace(/\.$/, '');
|
|
126
|
+
|
|
127
|
+
return texto;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// -----------------------------------------------------------------------------
|
|
131
|
+
// Parser principal
|
|
132
|
+
// -----------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detecta si el commit tiene marca de breaking change según el spec
|
|
136
|
+
* Conventional Commits 1.0.0:
|
|
137
|
+
* - `!` después del tipo/scope en el subject, o
|
|
138
|
+
* - Trailer `BREAKING CHANGE:` o `BREAKING-CHANGE:` al INICIO de una línea
|
|
139
|
+
* del body (no en medio de prosa). El spec requiere este formato literal
|
|
140
|
+
* como trailer; una mención de la palabra dentro de la descripción NO
|
|
141
|
+
* dispara breaking change.
|
|
142
|
+
*
|
|
143
|
+
* Bug histórico (fix v1.6.5): la regex previa `/BREAKING\s+CHANGE/i` generaba
|
|
144
|
+
* falsos positivos cuando el commit describía features que mencionaban
|
|
145
|
+
* "breaking changes" en su prosa sin ser un breaking change ellos mismos.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} mensajeCompleto - Mensaje completo del commit (subject + body).
|
|
148
|
+
* @param {string} marcaExclamacion - Match del grupo `!` del regex.
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
function esBreakingChange(mensajeCompleto, marcaExclamacion) {
|
|
152
|
+
if (marcaExclamacion === '!') return true;
|
|
153
|
+
// Trailer al inicio de línea, mayúsculas obligatorias, dos puntos al final.
|
|
154
|
+
return /^BREAKING[-\s]CHANGE:/m.test(mensajeCompleto);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Parsea una lista de commits y los clasifica.
|
|
159
|
+
*
|
|
160
|
+
* @param {Array<{hash: string, subject: string, body?: string}>} commits
|
|
161
|
+
* @returns {{
|
|
162
|
+
* categorias: Map<string, Array<{hash: string, descripcion: string, scope?: string}>>,
|
|
163
|
+
* conformidad: number,
|
|
164
|
+
* totalCommits: number,
|
|
165
|
+
* conformes: number,
|
|
166
|
+
* }}
|
|
167
|
+
*/
|
|
168
|
+
function parsearCommits(commits) {
|
|
169
|
+
const categorias = new Map();
|
|
170
|
+
for (const cat of ORDEN_CATEGORIAS) categorias.set(cat, []);
|
|
171
|
+
|
|
172
|
+
let conformes = 0;
|
|
173
|
+
|
|
174
|
+
for (const commit of commits) {
|
|
175
|
+
const { hash, subject = '', body = '' } = commit;
|
|
176
|
+
const completo = `${subject}\n${body}`;
|
|
177
|
+
const match = subject.match(RE_CONVENTIONAL);
|
|
178
|
+
|
|
179
|
+
if (!match) {
|
|
180
|
+
categorias.get('Otros').push({
|
|
181
|
+
hash,
|
|
182
|
+
descripcion: transformarLegible(subject),
|
|
183
|
+
});
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
conformes += 1;
|
|
188
|
+
const [, tipo, scope, marca, descripcion] = match;
|
|
189
|
+
const entrada = {
|
|
190
|
+
hash,
|
|
191
|
+
descripcion: transformarLegible(descripcion),
|
|
192
|
+
scope: scope || null,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Breaking change tiene precedencia sobre la categoría natural.
|
|
196
|
+
if (esBreakingChange(completo, marca)) {
|
|
197
|
+
categorias.get('Breaking changes').push(entrada);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const categoria = CATEGORIAS[tipo] || 'Otros';
|
|
202
|
+
categorias.get(categoria).push(entrada);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
categorias,
|
|
207
|
+
conformidad: commits.length === 0 ? 1 : conformes / commits.length,
|
|
208
|
+
totalCommits: commits.length,
|
|
209
|
+
conformes,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// -----------------------------------------------------------------------------
|
|
214
|
+
// Lectura desde git
|
|
215
|
+
// -----------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Obtiene commits desde git en formato estructurado.
|
|
219
|
+
*
|
|
220
|
+
* @param {object} opts
|
|
221
|
+
* @param {string} [opts.from] - Ref inicial (default: última tag).
|
|
222
|
+
* @param {string} [opts.to='HEAD'] - Ref final.
|
|
223
|
+
* @param {string} [opts.cwd] - Directorio del repo.
|
|
224
|
+
* @returns {Array<{hash: string, subject: string, body: string}>}
|
|
225
|
+
*/
|
|
226
|
+
function leerCommitsGit(opts = {}) {
|
|
227
|
+
const { from, to = 'HEAD', cwd = process.cwd() } = opts;
|
|
228
|
+
|
|
229
|
+
let rango;
|
|
230
|
+
if (from) {
|
|
231
|
+
rango = `${from}..${to}`;
|
|
232
|
+
} else {
|
|
233
|
+
// Default: desde la última tag (si existe) o desde el initial commit.
|
|
234
|
+
try {
|
|
235
|
+
const ultimaTag = execSync('git describe --tags --abbrev=0', {
|
|
236
|
+
cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'],
|
|
237
|
+
}).trim();
|
|
238
|
+
rango = `${ultimaTag}..${to}`;
|
|
239
|
+
} catch {
|
|
240
|
+
rango = to;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Separador único para parsear subject + body (que puede ser multi-línea).
|
|
245
|
+
const SEP = '<<<COMMIT-SWL-SEP>>>';
|
|
246
|
+
const FIELD = '<<<FIELD-SEP>>>';
|
|
247
|
+
const formato = `%H${FIELD}%s${FIELD}%b${SEP}`;
|
|
248
|
+
|
|
249
|
+
let salida;
|
|
250
|
+
try {
|
|
251
|
+
salida = execSync(`git log ${rango} --format=${JSON.stringify(formato)}`, {
|
|
252
|
+
cwd, encoding: 'utf8',
|
|
253
|
+
});
|
|
254
|
+
} catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return salida
|
|
259
|
+
.split(SEP)
|
|
260
|
+
.map(s => s.trim())
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
.map(linea => {
|
|
263
|
+
const [hash, subject, body] = linea.split(FIELD);
|
|
264
|
+
return {
|
|
265
|
+
hash: (hash || '').trim(),
|
|
266
|
+
subject: (subject || '').trim(),
|
|
267
|
+
body: (body || '').trim(),
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
.filter(c => c.hash);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// -----------------------------------------------------------------------------
|
|
274
|
+
// Generación de markdown
|
|
275
|
+
// -----------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Genera la entrada de CHANGELOG en formato Keep a Changelog.
|
|
279
|
+
*
|
|
280
|
+
* @param {Map<string, Array>} categorias
|
|
281
|
+
* @param {object} opts
|
|
282
|
+
* @param {string} opts.version - Ej: '1.6.5'
|
|
283
|
+
* @param {string} [opts.fecha] - Default: hoy en formato YYYY-MM-DD
|
|
284
|
+
* @param {boolean} [opts.incluirHash=true] - Mostrar hash corto al inicio de cada bullet.
|
|
285
|
+
* @returns {string}
|
|
286
|
+
*/
|
|
287
|
+
function generarChangelog(categorias, opts) {
|
|
288
|
+
const { version, fecha, incluirHash = true } = opts || {};
|
|
289
|
+
if (!version) throw new Error('generarChangelog: version es obligatoria');
|
|
290
|
+
|
|
291
|
+
const fechaUsada = fecha || new Date().toISOString().slice(0, 10);
|
|
292
|
+
const lineas = [`## [${version}] - ${fechaUsada}`, ''];
|
|
293
|
+
|
|
294
|
+
for (const categoria of ORDEN_CATEGORIAS) {
|
|
295
|
+
const entradas = categorias.get(categoria) || [];
|
|
296
|
+
if (entradas.length === 0) continue;
|
|
297
|
+
|
|
298
|
+
lineas.push(`### ${categoria}`);
|
|
299
|
+
lineas.push('');
|
|
300
|
+
for (const entrada of entradas) {
|
|
301
|
+
const hashCorto = incluirHash && entrada.hash
|
|
302
|
+
? `${entrada.hash.slice(0, 7)} `
|
|
303
|
+
: '';
|
|
304
|
+
const scope = entrada.scope ? `**${entrada.scope}**: ` : '';
|
|
305
|
+
lineas.push(`- ${hashCorto}${scope}${entrada.descripcion}`);
|
|
306
|
+
}
|
|
307
|
+
lineas.push('');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return lineas.join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// -----------------------------------------------------------------------------
|
|
314
|
+
// CLI
|
|
315
|
+
// -----------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
function _parseFlags(argv) {
|
|
318
|
+
const flags = { format: 'markdown' };
|
|
319
|
+
for (let i = 2; i < argv.length; i++) {
|
|
320
|
+
if (argv[i] === '--from' && argv[i + 1]) { flags.from = argv[++i]; continue; }
|
|
321
|
+
if (argv[i] === '--to' && argv[i + 1]) { flags.to = argv[++i]; continue; }
|
|
322
|
+
if (argv[i] === '--format' && argv[i + 1]) { flags.format = argv[++i]; continue; }
|
|
323
|
+
if (argv[i] === '--version' && argv[i + 1]) { flags.version = argv[++i]; continue; }
|
|
324
|
+
}
|
|
325
|
+
return flags;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (require.main === module) {
|
|
329
|
+
const flags = _parseFlags(process.argv);
|
|
330
|
+
const commits = leerCommitsGit({ from: flags.from, to: flags.to });
|
|
331
|
+
const resultado = parsearCommits(commits);
|
|
332
|
+
|
|
333
|
+
if (flags.format === 'json') {
|
|
334
|
+
process.stdout.write(JSON.stringify({
|
|
335
|
+
...resultado,
|
|
336
|
+
categorias: Object.fromEntries(resultado.categorias),
|
|
337
|
+
}, null, 2));
|
|
338
|
+
} else {
|
|
339
|
+
if (!flags.version) {
|
|
340
|
+
process.stderr.write('ERROR: --version requerido para format=markdown\n');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
process.stdout.write(generarChangelog(resultado.categorias, {
|
|
344
|
+
version: flags.version,
|
|
345
|
+
}));
|
|
346
|
+
}
|
|
347
|
+
process.stdout.write('\n');
|
|
348
|
+
|
|
349
|
+
if (resultado.conformidad < 0.8) {
|
|
350
|
+
process.stderr.write(
|
|
351
|
+
`\nADVERTENCIA: conformidad Conventional Commits = ${(resultado.conformidad * 100).toFixed(1)}% ` +
|
|
352
|
+
`(${resultado.conformes}/${resultado.totalCommits}). ` +
|
|
353
|
+
`Revisar bloque "Otros" antes de publicar.\n`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
parsearCommits,
|
|
360
|
+
leerCommitsGit,
|
|
361
|
+
generarChangelog,
|
|
362
|
+
transformarLegible,
|
|
363
|
+
esBreakingChange,
|
|
364
|
+
RE_CONVENTIONAL,
|
|
365
|
+
CATEGORIAS,
|
|
366
|
+
ORDEN_CATEGORIAS,
|
|
367
|
+
};
|