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.
- package/docs/RELEASE_NOTES.md +13 -0
- package/docs/USAGE.md +19 -0
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +205 -5
- package/integrations/git/stageRunners.ts +128 -30
- package/integrations/lifecycle/watch.ts +25 -0
- package/package.json +5 -2
- package/scripts/backlog-id-issue-map-lib.ts +1 -1
- package/scripts/reconcile-consumer-backlog-issues-lib.ts +2 -2
- package/scripts/watch-consumer-backlog-fleet-tick.ts +225 -0
- package/scripts/watch-consumer-backlog-fleet.ts +200 -0
- package/scripts/watch-consumer-backlog-lib.ts +2 -2
package/docs/RELEASE_NOTES.md
CHANGED
|
@@ -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
|
-
-
|
|
2189
|
-
-
|
|
2190
|
-
-
|
|
2191
|
-
-
|
|
2192
|
-
|
|
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
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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:
|
|
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
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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:
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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:
|
|
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
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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:
|
|
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.
|
|
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: '⏳',
|