pumuki 6.3.43 → 6.3.44

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/USAGE.md CHANGED
@@ -327,6 +327,8 @@ Watch runtime behavior:
327
327
  - `--notify-cooldown-ms` enables anti-spam throttling for repeated alerts with equal signature.
328
328
  - `--no-notify` keeps watch output without OS notifications.
329
329
  - `--once` or `--iterations=<n>` is recommended for CI/scripts to avoid long-running sessions.
330
+ - `--json` now includes per-tick file traceability: `lastTick.changedFiles[]` and `lastTick.evaluatedFiles[]`.
331
+ - Runtime artifacts hygiene is self-healed in `.git/info/exclude` (`.ai_evidence.json`, `.AI_EVIDENCE.json`, `.pumuki/`) to avoid dirty worktree noise.
330
332
 
331
333
  <a id="backlog-tooling"></a>
332
334
  Backlog tooling behavior (`watch` + `reconcile` scripts):
@@ -1868,9 +1868,243 @@
1868
1868
  - `openspec_compatible=true`
1869
1869
  - `session_active=true`
1870
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.
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
1872
  - Alcance:
1873
1873
  - reproducir `PUM-005` con evidencia local controlada,
1874
1874
  - aplicar fix mínimo en core (`sdd evidence` -> `version=\"1\"` + `slices[]`),
1875
1875
  - validar RED->GREEN con smoke en `Flux_training`,
1876
1876
  - actualizar leyenda en `docs/BUGS_Y_MEJORAS_PUMUKI.md` (Flux) sin tocar código del consumer.
1877
+ - Resultado (2026-03-05):
1878
+ - fix aplicado en core:
1879
+ - `integrations/sdd/evidenceScaffold.ts`: `artifact.version=\"1\"` + `artifact.slices[]`, manteniendo campos legacy (`scenario_id`, `test_run`, `ai_evidence`).
1880
+ - `integrations/sdd/stateSync.ts`: compatibilidad de lectura `version=1|1.0` para rollout sin ruptura.
1881
+ - validación local:
1882
+ - `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`.
1883
+ - `npm run -s typecheck` -> `PASS`.
1884
+ - release asociado: `pumuki@6.3.43` publicado en npm.
1885
+ - revalidación en Flux con `@latest`:
1886
+ - `pumuki sdd evidence ... --json` => `artifact.version=\"1\"` + `slices[]`,
1887
+ - `pnpm exec pumuki watch --once --stage=PRE_COMMIT --scope=staged --json` en `ALLOW` sin `TDD_BDD_EVIDENCE_INVALID`.
1888
+ - leyenda Flux actualizada: `PUM-005` -> `✅ Cerrado`; `PUM-006` -> `🚧 En construccion`.
1889
+
1890
+ - ✅ PUMUKI-151: Ejecutar siguiente pendiente activo de backlog Flux (`PUM-006`) para enriquecer salida JSON de `pumuki watch` con `changedFiles/evaluatedFiles`.
1891
+ - Resultado (2026-03-05):
1892
+ - `integrations/lifecycle/watch.ts`:
1893
+ - contrato extendido de tick con `changedFiles[]` y `evaluatedFiles[]`,
1894
+ - cálculo determinista por `facts` del scope evaluado.
1895
+ - `integrations/lifecycle/__tests__/watch.test.ts`:
1896
+ - cobertura de ambos campos en ticks `BLOCK/ALLOW/WARN`.
1897
+ - `integrations/lifecycle/__tests__/cli.test.ts`:
1898
+ - payload JSON estable de `watch --once --json` validando ambos campos.
1899
+ - `docs/USAGE.md`:
1900
+ - documentación de trazabilidad por tick en salida JSON.
1901
+ - Evidencia:
1902
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `42 pass / 0 fail`.
1903
+ - `npm run -s typecheck` -> `PASS`.
1904
+ - leyenda Flux actualizada: `PUM-006` -> `✅ Cerrado`; `PUM-007` -> `🚧 En construccion`.
1905
+
1906
+ - ✅ PUMUKI-152: Ejecutar siguiente pendiente activo de backlog Flux (`PUM-007`) para evitar artefactos efimeros en raíz del repo tras hooks/watch.
1907
+ - Alcance:
1908
+ - reproducir contaminación de worktree por `.ai_evidence.json` y `.pumuki/**`,
1909
+ - definir cleanup post-ejecución sin perder trazabilidad oficial del repo,
1910
+ - validar con tests + smoke en `Flux_training`,
1911
+ - cerrar leyenda en MD externo Flux al completar fix.
1912
+ - Avance actual (2026-03-05):
1913
+ - `integrations/lifecycle/artifacts.ts`:
1914
+ - nuevo `ensureRuntimeArtifactsIgnored(repoRoot)` con bloque gestionado en `.git/info/exclude`,
1915
+ - soporte para repos normales y `git worktree` (`.git` como `gitdir`).
1916
+ - integración aplicada en:
1917
+ - `integrations/lifecycle/install.ts`
1918
+ - `integrations/lifecycle/watch.ts`
1919
+ - `integrations/git/stageRunners.ts`
1920
+ - cobertura de regresión:
1921
+ - `integrations/lifecycle/__tests__/artifacts.test.ts` (bloque insert/replace/idempotent),
1922
+ - `integrations/git/__tests__/stageRunners.test.ts` (pre-commit asegura ignore runtime),
1923
+ - `integrations/lifecycle/__tests__/install.test.ts` (worktree sin regresión).
1924
+ - evidencia local:
1925
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/install.test.ts integrations/lifecycle/__tests__/artifacts.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `86 pass / 0 fail`.
1926
+ - `npm run -s typecheck` -> `PASS`.
1927
+ - smoke en consumer Flux:
1928
+ - `node /Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/bin/pumuki.js watch --once --stage=PRE_COMMIT --scope=staged --json` ejecutado en `Flux_training`,
1929
+ - `.git/info/exclude` con bloque `pumuki-runtime-artifacts`,
1930
+ - `git status --short` sin ruido de `.ai_evidence.json` / `.pumuki/**`.
1931
+ - leyenda Flux actualizada: `PUM-007` -> `✅ Cerrado`; `PUM-008` -> `🚧 En construccion`.
1932
+
1933
+ - ✅ PUMUKI-153: Ejecutar siguiente pendiente activo de backlog Flux (`PUM-008`) para hacer accionable el bloqueo `skills.frontend.no-solid-violations` en cambios incrementales.
1934
+ - Resultado (2026-03-05):
1935
+ - `integrations/config/skillsRuleSet.ts`:
1936
+ - mensajes enriquecidos para reglas `*.no-solid-violations` con `ast_nodes`, `observed_paths` y `sample_paths`.
1937
+ - `integrations/git/runPlatformGateOutput.ts`:
1938
+ - `next_action` específico y progresivo para `SKILLS_SKILLS_FRONTEND_NO_SOLID_VIOLATIONS` (extracción incremental por componente/hook).
1939
+ - cobertura:
1940
+ - `integrations/config/__tests__/skillsRuleSet.test.ts`,
1941
+ - `integrations/git/__tests__/runPlatformGateOutput.test.ts`.
1942
+ - Evidencia:
1943
+ - `npx --yes tsx@4.21.0 --test integrations/config/__tests__/skillsRuleSet.test.ts integrations/git/__tests__/runPlatformGateOutput.test.ts` -> `17 pass / 0 fail`.
1944
+ - `npm run -s typecheck` -> `PASS`.
1945
+ - leyenda Flux actualizada: `PUM-008` -> `✅ Cerrado`.
1946
+
1947
+ - ✅ PUMUKI-154: Ejecutar siguiente pendiente crítico del backlog SAAS (`PUMUKI-016`) para convertir bloqueos de `PRE_WRITE` por regla iOS crítica ausente en remediación guiada y determinista de bootstrap/reconcile.
1948
+ - Resultado (2026-03-05):
1949
+ - `integrations/mcp/preFlightCheck.ts`:
1950
+ - hints accionables para `EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING` y `EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE`.
1951
+ - `integrations/mcp/autoExecuteAiStart.ts`:
1952
+ - `next_action` determinista con comando `policy reconcile --strict` + revalidación `PRE_WRITE`.
1953
+ - clasificación de confianza alineada para ambos códigos críticos.
1954
+ - `integrations/lifecycle/cli.ts`:
1955
+ - `PRE_WRITE_HINTS_BY_CODE` enriquecido para esos códigos críticos.
1956
+ - `resolvePreWriteNextAction` ahora prioriza ruta de reconcile estricto en bloqueos de reglas críticas.
1957
+ - cobertura añadida:
1958
+ - `integrations/mcp/__tests__/preFlightCheck.test.ts`
1959
+ - `integrations/mcp/__tests__/autoExecuteAiStart.test.ts`
1960
+ - `integrations/lifecycle/__tests__/cli.test.ts`
1961
+ - Evidencia:
1962
+ - `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `50 pass / 0 fail`.
1963
+ - `npm run -s typecheck` -> `PASS`.
1964
+ - leyenda SAAS actualizada:
1965
+ - `PUMUKI-016` -> `✅ Cerrado`
1966
+ - `PUMUKI-012` -> `🚧 En construcción` (siguiente foco).
1967
+
1968
+ - ✅ PUMUKI-155: Ejecutar siguiente pendiente HIGH del backlog SAAS (`PUMUKI-012`) para cerrar la recurrencia de `active_rule_ids` vacío con cambios de código en `PRE_COMMIT`.
1969
+ - Alcance:
1970
+ - reproducir recurrencia en contexto real (diffs pequeños y grandes),
1971
+ - endurecer contrato de `rules_coverage` en `PRE_COMMIT` para evitar falso verde con cobertura vacía,
1972
+ - validar con tests focales + typecheck,
1973
+ - actualizar leyenda SAAS manteniendo una única `🚧`.
1974
+ - Avance técnico (2026-03-05):
1975
+ - `integrations/git/stageRunners.ts`:
1976
+ - en bloqueos de `PRE_COMMIT/PRE_PUSH/CI` prioriza `snapshot.findings` del stage actual como causa primaria (en lugar de depender solo de `ai_gate.violations`),
1977
+ - remediación específica añadida para `ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH`.
1978
+ - `integrations/git/runPlatformGateOutput.ts`:
1979
+ - `next_action` explícito para `ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH` con flujo `policy reconcile --strict` + `pumuki-pre-commit`.
1980
+ - cobertura añadida:
1981
+ - `integrations/git/__tests__/stageRunners.test.ts`
1982
+ - `integrations/git/__tests__/runPlatformGateOutput.test.ts`
1983
+ - evidencia local:
1984
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGateOutput.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `82 pass / 0 fail`.
1985
+ - `npm run -s typecheck` -> `PASS`.
1986
+ - Avance técnico adicional (2026-03-05, cierre de falso bloqueo cross-platform en PRE_WRITE):
1987
+ - `integrations/gate/evaluateAiGate.ts`:
1988
+ - detección efectiva de plataformas en `PRE_WRITE` basada en `rules_coverage` cuando hay plataformas detectadas, para evitar arrastre de scope legado (ej.: iOS) en diffs backend.
1989
+ - mantiene enforcement hard sin relajar contrato: si no hay señal efectiva de coverage por prefijo, conserva fallback al scope detectado original.
1990
+ - `integrations/gate/__tests__/evaluateAiGate.test.ts`:
1991
+ - nuevo caso de regresión: `platforms` arrastra `ios`, pero la cobertura efectiva real es `backend` y el gate debe quedar en `ALLOWED`.
1992
+ - evidencia local:
1993
+ - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/evaluateAiGate.test.ts` -> `26 pass / 0 fail`.
1994
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGateOutput.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `82 pass / 0 fail`.
1995
+ - `npm run -s typecheck` -> `PASS`.
1996
+ - Avance técnico adicional (2026-03-05, hardening de cobertura vacía en PRE_WRITE):
1997
+ - `integrations/gate/evaluateAiGate.ts`:
1998
+ - `EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES` ahora también bloquea cuando `active_rule_ids=[]` y la cobertura evaluada infiere plataforma (`skills.<scope>.*`) aunque `platforms` venga vacío.
1999
+ - mensaje distingue modo de detección (`detected`/`inferred`) para trazabilidad de root-cause.
2000
+ - `integrations/gate/__tests__/evaluateAiGate.test.ts`:
2001
+ - nuevo caso de regresión: `platforms={}` + `evaluated_rule_ids=['skills.backend.no-empty-catch']` + `active_rule_ids=[]` => `BLOCKED`.
2002
+ - evidencia local:
2003
+ - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/evaluateAiGate.test.ts` -> `27 pass / 0 fail`.
2004
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGate.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `73 pass / 0 fail`.
2005
+ - `npm run -s typecheck` -> `PASS`.
2006
+ - Cierre operativo final (2026-03-05):
2007
+ - `integrations/mcp/autoExecuteAiStart.ts`, `integrations/mcp/preFlightCheck.ts` y `integrations/lifecycle/cli.ts` alineados con remediación accionable para `EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES`.
2008
+ - pruebas de regresión completas:
2009
+ - `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/lifecycle/__tests__/cli.test.ts integrations/gate/__tests__/evaluateAiGate.test.ts` -> `80 pass / 0 fail`.
2010
+ - `npm run -s typecheck` -> `PASS`.
2011
+ - leyenda SAAS actualizada:
2012
+ - `PUMUKI-012` -> `✅ Cerrado`
2013
+ - `PUMUKI-M008` -> `🚧 En construcción` (siguiente foco único).
2014
+
2015
+ - ✅ PUMUKI-156: Ejecutar siguiente pendiente HIGH del backlog SAAS (`PUMUKI-017`) para reducir falsos bloqueos de `skills.backend.no-solid-violations` en `PRE_COMMIT` y mantener enforcement fuerte en `PRE_PUSH`.
2016
+ - Resultado (2026-03-05):
2017
+ - `integrations/config/skillsMarkdownRules.ts`:
2018
+ - reglas conocidas `*.no-solid-violations` ahora usan stage canónico `PRE_PUSH` cuando el markdown no define stage.
2019
+ - si el markdown define stage explícito, se respeta sin override.
2020
+ - `integrations/config/__tests__/skillsMarkdownRules.test.ts`:
2021
+ - cobertura nueva para stage canónico y respeto de stage explícito.
2022
+ - Evidencia:
2023
+ - `npx --yes tsx@4.21.0 --test integrations/config/__tests__/skillsMarkdownRules.test.ts integrations/config/__tests__/skillsRuleSet.test.ts` -> `20 pass / 0 fail`.
2024
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/runPlatformGate.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `101 pass / 0 fail`.
2025
+ - `npm run -s typecheck` -> `PASS`.
2026
+
2027
+ - ✅ PUMUKI-157: Ejecutar siguiente pendiente HIGH del backlog SAAS (`PUMUKI-M008`) para entregar asistente de partición atómica accionable en `PRE_WRITE` antes del bloqueo.
2028
+ - Alcance:
2029
+ - generar `suggested-atomic-slices` por contexto (scope/ruta/tipo de archivo),
2030
+ - exponer `next_action` con comandos concretos de staging por lote,
2031
+ - mantener comportamiento anti-spam y sin auto-aplicar cambios.
2032
+ - Resultado (2026-03-05):
2033
+ - helper nuevo `integrations/git/worktreeAtomicSlices.ts` para agrupar cambios por scope y proponer `git add -- ...` por lote.
2034
+ - `pre_flight_check` ahora expone `ATOMIC_SLICES` y `ATOMIC_SLICES[next]` en bloqueos de higiene de worktree.
2035
+ - `auto_execute_ai_start` ahora devuelve `next_action` con primer lote atómico accionable + comando de revalidación `PRE_WRITE`.
2036
+ - `pumuki sdd validate --stage=PRE_WRITE` ahora expone `next_action` con slice atómico cuando el umbral se supera.
2037
+ - Evidencia:
2038
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/worktreeAtomicSlices.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `56 pass / 0 fail`.
2039
+ - `npm run -s typecheck` -> `PASS`.
2040
+ - leyenda SAAS actualizada:
2041
+ - `PUMUKI-M008` -> `✅ Cerrado`
2042
+ - `PUMUKI-M005` -> `🚧 En construcción` (siguiente foco único).
2043
+
2044
+ - ✅ PUMUKI-158: Ejecutar siguiente pendiente MEDIUM del backlog SAAS (`PUMUKI-M005`) para cerrar convergencia determinista de `policy reconcile --strict` (diagnóstico + autofix + verificación).
2045
+ - Alcance:
2046
+ - definir salida `next_action` estable de autofix cuando detecte drift crítico,
2047
+ - añadir modo `--apply` seguro e idempotente para convergencia guiada,
2048
+ - dejar evidencia machine-readable de antes/después (`drift -> reconciled`).
2049
+ - Resultado (2026-03-05):
2050
+ - `integrations/lifecycle/policyReconcile.ts` ahora soporta `apply=true` y produce bloque `autofix` (`attempted/status/actions/details`).
2051
+ - `policy reconcile --strict --apply` escribe contrato determinista `.pumuki/policy-as-code.json` (`WRITE_POLICY_AS_CODE_CONTRACT`) y reevalúa convergencia.
2052
+ - `integrations/lifecycle/cli.ts` añade flag `--apply` + salida textual de autofix.
2053
+ - cobertura añadida:
2054
+ - `integrations/lifecycle/__tests__/policyReconcile.test.ts` (convergencia `--strict --apply`),
2055
+ - `integrations/lifecycle/__tests__/cli.test.ts` (parse + ejecución `--strict --apply --json`).
2056
+ - Evidencia:
2057
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/policyReconcile.test.ts integrations/lifecycle/__tests__/cli.test.ts integrations/git/__tests__/worktreeAtomicSlices.test.ts integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts` -> `63 pass / 0 fail`.
2058
+ - `npm run -s typecheck` -> `PASS`.
2059
+ - leyenda SAAS actualizada:
2060
+ - `PUMUKI-M005` -> `✅ Cerrado`
2061
+ - `PUMUKI-006` -> `🚧 En construcción` (siguiente foco único).
2062
+
2063
+ - ✅ PUMUKI-159: Ejecutar siguiente pendiente LOW del backlog SAAS (`PUMUKI-006`) para alinear `package_version` y `lifecycle_version` en `ai_gate_check`/MCP.
2064
+ - Resultado (2026-03-05):
2065
+ - `integrations/gate/evaluateAiGate.ts` incorpora normalización defensiva para evitar payload desalineado cuando `captureRepoState` mezcla fuentes de versión.
2066
+ - se alinea `repo_state.lifecycle.package_version` con `lifecycle_version` en salida de gate/MCP, manteniendo trazabilidad sin drift en contratos JSON.
2067
+ - regresión cubierta en `integrations/gate/__tests__/evaluateAiGate.test.ts` con fixture desalineada.
2068
+ - Evidencia:
2069
+ - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/evaluateAiGate.test.ts` -> `PASS`.
2070
+ - `npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/preFlightCheck.test.ts integrations/mcp/__tests__/autoExecuteAiStart.test.ts integrations/mcp/__tests__/aiGateCheck.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `PASS`.
2071
+ - `npm run -s typecheck` -> `PASS`.
2072
+ - leyenda SAAS actualizada:
2073
+ - `PUMUKI-006` -> `✅ Cerrado`
2074
+ - backlog SAAS externo queda en `✅ 100% cerrado`.
2075
+
2076
+ - ✅ PUMUKI-160: Iniciar siguiente ciclo activo sobre backlog RuralGo/consumidores (solo hallazgos netos nuevos) sin reabrir incidencias ya cerradas.
2077
+ - Resultado (2026-03-05):
2078
+ - validado estado canónico externo:
2079
+ - SAAS: `✅ Cerrados: 25`, `0` en `🚧/⏳/⛔`.
2080
+ - RuralGo: `✅ Cerrados: 98`, `0` en `🚧/⏳/⛔`.
2081
+ - Flux: backlog localizado en `docs/BUGS_Y_MEJORAS_PUMUKI.md` (ruta real), con foco activo en `PUM-010`.
2082
+ - cerrado `PUM-009` en Flux tras verificación directa contra paquete publicado (`pumuki@latest`) con salida válida de `sdd evidence` (`version: "1"` + `slices[]`).
2083
+ - Evidencia:
2084
+ - `npx --yes --package pumuki@latest pumuki sdd evidence --scenario-id=docs/validation/features/p3_t1_web_shell_dashboard --test-command='pnpm test' --test-status=passed --json` -> artefacto válido.
2085
+ - actualización de leyenda en `Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md`:
2086
+ - `PUM-009` -> `✅ Cerrado`
2087
+ - `PUM-010` -> `🚧 En construccion`
2088
+ - `PUM-011` -> `⏳ Pendiente`.
2089
+
2090
+ - ✅ PUMUKI-161: Ejecutar siguiente pendiente activo de Flux (`PUM-010`) para estabilizar persistencia de bundles/rules de `skills.frontend.*` entre iteraciones sin bootstrap manual repetitivo.
2091
+ - Resultado (2026-03-05):
2092
+ - `integrations/lifecycle/watch.ts` ahora detecta drift de skills coverage en runtime (`SKILLS_PLATFORM_COVERAGE_INCOMPLETE_HIGH`, `SKILLS_SCOPE_COMPLIANCE_INCOMPLETE_HIGH` y equivalentes `EVIDENCE_*`) y ejecuta auto-reconcile determinista (`policy reconcile --strict --apply`) dentro del mismo tick.
2093
+ - si el autofix aplica contrato (`WRITE_POLICY_AS_CODE_CONTRACT`), `watch` reevalúa el gate en la misma iteración para evitar bootstrap manual repetido.
2094
+ - cobertura añadida: test dedicado en `integrations/lifecycle/__tests__/watch.test.ts` (`auto-reconcilia policy en drift de skills y reevalua en la misma iteracion`).
2095
+ - leyenda Flux actualizada:
2096
+ - `PUM-010` -> `✅ Cerrado`
2097
+ - `PUM-011` -> `🚧 En construccion`.
2098
+ - Evidencia:
2099
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts integrations/lifecycle/__tests__/policyReconcile.test.ts` -> `53 pass / 0 fail`.
2100
+ - `npm run -s typecheck` -> `PASS`.
2101
+
2102
+ - 🚧 PUMUKI-162: Ejecutar siguiente pendiente activo de Flux (`PUM-011`) para cerrar paridad consumer de `watch --once --json` con `lastTick.changedFiles[]` y `lastTick.evaluatedFiles[]`.
2103
+ - Alcance:
2104
+ - verificar contrato JSON final en paquete publicado vs consumer runtime,
2105
+ - eliminar drift de payload entre core y distribución npm,
2106
+ - cerrar con validación reproducible en consumer (sin tocar código funcional del consumer).
2107
+ - Avance actual (2026-03-05):
2108
+ - verificado en core local (`bin/pumuki.js`): `lastTick.changedFiles[]` y `lastTick.evaluatedFiles[]` sí aparecen.
2109
+ - verificado en paquete publicado (`pumuki@latest`): esos campos aún no aparecen en JSON de `watch`.
2110
+ - conclusión: bug activo de rollout/distribución (no de lógica local), pendiente de corte/release para cerrar `PUM-011`.
@@ -87,6 +87,25 @@ const inferRuleStage = (raw: string): SkillsStage | undefined => {
87
87
  return undefined;
88
88
  };
89
89
 
90
+ const KNOWN_RULE_DEFAULT_STAGE: Readonly<Record<string, SkillsStage>> = {
91
+ 'skills.backend.no-solid-violations': 'PRE_PUSH',
92
+ 'skills.frontend.no-solid-violations': 'PRE_PUSH',
93
+ 'skills.backend.enforce-clean-architecture': 'PRE_PUSH',
94
+ 'skills.frontend.enforce-clean-architecture': 'PRE_PUSH',
95
+ 'skills.backend.no-god-classes': 'PRE_PUSH',
96
+ 'skills.frontend.no-god-classes': 'PRE_PUSH',
97
+ };
98
+
99
+ const resolveDefaultStageForKnownRule = (
100
+ ruleId: string,
101
+ inferredStage: SkillsStage | undefined
102
+ ): SkillsStage | undefined => {
103
+ if (inferredStage) {
104
+ return inferredStage;
105
+ }
106
+ return KNOWN_RULE_DEFAULT_STAGE[ruleId];
107
+ };
108
+
90
109
  const isRuleCandidateLine = (line: string): boolean => {
91
110
  if (CHECK_RULE_PREFIX.test(line)) {
92
111
  return true;
@@ -339,6 +358,11 @@ export const extractCompiledRulesFromSkillMarkdown = (params: {
339
358
  const evaluationMode: SkillsRuleEvaluationMode =
340
359
  knownRuleId || astNodeIds.length > 0 ? 'AUTO' : 'DECLARATIVE';
341
360
 
361
+ const inferredStage = inferRuleStage(rawLine);
362
+ const resolvedStage = knownRuleId
363
+ ? resolveDefaultStageForKnownRule(knownRuleId, inferredStage)
364
+ : inferredStage;
365
+
342
366
  rules.push({
343
367
  id: nextId,
344
368
  description,
@@ -346,7 +370,7 @@ export const extractCompiledRulesFromSkillMarkdown = (params: {
346
370
  platform,
347
371
  sourceSkill: params.sourceSkill,
348
372
  sourcePath: params.sourcePath,
349
- stage: inferRuleStage(rawLine),
373
+ stage: resolvedStage,
350
374
  confidence: inferRuleConfidence(rawLine),
351
375
  locked: true,
352
376
  evaluationMode,
@@ -256,6 +256,34 @@ const toSkillsRuntimeIrSource = (params: {
256
256
  );
257
257
  };
258
258
 
259
+ const buildRuleFindingMessage = (params: {
260
+ rule: SkillsCompiledRule;
261
+ mappedHeuristicRuleIds: ReadonlyArray<string>;
262
+ observedFilePaths?: ReadonlyArray<string>;
263
+ }): string => {
264
+ if (!params.rule.id.endsWith('.no-solid-violations')) {
265
+ return params.rule.description;
266
+ }
267
+
268
+ const relevantObservedPaths = (params.observedFilePaths ?? [])
269
+ .map((path) => normalizeObservedPath(path))
270
+ .filter((path) =>
271
+ isObservedPathForPlatform({
272
+ platform: params.rule.platform,
273
+ path,
274
+ })
275
+ );
276
+ const samplePaths = [...new Set(relevantObservedPaths)].slice(0, 3);
277
+ const astNodes = [...params.mappedHeuristicRuleIds].sort();
278
+ const astNodesToken = astNodes.length > 0 ? astNodes.join(',') : 'none';
279
+ const sampleToken = samplePaths.length > 0 ? ` sample_paths=[${samplePaths.join(', ')}].` : '';
280
+
281
+ return (
282
+ `${params.rule.description} ` +
283
+ `Criteria: ast_nodes=[${astNodesToken}], observed_paths=${relevantObservedPaths.length}.${sampleToken}`
284
+ );
285
+ };
286
+
259
287
  const stageApplies = (
260
288
  currentStage: Exclude<GateStage, 'STAGED'>,
261
289
  ruleStage?: Exclude<GateStage, 'STAGED'>
@@ -365,6 +393,11 @@ const toRuleDefinition = (params: {
365
393
  rule: params.rule,
366
394
  mappedHeuristicRuleIds,
367
395
  });
396
+ const findingMessage = buildRuleFindingMessage({
397
+ rule: params.rule,
398
+ mappedHeuristicRuleIds,
399
+ observedFilePaths: params.observedFilePaths,
400
+ });
368
401
 
369
402
  if (evaluationMode === 'AUTO') {
370
403
  if (mappedHeuristicRuleIds.length === 0) {
@@ -395,7 +428,7 @@ const toRuleDefinition = (params: {
395
428
  when,
396
429
  then: {
397
430
  kind: 'Finding',
398
- message: params.rule.description,
431
+ message: findingMessage,
399
432
  code: toCode(params.rule.id),
400
433
  source: runtimeIrSource,
401
434
  },
@@ -168,6 +168,23 @@ const toWarnViolation = (code: string, message: string): AiGateViolation => ({
168
168
  message,
169
169
  });
170
170
 
171
+ const normalizeRepoStateLifecycleVersions = (repoState: RepoState): RepoState => {
172
+ const packageVersion = repoState.lifecycle.package_version;
173
+ const lifecycleVersion = repoState.lifecycle.lifecycle_version;
174
+ if (packageVersion === lifecycleVersion) {
175
+ return repoState;
176
+ }
177
+ const canonicalVersion = packageVersion ?? lifecycleVersion ?? null;
178
+ return {
179
+ ...repoState,
180
+ lifecycle: {
181
+ ...repoState.lifecycle,
182
+ package_version: canonicalVersion,
183
+ lifecycle_version: canonicalVersion,
184
+ },
185
+ };
186
+ };
187
+
171
188
  const toPositiveInteger = (value: unknown, fallback: number): number => {
172
189
  if (typeof value !== 'number' || !Number.isFinite(value)) {
173
190
  return fallback;
@@ -272,6 +289,45 @@ const toDetectedSkillsPlatforms = (
272
289
  return detected;
273
290
  };
274
291
 
292
+ const toCoverageInferredPlatforms = (
293
+ coverage: NonNullable<Extract<EvidenceReadResult, { kind: 'valid' }>['evidence']['snapshot']['rules_coverage']> | undefined
294
+ ): ReadonlyArray<PreWriteSkillsPlatform> => {
295
+ if (!coverage) {
296
+ return [];
297
+ }
298
+ const ruleIds = [...coverage.active_rule_ids, ...coverage.evaluated_rule_ids];
299
+ const inferred = new Set<PreWriteSkillsPlatform>();
300
+ for (const ruleId of ruleIds) {
301
+ for (const platform of PREWRITE_SKILLS_PLATFORMS) {
302
+ const prefix = PLATFORM_SKILLS_RULE_PREFIXES[platform];
303
+ if (ruleId.startsWith(prefix)) {
304
+ inferred.add(platform);
305
+ }
306
+ }
307
+ }
308
+ return PREWRITE_SKILLS_PLATFORMS.filter((platform) => inferred.has(platform));
309
+ };
310
+
311
+ const toEffectiveSkillsPlatforms = (params: {
312
+ platforms: Extract<EvidenceReadResult, { kind: 'valid' }>['evidence']['platforms'] | undefined;
313
+ coverage: NonNullable<Extract<EvidenceReadResult, { kind: 'valid' }>['evidence']['snapshot']['rules_coverage']> | undefined;
314
+ }): ReadonlyArray<PreWriteSkillsPlatform> => {
315
+ const detectedPlatforms = toDetectedSkillsPlatforms(params.platforms);
316
+ if (detectedPlatforms.length === 0) {
317
+ return [];
318
+ }
319
+ const inferredFromCoverage = toCoverageInferredPlatforms(params.coverage);
320
+ if (inferredFromCoverage.length === 0) {
321
+ return detectedPlatforms;
322
+ }
323
+ const inferredSet = new Set(inferredFromCoverage);
324
+ const intersection = detectedPlatforms.filter((platform) => inferredSet.has(platform));
325
+ if (intersection.length > 0) {
326
+ return intersection;
327
+ }
328
+ return detectedPlatforms;
329
+ };
330
+
275
331
  const collectActiveRuleIdsCoverageViolations = (params: {
276
332
  stage: AiGateStage;
277
333
  evidence: Extract<EvidenceReadResult, { kind: 'valid' }>['evidence'];
@@ -280,14 +336,23 @@ const collectActiveRuleIdsCoverageViolations = (params: {
280
336
  if (params.coverage.active_rule_ids.length > 0) {
281
337
  return [];
282
338
  }
283
- const detectedPlatforms = toDetectedSkillsPlatforms(params.evidence.platforms);
284
- if (detectedPlatforms.length === 0) {
339
+ const effectivePlatforms = toEffectiveSkillsPlatforms({
340
+ platforms: params.evidence.platforms,
341
+ coverage: params.coverage,
342
+ });
343
+ const inferredPlatforms =
344
+ effectivePlatforms.length > 0 ? [] : toCoverageInferredPlatforms(params.coverage);
345
+ const blockedPlatforms = effectivePlatforms.length > 0
346
+ ? effectivePlatforms
347
+ : inferredPlatforms;
348
+ if (blockedPlatforms.length === 0) {
285
349
  return [];
286
350
  }
351
+ const detectionMode = effectivePlatforms.length > 0 ? 'detected' : 'inferred';
287
352
  return [
288
353
  toErrorViolation(
289
354
  'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES',
290
- `Active rules coverage is empty at ${params.stage} with detected code platforms=[${detectedPlatforms.join(', ')}].`
355
+ `Active rules coverage is empty at ${params.stage} with ${detectionMode} code platforms=[${blockedPlatforms.join(', ')}].`
291
356
  ),
292
357
  ];
293
358
  };
@@ -296,7 +361,10 @@ const collectPreWritePlatformSkillsViolations = (params: {
296
361
  evidence: Extract<EvidenceReadResult, { kind: 'valid' }>['evidence'];
297
362
  coverage: NonNullable<Extract<EvidenceReadResult, { kind: 'valid' }>['evidence']['snapshot']['rules_coverage']>;
298
363
  }): AiGateViolation[] => {
299
- const detectedPlatforms = toDetectedSkillsPlatforms(params.evidence.platforms);
364
+ const detectedPlatforms = toEffectiveSkillsPlatforms({
365
+ platforms: params.evidence.platforms,
366
+ coverage: params.coverage,
367
+ });
300
368
  if (detectedPlatforms.length === 0) {
301
369
  return [];
302
370
  }
@@ -393,7 +461,10 @@ const collectPreWriteCrossPlatformCriticalViolations = (params: {
393
461
  evidence: Extract<EvidenceReadResult, { kind: 'valid' }>['evidence'];
394
462
  coverage: NonNullable<Extract<EvidenceReadResult, { kind: 'valid' }>['evidence']['snapshot']['rules_coverage']>;
395
463
  }): AiGateViolation[] => {
396
- const detectedPlatforms = toDetectedSkillsPlatforms(params.evidence.platforms);
464
+ const detectedPlatforms = toEffectiveSkillsPlatforms({
465
+ platforms: params.evidence.platforms,
466
+ coverage: params.coverage,
467
+ });
397
468
  if (detectedPlatforms.length === 0) {
398
469
  return [];
399
470
  }
@@ -447,7 +518,10 @@ const toSkillsContractAssessment = (params: {
447
518
  }
448
519
 
449
520
  const coverage = params.evidenceResult.evidence.snapshot.rules_coverage;
450
- const detectedPlatforms = toDetectedSkillsPlatforms(params.evidenceResult.evidence.platforms);
521
+ const detectedPlatforms = toEffectiveSkillsPlatforms({
522
+ platforms: params.evidenceResult.evidence.platforms,
523
+ coverage,
524
+ });
451
525
  if (detectedPlatforms.length === 0) {
452
526
  return {
453
527
  stage: params.stage,
@@ -977,7 +1051,9 @@ export const evaluateAiGate = (
977
1051
  const protectedBranches = new Set(params.protectedBranches ?? Array.from(DEFAULT_PROTECTED_BRANCHES));
978
1052
  const nowMs = activeDependencies.now();
979
1053
  const evidenceResult = activeDependencies.readEvidenceResult(params.repoRoot);
980
- const repoState = activeDependencies.captureRepoState(params.repoRoot);
1054
+ const repoState = normalizeRepoStateLifecycleVersions(
1055
+ activeDependencies.captureRepoState(params.repoRoot)
1056
+ );
981
1057
  const policyStage = toPolicyStage(params.stage);
982
1058
  const resolvedPolicy = activeDependencies.resolvePolicyForStage(
983
1059
  policyStage,
@@ -17,6 +17,10 @@ const BLOCK_NEXT_ACTION_BY_CODE: Readonly<Record<string, string>> = {
17
17
  'git restore --staged . && separa cambios en commits más pequeños',
18
18
  GIT_ATOMICITY_TOO_MANY_SCOPES:
19
19
  'git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"',
20
+ ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
21
+ 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
22
+ SKILLS_SKILLS_FRONTEND_NO_SOLID_VIOLATIONS:
23
+ 'Aplica refactor incremental: extrae 1 componente/hook por commit y vuelve a ejecutar PRE_COMMIT.',
20
24
  };
21
25
 
22
26
  const severityWeight = (severity: string): number => {
@@ -21,6 +21,7 @@ import {
21
21
  import { readFileSync } from 'node:fs';
22
22
  import { readEvidence, readEvidenceResult } from '../evidence/readEvidence';
23
23
  import type { EvidenceReadResult } from '../evidence/readEvidence';
24
+ import { ensureRuntimeArtifactsIgnored } from '../lifecycle/artifacts';
24
25
 
25
26
  const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
26
27
  'pumuki pre-push blocked: branch has no upstream tracking reference. Configure upstream first (for example: git push --set-upstream origin <branch>) and retry.';
@@ -43,6 +44,10 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
43
44
  EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual y reintenta.',
44
45
  EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
45
46
  EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
47
+ ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
48
+ 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
49
+ EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
50
+ 'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
46
51
  GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
47
52
  PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>',
48
53
  PRE_PUSH_UPSTREAM_MISALIGNED:
@@ -83,6 +88,7 @@ type StageRunnerDependencies = {
83
88
  now: () => number;
84
89
  writeHookGateSummary: (message: string) => void;
85
90
  isQuietMode: () => boolean;
91
+ ensureRuntimeArtifactsIgnored: (repoRoot: string) => void;
86
92
  };
87
93
 
88
94
  const defaultDependencies: StageRunnerDependencies = {
@@ -132,6 +138,12 @@ const defaultDependencies: StageRunnerDependencies = {
132
138
  process.stdout.write(`${message}\n`);
133
139
  },
134
140
  isQuietMode: () => process.argv.includes('--quiet'),
141
+ ensureRuntimeArtifactsIgnored: (repoRoot) => {
142
+ try {
143
+ ensureRuntimeArtifactsIgnored(repoRoot);
144
+ } catch {
145
+ }
146
+ },
135
147
  };
136
148
 
137
149
  const getDependencies = (
@@ -160,9 +172,14 @@ const notifyGateBlockedForStage = (params: {
160
172
  }): void => {
161
173
  const repoRoot = params.dependencies.resolveRepoRoot();
162
174
  const evidence = params.dependencies.readEvidence(repoRoot);
175
+ const stageFindings =
176
+ evidence?.snapshot.stage === params.stage
177
+ ? evidence.snapshot.findings
178
+ : [];
179
+ const firstStageFinding = stageFindings[0];
163
180
  const firstViolation = evidence?.ai_gate.violations[0];
164
- const causeCode = firstViolation?.code ?? params.fallbackCauseCode;
165
- const causeMessage = firstViolation?.message ?? params.fallbackCauseMessage;
181
+ const causeCode = firstStageFinding?.code ?? firstViolation?.code ?? params.fallbackCauseCode;
182
+ const causeMessage = firstStageFinding?.message ?? firstViolation?.message ?? params.fallbackCauseMessage;
166
183
  const remediation =
167
184
  BLOCKED_REMEDIATION_BY_CODE[causeCode]
168
185
  ?? params.fallbackRemediation
@@ -170,7 +187,9 @@ const notifyGateBlockedForStage = (params: {
170
187
  params.dependencies.notifyGateBlocked({
171
188
  repoRoot,
172
189
  stage: params.stage,
173
- totalViolations: evidence?.ai_gate.violations.length ?? 0,
190
+ totalViolations: stageFindings.length > 0
191
+ ? stageFindings.length
192
+ : evidence?.ai_gate.violations.length ?? 0,
174
193
  causeCode,
175
194
  causeMessage,
176
195
  remediation,
@@ -338,6 +357,7 @@ export async function runPreCommitStage(
338
357
  ): Promise<number> {
339
358
  const activeDependencies = getDependencies(dependencies);
340
359
  const repoRoot = activeDependencies.resolveRepoRoot();
360
+ activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
341
361
  if (
342
362
  enforceGitAtomicityGate({
343
363
  dependencies: activeDependencies,
@@ -379,6 +399,7 @@ export async function runPrePushStage(
379
399
  ): Promise<number> {
380
400
  const activeDependencies = getDependencies(dependencies);
381
401
  const repoRoot = activeDependencies.resolveRepoRoot();
402
+ activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
382
403
  const upstreamRef = activeDependencies.resolveUpstreamRef();
383
404
  if (!upstreamRef) {
384
405
  const prePushInput = activeDependencies.readPrePushStdin();
@@ -519,6 +540,7 @@ export async function runCiStage(
519
540
  ): Promise<number> {
520
541
  const activeDependencies = getDependencies(dependencies);
521
542
  const repoRoot = activeDependencies.resolveRepoRoot();
543
+ activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
522
544
  const ciBaseRef = activeDependencies.resolveCiBaseRef();
523
545
  if (
524
546
  enforceGitAtomicityGate({