Construir dejando huella. Las decisiones no se documentan; se documentan sus conclusiones. Git recuerda qué cambió. Nadie recuerda por qué. Y ahora el por qué vive en un chat que se cierra a las seis de la tarde.
| commit | autor | src/api/payments.ts | por qué |
|---|---|---|---|
| 3f9a2c1 | Lu | import { z } from 'zod'; | — |
| 3f9a2c1 | Lu | amount: z.number().int().positive(), | — |
| 3f9a2c1 | Lu | currency: z.enum(['ARS', 'USD']), | — |
| 3f9a2c1 | Lu | const key = req.headers['idempotency-key']; | — |
| 3f9a2c1 | Lu | return withIdempotency(key, () => charge(data)); | — |
| Porque | Lu · IA | amount: z.number().int().positive(), | Centavos como entero: los floats de JS no representan exactamente los decimales y en pagos eso termina en diferencias de un centavo. Convención de Stripe y Mercado Pago. Descartado: Decimal (dependencia extra, serializa como string). |
"Armá el endpoint POST /payments. Que valide el body y que no cobre dos veces si reintentan."
"Hay un bug de validación en payments.ts, arreglalo."
Las dos IAs razonaron bien. La segunda nunca vio el razonamiento de la primera. El commit guardó el qué; el por qué murió con la pestaña.
Las decisiones técnicas se tomaban en reuniones, PRs largos y hilos de Slack. Pocas por semana. Alguien se acordaba, y con eso alcanzaba.
Cada persona toma decenas de decisiones por día conversando con su asistente. Qué librería, qué patrón, qué se descartó. El razonamiento suele ser bueno. Y es privado.
La IA de cada uno no conoce lo que decidió la IA de los demás. Así que se contradicen entre sí, con excelentes argumentos las dos.
El volumen de decisiones se multiplicó. La memoria del equipo siguió siendo la misma. Los ADR formales no escalan a este ritmo: nadie va a escribir treinta documentos por día. Hay que capturar donde la decisión nace: en la sesión.
CLAUDE.md queda con un aviso de tres líneas: "antes de tocar un archivo, llamá porque_why". Para humanos existe porque why <path>, pero el humano es el caso raro.
Sin base de datos, sin servicio, sin cuenta. Cero dependencias. Si borrás la herramienta, los archivos siguen ahí.
Sesión real de Claude Code, sin editar. Le pedimos que rompa la decisión del martes "sin vueltas". Nadie le dijo que consulte nada: tenía la tool y la usó.
No se negó a hacer el trabajo. Encontró el por qué original, propuso una salida que lo respeta, y dejó la reversión como una decisión explícita del equipo, que se registra, no como un accidente.
Este es el cambio de fondo: la interfaz de consulta no es un comando para personas. Es una tool para la IA de tu compañero.
Tomi ▸ El frontend manda los montos como decimal (1999.99) y la validación de src/api/payments.ts los rechaza. Cambiá el schema para aceptar decimales y listo, sin vueltas. ⚙ porque_why({ query: "src/api/payments.ts" }) ⚙ Read(src/api/payments.ts) Claude ▸ Hay una decisión activa que esto contradice directamente: "Representar amount en centavos como entero", que dice explícitamente que amount nunca debe ser decimal/float, para evitar los errores de precisión de floats de JS en conciliaciones de pagos. Si cambio el schema para aceptar 1999.99 tal cual, estaría revirtiendo esa decisión en silencio. El motivo sigue siendo válido: el frontend no está siguiendo la convención acordada. Lo que propongo en su lugar: mantener amount como entero en centavos puertas adentro y convertir el decimal del frontend en el borde de la validación (z.preprocess), rechazando si el resultado no es un entero exacto. ¿Querés que lo implemente así, o preferís revertir la decisión del equipo? En ese caso debería registrarse con porque_record.
$ porque check --base main --ask porque · 3 decisión(es) afectada(s) por este cambio ✖ Usar zod para validación de requests (2026-09-01) contradicts: El diff usa 'yup' en lugar de 'zod' para validar PaymentRequest en src/api/payments.ts, justo el endpoint que la decisión menciona explícitamente como punto de partida para zod. ✖ Representar amount en centavos como entero (2026-09-01) contradicts: amount se valida como yup.number().positive() con un comentario de ejemplo '1999.99', tratándolo como decimal/float en lugar de entero en centavos como exige la decisión. ✖ Idempotencia de pagos vía header Idempotency-Key + Redis (2026-09-01) contradicts: El handler de POST /payments no exige ni lee el header Idempotency-Key ni utiliza src/lib/idempotency.ts con Redis, cobrando directamente vía chargeCustomer sin protección de idempotencia. src/api/payments.ts · decisions/2026-09-01-…
Segunda red. Salida real del check sobre un commit que sí rompe las tres decisiones. Aparecen en el PR con su razonamiento original y el veredicto del modelo.
La tool evita el error en el momento de escribir; el check lo atrapa si alguien lo escribió igual, con otra herramienta o sin la tool.
Contradecir una decisión a veces es correcto. Lo que no puede pasar es hacerlo sin saberlo. Por eso el check informa y solo bloquea con --strict.
Cloná el repo y corré el demo. Crea un repo de prueba, reproduce la sesión del martes y pregunta porque why.
En tu propio repo: npx --yes porque-cli init. Crea decisions/, el .mcp.json con la tool, el aviso en CLAUDE.md y el hook de Claude Code. Después trabajás como siempre.
Sumá el workflow de GitHub Actions del repo para que cada PR muestre las decisiones que toca.
Necesita Node 18+ y el CLI claude logueado (o una ANTHROPIC_API_KEY). La tool MCP funciona con Claude Code, Cursor, Codex o cualquier cliente MCP. La captura automática lee el transcript de Claude Code hoy.
Ver el repo en GitHub →