pumuki 6.3.45 → 6.3.46

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.
@@ -5,6 +5,19 @@ 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.46)
9
+
10
+ - Paridad hook/watch en auto-remediación de skills coverage:
11
+ - `pre-commit` y `pre-push` ahora ejecutan `policy reconcile --strict --apply` y reintentan una única vez cuando el bloqueo es de coverage de skills.
12
+ - elimina recurrencia de bootstrap/reconcile manual entre iteraciones de consumer.
13
+ - Contrato `watch --json` enriquecido para drift de versión:
14
+ - nuevo bloque `version` con `effective/runtime/consumerInstalled/source`,
15
+ - incluye `driftFromRuntime` y `driftWarning` cuando el binario del consumer no está alineado con runtime/latest.
16
+ - Evidencia de validación:
17
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` (`30 pass / 0 fail`)
18
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts` (`48 pass / 0 fail`)
19
+ - `npm run -s typecheck` (`PASS`)
20
+
8
21
  ### 2026-03-05 (v6.3.45)
9
22
 
10
23
  - SDD sync canónico ampliado por defecto en consumer:
package/docs/USAGE.md CHANGED
@@ -334,6 +334,8 @@ Watch runtime behavior:
334
334
  Backlog tooling behavior (`watch` + `reconcile` scripts):
335
335
  - mapping precedence is deterministic: inline `#issue` -> `--id-issue-map-from` -> `--id-issue-map` -> `--resolve-missing-via-gh`.
336
336
  - `watch-consumer-backlog` is non-destructive and reports action-required drift.
337
+ - `watch-consumer-backlog-fleet` agrega varios backlogs en una sola ejecución y devuelve resumen consolidado por target.
338
+ - `watch-consumer-backlog-fleet-tick` ejecuta un ciclo canónico SAAS+RuralGo+Flux con overrides opcionales (`--saas/--ruralgo/--flux`).
337
339
  - `watch-consumer-backlog` reports heading drift (`heading_drift`) when section headers `### ✅/🚧/⏳/⛔ <ID>` diverge from effective table status.
338
340
  - `watch-consumer-backlog --json` exposes `heading_drift_count` for low-friction alerting parsers.
339
341
  - `watch-consumer-backlog --json` exposes `classification_counts` (`needs_issue`, `drift_closed_issue`, `active_issue`, `heading_drift`).
@@ -405,6 +407,9 @@ Backlog tooling quick reference:
405
407
  |---|---|
406
408
  | `npm run -s test:backlog-tooling` | Ejecutar suite focal de regresión del tooling de backlog. |
407
409
  | `scripts/watch-consumer-backlog.ts --json` | Detectar drift accionable sin mutar archivos. |
410
+ | `scripts/watch-consumer-backlog-fleet.ts --json` | Vigilar varios backlogs consumidores y consolidar estado global en una sola salida. |
411
+ | `npm run -s validation:backlog-watch:tick` | Ejecutar tick canónico único (SAAS+RuralGo+Flux) para decisión rápida `fix now` vs `no-action`. |
412
+ | `npm run -s validation:backlog-watch:gate` | Gate previo a release: falla con exit code `1` si hay señal neta accionable en cualquier consumer. |
408
413
  | `scripts/reconcile-consumer-backlog-issues.ts --json` | Simular reconciliación (dry-run) y revisar cambios planeados. |
409
414
  | `scripts/reconcile-consumer-backlog-issues.ts --apply` | Aplicar reconciliación sobre el backlog consumidor. |
410
415
 
@@ -429,6 +434,20 @@ npx --yes tsx@4.21.0 scripts/watch-consumer-backlog.ts \
429
434
  --resolve-missing-via-gh \
430
435
  --json
431
436
 
437
+ # watch fleet: varios consumers en una sola pasada (sin mutar, sin fallar pipeline)
438
+ npx --yes tsx@4.21.0 scripts/watch-consumer-backlog-fleet.ts \
439
+ --target=/abs/path/consumer1/PUMUKI_BUGS_MEJORAS.md::SwiftEnProfundidad/ast-intelligence-hooks \
440
+ --target=/abs/path/consumer2/pumuki-integration-feedback.md::SwiftEnProfundidad/ast-intelligence-hooks \
441
+ --target=/abs/path/consumer3/BUGS_Y_MEJORAS_PUMUKI.md \
442
+ --json \
443
+ --no-fail
444
+
445
+ # watch tick canónico (usa rutas por defecto y devuelve JSON consolidado)
446
+ npm run -s validation:backlog-watch:tick
447
+
448
+ # gate de release (misma señal, pero bloquea con exit code 1 si hay acción requerida)
449
+ npm run -s validation:backlog-watch:gate
450
+
432
451
  # reconcile dry-run: same source chain
433
452
  npx --yes tsx@4.21.0 scripts/reconcile-consumer-backlog-issues.ts \
434
453
  --file=/abs/path/consumer/PUMUKI_BUGS_MEJORAS.md \
@@ -2185,8 +2185,208 @@
2185
2185
  - `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 integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `78 pass / 0 fail`.
2186
2186
  - `npm run -s typecheck` -> `PASS`.
2187
2187
 
2188
- - 🚧 PUMUKI-167: Preparar release + rollout consumidor tras cierre de backlog SDD/Flux/RuralGo.
2189
- - Alcance:
2190
- - consolidar changelog de fixes/mejoras cerradas en este bloque,
2191
- - publicar nueva versión npm con evidencia de tests en verde,
2192
- - ejecutar upgrade/update en repos consumidores (`SAAS`, `R_GO`) y validar smoke mínimo post-upgrade.
2188
+ - PUMUKI-167: Preparar release + rollout consumidor tras cierre de backlog SDD/Flux/RuralGo.
2189
+ - Resultado (2026-03-05):
2190
+ - versión publicada: `pumuki@6.3.45`.
2191
+ - changelog/release notes actualizados para bloque SDD:
2192
+ - `sync-docs` default 3 docs canónicos,
2193
+ - `auto-sync` default `tasks/design/retrospective`,
2194
+ - `learning_context` automático en tools MCP.
2195
+ - rollout consumidor completado:
2196
+ - `R_GO`: `install + status --json + doctor --json` en verde (`lifecycleState.version=6.3.45`, `issues=[]`).
2197
+ - `SAAS:APP_SUPERMERCADOS`: `install + status --json + doctor --json` en verde (`lifecycleState.version=6.3.45`, `issues=[]`).
2198
+ - `Flux_training`: `install + status --json + doctor --json` en verde (`lifecycleState.version=6.3.45`, `issues=[]`).
2199
+ - Evidencia:
2200
+ - `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 integrations/sdd/__tests__/syncDocs.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `78 pass / 0 fail`.
2201
+ - `npm run -s typecheck` -> `PASS`.
2202
+ - `npm publish --access public` -> `+ pumuki@6.3.45`.
2203
+ - `npm view pumuki@6.3.45 version` -> `6.3.45`.
2204
+
2205
+ - ✅ PUMUKI-168: Monitorización post-release 6.3.45 en consumidores reales y captura de hallazgos netos nuevos (sin reabrir cerrados).
2206
+ - Resultado (2026-03-05):
2207
+ - pasada completa de `backlog-watch` en consumidores:
2208
+ - `SAAS`: `entriesScanned=25`, `nonClosedEntries=0`, `hasActionRequired=false`.
2209
+ - `RuralGo`: `entriesScanned=100`, `nonClosedEntries=0`, `hasActionRequired=false`.
2210
+ - `Flux`: se detectó gap de parser (`entriesScanned=0`) con IDs `PUM-*`.
2211
+ - fix inmediato aplicado en core backlog tooling para compatibilidad con IDs Flux:
2212
+ - regex de IDs ampliado a `PUM-*` en:
2213
+ - `scripts/watch-consumer-backlog-lib.ts`
2214
+ - `scripts/reconcile-consumer-backlog-issues-lib.ts`
2215
+ - `scripts/backlog-id-issue-map-lib.ts`
2216
+ - tests de regresión añadidos:
2217
+ - `scripts/__tests__/watch-consumer-backlog.test.ts`
2218
+ - `scripts/__tests__/reconcile-consumer-backlog-issues.test.ts`
2219
+ - `scripts/__tests__/backlog-id-issue-map-lib.test.ts`
2220
+ - revalidación post-fix:
2221
+ - `Flux` pasa a `entriesScanned=12`, `nonClosedEntries=0`, `hasActionRequired=false`.
2222
+ - Evidencia:
2223
+ - `npm run -s test:backlog-tooling` -> `51 pass / 0 fail`.
2224
+ - `npm run -s typecheck` -> `PASS`.
2225
+ - `npm run -s validation:backlog-watch -- --file=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md\" --repo=SwiftEnProfundidad/ast-intelligence-hooks --json --no-fail` -> `hasActionRequired=false`.
2226
+ - `npm run -s validation:backlog-watch -- --file=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md\" --repo=SwiftEnProfundidad/ast-intelligence-hooks --json --no-fail` -> `hasActionRequired=false`.
2227
+ - `npm run -s validation:backlog-watch -- --file=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md\" --repo=SwiftEnProfundidad/ast-intelligence-hooks --json --no-fail` -> `entriesScanned=12`, `hasActionRequired=false`.
2228
+
2229
+ - ✅ PUMUKI-169: Vigilancia continua post-6.3.45 + preparación de siguiente corte solo ante incidencia neta reproducible.
2230
+ - Resultado (2026-03-05):
2231
+ - monitorización ejecutada en los 3 consumers con `backlog-watch`:
2232
+ - `SAAS`: `entriesScanned=25`, `nonClosedEntries=0`, `hasActionRequired=false`.
2233
+ - `RuralGo`: `entriesScanned=100`, `nonClosedEntries=0`, `hasActionRequired=false`.
2234
+ - `Flux`: se detectó bug de parser interno (IDs `PUM-*` no contabilizados, `entriesScanned=0`).
2235
+ - fix aplicado en core backlog-tooling:
2236
+ - soporte de IDs `PUM-*` añadido en:
2237
+ - `scripts/watch-consumer-backlog-lib.ts`
2238
+ - `scripts/reconcile-consumer-backlog-issues-lib.ts`
2239
+ - `scripts/backlog-id-issue-map-lib.ts`
2240
+ - regresión cubierta en tests:
2241
+ - `scripts/__tests__/watch-consumer-backlog.test.ts`
2242
+ - `scripts/__tests__/reconcile-consumer-backlog-issues.test.ts`
2243
+ - `scripts/__tests__/backlog-id-issue-map-lib.test.ts`
2244
+ - revalidación post-fix:
2245
+ - `Flux` pasa a `entriesScanned=12`, `nonClosedEntries=0`, `hasActionRequired=false`.
2246
+ - Evidencia:
2247
+ - `npm run -s test:backlog-tooling` -> `51 pass / 0 fail`.
2248
+ - `npm run -s typecheck` -> `PASS`.
2249
+ - `npm run -s validation:backlog-watch -- --file=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md\" --repo=SwiftEnProfundidad/ast-intelligence-hooks --json --no-fail` -> `entriesScanned=12`, `hasActionRequired=false`.
2250
+
2251
+ - ✅ PUMUKI-170: Consolidar vigilancia multi-consumer en un solo comando fleet para reducir fricción operativa y mantener la señal neta en una ejecución única.
2252
+ - Resultado (2026-03-05):
2253
+ - nuevo comando fleet:
2254
+ - `scripts/watch-consumer-backlog-fleet.ts`
2255
+ - permite múltiples `--target=<path>[::repo]`, resumen agregado y salida JSON consolidada.
2256
+ - cobertura añadida:
2257
+ - `scripts/__tests__/watch-consumer-backlog-fleet.test.ts` (help, agregación JSON multi-target, exit code determinista con/ sin `--no-fail`).
2258
+ - wiring operativo:
2259
+ - `package.json`: nuevo script `validation:backlog-watch:fleet`.
2260
+ - `docs/USAGE.md`: comportamiento, referencia rápida y ejemplo de uso multi-consumer.
2261
+ - Evidencia:
2262
+ - `npm run -s test:backlog-tooling` -> `54 pass / 0 fail`.
2263
+ - `npm run -s typecheck` -> `PASS`.
2264
+ - `npm run -s validation:backlog-watch:fleet -- --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md::SwiftEnProfundidad/ast-intelligence-hooks\" --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md::SwiftEnProfundidad/ast-intelligence-hooks\" --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md\" --json --no-fail` -> `targets=3`, `has_action_required=false`.
2265
+
2266
+ - ✅ PUMUKI-171: Mantener vigilancia continua con el nuevo comando fleet y abrir fix incremental solo ante incidencia neta reproducible.
2267
+ - Resultado (2026-03-05):
2268
+ - ciclo de vigilancia ejecutado con `validation:backlog-watch:fleet` en `SAAS`, `RuralGo`, `Flux`.
2269
+ - salida consolidada limpia:
2270
+ - `targets=3`
2271
+ - `entries_scanned_total=137`
2272
+ - `non_closed_total=0`
2273
+ - `action_required_targets=0`
2274
+ - `has_action_required=false`
2275
+ - no se abrió frente técnico nuevo porque no hubo señal neta reproducible.
2276
+ - Evidencia:
2277
+ - `npm run -s validation:backlog-watch:fleet -- --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md::SwiftEnProfundidad/ast-intelligence-hooks\" --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md::SwiftEnProfundidad/ast-intelligence-hooks\" --target=\"/Users/juancarlosmerlosalbarracin/Developer/Projects/Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md\" --json --no-fail` -> `has_action_required=false`.
2278
+
2279
+ - ✅ PUMUKI-172: Automatizar vigilancia cíclica sin fricción (tick único) y dejar evidencia compacta para decisión de release/fix.
2280
+ - Resultado (2026-03-05):
2281
+ - nuevo comando operativo de tick:
2282
+ - `scripts/watch-consumer-backlog-fleet-tick.ts`
2283
+ - defaults canónicos para `SAAS`, `RuralGo` y `Flux` con overrides por flag (`--saas/--ruralgo/--flux`) y repo configurable (`--repo`).
2284
+ - wiring operativo:
2285
+ - `package.json`: nuevo script `validation:backlog-watch:tick`.
2286
+ - `docs/USAGE.md`: comportamiento + referencia rápida + ejemplo de uso.
2287
+ - cobertura añadida:
2288
+ - `scripts/__tests__/watch-consumer-backlog-fleet-tick.test.ts` (`--help`, JSON limpio, exit code 1 con findings sin `--no-fail`).
2289
+ - ejecución real del tick canónico completada en verde (`has_action_required=false`).
2290
+ - Evidencia:
2291
+ - `npm run -s test:backlog-tooling` -> `57 pass / 0 fail`.
2292
+ - `npm run -s typecheck` -> `PASS`.
2293
+ - `npm run -s validation:backlog-watch:tick` -> `targets=3`, `entries_scanned_total=137`, `has_action_required=false`.
2294
+
2295
+ - ✅ PUMUKI-173: Cerrar ciclo de release incremental con criterio “no-action” documentado y checklist de publicación solo cuando haya señal neta.
2296
+ - Resultado (2026-03-05):
2297
+ - criterio operativo formalizado en comandos:
2298
+ - `validation:backlog-watch:tick` (observabilidad, no bloquea pipeline).
2299
+ - `validation:backlog-watch:gate` (gate de release, bloquea con exit code `1` cuando hay señal neta).
2300
+ - documentación actualizada en `docs/USAGE.md` con quick reference y ejemplo explícito para ambos comandos.
2301
+ - verificación real:
2302
+ - `tick` y `gate` devuelven `has_action_required=false` en el estado actual de `SAAS`, `RuralGo`, `Flux`.
2303
+ - Evidencia:
2304
+ - `npm run -s validation:backlog-watch:tick` -> `targets=3`, `has_action_required=false`.
2305
+ - `npm run -s validation:backlog-watch:gate` -> `targets=3`, `has_action_required=false`.
2306
+ - `npm run -s typecheck` -> `PASS`.
2307
+
2308
+ - ✅ PUMUKI-174: Ejecutar ciclo de vigilancia continua y preparar fix inmediato solo al primer `has_action_required=true` en consumers.
2309
+ - Resultado (2026-03-05):
2310
+ - ciclo de vigilancia ejecutado con ambos modos:
2311
+ - `validation:backlog-watch:tick` (observabilidad),
2312
+ - `validation:backlog-watch:gate` (bloqueante para release).
2313
+ - salida consolidada del ciclo:
2314
+ - `targets=3`
2315
+ - `entries_scanned_total=137`
2316
+ - `non_closed_total=0`
2317
+ - `has_action_required=false`
2318
+ - no se abrió issue/fix nuevo porque no hubo señal neta reproducible.
2319
+ - Evidencia:
2320
+ - `npm run -s validation:backlog-watch:tick` -> `has_action_required=false`.
2321
+ - `npm run -s validation:backlog-watch:gate` -> `has_action_required=false`.
2322
+
2323
+ - ✅ PUMUKI-175: Mantener operación continua de vigilancia y disparar fix/release incremental únicamente con señal neta (`has_action_required=true`).
2324
+ - Resultado (2026-03-05):
2325
+ - ciclo de vigilancia periódico ejecutado en modo observabilidad (`tick`) y modo gate (`gate`).
2326
+ - señal consolidada estable:
2327
+ - `targets=3`
2328
+ - `entries_scanned_total=137`
2329
+ - `non_closed_total=0`
2330
+ - `action_required_targets=0`
2331
+ - `has_action_required=false`
2332
+ - no se abrió issue/fix nuevo al no existir incidencia neta reproducible.
2333
+ - Evidencia:
2334
+ - `npm run -s validation:backlog-watch:tick` -> `has_action_required=false`.
2335
+ - `npm run -s validation:backlog-watch:gate` -> `has_action_required=false`.
2336
+
2337
+ - ✅ PUMUKI-176: Continuar vigilancia operativa y abrir ejecución técnica inmediata al primer hallazgo neto en SAAS/RuralGo/Flux.
2338
+ - Resultado (2026-03-05):
2339
+ - ciclo ejecutado con `tick` y `gate` en los tres consumers.
2340
+ - resumen consolidado:
2341
+ - `targets=3`
2342
+ - `entries_scanned_total=137`
2343
+ - `non_closed_total=0`
2344
+ - `action_required_targets=0`
2345
+ - `has_action_required=false`
2346
+ - sin señal neta reproducible, por lo que no se abrió fix nuevo.
2347
+ - Evidencia:
2348
+ - `npm run -s validation:backlog-watch:tick` -> `has_action_required=false`.
2349
+ - `npm run -s validation:backlog-watch:gate` -> `has_action_required=false`.
2350
+
2351
+ - ✅ PUMUKI-177: Mantener vigilancia continua y disparar fix inmediato al primer `has_action_required=true` con actualización simultánea de leyendas externas e internas.
2352
+ - Resultado (2026-03-05):
2353
+ - se detectó señal neta en `Flux` (`has_action_required=true`) por `needs_issue` en `PUM-009/010/011`.
2354
+ - normalización ejecutada sin tocar código funcional del consumer:
2355
+ - `PUM-009` validado como corregido con `pumuki@latest` (`artifact.version="1"` + `artifact.slices[]`) y marcado `✅ Cerrado`.
2356
+ - `PUM-011` validado como corregido con `pumuki@latest` (`lastTick.changedFiles[]` + `lastTick.evaluatedFiles[]`) y marcado `✅ Cerrado`.
2357
+ - `PUM-010` mantenido activo y enlazado a issue upstream `#719` para trazabilidad (`🚧 En construccion`).
2358
+ - tras normalización de leyenda/refs en MD externo, el ciclo vuelve a estado saludable (`has_action_required=false`).
2359
+ - Evidencia:
2360
+ - `gh issue create ...` -> `https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/issues/719`.
2361
+ - `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` (en Flux) -> contrato válido.
2362
+ - `npx --yes --package pumuki@latest pumuki watch --once --stage=PRE_COMMIT --scope=staged --json` (en Flux) -> `lastTick.changedFiles[]` y `lastTick.evaluatedFiles[]` presentes.
2363
+ - `npm run -s validation:backlog-watch:tick` + `npm run -s validation:backlog-watch:gate` -> `has_action_required=false`.
2364
+
2365
+ - ✅ PUMUKI-178: Ejecutar implementación técnica del issue `#719` para persistencia estable de skills coverage entre iteraciones en consumer.
2366
+ - Resultado (2026-03-05):
2367
+ - `stageRunners` ahora replica el patrón de auto-reconcile de `watch` para bloqueos de skills coverage en hooks (`PRE_COMMIT`/`PRE_PUSH`):
2368
+ - detecta códigos de bloqueo de cobertura de skills,
2369
+ - ejecuta `policy reconcile --strict --apply`,
2370
+ - reintenta una única vez el gate de hook con política re-resuelta.
2371
+ - cobertura de regresión añadida:
2372
+ - retry exitoso con reconcile automático en `PRE_COMMIT`,
2373
+ - no-retry cuando `PUMUKI_HOOK_POLICY_AUTO_RECONCILE=0`.
2374
+ - Evidencia:
2375
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` -> `30 pass / 0 fail`.
2376
+ - `npm run -s typecheck` -> `PASS`.
2377
+
2378
+ - ✅ PUMUKI-179: Ejecutar siguiente foco activo de Flux (`PUM-012`) para eliminar drift entre binario local del consumer y `pumuki@latest` en contrato `watch --json` (`changedFiles/evaluatedFiles` + versión efectiva).
2379
+ - Resultado (2026-03-05):
2380
+ - `watch --json` ahora expone bloque `version` con:
2381
+ - `effective`, `runtime`, `consumerInstalled`, `source`,
2382
+ - `driftFromRuntime`,
2383
+ - `driftWarning` humano/accionable cuando existe desalineación.
2384
+ - cobertura técnica añadida:
2385
+ - `integrations/lifecycle/__tests__/watch.test.ts` (metadata + warning de drift),
2386
+ - ajuste de contrato en `integrations/lifecycle/__tests__/cli.test.ts`.
2387
+ - Evidencia:
2388
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `48 pass / 0 fail`.
2389
+ - `npm run -s typecheck` -> `PASS`.
2390
+ - smoke consumer Flux (binario local core): `watch --once --json` -> `version.driftFromRuntime=true` + `driftWarning` presente.
2391
+
2392
+ - 🚧 PUMUKI-180: Cerrar rollout consumidor de `PUM-012` (release + update en Flux) y mover leyenda externa de `🚧` a `✅` con evidencia en `pnpm exec pumuki watch --json`.
@@ -22,6 +22,7 @@ import { readFileSync } from 'node:fs';
22
22
  import { readEvidence, readEvidenceResult } from '../evidence/readEvidence';
23
23
  import type { EvidenceReadResult } from '../evidence/readEvidence';
24
24
  import { ensureRuntimeArtifactsIgnored } from '../lifecycle/artifacts';
25
+ import { runPolicyReconcile } from '../lifecycle/policyReconcile';
25
26
 
26
27
  const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
27
28
  'pumuki pre-push blocked: branch has no upstream tracking reference. Configure upstream first (for example: git push --set-upstream origin <branch>) and retry.';
@@ -54,6 +55,14 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
54
55
  'Alinea upstream con la rama actual: git branch --unset-upstream && git push --set-upstream origin <branch>',
55
56
  };
56
57
 
58
+ const HOOK_POLICY_RECONCILE_CODES = new Set<string>([
59
+ 'SKILLS_PLATFORM_COVERAGE_INCOMPLETE_HIGH',
60
+ 'SKILLS_SCOPE_COMPLIANCE_INCOMPLETE_HIGH',
61
+ 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE',
62
+ 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING',
63
+ 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE',
64
+ ]);
65
+
57
66
  type StageRunnerDependencies = {
58
67
  resolvePolicyForStage: typeof resolvePolicyForStage;
59
68
  resolveUpstreamRef: typeof resolveUpstreamRef;
@@ -89,6 +98,7 @@ type StageRunnerDependencies = {
89
98
  writeHookGateSummary: (message: string) => void;
90
99
  isQuietMode: () => boolean;
91
100
  ensureRuntimeArtifactsIgnored: (repoRoot: string) => void;
101
+ runPolicyReconcile: typeof runPolicyReconcile;
92
102
  };
93
103
 
94
104
  const defaultDependencies: StageRunnerDependencies = {
@@ -144,6 +154,7 @@ const defaultDependencies: StageRunnerDependencies = {
144
154
  } catch {
145
155
  }
146
156
  },
157
+ runPolicyReconcile,
147
158
  };
148
159
 
149
160
  const getDependencies = (
@@ -196,6 +207,93 @@ const notifyGateBlockedForStage = (params: {
196
207
  });
197
208
  };
198
209
 
210
+ const isHookPolicyAutoReconcileEnabled = (): boolean =>
211
+ process.env.PUMUKI_HOOK_POLICY_AUTO_RECONCILE !== '0';
212
+
213
+ const shouldRetryAfterPolicyReconcile = (params: {
214
+ dependencies: StageRunnerDependencies;
215
+ repoRoot: string;
216
+ stage: 'PRE_COMMIT' | 'PRE_PUSH';
217
+ }): boolean => {
218
+ const evidence = params.dependencies.readEvidence(params.repoRoot);
219
+ if (!evidence) {
220
+ return false;
221
+ }
222
+ const stageCodes = new Set<string>();
223
+ if (evidence.snapshot.stage === params.stage) {
224
+ for (const finding of evidence.snapshot.findings) {
225
+ stageCodes.add(finding.code);
226
+ }
227
+ }
228
+ for (const violation of evidence.ai_gate.violations) {
229
+ stageCodes.add(violation.code);
230
+ }
231
+ for (const code of stageCodes) {
232
+ if (HOOK_POLICY_RECONCILE_CODES.has(code)) {
233
+ return true;
234
+ }
235
+ }
236
+ return false;
237
+ };
238
+
239
+ type HookStage = 'PRE_COMMIT' | 'PRE_PUSH';
240
+ type HookPolicyTrace = NonNullable<ReturnType<typeof resolvePolicyForStage>['trace']>;
241
+
242
+ const runHookGateAttempt = async (params: {
243
+ dependencies: StageRunnerDependencies;
244
+ stage: HookStage;
245
+ scope: Parameters<typeof runPlatformGate>[0]['scope'];
246
+ }): Promise<{ exitCode: number; policyTrace: HookPolicyTrace }> => {
247
+ const resolved = params.dependencies.resolvePolicyForStage(params.stage);
248
+ const exitCode = await params.dependencies.runPlatformGate({
249
+ policy: resolved.policy,
250
+ policyTrace: resolved.trace,
251
+ scope: params.scope,
252
+ });
253
+ return {
254
+ exitCode,
255
+ policyTrace: resolved.trace,
256
+ };
257
+ };
258
+
259
+ const runHookGateWithPolicyRetry = async (params: {
260
+ dependencies: StageRunnerDependencies;
261
+ repoRoot: string;
262
+ stage: HookStage;
263
+ scope: Parameters<typeof runPlatformGate>[0]['scope'];
264
+ }): Promise<{ exitCode: number; policyTrace: HookPolicyTrace }> => {
265
+ const firstAttempt = await runHookGateAttempt({
266
+ dependencies: params.dependencies,
267
+ stage: params.stage,
268
+ scope: params.scope,
269
+ });
270
+ if (firstAttempt.exitCode === 0) {
271
+ return firstAttempt;
272
+ }
273
+ if (!isHookPolicyAutoReconcileEnabled()) {
274
+ return firstAttempt;
275
+ }
276
+ if (
277
+ !shouldRetryAfterPolicyReconcile({
278
+ dependencies: params.dependencies,
279
+ repoRoot: params.repoRoot,
280
+ stage: params.stage,
281
+ })
282
+ ) {
283
+ return firstAttempt;
284
+ }
285
+ params.dependencies.runPolicyReconcile({
286
+ repoRoot: params.repoRoot,
287
+ strict: true,
288
+ apply: true,
289
+ });
290
+ return runHookGateAttempt({
291
+ dependencies: params.dependencies,
292
+ stage: params.stage,
293
+ scope: params.scope,
294
+ });
295
+ };
296
+
199
297
  const ZERO_HASH = /^0+$/;
200
298
 
201
299
  const toEvidenceAgeSeconds = (
@@ -367,10 +465,10 @@ export async function runPreCommitStage(
367
465
  ) {
368
466
  return 1;
369
467
  }
370
- const resolved = activeDependencies.resolvePolicyForStage('PRE_COMMIT');
371
- const exitCode = await activeDependencies.runPlatformGate({
372
- policy: resolved.policy,
373
- policyTrace: resolved.trace,
468
+ const result = await runHookGateWithPolicyRetry({
469
+ dependencies: activeDependencies,
470
+ repoRoot,
471
+ stage: 'PRE_COMMIT',
374
472
  scope: {
375
473
  kind: 'staged',
376
474
  },
@@ -378,10 +476,10 @@ export async function runPreCommitStage(
378
476
  emitSuccessfulHookGateSummary({
379
477
  dependencies: activeDependencies,
380
478
  stage: 'PRE_COMMIT',
381
- policyTrace: resolved.trace,
382
- exitCode,
479
+ policyTrace: result.policyTrace,
480
+ exitCode: result.exitCode,
383
481
  });
384
- if (exitCode !== 0) {
482
+ if (result.exitCode !== 0) {
385
483
  notifyGateBlockedForStage({
386
484
  dependencies: activeDependencies,
387
485
  stage: 'PRE_COMMIT',
@@ -391,7 +489,7 @@ export async function runPreCommitStage(
391
489
  });
392
490
  }
393
491
  notifyAuditSummaryForStage(activeDependencies, 'PRE_COMMIT');
394
- return exitCode;
492
+ return result.exitCode;
395
493
  }
396
494
 
397
495
  export async function runPrePushStage(
@@ -427,10 +525,10 @@ export async function runPrePushStage(
427
525
  ) {
428
526
  return 1;
429
527
  }
430
- const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
431
- const exitCode = await activeDependencies.runPlatformGate({
432
- policy: resolved.policy,
433
- policyTrace: resolved.trace,
528
+ const result = await runHookGateWithPolicyRetry({
529
+ dependencies: activeDependencies,
530
+ repoRoot,
531
+ stage: 'PRE_PUSH',
434
532
  scope: {
435
533
  kind: 'range',
436
534
  fromRef: bootstrapBaseRef,
@@ -440,18 +538,18 @@ export async function runPrePushStage(
440
538
  emitSuccessfulHookGateSummary({
441
539
  dependencies: activeDependencies,
442
540
  stage: 'PRE_PUSH',
443
- policyTrace: resolved.trace,
444
- exitCode,
541
+ policyTrace: result.policyTrace,
542
+ exitCode: result.exitCode,
445
543
  });
446
544
  notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
447
- return exitCode;
545
+ return result.exitCode;
448
546
  }
449
547
  if (manualInvocationFallback) {
450
548
  process.stderr.write(`${PRE_PUSH_MANUAL_FALLBACK_MESSAGE}\n`);
451
- const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
452
- const exitCode = await activeDependencies.runPlatformGate({
453
- policy: resolved.policy,
454
- policyTrace: resolved.trace,
549
+ const result = await runHookGateWithPolicyRetry({
550
+ dependencies: activeDependencies,
551
+ repoRoot,
552
+ stage: 'PRE_PUSH',
455
553
  scope: {
456
554
  kind: 'workingTree',
457
555
  },
@@ -459,11 +557,11 @@ export async function runPrePushStage(
459
557
  emitSuccessfulHookGateSummary({
460
558
  dependencies: activeDependencies,
461
559
  stage: 'PRE_PUSH',
462
- policyTrace: resolved.trace,
463
- exitCode,
560
+ policyTrace: result.policyTrace,
561
+ exitCode: result.exitCode,
464
562
  });
465
563
  notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
466
- return exitCode;
564
+ return result.exitCode;
467
565
  }
468
566
  process.stderr.write(`${PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE}\n`);
469
567
  notifyGateBlockedForStage({
@@ -506,10 +604,10 @@ export async function runPrePushStage(
506
604
  return 1;
507
605
  }
508
606
 
509
- const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
510
- const exitCode = await activeDependencies.runPlatformGate({
511
- policy: resolved.policy,
512
- policyTrace: resolved.trace,
607
+ const result = await runHookGateWithPolicyRetry({
608
+ dependencies: activeDependencies,
609
+ repoRoot,
610
+ stage: 'PRE_PUSH',
513
611
  scope: {
514
612
  kind: 'range',
515
613
  fromRef: upstreamRef,
@@ -519,10 +617,10 @@ export async function runPrePushStage(
519
617
  emitSuccessfulHookGateSummary({
520
618
  dependencies: activeDependencies,
521
619
  stage: 'PRE_PUSH',
522
- policyTrace: resolved.trace,
523
- exitCode,
620
+ policyTrace: result.policyTrace,
621
+ exitCode: result.exitCode,
524
622
  });
525
- if (exitCode !== 0) {
623
+ if (result.exitCode !== 0) {
526
624
  notifyGateBlockedForStage({
527
625
  dependencies: activeDependencies,
528
626
  stage: 'PRE_PUSH',
@@ -532,7 +630,7 @@ export async function runPrePushStage(
532
630
  });
533
631
  }
534
632
  notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
535
- return exitCode;
633
+ return result.exitCode;
536
634
  }
537
635
 
538
636
  export async function runCiStage(
@@ -15,6 +15,7 @@ import {
15
15
  emitGateBlockedNotification,
16
16
  } from '../notifications/emitAuditSummaryNotification';
17
17
  import { runPolicyReconcile } from './policyReconcile';
18
+ import { resolvePumukiVersionMetadata, type PumukiVersionMetadata } from './packageInfo';
18
19
 
19
20
  export type LifecycleWatchStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
20
21
  export type LifecycleWatchScope = 'workingTree' | 'staged' | 'repoAndStaged' | 'repo';
@@ -49,6 +50,14 @@ export type LifecycleWatchTickResult = {
49
50
  export type LifecycleWatchResult = {
50
51
  command: 'pumuki watch';
51
52
  repoRoot: string;
53
+ version: {
54
+ effective: string;
55
+ runtime: string;
56
+ consumerInstalled: string | null;
57
+ source: PumukiVersionMetadata['source'];
58
+ driftFromRuntime: boolean;
59
+ driftWarning: string | null;
60
+ };
52
61
  stage: LifecycleWatchStage;
53
62
  scope: LifecycleWatchScope;
54
63
  intervalMs: number;
@@ -73,6 +82,7 @@ type LifecycleWatchDependencies = {
73
82
  emitAuditSummaryNotificationFromEvidence: typeof emitAuditSummaryNotificationFromEvidence;
74
83
  emitGateBlockedNotification: typeof emitGateBlockedNotification;
75
84
  runPolicyReconcile: typeof runPolicyReconcile;
85
+ resolvePumukiVersionMetadata: (params?: { repoRoot?: string }) => PumukiVersionMetadata;
76
86
  nowMs: () => number;
77
87
  sleep: (ms: number) => Promise<void>;
78
88
  };
@@ -96,6 +106,7 @@ const defaultDependencies: LifecycleWatchDependencies = {
96
106
  emitAuditSummaryNotificationFromEvidence,
97
107
  emitGateBlockedNotification,
98
108
  runPolicyReconcile,
109
+ resolvePumukiVersionMetadata,
99
110
  nowMs: () => Date.now(),
100
111
  sleep: async (ms) => {
101
112
  await sleepTimer(ms);
@@ -231,6 +242,12 @@ export const runLifecycleWatch = async (
231
242
  ...dependencies,
232
243
  };
233
244
  const repoRoot = params?.repoRoot ?? activeDependencies.resolveRepoRoot();
245
+ const versionMetadata = activeDependencies.resolvePumukiVersionMetadata({ repoRoot });
246
+ const driftFromRuntime = versionMetadata.resolvedVersion !== versionMetadata.runtimeVersion;
247
+ const driftWarning = driftFromRuntime
248
+ ? `Version drift detectado: effective=${versionMetadata.resolvedVersion} runtime=${versionMetadata.runtimeVersion}. ` +
249
+ 'Actualiza el consumer para alinear el binario local con @latest y evitar diagnósticos inconsistentes.'
250
+ : null;
234
251
  const stage = params?.stage ?? 'PRE_COMMIT';
235
252
  const scope = params?.scope ?? 'workingTree';
236
253
  const intervalMs = Math.max(250, Math.trunc(params?.intervalMs ?? 3000));
@@ -450,6 +467,14 @@ export const runLifecycleWatch = async (
450
467
  return {
451
468
  command: 'pumuki watch',
452
469
  repoRoot,
470
+ version: {
471
+ effective: versionMetadata.resolvedVersion,
472
+ runtime: versionMetadata.runtimeVersion,
473
+ consumerInstalled: versionMetadata.consumerInstalledVersion,
474
+ source: versionMetadata.source,
475
+ driftFromRuntime,
476
+ driftWarning,
477
+ },
453
478
  stage,
454
479
  scope,
455
480
  intervalMs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.45",
3
+ "version": "6.3.46",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -49,7 +49,7 @@
49
49
  "test:heuristics": "npx --yes tsx@4.21.0 --test core/facts/__tests__/extractHeuristicFacts.test.ts",
50
50
  "test:evidence": "npx --yes tsx@4.21.0 --test integrations/evidence/__tests__/buildEvidence.test.ts integrations/evidence/__tests__/humanIntent.test.ts",
51
51
  "test:mcp": "npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/*.test.ts",
52
- "test:backlog-tooling": "npx --yes tsx@4.21.0 --test scripts/__tests__/backlog-action-reasons-lib.test.ts scripts/__tests__/backlog-json-contract-lib.test.ts scripts/__tests__/backlog-cli-help-exit-code.test.ts scripts/__tests__/backlog-id-issue-map-lib.test.ts scripts/__tests__/reconcile-consumer-backlog-issues.test.ts scripts/__tests__/watch-consumer-backlog.test.ts",
52
+ "test:backlog-tooling": "npx --yes tsx@4.21.0 --test scripts/__tests__/backlog-action-reasons-lib.test.ts scripts/__tests__/backlog-json-contract-lib.test.ts scripts/__tests__/backlog-cli-help-exit-code.test.ts scripts/__tests__/backlog-id-issue-map-lib.test.ts scripts/__tests__/reconcile-consumer-backlog-issues.test.ts scripts/__tests__/watch-consumer-backlog.test.ts scripts/__tests__/watch-consumer-backlog-fleet.test.ts scripts/__tests__/watch-consumer-backlog-fleet-tick.test.ts",
53
53
  "test:saas-ingestion": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/saasIngestionContract.test.ts integrations/lifecycle/__tests__/saasIngestionBuilder.test.ts integrations/lifecycle/__tests__/saasIngestionTransport.test.ts integrations/lifecycle/__tests__/saasIngestionIdempotency.test.ts integrations/lifecycle/__tests__/saasIngestionAuth.test.ts integrations/lifecycle/__tests__/saasIngestionAudit.test.ts integrations/lifecycle/__tests__/saasIngestionMetrics.test.ts integrations/lifecycle/__tests__/saasIngestionGovernance.test.ts integrations/lifecycle/__tests__/saasFederation.test.ts integrations/lifecycle/__tests__/saasEnterpriseAnalytics.test.ts integrations/lifecycle/__tests__/cli.test.ts",
54
54
  "test:operational-memory": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/operationalMemoryContract.test.ts integrations/lifecycle/__tests__/operationalMemorySignals.test.ts integrations/lifecycle/__tests__/operationalMemorySnapshot.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/git/__tests__/runPlatformGateEvidence.test.ts integrations/evidence/__tests__/buildEvidence.test.ts integrations/evidence/writeEvidence.test.ts integrations/evidence/generateEvidence.test.ts",
55
55
  "test:stage-gates": "npx --yes tsx@4.21.0 --test integrations/config/__tests__/*.test.ts integrations/gate/__tests__/*.test.ts integrations/git/__tests__/*.test.ts integrations/lifecycle/__tests__/*.test.ts integrations/sdd/__tests__/*.test.ts scripts/__tests__/*.test.ts",
@@ -101,6 +101,9 @@
101
101
  "validation:tracking-single-active": "bash scripts/check-tracking-single-active.sh",
102
102
  "validation:backlog-reconcile": "node --import tsx scripts/reconcile-consumer-backlog-issues.ts",
103
103
  "validation:backlog-watch": "node --import tsx scripts/watch-consumer-backlog.ts",
104
+ "validation:backlog-watch:fleet": "node --import tsx scripts/watch-consumer-backlog-fleet.ts",
105
+ "validation:backlog-watch:tick": "node --import tsx scripts/watch-consumer-backlog-fleet-tick.ts --json --no-fail",
106
+ "validation:backlog-watch:gate": "node --import tsx scripts/watch-consumer-backlog-fleet-tick.ts --json",
104
107
  "validation:phase5-escalation:ready-to-submit": "bash scripts/check-phase5-escalation-ready-to-submit.sh",
105
108
  "validation:phase5-escalation:prepare": "bash scripts/prepare-phase5-escalation-submission.sh",
106
109
  "validation:phase5-escalation:close-submission": "bash scripts/close-phase5-escalation-submission.sh",
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
 
4
- export const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
4
+ export const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
5
5
 
6
6
  export type BacklogIdIssueMapRecord = Readonly<Record<string, number>>;
7
7
 
@@ -75,10 +75,10 @@ export type BacklogReconcileResult = {
75
75
 
76
76
  const STATUS_EMOJI_PATTERN = /(✅|🚧|⏳|⛔)/;
77
77
  const ISSUE_REF_PATTERN = /#(\d+)/;
78
- const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
78
+ const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
79
79
  const PENDING_REFERENCE_PATTERN = /\|\s*Pendiente(?:\s*\(rel\.\s*#\d+\))?\s*\|/;
80
80
  const BACKLOG_SECTION_HEADING_PATTERN =
81
- /^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
81
+ /^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
82
82
 
83
83
  export type BacklogIssueNumberResolver = (
84
84
  backlogId: string,
@@ -0,0 +1,225 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+ import {
6
+ buildWatchActionRequiredReasons,
7
+ formatActionReasonsForHuman,
8
+ } from './backlog-action-reasons-lib';
9
+ import {
10
+ BACKLOG_JSON_COMPAT_CONTRACT_ID,
11
+ BACKLOG_JSON_COMPAT_MIN_READER_VERSION,
12
+ BACKLOG_JSON_SCHEMA_VERSION,
13
+ } from './backlog-json-contract-lib';
14
+ import { runBacklogWatch } from './watch-consumer-backlog-lib';
15
+
16
+ type FleetTarget = {
17
+ key: 'saas' | 'ruralgo' | 'flux';
18
+ filePath: string;
19
+ repo?: string;
20
+ };
21
+
22
+ type ParsedArgs = {
23
+ json: boolean;
24
+ failOnFindings: boolean;
25
+ targets: ReadonlyArray<FleetTarget>;
26
+ };
27
+
28
+ const JSON_TOOL_NAME = 'backlog-watch-fleet-tick';
29
+ const DEFAULT_REPO = 'SwiftEnProfundidad/ast-intelligence-hooks';
30
+ const DEFAULT_TARGETS = {
31
+ saas: resolve(
32
+ homedir(),
33
+ 'Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md'
34
+ ),
35
+ ruralgo: resolve(
36
+ homedir(),
37
+ 'Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md'
38
+ ),
39
+ flux: resolve(homedir(), 'Developer/Projects/Flux_training/docs/BUGS_Y_MEJORAS_PUMUKI.md'),
40
+ } as const;
41
+
42
+ const HELP_TEXT = `Usage:
43
+ npx --yes tsx@4.21.0 scripts/watch-consumer-backlog-fleet-tick.ts [--json] [--no-fail]
44
+ [--saas=<path>] [--ruralgo=<path>] [--flux=<path>] [--repo=<owner/name>]
45
+
46
+ Defaults:
47
+ --saas=${DEFAULT_TARGETS.saas}
48
+ --ruralgo=${DEFAULT_TARGETS.ruralgo}
49
+ --flux=${DEFAULT_TARGETS.flux}
50
+ --repo=${DEFAULT_REPO}
51
+
52
+ Options:
53
+ --json Imprime salida consolidada JSON.
54
+ --no-fail No devuelve exit code 1 aunque existan findings accionables.
55
+ --saas=PATH Override ruta backlog SAAS.
56
+ --ruralgo=PATH Override ruta backlog RuralGo.
57
+ --flux=PATH Override ruta backlog Flux.
58
+ --repo=NAME Override repo para targets que consultan upstream.
59
+ `;
60
+
61
+ class HelpRequestedError extends Error {
62
+ constructor() {
63
+ super(HELP_TEXT);
64
+ this.name = 'HelpRequestedError';
65
+ }
66
+ }
67
+
68
+ const parseArgs = (argv: ReadonlyArray<string>): ParsedArgs => {
69
+ let json = false;
70
+ let failOnFindings = true;
71
+ let saasPath = DEFAULT_TARGETS.saas;
72
+ let ruralgoPath = DEFAULT_TARGETS.ruralgo;
73
+ let fluxPath = DEFAULT_TARGETS.flux;
74
+ let repo = DEFAULT_REPO;
75
+
76
+ for (const arg of argv) {
77
+ if (arg === '--help' || arg === '-h') {
78
+ throw new HelpRequestedError();
79
+ }
80
+ if (arg === '--json') {
81
+ json = true;
82
+ continue;
83
+ }
84
+ if (arg === '--no-fail') {
85
+ failOnFindings = false;
86
+ continue;
87
+ }
88
+ if (arg.startsWith('--saas=')) {
89
+ saasPath = resolve(arg.slice('--saas='.length).trim());
90
+ continue;
91
+ }
92
+ if (arg.startsWith('--ruralgo=')) {
93
+ ruralgoPath = resolve(arg.slice('--ruralgo='.length).trim());
94
+ continue;
95
+ }
96
+ if (arg.startsWith('--flux=')) {
97
+ fluxPath = resolve(arg.slice('--flux='.length).trim());
98
+ continue;
99
+ }
100
+ if (arg.startsWith('--repo=')) {
101
+ const value = arg.slice('--repo='.length).trim();
102
+ repo = value.length > 0 ? value : DEFAULT_REPO;
103
+ continue;
104
+ }
105
+ throw new Error(`Unknown argument "${arg}"\n\n${HELP_TEXT}`);
106
+ }
107
+
108
+ const targets: FleetTarget[] = [
109
+ { key: 'saas', filePath: saasPath, repo },
110
+ { key: 'ruralgo', filePath: ruralgoPath, repo },
111
+ { key: 'flux', filePath: fluxPath },
112
+ ];
113
+
114
+ for (const target of targets) {
115
+ if (!existsSync(target.filePath)) {
116
+ throw new Error(
117
+ `Target backlog not found for ${target.key}: ${target.filePath}\n` +
118
+ `Use --${target.key}=<path> to override.\n\n${HELP_TEXT}`
119
+ );
120
+ }
121
+ }
122
+
123
+ return {
124
+ json,
125
+ failOnFindings,
126
+ targets,
127
+ };
128
+ };
129
+
130
+ const formatHumanOutput = (
131
+ targets: ReadonlyArray<
132
+ Awaited<ReturnType<typeof runBacklogWatch>> & {
133
+ key: FleetTarget['key'];
134
+ actionRequiredReasons: ReadonlyArray<string>;
135
+ }
136
+ >
137
+ ): string => {
138
+ const lines: string[] = [];
139
+ const actionRequiredTargets = targets.filter((item) => item.hasActionRequired).length;
140
+ const entriesTotal = targets.reduce((acc, item) => acc + item.entriesScanned, 0);
141
+ const nonClosedTotal = targets.reduce((acc, item) => acc + item.nonClosedEntries, 0);
142
+ lines.push(
143
+ `[pumuki][backlog-watch-fleet-tick] targets=${targets.length} entries_total=${entriesTotal} non_closed_total=${nonClosedTotal} action_required_targets=${actionRequiredTargets}`
144
+ );
145
+ for (const target of targets) {
146
+ lines.push(
147
+ `[pumuki][backlog-watch-fleet-tick] key=${target.key} target=${target.filePath} entries=${target.entriesScanned} non_closed=${target.nonClosedEntries} action_required=${target.hasActionRequired ? 'yes' : 'no'} reasons=${formatActionReasonsForHuman(target.actionRequiredReasons)}`
148
+ );
149
+ }
150
+ return `${lines.join('\n')}\n`;
151
+ };
152
+
153
+ const main = async (): Promise<void> => {
154
+ const parsed = parseArgs(process.argv.slice(2));
155
+ const results = await Promise.all(
156
+ parsed.targets.map(async (target) => {
157
+ const watchResult = await runBacklogWatch({
158
+ filePath: target.filePath,
159
+ repo: target.repo,
160
+ });
161
+ const actionRequiredReasons = buildWatchActionRequiredReasons({
162
+ needsIssueCount: watchResult.classification.needsIssue.length,
163
+ driftClosedIssueCount: watchResult.classification.driftClosedIssue.length,
164
+ headingDriftCount: watchResult.headingDrift.length,
165
+ });
166
+ return {
167
+ ...watchResult,
168
+ key: target.key,
169
+ actionRequiredReasons,
170
+ };
171
+ })
172
+ );
173
+
174
+ const hasActionRequired = results.some((item) => item.hasActionRequired);
175
+ const actionRequiredTargets = results.filter((item) => item.hasActionRequired).length;
176
+ const entriesScannedTotal = results.reduce((acc, item) => acc + item.entriesScanned, 0);
177
+ const nonClosedTotal = results.reduce((acc, item) => acc + item.nonClosedEntries, 0);
178
+
179
+ if (parsed.json) {
180
+ writeFileSync(
181
+ process.stdout.fd,
182
+ `${JSON.stringify(
183
+ {
184
+ tool: JSON_TOOL_NAME,
185
+ schema_version: BACKLOG_JSON_SCHEMA_VERSION,
186
+ generated_at: new Date().toISOString(),
187
+ run_id: randomUUID(),
188
+ compat: {
189
+ contract_id: BACKLOG_JSON_COMPAT_CONTRACT_ID,
190
+ min_reader_version: BACKLOG_JSON_COMPAT_MIN_READER_VERSION,
191
+ is_backward_compatible: true,
192
+ breaking_changes: [],
193
+ },
194
+ summary: {
195
+ targets: parsed.targets.length,
196
+ entries_scanned_total: entriesScannedTotal,
197
+ non_closed_total: nonClosedTotal,
198
+ action_required_targets: actionRequiredTargets,
199
+ has_action_required: hasActionRequired,
200
+ },
201
+ results,
202
+ },
203
+ null,
204
+ 2
205
+ )}\n`
206
+ );
207
+ } else {
208
+ writeFileSync(process.stdout.fd, formatHumanOutput(results));
209
+ }
210
+
211
+ if (parsed.failOnFindings && hasActionRequired) {
212
+ process.exitCode = 1;
213
+ }
214
+ };
215
+
216
+ main().catch((error: unknown) => {
217
+ if (error instanceof HelpRequestedError) {
218
+ writeFileSync(process.stdout.fd, `${HELP_TEXT}\n`);
219
+ process.exitCode = 0;
220
+ return;
221
+ }
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ writeFileSync(process.stderr.fd, `${message}\n`);
224
+ process.exitCode = 1;
225
+ });
@@ -0,0 +1,200 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import {
5
+ buildWatchActionRequiredReasons,
6
+ formatActionReasonsForHuman,
7
+ } from './backlog-action-reasons-lib';
8
+ import {
9
+ BACKLOG_JSON_COMPAT_CONTRACT_ID,
10
+ BACKLOG_JSON_COMPAT_MIN_READER_VERSION,
11
+ BACKLOG_JSON_SCHEMA_VERSION,
12
+ } from './backlog-json-contract-lib';
13
+ import { runBacklogWatch } from './watch-consumer-backlog-lib';
14
+
15
+ type FleetTarget = {
16
+ filePath: string;
17
+ repo?: string;
18
+ };
19
+
20
+ type ParsedArgs = {
21
+ targets: ReadonlyArray<FleetTarget>;
22
+ json: boolean;
23
+ failOnFindings: boolean;
24
+ };
25
+
26
+ const JSON_TOOL_NAME = 'backlog-watch-fleet';
27
+
28
+ const HELP_TEXT = `Usage:
29
+ npx --yes tsx@4.21.0 scripts/watch-consumer-backlog-fleet.ts --target=<markdown-path>[::owner/name] [--target=<markdown-path>[::owner/name] ...] [--json] [--no-fail]
30
+
31
+ Options:
32
+ --target=<path>[::repo] Backlog markdown consumidor a vigilar. Puedes repetir el flag.
33
+ Ejemplo: --target=/repo/docs/backlog.md::owner/name
34
+ --json Imprime resultado consolidado en JSON.
35
+ --no-fail No devuelve exit code 1 aunque existan findings accionables.
36
+ `;
37
+
38
+ class HelpRequestedError extends Error {
39
+ constructor() {
40
+ super(HELP_TEXT);
41
+ this.name = 'HelpRequestedError';
42
+ }
43
+ }
44
+
45
+ const parseTarget = (rawTarget: string): FleetTarget => {
46
+ const trimmed = rawTarget.trim();
47
+ const separatorIndex = trimmed.indexOf('::');
48
+ if (separatorIndex === -1) {
49
+ return {
50
+ filePath: resolve(trimmed),
51
+ };
52
+ }
53
+ const filePath = trimmed.slice(0, separatorIndex).trim();
54
+ const repo = trimmed.slice(separatorIndex + 2).trim();
55
+ return {
56
+ filePath: resolve(filePath),
57
+ repo: repo.length > 0 ? repo : undefined,
58
+ };
59
+ };
60
+
61
+ const parseArgs = (argv: ReadonlyArray<string>): ParsedArgs => {
62
+ const targets: FleetTarget[] = [];
63
+ let json = false;
64
+ let failOnFindings = true;
65
+
66
+ for (const arg of argv) {
67
+ if (arg === '--help' || arg === '-h') {
68
+ throw new HelpRequestedError();
69
+ }
70
+ if (arg === '--json') {
71
+ json = true;
72
+ continue;
73
+ }
74
+ if (arg === '--no-fail') {
75
+ failOnFindings = false;
76
+ continue;
77
+ }
78
+ if (arg.startsWith('--target=')) {
79
+ const rawTarget = arg.slice('--target='.length);
80
+ if (rawTarget.trim().length === 0) {
81
+ throw new Error(`Invalid --target (empty value)\n\n${HELP_TEXT}`);
82
+ }
83
+ targets.push(parseTarget(rawTarget));
84
+ continue;
85
+ }
86
+ throw new Error(`Unknown argument "${arg}"\n\n${HELP_TEXT}`);
87
+ }
88
+
89
+ if (targets.length === 0) {
90
+ throw new Error(`Missing --target\n\n${HELP_TEXT}`);
91
+ }
92
+
93
+ return {
94
+ targets,
95
+ json,
96
+ failOnFindings,
97
+ };
98
+ };
99
+
100
+ const formatHumanOutput = (
101
+ targets: ReadonlyArray<
102
+ Awaited<ReturnType<typeof runBacklogWatch>> & {
103
+ actionRequiredReasons: ReadonlyArray<string>;
104
+ }
105
+ >
106
+ ): string => {
107
+ const lines: string[] = [];
108
+ const actionRequiredTargets = targets.filter((item) => item.hasActionRequired).length;
109
+ const entriesTotal = targets.reduce((acc, item) => acc + item.entriesScanned, 0);
110
+ const nonClosedTotal = targets.reduce((acc, item) => acc + item.nonClosedEntries, 0);
111
+
112
+ lines.push(
113
+ `[pumuki][backlog-watch-fleet] targets=${targets.length} entries_total=${entriesTotal} non_closed_total=${nonClosedTotal} action_required_targets=${actionRequiredTargets}`
114
+ );
115
+ for (const target of targets) {
116
+ lines.push(
117
+ `[pumuki][backlog-watch-fleet] target=${target.filePath} repo=${target.repo ?? '-'} entries=${target.entriesScanned} non_closed=${target.nonClosedEntries} action_required=${target.hasActionRequired ? 'yes' : 'no'} reasons=${formatActionReasonsForHuman(target.actionRequiredReasons)}`
118
+ );
119
+ }
120
+ return `${lines.join('\n')}\n`;
121
+ };
122
+
123
+ const main = async (): Promise<void> => {
124
+ const parsed = parseArgs(process.argv.slice(2));
125
+ const results = await Promise.all(
126
+ parsed.targets.map(async (target) => {
127
+ const watchResult = await runBacklogWatch({
128
+ filePath: target.filePath,
129
+ repo: target.repo,
130
+ });
131
+ const actionRequiredReasons = buildWatchActionRequiredReasons({
132
+ needsIssueCount: watchResult.classification.needsIssue.length,
133
+ driftClosedIssueCount: watchResult.classification.driftClosedIssue.length,
134
+ headingDriftCount: watchResult.headingDrift.length,
135
+ });
136
+ return {
137
+ ...watchResult,
138
+ actionRequiredReasons,
139
+ };
140
+ })
141
+ );
142
+
143
+ const hasActionRequired = results.some((item) => item.hasActionRequired);
144
+ const actionRequiredTargets = results.filter((item) => item.hasActionRequired).length;
145
+ const entriesScannedTotal = results.reduce((acc, item) => acc + item.entriesScanned, 0);
146
+ const nonClosedTotal = results.reduce((acc, item) => acc + item.nonClosedEntries, 0);
147
+
148
+ if (parsed.json) {
149
+ const generatedAt = new Date().toISOString();
150
+ const runId = randomUUID();
151
+ writeFileSync(
152
+ process.stdout.fd,
153
+ `${JSON.stringify(
154
+ {
155
+ tool: JSON_TOOL_NAME,
156
+ schema_version: BACKLOG_JSON_SCHEMA_VERSION,
157
+ generated_at: generatedAt,
158
+ run_id: runId,
159
+ invocation: {
160
+ mode: 'json',
161
+ targets_count: parsed.targets.length,
162
+ },
163
+ compat: {
164
+ contract_id: BACKLOG_JSON_COMPAT_CONTRACT_ID,
165
+ min_reader_version: BACKLOG_JSON_COMPAT_MIN_READER_VERSION,
166
+ is_backward_compatible: true,
167
+ breaking_changes: [],
168
+ },
169
+ summary: {
170
+ targets: parsed.targets.length,
171
+ entries_scanned_total: entriesScannedTotal,
172
+ non_closed_total: nonClosedTotal,
173
+ action_required_targets: actionRequiredTargets,
174
+ has_action_required: hasActionRequired,
175
+ },
176
+ results,
177
+ },
178
+ null,
179
+ 2
180
+ )}\n`
181
+ );
182
+ } else {
183
+ writeFileSync(process.stdout.fd, formatHumanOutput(results));
184
+ }
185
+
186
+ if (parsed.failOnFindings && hasActionRequired) {
187
+ process.exitCode = 1;
188
+ }
189
+ };
190
+
191
+ main().catch((error: unknown) => {
192
+ if (error instanceof HelpRequestedError) {
193
+ writeFileSync(process.stdout.fd, `${HELP_TEXT}\n`);
194
+ process.exitCode = 0;
195
+ return;
196
+ }
197
+ const message = error instanceof Error ? error.message : String(error);
198
+ writeFileSync(process.stderr.fd, `${message}\n`);
199
+ process.exitCode = 1;
200
+ });
@@ -53,10 +53,10 @@ export type BacklogIssueNumberResolver = (
53
53
 
54
54
  const STATUS_EMOJI_PATTERN = /(✅|🚧|⏳|⛔)/;
55
55
  const ISSUE_REF_PATTERN = /#(\d+)/;
56
- const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
56
+ const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
57
57
  const STATUS_TEXT_PATTERN = /^(OPEN|PENDING|REPORTED|IN_PROGRESS|BLOCKED|FIXED|CLOSED)\b/i;
58
58
  const BACKLOG_SECTION_HEADING_PATTERN =
59
- /^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
59
+ /^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
60
60
  const STATUS_TEXT_TO_EMOJI: Record<string, BacklogStatusEmoji> = {
61
61
  OPEN: '⏳',
62
62
  PENDING: '⏳',