@saulwade/swl-ses 2.2.4 → 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 +48 -7
- package/README.md +3 -3
- 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/aprobar-plan.md +146 -141
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/predecir.md +139 -139
- package/comandos/swl/release.md +30 -8
- package/comandos/swl/sesiones.md +1 -1
- package/comandos/swl/status.md +343 -333
- 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/contenedores-docker/SKILL.md +3 -1
- 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-fase/SKILL.md +564 -541
- 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/extractor-de-aprendizajes/SKILL.md +3 -3
- 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/planear-fase/SKILL.md +15 -1
- 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/SKILL.md +191 -164
- 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/SKILL.md +33 -1
- 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/captura-acciones-post.js +151 -0
- package/hooks/captura-acciones-session.js +155 -0
- 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/briefing.js +512 -474
- package/hooks/lib/captura-acciones.js +392 -0
- 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 +2 -2
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +2291 -1637
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +4 -0
- package/manifiestos/planning-paths.json +44 -44
- package/manifiestos/skills-lock.json +1282 -1282
- 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 +2 -2
- 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/memoria-consolidada.md +41 -0
- package/reglas/monitor-ci.md +309 -309
- package/reglas/patrones.md +6 -6
- package/reglas/seguridad-agentes.md +66 -0
- 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/instinct.schema.json +161 -152
- 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/bootstrap-instintos.js +25 -7
- 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/gitignore-manifest.js +44 -11
- 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/skill-metrics.js +41 -1
- 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
- package/scripts/verificar-trazabilidad.js +329 -298
|
@@ -1,257 +1,257 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* git-metricas.js
|
|
5
|
-
*
|
|
6
|
-
* Métricas DORA derivables SOLO de git (sin CI), para el proyecto destino donde
|
|
7
|
-
* SWL está instalado:
|
|
8
|
-
* - deployment frequency: tags de release en una ventana -> deploys/semana.
|
|
9
|
-
* - lead time for changes: commit más antiguo desde el release previo -> tag
|
|
10
|
-
* actual (p50/p95).
|
|
11
|
-
* - detección de degradación de entrega: compara ventana actual vs previa
|
|
12
|
-
* (consumido por el recolector del briefing — git-only, <200ms, NUNCA gh).
|
|
13
|
-
*
|
|
14
|
-
* "deployment" = tag de release que matchea `patronTag` (default /^v?\d+\.\d+\.\d+/).
|
|
15
|
-
*
|
|
16
|
-
* Funciones puras zero-deps. git vía execFileSync sin shell, con try/catch que
|
|
17
|
-
* degrada a `{disponible:false}` (no es repo) o a 0/sinDatos (repo sin tags).
|
|
18
|
-
* Entrypoint CLI `--json`. Parte de la Fase 15 (ADR-0039).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const { execFileSync } = require('node:child_process');
|
|
22
|
-
|
|
23
|
-
const PATRON_TAG_DEFAULT = /^v?\d+\.\d+\.\d+/;
|
|
24
|
-
// Rango git válido: prohíbe `-` inicial en cada extremo (defensa en profundidad
|
|
25
|
-
// contra un refname que git interpretaría como flag de `git log`). Los nombres
|
|
26
|
-
// vienen de `git for-each-ref` —git ya rechaza refs con `-` inicial—, pero la
|
|
27
|
-
// validación local cierra el hueco si el origen del rango cambiara (H-02).
|
|
28
|
-
const RE_RANGO_VALIDO =
|
|
29
|
-
/^(?!-)[A-Za-z0-9_./@~^][A-Za-z0-9_./@~^-]*(\.\.\.?(?!-)[A-Za-z0-9_./@~^][A-Za-z0-9_./@~^-]*)?$/;
|
|
30
|
-
const MS_DIA = 24 * 60 * 60 * 1000;
|
|
31
|
-
const MS_HORA = 60 * 60 * 1000;
|
|
32
|
-
const GIT_MAX_BUFFER = 8 * 1024 * 1024; // 8 MiB
|
|
33
|
-
const GIT_TIMEOUT_MS = 5000; // evita hangs (H-04); el recolector del briefing degrada si git cuelga
|
|
34
|
-
|
|
35
|
-
function _git(baseDir, args) {
|
|
36
|
-
return execFileSync('git', args, {
|
|
37
|
-
cwd: baseDir,
|
|
38
|
-
encoding: 'utf8',
|
|
39
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
40
|
-
maxBuffer: GIT_MAX_BUFFER,
|
|
41
|
-
timeout: GIT_TIMEOUT_MS,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Lista tags que matchean patronTag con su fecha de creación.
|
|
47
|
-
* @returns {Array<{name:string, date:Date}>|null} null si no es repo git.
|
|
48
|
-
*/
|
|
49
|
-
function _gitTagsConFecha(baseDir, patronTag = PATRON_TAG_DEFAULT) {
|
|
50
|
-
let salida;
|
|
51
|
-
try {
|
|
52
|
-
salida = _git(baseDir, [
|
|
53
|
-
'for-each-ref',
|
|
54
|
-
'--sort=creatordate',
|
|
55
|
-
'--format=%(refname:short)|%(creatordate:iso-strict)',
|
|
56
|
-
'refs/tags',
|
|
57
|
-
]);
|
|
58
|
-
} catch {
|
|
59
|
-
return null; // no es repo git / git no disponible
|
|
60
|
-
}
|
|
61
|
-
const tags = [];
|
|
62
|
-
for (const linea of salida.split('\n')) {
|
|
63
|
-
const l = linea.trim();
|
|
64
|
-
if (!l) continue;
|
|
65
|
-
const sep = l.lastIndexOf('|');
|
|
66
|
-
if (sep === -1) continue;
|
|
67
|
-
const name = l.slice(0, sep);
|
|
68
|
-
const iso = l.slice(sep + 1);
|
|
69
|
-
if (!patronTag.test(name)) continue;
|
|
70
|
-
const t = Date.parse(iso);
|
|
71
|
-
if (!Number.isFinite(t)) continue;
|
|
72
|
-
tags.push({ name, date: new Date(t) });
|
|
73
|
-
}
|
|
74
|
-
tags.sort((a, b) => a.date - b.date);
|
|
75
|
-
return tags;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** ISO del commit más antiguo (primero) de un rango git, o null. */
|
|
79
|
-
function _primerCommitISO(baseDir, rango) {
|
|
80
|
-
if (!RE_RANGO_VALIDO.test(rango)) return null;
|
|
81
|
-
let salida;
|
|
82
|
-
try {
|
|
83
|
-
salida = _git(baseDir, ['log', rango, '--reverse', '--format=%cI', '--']);
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const primera = salida.split('\n').map((s) => s.trim()).filter(Boolean)[0];
|
|
88
|
-
return primera || null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Nearest-rank (ceil), no interpolación. Con 2 muestras [a,b] y p=50 → a.
|
|
92
|
-
function _percentil(valoresOrdenados, p) {
|
|
93
|
-
if (valoresOrdenados.length === 0) return null;
|
|
94
|
-
const idx = Math.ceil((p / 100) * valoresOrdenados.length) - 1;
|
|
95
|
-
return valoresOrdenados[Math.max(0, Math.min(idx, valoresOrdenados.length - 1))];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function _enVentana(date, desde, hasta) {
|
|
99
|
-
return date >= desde && date <= hasta;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Deployment frequency en la ventana.
|
|
104
|
-
* @returns {{disponible:boolean, totalDeploys?:number, deploysPorSemana?:number,
|
|
105
|
-
* sinDatos?:boolean, razon?:string}}
|
|
106
|
-
*/
|
|
107
|
-
function deploymentFrequency(baseDir, opts = {}) {
|
|
108
|
-
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
109
|
-
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
110
|
-
if (tags === null) return { disponible: false, razon: 'no es un repositorio git' };
|
|
111
|
-
|
|
112
|
-
const desde = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
113
|
-
const enVentana = tags.filter((t) => _enVentana(t.date, desde, hoy));
|
|
114
|
-
const semanas = ventanaDias / 7;
|
|
115
|
-
return {
|
|
116
|
-
disponible: true,
|
|
117
|
-
totalDeploys: enVentana.length,
|
|
118
|
-
deploysPorSemana: semanas > 0 ? enVentana.length / semanas : 0,
|
|
119
|
-
sinDatos: tags.length === 0,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Lead times (horas) commit->tag para tags en [desde,hasta]. */
|
|
124
|
-
function _leadTimesEnVentana(baseDir, tags, desde, hasta) {
|
|
125
|
-
const horas = [];
|
|
126
|
-
for (let i = 0; i < tags.length; i++) {
|
|
127
|
-
const tag = tags[i];
|
|
128
|
-
if (!_enVentana(tag.date, desde, hasta)) continue;
|
|
129
|
-
const prev = tags[i - 1];
|
|
130
|
-
const rango = prev ? `${prev.name}..${tag.name}` : tag.name;
|
|
131
|
-
const primeroISO = _primerCommitISO(baseDir, rango);
|
|
132
|
-
if (!primeroISO) continue;
|
|
133
|
-
const t = Date.parse(primeroISO);
|
|
134
|
-
if (!Number.isFinite(t)) continue;
|
|
135
|
-
const h = (tag.date.getTime() - t) / MS_HORA;
|
|
136
|
-
if (h >= 0) horas.push(h);
|
|
137
|
-
}
|
|
138
|
-
return horas.sort((a, b) => a - b);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Lead time for changes (commit->tag) en la ventana.
|
|
143
|
-
* @returns {{disponible:boolean, p50Horas?:number, p95Horas?:number,
|
|
144
|
-
* muestras?:number, sinDatos?:boolean, razon?:string}}
|
|
145
|
-
*/
|
|
146
|
-
function leadTimeForChanges(baseDir, opts = {}) {
|
|
147
|
-
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
148
|
-
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
149
|
-
if (tags === null) return { disponible: false, razon: 'no es un repositorio git' };
|
|
150
|
-
|
|
151
|
-
const desde = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
152
|
-
const horas = _leadTimesEnVentana(baseDir, tags, desde, hoy);
|
|
153
|
-
if (horas.length === 0) {
|
|
154
|
-
return { disponible: true, sinDatos: true, muestras: 0, p50Horas: null, p95Horas: null };
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
disponible: true,
|
|
158
|
-
sinDatos: false,
|
|
159
|
-
muestras: horas.length,
|
|
160
|
-
p50Horas: _percentil(horas, 50),
|
|
161
|
-
p95Horas: _percentil(horas, 95),
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Detecta degradación de entrega comparando la ventana actual vs la previa de
|
|
167
|
-
* igual duración. Git-only y rápido — apto para el recolector del briefing.
|
|
168
|
-
* Degradación = deploy freq cae >50% O lead time mediano sube >50%.
|
|
169
|
-
* @returns {{disponible:boolean, degradado:boolean, motivo:(string|null),
|
|
170
|
-
* deployFreqActual:number, deployFreqPrevia:number,
|
|
171
|
-
* leadTimeP50Actual:(number|null), leadTimeP50Previo:(number|null)}}
|
|
172
|
-
*/
|
|
173
|
-
function detectarDegradacionEntrega(baseDir, opts = {}) {
|
|
174
|
-
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
175
|
-
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
176
|
-
if (tags === null) {
|
|
177
|
-
return {
|
|
178
|
-
disponible: false,
|
|
179
|
-
degradado: false,
|
|
180
|
-
motivo: null,
|
|
181
|
-
deployFreqActual: 0,
|
|
182
|
-
deployFreqPrevia: 0,
|
|
183
|
-
leadTimeP50Actual: null,
|
|
184
|
-
leadTimeP50Previo: null,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const finActual = hoy;
|
|
189
|
-
const inicioActual = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
190
|
-
const inicioPrevio = new Date(hoy.getTime() - 2 * ventanaDias * MS_DIA);
|
|
191
|
-
const semanas = ventanaDias / 7;
|
|
192
|
-
|
|
193
|
-
const deploysActual = tags.filter((t) => _enVentana(t.date, inicioActual, finActual)).length;
|
|
194
|
-
const deploysPrevio = tags.filter((t) => _enVentana(t.date, inicioPrevio, inicioActual)).length;
|
|
195
|
-
const deployFreqActual = semanas > 0 ? deploysActual / semanas : 0;
|
|
196
|
-
const deployFreqPrevia = semanas > 0 ? deploysPrevio / semanas : 0;
|
|
197
|
-
|
|
198
|
-
const ltActual = _percentil(_leadTimesEnVentana(baseDir, tags, inicioActual, finActual), 50);
|
|
199
|
-
const ltPrevio = _percentil(_leadTimesEnVentana(baseDir, tags, inicioPrevio, inicioActual), 50);
|
|
200
|
-
|
|
201
|
-
const motivos = [];
|
|
202
|
-
if (deployFreqPrevia > 0 && deployFreqActual < deployFreqPrevia * 0.5) {
|
|
203
|
-
motivos.push(
|
|
204
|
-
`deployment frequency cayó >50% (${deployFreqPrevia.toFixed(2)} → ${deployFreqActual.toFixed(2)} deploys/semana)`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
if (ltPrevio !== null && ltActual !== null && ltActual > ltPrevio * 1.5) {
|
|
208
|
-
motivos.push(
|
|
209
|
-
`lead time mediano subió >50% (${ltPrevio.toFixed(1)}h → ${ltActual.toFixed(1)}h)`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
disponible: true,
|
|
215
|
-
degradado: motivos.length > 0,
|
|
216
|
-
motivo: motivos.length > 0 ? motivos.join('; ') : null,
|
|
217
|
-
deployFreqActual,
|
|
218
|
-
deployFreqPrevia,
|
|
219
|
-
leadTimeP50Actual: ltActual,
|
|
220
|
-
leadTimeP50Previo: ltPrevio,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
module.exports = {
|
|
225
|
-
deploymentFrequency,
|
|
226
|
-
leadTimeForChanges,
|
|
227
|
-
detectarDegradacionEntrega,
|
|
228
|
-
PATRON_TAG_DEFAULT,
|
|
229
|
-
_gitTagsConFecha,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// Entrypoint CLI: node scripts/lib/git-metricas.js [--json] [--dias=N] [baseDir]
|
|
233
|
-
if (!require.main || require.main === module) {
|
|
234
|
-
const args = process.argv.slice(2);
|
|
235
|
-
const json = args.includes('--json');
|
|
236
|
-
const diasArg = args.find((a) => a.startsWith('--dias='));
|
|
237
|
-
const ventanaDias = Math.max(1, Math.min(365, (diasArg ? parseInt(diasArg.slice('--dias='.length), 10) : 30) || 30));
|
|
238
|
-
const baseDir = args.find((a) => !a.startsWith('--')) || process.cwd();
|
|
239
|
-
|
|
240
|
-
const out = {
|
|
241
|
-
ventana_dias: ventanaDias,
|
|
242
|
-
deploymentFrequency: deploymentFrequency(baseDir, { ventanaDias }),
|
|
243
|
-
leadTime: leadTimeForChanges(baseDir, { ventanaDias }),
|
|
244
|
-
degradacion: detectarDegradacionEntrega(baseDir, { ventanaDias }),
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
if (json) {
|
|
248
|
-
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
249
|
-
} else {
|
|
250
|
-
const df = out.deploymentFrequency;
|
|
251
|
-
const lt = out.leadTime;
|
|
252
|
-
process.stdout.write(`Deployment frequency: ${df.disponible ? (df.sinDatos ? 'sin datos' : df.deploysPorSemana.toFixed(2) + ' deploys/semana') : 'no disponible'}\n`);
|
|
253
|
-
process.stdout.write(`Lead time (p50/p95): ${lt.disponible && !lt.sinDatos ? `${lt.p50Horas.toFixed(1)}h / ${lt.p95Horas.toFixed(1)}h` : 'sin datos'}\n`);
|
|
254
|
-
process.stdout.write(`Degradación: ${out.degradacion.degradado ? out.degradacion.motivo : 'no'}\n`);
|
|
255
|
-
}
|
|
256
|
-
process.exit(0);
|
|
257
|
-
}
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* git-metricas.js
|
|
5
|
+
*
|
|
6
|
+
* Métricas DORA derivables SOLO de git (sin CI), para el proyecto destino donde
|
|
7
|
+
* SWL está instalado:
|
|
8
|
+
* - deployment frequency: tags de release en una ventana -> deploys/semana.
|
|
9
|
+
* - lead time for changes: commit más antiguo desde el release previo -> tag
|
|
10
|
+
* actual (p50/p95).
|
|
11
|
+
* - detección de degradación de entrega: compara ventana actual vs previa
|
|
12
|
+
* (consumido por el recolector del briefing — git-only, <200ms, NUNCA gh).
|
|
13
|
+
*
|
|
14
|
+
* "deployment" = tag de release que matchea `patronTag` (default /^v?\d+\.\d+\.\d+/).
|
|
15
|
+
*
|
|
16
|
+
* Funciones puras zero-deps. git vía execFileSync sin shell, con try/catch que
|
|
17
|
+
* degrada a `{disponible:false}` (no es repo) o a 0/sinDatos (repo sin tags).
|
|
18
|
+
* Entrypoint CLI `--json`. Parte de la Fase 15 (ADR-0039).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { execFileSync } = require('node:child_process');
|
|
22
|
+
|
|
23
|
+
const PATRON_TAG_DEFAULT = /^v?\d+\.\d+\.\d+/;
|
|
24
|
+
// Rango git válido: prohíbe `-` inicial en cada extremo (defensa en profundidad
|
|
25
|
+
// contra un refname que git interpretaría como flag de `git log`). Los nombres
|
|
26
|
+
// vienen de `git for-each-ref` —git ya rechaza refs con `-` inicial—, pero la
|
|
27
|
+
// validación local cierra el hueco si el origen del rango cambiara (H-02).
|
|
28
|
+
const RE_RANGO_VALIDO =
|
|
29
|
+
/^(?!-)[A-Za-z0-9_./@~^][A-Za-z0-9_./@~^-]*(\.\.\.?(?!-)[A-Za-z0-9_./@~^][A-Za-z0-9_./@~^-]*)?$/;
|
|
30
|
+
const MS_DIA = 24 * 60 * 60 * 1000;
|
|
31
|
+
const MS_HORA = 60 * 60 * 1000;
|
|
32
|
+
const GIT_MAX_BUFFER = 8 * 1024 * 1024; // 8 MiB
|
|
33
|
+
const GIT_TIMEOUT_MS = 5000; // evita hangs (H-04); el recolector del briefing degrada si git cuelga
|
|
34
|
+
|
|
35
|
+
function _git(baseDir, args) {
|
|
36
|
+
return execFileSync('git', args, {
|
|
37
|
+
cwd: baseDir,
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
40
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
41
|
+
timeout: GIT_TIMEOUT_MS,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Lista tags que matchean patronTag con su fecha de creación.
|
|
47
|
+
* @returns {Array<{name:string, date:Date}>|null} null si no es repo git.
|
|
48
|
+
*/
|
|
49
|
+
function _gitTagsConFecha(baseDir, patronTag = PATRON_TAG_DEFAULT) {
|
|
50
|
+
let salida;
|
|
51
|
+
try {
|
|
52
|
+
salida = _git(baseDir, [
|
|
53
|
+
'for-each-ref',
|
|
54
|
+
'--sort=creatordate',
|
|
55
|
+
'--format=%(refname:short)|%(creatordate:iso-strict)',
|
|
56
|
+
'refs/tags',
|
|
57
|
+
]);
|
|
58
|
+
} catch {
|
|
59
|
+
return null; // no es repo git / git no disponible
|
|
60
|
+
}
|
|
61
|
+
const tags = [];
|
|
62
|
+
for (const linea of salida.split('\n')) {
|
|
63
|
+
const l = linea.trim();
|
|
64
|
+
if (!l) continue;
|
|
65
|
+
const sep = l.lastIndexOf('|');
|
|
66
|
+
if (sep === -1) continue;
|
|
67
|
+
const name = l.slice(0, sep);
|
|
68
|
+
const iso = l.slice(sep + 1);
|
|
69
|
+
if (!patronTag.test(name)) continue;
|
|
70
|
+
const t = Date.parse(iso);
|
|
71
|
+
if (!Number.isFinite(t)) continue;
|
|
72
|
+
tags.push({ name, date: new Date(t) });
|
|
73
|
+
}
|
|
74
|
+
tags.sort((a, b) => a.date - b.date);
|
|
75
|
+
return tags;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** ISO del commit más antiguo (primero) de un rango git, o null. */
|
|
79
|
+
function _primerCommitISO(baseDir, rango) {
|
|
80
|
+
if (!RE_RANGO_VALIDO.test(rango)) return null;
|
|
81
|
+
let salida;
|
|
82
|
+
try {
|
|
83
|
+
salida = _git(baseDir, ['log', rango, '--reverse', '--format=%cI', '--']);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const primera = salida.split('\n').map((s) => s.trim()).filter(Boolean)[0];
|
|
88
|
+
return primera || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Nearest-rank (ceil), no interpolación. Con 2 muestras [a,b] y p=50 → a.
|
|
92
|
+
function _percentil(valoresOrdenados, p) {
|
|
93
|
+
if (valoresOrdenados.length === 0) return null;
|
|
94
|
+
const idx = Math.ceil((p / 100) * valoresOrdenados.length) - 1;
|
|
95
|
+
return valoresOrdenados[Math.max(0, Math.min(idx, valoresOrdenados.length - 1))];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _enVentana(date, desde, hasta) {
|
|
99
|
+
return date >= desde && date <= hasta;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Deployment frequency en la ventana.
|
|
104
|
+
* @returns {{disponible:boolean, totalDeploys?:number, deploysPorSemana?:number,
|
|
105
|
+
* sinDatos?:boolean, razon?:string}}
|
|
106
|
+
*/
|
|
107
|
+
function deploymentFrequency(baseDir, opts = {}) {
|
|
108
|
+
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
109
|
+
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
110
|
+
if (tags === null) return { disponible: false, razon: 'no es un repositorio git' };
|
|
111
|
+
|
|
112
|
+
const desde = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
113
|
+
const enVentana = tags.filter((t) => _enVentana(t.date, desde, hoy));
|
|
114
|
+
const semanas = ventanaDias / 7;
|
|
115
|
+
return {
|
|
116
|
+
disponible: true,
|
|
117
|
+
totalDeploys: enVentana.length,
|
|
118
|
+
deploysPorSemana: semanas > 0 ? enVentana.length / semanas : 0,
|
|
119
|
+
sinDatos: tags.length === 0,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Lead times (horas) commit->tag para tags en [desde,hasta]. */
|
|
124
|
+
function _leadTimesEnVentana(baseDir, tags, desde, hasta) {
|
|
125
|
+
const horas = [];
|
|
126
|
+
for (let i = 0; i < tags.length; i++) {
|
|
127
|
+
const tag = tags[i];
|
|
128
|
+
if (!_enVentana(tag.date, desde, hasta)) continue;
|
|
129
|
+
const prev = tags[i - 1];
|
|
130
|
+
const rango = prev ? `${prev.name}..${tag.name}` : tag.name;
|
|
131
|
+
const primeroISO = _primerCommitISO(baseDir, rango);
|
|
132
|
+
if (!primeroISO) continue;
|
|
133
|
+
const t = Date.parse(primeroISO);
|
|
134
|
+
if (!Number.isFinite(t)) continue;
|
|
135
|
+
const h = (tag.date.getTime() - t) / MS_HORA;
|
|
136
|
+
if (h >= 0) horas.push(h);
|
|
137
|
+
}
|
|
138
|
+
return horas.sort((a, b) => a - b);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Lead time for changes (commit->tag) en la ventana.
|
|
143
|
+
* @returns {{disponible:boolean, p50Horas?:number, p95Horas?:number,
|
|
144
|
+
* muestras?:number, sinDatos?:boolean, razon?:string}}
|
|
145
|
+
*/
|
|
146
|
+
function leadTimeForChanges(baseDir, opts = {}) {
|
|
147
|
+
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
148
|
+
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
149
|
+
if (tags === null) return { disponible: false, razon: 'no es un repositorio git' };
|
|
150
|
+
|
|
151
|
+
const desde = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
152
|
+
const horas = _leadTimesEnVentana(baseDir, tags, desde, hoy);
|
|
153
|
+
if (horas.length === 0) {
|
|
154
|
+
return { disponible: true, sinDatos: true, muestras: 0, p50Horas: null, p95Horas: null };
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
disponible: true,
|
|
158
|
+
sinDatos: false,
|
|
159
|
+
muestras: horas.length,
|
|
160
|
+
p50Horas: _percentil(horas, 50),
|
|
161
|
+
p95Horas: _percentil(horas, 95),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Detecta degradación de entrega comparando la ventana actual vs la previa de
|
|
167
|
+
* igual duración. Git-only y rápido — apto para el recolector del briefing.
|
|
168
|
+
* Degradación = deploy freq cae >50% O lead time mediano sube >50%.
|
|
169
|
+
* @returns {{disponible:boolean, degradado:boolean, motivo:(string|null),
|
|
170
|
+
* deployFreqActual:number, deployFreqPrevia:number,
|
|
171
|
+
* leadTimeP50Actual:(number|null), leadTimeP50Previo:(number|null)}}
|
|
172
|
+
*/
|
|
173
|
+
function detectarDegradacionEntrega(baseDir, opts = {}) {
|
|
174
|
+
const { ventanaDias = 30, hoy = new Date(), patronTag = PATRON_TAG_DEFAULT } = opts;
|
|
175
|
+
const tags = _gitTagsConFecha(baseDir, patronTag);
|
|
176
|
+
if (tags === null) {
|
|
177
|
+
return {
|
|
178
|
+
disponible: false,
|
|
179
|
+
degradado: false,
|
|
180
|
+
motivo: null,
|
|
181
|
+
deployFreqActual: 0,
|
|
182
|
+
deployFreqPrevia: 0,
|
|
183
|
+
leadTimeP50Actual: null,
|
|
184
|
+
leadTimeP50Previo: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const finActual = hoy;
|
|
189
|
+
const inicioActual = new Date(hoy.getTime() - ventanaDias * MS_DIA);
|
|
190
|
+
const inicioPrevio = new Date(hoy.getTime() - 2 * ventanaDias * MS_DIA);
|
|
191
|
+
const semanas = ventanaDias / 7;
|
|
192
|
+
|
|
193
|
+
const deploysActual = tags.filter((t) => _enVentana(t.date, inicioActual, finActual)).length;
|
|
194
|
+
const deploysPrevio = tags.filter((t) => _enVentana(t.date, inicioPrevio, inicioActual)).length;
|
|
195
|
+
const deployFreqActual = semanas > 0 ? deploysActual / semanas : 0;
|
|
196
|
+
const deployFreqPrevia = semanas > 0 ? deploysPrevio / semanas : 0;
|
|
197
|
+
|
|
198
|
+
const ltActual = _percentil(_leadTimesEnVentana(baseDir, tags, inicioActual, finActual), 50);
|
|
199
|
+
const ltPrevio = _percentil(_leadTimesEnVentana(baseDir, tags, inicioPrevio, inicioActual), 50);
|
|
200
|
+
|
|
201
|
+
const motivos = [];
|
|
202
|
+
if (deployFreqPrevia > 0 && deployFreqActual < deployFreqPrevia * 0.5) {
|
|
203
|
+
motivos.push(
|
|
204
|
+
`deployment frequency cayó >50% (${deployFreqPrevia.toFixed(2)} → ${deployFreqActual.toFixed(2)} deploys/semana)`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (ltPrevio !== null && ltActual !== null && ltActual > ltPrevio * 1.5) {
|
|
208
|
+
motivos.push(
|
|
209
|
+
`lead time mediano subió >50% (${ltPrevio.toFixed(1)}h → ${ltActual.toFixed(1)}h)`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
disponible: true,
|
|
215
|
+
degradado: motivos.length > 0,
|
|
216
|
+
motivo: motivos.length > 0 ? motivos.join('; ') : null,
|
|
217
|
+
deployFreqActual,
|
|
218
|
+
deployFreqPrevia,
|
|
219
|
+
leadTimeP50Actual: ltActual,
|
|
220
|
+
leadTimeP50Previo: ltPrevio,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
deploymentFrequency,
|
|
226
|
+
leadTimeForChanges,
|
|
227
|
+
detectarDegradacionEntrega,
|
|
228
|
+
PATRON_TAG_DEFAULT,
|
|
229
|
+
_gitTagsConFecha,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Entrypoint CLI: node scripts/lib/git-metricas.js [--json] [--dias=N] [baseDir]
|
|
233
|
+
if (!require.main || require.main === module) {
|
|
234
|
+
const args = process.argv.slice(2);
|
|
235
|
+
const json = args.includes('--json');
|
|
236
|
+
const diasArg = args.find((a) => a.startsWith('--dias='));
|
|
237
|
+
const ventanaDias = Math.max(1, Math.min(365, (diasArg ? parseInt(diasArg.slice('--dias='.length), 10) : 30) || 30));
|
|
238
|
+
const baseDir = args.find((a) => !a.startsWith('--')) || process.cwd();
|
|
239
|
+
|
|
240
|
+
const out = {
|
|
241
|
+
ventana_dias: ventanaDias,
|
|
242
|
+
deploymentFrequency: deploymentFrequency(baseDir, { ventanaDias }),
|
|
243
|
+
leadTime: leadTimeForChanges(baseDir, { ventanaDias }),
|
|
244
|
+
degradacion: detectarDegradacionEntrega(baseDir, { ventanaDias }),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (json) {
|
|
248
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
249
|
+
} else {
|
|
250
|
+
const df = out.deploymentFrequency;
|
|
251
|
+
const lt = out.leadTime;
|
|
252
|
+
process.stdout.write(`Deployment frequency: ${df.disponible ? (df.sinDatos ? 'sin datos' : df.deploysPorSemana.toFixed(2) + ' deploys/semana') : 'no disponible'}\n`);
|
|
253
|
+
process.stdout.write(`Lead time (p50/p95): ${lt.disponible && !lt.sinDatos ? `${lt.p50Horas.toFixed(1)}h / ${lt.p95Horas.toFixed(1)}h` : 'sin datos'}\n`);
|
|
254
|
+
process.stdout.write(`Degradación: ${out.degradacion.degradado ? out.degradacion.motivo : 'no'}\n`);
|
|
255
|
+
}
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
* Todo lo que instala o genera en runtime se ignora en git (framework, hooks,
|
|
12
12
|
* métricas, sesiones, aprendizajes, trazas, instintos).
|
|
13
13
|
*
|
|
14
|
-
* Solo se commitean: CLAUDE.md, .claude/settings.json,
|
|
15
|
-
* .planning/ que el equipo edita
|
|
14
|
+
* Solo se commitean: CLAUDE.md, .claude/settings.json, instintos/proyecto.yaml
|
|
15
|
+
* (memoria del proyecto), y las plantillas de .planning/ que el equipo edita
|
|
16
|
+
* (PROYECTO.md, REQUISITOS.md, etc.).
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
const fs = require("fs");
|
|
@@ -54,11 +55,8 @@ const ENTRADAS_BASE = [
|
|
|
54
55
|
".swl-ses",
|
|
55
56
|
|
|
56
57
|
// ── Runtime: archivos generados automáticamente por hooks ──────────
|
|
57
|
-
//
|
|
58
|
-
".planning/APRENDIZAJES.md",
|
|
58
|
+
// Métricas (tracking-costos)
|
|
59
59
|
".planning/METRICAS.md",
|
|
60
|
-
".planning/COMPACTACION.md",
|
|
61
|
-
".planning/REPORTE-GRAFO.md",
|
|
62
60
|
|
|
63
61
|
// Audit trail (audit-trail.js, merkle-audit.js)
|
|
64
62
|
".planning/*.jsonl",
|
|
@@ -88,8 +86,13 @@ const ENTRADAS_BASE = [
|
|
|
88
86
|
"!.planning/locks/*PLAN.md.lock",
|
|
89
87
|
".planning/user-profile/",
|
|
90
88
|
|
|
91
|
-
// Instintos
|
|
92
|
-
|
|
89
|
+
// Instintos: global/perfil-usuario/prompt-appendices/autonomia son framework +
|
|
90
|
+
// runtime + personal (no se commitean). proyecto.yaml es memoria del proyecto
|
|
91
|
+
// (hermano YAML de APRENDIZAJES.md, memoria-consolidada § canal 3) → versionable.
|
|
92
|
+
// Patrón dir/* + negación: git no re-incluye archivos bajo un dir ignorado
|
|
93
|
+
// (mismo gotcha documentado en locks/ arriba), por eso se ignora el contenido.
|
|
94
|
+
"instintos/*",
|
|
95
|
+
"!instintos/proyecto.yaml",
|
|
93
96
|
|
|
94
97
|
// Base de datos de uso
|
|
95
98
|
"usage.db",
|
|
@@ -305,8 +308,16 @@ function limpiarTracked(cwd, extras = []) {
|
|
|
305
308
|
return resultado; // No es repo git, nada que hacer
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
// Obtener todas las entradas gitignore de swl-ses (directorios y archivos)
|
|
309
|
-
|
|
311
|
+
// Obtener todas las entradas gitignore de swl-ses (directorios y archivos).
|
|
312
|
+
// Las negaciones (!...) marcan archivos versionables que NUNCA se des-trackean
|
|
313
|
+
// (instintos/proyecto.yaml, plan-locks G1 *PLAN.md.lock). git ls-files NO honra
|
|
314
|
+
// las negaciones del .gitignore, así que se excluyen explícitamente de la lista
|
|
315
|
+
// a limpiar — de lo contrario el glob padre (instintos/*, locks/*) las arrastraría.
|
|
316
|
+
const todas = [...new Set([...ENTRADAS_BASE, ...extras])];
|
|
317
|
+
const negaciones = todas
|
|
318
|
+
.filter((e) => e.startsWith("!"))
|
|
319
|
+
.map((e) => patronAGlobRegex(e.slice(1)));
|
|
320
|
+
const entradas = todas.filter((e) => !e.startsWith("!"));
|
|
310
321
|
|
|
311
322
|
for (const entrada of entradas) {
|
|
312
323
|
try {
|
|
@@ -322,7 +333,12 @@ function limpiarTracked(cwd, extras = []) {
|
|
|
322
333
|
|
|
323
334
|
if (!archivosTracked) continue;
|
|
324
335
|
|
|
325
|
-
const lista = archivosTracked
|
|
336
|
+
const lista = archivosTracked
|
|
337
|
+
.split("\n")
|
|
338
|
+
.filter(Boolean)
|
|
339
|
+
// Preservar archivos versionables cubiertos por una negación del gitignore
|
|
340
|
+
// (ej. instintos/proyecto.yaml frente al glob instintos/*).
|
|
341
|
+
.filter((archivo) => !negaciones.some((re) => re.test(archivo)));
|
|
326
342
|
if (lista.length === 0) continue;
|
|
327
343
|
|
|
328
344
|
// Quitar del índice uno por uno (--cached = solo del índice, no del disco)
|
|
@@ -353,6 +369,23 @@ function escapeRegex(str) {
|
|
|
353
369
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
354
370
|
}
|
|
355
371
|
|
|
372
|
+
/**
|
|
373
|
+
* Convierte un patrón estilo gitignore (sin el '!' inicial) a una RegExp anclada
|
|
374
|
+
* para matchear rutas que devuelve `git ls-files` (siempre con '/' como separador).
|
|
375
|
+
* `*` no cruza '/' (semántica gitignore). Una entrada de directorio matchea también
|
|
376
|
+
* su contenido. Se usa en limpiarTracked para excluir archivos negados del untrack.
|
|
377
|
+
*
|
|
378
|
+
* @param {string} patron Ej. "instintos/proyecto.yaml", ".planning/locks/*PLAN.md.lock"
|
|
379
|
+
* @returns {RegExp}
|
|
380
|
+
*/
|
|
381
|
+
function patronAGlobRegex(patron) {
|
|
382
|
+
const cuerpo = patron
|
|
383
|
+
.replace(/\/$/, "")
|
|
384
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
385
|
+
.replace(/\*/g, "[^/]*");
|
|
386
|
+
return new RegExp("^" + cuerpo + "(?:/.*)?$");
|
|
387
|
+
}
|
|
388
|
+
|
|
356
389
|
module.exports = {
|
|
357
390
|
actualizarGitignore,
|
|
358
391
|
entradasParaRuntime,
|