pumuki 6.3.41 → 6.3.43
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/docs/MCP_SERVERS.md +23 -0
- package/docs/RELEASE_NOTES.md +29 -0
- package/docs/USAGE.md +2 -0
- package/docs/registro-maestro-de-seguimiento.md +3 -3
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +120 -1
- package/integrations/git/gitAtomicity.ts +9 -1
- package/integrations/git/runPlatformGate.ts +74 -17
- package/integrations/git/runPlatformGateOutput.ts +59 -0
- package/integrations/lifecycle/cli.ts +2 -2
- package/integrations/mcp/aiGateCheck.ts +69 -0
- package/integrations/mcp/autoExecuteAiStart.ts +39 -0
- package/integrations/mcp/preFlightCheck.ts +17 -0
- package/integrations/sdd/evidenceScaffold.ts +53 -2
- package/integrations/sdd/policy.ts +57 -2
- package/integrations/sdd/sessionStore.ts +39 -3
- package/integrations/sdd/stateSync.ts +3 -3
- package/package.json +1 -1
- package/scripts/framework-menu-system-notifications-lib.ts +402 -30
package/docs/MCP_SERVERS.md
CHANGED
|
@@ -162,6 +162,8 @@ URI no soportada devuelve `404`.
|
|
|
162
162
|
### Catálogo de tools
|
|
163
163
|
|
|
164
164
|
- `ai_gate_check`
|
|
165
|
+
- `pre_flight_check`
|
|
166
|
+
- `auto_execute_ai_start`
|
|
165
167
|
- `check_sdd_status`
|
|
166
168
|
- `validate_and_fix`
|
|
167
169
|
- `sync_branches`
|
|
@@ -198,11 +200,32 @@ URI no soportada devuelve `404`.
|
|
|
198
200
|
|
|
199
201
|
- `status`: `ALLOWED|BLOCKED`
|
|
200
202
|
- `stage`
|
|
203
|
+
- `message` (resumen humano corto)
|
|
204
|
+
- `branch`
|
|
205
|
+
- `timestamp`
|
|
201
206
|
- `violations[]`
|
|
207
|
+
- `warnings[]` (por ejemplo rama protegida o upstream ausente)
|
|
208
|
+
- `auto_fixes[]` (remediaciones accionables)
|
|
202
209
|
- `evidence` (kind + age/max-age)
|
|
203
210
|
- `mcp_receipt` (required/kind/path/age para enforcement de PRE_WRITE)
|
|
204
211
|
- `repo_state` (git + lifecycle snapshot)
|
|
205
212
|
|
|
213
|
+
`pre_flight_check` añade salida de paridad legacy para lectura inmediata:
|
|
214
|
+
|
|
215
|
+
- `phase`: `GREEN|RED`
|
|
216
|
+
- `message`
|
|
217
|
+
- `instruction`
|
|
218
|
+
- `hints[]`
|
|
219
|
+
|
|
220
|
+
`auto_execute_ai_start` añade contrato humano estable por iteración:
|
|
221
|
+
|
|
222
|
+
- `action`: `proceed|ask`
|
|
223
|
+
- `phase`: `GREEN|RED`
|
|
224
|
+
- `message`
|
|
225
|
+
- `instruction`
|
|
226
|
+
- `platforms.force`
|
|
227
|
+
- `confidence_pct`, `reason_code`, `next_action`
|
|
228
|
+
|
|
206
229
|
Además, cada ejecución de `ai_gate_check` persiste un recibo auditable en:
|
|
207
230
|
|
|
208
231
|
- `.pumuki/artifacts/mcp-ai-gate-receipt.json`
|
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -5,6 +5,35 @@ Detailed commit history remains available through Git history (`git log` / `git
|
|
|
5
5
|
|
|
6
6
|
## 2026-03 (enterprise hardening updates)
|
|
7
7
|
|
|
8
|
+
### 2026-03-05 (v6.3.43)
|
|
9
|
+
|
|
10
|
+
- `pumuki sdd evidence` alinea su salida con el contrato TDD/BDD del gate:
|
|
11
|
+
- `version: "1"` (antes `1.0`),
|
|
12
|
+
- `slices[]` generado por defecto con estructura `red/green/refactor`.
|
|
13
|
+
- Compatibilidad de transición mantenida:
|
|
14
|
+
- el artefacto conserva campos legacy (`scenario_id`, `test_run`, `ai_evidence`) para flujos existentes,
|
|
15
|
+
- `pumuki sdd state-sync` acepta source evidence `version=1` y `version=1.0`.
|
|
16
|
+
- Evidencia de validación:
|
|
17
|
+
- `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/evidenceScaffold.test.ts integrations/sdd/__tests__/stateSync.test.ts integrations/lifecycle/__tests__/cli.test.ts` (`47 pass / 0 fail`)
|
|
18
|
+
- `npm run -s typecheck` (`PASS`)
|
|
19
|
+
- smoke real en Flux con CLI local: `pumuki sdd evidence ... --json` -> artefacto `version: "1"` con `slices[]`.
|
|
20
|
+
|
|
21
|
+
### 2026-03-05 (v6.3.42)
|
|
22
|
+
|
|
23
|
+
- Modal flotante de bloqueo (macOS) ajustado para legibilidad real:
|
|
24
|
+
- crecimiento preferente en vertical según contenido,
|
|
25
|
+
- ancho acotado para evitar diálogos excesivamente horizontales,
|
|
26
|
+
- tipografía más compacta para causa/solución.
|
|
27
|
+
- Remediación por defecto más accionable en bloqueos:
|
|
28
|
+
- mensajes enriquecidos para `EVIDENCE_*`, `PRE_PUSH_UPSTREAM_MISSING`, `SDD_SESSION_*`,
|
|
29
|
+
- más contexto operativo sin truncado agresivo.
|
|
30
|
+
- Ajustes de robustez visual:
|
|
31
|
+
- wrapping explícito multilínea en campos del modal,
|
|
32
|
+
- pinning bottom-right estable tras recalcular layout.
|
|
33
|
+
- Evidencia de validación:
|
|
34
|
+
- `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts integrations/mcp/__tests__/aiGateCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/git/__tests__/runPlatformGateOutput.test.ts integrations/git/__tests__/gitAtomicity.test.ts integrations/sdd/__tests__/policy.test.ts integrations/sdd/__tests__/sessionStore.test.ts` (`94 pass / 0 fail`)
|
|
35
|
+
- `npm run -s typecheck` (`PASS`)
|
|
36
|
+
|
|
8
37
|
### 2026-03-05 (v6.3.41)
|
|
9
38
|
|
|
10
39
|
- UX de notificaciones de bloqueo mejorada para escenarios multi-repo:
|
package/docs/USAGE.md
CHANGED
|
@@ -204,6 +204,8 @@ Stage mapping:
|
|
|
204
204
|
If a scope is empty, the menu prints an explicit operational hint (`Scope vacío`), so `PASS` with zero findings is distinguishable from a clean repository scan.
|
|
205
205
|
|
|
206
206
|
System notifications (macOS) can be enabled from advanced menu option `31` (persisted in `.pumuki/system-notifications.json`).
|
|
207
|
+
Blocked notifications now use a native Swift floating modal (bottom-right) by default, with AppleScript fallback.
|
|
208
|
+
Override mode with `PUMUKI_MACOS_BLOCKED_DIALOG_MODE=auto|swift-floating|applescript`.
|
|
207
209
|
Custom skills import is available in advanced menu option `33` (writes `/.pumuki/custom-rules.json`).
|
|
208
210
|
Menu-driven audits apply SDD guardrails with the same strict semantics as stage runners (no bypass).
|
|
209
211
|
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
## Estado actual
|
|
8
8
|
- Plan activo: `docs/seguimiento-activo-pumuki-saas-supermercados.md`
|
|
9
9
|
- Estado del plan: EJECUCION
|
|
10
|
-
- Última task cerrada (`✅`): PUMUKI-
|
|
11
|
-
- Task activa (`🚧`): PUMUKI-
|
|
12
|
-
- Nuevos pendientes añadidos (`⏳`): ninguno
|
|
10
|
+
- Última task cerrada (`✅`): PUMUKI-146 (opción 2 UX cerrada: helper Swift nativo para modal flotante abajo-derecha con fallback AppleScript).
|
|
11
|
+
- Task activa (`🚧`): PUMUKI-147 (monitorización post-opción-2 en uso real multi-repo).
|
|
12
|
+
- Nuevos pendientes añadidos (`⏳`): ninguno.
|
|
13
13
|
|
|
14
14
|
## Historial resumido
|
|
15
15
|
- Bloque RuralGO cerrado: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`.
|
|
@@ -1740,7 +1740,7 @@
|
|
|
1740
1740
|
- `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts` -> `13 pass / 0 fail`.
|
|
1741
1741
|
- `npm run -s typecheck` -> `PASS`.
|
|
1742
1742
|
|
|
1743
|
-
-
|
|
1743
|
+
- ✅ PUMUKI-139: Monitorizar feedback post-release en consumidores reales (SAAS y RuralGo) y registrar únicamente hallazgos netos nuevos para siguiente corte.
|
|
1744
1744
|
- Alcance:
|
|
1745
1745
|
- vigilar nuevos bloqueos/regresiones tras `6.3.41`,
|
|
1746
1746
|
- mantener MDs externos sincronizados por leyenda sin contradicciones,
|
|
@@ -1755,3 +1755,122 @@
|
|
|
1755
1755
|
- Evidencia (2026-03-05):
|
|
1756
1756
|
- `npm publish --access public` -> `+ pumuki@6.3.41`.
|
|
1757
1757
|
- `npm view pumuki version` -> `6.3.41`.
|
|
1758
|
+
|
|
1759
|
+
- ✅ PUMUKI-141: Cerrar hallazgo Flux `PUM-001` (autoguía SDD en `SDD_SESSION_MISSING`) con comando asistido y sugerencia concreta.
|
|
1760
|
+
- Resultado implementado:
|
|
1761
|
+
- `integrations/sdd/sessionStore.ts`: soporte real para `pumuki sdd session --open --change=auto`.
|
|
1762
|
+
- `integrations/sdd/policy.ts`: guidance contextual para `SDD_SESSION_MISSING` con `command/fallbackCommand/suggestedChangeId`.
|
|
1763
|
+
- `integrations/lifecycle/cli.ts`: ayuda CLI actualizada (`--change=<change-id|auto>`).
|
|
1764
|
+
- Evidencia (2026-03-05):
|
|
1765
|
+
- `npx --yes tsx@4.21.0 --test integrations/sdd/__tests__/sessionStore.test.ts integrations/sdd/__tests__/policy.test.ts` -> `27 pass / 0 fail`.
|
|
1766
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1767
|
+
|
|
1768
|
+
- ✅ PUMUKI-142: Cerrar hallazgos Flux `PUM-003` + `PUM-004` (DX de bloqueo jerárquico + remediación accionable por scopes).
|
|
1769
|
+
- Resultado implementado:
|
|
1770
|
+
- `integrations/git/runPlatformGateOutput.ts`: resumen jerárquico de bloqueo (`primary`, `next_action`) antes del detalle completo de findings.
|
|
1771
|
+
- `integrations/git/gitAtomicity.ts`: remediación `GIT_ATOMICITY_TOO_MANY_SCOPES` con scopes detectados y ejemplo directo de split de staging.
|
|
1772
|
+
- Tests nuevos:
|
|
1773
|
+
- `integrations/git/__tests__/runPlatformGateOutput.test.ts`
|
|
1774
|
+
- ampliación en `integrations/git/__tests__/gitAtomicity.test.ts`.
|
|
1775
|
+
- Evidencia (2026-03-05):
|
|
1776
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/gitAtomicity.test.ts integrations/git/__tests__/runPlatformGateOutput.test.ts` -> `7 pass / 0 fail`.
|
|
1777
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `91 pass / 0 fail`.
|
|
1778
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1779
|
+
|
|
1780
|
+
- ✅ PUMUKI-143: Ejecutar siguiente hallazgo Flux `PUM-002` (modo proporcional por riesgo para bloqueos `skills.frontend.*` en cambios low-risk).
|
|
1781
|
+
- Resultado implementado:
|
|
1782
|
+
- `integrations/git/runPlatformGate.ts`:
|
|
1783
|
+
- nuevo soft-enforcement determinista para `PRE_COMMIT` low-risk (`PUMUKI_PRE_COMMIT_SOFT_SKILLS`, on por defecto),
|
|
1784
|
+
- degrada a `WARN` (sin bypass total) findings de coverage skills (`platform-coverage`, `cross-platform-critical`, `scope-compliance`) cuando:
|
|
1785
|
+
- ventana low-risk (`observed_code_paths<=3`),
|
|
1786
|
+
- no hay bloqueantes core reales (`ERROR/CRITICAL`) en findings nativos/TDD/AST/policy/coverage hard.
|
|
1787
|
+
- `integrations/git/__tests__/runPlatformGate.test.ts`:
|
|
1788
|
+
- cobertura RED->GREEN para soft-enforcement en `PRE_COMMIT`.
|
|
1789
|
+
- Evidencia (2026-03-05):
|
|
1790
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGate.test.ts` -> `33 pass / 0 fail`.
|
|
1791
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts integrations/sdd/__tests__/sessionStore.test.ts integrations/sdd/__tests__/policy.test.ts integrations/git/__tests__/gitAtomicity.test.ts integrations/git/__tests__/runPlatformGateOutput.test.ts` -> `60 pass / 0 fail`.
|
|
1792
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1793
|
+
- Alcance:
|
|
1794
|
+
- diseñar criterio determinista de low-risk (copy/docs) sin abrir bypass inseguro,
|
|
1795
|
+
- mantener enforcement fuerte en cambios funcionales reales,
|
|
1796
|
+
- validar con RED->GREEN->REFACTOR y actualizar leyenda del MD de Flux.
|
|
1797
|
+
|
|
1798
|
+
- ✅ PUMUKI-144: Monitorizar nuevos hallazgos netos tras cierre completo del backlog Flux (sin reabrir issues ya cerradas).
|
|
1799
|
+
- Resultado:
|
|
1800
|
+
- reapareció gap neto de paridad UX legacy en tools MCP (feedback humano por iteración),
|
|
1801
|
+
- se consolidó como mejora real de producto (no reapertura de `PUM-001..004`).
|
|
1802
|
+
|
|
1803
|
+
- ✅ PUMUKI-145: Implementar paridad de feedback humano en tools MCP (`auto_execute_ai_start`, `pre_flight_check`, `ai_gate_check`).
|
|
1804
|
+
- Resultado implementado:
|
|
1805
|
+
- `integrations/mcp/autoExecuteAiStart.ts`: salida humana estable con `phase`, `message`, `instruction` y `platforms.force`.
|
|
1806
|
+
- `integrations/mcp/preFlightCheck.ts`: añade `phase`, `message`, `instruction`, `ast_analysis`, `tdd_status` para lectura directa en chat/tooling.
|
|
1807
|
+
- `integrations/mcp/aiGateCheck.ts`: añade `message`, `timestamp`, `branch`, `warnings`, `auto_fixes` sin romper contrato existente.
|
|
1808
|
+
- Tests ampliados:
|
|
1809
|
+
- `integrations/mcp/__tests__/autoExecuteAiStart.test.ts`
|
|
1810
|
+
- `integrations/mcp/__tests__/preFlightCheck.test.ts`
|
|
1811
|
+
- `integrations/mcp/__tests__/aiGateCheck.test.ts`
|
|
1812
|
+
- Evidencia (2026-03-05):
|
|
1813
|
+
- `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/aiGateCheck.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts` -> `12 pass / 0 fail`.
|
|
1814
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1815
|
+
|
|
1816
|
+
- ✅ PUMUKI-146: Cerrar opción 2 UX de notificación bloqueante (modal flotante no intrusiva + posicionamiento consistente para no tapar el centro).
|
|
1817
|
+
- Resultado implementado:
|
|
1818
|
+
- `scripts/framework-menu-system-notifications-lib.ts`:
|
|
1819
|
+
- helper nativo Swift para diálogo bloqueante flotante (esquina inferior derecha),
|
|
1820
|
+
- persistencia runtime del script en `.pumuki/runtime/pumuki-blocked-dialog.swift`,
|
|
1821
|
+
- selector de modo `PUMUKI_MACOS_BLOCKED_DIALOG_MODE=auto|swift-floating|applescript`,
|
|
1822
|
+
- fallback automático a AppleScript si el helper Swift falla.
|
|
1823
|
+
- `scripts/__tests__/framework-menu-system-notifications.test.ts`:
|
|
1824
|
+
- cobertura para modo por defecto (Swift), modo forzado AppleScript y fallback Swift->AppleScript.
|
|
1825
|
+
- `docs/USAGE.md`: contrato operativo del nuevo modo de diálogo.
|
|
1826
|
+
- Evidencia (2026-03-05):
|
|
1827
|
+
- `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts` -> `15 pass / 0 fail`.
|
|
1828
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1829
|
+
|
|
1830
|
+
- ✅ PUMUKI-147: Monitorizar feedback real post-opción-2 (legibilidad, anti-spam, foco no intrusivo) y cerrar ajustes finos si aparece regresión.
|
|
1831
|
+
- Alcance:
|
|
1832
|
+
- validar comportamiento en múltiples repos abiertos simultáneamente,
|
|
1833
|
+
- confirmar que acciones de diálogo (`Desactivar`, `Silenciar 30 min`, `Mantener activas`) se mantienen consistentes,
|
|
1834
|
+
- registrar solo hallazgos netos nuevos antes del siguiente corte release.
|
|
1835
|
+
- Ajuste aplicado (2026-03-05):
|
|
1836
|
+
- pinning reforzado de posición del helper Swift: selección de pantalla por cursor, posicionamiento explícito bottom-right y repinning tras `orderFront` para evitar recentrado.
|
|
1837
|
+
- UI responsive del modal flotante: ancho/alto dinámicos según longitud real de `title/cause/remediation`, con límites por pantalla y autoajuste de layout para evitar texto truncado.
|
|
1838
|
+
- tuning visual adicional: ancho máximo reducido, crecimiento preferente en vertical y tipografía compacta para evitar modales excesivamente anchos/bastos en textos largos.
|
|
1839
|
+
- Evidencia:
|
|
1840
|
+
- `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts` -> `15 pass / 0 fail`.
|
|
1841
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
1842
|
+
- smoke manual con payload largo (`/tmp/pumuki_modal_smoke.ts`) -> `{"delivered":true,"reason":"delivered"}`.
|
|
1843
|
+
|
|
1844
|
+
- ✅ PUMUKI-148: Ejecutar verificación UX final en entorno real multi-proyecto (notificación + modal) y preparar corte de release sin regresiones.
|
|
1845
|
+
- Alcance:
|
|
1846
|
+
- validar que textos largos no truncen en causa/solución,
|
|
1847
|
+
- validar consistencia visual de acciones del modal en distintas resoluciones,
|
|
1848
|
+
- confirmar que no hay regresiones en fallback AppleScript.
|
|
1849
|
+
- Avance (2026-03-05):
|
|
1850
|
+
- patrón de remediación enriquecido por defecto en bloqueos (`EVIDENCE_STALE`, `PRE_PUSH_UPSTREAM_MISSING`, `SDD_SESSION_*`, `BACKEND_AVOID_EXPLICIT_ANY`) para mantener mensajes accionables y humanos en modal/notificación.
|
|
1851
|
+
- longitud de remediación ampliada (`max 220`) para preservar pasos útiles sin recorte agresivo.
|
|
1852
|
+
- validación manual de modales de prueba (corto/medio/largo/extremo) en helper Swift flotante con rendering consistente y sin regresión de acciones (`Desactivar`, `Silenciar 30 min`, `Mantener activas`).
|
|
1853
|
+
|
|
1854
|
+
- ✅ PUMUKI-149: Preparar corte release del bloque UX-notificaciones y ejecutar upgrade/smoke en consumidores (`SAAS`, `RuralGo`, `Flux_training`).
|
|
1855
|
+
- Alcance:
|
|
1856
|
+
- cerrar changelog/release notes del bloque `PUMUKI-145..148`,
|
|
1857
|
+
- publicar versión npm con trazabilidad,
|
|
1858
|
+
- ejecutar `install/update` + smoke mínimo en los tres repos consumidores,
|
|
1859
|
+
- actualizar estado final en MDs externos por leyenda.
|
|
1860
|
+
- Resultado (2026-03-05):
|
|
1861
|
+
- publicación npm completada: `pumuki@6.3.42`.
|
|
1862
|
+
- upgrade aplicado con `pumuki install` en:
|
|
1863
|
+
- `SAAS:APP_SUPERMERCADOS` (`lifecycle_version=6.3.42`)
|
|
1864
|
+
- `R_GO` (`lifecycle_version=6.3.42`)
|
|
1865
|
+
- `Flux_training` (`lifecycle_version=6.3.42`)
|
|
1866
|
+
- smoke de salud post-upgrade en 3/3 consumidores:
|
|
1867
|
+
- `doctor_issues=0`
|
|
1868
|
+
- `openspec_compatible=true`
|
|
1869
|
+
- `session_active=true`
|
|
1870
|
+
|
|
1871
|
+
- 🚧 PUMUKI-150: Ejecutar siguiente pendiente real en backlog externo (Flux `PUM-005`) y cerrar incidencia de `pumuki sdd evidence` con contrato v1 compatible con gate TDD.
|
|
1872
|
+
- Alcance:
|
|
1873
|
+
- reproducir `PUM-005` con evidencia local controlada,
|
|
1874
|
+
- aplicar fix mínimo en core (`sdd evidence` -> `version=\"1\"` + `slices[]`),
|
|
1875
|
+
- validar RED->GREEN con smoke en `Flux_training`,
|
|
1876
|
+
- actualizar leyenda en `docs/BUGS_Y_MEJORAS_PUMUKI.md` (Flux) sin tocar código del consumer.
|
|
@@ -265,11 +265,19 @@ export const evaluateGitAtomicity = (params: {
|
|
|
265
265
|
.filter((scopeKey) => scopeKey.length > 0)
|
|
266
266
|
);
|
|
267
267
|
if (scopeKeys.size > config.maxScopes) {
|
|
268
|
+
const sortedScopes = [...scopeKeys].sort();
|
|
269
|
+
const suggestedScopeAdds = sortedScopes
|
|
270
|
+
.slice(0, Math.max(1, Math.min(config.maxScopes + 1, 3)))
|
|
271
|
+
.map((scope) => `git add ${scope}/`)
|
|
272
|
+
.join(' && ');
|
|
268
273
|
violations.push({
|
|
269
274
|
code: 'GIT_ATOMICITY_TOO_MANY_SCOPES',
|
|
270
275
|
message:
|
|
271
276
|
`Git atomicity guard blocked at ${params.stage}: changed_scopes=${scopeKeys.size} exceeds max_scopes=${config.maxScopes}.`,
|
|
272
|
-
remediation:
|
|
277
|
+
remediation:
|
|
278
|
+
`Agrupa cambios por ámbito funcional (máximo ${config.maxScopes} scopes por commit). ` +
|
|
279
|
+
`scopes_detectados=[${sortedScopes.join(', ')}]. ` +
|
|
280
|
+
`Sugerencia: git restore --staged . && ${suggestedScopeAdds} && git commit -m "<tipo>: <scope>".`,
|
|
273
281
|
});
|
|
274
282
|
}
|
|
275
283
|
|
|
@@ -742,6 +742,28 @@ const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
|
|
|
742
742
|
return finding.severity === 'ERROR' || finding.severity === 'CRITICAL';
|
|
743
743
|
};
|
|
744
744
|
|
|
745
|
+
const toSoftPreCommitSkillsFinding = (params: {
|
|
746
|
+
finding: Finding | undefined;
|
|
747
|
+
enabled: boolean;
|
|
748
|
+
observedCodePaths: ReadonlyArray<string>;
|
|
749
|
+
}): Finding | undefined => {
|
|
750
|
+
if (!params.finding) {
|
|
751
|
+
return undefined;
|
|
752
|
+
}
|
|
753
|
+
if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
|
|
754
|
+
return params.finding;
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
...params.finding,
|
|
758
|
+
severity: 'WARN',
|
|
759
|
+
code: `${params.finding.code}_SOFT_PRECOMMIT`,
|
|
760
|
+
message:
|
|
761
|
+
`${params.finding.message} ` +
|
|
762
|
+
`Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
|
|
763
|
+
'Strict enforcement remains active at PRE_PUSH/CI.',
|
|
764
|
+
};
|
|
765
|
+
};
|
|
766
|
+
|
|
745
767
|
export async function runPlatformGate(params: {
|
|
746
768
|
policy: GatePolicy;
|
|
747
769
|
auditMode?: 'gate' | 'engine';
|
|
@@ -813,6 +835,7 @@ export async function runPlatformGate(params: {
|
|
|
813
835
|
git,
|
|
814
836
|
});
|
|
815
837
|
const filesScanned = countScannedFilesFromFacts(facts);
|
|
838
|
+
const observedCodePaths = collectObservedCodePathsFromFacts(facts);
|
|
816
839
|
|
|
817
840
|
const {
|
|
818
841
|
detectedPlatforms,
|
|
@@ -1002,15 +1025,49 @@ export async function runPlatformGate(params: {
|
|
|
1002
1025
|
const hasTddBddBlockingFinding = tddBddEvaluation.findings.some(
|
|
1003
1026
|
(finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
|
|
1004
1027
|
);
|
|
1028
|
+
const hasNativeBlockingFinding = findings.some(
|
|
1029
|
+
(finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
|
|
1030
|
+
);
|
|
1031
|
+
const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS !== '0';
|
|
1032
|
+
const lowRiskPreCommitWindow = observedCodePaths.length > 0 && observedCodePaths.length <= 3;
|
|
1033
|
+
const shouldSoftEnforceSkillsFindings =
|
|
1034
|
+
params.policy.stage === 'PRE_COMMIT'
|
|
1035
|
+
&& preCommitSoftSkillsEnabled
|
|
1036
|
+
&& lowRiskPreCommitWindow
|
|
1037
|
+
&& !sddBlockingFinding
|
|
1038
|
+
&& !degradedModeBlocks
|
|
1039
|
+
&& !shouldBlockFromFinding(policyAsCodeBlockingFinding)
|
|
1040
|
+
&& !shouldBlockFromFinding(unsupportedSkillsMappingFinding)
|
|
1041
|
+
&& !shouldBlockFromFinding(coverageBlockingFinding)
|
|
1042
|
+
&& !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
|
|
1043
|
+
&& !shouldBlockFromFinding(iosTestsQualityFinding)
|
|
1044
|
+
&& !shouldBlockFromFinding(astIntelligenceDualFinding)
|
|
1045
|
+
&& !hasTddBddBlockingFinding
|
|
1046
|
+
&& !hasNativeBlockingFinding;
|
|
1047
|
+
const effectivePlatformSkillsCoverageFinding = toSoftPreCommitSkillsFinding({
|
|
1048
|
+
finding: platformSkillsCoverageFinding,
|
|
1049
|
+
enabled: shouldSoftEnforceSkillsFindings,
|
|
1050
|
+
observedCodePaths,
|
|
1051
|
+
});
|
|
1052
|
+
const effectiveCrossPlatformCriticalFinding = toSoftPreCommitSkillsFinding({
|
|
1053
|
+
finding: crossPlatformCriticalFinding,
|
|
1054
|
+
enabled: shouldSoftEnforceSkillsFindings,
|
|
1055
|
+
observedCodePaths,
|
|
1056
|
+
});
|
|
1057
|
+
const effectiveSkillsScopeComplianceFinding = toSoftPreCommitSkillsFinding({
|
|
1058
|
+
finding: skillsScopeComplianceFinding,
|
|
1059
|
+
enabled: shouldSoftEnforceSkillsFindings,
|
|
1060
|
+
observedCodePaths,
|
|
1061
|
+
});
|
|
1005
1062
|
const effectiveFindings = sddBlockingFinding
|
|
1006
1063
|
? [
|
|
1007
1064
|
sddBlockingFinding,
|
|
1008
1065
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1009
1066
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1010
1067
|
...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
|
|
1011
|
-
...(
|
|
1012
|
-
...(
|
|
1013
|
-
...(
|
|
1068
|
+
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
1069
|
+
...(effectiveCrossPlatformCriticalFinding ? [effectiveCrossPlatformCriticalFinding] : []),
|
|
1070
|
+
...(effectiveSkillsScopeComplianceFinding ? [effectiveSkillsScopeComplianceFinding] : []),
|
|
1014
1071
|
...(activeRulesEmptyForCodeChangesFinding ? [activeRulesEmptyForCodeChangesFinding] : []),
|
|
1015
1072
|
...(iosTestsQualityFinding ? [iosTestsQualityFinding] : []),
|
|
1016
1073
|
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
@@ -1019,9 +1076,9 @@ export async function runPlatformGate(params: {
|
|
|
1019
1076
|
...findings,
|
|
1020
1077
|
]
|
|
1021
1078
|
: unsupportedSkillsMappingFinding
|
|
1022
|
-
||
|
|
1023
|
-
||
|
|
1024
|
-
||
|
|
1079
|
+
|| effectivePlatformSkillsCoverageFinding
|
|
1080
|
+
|| effectiveCrossPlatformCriticalFinding
|
|
1081
|
+
|| effectiveSkillsScopeComplianceFinding
|
|
1025
1082
|
|| activeRulesEmptyForCodeChangesFinding
|
|
1026
1083
|
|| iosTestsQualityFinding
|
|
1027
1084
|
|| astIntelligenceDualFinding
|
|
@@ -1033,9 +1090,9 @@ export async function runPlatformGate(params: {
|
|
|
1033
1090
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1034
1091
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1035
1092
|
...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
|
|
1036
|
-
...(
|
|
1037
|
-
...(
|
|
1038
|
-
...(
|
|
1093
|
+
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
1094
|
+
...(effectiveCrossPlatformCriticalFinding ? [effectiveCrossPlatformCriticalFinding] : []),
|
|
1095
|
+
...(effectiveSkillsScopeComplianceFinding ? [effectiveSkillsScopeComplianceFinding] : []),
|
|
1039
1096
|
...(activeRulesEmptyForCodeChangesFinding ? [activeRulesEmptyForCodeChangesFinding] : []),
|
|
1040
1097
|
...(iosTestsQualityFinding ? [iosTestsQualityFinding] : []),
|
|
1041
1098
|
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
@@ -1049,15 +1106,15 @@ export async function runPlatformGate(params: {
|
|
|
1049
1106
|
const baseGateOutcome =
|
|
1050
1107
|
sddBlockingFinding ||
|
|
1051
1108
|
degradedModeBlocks ||
|
|
1052
|
-
policyAsCodeBlockingFinding ||
|
|
1053
|
-
unsupportedSkillsMappingFinding ||
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
activeRulesEmptyForCodeChangesFinding ||
|
|
1058
|
-
iosTestsQualityFinding ||
|
|
1109
|
+
shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
|
|
1110
|
+
shouldBlockFromFinding(unsupportedSkillsMappingFinding) ||
|
|
1111
|
+
shouldBlockFromFinding(effectivePlatformSkillsCoverageFinding) ||
|
|
1112
|
+
shouldBlockFromFinding(effectiveCrossPlatformCriticalFinding) ||
|
|
1113
|
+
shouldBlockFromFinding(effectiveSkillsScopeComplianceFinding) ||
|
|
1114
|
+
shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding) ||
|
|
1115
|
+
shouldBlockFromFinding(iosTestsQualityFinding) ||
|
|
1059
1116
|
hasAstIntelligenceBlockingFinding ||
|
|
1060
|
-
coverageBlockingFinding ||
|
|
1117
|
+
shouldBlockFromFinding(coverageBlockingFinding) ||
|
|
1061
1118
|
hasTddBddBlockingFinding
|
|
1062
1119
|
? 'BLOCK'
|
|
1063
1120
|
: decision.outcome;
|
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
import type { Finding } from '../../core/gate/Finding';
|
|
2
2
|
|
|
3
|
+
const BLOCK_NEXT_ACTION_BY_CODE: Readonly<Record<string, string>> = {
|
|
4
|
+
SDD_SESSION_MISSING:
|
|
5
|
+
'npx --yes --package pumuki@latest pumuki sdd session --open --change=auto',
|
|
6
|
+
SDD_SESSION_INVALID:
|
|
7
|
+
'npx --yes --package pumuki@latest pumuki sdd session --refresh --ttl-minutes=90',
|
|
8
|
+
PRE_PUSH_UPSTREAM_MISSING:
|
|
9
|
+
'git push --set-upstream origin <branch>',
|
|
10
|
+
PRE_PUSH_UPSTREAM_MISALIGNED:
|
|
11
|
+
'git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
12
|
+
EVIDENCE_STALE:
|
|
13
|
+
'npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
14
|
+
EVIDENCE_BRANCH_MISMATCH:
|
|
15
|
+
'npx --yes --package pumuki@latest pumuki-pre-commit',
|
|
16
|
+
GIT_ATOMICITY_TOO_MANY_FILES:
|
|
17
|
+
'git restore --staged . && separa cambios en commits más pequeños',
|
|
18
|
+
GIT_ATOMICITY_TOO_MANY_SCOPES:
|
|
19
|
+
'git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const severityWeight = (severity: string): number => {
|
|
23
|
+
switch (severity.toUpperCase()) {
|
|
24
|
+
case 'CRITICAL':
|
|
25
|
+
return 5;
|
|
26
|
+
case 'ERROR':
|
|
27
|
+
return 4;
|
|
28
|
+
case 'WARN':
|
|
29
|
+
return 3;
|
|
30
|
+
case 'INFO':
|
|
31
|
+
return 2;
|
|
32
|
+
default:
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const resolvePrimaryFinding = (findings: ReadonlyArray<Finding>): Finding => {
|
|
38
|
+
let primary = findings[0];
|
|
39
|
+
for (const finding of findings.slice(1)) {
|
|
40
|
+
if (!primary) {
|
|
41
|
+
primary = finding;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (severityWeight(finding.severity) > severityWeight(primary.severity)) {
|
|
45
|
+
primary = finding;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return primary ?? findings[0]!;
|
|
49
|
+
};
|
|
50
|
+
|
|
3
51
|
const normalizeAnchorLine = (lines: Finding['lines']): number => {
|
|
4
52
|
if (Array.isArray(lines)) {
|
|
5
53
|
const candidates = lines
|
|
@@ -43,6 +91,17 @@ const formatFinding = (finding: Finding): string => {
|
|
|
43
91
|
};
|
|
44
92
|
|
|
45
93
|
export const printGateFindings = (findings: ReadonlyArray<Finding>): void => {
|
|
94
|
+
if (findings.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const primary = resolvePrimaryFinding(findings);
|
|
98
|
+
const nextAction =
|
|
99
|
+
BLOCK_NEXT_ACTION_BY_CODE[primary.code]
|
|
100
|
+
?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.';
|
|
101
|
+
process.stdout.write(
|
|
102
|
+
`[pumuki][block-summary] primary=${primary.code} severity=${primary.severity.toUpperCase()} rule=${primary.ruleId}\n`
|
|
103
|
+
);
|
|
104
|
+
process.stdout.write(`[pumuki][block-summary] next_action=${nextAction}\n`);
|
|
46
105
|
for (const finding of findings) {
|
|
47
106
|
process.stdout.write(`${formatFinding(finding)}\n`);
|
|
48
107
|
}
|
|
@@ -191,7 +191,7 @@ Pumuki lifecycle commands:
|
|
|
191
191
|
pumuki policy reconcile [--strict] [--json]
|
|
192
192
|
pumuki sdd status [--json]
|
|
193
193
|
pumuki sdd validate [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--json]
|
|
194
|
-
pumuki sdd session --open --change=<change-id> [--ttl-minutes=<n>] [--json]
|
|
194
|
+
pumuki sdd session --open --change=<change-id|auto> [--ttl-minutes=<n>] [--json]
|
|
195
195
|
pumuki sdd session --refresh [--ttl-minutes=<n>] [--json]
|
|
196
196
|
pumuki sdd session --close [--json]
|
|
197
197
|
pumuki sdd sync-docs [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
|
|
@@ -2417,7 +2417,7 @@ export const runLifecycleCli = async (
|
|
|
2417
2417
|
}
|
|
2418
2418
|
}
|
|
2419
2419
|
const shouldEvaluateAiGate = result.stage === 'PRE_WRITE';
|
|
2420
|
-
let aiGate = shouldEvaluateAiGate
|
|
2420
|
+
let aiGate: ReturnType<typeof evaluateAiGate> | null = shouldEvaluateAiGate
|
|
2421
2421
|
? runEnterpriseAiGateCheck({
|
|
2422
2422
|
repoRoot: process.cwd(),
|
|
2423
2423
|
stage: result.stage,
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
|
|
2
2
|
|
|
3
|
+
const AUTO_FIX_BY_CODE: Readonly<Record<string, string>> = {
|
|
4
|
+
EVIDENCE_MISSING: 'Ejecuta una auditoría para generar .ai_evidence.json.',
|
|
5
|
+
EVIDENCE_INVALID: 'Regenera .ai_evidence.json y vuelve a evaluar.',
|
|
6
|
+
EVIDENCE_STALE: 'Refresca evidencia antes de continuar.',
|
|
7
|
+
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual.',
|
|
8
|
+
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este repositorio.',
|
|
9
|
+
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta git push --set-upstream origin <branch>.',
|
|
10
|
+
GITFLOW_PROTECTED_BRANCH: 'Crea una rama feature/* y mueve el trabajo allí.',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
14
|
+
|
|
3
15
|
export type EnterpriseAiGateCheckResult = {
|
|
4
16
|
tool: 'ai_gate_check';
|
|
5
17
|
dryRun: true;
|
|
@@ -8,9 +20,14 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
8
20
|
result: {
|
|
9
21
|
allowed: ReturnType<typeof evaluateAiGate>['allowed'];
|
|
10
22
|
status: ReturnType<typeof evaluateAiGate>['status'];
|
|
23
|
+
timestamp: string | null;
|
|
24
|
+
branch: string | null;
|
|
25
|
+
message: string;
|
|
11
26
|
stage: ReturnType<typeof evaluateAiGate>['stage'];
|
|
12
27
|
policy: ReturnType<typeof evaluateAiGate>['policy'];
|
|
13
28
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
29
|
+
warnings: ReadonlyArray<string>;
|
|
30
|
+
auto_fixes: ReadonlyArray<string>;
|
|
14
31
|
evidence: ReturnType<typeof evaluateAiGate>['evidence'];
|
|
15
32
|
mcp_receipt: ReturnType<typeof evaluateAiGate>['mcp_receipt'];
|
|
16
33
|
skills_contract: ReturnType<typeof evaluateAiGate>['skills_contract'];
|
|
@@ -68,6 +85,48 @@ const buildConsistencyHint = (
|
|
|
68
85
|
};
|
|
69
86
|
};
|
|
70
87
|
|
|
88
|
+
const buildWarnings = (evaluation: ReturnType<typeof evaluateAiGate>): ReadonlyArray<string> => {
|
|
89
|
+
const warnings: string[] = [];
|
|
90
|
+
const currentBranch = evaluation.repo_state.git.branch;
|
|
91
|
+
if (typeof currentBranch === 'string' && PROTECTED_BRANCHES.has(currentBranch.toLowerCase())) {
|
|
92
|
+
warnings.push(
|
|
93
|
+
`ON_PROTECTED_BRANCH: Estás en '${currentBranch}'. Crea una rama feature/* antes de continuar.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (evaluation.stage === 'PRE_PUSH' && !evaluation.repo_state.git.upstream) {
|
|
97
|
+
warnings.push('NO_UPSTREAM: Configura upstream con git push --set-upstream origin <branch>.');
|
|
98
|
+
}
|
|
99
|
+
return warnings;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const buildAutoFixes = (evaluation: ReturnType<typeof evaluateAiGate>): ReadonlyArray<string> => {
|
|
103
|
+
const fixes: string[] = [];
|
|
104
|
+
const emittedCodes = new Set<string>();
|
|
105
|
+
for (const violation of evaluation.violations) {
|
|
106
|
+
if (emittedCodes.has(violation.code)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const fix = AUTO_FIX_BY_CODE[violation.code];
|
|
110
|
+
if (!fix) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
fixes.push(fix);
|
|
114
|
+
emittedCodes.add(violation.code);
|
|
115
|
+
}
|
|
116
|
+
return fixes;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const buildMessage = (evaluation: ReturnType<typeof evaluateAiGate>): string => {
|
|
120
|
+
if (evaluation.allowed) {
|
|
121
|
+
return `✅ Gate ${evaluation.stage} ALLOWED.`;
|
|
122
|
+
}
|
|
123
|
+
const firstViolation = evaluation.violations[0];
|
|
124
|
+
if (!firstViolation) {
|
|
125
|
+
return `🔴 Gate ${evaluation.stage} BLOCKED.`;
|
|
126
|
+
}
|
|
127
|
+
return `🔴 ${firstViolation.code}: ${firstViolation.message}`;
|
|
128
|
+
};
|
|
129
|
+
|
|
71
130
|
export const runEnterpriseAiGateCheck = (params: {
|
|
72
131
|
repoRoot: string;
|
|
73
132
|
stage: AiGateStage;
|
|
@@ -82,6 +141,11 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
82
141
|
stage: params.stage,
|
|
83
142
|
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
84
143
|
});
|
|
144
|
+
const branch = evaluation.repo_state.git.branch;
|
|
145
|
+
const timestamp = evaluation.evidence.source.generated_at;
|
|
146
|
+
const warnings = buildWarnings(evaluation);
|
|
147
|
+
const autoFixes = buildAutoFixes(evaluation);
|
|
148
|
+
const message = buildMessage(evaluation);
|
|
85
149
|
|
|
86
150
|
return {
|
|
87
151
|
tool: 'ai_gate_check',
|
|
@@ -91,9 +155,14 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
91
155
|
result: {
|
|
92
156
|
allowed: evaluation.allowed,
|
|
93
157
|
status: evaluation.status,
|
|
158
|
+
timestamp,
|
|
159
|
+
branch,
|
|
160
|
+
message,
|
|
94
161
|
stage: evaluation.stage,
|
|
95
162
|
policy: evaluation.policy,
|
|
96
163
|
violations: evaluation.violations,
|
|
164
|
+
warnings,
|
|
165
|
+
auto_fixes: autoFixes,
|
|
97
166
|
evidence: evaluation.evidence,
|
|
98
167
|
mcp_receipt: evaluation.mcp_receipt,
|
|
99
168
|
skills_contract: evaluation.skills_contract,
|