pumuki 6.3.46 → 6.3.48
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
CHANGED
|
@@ -5,6 +5,37 @@ 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.48)
|
|
9
|
+
|
|
10
|
+
- Anti-drift hardening for consumer validation flows:
|
|
11
|
+
- `pumuki watch` now applies manifest integrity guard and restores unexpected mutations to:
|
|
12
|
+
- `package.json`
|
|
13
|
+
- `package-lock.json`
|
|
14
|
+
- `pnpm-lock.yaml`
|
|
15
|
+
- `yarn.lock`
|
|
16
|
+
- If mutation is detected, watch blocks with `MANIFEST_MUTATION_DETECTED`.
|
|
17
|
+
- Hook hardening parity:
|
|
18
|
+
- `pre-commit` / `pre-push` manifest guard now covers npm + pnpm + yarn lockfiles (not only npm).
|
|
19
|
+
- Operational impact:
|
|
20
|
+
- avoids silent manifest/lockfile drift in consumers during validation loops unless upgrade is explicitly requested.
|
|
21
|
+
- Validation evidence:
|
|
22
|
+
- `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/watch.test.ts integrations/git/__tests__/stageRunners.test.ts` (`38 pass / 0 fail`)
|
|
23
|
+
- `npm run -s typecheck` (`PASS`)
|
|
24
|
+
- consumer smoke (Flux, local core bin): `watch --once --json` with hashes unchanged for `package.json` / `pnpm-lock.yaml`.
|
|
25
|
+
|
|
26
|
+
### 2026-03-05 (v6.3.47)
|
|
27
|
+
|
|
28
|
+
- Manifest integrity hardening in hook stages (`PRE_COMMIT` / `PRE_PUSH`):
|
|
29
|
+
- Pumuki now snapshots `package.json` and `package-lock.json` before hook gate execution.
|
|
30
|
+
- If an unexpected mutation appears during the hook flow, Pumuki restores both files automatically and blocks with:
|
|
31
|
+
- `MANIFEST_MUTATION_DETECTED`
|
|
32
|
+
- Operational impact:
|
|
33
|
+
- avoids silent consumer manifest drift while keeping explicit upgrade flows (`pumuki update --latest`) under developer control.
|
|
34
|
+
- Validation evidence:
|
|
35
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` (`32 pass / 0 fail`)
|
|
36
|
+
- `npm run -s typecheck` (`PASS`)
|
|
37
|
+
- consumer check with local bin in RuralGo: hashes of `package.json`/`package-lock.json` unchanged after `PRE_WRITE + pre-commit + pre-push`.
|
|
38
|
+
|
|
8
39
|
### 2026-03-05 (v6.3.46)
|
|
9
40
|
|
|
10
41
|
- Paridad hook/watch en auto-remediación de skills coverage:
|
|
@@ -2389,4 +2389,161 @@
|
|
|
2389
2389
|
- `npm run -s typecheck` -> `PASS`.
|
|
2390
2390
|
- smoke consumer Flux (binario local core): `watch --once --json` -> `version.driftFromRuntime=true` + `driftWarning` presente.
|
|
2391
2391
|
|
|
2392
|
-
-
|
|
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`.
|
|
2393
|
+
- Resultado (2026-03-05):
|
|
2394
|
+
- release publicado: `pumuki@6.3.46`.
|
|
2395
|
+
- rollout en Flux:
|
|
2396
|
+
- `pnpm add -Dw pumuki@latest`
|
|
2397
|
+
- `pnpm exec pumuki watch --once --stage=PRE_COMMIT --scope=staged --json`
|
|
2398
|
+
- contrato validado en consumer:
|
|
2399
|
+
- `version.effective=6.3.46`
|
|
2400
|
+
- `version.runtime=6.3.46`
|
|
2401
|
+
- `version.driftFromRuntime=false`
|
|
2402
|
+
- `lastTick.changedFiles[]` y `lastTick.evaluatedFiles[]` presentes.
|
|
2403
|
+
- MD externo Flux actualizado:
|
|
2404
|
+
- `PUM-012` -> `✅ Cerrado`
|
|
2405
|
+
- backlog Flux queda `✅ 100% cerrado`.
|
|
2406
|
+
|
|
2407
|
+
- ✅ PUMUKI-181: Ejecutar verificación post-release en SAAS y RuralGo (`status/doctor/watch`) y registrar únicamente hallazgos netos nuevos.
|
|
2408
|
+
- Resultado (2026-03-05):
|
|
2409
|
+
- verificación completa en `SAAS`:
|
|
2410
|
+
- `status --json`: `packageVersion=6.3.46`, `lifecycleState.version=6.3.46`.
|
|
2411
|
+
- `doctor --json`: `issues=[]`.
|
|
2412
|
+
- `watch --once --json`: `version.effective=6.3.46`, `driftFromRuntime=false`, gate `ALLOW`.
|
|
2413
|
+
- verificación completa en `R_GO`:
|
|
2414
|
+
- `status --json`: `packageVersion=6.3.46`, `lifecycleState.version=6.3.46`.
|
|
2415
|
+
- `doctor --json`: `issues=[]`.
|
|
2416
|
+
- `watch --once --json`: `version.effective=6.3.46`, `driftFromRuntime=false`, gate `ALLOW`.
|
|
2417
|
+
- contraste de backlog externo (solo señal neta):
|
|
2418
|
+
- `SAAS`: `hasActionRequired=false`.
|
|
2419
|
+
- `RuralGo`: `hasActionRequired=false`.
|
|
2420
|
+
- no se registran hallazgos netos nuevos en esta pasada.
|
|
2421
|
+
- Evidencia:
|
|
2422
|
+
- `pnpm exec pumuki status --json` (SAAS).
|
|
2423
|
+
- `pnpm exec pumuki doctor --json` (SAAS).
|
|
2424
|
+
- `pnpm exec pumuki watch --once --stage=PRE_COMMIT --scope=staged --json` (SAAS).
|
|
2425
|
+
- `npx --yes --package pumuki@latest pumuki status --json` (R_GO).
|
|
2426
|
+
- `npx --yes --package pumuki@latest pumuki doctor --json` (R_GO).
|
|
2427
|
+
- `npx --yes --package pumuki@latest pumuki watch --once --stage=PRE_COMMIT --scope=staged --json` (R_GO).
|
|
2428
|
+
- `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`.
|
|
2429
|
+
- `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`.
|
|
2430
|
+
|
|
2431
|
+
- ✅ PUMUKI-182: Ejecutar ciclo de vigilancia activa post-6.3.46 en SAAS/RuralGo/Flux y abrir fix inmediato solo si aparece `has_action_required=true`.
|
|
2432
|
+
- Resultado (2026-03-05):
|
|
2433
|
+
- ciclo `tick` y `gate` ejecutado en flota completa (`SAAS`, `RuralGo`, `Flux`).
|
|
2434
|
+
- estado consolidado:
|
|
2435
|
+
- `targets=3`
|
|
2436
|
+
- `entries_scanned_total=137`
|
|
2437
|
+
- `non_closed_total=0`
|
|
2438
|
+
- `has_action_required=false`
|
|
2439
|
+
- no se abrió fix nuevo al no existir señal neta reproducible.
|
|
2440
|
+
- Evidencia:
|
|
2441
|
+
- `npm run -s validation:backlog-watch:tick`.
|
|
2442
|
+
- `npm run -s validation:backlog-watch:gate`.
|
|
2443
|
+
|
|
2444
|
+
- ✅ PUMUKI-183: Mantener vigilancia continua (tick/gate) y preparar corte de release solo cuando aparezca incidencia neta reproducible en consumers.
|
|
2445
|
+
- Resultado (2026-03-05):
|
|
2446
|
+
- nueva pasada operativa completada con `tick` y `gate` sobre `SAAS`, `RuralGo` y `Flux`.
|
|
2447
|
+
- estado consolidado:
|
|
2448
|
+
- `targets=3`
|
|
2449
|
+
- `entries_scanned_total=137`
|
|
2450
|
+
- `non_closed_total=0`
|
|
2451
|
+
- `action_required_targets=0`
|
|
2452
|
+
- `has_action_required=false`
|
|
2453
|
+
- sin señal neta reproducible, no se abre fix nuevo ni corte de release.
|
|
2454
|
+
- Evidencia:
|
|
2455
|
+
- `npm run -s validation:backlog-watch:tick`.
|
|
2456
|
+
- `npm run -s validation:backlog-watch:gate`.
|
|
2457
|
+
|
|
2458
|
+
- ✅ PUMUKI-184: Mantener ciclo continuo de vigilancia multi-consumer (tick/gate) y ejecutar fix inmediato al primer `has_action_required=true` con actualización de leyenda en MD externo + MD interno.
|
|
2459
|
+
- Resultado (2026-03-05):
|
|
2460
|
+
- el ciclo `tick/gate` detectó señal neta reproducible:
|
|
2461
|
+
- `targets=3`
|
|
2462
|
+
- `entries_scanned_total=139`
|
|
2463
|
+
- `non_closed_total=1`
|
|
2464
|
+
- `has_action_required=true`
|
|
2465
|
+
- origen: `RuralGo` -> `PUMUKI-INC-063` (`needs_issue`).
|
|
2466
|
+
- trazabilidad inmediata ejecutada:
|
|
2467
|
+
- issue upstream creada: `#720`.
|
|
2468
|
+
- MD externo RuralGo actualizado con referencia de issue:
|
|
2469
|
+
- fila resumen activa `PUMUKI-INC-063` -> incluye `issue #720`.
|
|
2470
|
+
- entrada incremental `PUMUKI-INC-063` -> `🚧 REPORTED (#720)`.
|
|
2471
|
+
- se mantiene disciplina de una sola task activa y se abre implementación técnica del fix.
|
|
2472
|
+
- Evidencia:
|
|
2473
|
+
- `npm run -s validation:backlog-watch:tick`.
|
|
2474
|
+
- `npm run -s validation:backlog-watch:gate`.
|
|
2475
|
+
- `gh issue create --repo SwiftEnProfundidad/ast-intelligence-hooks ...` -> `https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/issues/720`.
|
|
2476
|
+
- edición de `/Users/juancarlosmerlosalbarracin/Developer/Projects/R_GO/docs/technical/08-validation/refactor/pumuki-integration-feedback.md`.
|
|
2477
|
+
|
|
2478
|
+
- ✅ PUMUKI-185: Implementar fix técnico de `#720` (no mutación de `package.json`/`package-lock.json` en hooks/gates sin upgrade explícito) con RED -> GREEN -> REFACTOR y cerrar trazabilidad en MD externo + interno.
|
|
2479
|
+
- Resultado (2026-03-05):
|
|
2480
|
+
- `integrations/git/stageRunners.ts` incorpora guard de integridad de manifests en hooks:
|
|
2481
|
+
- snapshot previo de `package.json` + `package-lock.json`,
|
|
2482
|
+
- detección de mutación inesperada tras gate,
|
|
2483
|
+
- reversión automática al snapshot original,
|
|
2484
|
+
- bloqueo explícito con código `MANIFEST_MUTATION_DETECTED`.
|
|
2485
|
+
- comportamiento objetivo:
|
|
2486
|
+
- los hooks no dejan side-effects en manifests del consumer sin comando explícito de upgrade.
|
|
2487
|
+
- cobertura RED -> GREEN añadida:
|
|
2488
|
+
- `runPreCommitStage bloquea y revierte mutación inesperada de manifests`.
|
|
2489
|
+
- `runPrePushStage bloquea y revierte mutación inesperada de manifests`.
|
|
2490
|
+
- Evidencia:
|
|
2491
|
+
- `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` -> `32 pass / 0 fail`.
|
|
2492
|
+
- `npm run -s typecheck` -> `PASS`.
|
|
2493
|
+
- issue de trazabilidad: `https://github.com/SwiftEnProfundidad/ast-intelligence-hooks/issues/720`.
|
|
2494
|
+
|
|
2495
|
+
- ✅ PUMUKI-186: Validar fix `#720` en consumer real (RuralGo), actualizar estado del hallazgo `PUMUKI-INC-063` y cerrar issue/loop de vigilancia si `has_action_required=false`.
|
|
2496
|
+
- Resultado (2026-03-05):
|
|
2497
|
+
- validación ejecutada en RuralGo con binario local del core (incluye fix `#720`) sobre ciclo real:
|
|
2498
|
+
- `pumuki sdd validate --stage=PRE_WRITE --json`
|
|
2499
|
+
- `pumuki-pre-commit`
|
|
2500
|
+
- `pumuki-pre-push`
|
|
2501
|
+
- comprobación de integridad de manifests:
|
|
2502
|
+
- `package.json`: hash sin cambios antes/después.
|
|
2503
|
+
- `package-lock.json`: hash sin cambios antes/después.
|
|
2504
|
+
- resultado funcional:
|
|
2505
|
+
- `RC prewrite=0`, `RC precommit=0`, `RC prepush=0`.
|
|
2506
|
+
- vigilancia consolidada:
|
|
2507
|
+
- `validation:backlog-watch:tick` en verde (`has_action_required=false`).
|
|
2508
|
+
- Evidencia:
|
|
2509
|
+
- ejecución local fix en consumer RuralGo:
|
|
2510
|
+
- `node /Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/bin/pumuki.js sdd validate --stage=PRE_WRITE --json`
|
|
2511
|
+
- `node /Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/bin/pumuki-pre-commit.js`
|
|
2512
|
+
- `PUMUKI_PRE_PUSH_STDIN=\"\" node /Users/juancarlosmerlosalbarracin/Developer/Projects/ast-intelligence-hooks/bin/pumuki-pre-push.js`
|
|
2513
|
+
- verificación de hashes + estado:
|
|
2514
|
+
- `shasum -a 256 package.json package-lock.json`
|
|
2515
|
+
- `git status --short -- package.json package-lock.json`
|
|
2516
|
+
- vigilancia:
|
|
2517
|
+
- `npm run -s validation:backlog-watch:tick`.
|
|
2518
|
+
|
|
2519
|
+
- ✅ PUMUKI-187: Publicar corte con fix `#720`, actualizar consumers a la versión publicada y mover `PUMUKI-INC-063` de `🚧 REPORTED` a `✅ FIXED` con referencia de issue/commit/release.
|
|
2520
|
+
- Resultado (2026-03-05):
|
|
2521
|
+
- release publicada: `pumuki@6.3.47`.
|
|
2522
|
+
- rollout en consumers:
|
|
2523
|
+
- `R_GO`: upgrade a `pumuki@6.3.47` (con `npm_config_engine_strict=false` por restricción de engines del consumer), `PRE_WRITE/PRE_COMMIT/PRE_PUSH` en `RC=0`.
|
|
2524
|
+
- `SAAS`: upgrade a `pumuki@6.3.47`, validación local con binarios `node node_modules/pumuki/bin/*` (bloqueos existentes del repo por reglas/gate, no regresión de manifests).
|
|
2525
|
+
- `Flux_training`: upgrade a `pumuki@6.3.47`, `PRE_WRITE/PRE_COMMIT` en `RC=0` y `PRE_PUSH` bloqueado por atomicidad del propio worktree (`changed_files=720`), sin mutación de manifests.
|
|
2526
|
+
- verificación de no mutación de manifests:
|
|
2527
|
+
- `R_GO`: `package.json` y `package-lock.json` sin cambios de hash antes/después de `validate + pre-commit + pre-push`.
|
|
2528
|
+
- `SAAS`: `package.json` y `package-lock.json` sin cambios de hash antes/después de `validate + pre-commit + pre-push`.
|
|
2529
|
+
- `Flux_training`: `package.json` y `pnpm-lock.yaml` sin cambios de hash antes/después de `validate + pre-commit + pre-push`.
|
|
2530
|
+
- MD canónico RuralGo actualizado:
|
|
2531
|
+
- `PUMUKI-INC-063` -> `✅ FIXED (#720, release 6.3.47)`.
|
|
2532
|
+
- activos High reducidos a `PUMUKI-INC-064` como único foco.
|
|
2533
|
+
- Evidencia:
|
|
2534
|
+
- `npm publish --access public` -> `+ pumuki@6.3.47`.
|
|
2535
|
+
- `npm ls pumuki --depth=0` (`R_GO`/`SAAS`) y `pnpm list -D pumuki --depth 0` (`Flux_training`) -> `6.3.47`.
|
|
2536
|
+
- hashes before/after de manifests en los tres consumers.
|
|
2537
|
+
|
|
2538
|
+
- ✅ PUMUKI-188: Cerrar trazabilidad de RuralGo tras validación de `PUMUKI-INC-064` y ajustar foco activo al siguiente hallazgo neto real.
|
|
2539
|
+
- Resultado (2026-03-05):
|
|
2540
|
+
- MD canónico RuralGo quedó 100% cerrado:
|
|
2541
|
+
- `✅ Cerrados: 100`
|
|
2542
|
+
- `🚧/⏳/⛔: 0`.
|
|
2543
|
+
- `PUMUKI-INC-064` se consolidó en `✅ FIXED` con referencia de release `6.3.47` y evidencia de `PRE_PUSH=ALLOW` sin workaround manual de `skills.sources.json`/`skills.lock.json`.
|
|
2544
|
+
- `issue #720` cerrada tras validación del fix de mutación de manifests.
|
|
2545
|
+
- Evidencia:
|
|
2546
|
+
- `sed -n '1,60p' /Users/.../R_GO/docs/.../pumuki-integration-feedback.md` (estado cerrado 100/0).
|
|
2547
|
+
- `gh issue close 720 --repo SwiftEnProfundidad/ast-intelligence-hooks ...`.
|
|
2548
|
+
|
|
2549
|
+
- 🚧 PUMUKI-189: Ejecutar siguiente bug prioritaria activa de Flux (`PUM-013`) sobre drift de dependencia en manifests durante validación y cerrar con fix + release + validación consumer.
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
emitAuditSummaryNotificationFromEvidence,
|
|
19
19
|
emitGateBlockedNotification,
|
|
20
20
|
} from '../notifications/emitAuditSummaryNotification';
|
|
21
|
-
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
22
23
|
import { readEvidence, readEvidenceResult } from '../evidence/readEvidence';
|
|
23
24
|
import type { EvidenceReadResult } from '../evidence/readEvidence';
|
|
24
25
|
import { ensureRuntimeArtifactsIgnored } from '../lifecycle/artifacts';
|
|
@@ -53,6 +54,8 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
53
54
|
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>',
|
|
54
55
|
PRE_PUSH_UPSTREAM_MISALIGNED:
|
|
55
56
|
'Alinea upstream con la rama actual: git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
57
|
+
MANIFEST_MUTATION_DETECTED:
|
|
58
|
+
'Los hooks/gates no deben modificar manifests. Revisa wiring y ejecuta upgrade explícito solo cuando aplique (por ejemplo: pumuki update --latest).',
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
const HOOK_POLICY_RECONCILE_CODES = new Set<string>([
|
|
@@ -238,6 +241,87 @@ const shouldRetryAfterPolicyReconcile = (params: {
|
|
|
238
241
|
|
|
239
242
|
type HookStage = 'PRE_COMMIT' | 'PRE_PUSH';
|
|
240
243
|
type HookPolicyTrace = NonNullable<ReturnType<typeof resolvePolicyForStage>['trace']>;
|
|
244
|
+
const MANIFEST_GUARD_FILES = [
|
|
245
|
+
'package.json',
|
|
246
|
+
'package-lock.json',
|
|
247
|
+
'pnpm-lock.yaml',
|
|
248
|
+
'yarn.lock',
|
|
249
|
+
] as const;
|
|
250
|
+
|
|
251
|
+
type ManifestGuardEntry = {
|
|
252
|
+
relativePath: (typeof MANIFEST_GUARD_FILES)[number];
|
|
253
|
+
absolutePath: string;
|
|
254
|
+
existed: boolean;
|
|
255
|
+
contents: string;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const captureManifestGuardSnapshot = (repoRoot: string): Array<ManifestGuardEntry> =>
|
|
259
|
+
MANIFEST_GUARD_FILES.map((relativePath) => {
|
|
260
|
+
const absolutePath = join(repoRoot, relativePath);
|
|
261
|
+
const existed = existsSync(absolutePath);
|
|
262
|
+
return {
|
|
263
|
+
relativePath,
|
|
264
|
+
absolutePath,
|
|
265
|
+
existed,
|
|
266
|
+
contents: existed ? readFileSync(absolutePath, 'utf8') : '',
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const restoreManifestGuardSnapshot = (
|
|
271
|
+
snapshot: ReadonlyArray<ManifestGuardEntry>
|
|
272
|
+
): Array<string> => {
|
|
273
|
+
const mutated: Array<string> = [];
|
|
274
|
+
for (const entry of snapshot) {
|
|
275
|
+
const existsNow = existsSync(entry.absolutePath);
|
|
276
|
+
if (entry.existed) {
|
|
277
|
+
if (!existsNow) {
|
|
278
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
279
|
+
mutated.push(entry.relativePath);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const current = readFileSync(entry.absolutePath, 'utf8');
|
|
283
|
+
if (current !== entry.contents) {
|
|
284
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
285
|
+
mutated.push(entry.relativePath);
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (existsNow) {
|
|
290
|
+
unlinkSync(entry.absolutePath);
|
|
291
|
+
mutated.push(entry.relativePath);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return Array.from(new Set(mutated));
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const enforceManifestGuard = (params: {
|
|
298
|
+
dependencies: StageRunnerDependencies;
|
|
299
|
+
repoRoot: string;
|
|
300
|
+
stage: HookStage;
|
|
301
|
+
snapshot: ReadonlyArray<ManifestGuardEntry>;
|
|
302
|
+
}): boolean => {
|
|
303
|
+
const mutated = restoreManifestGuardSnapshot(params.snapshot);
|
|
304
|
+
if (mutated.length === 0) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
const summary = mutated.join(', ');
|
|
308
|
+
process.stderr.write(
|
|
309
|
+
`[pumuki][manifest-guard] unexpected manifest mutation detected and reverted: ${summary}\n`
|
|
310
|
+
);
|
|
311
|
+
params.dependencies.notifyGateBlocked({
|
|
312
|
+
repoRoot: params.repoRoot,
|
|
313
|
+
stage: params.stage,
|
|
314
|
+
totalViolations: mutated.length,
|
|
315
|
+
causeCode: 'MANIFEST_MUTATION_DETECTED',
|
|
316
|
+
causeMessage:
|
|
317
|
+
`Unexpected manifest mutation detected during ${params.stage}: ${summary}. ` +
|
|
318
|
+
'Hooks/gates must not mutate consumer manifests without explicit upgrade command.',
|
|
319
|
+
remediation: BLOCKED_REMEDIATION_BY_CODE.MANIFEST_MUTATION_DETECTED
|
|
320
|
+
?? DEFAULT_BLOCKED_REMEDIATION,
|
|
321
|
+
});
|
|
322
|
+
notifyAuditSummaryForStage(params.dependencies, params.stage);
|
|
323
|
+
return true;
|
|
324
|
+
};
|
|
241
325
|
|
|
242
326
|
const runHookGateAttempt = async (params: {
|
|
243
327
|
dependencies: StageRunnerDependencies;
|
|
@@ -455,6 +539,7 @@ export async function runPreCommitStage(
|
|
|
455
539
|
): Promise<number> {
|
|
456
540
|
const activeDependencies = getDependencies(dependencies);
|
|
457
541
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
542
|
+
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
458
543
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
459
544
|
if (
|
|
460
545
|
enforceGitAtomicityGate({
|
|
@@ -473,6 +558,16 @@ export async function runPreCommitStage(
|
|
|
473
558
|
kind: 'staged',
|
|
474
559
|
},
|
|
475
560
|
});
|
|
561
|
+
if (
|
|
562
|
+
enforceManifestGuard({
|
|
563
|
+
dependencies: activeDependencies,
|
|
564
|
+
repoRoot,
|
|
565
|
+
stage: 'PRE_COMMIT',
|
|
566
|
+
snapshot: manifestSnapshot,
|
|
567
|
+
})
|
|
568
|
+
) {
|
|
569
|
+
return 1;
|
|
570
|
+
}
|
|
476
571
|
emitSuccessfulHookGateSummary({
|
|
477
572
|
dependencies: activeDependencies,
|
|
478
573
|
stage: 'PRE_COMMIT',
|
|
@@ -497,6 +592,7 @@ export async function runPrePushStage(
|
|
|
497
592
|
): Promise<number> {
|
|
498
593
|
const activeDependencies = getDependencies(dependencies);
|
|
499
594
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
595
|
+
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
500
596
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
501
597
|
const upstreamRef = activeDependencies.resolveUpstreamRef();
|
|
502
598
|
if (!upstreamRef) {
|
|
@@ -535,6 +631,16 @@ export async function runPrePushStage(
|
|
|
535
631
|
toRef: 'HEAD',
|
|
536
632
|
},
|
|
537
633
|
});
|
|
634
|
+
if (
|
|
635
|
+
enforceManifestGuard({
|
|
636
|
+
dependencies: activeDependencies,
|
|
637
|
+
repoRoot,
|
|
638
|
+
stage: 'PRE_PUSH',
|
|
639
|
+
snapshot: manifestSnapshot,
|
|
640
|
+
})
|
|
641
|
+
) {
|
|
642
|
+
return 1;
|
|
643
|
+
}
|
|
538
644
|
emitSuccessfulHookGateSummary({
|
|
539
645
|
dependencies: activeDependencies,
|
|
540
646
|
stage: 'PRE_PUSH',
|
|
@@ -554,6 +660,16 @@ export async function runPrePushStage(
|
|
|
554
660
|
kind: 'workingTree',
|
|
555
661
|
},
|
|
556
662
|
});
|
|
663
|
+
if (
|
|
664
|
+
enforceManifestGuard({
|
|
665
|
+
dependencies: activeDependencies,
|
|
666
|
+
repoRoot,
|
|
667
|
+
stage: 'PRE_PUSH',
|
|
668
|
+
snapshot: manifestSnapshot,
|
|
669
|
+
})
|
|
670
|
+
) {
|
|
671
|
+
return 1;
|
|
672
|
+
}
|
|
557
673
|
emitSuccessfulHookGateSummary({
|
|
558
674
|
dependencies: activeDependencies,
|
|
559
675
|
stage: 'PRE_PUSH',
|
|
@@ -614,6 +730,16 @@ export async function runPrePushStage(
|
|
|
614
730
|
toRef: 'HEAD',
|
|
615
731
|
},
|
|
616
732
|
});
|
|
733
|
+
if (
|
|
734
|
+
enforceManifestGuard({
|
|
735
|
+
dependencies: activeDependencies,
|
|
736
|
+
repoRoot,
|
|
737
|
+
stage: 'PRE_PUSH',
|
|
738
|
+
snapshot: manifestSnapshot,
|
|
739
|
+
})
|
|
740
|
+
) {
|
|
741
|
+
return 1;
|
|
742
|
+
}
|
|
617
743
|
emitSuccessfulHookGateSummary({
|
|
618
744
|
dependencies: activeDependencies,
|
|
619
745
|
stage: 'PRE_PUSH',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
2
4
|
import { setTimeout as sleepTimer } from 'node:timers/promises';
|
|
3
5
|
import { isSeverityAtLeast, type Severity } from '../../core/rules/Severity';
|
|
4
6
|
import type { GateScope } from '../git/runPlatformGateFacts';
|
|
@@ -125,6 +127,8 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
125
127
|
SDD_SESSION_INVALID: 'Refresca la sesión SDD y vuelve a intentar.',
|
|
126
128
|
OPENSPEC_MISSING: 'Instala OpenSpec y reintenta la validación.',
|
|
127
129
|
MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP antes de continuar.',
|
|
130
|
+
MANIFEST_MUTATION_DETECTED:
|
|
131
|
+
'Validación no debe mutar package.json/lockfiles. Revisa wiring y realiza upgrades solo con comando explícito.',
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
const WATCH_POLICY_RECONCILE_CODES = new Set<string>([
|
|
@@ -142,6 +146,61 @@ const THRESHOLD_TO_SEVERITY: Record<LifecycleWatchSeverityThreshold, Severity> =
|
|
|
142
146
|
low: 'INFO',
|
|
143
147
|
};
|
|
144
148
|
|
|
149
|
+
const WATCH_MANIFEST_GUARD_FILES = [
|
|
150
|
+
'package.json',
|
|
151
|
+
'package-lock.json',
|
|
152
|
+
'pnpm-lock.yaml',
|
|
153
|
+
'yarn.lock',
|
|
154
|
+
] as const;
|
|
155
|
+
|
|
156
|
+
type WatchManifestGuardEntry = {
|
|
157
|
+
relativePath: (typeof WATCH_MANIFEST_GUARD_FILES)[number];
|
|
158
|
+
absolutePath: string;
|
|
159
|
+
existed: boolean;
|
|
160
|
+
contents: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const captureWatchManifestGuardSnapshot = (
|
|
164
|
+
repoRoot: string
|
|
165
|
+
): Array<WatchManifestGuardEntry> =>
|
|
166
|
+
WATCH_MANIFEST_GUARD_FILES.map((relativePath) => {
|
|
167
|
+
const absolutePath = join(repoRoot, relativePath);
|
|
168
|
+
const existed = existsSync(absolutePath);
|
|
169
|
+
return {
|
|
170
|
+
relativePath,
|
|
171
|
+
absolutePath,
|
|
172
|
+
existed,
|
|
173
|
+
contents: existed ? readFileSync(absolutePath, 'utf8') : '',
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const restoreWatchManifestGuardSnapshot = (
|
|
178
|
+
snapshot: ReadonlyArray<WatchManifestGuardEntry>
|
|
179
|
+
): Array<string> => {
|
|
180
|
+
const mutated: Array<string> = [];
|
|
181
|
+
for (const entry of snapshot) {
|
|
182
|
+
const existsNow = existsSync(entry.absolutePath);
|
|
183
|
+
if (entry.existed) {
|
|
184
|
+
if (!existsNow) {
|
|
185
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
186
|
+
mutated.push(entry.relativePath);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const current = readFileSync(entry.absolutePath, 'utf8');
|
|
190
|
+
if (current !== entry.contents) {
|
|
191
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
192
|
+
mutated.push(entry.relativePath);
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (existsNow) {
|
|
197
|
+
unlinkSync(entry.absolutePath);
|
|
198
|
+
mutated.push(entry.relativePath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return Array.from(new Set(mutated));
|
|
202
|
+
};
|
|
203
|
+
|
|
145
204
|
const toGateScope = (scope: LifecycleWatchScope): GateScope => {
|
|
146
205
|
if (scope === 'staged') {
|
|
147
206
|
return { kind: 'staged' };
|
|
@@ -322,21 +381,52 @@ export const runLifecycleWatch = async (
|
|
|
322
381
|
gateOutcome: 'BLOCK' | 'WARN' | 'ALLOW' | 'NO_EVIDENCE';
|
|
323
382
|
topCodes: ReadonlyArray<string>;
|
|
324
383
|
}> => {
|
|
325
|
-
const
|
|
384
|
+
const manifestSnapshot = captureWatchManifestGuardSnapshot(repoRoot);
|
|
385
|
+
let gateExitCode = await activeDependencies.runPlatformGate({
|
|
326
386
|
policy: resolvedPolicy.policy,
|
|
327
387
|
policyTrace: resolvedPolicy.trace,
|
|
328
388
|
scope: gateScope,
|
|
329
389
|
silent: true,
|
|
330
390
|
});
|
|
331
391
|
const evidence = activeDependencies.readEvidence(repoRoot);
|
|
332
|
-
const
|
|
333
|
-
const
|
|
392
|
+
const allFindingsBase = evidence?.snapshot.findings ?? [];
|
|
393
|
+
const matchedFindingsBase = allFindingsBase.filter((finding) =>
|
|
334
394
|
isSeverityAtLeast(finding.severity, thresholdSeverity)
|
|
335
395
|
);
|
|
396
|
+
const manifestMutations = restoreWatchManifestGuardSnapshot(manifestSnapshot);
|
|
397
|
+
const manifestMutationFinding: SnapshotFinding | null =
|
|
398
|
+
manifestMutations.length > 0
|
|
399
|
+
? {
|
|
400
|
+
ruleId: 'governance.manifest.no-silent-mutation',
|
|
401
|
+
severity: 'ERROR',
|
|
402
|
+
code: 'MANIFEST_MUTATION_DETECTED',
|
|
403
|
+
message:
|
|
404
|
+
`Unexpected manifest mutation detected during watch and reverted: ${manifestMutations.join(', ')}.`,
|
|
405
|
+
file: manifestMutations[0] ?? 'package.json',
|
|
406
|
+
matchedBy: 'LifecycleWatch',
|
|
407
|
+
source: 'skills.backend.runtime-hygiene',
|
|
408
|
+
}
|
|
409
|
+
: null;
|
|
410
|
+
const allFindings = manifestMutationFinding
|
|
411
|
+
? [...allFindingsBase, manifestMutationFinding]
|
|
412
|
+
: allFindingsBase;
|
|
413
|
+
const matchedFindings = manifestMutationFinding
|
|
414
|
+
? [...matchedFindingsBase, manifestMutationFinding]
|
|
415
|
+
: matchedFindingsBase;
|
|
336
416
|
const rawGateOutcome =
|
|
337
417
|
evidence?.snapshot.outcome ??
|
|
338
418
|
(gateExitCode !== 0 ? 'BLOCK' : 'NO_EVIDENCE');
|
|
339
|
-
const gateOutcome =
|
|
419
|
+
const gateOutcome = manifestMutationFinding
|
|
420
|
+
? 'BLOCK'
|
|
421
|
+
: rawGateOutcome === 'PASS'
|
|
422
|
+
? 'ALLOW'
|
|
423
|
+
: rawGateOutcome;
|
|
424
|
+
if (manifestMutationFinding) {
|
|
425
|
+
gateExitCode = 1;
|
|
426
|
+
process.stderr.write(
|
|
427
|
+
`[pumuki][watch-manifest-guard] unexpected manifest mutation detected and reverted: ${manifestMutations.join(', ')}\n`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
340
430
|
const topCodes = toTopCodes(matchedFindings);
|
|
341
431
|
return {
|
|
342
432
|
gateExitCode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.48",
|
|
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": {
|