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.
@@ -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`
@@ -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-131 (issue `#716`, `next_commands[].probe_timeout_ms`).
11
- - Task activa (`🚧`): PUMUKI-132 (issue `#717`, `next_commands[].probe_kind`).
12
- - Nuevos pendientes añadidos (`⏳`): ninguno en este bloque inmediato.
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
- - 🚧 PUMUKI-139: Monitorizar feedback post-release en consumidores reales (SAAS y RuralGo) y registrar únicamente hallazgos netos nuevos para siguiente corte.
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: `Agrupa cambios por ámbito funcional (máximo ${config.maxScopes} scopes por commit).`,
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
- ...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
1012
- ...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
1013
- ...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
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
- || platformSkillsCoverageFinding
1023
- || crossPlatformCriticalFinding
1024
- || skillsScopeComplianceFinding
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
- ...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
1037
- ...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
1038
- ...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
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
- platformSkillsCoverageFinding ||
1055
- crossPlatformCriticalFinding ||
1056
- skillsScopeComplianceFinding ||
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,