pumuki 6.3.37 → 6.3.39

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.37
1
+ v6.3.39
package/docs/README.md CHANGED
@@ -5,7 +5,7 @@ Canonical index for active Pumuki documentation.
5
5
  ## Seguimiento Activo (único)
6
6
 
7
7
  - Maestro: `docs/registro-maestro-de-seguimiento.md`
8
- - Plan activo: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`
8
+ - Plan activo: `docs/seguimiento-activo-pumuki-saas-supermercados.md`
9
9
  - Política: una sola tarea en construcción (`🚧`) en el plan activo.
10
10
 
11
11
  ## Product and Architecture
@@ -5,6 +5,59 @@ 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-04 (v6.3.39)
9
+
10
+ - Adapter/runtime bootstrap hardening:
11
+ - adapter-generated hooks/CI templates now use `npx --yes --package pumuki@latest ...` for deterministic command resolution in consumer repos.
12
+ - Git-range robustness:
13
+ - commit-range facts now guard unresolved refs (`rev-parse --verify`) and avoid ambiguous failures on repos without `HEAD`.
14
+ - Cross-platform critical enforcement:
15
+ - gate now blocks when a detected platform does not have critical (`CRITICAL/ERROR`) skills rules active/evaluated.
16
+ - finding id: `governance.skills.cross-platform-critical.incomplete`.
17
+ - Git atomicity by default:
18
+ - atomicity guard is enabled by default in core gate flow (`PRE_COMMIT/PRE_PUSH/CI`).
19
+ - keeps env/config overrides for enterprise tuning without patching source.
20
+ - Versioned hooks diagnostics hardening (`core.hooksPath`):
21
+ - lifecycle hook resolution now includes fallback to local `.git/config` (`core.hooksPath`) when `git rev-parse --git-path hooks` is unavailable.
22
+ - `status/doctor` now expose effective hook path metadata (`hooksDirectory`, `hooksDirectoryResolution`) and print it in human-readable mode.
23
+ - Validation hardening:
24
+ - `test:stage-gates` stabilized and green with current contracts (`1020 pass / 0 fail / 4 skip`).
25
+ - fixtures aligned to evidence v2.1 (`evidence_chain`, `evidence.source`) and architecture guardrail overrides updated for `integrations/lifecycle/cli.ts`.
26
+ - Traceability:
27
+ - commits: `104fc92`, `2f175ec`, `da7b073`, `2c40a4c`, `b124599`, `2aeb435`
28
+ - Consumer quick verification:
29
+ - `npx --yes --package pumuki@latest pumuki status --json`
30
+ - `npx --yes --package pumuki@latest pumuki doctor --json`
31
+ - `npm run -s typecheck`
32
+ - `npm run -s test:stage-gates`
33
+
34
+ ### 2026-03-04 (v6.3.38)
35
+
36
+ - Blocked notification UX hardening for macOS:
37
+ - short, human-readable Spanish banner (`🔴 Pumuki bloqueado`) with stage + summarized cause.
38
+ - remediation-first body (`Solución: ...`) to maximize visibility in truncated notification banners.
39
+ - Optional blocked-dialog workflow (`PUMUKI_MACOS_BLOCKED_DIALOG=1`):
40
+ - full cause/remediation modal for critical blocks.
41
+ - anti-spam controls in dialog:
42
+ - `Mantener activas`
43
+ - `Silenciar 30 min`
44
+ - `Desactivar`
45
+ - `giving up after 15` timeout to avoid local execution hangs.
46
+ - Notification delivery contract update:
47
+ - config supports `muteUntil` in `.pumuki/system-notifications.json`.
48
+ - delivery result now reports `reason=muted` while silence window is active.
49
+ - Regression baseline alignment:
50
+ - `integrations/git/__tests__/stageRunners.test.ts` updated to run with core skills enabled in test harness, matching current gate contract and eliminating false failures.
51
+ - Traceability:
52
+ - commits: `2f957a2`, `ceb1849`, `98fc108`, `ae90f31`
53
+ - Consumer quick verification:
54
+ - `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts`
55
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts`
56
+ - `PUMUKI_MACOS_BLOCKED_DIALOG=1 npx --yes tsx@4.21.0 -e "import { emitSystemNotification } from './scripts/framework-menu-system-notifications-lib'; console.log(JSON.stringify(emitSystemNotification({ repoRoot: process.cwd(), event: { kind: 'gate.blocked', stage: 'PRE_COMMIT', totalViolations: 1, causeCode: 'BACKEND_AVOID_EXPLICIT_ANY', causeMessage: 'Avoid explicit any in backend code.', remediation: 'Tipa el valor y elimina any explícito en backend.' } })));"`
57
+ - expected signal:
58
+ - banner appears with concise remediation text,
59
+ - optional dialog appears with anti-spam actions when flag is enabled.
60
+
8
61
  ### 2026-03-04 (v6.3.37)
9
62
 
10
63
  - Policy-as-code enterprise hardening shipped:
@@ -5,14 +5,15 @@
5
5
  - Referenciar un único plan activo con fases, tasks y leyenda.
6
6
 
7
7
  ## Estado actual
8
- - Plan activo: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`
9
- - Estado del plan: EN CURSO
10
- - Última task cerrada (`✅`): `P12.F2.T72` (hardening `policy-as-code` completado: issue `#606` cerrada con PR `#608`).
11
- - Task activa (`🚧`): `P12.F2.T73` (preparar/publicar release incremental con el hardening de `#606`).
8
+ - Plan activo: `docs/seguimiento-activo-pumuki-saas-supermercados.md`
9
+ - Estado del plan: EJECUCION
10
+ - Última task cerrada (`✅`): PUMUKI-019 (hardening de diagnóstico para hooks versionados `core.hooksPath` con fallback robusto + cobertura de no-regresión).
11
+ - Task activa (`🚧`): PUMUKI-020 (publicación del siguiente corte tras cierre de PUMUKI-019).
12
+ - Nuevos pendientes añadidos (`⏳`): ninguno en este bloque inmediato.
12
13
 
13
14
  ## Historial resumido
14
- - No se mantienen MDs históricos de seguimiento en este repositorio.
15
- - La trazabilidad histórica relevante debe consolidarse dentro del plan activo o en documentación oficial no temporal.
15
+ - Bloque RuralGO cerrado: `docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md`.
16
+ - Se inicia bloque SAAS_SUPERMERCADOS con plan activo único y legible.
16
17
 
17
18
  ## Regla de operación
18
19
  - Debe existir exactamente una task `🚧` en el plan activo.
@@ -0,0 +1,129 @@
1
+ # Plan Activo Pumuki SAAS Supermercados
2
+
3
+ ## Leyenda
4
+
5
+ - ✅ Cerrado
6
+ - 🚧 En construccion (maximo 1)
7
+ - ⏳ Pendiente
8
+ - ⛔ Bloqueado
9
+
10
+ ## Objetivo
11
+
12
+ - Resolver e implementar bugs y mejoras reportados en:
13
+ - `/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md`
14
+ - Mantener trazabilidad: hallazgo -> fix -> test -> release notes.
15
+
16
+ ## Fase 0. Intake y priorizacion
17
+
18
+ - ✅ Consolidar hallazgos y deduplicar causas raiz del MD canónico.
19
+ - ✅ Priorizar por impacto inicial:
20
+ - P1: `PUMUKI-001`, `PUMUKI-003`, `PUMUKI-005`, `PUMUKI-007`
21
+ - P2: `PUMUKI-002`, `PUMUKI-004`, `PUMUKI-006`
22
+
23
+ ## Fase 1. Bugs P1 (ejecucion tecnica)
24
+
25
+ - ✅ PUMUKI-001: Compatibilidad de receipt MCP entre stages (`PRE_WRITE` vs `PRE_COMMIT`) sin bloqueo falso.
26
+ - ✅ PUMUKI-003: Endurecer resolución de binarios en hooks/scripts para evitar `command not found`.
27
+ - ✅ PUMUKI-005: Soporte robusto para repos con `:` en path (evitar dependencia frágil de PATH).
28
+ - ✅ PUMUKI-007: Soportar repos sin commits (`HEAD` ausente) sin error ambiguo.
29
+
30
+ ## Fase 2. Mejoras P2
31
+
32
+ - ✅ PUMUKI-002: Rule-pack opcional de atomicidad Git + trazabilidad de commit message.
33
+ - ✅ PUMUKI-004: Mejorar diagnóstico de hooks efectivos en escenarios versionados/custom.
34
+ - ✅ PUMUKI-006: Alinear `package_version` reportada por MCP con versión local efectiva del repo consumidor.
35
+
36
+ ## Fase 2.1 Paridad legacy (CLI vs MCP) en SAAS_SUPERMERCADOS
37
+
38
+ - ✅ PUMUKI-008: Feedback iterativo en chat no equivalente a flujo legacy.
39
+ - Evidencia: en ejecución MCP no aparece feedback operativo por iteración del modelo como en el grafo legacy.
40
+ - Esperado: resumen corto y humano en cada iteración (`stage`, `decision`, `next_action`).
41
+ - Entregable: tool MCP de pre-flight para chat con salida estable y accionable.
42
+ - ✅ PUMUKI-009: Desalineación operativa entre `ai_gate_check` y `pre_flight_check`.
43
+ - Evidencia: `ai_gate_check => BLOCKED (EVIDENCE_STALE)` mientras `pre_flight_check => allowed=true`.
44
+ - Esperado: criterio homogéneo o explicación explícita y trazable de por qué uno bloquea y el otro permite.
45
+ - Entregable: decisión unificada desde el mismo evaluador/política.
46
+ - ✅ PUMUKI-010: Respuesta no accionable en `auto_execute_ai_start` para confianza media.
47
+ - Evidencia: `success=true`, `action=ask`, `message=Medium confidence (undefined%)...`.
48
+ - Esperado: `next_action` determinista + confidence numérico consistente + remediación concreta.
49
+ - Entregable: contrato MCP estable (`confidence_pct`, `reason_code`, `next_action`).
50
+ - ✅ PUMUKI-011: Notificación macOS obligatoria en cualquier bloqueo de gate/fase.
51
+ - Requisito hard: cuando Pumuki bloquee (`PRE_WRITE`, `PRE_COMMIT`, `PRE_PUSH`, `CI`), lanzar notificación nativa macOS con sonido.
52
+ - Contenido mínimo: `🔴 BLOQUEADO`, causa exacta (`code + message`) y `cómo solucionarlo` (`next_action`).
53
+ - Entregable: comportamiento consistente en CLI, hooks y herramientas MCP con formato humano.
54
+ - Ajuste UX (2026-03-04): mensaje corto y legible para humanos.
55
+ - Nuevo formato: subtítulo con causa resumida + cuerpo iniciando por `Solución: ...` para que no se corte la remediación.
56
+ - ✅ PoC (2026-03-04): modo opcional de diálogo completo para bloqueo en macOS con `PUMUKI_MACOS_BLOCKED_DIALOG=1` (banner corto + modal con causa/solución completas).
57
+ - ✅ PoC anti-spam (2026-03-04): diálogo con acciones de control (`Mantener activas`, `Silenciar 30 min`, `Desactivar`) + timeout automático de 15s para no bloquear flujo.
58
+
59
+ ## Decisión de producto (hard)
60
+
61
+ - La automatización es obligatoria: minimizar pasos manuales en bootstrap, pre-flight, gate y remediación.
62
+ - Objetivo operativo: que instalación + wiring de agente dejen el flujo listo para ejecutar y reportar bloqueos sin intervención manual extra.
63
+
64
+ ## Aclaración operativa (para no perderse)
65
+
66
+ - CLI: lo ejecutan hooks Git (`pre-commit`, `pre-push`) y comandos manuales (`pumuki ...`).
67
+ - MCP: lo consume el agente/herramienta (Codex/Cursor/Windsurf...) cuando está configurado.
68
+ - `pumuki install`: instala hooks Git y bootstrap base.
69
+ - `pumuki adapter install --agent=<...>`: cablea hooks de agente + servidores MCP en el entorno del agente.
70
+
71
+ ## Fase 3. Cierre
72
+
73
+ - ✅ Ejecutar suite de tests de regresión afectada.
74
+ - Evidencia (2026-03-04): `npx --yes tsx@4.21.0 --test scripts/__tests__/framework-menu-system-notifications.test.ts integrations/git/__tests__/stageRunners.test.ts integrations/lifecycle/__tests__/lifecycle.test.ts` -> `44 pass / 0 fail`.
75
+ - ✅ Actualizar `CHANGELOG.md` y `docs/RELEASE_NOTES.md` con fixes reales.
76
+ - Evidencia (2026-03-04): se documenta en `6.3.38` (CHANGELOG) y en `v6.3.38` (RELEASE_NOTES) el paquete de mejoras `PUMUKI-011` + baseline test alignment.
77
+ - ✅ Publicar versión cuando las tareas en construcción/pending críticas estén cerradas.
78
+ - Evidencia (2026-03-04): `npm publish --access public` => `+ pumuki@6.3.38` y verificación remota `npm view pumuki version` => `6.3.38`.
79
+
80
+ ## Fase 4. Post-release
81
+
82
+ - ✅ Monitorizar feedback de repos consumidores y registrar nuevos hallazgos canónicos.
83
+ - Evidencia (2026-03-04): se activa nuevo frente real en consumer repo con backlog dedicado en `/Users/juancarlosmerlosalbarracin/Developer/Projects/SAAS:APP_SUPERMERCADOS/docs/pumuki/PUMUKI_BUGS_MEJORAS.md`.
84
+ - ✅ Priorizar nuevos bugs/mejoras y abrir siguiente ciclo de implementación.
85
+ - Evidencia (2026-03-04): ciclo técnico arrancado en `ast-intelligence-hooks` con ejecución sobre bugs reales reportados desde SAAS.
86
+
87
+ ## Fase 4.1 Ciclo técnico actual (core Pumuki)
88
+
89
+ - ✅ PUMUKI-012: Endurecer comandos de adapter templates para hooks/CI sin dependencia frágil de `./node_modules/.bin`.
90
+ - Fix: `integrations/lifecycle/adapter.templates.json` ahora usa `npx --yes --package pumuki@latest ...` en `pre_write/pre_commit/pre_push/ci`.
91
+ - Test: `integrations/lifecycle/__tests__/adapter.test.ts`, `integrations/lifecycle/__tests__/doctor.test.ts`, `integrations/lifecycle/__tests__/cli.test.ts`.
92
+ - ✅ PUMUKI-013: Blindar resolución de rango Git cuando `HEAD`/refs no son resolubles (repos sin commits o refs ambiguas).
93
+ - Fix: `integrations/git/getCommitRangeFacts.ts` añade guardas `rev-parse --verify` + fallback seguro sin crash.
94
+ - Test: `integrations/git/__tests__/getCommitRangeFacts.test.ts` (nuevo caso repo sin commits) y `integrations/git/__tests__/runPlatformGateFacts.test.ts`.
95
+ - ✅ PUMUKI-014: Enforcement crítico transversal por plataforma (sin huecos entre skills activas y evaluación real).
96
+ - Fix: `integrations/git/runPlatformGate.ts` incorpora `governance.skills.cross-platform-critical.incomplete` y bloquea cuando una plataforma detectada no tiene reglas críticas (`CRITICAL/ERROR`) activas/evaluadas.
97
+ - Test: `integrations/git/__tests__/runPlatformGate.test.ts` añade casos de bloqueo/allow para cobertura crítica multi-plataforma.
98
+ - ✅ PUMUKI-015: Ejecutar validación extendida de no-regresión (suite stage-gates focal + smoke de hooks) y cerrar trazabilidad final de este bloque crítico.
99
+ - Evidencia (2026-03-04): `npm run -s test:stage-gates` -> `1018 pass / 0 fail / 4 skip`.
100
+ - Fixes incluidos para estabilizar la suite:
101
+ - `integrations/lifecycle/__tests__/saasIngestionBuilder.test.ts` (fixture de evidencia v2.1 con `evidence_chain` válido).
102
+ - `scripts/__tests__/framework-menu-consumer-preflight.test.ts` (contrato `evidence.source` completo en fixtures).
103
+ - `scripts/__tests__/architecture-file-size-guardrails.test.ts` (override explícito para `integrations/lifecycle/cli.ts` en límites de líneas/imports).
104
+ - ✅ PUMUKI-016: Preparar release notes del siguiente corte con trazabilidad de commits y validación ejecutada.
105
+ - Evidencia (2026-03-04):
106
+ - `CHANGELOG.md` actualizado en `[Unreleased]` con `adapter hooks`, `commit-range` y `cross-platform critical enforcement`.
107
+ - `docs/RELEASE_NOTES.md` actualizado con bloque `next cut candidate, post v6.3.38`.
108
+ - ✅ PUMUKI-017: Ejecutar siguiente bug/mejora del backlog SAAS (`PUMUKI-002`: enforcement de atomicidad Git por defecto) con RED->GREEN->REFACTOR.
109
+ - Fix:
110
+ - `integrations/git/gitAtomicity.ts` activa atomicidad por defecto (`enabled: true`) manteniendo override por env/config.
111
+ - `integrations/git/__tests__/gitAtomicity.test.ts` actualiza contrato base a enforcement activo por defecto.
112
+ - Evidencia (2026-03-04):
113
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/gitAtomicity.test.ts` -> `3 pass / 0 fail`.
114
+ - `npx --yes tsx@4.21.0 --test integrations/git/__tests__/stageRunners.test.ts` -> `21 pass / 0 fail`.
115
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/cli.test.ts` -> `29 pass / 0 fail`.
116
+ - ✅ PUMUKI-018: Preparar cierre de corte/publicación tras validación acumulada de PUMUKI-012..017.
117
+ - Evidencia (2026-03-04):
118
+ - `npm run -s test:stage-gates` -> `1018 pass / 0 fail / 4 skip` tras ajuste de regresión en `integrations/git/__tests__/hookGateSummary.test.ts`.
119
+ - Smoke complementario ya validado dentro del bloque: `gitAtomicity`, `stageRunners`, `lifecycle/cli`, `typecheck`.
120
+ - ✅ PUMUKI-019: Ejecutar siguiente bug/mejora del backlog SAAS de prioridad media (`PUMUKI-004`: hooks versionados `core.hooksPath`).
121
+ - Fix:
122
+ - `integrations/lifecycle/hookManager.ts` añade resolución robusta de hooks con fallback a `.git/config` (`core.hooksPath`) cuando no está disponible `git rev-parse --git-path hooks`.
123
+ - `integrations/lifecycle/status.ts` y `integrations/lifecycle/doctor.ts` exponen metadatos de ruta efectiva (`hooksDirectory`, `hooksDirectoryResolution`).
124
+ - `integrations/lifecycle/cli.ts` imprime ruta efectiva de hooks en `status` y `doctor` modo texto para diagnóstico humano.
125
+ - Evidencia (2026-03-04):
126
+ - `npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/hookManager.test.ts integrations/lifecycle/__tests__/status.test.ts integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts` -> `50 pass / 0 fail`.
127
+ - `npm run -s test:stage-gates` -> `1020 pass / 0 fail / 4 skip`.
128
+ - `npm run -s typecheck` -> `PASS`.
129
+ - 🚧 PUMUKI-020: Preparar publicación del siguiente corte cuando PUMUKI-019 quede cerrada sin regresiones.
@@ -1994,10 +1994,34 @@ Criterio de salida F5:
1994
1994
  - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/stagePolicies.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/lifecycle/__tests__/status.test.ts integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `81 passed, 0 failed`.
1995
1995
  - `npm run -s typecheck` => `exit 0`.
1996
1996
 
1997
- - 🚧 `P12.F2.T73` Preparar y publicar release con el hardening de `#606`.
1997
+ - `P12.F2.T73` Preparar y publicar release con el hardening de `#606`.
1998
+ - cierre ejecutado:
1999
+ - release branch creada: `release/6.3.37`.
2000
+ - versionado aplicado en `package.json`, `package-lock.json`, `VERSION` y release notes.
2001
+ - PR de release mergeada: `#610` (`commit acbdb73`).
2002
+ - publicación npm completada con éxito (`npm publish --access public`).
2003
+ - propagación validada: `latest=6.3.37`.
2004
+ - evidencia:
2005
+ - `npx --yes tsx@4.21.0 --test integrations/gate/__tests__/stagePolicies.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/lifecycle/__tests__/status.test.ts integrations/lifecycle/__tests__/doctor.test.ts integrations/lifecycle/__tests__/cli.test.ts` => `81 passed, 0 failed`.
2006
+ - `npm run -s typecheck` => `exit 0`.
2007
+ - `npm view pumuki dist-tags --json` => `"latest": "6.3.37"`.
2008
+ - `npm view pumuki@6.3.37 version` => `6.3.37`.
2009
+
2010
+ - ✅ **Cierre consolidado del bloque P12.F2** (ID interno: `P12.F2.T74`).
2011
+ - cierre ejecutado:
2012
+ - bloque `P12.F2` consolidado con trazabilidad completa (issues/PRs/releases y evidencias de validación).
2013
+ - backlog siguiente preparado en modo standby, sin deuda técnica abierta obligatoria del bloque actual.
2014
+ - confirmada publicación estable `pumuki@6.3.37` como baseline para nuevos proyectos.
2015
+ - evidencia:
2016
+ - `gh issue view 606 --json state` => `CLOSED`.
2017
+ - `gh pr view 608 --json state,mergedAt` => `MERGED`.
2018
+ - `gh pr view 610 --json state,mergedAt` => `MERGED`.
2019
+ - `npm view pumuki dist-tags --json` => `"latest": "6.3.37"`.
2020
+
2021
+ - 🚧 **En espera de nueva orden** (ID interno: `P12.F2.T75`; sin deuda técnica pendiente de este bloque).
1998
2022
  - salida esperada:
1999
- - versionar release incremental y notas de publicación.
2000
- - publicar en npm y verificar propagación `dist-tags`.
2023
+ - mantener una sola `🚧` activa por política del tablero.
2024
+ - no ejecutar trabajo técnico nuevo hasta instrucción explícita del usuario.
2001
2025
 
2002
2026
  Criterio de salida F6:
2003
2027
  - veredicto final trazable y cierre administrativo completo.
@@ -81,6 +81,12 @@ const DEFAULT_MAX_AGE_SECONDS: Readonly<Record<AiGateStage, number>> = {
81
81
  };
82
82
 
83
83
  const DEFAULT_PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
84
+ const MCP_RECEIPT_STAGE_ORDER: Readonly<Record<AiGateStage, number>> = {
85
+ PRE_WRITE: 0,
86
+ PRE_COMMIT: 1,
87
+ PRE_PUSH: 2,
88
+ CI: 3,
89
+ };
84
90
 
85
91
  const toErrorViolation = (code: string, message: string): AiGateViolation => ({
86
92
  code,
@@ -335,6 +341,13 @@ const toPolicyStage = (stage: AiGateStage): SkillsStage => {
335
341
  return stage;
336
342
  };
337
343
 
344
+ const isMcpReceiptStageCompatible = (params: {
345
+ receiptStage: AiGateStage;
346
+ requestedStage: AiGateStage;
347
+ }): boolean => {
348
+ return MCP_RECEIPT_STAGE_ORDER[params.receiptStage] >= MCP_RECEIPT_STAGE_ORDER[params.requestedStage];
349
+ };
350
+
338
351
  const collectMcpReceiptViolations = (params: {
339
352
  required: boolean;
340
353
  stage: AiGateStage;
@@ -405,11 +418,16 @@ const collectMcpReceiptViolations = (params: {
405
418
  )
406
419
  );
407
420
  }
408
- if (receiptRead.receipt.stage !== params.stage) {
421
+ if (
422
+ !isMcpReceiptStageCompatible({
423
+ receiptStage: receiptRead.receipt.stage,
424
+ requestedStage: params.stage,
425
+ })
426
+ ) {
409
427
  violations.push(
410
428
  toErrorViolation(
411
429
  'MCP_ENTERPRISE_RECEIPT_STAGE_MISMATCH',
412
- `MCP receipt stage mismatch (${receiptRead.receipt.stage} != ${params.stage}).`
430
+ `MCP receipt stage mismatch (${receiptRead.receipt.stage} incompatible with ${params.stage}).`
413
431
  )
414
432
  );
415
433
  }
@@ -2,6 +2,7 @@ import { execFileSync as runBinarySync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import type { Fact } from '../../core/facts/Fact';
5
+ import type { GitChange } from './gitDiffUtils';
5
6
  import { parseNameStatus, hasAllowedExtension, buildFactsFromChanges } from './gitDiffUtils';
6
7
  export { parseNameStatus } from './gitDiffUtils';
7
8
 
@@ -77,7 +78,16 @@ export class GitService implements IGitService {
77
78
  }
78
79
 
79
80
  getStagedAndUnstagedFacts(extensions: ReadonlyArray<string>): ReadonlyArray<Fact> {
80
- const trackedChanges = parseNameStatus(this.runGit(['diff', '--name-status', 'HEAD']))
81
+ const hasHeadCommit = this.hasHeadCommit();
82
+ const trackedNameStatus = hasHeadCommit
83
+ ? this.runGit(['diff', '--name-status', 'HEAD'])
84
+ : [
85
+ this.runGit(['diff', '--cached', '--name-status']),
86
+ this.runGit(['diff', '--name-status']),
87
+ ]
88
+ .filter((chunk) => chunk.trim().length > 0)
89
+ .join('\n');
90
+ const trackedChanges = this.deduplicateChangesByPath(parseNameStatus(trackedNameStatus))
81
91
  .filter((change) => hasAllowedExtension(change.path, extensions));
82
92
  const untrackedPaths = this.runGit(['ls-files', '--others', '--exclude-standard'])
83
93
  .split('\n')
@@ -100,6 +110,23 @@ export class GitService implements IGitService {
100
110
  );
101
111
  }
102
112
 
113
+ private hasHeadCommit(): boolean {
114
+ try {
115
+ this.runGit(['rev-parse', '--verify', 'HEAD']);
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+
122
+ private deduplicateChangesByPath(changes: ReadonlyArray<GitChange>): ReadonlyArray<GitChange> {
123
+ const byPath = new Map<string, GitChange>();
124
+ for (const change of changes) {
125
+ byPath.set(change.path, change);
126
+ }
127
+ return Array.from(byPath.values());
128
+ }
129
+
103
130
  private readWorkingTreeFile(repoRoot: string, filePath: string): string {
104
131
  try {
105
132
  return readFileSync(join(repoRoot, filePath), 'utf8');
@@ -5,6 +5,24 @@ import { GitService } from './GitService';
5
5
 
6
6
  const defaultGit: IGitService = new GitService();
7
7
 
8
+ const isResolvableRef = (git: IGitService, ref: string): boolean => {
9
+ try {
10
+ git.runGit(['rev-parse', '--verify', ref]);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ };
16
+
17
+ const isRangeResolutionError = (error: unknown): boolean => {
18
+ if (!(error instanceof Error)) {
19
+ return false;
20
+ }
21
+ return /unknown revision|bad revision|ambiguous argument|fatal:\s+invalid object name/i.test(
22
+ error.message
23
+ );
24
+ };
25
+
8
26
  export async function getFactsForCommitRange(params: {
9
27
  fromRef: string;
10
28
  toRef: string;
@@ -12,11 +30,23 @@ export async function getFactsForCommitRange(params: {
12
30
  git?: IGitService;
13
31
  }): Promise<ReadonlyArray<Fact>> {
14
32
  const git = params.git ?? defaultGit;
15
- const diffOutput = git.runGit([
16
- 'diff',
17
- '--name-status',
18
- `${params.fromRef}..${params.toRef}`,
19
- ]);
33
+ if (!isResolvableRef(git, params.fromRef) || !isResolvableRef(git, params.toRef)) {
34
+ return [];
35
+ }
36
+
37
+ let diffOutput = '';
38
+ try {
39
+ diffOutput = git.runGit([
40
+ 'diff',
41
+ '--name-status',
42
+ `${params.fromRef}..${params.toRef}`,
43
+ ]);
44
+ } catch (error) {
45
+ if (isRangeResolutionError(error)) {
46
+ return [];
47
+ }
48
+ throw error;
49
+ }
20
50
  const changes = parseNameStatus(diffOutput).filter((change) =>
21
51
  hasAllowedExtension(change.path, params.extensions)
22
52
  );