# Changelog

Formato basado en [Keep a Changelog](https://keepachangelog.com/es/1.1.0/) y versionado según
[SemVer](https://semver.org/lang/es/).

`cauce upgrade` reemplaza `system/` completo sin pedir confirmación. Este archivo es lo que hace que
esa operación sea confiable en vez de sólo cómoda: acá se lee qué cambió antes de aplicarlo. Por eso
un cambio en el protocolo, en las reglas del sistema o en un guard es visible para el usuario y sube
minor aunque no toque una sola línea de código.

Cada entrada la imprime `upgrade` a quien está por aplicarla, y un salto de varias versiones las
imprime todas seguidas. Dice qué cambia en lo que recibe y qué tiene que hacer; lo que sólo se observa
desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuando una entrada pasa de
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
diseño — eso vive en el commit y en el código.

## [0.98.0] - 2026-09-17

### Cambiado

- **Descartar una propuesta que decide un cambio pide el motivo, y el informe siguiente lo lee.**
  `ops learn <cargo> --archived` se niega sobre una propuesta cuyo «Cambio propuesto» dice qué hacer si no
  lleva `--reason "<motivo>"`. El motivo queda en el documento, como `- Motivo:`, y en la fila de
  `HISTORY.md`. Cada informe nuevo trae arriba, en un comentario, las propuestas descartadas del
  cargo con su motivo, y le pide no volver a recomendarlas salvo que aparezca un hecho nuevo.

  Antes, archivar no dejaba ningún porqué, y el mismo hallazgo volvía todas las semanas. Una propuesta
  con el molde intacto se sigue archivando sin motivo: nadie decidió nada, y lo que traía sigue vivo.
  Si tenés un script que archiva propuestas decididas, agregale `--reason`.

- **La investigación de los cargos pasa a ser mensual: ya no hay cadencia semanal.** Los cargos con
  fuentes de tipo `advisory`, `platform` o `project` investigaban cada lunes. Ahora investigan el 24, igual
  que los demás, y el 1 se consolida como siempre. `profession` sigue trimestral. Se quitó el cron del
  lunes y la opción `semanal` de la corrida manual. El informe se llama «Investigación» a secas, y el
  prompt le pide al cargo cubrir todo lo publicado desde su informe anterior.

  Es una decisión medida. En tres semanas de septiembre, las corridas semanales dieron unos 353
  hallazgos: ninguno urgente, ninguno aplicado y la mitad repetidos. Lo que produce el ciclo es un cambio
  de contrato, que se firma una vez al mes y llega con `upgrade`, así que detectarlo antes no lo
  adelantaba.

  Si programaste la investigación en tu instalación siguiendo el `learning/AUTOMATION.md` de un cargo,
  pasala a mensual: esos archivos ya lo dicen.

### Corregido

- **Confirmar un bloqueo ya no exige una palabra.** El guard aprobaba sólo si la respuesta empezaba con
  una de once formas —«dale», «sí», «ok»…—, así que «listo», «claro», «confirmo» o «bueno dale» volvían
  a frenar lo que la persona acababa de aprobar. Ahora la confirmación es con tus palabras, y juzgar si es
  un sí le toca al agente: el mensaje del bloqueo se lo pide así. Lo único que el guard sigue frenando es
  la respuesta que niega, que arranca frenando («pará», «esperá», «cancelá») o que pregunta. Y lo que se
  frena mientras decís que no ya no queda esperando una confirmación.

- **La fila que `autobuild` registra al frenarse nace pendiente, y si no quedó así la parada lo dice.**
  En una corrida real, el agente que escribe la fila la dejó `resuelta` y «decidida por el dueño», y la
  tarea quedó desbloqueada sin que nadie decidiera nada. Las cuatro paradas que registran una acción
  humana ahora piden la fila `pendiente` y prohíben atribuir decisiones. Las tres que registran la
  propia tarea relen `ops context` y, si la fila no quedó pendiente, lo dicen en el detalle de la
  parada.

- **El guard de dependencias ya no frena un `package.json` que cambió sin tocar nada del lockfile.**
  Un commit que agrega un script o ajusta la configuración de `jest` se frenaba por llevar el manifiesto
  sin su lock, y había que pedir permiso para un cambio sin riesgo. Pasa sólo con `package-lock.json` y
  sólo si lo que cambió está en una lista medida con npm 11.16.0. `version`, `name`, `license`,
  `engines`, `bin` y los scripts de instalación sí mueven el lock, y siguen frenando. Con pnpm, yarn o
  bun no cambia nada.

- **Las ramas que mergea el auto-merge del ciclo se borran.** El auto-merge de los informes que no
  proponen cambios lo firma `github-actions`, y ese merge no dispara el workflow que borra las ramas,
  así que quedaban en el remoto. Ahora cada corrida del ciclo barre las ramas `automation/*` mergeadas
  que no se movieron después del merge.

- **La estrategia de prueba que el plan fija llega al WIP y a quien construye.** `testStrategy` es
  obligatorio en el plan —el que planifica está obligado a escribirlo— y después no aparecía en ninguna
  otra parte de la corrida. Sus propios pasos la citaban, los pasos sí viajan al WIP, y ella no: un paso
  que decía «correr la mutación declarada en testStrategy» apuntaba a un lugar que no existe.

  El costo es de quien revisa: R9 pide la mutación **declarada**, y sin ella no se puede distinguir la que
  se corrió de la que se pensó, así que la única salida es rehacer la revisión. Medido dos veces, en dos
  corridas distintas.

## [0.97.0] - 2026-09-17

### Agregado

- **La revisión de `autobuild` tiene dónde poner una decisión que no le toca tomar.** Tenía dos puertas:
  marcar el hallazgo como bloqueante —y mandar a tocar código— o dejarlo en el INBOX como propuesta, con
  tope de tres. Un contrato público que alguien tiene que definir no es ninguna de las dos, así que el
  revisor elegía frenar, que de las dos es la correcta y costaba la corrida entera sobre trabajo que
  estaba bien.

  Ahora ese hallazgo va a `HUMAN_ACTIONS.md` con qué lo cierra y quién puede tomarlo, nombrando la épica o
  el hito al que alcanza —nunca la tarea, para no bloquear a la que lo encontró—, y la corrida sigue. La
  entrada de DONE cuenta cuántas quedaron registradas.

### Corregido

- **Una parada que espera una decisión tuya suelta la tarea que había reservado.** Al registrar la fila
  en `HUMAN_ACTIONS.md`, la tarea queda bloqueada; si además seguía reservada, el runner quedaba ocupado
  por algo que nadie podía tomar y la corrida siguiente moría reclamando la que sigue —«este runner ya
  tiene …»—. Pasaba en toda parada anterior a Build, que son las más frecuentes mientras una tarea
  todavía se está definiendo, y costaba una corrida entera por vez.

  Las paradas de después de construir **no** sueltan nada: ahí el WIP existe y es resumible, y la reserva
  es lo único que dice de quién es ese trabajo.

- **Lo que la línea de una tarea ya decidió llega a las fases que deciden.** La descripción —dónde vive un
  símbolo, qué queda fuera de alcance, con qué se produce la evidencia— se leía del BACKLOG y se
  descartaba, así que Plan la volvía a decidir por su cuenta y decidía distinto. Critique **sí** abre el
  BACKLOG, y bloqueaba el plan citando la línea palabra por palabra: una compuerta juzgando contra un
  texto que la otra no recibió. Dos corridas medidas se pagaron enteras por esto, 1,10 M y 815 k tokens,
  sin escribir una línea.

  Ahora `ops context` la emite con la tarea y llega a Ready, Plan, Critique y Build, dicha como lo que es:
  decisiones ya tomadas, que no se re-deciden. **No tenés que cambiar nada en tus líneas** — es el texto
  que ya escribías antes de la aceptación.

- **`git commit --amend` se frena sobre historia publicada, y ya no sobre la que nadie vio.** Lo que R8
  protege es la historia que otro leyó —lo mismo que ya decía del force-push—, y el guard lo bloqueaba
  siempre «por política y no por daño». Eso no impedía el resultado: `git reset --soft HEAD~1` y volver a
  commitear produce exactamente lo mismo, no lo frena nada y no deja constancia. Sólo impedía el comando
  que lo nombra.

  Ahora el guard mira si algún remoto alcanza al commit, con una lectura local que no habla con ningún
  servidor. Publicado sigue cerrado y sin salida por chat, igual que el force-push. **R8 también cambió**:
  pasa a prohibir reescribir historia que otro ya leyó, en vez de nombrar el comando.

- **Una etapa de un recorrido ya no muere por escribir de más.** Pasado cierto tamaño el modelo deja de
  emitir los campos y devuelve el JSON envuelto como texto, que no valida; cinco reintentos después la
  etapa se cae y la corrida entera se para. Cada campo de la respuesta lleva ahora su tope declarado
  —`summary` 1000 caracteres, `missing` y `humanAction` 500, cada evidencia 200—, así que el rechazo
  nombra el campo y el número en vez de decir que no se pudo parsear.

  Lo que no entra no se pierde: va al archivo de análisis, que es lo que lee quien sintetiza al final.
  Si escribís un recorrido propio, no tenés que hacer nada — el tope es del esquema, no del contrato.

- **`autobuild` ya no frena una tarea correcta porque el nombre del test venga anotado de dos formas.**
  La puerta comprueba que un borde que el build arregló aparezca en algún rojo declarado, comparando los
  dos nombres por contención. Eso cubría que uno fuera prefijo del otro, y dejaba afuera la forma que
  aparece de verdad: los dos nombran el mismo test y cada uno le agrega **su propia** anotación entre
  paréntesis —dónde está la línea de un lado, por qué se vio en rojo del otro—. Ahí ninguno contenía al
  otro y la corrida paraba con `edge-unproven` sobre trabajo terminado y en verde.

  Si te pasó, la tarea estaba bien: no hacía falta rehacerla. Y el motivo de la parada ya no se contradice
  solo — dice qué comparó la puerta en vez de afirmar que faltaba la prueba.

- **Una corrección de Review se lleva también lo que ella misma dejó desactualizado.** «Corregí sólo estos
  hallazgos» acota el alcance y dejaba un cabo suelto: el conteo que enumeraba lo que cambió, el
  comentario que describía la forma vieja, la fila que la afirmaba. Como Review tiene una sola vuelta de
  corrección, esa deriva aterrizaba en la re-revisión y la corrida frenaba sobre trabajo correcto.

- **El checkpoint de hito se destraba escribiendo, no borrando.** `AWAITING_REVIEW.md` frenaba a todos los
  runners por el hecho de existir, así que resolverlo exigía acordarse de borrarlo — y mientras tanto
  quien lo leía podía ver ahí escrito que ya estaba revisado. Es lo que R28 prohíbe, y el propio archivo
  lo decía de sí mismo.

  Ahora lleva `status: pendiente` en su frontmatter y se destraba cambiándolo a `resuelta`, con lo cual el
  archivo **se queda** y se puede leer después qué se revisó. Borrarlo sigue funcionando.

  **Tu archivo actual no cambia de conducta**: sin `status` sigue frenando, que es lo que corresponde
  —abrirlo al actualizar sería destrabar una compuerta en silencio—. Agregale la línea cuando quieras
  resolverlo sin perderlo; los que escriba `autobuild` de acá en más ya vienen con ella.

- **`check` rechaza una fila de `HUMAN_ACTIONS.md` que nombra a su tarea sin ser su slug.** El motor
  bloquea por esa primera columna **exacta**, así que `| **alta-de-cliente: falta el token** | pendiente |`
  se escribía sin error, salía en `ops context` bajo `HUMAN` como si estuviera registrada, y la tarea se
  seguía ofreciendo. Es la forma que sale natural, porque esa tabla la lee una persona; en la instancia
  donde se midió, **once filas pendientes bloqueaban cero tareas** y la corrida siguiente volvía a elegir
  lo mismo y a pagar la misma fase.

  Si te aparece, el mensaje dice qué fila y a qué tarea apuntaba: dejá el slug solo en esa celda y contá
  el resto en la columna de la acción. Las otras dos formas no cambian — el slug exacto sigue bloqueando,
  y nombrar la épica, el hito o el recorrido sigue siendo válido cuando lo que se frena no es una tarea.

- **Una decisión que `autobuild` deja abierta ya no bloquea la tarea que la encontró.** Esas filas se
  registran para que la corrida siga —lo que de verdad frena tiene otro camino—, pero la fase que las
  escribe podía poner en la primera columna el slug de la tarea en construcción y bloquearla. No se veía
  mientras la corrida vivía, porque un WIP activo manda sobre la acción humana; aparecía al cerrar el WIP.

- **Una corrida de `autobuild` que no puede tomar nada lo dice, en vez de terminar como si la cola
  estuviera vacía.** Eran dos estados distintos con la misma salida: la cola terminada, y la cola cuyas
  tareas están todas reclamadas por otro runner o esperando una dependencia. La corrida informaba que no
  había nada que hacer sobre una cola que sí tenía trabajo.

  Se paga después de una parada que espera una decisión tuya: ahí el reclamo se queda puesto a propósito
  —quien paró va a volver y su `claim` es idempotente—, así que cuando contestás, **otro** runner pregunta
  y se va sin nada. Ahora para con `queue-unavailable` y dice cuántas hay; la línea `TAKEN` de
  `ops context` nombra quién tiene cada una, y se sueltan con `ops release`.

## [0.96.0] - 2026-09-16

### Corregido

- **Tres errores que el CLI contestaba con exit 1 ahora contestan 2, que es lo que dice la convención.**
  Son «no se llegó a la pregunta»: `integrations/config.json` ilegible, un proveedor que no está en ese
  registro, y un `package.json` inválido al declarar el motor. Sus hermanos —un `ops.config.json`
  ilegible, un proveedor que Cauce no trae— ya contestaban 2.

  El corte, que hasta ahora estaba en la cabeza de quien escribía cada salida, está escrito y sale en
  `ops --help`: **2** es que el comando no existe, falta un argumento, o la raíz que nombrás no es lo que
  dice ser —lo arreglás cambiando la invocación—; **1** es que se llegó y la respuesta es que no —una
  validación encontró problemas, el estado se niega, o una operación falló a mitad de camino—.

  Si tenés un script que distingue los dos, revisá esos tres casos. Si sólo mira «distinto de cero», no
  cambia nada.

- **`ops flow list|check|show` acepta la raíz de la instancia, como el resto.** Todos los comandos que leen
  una instancia la toman como posicional; `flow` la descartaba y resolvía por el directorio actual, así que
  `ops flow list <raíz>` contestaba sobre otra cosa — y una lista de recorridos se lee igual de bien venga
  de donde venga. Ahora es `ops flow list [ops-root]`, y con recorrido va después de él.

- **Una lista de cargos o recorridos vacía dice si es porque no hay o porque no se pudo resolver el
  paquete.** Eran dos hechos distintos con la misma respuesta, y las acciones son opuestas. El aviso sale
  por `stderr` y sólo cuando la lista viene vacía; el código de salida y el `--json` no cambian, porque
  ese JSON lo consume el cron del ciclo de aprendizaje.

- **`automation check` nombra un `ops.config.json` ilegible en vez de culpar al motor.** La resolución del
  paquete cuelga de esa configuración, así que un JSON roto se propagaba como archivos del motor que faltan
  y el aviso mandaba a correr `npm install` sobre un motor instalado: la acción sugerida no arreglaba nada.

- **El catálogo de cargos y recorridos se encuentra también cuando el paquete vive un nivel arriba.** Es el
  layout que documenta el arranque —`npm install @ingeniomaps/cauce` en la carpeta de la empresa y la
  instancia adentro—, y 0.92.0 lo arregló para el motor y dejó afuera el catálogo. En ese estado `check`
  pasaba en verde y `agents list` contestaba **una lista vacía con exit 0**, indistinguible de «esta
  instancia no tiene cargos»; `flow list` igual, y `evaluate` culpaba a un `SKILL.md` que falta, mandando
  a mirar el cargo en vez del paquete.

  Si te pasó, no hacía falta bajar una segunda copia: con esta versión los cargos aparecen donde ya estaban.
  Verificado instalando el paquete publicado desde el registro y recorriendo la superficie del CLI: de 0
  cargos y 0 recorridos a **53 y 7**, sobre la misma instancia.

## [0.95.0] - 2026-09-16

### Corregido

- **El bloqueo dice hasta dónde llega el «dale».** Ofrece dos salidas y sólo la del pegado decía su
  alcance —«valen para ese conjunto y dejan de valer en cuanto cambie»—; la del chat, que es la que se
  ofrece primero, decía sólo «reintentá el mismo cambio y pasa».

  Faltaban **dos** cosas y no son la misma. Que el «dale» cubre lo que se frenó y nada más: por eso en una
  carpeta donde se itera cada archivo nuevo vuelve a frenar, que desde afuera se lee como si el guard se
  hubiera olvidado de que ya la aprobaste. Y que lo concedido **sigue valiendo en los mensajes
  siguientes** hasta que lo niegues — la mitad permisiva, la que no se nota porque lo que no ocurre es un
  bloqueo. `check` la muestra al final de la corrida; ahora también se dice al concederla.

  No cambia ningún permiso: el diff es texto.

- **Partir una tarea de una épica actualiza también sus historias, así que `check` no queda en rojo.** Una
  tarea que viene de una épica es además una historia suya, y la partición reemplazaba sólo su línea del
  BACKLOG. La lista de historias quedaba nombrando un slug que ya no existe en ninguna parte, y eso rompe
  por los dos lados según qué escriba la partición: con las subtareas declarando la épica, `check` falla en
  el acto con «BACKLOG \<sub\>: no existe en epic-NNN»; sin declararla pasa en verde y **la épica no puede
  cerrar nunca**, porque `closed` exige evidencia de cada historia y la original jamás la va a tener.

  Ahí es donde la unidad partida se cierra diciendo en qué se partió, que es lo que pide R25. **No va una
  entrada en `done/`**: ese directorio es la evidencia de lo que una tarea entregó, y una partida no
  entregó nada. Una tarea sin épica no necesita ningún cierre — deja de existir limpiamente y no hay
  cruce que se rompa.

  Si tenés una épica con una historia que ninguna tarea de la cola nombra y que nunca vas a poder cerrar,
  viene de esto: reemplazá esa historia por las de las subtareas que la partición dejó en el BACKLOG.

- **Un límite escrito en dos líneas ya viaja entero a los agentes.** Una viñeta bajo `### Límites` se
  recorría línea por línea, así que de un límite que no entraba en el ancho del archivo —que es como se
  escribe cualquiera de verdad— llegaba **sólo la primera línea**, cortada a mitad de frase. Y lo que se
  pierde ahí suele ser lo que el límite decide: «…crear, editar o borrar productos, pedidos o» sin el «no
  las hace un runner» que venía abajo.

  La continuación, además, volvía por el otro lado: no coincidía con ninguna viñeta declarada y `check` la
  reportaba como un párrafo «que no llega a los agentes», citando media frase tuya. O sea que el proyecto
  hacía lo que el aviso pedía, el aviso seguía sonando, y el límite que sí viajaba era el mutilado.

  Si escribiste tus límites en una sola línea para que el aviso bajara a cero, ya podés volver a partirlos
  donde corresponda. Una línea en blanco cierra la viñeta, así que la prosa que venga después sigue sin
  contar como límite — y sigue avisándose.

- **Una notificación de tarea de fondo ya no borra del chat a la persona que está mirando.** Cuando un
  guard frena algo, el bloqueo ofrece dos salidas: contestar «dale» —corto, y es lo que la persona ya está
  haciendo— o pegar líneas a mano en `planning/.ops-approval`. La primera sólo se ofrecía si el registro
  de la sesión decía que había alguien.

  Una notificación de una tarea de fondo entra por el mismo hook que un mensaje, así que el registro
  pasaba a decir que el último que habló no era una persona — y en el turno que esa notificación despierta
  **la persona sigue ahí**. El bloqueo le ofrecía la salida cara y terminaba copiando y pegando un comando
  que no hacía falta. Se disparaba solo en el patrón más común de una sesión larga: lanzar trabajo de
  fondo y retomar cuando vuelve.

  Que haya alguien a quien preguntarle pasa a ser de la sesión y no del mensaje. **No cambia ninguna
  autorización**: preguntar no es conceder, y lo que decide si un «dale» viejo cubre algo nuevo quedó
  intacto — un mensaje de hace tres turnos sigue sin autorizar lo que se frenó ahora. Y un recorrido de
  Cauce sigue sin ofrecer el «dale», porque ahí no hay nadie leyendo.

- **Una tarea que `autobuild` parte ya suelta su reserva, así que la corrida sigue.** El recorrido reclama
  la tarea antes de estimarla, y cuando Decompose la partía el reclamo quedaba puesto sobre un slug que ya
  no estaba ni en la cola ni en lo hecho. Con eso el runner quedaba ocupado por una tarea que no existe: el
  Claim de la primera subtarea se negaba —«este runner ya tiene …»— y la corrida entera terminaba en
  `claim-stuck` **sin construir nada**, dejando además `check` en rojo con un reclamo huérfano que soltaba
  una persona a mano.

  No dependía de una carrera ni de un id de runner compartido, que es a lo que el mensaje mandaba a mirar:
  pasaba siempre que Decompose partiera, que es una fase que el propio protocolo manda correr. Si venís de
  una versión anterior y tenés un reclamo huérfano de esto, se suelta con
  `node tools/ops.js release planning <tarea>`.

  La reserva se suelta **después** de comprobar que el reemplazo en el BACKLOG ocurrió: si no ocurrió, la
  tarea sigue viva y su reserva tiene que seguir puesta.

- **Un veredicto se lee de una sola forma, así que las dos cuentas de un registro coinciden.** El registro
  de una evaluación transcribe la respuesta literal del cargo —es la evidencia de la corrida—, así que su
  cuerpo puede traer cualquier cosa, incluida una línea con forma de veredicto. La cuenta de la última
  corrida las tomaba todas y la cuenta compuesta sólo las que cuelgan de un `### <caso>`: dos números
  distintos sobre el mismo archivo y ninguna forma de saber cuál valía.

  Con un registro de dos casos cuya respuesta citaba un veredicto ajeno, la última corrida decía «2 de 3»
  sobre una corrida que midió dos y pasó las dos. Ahora un veredicto es un caso con su línea —sin
  encabezado no hay a qué atribuirlo— y las dos lecturas salen del mismo lugar.

- **Un `agents fork` que se corta a la mitad se retira en vez de quedarse puesto.** Si el copiado fallaba
  —un archivo ilegible, disco lleno— quedaba medio cargo en `agents/`, y lo caro no es perder la copia: es
  dejarla. El catálogo pasa a resolver el slug contra esa copia, así que el intento siguiente ya no dice
  que se cortó sino **«ya lo mantiene esta empresa»** — y lo que la empresa mantiene es un contrato
  incompleto que ningún manifiesto registra, o sea que el aviso de deriva tampoco lo mira nunca.

  Verificado provocando el corte con un archivo ilegible: antes el segundo intento contestaba «ya lo
  mantiene esta empresa» con `.cauce` vacío; ahora no queda nada en `agents/` y arreglando lo que lo
  cortó el fork sale entero. Sólo se borra lo que esa llamada creó, y la ruta se comprueba contra la raíz
  de la instancia antes de tocarla.

- **`automation uninstall` no borra nada si no puede leer la configuración de tu runner.** Borraba
  primero y leía el `settings.json` al final, con un `JSON.parse` sin proteger. Con un archivo que alguien
  dejó a medio fusionar, el comando moría con «Expected property name or '}' in JSON at position 13»
  —que no nombra ni un archivo— **después** de haber borrado los workflows y los cargos: la instancia
  quedaba a medio desinstalar, con la configuración registrando guards que ya no existen.

  Verificado sobre un banco: el comando se para antes de tocar el disco, dice qué archivo no pudo leer, y
  arreglando ese archivo vuelve a correr.

- **Un adaptador propio que no trae `components` ya no rompe el borrador.** El README declara el contrato
  de un adaptador por sus tres funciones y no dice qué campos trae un item, así que un adaptador de la
  empresa —que es lo que ese README invita a escribir— puede no traerlo. Con `serviceFrom: "component"`,
  que es lo que trae el molde, eso era un `TypeError` sobre `undefined` sin nada que lo atribuyera.

  Sin el campo la respuesta es la que el borrador ya sabía dar: `service: ""` y «Debe definirse un
  servicio único para la promoción». Lo que se toleró es la ausencia y no el caso de uso: con un
  componente sigue resolviendo el servicio y con dos sigue sin resolverlo.

- **`promotionEpic` se valida también cuando la promoción es una épica.** El campo se exigía `NNN` sólo
  para una historia. Para una épica es opcional —sin él el número lo elige el motor—, y por eso nadie lo
  miraba; pero cuando está se usa tal cual para armar el nombre del archivo. O sea que el único caso sin
  validar era el único que escribe una ruta con lo que diga el campo.

  `promotionEpic: 7` producía `epic-7-…`, que el roadmap no reconoce como épica: la promoción se anunciaba
  bien y lo escrito quedaba invisible para la cola. Y con un `..` la épica salía de `roadmap/` entera.
  Verificado sobre una instancia desechable: `integration check` daba exit 0 y `integration promote`
  devolvía «✓ jira:DEMO-42 promovido como epic» dejando el archivo fuera del roadmap. Ahora `check` lo
  rechaza con «promotionEpic debe ser NNN» y `promote`, que valida antes de escribir, no llega a tocar el
  disco.

- **El aviso de commits sin entrada de DONE vuelve a decir lo que ve.** El filtro por fecha leía la
  columna del `git log` en una posición fija, y `%h` no mide siempre lo mismo: git sube el largo del hash
  abreviado solo cuando el repositorio crece. En cuanto pasa de siete, la posición fija lee un espacio en
  vez de la fecha y **ningún commit pasa el filtro**, así que el aviso quedaba mudo sin decir por qué —la
  peor forma de que una puerta falle, porque se lee igual que «no hay nada que avisar»—. Ahora se parte
  por espacios y se lee el campo.

  Verificado en este repositorio, donde el abreviado mide ocho: el aviso pasó de cero a «66 commit(s)
  desde 2026-09-15 que ninguna entrada de DONE nombra». La prueba nueva lo fija en 7, 8 y 12.

- **El sello de un registro de evaluación se escribe en el frontmatter y no en el cuerpo.**
  `markConsolidated` buscaba `status:` en el documento entero. Un registro nace sin `status` en su
  frontmatter y su cuerpo transcribe la respuesta literal del cargo: si esa respuesta traía una línea
  `status: <algo>` en la columna cero, el sello aterrizaba ahí y el frontmatter quedaba sin sellar.

  Un registro sin sellar vuelve a entrar en la propuesta siguiente, o sea que el mismo hallazgo llega dos
  veces — que es exactamente lo que sellar existe para evitar.

- **`ops -h` pide la ayuda, y un nombre heredado de `Object` ya no pasa por comando.** Dos huecos de la
  puerta de entrada del CLI, los dos de la misma forma: leer algo que no era ni un comando ni una bandera.

  `-h` se anunciaba en el uso y no existía: el parser sólo reconoce lo que empieza con `--`, así que caía
  de argumento posicional y `ops check -h` contestaba «no existe el planning en …/-h» **saliendo con 0**.
  Pedir ayuda y recibir un error sobre un directorio inventado es la peor forma de contestar, porque
  parece que el comando corrió.

  Y la tabla de comandos se consultaba con `FLAGS[comando]`, que resuelve contra `Object.prototype`:
  `ops constructor` salía con 0 sin hacer nada y `ops toString --json` moría con
  «FLAGS[command].includes is not a function». No es un nombre exótico — es lo que sale de pasarle al CLI
  una variable que vino vacía o mal leída desde un script.

- **El tope de horas de una propuesta deja de desaparecer cuando la configuración no lo declara.** La
  validación de propuestas lee `runner.maxTaskHours` de `ops.config.json` y tenía un default de cuatro
  horas para cuando no se puede leer. Ese default sólo cubría el archivo ilegible: un `runner` sin el
  campo no lanza nada, así que el tope quedaba en `undefined` y la comparación pasaba a ser falsa
  siempre. O sea que el límite no se aflojaba: se iba entero, y en silencio.

  Se nota poco a propósito de lo que es — lo que deja de pasar es un **rechazo**—, así que una propuesta
  de cuarenta horas viajaba a Jira sin que nada la mirara. Verificado sobre una configuración con
  `runner: {}`: antes no salía ni un error, ahora sale «supera 4h; debe dividirse o justificarlo».

  Si tu `ops.config.json` declara `maxTaskHours`, nada cambia — `check` ya lo exigía mayor que cero.

- **`rm -r` sobre la raíz o el home se frena también entrecomillado.** La regla reconocía el destino sólo
  desnudo: `rm -rf /` bloqueaba y `rm -rf "/"`, `rm -rf "$HOME"`, `rm -rf ${HOME}`, `rm -rf $HOME/` y
  `rm -rf -- /` pasaban. La comilla es lo que más importa — citar una variable es la forma *correcta* de
  escribirlo en bash, así que el hueco premiaba al que tiene el hábito bueno.

  Los borrados con destino concreto siguen pasando, también entrecomillados: se comprobó con
  `rm -rf "$HOME/proyecto/dist"` entre otros cinco.

- **Un commit escrito en dos líneas vuelve a pasar por los guards.** `isCommit` reconocía el commit
  después de `;`, `&` o `|` y no después de un salto de línea ni dentro de un subshell — o sea que
  `git add x` ⏎ `git commit -m x` en un solo Bash, que es como se escriben dos pasos, dejaba sin correr
  `governance`, `dependencies` y `verify`, y también el bloqueo que impide stagear y commitear a la vez.

  `bash -c "git commit …"` sigue sin reconocerse, a propósito: hacerlo exige desentrecomillar, y eso
  choca con que el mensaje de un commit sea dato y no código.

- **Redirigir con `>|` ya no evade el guard que mira a dónde escribe un comando.** `>|` es el override de
  `noclobber` y escribe igual que `>`, pero el guard no veía su destino: el comando se partía en segmentos
  por `|` antes de buscar destinos, así que la redirección quedaba separada de su ruta. Verificado sobre
  un banco: `echo x > <fuera>` y `echo x >> <fuera>` bloqueaban y `echo x >| <fuera>` pasaba.

  Importa porque una de las rutas que se escriben por ese hueco es `planning/.ops-approval` — la
  aprobación que después habilita el push a la rama viva—, que es lo que cerraron el 098 y el 119.

  Las tuberías corrientes siguen sin producir destino: se comprobó con `ls | wc -l` y `a || b`.

## [0.94.0] - 2026-09-16

### Agregado

- **Cinco reglas nuevas, traídas de una empresa que las pagó.** Salieron de revisar las reglas propias de
  una instancia real: no son ideas, cada una tiene adentro la corrida que costó.

  - **R24 — una premisa sobre el propio código se abre antes de usarla.** R14 ya exigía registro para lo
    que se afirma de una herramienta o una norma, y dejaba afuera tu propio repositorio, que es donde
    nadie te va a discutir. Cuatro corridas perdidas en un día, todas frenadas en la puerta y ninguna por
    el código: la aceptación pedía algo que el sistema no hace. Y el ancla `archivo:línea` se abre, no se
    copia — una función se movió de la 555 a la 733 en la misma sesión.
  - **R25 — el identificador de una unidad de trabajo no cambia mientras está viva.** Renombrar un slug a
    mitad de camino rompe el cruce entre la cola y lo hecho **sin que nada falle**: cada lado se lee
    coherente por separado. Una tarea partida en cuatro y cerrada con otros nombres costó 594k tokens de
    la corrida siguiente para descubrir que ya estaba construida. Si el nombre tiene que cambiar, va
    `slug-nuevo (antes: slug-viejo)` hasta cerrar.
  - **R26 — una puerta acota su propio costo y no escribe en el árbol que juzga.** Dos revisores lanzando
    la misma suite fueron cuatro corridas en cuatro minutos: el sistema operativo mató la sesión entera
    con un pico de 24,2 GB. Y un formateador con `--fix` o un build que limpia su salida editan el trabajo
    de quien está commiteando. Una puerta que estorba se saltea, y desde ahí no protege de nada.
  - **R27 — una defensa se aplica por defecto y cada excepción se declara sola.** Con lista de lo que
    protege, todo lo que se agregue después nace afuera y nada lo compara. Incluye el caso que más se
    disfraza: «esta comprobación no corre en desarrollo» es una quita escrita como agregado, y garantiza
    que el camino de producción sea el único que nunca se ejercitó.
  - **R28 — un estado lo dice el contenido de un archivo, nunca su presencia.** Un centinela cuya única
    información es existir obliga a que borrarlo sea parte de la resolución, y eso alguien lo olvida: la
    corrida arranca, lee todo el estado y recién ahí muere. Pasó dos veces el mismo día, a 42k tokens por
    vez. Es lo que el WIP de Cauce ya hace bien con `status: IDLE`.

- **R9 dice cuándo se puede quitar lo que está en uso.** Exigía probar la ausencia de lo quitado y nunca
  decía cuándo se puede quitar. Ahora: lo que está en uso no se corta, se depreca, y la marca dice las
  dos cosas que la vuelven una salida y no una etiqueta — qué lo reemplaza, y qué condición permite
  borrarlo. Sin la primera, quien lo usa no sabe a dónde ir; sin la segunda, el deprecado es código
  muerto con un cartel puesto y se queda para siempre.

  Y lo que no llama nadie es otra cosa: se borra. Cortar de golpe rompe a un consumidor que nadie miró;
  deprecar lo que nadie usa cuesta mantener dos caminos para nadie.

- **R10 dice a dónde va lo que se publica, no sólo quién lo autoriza.** La autorización decía si se
  publica y nunca dónde. Ahora: lo que se publica va al repositorio en el que estás trabajando, y si ese
  remoto es un fork, va al fork — con la rama cortada de la suya, porque una rama cortada del principal
  es la antesala de mandarle el PR.

  No se deduce del contexto: que la herramienta resuelva sola el repositorio de origen no es una
  autorización, ni lo son que el cambio «obviamente tenga que llegar ahí» ni que un PR anterior haya ido
  a parar allá. Saltar al principal se pide con todas las letras y para ese caso concreto.

  Es de las pocas sin vuelta atrás: un PR mal apuntado es trabajo publicado en el repositorio de otro
  equipo — lo vieron, les llegó la notificación, y cerrarlo no deshace nada de eso.

- **R8 dice que la prohibición de firmas de IA cubre todo lo que se publica**, no sólo el mensaje del
  commit: el título y el cuerpo del pull request, y los comentarios que se dejen ahí. Y casi nunca es
  algo que alguien tipea — lo agrega la herramienta sola, al final del texto que escribiste—, así que
  cumplirla es revisar la salida antes de publicarla, no acordarse de no escribirla.

- **R17 dice qué cuenta como una condición, que es lo que volvía incontable su umbral.** La barra son
  cinco condiciones de aceptación y nunca decía qué es una. Ahora: una condición es un resultado que se
  puede mirar por separado, no una viñeta. Cinco viñetas que describen el mismo invariante desde cinco
  ángulos son **una**, y contarlas como cinco parte por la mitad lo que era una sola cosa.

  La otra dirección es la cara y la que nadie mira: una frase que promete dos resultados con vidas
  distintas —«valida el pago y manda el email»— son **dos**, y escrita como una el umbral no se entera
  nunca. Contar de menos no dispara nada, y se lee igual que una unidad chica.

  La prueba no pide criterio: si al tachar una condición las otras siguen valiendo, son distintas; si
  tachar una deja a las demás sin sentido, era una sola dicha en partes.

- **R9 ahora pide que la mutación quede escrita, no sólo que se corra.** R9 ya exigía romper, con el
  código puesto, exactamente lo que el caso dice cuidar, y verlo ponerse rojo. Lo que faltaba es que eso
  quedara en la aceptación: una línea con qué se rompe y qué prueba tiene que ponerse roja. Sin ella,
  quien revisa no puede distinguir la mutación que se corrió de la que se pensó, y lo único que le queda
  es volver a correrla — o sea rehacer el trabajo que delegarlo evitaba.

  Y escribirla antes cambia lo que se escribe: una aceptación que tiene que nombrar qué romper deja de
  poder pedir algo que ninguna mutación puede tocar. Si no hay nada que romper, no había propiedad que
  cuidar, y eso se ve al redactarla en vez de al final de la vuelta.

  **Lo que cuesta:** el bloque de reglas que cada agente carga al arrancar pasa de **39,1 a 49,1 KB**.
  Está medido, no estimado, y el umbral del aviso de `check` **no se movió**: sigue en 64 KB, porque lo
  que mide es cuánto agregaste vos, y subirlo para hacerle lugar al piso apagaría justamente eso. Quedan
  ~18 KB de margen antes de que el aviso hable.

### Corregido

- **El aviso de peso mide lo que agregaste vos, no el total.** Comparaba el bloque entero contra el
  umbral, así que el piso del toolkit y tus reglas salían del mismo bolsillo: decía «tu bloque pesa»
  cuando la mitad la habíamos puesto nosotros, y cada regla que Cauce agregaba te achicaba el margen sin
  que nadie lo decidiera. Ahora el umbral se compara contra tus reglas, y la línea dice las dos cosas —
  `114.8 KB en cada agente (93.8 KB propias)`—: el total es lo que paga el agente y lo propio es lo único
  sobre lo que podés hacer algo.

  **Una instancia recién creada ya no puede cruzarlo**, por más que el piso crezca. Antes era una cuenta
  que había que rehacer cada vez que agregábamos una regla.

  El número **no se movió**: sigue en 64 KB, a propósito, porque cambiar qué se mide y cuánto a la vez
  deja sin saber cuál de los dos movió el resultado. Lo que sí quedó medido es que 64 está por debajo de
  lo que una empresa real usa — una instancia medida tiene 93,8 KB de reglas propias — así que elegirlo
  con esa evidencia es lo que sigue.


- **Escribir tu propio `process.md` ya no te deja sin las reglas que no ibas a reemplazar.** El override
  es por nombre de archivo, así que reemplazar «pensar antes de editar» por tu versión se llevaba puesto
  el archivo entero: R16, R17, R20, R21 y R22 dejaban de llegarle a todo agente. `check` te lo decía —lo
  hace desde 0.57.0— y no había nada que hacer al respecto, porque conservarlas exigía copiar su texto y
  una copia deja de recibir las mejoras del `upgrade`.

  Ahora **R16, R20, R21 y R22 viven en `system/runs.md`** —lo que cuesta una corrida, cuándo una medición
  vale, cómo se retoma lo interrumpido y qué no se toca mientras se mide—, un archivo que reemplazar tu
  proceso no toca. `system/process.md` se queda con R1..R4 y R17.

  **No tenés que hacer nada**: el archivo nuevo llega en tu próximo `upgrade`. Si ya sobrescribiste
  `process.md`, esas cuatro reglas vuelven a regir solas, y el aviso de `check` se acorta a lo que de
  verdad reemplazaste. El bloque de reglas pasa de cuatro archivos a cinco y pesa lo mismo: 39,1 KB.

## [0.93.0] - 2026-09-16

### Corregido

- **Declarar un límite ahora lo saca del aviso, en vez de sumarlo.** Si escribiste tus límites como
  viñetas bajo `### Límites` —el camino que 0.92.0 agregó—, `check` los contaba igual como párrafos que
  no llegan a los agentes, y también contaba la frase con la que presentabas la lista. O sea que hacer
  lo correcto **subía** el número: medido sobre un banco, de 4 párrafos avisados pasaba a 6.

  Ahora una viñeta declarada y la prosa que la presenta no entran en el aviso. Lo que sigue entrando es
  la prosa de afuera del bloque, que es lo único para lo que el aviso existe: descontar el bloque entero
  la habría silenciado, porque a un bloque `### Límites` no lo cierra nada más que el próximo
  encabezado y el del molde se extiende hasta donde escribas el tuyo.

- **El aviso te manda al camino declarado y no a imitar una gramática.** Decía que tus párrafos no
  llegan «porque no arrancan con «El runner», «Debe» o «Nunca»». Desde 0.92.0 hay una forma de
  arreglarlo sin imitar nada, y es la que el aviso nombra ahora: sumar el límite como viñeta bajo
  `### Límites`. Los dos caminos siguen valiendo; lo que cambia es cuál se recomienda.

## [0.92.0] - 2026-09-15

### Agregado

- **El ciclo de aprendizaje ya no deja la propuesta mensual en «por definir».** Cada mes, el ciclo
  consolida lo que recomendaron los informes semanales de un cargo y abre su propuesta. Hasta ahora la
  dejaba con el molde intacto en «Cambio propuesto», y eso no es aprobable: nadie firma una intención.
  Medido sobre `2026-09`: de **25** propuestas, **15** se archivaron con el molde y sólo 10 llegaron a
  algo, las que alguien completó a mano.

  El recorrido que escribe el cambio exacto —archivo por archivo, contrastado contra los casos
  adversariales vigentes— ya existía; lo que faltaba era que el ciclo lo corriera. Ahora lo corre, y sólo
  sobre las propuestas que quedaron sin decidir.

  **Sin credencial no se rompe nada**: el paso avisa y el mes queda como quedaba antes, con la rama
  empujada y sus sellos puestos. Lo mismo si la corrida falla. Lo que decide si se te pide una firma
  sigue siendo el documento, no quién lo llenó.

- **Tu proyecto puede declarar sus límites en una lista, y `check` avisa del que no llegue a los agentes.**
  Los límites que tu proyecto amplía o restringe viven en `organization/workspace.md`, y de ahí viajan al
  preámbulo de cada subagente. Hasta ahora se reconocían **por cómo arrancaba el párrafo** —«El runner»,
  «Debe», «Nunca»—, que es la gramática del molde: un límite escrito de cualquier otra forma, que es como
  lo escribiría cualquiera, no llegaba a ningún agente y nada lo decía. La lista salía más corta y se leía
  igual de completa.

  Ahora esa sección trae un `### Límites` con una viñeta por límite:

  ```markdown
  ### Límites

  - En `api/` no se tocan migraciones sin aprobación de quien administra la base.
  ```

  **Lo que ya tenías escrito sigue contando**: la gramática vieja se lee igual, así que no hay nada que
  migrar. Y lo que no entra por ninguno de los dos caminos ya no se pierde callado — `ops check` lo
  reporta citando el párrafo, para que sepas cuál de tus límites se quedó afuera.

  El ejemplo del molde viene comentado a propósito: un límite de ejemplo que se obedece es peor que
  ninguno, porque nadie lo escribió y todos lo cumplirían.

- **Reanudar una tarea dejó de pagar la fase que no tiene nada que hacer, y la corrida dice cuándo lo
  hizo.** Una tarea que para antes de Commit —una revisión que pidió algo, un gate en rojo— deja su plan
  en disco con los pasos tildados. Al relanzar, el recorrido entraba igual a Build: el agente releía el
  WIP, comprobaba el disco y contestaba que no había nada pendiente. Hacía lo correcto; lo que costaba
  era haberlo llamado — **893.000 tokens sobre tres corridas de una sola tarea**, medido.

  Ahora, si el WIP no tiene pasos pendientes, esa llamada no se hace y la fase se anuncia como
  `Build (reanudado)`, que viaja al resultado de la corrida y a la entrada de DONE. Antes una corrida
  reanudada se veía idéntica a una que construyó salvo por el costo, así que comprobar qué se reutilizó
  exigía abrir la salida cruda y sumar tokens a mano.

  **Lo que no cambia es qué se revisa.** No se saltea la fase, se saltea la llamada: Review, Verify y QA
  siguen mirando el diff real que quedó en disco, venga de la corrida que venga. Y si tu instancia no
  emite ese dato, todo se comporta como antes.

- **Una raíz puede declarar qué rutas lee su puerta, y un archivo sucio que el gate no va a abrir deja de
  forzar la copia del índice.** Al commitear, `verify` corre sobre el árbol cuando árbol e índice
  coinciden y materializa el índice en un temporal cuando difieren. Hasta ahora alcanzaba **cualquier**
  archivo sin trackear para materializar: un README a medio escribir, un `tsconfig.json` que dejó otra
  sesión, o el propio `planning/.ops-approval` que el bloqueo te manda crear para aprobar unas rutas.

  Ahora, junto a `verify`, una raíz puede declarar `scope`:

  ```json
  "workspaceRoots": [
    { "name": "web", "path": "apps/web", "verify": "pnpm build",
      "scope": ["src/**", "package.json", "tsconfig.json"] }
  ]
  ```

  Con eso, sólo materializa si alguna ruta que difiere cae dentro de ese alcance. Los patrones son
  relativos a la raíz —el `scope` de `apps/web` habla de `src/**`, no de `apps/web/src/**`— y admiten
  `*` dentro de un segmento, `**` cruzando segmentos y `?` por un carácter.

  **Sin `scope` declarado no cambia nada**: cualquier delta sigue forzando la copia, que es el
  comportamiento de siempre. El campo es opcional y no hay que adoptarlo.

  Lo que se gana no es tiempo. Dentro de la copia `node_modules` viaja por **enlace**, y eso rompe
  cualquier build de Turbopack sin salida del lado del proyecto: ahí un archivo ajeno al commit no cuesta
  segundos, deja el gate sin poder pasar. Declarar el alcance es lo que lo destraba.

  Dos bordes que valen la pena saber. Un directorio sin trackear llega colapsado —git reporta `extra/` y
  no dice qué hay adentro—, así que se materializa igual si algún patrón apunta hacia adentro de él. Y
  una ruta que no cuelga de **ninguna** raíz declarada también cuenta: puede ser de la instancia o de un
  servicio que nadie declaró, y suponer que no importa es justo lo que este campo existe para evitar.

### Corregido

- **El motor se encuentra aunque lo hayas instalado un nivel arriba de tu instancia.** Si corrés
  `npm install @ingeniomaps/cauce` en la carpeta de tu empresa y después `cauce init ops`, la instancia
  queda adentro y el motor arriba. Ese es el árbol que el propio comando sugiere, y hasta ahora
  `automation check` devolvía **nueve errores** sobre un motor que estaba instalado, cada uno mandándote a
  correr `npm install` otra vez un nivel más abajo — o sea a bajar una segunda copia del paquete.

  Ahora se busca también en la raíz que tu `ops.config.json` declara, que es la misma que el motor ya usa
  para decidir dónde instalar el runner. Si tu `<empresa>-ops` tiene su propio `node_modules`, ése sigue
  ganando y nada cambia.

  **No se adivina el layout, se lee el declarado**: no se sube por el árbol como hace Node —en un monorepo
  podría encontrar un motor de otra versión, en silencio— ni se mira si hay repositorio git, porque tener
  la instancia sin versionar es un uso legítimo.

  Vale para los tres caminos, no sólo para el CLI: el shim que lanza cada guard y el bridge de Antigravity
  repiten esa búsqueda porque corren antes de poder cargar el motor, y los tres se actualizaron juntos.

## [0.91.0] - 2026-09-15

### Agregado

- **`ops contract <ops-root> [--json]`: el contrato de tu proyecto sin pasarlo por un modelo.** Devuelve lo
  que un recorrido necesita antes de la primera fase —cómo se llama el proyecto, dónde puede escribir, con
  qué se verifica, qué límites rigen y qué formatos exige `planning/`—, derivado de `ops.config.json`,
  `AGENTS.md`, `organization/workspace.md` y `planning/PROTOCOL.md`.

  Los diez campos salen de parsear, así que el comando no inventa nada y cuesta cero tokens. `autobuild` los
  derivaba con un agente que leía esos cuatro archivos y los transcribía; el comando existe para que deje de
  hacerlo, aunque el recorrido todavía no lo use.

  **Falla en vez de contestar a medias.** Si falta uno de los cuatro archivos, o si `AGENTS.md` o
  `PROTOCOL.md` perdieron la sección de la que sale el contrato, se niega nombrando cuál y manda a correr
  `ops upgrade`, que es quien los repone: entregar límites vacíos es peor que parar, porque un límite que no
  llega se lee igual que uno que no existe. Que `organization/workspace.md` no declare excepciones **no** es
  un error: es tuyo y puede no tenerlas.

### Corregido

- **La protección que evita que un gate te borre el `node_modules` no estaba puesta.** `verify` corre los
  gates sobre una copia del índice con el `node_modules` enlazado a tu proyecto, y le pedía a pnpm que no
  sincronizara dependencias antes de correr el script. Se lo pedía con un nombre que pnpm ignora: sus
  ajustes se leen del entorno con el prefijo `pnpm_config_`, no con el de npm. La petición viajaba y se
  descartaba en silencio desde 0.75.0, que es cuando entró.

  Con pnpm 11, donde esa comprobación viene encendida, se nota al commitear un cambio de `pnpm-lock.yaml`:
  pnpm decide reinstalar, la reinstalación **empieza borrando** el directorio de módulos —el tuyo, por el
  enlace— y sin TTY aborta con `ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY`. Los gates vuelven en poco más
  de un segundo, así que se leen como una suite en rojo que no lo es.

  Si te está pasando, alcanza con actualizar. Lo que **no** corresponde, aunque el bloqueo lo sugiera, es
  aprobar las rutas o `OPS_SKIP_VERIFY=1` —las dos commitean sin haber corrido el gate— ni `CI=true`, que
  desarma justamente la confirmación con la que pnpm frena antes de purgar.

- **`plan-first` decía que no tenías plan cuando lo que pasaba es que no veía el tuyo.** El guard busca el
  WIP de tu id, y ese id sale del árbol donde corre el proceso mientras no exportes `CAUCE_RUNNER`. Con la
  instancia al lado de dos repositorios, el mismo cambio se frenaba o pasaba según el directorio desde el
  que saliera la llamada — y el bloqueo decía «WIP está en IDLE» con el plan escrito y a la vista, así que
  mandaba a escribir de nuevo algo que ya existía.

  Ahora distingue las dos causas: si hay un plan bajo otro id, lo nombra con su tarea y te manda a volver a
  ese id —`ops runners <planning>` los lista— o a montar el tuyo con `ops worktree <planning> <tarea>`. Y
  **deja de ofrecerte aprobar la ruta**, porque con un plan a la vista eso escribe por vos que el cambio no
  es trabajo de ninguna tarea, que es falso y queda en el registro.

  Lo que no cambia: el plan de otro runner sigue sin autorizarte a escribir. Si van a trabajar en paralelo,
  cada agente necesita su árbol —`git worktree`, que es lo que `ops worktree` prepara—, y ahí el problema no
  existe porque no comparten archivos.

## [0.90.0] - 2026-09-15

### Corregido

- **Registrar un piso de cobertura dejó de poder borrar los demás.** `coverage:update` reconstruye el
  registro entero desde lo que midió, y se negaba a escribir sólo cuando no había medido **nada**. Una
  corrida que arrancaba y moría temprano dejaba un lcov corto: los archivos que no llegó a medir
  desaparecían del registro, y la actualización salía en 0 anunciando éxito. El registro mutilado se lee
  igual que uno sano, y con él la puerta de cobertura deja de cuidar lo que perdió.

  Ahora se niega cuando un archivo que **sigue en disco** y **tenía piso** no fue medido, nombrando
  cuáles. Retirar un archivo del motor sigue funcionando igual —ya no está en disco, así que no cuenta
  como pérdida— y no hace falta ninguna excepción que recordar.

  **No tenés que hacer nada.** Si al correr `coverage:update` te frena, la corrida quedó corta: revisá que
  la suite haya terminado antes de volver a intentarlo.

- **Las pruebas que juzgan el repositorio dicen cuándo no pueden correr, en vez de fallar.** Cinco de
  ellas preguntan qué está trackeado —lo que define al árbol es el índice de git, no lo que haya en el
  disco— y en una copia sin `.git` fallaban con mensajes que hablaban del repositorio: «la suite declara
  0 pruebas y el piso es 505». Se leían como defectos reales y no lo eran.

  Importa porque R23 manda ejercitar en una copia lo que las pruebas invocan, y la copia barata
  —`git ls-files | tar`, `git archive`— no lleva `.git`. Ahora se saltean declarando qué les falta.

  **No tenés que hacer nada.** Si corrés la suite sobre un tarball o un árbol sin historia, vas a ver
  cinco `skipped` con su razón donde antes veías cinco rojos.

- **El ciclo de aprendizaje ya no te deja una rama muerta por cada informe que se mergea solo.** Cuando un
  informe declara `propone: no`, su PR se cierra con auto-merge y su rama quedaba viva para siempre: una
  por cargo y por semana. La bandera que debía borrarla estaba puesta desde la versión anterior y nunca
  hizo nada — `gh` borra «after merge», y con auto-merge el comando termina al *armar*, horas antes de que
  el merge ocurra.

  Ahora lo hace `delete-merged-branch.yml`, que escucha el cierre del PR y borra la rama cuando el merge
  ya pasó. Vale para cualquier PR mergeado de este repositorio, no sólo los del ciclo: el mismo hueco
  dejaba vivas las ramas de release. Una rama de un fork no se toca, y un PR cerrado sin mergear tampoco
  —ahí la rama es trabajo de alguien—.

  **No tenés que hacer nada.** Si tu instancia acumuló ramas `automation/*` de informes ya mergeados, se
  borran a mano una vez; las nuevas ya no se quedan.

- **La puerta del toolkit dejó de dar verde con pruebas en rojo.** `npm run ci` encadena cinco
  comprobaciones y ninguna miraba el veredicto de la suite: entraba sólo por la de cobertura, que ignora
  a propósito el exit de `node --test` para poder registrar un piso cuando la puerta está en rojo. Esa
  tolerancia valía para los dos caminos, así que seis pruebas fallando terminaban en exit 0 — y eso es lo
  que corre en CI y lo que exige `prepublishOnly`.

  Ahora la tolerancia es del modo que la necesita: al **registrar** (`coverage:update`) el exit de la
  suite sigue sin decidir, y al **comprobar** una suite en rojo corta la corrida antes de mirar
  cobertura, diciendo que el rojo es de las pruebas y no de los pisos.

  **No tenés que hacer nada, pero puede que veas rojo donde antes veías verde:** si tu proyecto tenía
  pruebas fallando, la puerta las muestra en vez de taparlas. Ninguna versión publicada de este toolkit
  llegó a npm con la suite en rojo — se comprobó clonando y corriendo las cinco últimas.

## [0.89.0] - 2026-09-14

### Agregado

- **`check` te avisa de una condición de aceptación que no se va a poder comprobar, antes de que el
  recorrido la construya.** Verify corre antes que Commit y que Done, así que una condición que pide ver
  el commit, el reclamo, `done/` o la evidencia registrada pide algo que todavía no existe cuando se la
  mira. El recorrido ya lo frenaba —y hace bien—, pero recién en Verify: en la corrida que originó esto
  fueron **1,2 M de tokens y once agentes** para terminar con el trabajo hecho, sin commit y sin poder
  cerrar la tarea.

  Ahora sale de `check`, cuesta un regex sobre la cola y corre en los cuatro carriles, incluidos los que
  saltean Ready. Avisa y no falla.

  El aviso dice qué hacer: esa cláusula va en `tests:`, `qa:` o `commit:` de la entrada de DONE, que ya la
  exigen, así que repetirla en la aceptación no agrega garantía sino un bloqueo. **Y si de verdad va ahí,
  se declara y deja de avisarse**: `(fuera de verify: <razón>)` dentro de la propia condición, la misma
  salida explícita que `(sin partir: …)` y que `n/a — razón`. Se juzga condición por condición, así que
  declarar una no exime a las demás.

- **Una regla propia puede declarar sobre qué superficie rige, y entonces se nombra sin cargarse.** Todo
  lo que `planning/rules/` contiene viaja en el contexto de arranque de **cada** agente —medido: sólo las
  cuatro reglas que trae Cauce son 38,3 KB por agente—, así que una regla de dos páginas que importa en
  una tarea de cada cien se leía cien veces.

  Ahora una regla puede llevar `aplica: <superficie>` en su frontmatter. El bloque que escribe
  `automation install` la lista —`- ruta (aplica: pagos)`— en lugar de importarla: pesa una línea en vez
  de su archivo entero. **Sigue rigiendo igual**: `ops context` la devuelve entre las reglas del proyecto
  y el recorrido se la nombra a cada agente que toca código, para que quien trabaje sobre esa superficie
  la lea antes de planificar o construir.

  **Sin el campo, la regla se carga como siempre, y por eso este cambio no te pide hacer nada.** El
  default es ése a propósito: una regla que no se leyó no existe, así que apartarla del arranque es una
  decisión del proyecto y nunca algo que se deduzca. El valor lo elegís vos —`pagos`,
  `infraestructura`— y lo único que tiene que lograr es que quien lo lea sepa cuándo le toca. Conviene
  para lo que es de un dominio acotado; lo que gobierna cómo se trabaja se paga y se carga.

- **`automation install` te dice lo que ese bloque va a pesar, y `check` avisa cuando ya pesa demasiado.**
  Escribir una regla propia encarece todas las corridas futuras de todos los agentes, y hasta acá no lo
  decía nadie: había que sumar los tamaños a mano después de una corrida cara para enterarse.

  Al instalar, una línea declara cuántos archivos carga el bloque, cuánto pesan y cuáles son los dos más
  grandes —`el bloque de reglas carga 4 archivo(s), 38.3 KB en cada agente (las más grandes: conduct.md,
  process.md)`—. Y `check` lo repite como advertencia cuando el total pasa de **64 KB**, que es el umbral
  elegido para dejar unos 26 KB de reglas propias por encima del piso que trae Cauce: más abajo avisaría
  en toda instancia recién creada y se apagaría por ruido el primer día.

  **El número va en KB y no en tokens** a propósito. Los bytes los mide el motor y se pueden comprobar;
  la equivalencia en tokens depende del modelo y del tokenizador, y una cifra estimada en una salida que
  se cita para decidir vale menos que una exacta. Como referencia, en el repositorio de Cauce esos 38,3 KB
  rondaron los 10 K tokens medidos una vez, pero eso es una observación y no un factor de conversión.

  Una regla declarada con `aplica:` no suma en ninguno de los dos números: es justamente lo que se apartó
  del arranque, y contarla haría que declararla no sirviera de nada.

### Corregido

- **El ciclo de aprendizaje ya no te pide una firma por una propuesta que no decide nada.** El documento
  se compone en dos tiempos: `learn --proposal` lo arma desde los informes y los veredictos, y
  `/agent-propose` escribe el cambio concreto. El ciclo automático corre el primero y abría el PR ahí
  mismo, con «Cambio propuesto» todavía en el molde — así se gastaron siete firmas el 2026-09-14, y
  ninguna de esas propuestas se pudo aplicar.

  Ahora el paso que compone dice si el documento quedó sin decidir y el que publica lo lee. **La rama se
  empuja igual**, porque lleva el sello de los informes que el ciclo consumió y perderlo haría entrar el
  mismo material el mes siguiente; lo que se posterga es sólo el PR. La corrida lo deja anotado con el
  nombre de la rama y qué falta para abrirlo.

  Y `ops learn <cargo> --proposal` lo dice también cuando lo corrés a mano: «sin cambio decidido: falta
  correr agent-propose antes de que esto se pueda firmar». Sin esa línea el archivo se ve terminado y no
  lo está.

- **En `mode: sidecar`, `ops context` ya no te esconde tu propio plan.** El id de un runner sale de
  `CAUCE_RUNNER` y, sin ella, del repositorio git desde el que corrés el comando. Cuando la instancia es su
  **propio repositorio** —lo habitual: `<empresa>/` y `<empresa>-ops/` al lado— hay dos raíces, y el mismo
  agente recibe un id distinto según desde cuál invoque. El reclamo queda guardado con uno, el plan se
  escribe bajo ese mismo, y la sesión que pregunta desde el otro recibe `WIP idle` y `TAKEN … vos, desde
  otro runner`: el plan existe, con sus pasos ya tildados, y nadie lo nombra.

  Ahora `context` lo nombra. Cuando una tarea tomada es tuya y hay un plan escrito bajo el otro id, agrega
  una línea `PLAN` con el archivo y con el `export CAUCE_RUNNER` que lo recupera. Y **`ops claim` imprime
  ese id al tomar la tarea**, que es el momento en que se conoce: hasta ahora sólo lo hacía `ops worktree`,
  y en sidecar no se crea ningún worktree, así que el dato no quedaba escrito en ninguna parte.

  El id sigue sin ser estable, y eso no cambia: derivarlo de quién sos en vez de dónde corrés le devolvería
  a cada agente de la máquina la tarea del otro, que es peor que no encontrar la propia. Lo que cambia es
  que dejó de ser mudo.

- **`automation install` te dice dónde quedaron los recorridos cuando no es donde estás parado.** En
  sidecar el adaptador se instala en la carpeta de la empresa y no en el repo ops desde el que corrés el
  comando. Eso es correcto y no cambió —es donde el runner ve el código—; lo que engañaba era la salida.
  Avisaba «el runner se abre en `<raíz>` — ahí queda su configuración» para un archivo, y a continuación
  nombraba los recorridos en relativo, `.claude/workflows/autobuild.js`, que leído desde el repo ops apunta
  a una carpeta vacía. Cerraba en «adaptador operativo (0 advertencia(s))», y todo era cierto.

  Lo que cuesta es que un recorrido se invoca **por nombre**, y el nombre lo resuelve la sesión contra la
  carpeta en la que se abrió: una sesión abierta en el repo ops no encuentra ninguno y recibe un «no
  existe» que se lee como instalación fallida. Ahora la salida nombra ese directorio con su raíz puesta y
  desde dónde hay que abrir la sesión para que los vea.

- **Los recorridos dejan de depender de la carpeta desde la que se abrió la sesión.** La raíz que traen
  escrita —la que usan para nombrar el planning, la configuración y el CLI en cada comando que le dictan a
  un agente— era **relativa** a la carpeta donde se abre la herramienta. Eso vale mientras el agente esté
  parado ahí, y nadie lo promete: una sesión abierta en el repo ops resolvía `<empresa>-ops/planning`
  contra su propio directorio, el tramo se duplicaba, y el comando contestaba que el planning no existe.
  En una corrida real de `autobuild` costó una vuelta entera de la fase Claim.

  Ahora esa raíz es **absoluta** y la escribe `automation install`, que es quien la conoce. Ninguna
  consigna depende ya de dónde arranque la sesión.

  **Lo que tenés que saber**: un archivo que lleva la raíz escrita se rompe si movés el proyecto de
  carpeta. Se repara con `automation install` del runner, que es lo que ya hacía falta cuando el wiring
  quedaba apuntando a otro lado. Y como los recorridos vienen del paquete, esto llega con el `upgrade`:
  después conviene reinstalar el adaptador para que la raíz nueva quede escrita.

- **Agregar un archivo al motor ya no deja la cobertura en un callejón.** La puerta de pisos fallaba
  diciendo «corré `npm run coverage:update`», y ese comando no podía completarse **mientras el piso
  faltara**: corría la suite entera bajo `set -e`, la suite incluía la prueba que estaba en rojo por el
  piso que faltaba, y el script moría antes de llegar a la línea que lo registra.

  Ahora las corridas de medición no deciden por su código de salida —existen para producir el archivo de
  cobertura— y en su lugar se exige que ese archivo traiga contenido, que es lo único que distingue una
  suite que falló de una que no llegó a arrancar. Y al abortar ya no se borran las mediciones que sí se
  completaron, así que se puede retomar desde donde quedó.

  **Y medir cero dejó de anunciarse como éxito**: `coverage-files.js --update` con un archivo de
  cobertura vacío imprimía «piso registrado» y salía en 0, dejando el registro vacío sin que nada lo
  dijera. Ahora se niega y explica por qué.

## [0.88.0] - 2026-09-14

### Corregido

- **Una revisión de propuesta que nadie decidió ya no puede cerrar el ciclo.** Cuando un cargo tiene su
  propuesta del período aplicada y aparece material nuevo, el ciclo abre una **revisión**, y su sección
  «Cambio propuesto» llega con el texto del molde hasta que alguien escribe el cambio. Ese texto empezaba
  con «Una revisión suele no ser aditiva…», y el criterio que detecta una propuesta sin decidir reconoce lo
  que empieza con «Por definir» o «Pendiente» — así que a las revisiones no las veía.

  El resultado era que una revisión se podía firmar, mergear y **sellar como aplicada** con el molde
  adentro: el ciclo llegaba a su estado terminal sin que nadie hubiera decidido nada, y el cargo quedaba
  habilitado a abrir la siguiente como si hubiera aprendido algo. Pasó con siete cargos el 2026-09-14.

  Ahora el molde de una revisión empieza por «Por definir», igual que el de una propuesta nueva y el de un
  recorrido. Y como los documentos ya escritos no cambian, el criterio reconoce además el molde viejo tal
  cual: completo y sin editar. Si lo continuaste para decir qué cambia —que es como se redacta— cuenta
  como decidido y se aplica igual; lo que se frena es el molde intacto.

  **Si tenés revisiones firmadas con el molde adentro, no se van a poder aplicar**: sellar falla con
  «todavía no la decidió nadie» y el documento se queda en `proposed`. Escribiles el cambio y volvé a
  firmarlas, o archivalas: archivar sigue siendo una salida incluso para una que ya firmaste, y cómo
  queda registrada lo explica la entrada de abajo.

- **Los encabezados que un cargo usó en su respuesta ya no se vuelven secciones de su propuesta.** El
  hallazgo de un caso en rojo viaja con el contraste entero —eso es deliberado y no cambió—, y adentro
  venía la respuesta con la estructura que el cargo eligió. Sus `##` quedaban al mismo nivel que
  «Hallazgos» o «Cambio propuesto» y pasaban por secciones del documento: dos propuestas del 2026-09
  llegaron con once y siete secciones que no eran suyas.

  Ahora bajan un nivel al componerse. No se pierde ni una línea del detalle, y las secciones del documento
  vuelven a ser sólo las del molde — que importa además porque sellar y aplicar ubican «Cambio propuesto»
  por su encabezado.

- **Una propuesta que nadie decidió ya no deja al cargo sin salida ni le bloquea la siguiente.** Al frenar
  el sellado de lo que no decide nada quedó un encierro: una propuesta firmada sobre el molde no se podía
  aplicar —no decide nada— **ni** archivar —estaba firmada—, y mientras tanto su cargo no volvía a
  proponer. La única salida era editar el frontmatter a mano, que es justo lo que este ciclo existe para
  no pedirte.

  Tres cosas cambian. **Archivar acepta una propuesta firmada cuando no decide nada**: lo que la guarda
  cuida es que no se tire una decisión, y ahí no hay ninguna. **Una archivada deja de retener el
  período**, así que el cargo puede proponer de nuevo — antes sólo una aplicada lo liberaba. Y **el
  registro dice cuál de las dos cosas pasó**: archivar lo que alguien miró sigue diciendo «se miró y no
  cambia nada», y archivar un documento que nadie llegó a llenar dice que se archivó sin decidir, en el
  historial del cargo y en la salida del comando.

  Una propuesta firmada **que sí decide** se sigue sin poder archivar: ahí la firma autorizó algo y lo
  que corresponde es aplicarla.

## [0.87.0] - 2026-09-14

### Corregido

- **El aviso de credenciales sin dueño mira el `.env.example` entero y ya no sus primeras cuarenta
  variables.** `check` te avisa de las credenciales que ningún contrato declara, y el escaneo cortaba en
  cuarenta variables por servicio **antes** de mirar qué era cada una. En un servicio grande eso no
  recortaba una lista: apagaba el análisis, y qué credencial se veía terminaba dependiendo de en qué línea
  del archivo había caído.

  El riesgo estaba escrito como aceptado y sin medir desde que el tope existe. Medirlo contra una instancia
  real lo convirtió en dos nombres: un servicio con 61 variables tenía tres credenciales pasado el corte y
  **dos que ningún contrato declaraba**, en las líneas 43 y 60. Lo único que salía era el aviso de que había
  cortado.

  Ahora el tope recorta lo que se **lista** y no lo que se mira: las credenciales se buscan sobre el archivo
  entero, y `scan` sigue mostrando cuarenta y diciendo cuántas más hay. Si tenés un servicio con más de
  cuarenta variables, contá con avisos nuevos en el próximo `check` — son credenciales que ya estaban y que
  nadie estaba mirando.

## [0.86.0] - 2026-09-12

### Corregido

- **Tu guard propio ya no se pierde al actualizar una instancia vieja.** Si tu instancia es anterior al
  registro de entregas —no tiene `.cauce/manifest.json`— y tenés un guard tuyo con un nombre que el paquete
  hoy trae, `upgrade` lo reemplazaba sin nombrarlo, salía con 0 y después lo anotaba como entregado por
  Cauce. Los tres pasos juntos hacían la pérdida silenciosa **e** irrecuperable: para cuando la notabas, el
  registro decía que ese archivo siempre había sido nuestro. Y la última línea de la corrida afirmaba lo
  contrario de lo que había pasado — «planning, organization y todo lo propio quedaron intactos».

  Ahora, sin registro para esa ruta, la duda se resuelve del lado del que se vuelve: el archivo se conserva,
  `upgrade` lo nombra y `upgrade --check` sale con 1 **antes** de tocar nada. Si el que querés es el del
  paquete, `--force` lo reemplaza diciéndolo, como ya hacía.

### Agregado

- **`check` te avisa de una fila de `HUMAN_ACTIONS.md` que figura resuelta y que ningún commit registró.**
  Una decisión que nadie dejó escrita es una aprobación autoservida, y la puerta barata no la miraba: el
  recorrido la rechazaba recién en Ready, con Triage, Pick, Claim y Decompose ya pagados. En la corrida que
  lo destapó fueron tres paradas y 1,21 M de tokens, con `check` en verde las tres veces.

  Avisa, no falla —rechazar enunciados por heurística frenaría trabajo legítimo— y **se calla cuando no hay
  con qué contestar**: sin repositorio, o con el archivo todavía sin commitear, no dice nada.

- **`check` muestra lo que autorizaste en el chat y sigue vigente.** Desde 0.83.0, lo que un guard te deja
  pasar queda concedido para el resto de la sesión, así no te vuelve a preguntar lo mismo en cada mensaje.
  Eso está bien, y no se veía en ninguna parte: por una línea olvidada en `planning/.ops-approval` `check` te
  avisaba, y por una concesión que vale toda la sesión no decía nada. Ahora lista las dos. Lo que concediste
  trabajando en otra instancia no se le cuenta a ésta.

- **Podés acotar una concesión diciendo hasta cuándo vale, y Cauce te hace caso.** Si al autorizar algo
  escribís «mientras dure la tarea t-014», esa concesión deja de valer en cuanto esa tarea ya no sea la de
  tu WIP, en vez de durar toda la sesión. Ya lo escribías y se perdía: de esa frase sobrevivía la ruta y el
  acote se tiraba.

  Hace falta la palabra `tarea` o `task` —«mientras dure la tarea t-014», «only for task t-014»—, porque sin
  ella no hay contra qué comparar. Lo que no se reconoce no se pierde: vale lo de antes, la sesión entera.

- **Lo que concedés en el chat deja un rastro local en `planning/.grant-log`.** Una línea por concesión, con
  la fecha, el alcance, la vía y la sesión; **no guarda lo que escribiste**. Es el mismo molde que
  `planning/.push-log`: sólo agrega, y sirve para contestar meses después quién autorizó qué.

  **Si tu instancia ya existía, agregale a mano esta línea a tu `.gitignore`:** `planning/.grant-log`.
  `upgrade` no puede tocar ese archivo porque es tuyo, así que la línea sólo llega a las instancias nuevas;
  sin ella, el rastro te va a aparecer en `git status`. Es lo mismo que pasó con `planning/.push-log`.

- **`check` te avisa si git no está ignorando los rastros locales de Cauce.** Son tres —
  `planning/.verify-log`, `planning/.push-log` y `planning/.grant-log`— y no deben viajar: son evidencia de
  una corrida tuya, de tu máquina. La línea que los cubre la trae el `.gitignore` que se escribe **al crear**
  la instancia, y `upgrade` no lo toca porque ese archivo es tuyo y puede tener líneas propias. Así que una
  instancia anterior a cada rastro nuevo se quedaba sin su línea para siempre y el archivo aparecía en
  `git status` listo para commitearse.

  Ahora `check` lo dice y nombra las rutas, que es lo que hay que pegar. Pregunta si git **los ignora**, no
  si la línea está escrita: si ya los cubrís con una regla propia, no te molesta. Sin repositorio se calla.

  **Si tu instancia ya existía, pegá estas tres líneas en tu `.gitignore`** — o dejá que `check` te diga
  cuáles te faltan:

  ```
  planning/.verify-log
  planning/.push-log
  planning/.grant-log
  ```

### Cambiado

- **La guía dejó de pedirle al agente que borre una autorización que no escribió él.** `AGENTS.md` decía que
  «borrarla es parte de terminar» sin distinguir la línea que el agente pidió para una operación de la que
  dejaste puesta vos a propósito. Ahora sólo se borra la primera.

## [0.85.0] - 2026-09-12

### Corregido

- **Gobernanza ya no te interroga cuando sos vos quien pide el trabajo.** El guard que frena un commit que
  toca reglas, ADR, `automatization/`, `engine/` o contratos de cargo te pedía **nombrar cada archivo** en
  tu mensaje, o contestar «dale», o pegar las rutas en `planning/.ops-approval` — y te lo cobraba en cada
  commit. Ahora, con una persona conduciendo el turno, no pregunta nada.

  Lo que **no** cambia, y es el punto del guard: sigue frenando igual cuando el que commitea es un
  subagente, un recorrido de Cauce o CI. Ahí nadie está conduciendo, que es exactamente para lo que existe.

  Sus dos vecinos —`verify` y `dependencies`— **siguen preguntando**, y la diferencia no es quién pidió el
  commit: esos frenan porque algo está mal —una verificación que falla, un manifiesto sin su lockfile— y
  callarlos porque hay alguien hablando sería taparte un rojo. `OPS_GOVERNANCE_OVERRIDE` sigue existiendo
  para el caso sin persona.

  **Qué cambia para vos**: nada que hacer. Si venías aprobando commits de gobernanza uno por uno, o pegando
  rutas a mano para poder trabajar, eso se terminó.

## [0.84.0] - 2026-09-12

### Corregido

- **Una frase en inglés que prohíbe ya no autoriza.** Cauce reconoce si le negás algo antes de dejarlo
  pasar, y en inglés sólo entendía `don't`: «the tool doesn't read the .env» o «we can't read the .env»
  nombraban el archivo, traían un verbo y ninguna negación reconocida, así que **una frase que prohíbe
  terminaba autorizando**. Entran ahora las contraídas con auxiliar —`doesn't`, `isn't`, `can't`,
  `won't`, `wasn't`, `wouldn't`, `shouldn't`…—, con o sin apóstrofo.

  **Qué cambia para vos**: nada que hacer. Si trabajás en inglés, negar algo ahora se respeta; lo que antes
  pasaba por no entenderse, se frena. Una frase como «I can't tell if the .env is right» deja de autorizar
  y cuesta un «dale», que es el mismo costo que el español ya tenía.

- **La entrada de DONE dice contra qué reglas se revisó.** `autobuild` exige que Review nombre las reglas
  contra las que revisó —si no las nombra, la corrida para—, pero esa lista quedaba sólo en el registro de
  la corrida, que no sobrevive. Ahora va también en la línea `review=` de `DONE.md`, que es lo que queda.

  **Qué cambia para vos**: nada que hacer. Al auditar una entrega vieja vas a poder decir contra qué se
  revisó sin depender del registro de aquella corrida.

- **Lo que pedís vos ya no se frena sin ofrecerte ninguna salida.** Siete de los guards que pueden frenar
  nunca consultaban si lo habías pedido: no tenían variable, ni línea que pegar en
  `planning/.ops-approval`, ni «dale». Entre ellos estaban las reglas que cubren trabajo de todos los días
  —`git reset --hard`, `git clean -f`, `git checkout -- .`, `docker compose down`, `docker system prune`—
  y los que frenan escribir una credencial, un archivo generado, un snapshot del sincronizador o el motor
  instalado. Pedirlos con todas las letras no servía de nada: se frenaban igual que si los hubiera
  decidido el agente por su cuenta.

  Ahora preguntan lo mismo que el resto de los guards: si lo pediste en el chat pasa, y si no, el bloqueo
  te dice qué se frenó y un «dale» lo aprueba. De paso se cierra una asimetría que nadie había decidido:
  **leer** una credencial se podía aprobar desde 0.80.0 y **escribirla** no.

  **Lo que no cambia, y es a propósito**: reescribir historia publicada —`git push --force`,
  `git commit --amend`—, el borrado de disco, `rm -r` sobre la raíz, el home o el directorio padre, y
  `git add -A`. Ésos no se abren ni pidiéndolos, porque R8 y R23 no admiten excepción configurable.

  **Qué cambia para vos**: nada que hacer ni que reinstalar. Lo que antes te obligaba a exportar una
  variable para poder trabajar —o directamente no tenía salida— ahora se destraba diciéndolo. El permiso
  es angosto a propósito: vale para el comando entero tal como lo escribiste, así que otra bandera es otro
  comando y se vuelve a preguntar.

## [0.83.0] - 2026-09-11

### Agregado

- **Autorizar en el chat ahora se dice como se dice.** Un guard dejaba pasar lo que pedías con un verbo de
  acción —«leé el .env», «usá esa ruta»— y se seguía frenando si lo decías autorizando: «autorizo la
  lectura del .env» no traía ninguna palabra que Cauce reconociera, así que había que repetirlo con otras
  palabras, contestar «dale», o escribir la línea a mano. Entran las formas con que una persona concede, en
  español y en inglés: `autorizo`, `autorizá`, `te autorizo`, `permito`, `apruebo`, `habilito`,
  `authorize`, `allow`, `grant`, `approved`.

  Lo que **no** cambia es la línea de siempre: nombrar algo no lo autoriza. Los sustantivos que nombran el
  acto —«la lectura del .env», «¿hace falta autorizacion?»— siguen sin autorizar nada, porque aparecen
  igual en una pregunta que no pide nada. Y negar sigue negando, también con las palabras nuevas.

  **Qué cambia para vos**: si autorizás algo diciendo que lo autorizás, pasa a la primera. Antes eso
  costaba una vuelta, y a veces terminaba en la variable que apaga el guard para toda la sesión.

- **El permiso de push ya no se lo puede escribir el agente.** `runner.allowPush` y
  `runner.pushToLiveBranches` deciden qué push se publica y viven en `ops.config.json`, que hasta ahora
  ningún guard miraba: el bloqueo que dice «esto lo decide una persona» se levantaba editando ese mismo
  archivo, con la herramienta de escritura o con un `sed -i`. Lo cierran dos guards nuevos: `ops-config`
  compara lo que se va a escribir contra lo que hay en disco y frena si cambia alguna de esas dos llaves,
  y `ops-config-shell` frena toda escritura por shell sobre el archivo, porque un comando no dice con qué
  va a quedar.

  **Qué cambia para vos**: el resto de `ops.config.json` —`workspaceRoots`, `writableOutsideRoots`,
  `migrations`, lo que sea— se sigue editando igual que siempre; sólo esas dos llaves pasan a necesitar una
  persona, que las pone editando el archivo ella o pidiéndoselo al agente en el chat nombrando
  `ops.config.json`. Por shell el archivo queda cerrado entero: ese cambio va por la herramienta de
  escritura y con el archivo completo, que es lo único que el guard puede comparar. Si tu runner registra
  los guards de a uno en vez de por grupo, corré `automation install` para que aparezcan
  `guard-ops-config.sh` y `guard-ops-config-shell.sh`.

- **Un push aprobado deja constancia en `planning/.push-log`.** Cuando Cauce deja pasar un `git push` por
  una aprobación —la orden en el chat, un «dale», o la línea exacta en `planning/.ops-approval`— anexa una
  línea con la fecha en que se autorizó, el remoto, la rama, por qué vía y la sesión. Por
  `runner.allowPush` no anota nada: esa autorización ya está escrita en `ops.config.json`.

  El archivo sólo agrega, no guarda el texto de lo que escribiste, y si no se puede escribir no frena el
  push. La fecha se llama `authorizedAt` y no `at` a propósito: el hook corre antes del comando, así que la
  línea dice que el push **se autorizó**, no que se haya publicado.

  El anillo «Local» de `planning/delivery/teamwork.md` lo nombra junto al resto de lo que no viaja por git.
  Ese archivo lo mantiene Cauce, así que `upgrade` te lo reemplaza: si lo editaste, mirá el diff antes.

  **Qué cambia para vos**: si necesitás responder quién autorizó un push y cuándo, ahora hay de dónde
  sacarlo, en la máquina donde corrió la sesión —el rastro dice la sesión y la vía, no el nombre de la
  persona: Cauce no tiene identidad de usuario—. Las instancias nuevas lo traen gitignoreado; si ya tenés
  una, agregá `planning/.push-log` a tu `.gitignore`.

- **Lo que un recorrido deja en el INBOX dice ahora de qué vía entró.** Cada línea que `autobuild`, `flow` y
  `onboard` le pasan al agente que escribe viene con su procedencia al final —`(autobuild · t-012 ·
  2026-09-11)`, `(flow · planning/reports/2026-09-11-alta.md · 2026-09-11)`, `(onboard · 2026-09-11)`—: el
  recorrido, la unidad de la que salió y la fecha. La arma el recorrido y el pedido dice que se copie tal
  cual, en vez de confiar en que el agente se acuerde de escribirla. El formato de una entrada no cambió ni
  el molde tampoco, así que `ops tree`, `check` y `context --json` siguen leyendo exactamente lo mismo.

  **Qué cambia para vos**: nada que hacer. Al recorrer el INBOX vas a ver el origen en lo que se escriba de
  acá en adelante; lo que ya tenés escrito no lo gana, y una entrada que escribas a mano sigue sin él,
  porque el INBOX es tuyo. Si en algún lado recortás el ancho de esas líneas, contá entre 20 y 60
  caracteres más por entrada: el tope de 240 pasó a medir la línea entera, así que lo que cede es el texto
  del hallazgo y nunca el origen.

### Cambiado

- **Lo que autorizás en el chat no alcanza a los gates de un commit.** Lo que un guard deja pasar porque
  vos lo pediste sigue valiendo mientras dure la sesión, con dos excepciones: publicar, y los gates de un
  commit —`governance`, `verify` y `dependencies`—, que preguntan cada vez. Lo que autoriza un commit no
  dice nada del siguiente, porque el índice ya es otro. Por el mismo motivo, nombrar `ops.config.json` una
  vez ya no deja escribir `runner.allowPush` ni `runner.pushToLiveBranches` por el resto de la sesión: ahí
  también se pregunta cada vez.

  **Qué cambia para vos**: si commiteás varias veces seguidas tocando gobernanza, con un gate en rojo o con
  un manifiesto sin su lockfile, vas a decir «dale» en cada commit en vez de una vez por sesión. Lo que no
  es un gate de commit —leer un `.env`, reescribir una migración, borrar una prueba— sigue valiendo toda la
  sesión.

### Corregido

- **Un bloqueo ya no te ofrece pegar una ruta que el shell no expandió.** Cuando el comando nombra la
  credencial a través de una variable —`cat ops/$O/.env.infisical`—, Cauce no puede saber qué archivo es:
  esa variable la expande tu shell, que es otro proceso. Antes el bloqueo ofrecía igual esa línea para
  `planning/.ops-approval`, y pegarla no servía: sólo volvía a valer si el comando se repetía escrito
  igual, y dejaba de valer apenas alguien escribía la ruta de verdad. Con la forma `${O}` era peor, porque
  la línea ofrecida ni siquiera era el archivo que el comando lee. Ahora no la ofrece, dice por qué y qué
  hacer.

  **Qué cambia para vos**: lo que un bloqueo te ofrece pegar es siempre una línea que después funciona. Si
  armás la ruta con variables, escribila entera en ese comando —o contestá «dale», que destraba igual—.

- **Lo que autorizás en el chat ya no deja de valer con el mensaje siguiente.** Hasta 0.82.0 el permiso
  duraba un mensaje: pedías leer el `.env`, el agente lo leía, y apenas escribías cualquier otra cosa
  —«gracias, seguí»— el mismo archivo volvía a frenarse. Ahora lo que un guard deja pasar porque vos lo
  pediste queda anotado y sigue valiendo mientras la sesión siga. Lo revoca decirlo: «no toques el .env» lo
  saca, y lo revocado no vuelve solo.

  El permiso sigue siendo angosto: vale para el ítem tal como el guard lo nombra, no cruza de sesión y no
  alcanza a un subagente ni a un recorrido de Cauce. Y **no alcanza a lo que vuelve a tener consecuencia
  cada vez**: publicar y los gates de un commit, que preguntan siempre —ver la entrada de abajo—. Volver a
  leer lo que ya se leyó no agrega consecuencia; volver a publicar, o commitear otra vez con otro índice,
  sí.

  **Qué cambia para vos**: si trabajás con credenciales o con rutas que los guards vigilan, alcanza con
  pedirlo una vez por sesión en lugar de repetir la frase o contestar «dale» a cada rato. Si querés cortar
  un permiso antes de cerrar la sesión, decilo nombrando el archivo.

- **El agente no puede escribir en tu aprobación una línea que vos no pediste.** `planning/.ops-approval`
  sólo lo podía escribir el agente si vos nombrabas el archivo en el chat, y ahí se terminaba el control:
  pedías agregar `src/login.js` y podía escribir `push origin main`, que es la línea que autoriza publicar
  en la rama viva. Ahora lo que se escribe se compara línea por línea con lo que pediste: pasan las que
  nombraste, y cualquier otra frena la escritura mostrándote cuál era. Las que ya estaban en el archivo no
  hay que volver a nombrarlas, y quitar líneas no pide permiso. Por shell —un `echo >>`, un `sed -i`— el
  archivo queda cerrado: un comando no dice con qué va a quedar, así que no hay contenido que comparar.

  **Qué cambia para vos**: si le pedís al agente que te agregue rutas, nombralas en el mismo mensaje
  —«agregá `src/login.js` y `src/altas.js` a `.ops-approval`»—; si frena, te dice exactamente qué línea
  quería escribir y un «dale» aprueba esa y nada más. Vos seguís editando el archivo a mano como siempre,
  que ningún guard mira. Esto venía desde 0.82.0, así que si actualizás desde ahí, cerrás de paso esa vía.

## [0.82.0] - 2026-09-11

### Cambiado

- **`allowPush: true` ya no publica en la rama viva ni desde un subagente.** La llave dejaba pasar cualquier
  push, `main` incluido, y el de un subagente igual que el tuyo. Ahora no alcanza a `main`, `master` ni a la
  rama por defecto del remoto, y un subagente no publica con ningún permiso. Tampoco la alcanza una orden en
  el chat.

  **Qué cambia para vos**: si publicabas en la rama viva con `allowPush: true`, agregá
  `"pushToLiveBranches": ["main"]` en `runner` de `ops.config.json`, o pegá `push origin main` en
  `planning/.ops-approval` para un push puntual.

- **R10 y el párrafo de publicación de `AGENTS.md` se reescribieron.** Los dos decían que el push se
  comprueba contra `runner.allowPush` y nada más; ahora dicen lo que de verdad comprueba el motor: esa llave
  —que no llega a la rama viva sin `runner.pushToLiveBranches`, ni a un subagente— o la orden que la persona
  da en el chat nombrando el remoto y la rama. Los dos archivos los mantiene Cauce, así que `upgrade` los
  reemplaza enteros.

  **Qué cambia para vos**: nada que hacer. Si tu equipo cita R10 en algún documento propio, el texto que
  baja ahora es más largo y dice qué sostiene un guard y qué sostiene una persona.

### Corregido

- **Nombrar algo en el chat ya no lo autoriza: hay que pedirlo.** Desde 0.81.0, lo que nombrabas en el chat
  pasaba los guards aunque no lo pidieras: «¿para qué sirven las credentials?» dejaba leer `credentials`, y
  «el .env tiene algo raro?» dejaba leer el `.env`. Ahora la frase que lo nombra tiene que pedir una acción
  —«leé», «abrime», «borrá», «read»…—, y la negación sigue frenando como antes.

  **Qué cambia para vos**: si pedís algo sin un verbo que Cauce reconozca, el agente te dice qué se frenó y
  con un «dale» pasa.

- **`upgrade` ya no pisa un guard tuyo que se llama como uno nuevo del paquete.** Cuando una versión empezaba
  a traer un nombre que ya usabas en `automatization/hooks/`, lo reemplazaba sin decir nada: 0.81.0 lo hizo
  con `guard-chat.sh`. Ahora lo conserva y lo avisa; `--check` lo cuenta, y `--force` lo reemplaza diciéndolo.

  **Qué cambia para vos**: si ves «ya existía y Cauce no lo entregó», renombrá tu guard y repetí `upgrade`;
  mientras tanto, el guard del paquete con ese nombre no está instalado.

- **Un guard propio ya no aparece como «editado localmente».** `upgrade` registraba todo lo que había en
  `automatization/hooks/`, así que editar un guard tuyo lo volvía una edición del molde: `upgrade --check`
  salía con 1, `check` lo contaba como congelado, y `--force` anunciaba que lo descartaba sin tocarlo. Ahora
  sólo registra lo que trae el paquete, y el primer `upgrade` suelta lo que una versión anterior registró de
  más.

  **Qué cambia para vos**: nada que hacer; tus guards dejan de aparecer en esos avisos.

- **El aviso de credenciales sin dueño avisa sólo credenciales.** `check` listaba toda variable que el
  proyecto declaraba y ningún contrato nombraba: en un proyecto real fueron ciento ocho nombres de build, y
  la única credencial quedó en «y 1 más». Ahora cuenta las que tienen nombre de secreto —`…_PASSWORD`,
  `…_TOKEN`, `…_KEY`, `…_DSN`…—, dice en qué servicio está cada una y dónde se escribe su dueño, y avisa
  aparte cuando un servicio pasó el tope de variables que se revisan.

  **Qué cambia para vos**: la declaración de secretos y la configuración de una integración ahora también
  rechazan una clave que termine en `_key`, `dsn` o `credentials`, igual que ya rechazaban `…token`. Si
  tenés una, cambiala por la referencia a una variable de entorno. Y una credencial con nombre de
  configuración —`STRIPE_LIVE`— no aparece en el aviso: el criterio es el nombre.

- **Una variable ya no se da por cargada porque otra la contiene en su nombre.** Con `API_SECRET_ROTATION`
  escrita en `organization/workspace.md`, `API_SECRET` dejaba de avisarse aunque nadie la nombrara. Ahora
  cuenta sólo el nombre entero.

  **Qué cambia para vos**: puede aparecer en el aviso una credencial que antes quedaba tapada; escribí su
  dueño y se va.

- **Un push que pedís en el chat ya no se frena.** Con `allowPush: false`, `git push` se frenaba aunque lo
  hubieras pedido con todas las letras. Ahora pasa si tu mensaje nombra el remoto y la rama —«subí feat/x a
  origin», `git push origin feat/x`—, y si no los nombra, el agente te dice qué se frenó y con un «dale»
  pasa ese push y ningún otro. `planning/.ops-approval` también acepta la línea `push <remoto> <rama>`, tal
  cual y sin patrones. Un `git push origin +rama` ahora se frena como el `--force` que es.

  **Qué cambia para vos**: para publicar una rama de trabajo alcanza con pedirlo nombrando remoto y rama, o
  con contestar «dale».

- **La sesión carga las reglas que rigen tu proyecto, no las cuatro de `system/` fijas.** El archivo de
  instrucciones de cada runner importaba siempre `planning/rules/system/`: cargaba la regla que tu proyecto
  había sobrescrito y ninguna de las tuyas. Ahora `automation install` pone las vigentes —las tuyas y las de
  `system/` que no sobrescribiste— en Claude, Gemini, Codex y Antigravity, y un `CLAUDE.md` que editaste
  recibe el bloque nuevo sin perder lo tuyo. Como `upgrade` no reinstala, `check` y `automation doctor`
  avisan cuando lo instalado quedó atrás. `AGENTS.md` dice ahora que, donde una regla tuya y una del
  sistema chocan, rige la tuya.

  **Qué cambia para vos**: después de actualizar, o cada vez que escribas o sobrescribas una regla,
  reinstalá el adaptador (`make install-claude`, o el de tu runner); `check` te dice cuándo hace falta. Si
  tu `CLAUDE.md` es tuyo y no tiene nada de Cauce, poné las marcas `<!-- cauce:reglas inicio -->` y
  `<!-- cauce:reglas fin -->` donde quieras el bloque y reinstalá.

- **`autobuild` trabaja con las reglas de tu proyecto y ya no cita una que retiraste.** Los subagentes
  planificaban, construían y revisaban sin conocer tus reglas, y la fila que pedía partir una tarea citaba
  R17 aunque la hubieras retirado. Ahora `context` dice qué reglas rigen —línea `RULES`, campo `rules` en
  `--json`—, cada fase recibe esa lista, y Review tiene que nombrar contra cuáles revisó o la corrida para
  con `review-unbacked`. Ningún prompt cita una regla por número.

  **Qué cambia para vos**: nada que hacer.

- **Los recorridos ya no llenan el INBOX sin medida.** `autobuild` volcaba en Propuestas todo lo que Review
  anotaba sin frenar, entero, y `flow` y `onboard` escribían sin tope ni forma. Ahora cada recorrido escribe
  como mucho tres entradas por tarea o por corrida, de una línea, con la forma del molde y sin repetir un
  nombre que ya está. Lo que pasa del tope no se pierde: `autobuild` lo cuenta en el `done/` de la tarea,
  `flow` lo deja en el informe y `onboard` lo nombra al cerrar. Y `check` avisa, sin fallar, cuando
  `INBOX.md` pasa de 300 líneas.

  **Qué cambia para vos**: nada que hacer. Si querés otro umbral para el aviso, poné
  `"inbox": { "warnLines": N }` en `ops.config.json`. El molde de `INBOX.md` ya no pide la evidencia dentro
  de una propuesta; tu `INBOX.md` no se toca.

- **La recurrencia que recorre el INBOX viene activa, y descomentar los ejemplos de `RECURRING.md` ya no
  rompe `check`.** Una instancia nueva trae la fila `inbox`, trimestral, que vence por primera vez tres meses
  después de `init`. Las filas de ejemplo declaran el `(service: …)` que `check` exige. Y `check` avisa una
  entrada del INBOX que se llama como una tarea de `done/`: probablemente se promovió y quedó ahí.

  **Qué cambia para vos**: `RECURRING.md` es tuyo y `upgrade` no lo toca. Para tener la recurrencia en una
  instancia que ya existe, agregá debajo de la tabla
  `| inbox | trimestral | AAAA-MM-DD | Recorrer el INBOX entero. _Aceptación: ninguna viñeta queda sin decisión de promover, dejar o borrar._ (service: planning) |`
  con una fecha futura: `Desde` es cuándo vence por primera vez, no el día en que escribís la fila.

- **Dos repositorios que terminan en una carpeta con el mismo nombre ya no salen con el mismo nombre.** Con
  varias raíces declaradas, el inventario nombraba cada servicio por la **carpeta** de su raíz, así que
  `../gouduet/keycloak` y `../hypixo/keycloak` salían los dos `keycloak` en `check`, en `onboard` y en
  `scan`, y no había con qué saber de qué repositorio era cada credencial. Ahora los nombra el `name` que
  cada raíz declara en `ops.config.json`, y `check` avisa cuando dos raíces se llaman igual, diciendo
  cuáles son las dos.

  **Qué cambia para vos**: si dos raíces de tu `ops.config.json` comparten `name`, `check` te lo avisa y
  sigue pasando —renombrá una cuando puedas—. Y donde el `name` y la carpeta difieren, los servicios pasan
  a listarse con el `name` —el que escribiste vos— en vez de con la carpeta; `onboard --json` sigue
  emitiendo `roots` como lista de rutas.

## [0.81.0] - 2026-09-11

### Cambiado

- **Lo que pedís en el chat ya no te frena.** Los guards veían la herramienta y nada de la conversación,
  así que frenaban igual lo que pediste con todas las letras y lo que el agente decidía solo. Ahora, en
  Claude Code, Codex y Gemini, un hook registra tu mensaje y los guards lo leen: si nombraste lo que se
  iba a frenar —«borrá la prueba de altas», «reescribí la migración 004»—, pasa; si no, el agente te dice
  qué se frenó y un «dale» aprueba exactamente eso. `plan-first` deja de pedir un plan cuando el cambio lo
  pediste vos. Lo que el agente hace por su cuenta, dentro de un recorrido o en un subagente, se sigue
  frenando igual.

  **Qué cambia para vos**: corré `automation install` para que tu runner registre el hook nuevo
  (`UserPromptSubmit` en Claude y Codex, `BeforeAgent` en Gemini); en Codex, confialo con `/hooks`. Tu
  mensaje se guarda en el temporal del sistema, no en el repositorio, y sólo el último de cada sesión.
  Antigravity sigue con el archivo de aprobación.

- **Leer una credencial por shell se frena en los cuatro runners, y en Claude ya no te frena a vos.** Un
  guard nuevo, `secrets-shell`, frena el comando que muestra una credencial —`cat .env`, `head`, `grep`,
  `source`, una redirección `<`, un `node -e`— en Claude, Codex, Gemini y Antigravity. Hasta ahora sólo
  Claude lo frenaba, con reglas nativas `permissions.deny` que tampoco dejaban pasar tu pedido; esas reglas
  se retiran, y como con el resto de los guards, si lo pedís en el chat, pasa.

  **Qué cambia para vos**: corré `automation install`; en Claude quita las reglas que Cauce había puesto y
  conserva las tuyas. Si querés un bloqueo nativo total, escribilo como regla propia.

### Corregido

- **El bloqueo de `verify` cita la prueba que falló también con jest, vitest, mocha y pytest.** Con esas
  herramientas citaba el archivo, el resumen o —con `pytest -v`— una prueba verde cuyo nombre decía «error».
  Ahora reconoce cómo marca cada una la prueba que falla, comprobado contra la salida real de cada una.

  **Qué cambia para vos**: el mensaje apunta a la prueba que hay que mirar.

- **El agente ya no puede escribirse la aprobación.** `planning/.ops-approval` se escribía con la misma
  herramienta que usa el agente y ningún guard lo miraba, así que una aprobación suya destrababa igual que
  una tuya. Los guards de límites lo frenan ahora —por la herramienta de escritura y por el destino evidente
  de un comando—, salvo que se lo hayas pedido nombrándolo en el chat.

  **Qué cambia para vos**: nada si el archivo lo editás vos. Si el agente lo escribía por pedido tuyo,
  ahora se lo pedís en el chat.

- **En sidecar, el bloqueo dice en qué archivo aprobar.** Mandaba a `planning/.ops-approval`, y desde la
  carpeta del workspace, donde se abre la sesión, ése no es el `planning/` de la instancia: pegar donde
  decía no destrababa nada. Ahora nombra la ruta que el guard lee —`acme-ops/planning/.ops-approval`—; en
  modo embebido sigue diciendo `planning/.ops-approval`.

  **Qué cambia para vos**: nada que hacer; el mensaje dice dónde.

## [0.80.0] - 2026-09-10

### Agregado

- **Leer una credencial también se frena, y las identidades declaradas cuentan como credencial.** Un guard
  nuevo, `secrets-read`, corre en la herramienta de lectura de Claude (`Read`) y de Gemini (`read_file`) y
  frena los mismos archivos que `secrets` frena al escribir. Esos archivos ahora incluyen las identidades
  `source: file` de `organization/secrets.json`, que antes pasaban por no tener nombre de credencial. En
  Claude, además, la instalación agrega reglas `permissions.deny` `Read(...)` que el propio Claude aplica
  también a `cat`, `head`, `tail`, `sed` y redirecciones.

  **Qué cambia para vos**: el agente deja de poder leer `.env`, claves y tokens conocidos; `.env.example`
  sigue legible. Si una lectura hace falta, aprobá la ruta en `planning/.ops-approval`. Corré
  `automation install` para que tu `.claude/settings.json` reciba las reglas: se suman a las tuyas. No es un
  límite de seguridad —un `grep -r` o un script propio siguen leyendo—, y en Codex y Antigravity no hay guard
  de lectura.

- **Un proveedor de integraciones propio, sin tocar Cauce.** El registro de `integrations/config.json`
  ya tenía un campo `adapter` que nadie leía: ahora `"adapter": "./adapter.js"` carga el adaptador de la
  empresa desde `integrations/<nombre>/`, e `integration enable` lo conecta sin exigir un molde de Cauce.
  El adaptador declara `contract: 1` y las tres funciones del contrato, y `check` rechaza el que no
  cumpla.

  **Qué cambia para vos**: nada si sólo usás Jira. Para otra herramienta, `integrations/README.md` de tu
  instancia trae el recorrido.

- **Un contrato de secretos compartido entre repositorios, y un chequeo sin red.** Si tus servicios
  usan un gestor de secretos, los scripts y workflows que lo conectan terminan copiados en cada
  repositorio, y el arreglo que alguien hizo en uno no llega a los demás. `organization/secrets.json`
  declara qué cuenta, qué identidad y qué archivos comparte cada servicio, y
  `node tools/ops.js secrets check .` compara cada copia contra la canónica que guarda la instancia:
  dice cuál quedó atrás y el `cp` que la pone al día. También falla si una credencial vive dentro de un
  repositorio o si la declaración guarda un valor en vez de una referencia.

  **Qué cambia para vos**: nada si no lo usás. Para adoptarlo, `organization/README.md` trae el formato
  y el recorrido. Cauce no se conecta a ningún gestor ni trae adaptadores: lo que habla con el gestor
  sigue siendo tuyo. Si tus proyectos tienen instancias separadas, el chequeo sólo compara dentro de
  cada una: para que un esqueleto y sus derivados se midan contra lo mismo, van como raíces de la misma
  instancia.

### Corregido

- **`init` rechaza un runner mal escrito sin crear la instancia.** Con `--runner none` —el valor es
  `ninguno`— o una `--integration` que no existe, `init` escribía la instancia entera y recién después
  salía con error: el código de salida decía que no había pasado nada y el segundo intento encontraba la
  carpeta creada. Ahora valida los dos valores antes de escribir.

  **Qué cambia para vos**: nada con un valor correcto. Con uno mal escrito, el destino queda como estaba.

- **El bloqueo de `verify` cita la prueba que falló.** Mostraba la primera línea de la salida que dijera
  «error», y en un reporte de pruebas ésa puede ser una verde cuyo nombre lo dice. Ahora, con la salida de
  `node --test` —spec y TAP— y de `go test`, cita la primera prueba en rojo; con otras herramientas sigue
  buscando por palabra, pero ya no elige una línea marcada como verde.

  **Qué cambia para vos**: el mensaje apunta a la prueba que hay que mirar.

- **Cada bloqueo con salida angosta dice qué líneas pegar.** Aprobar en `planning/.ops-approval` sólo
  funciona si la ruta está escrita en la forma que ese guard coteja —absoluta la de un `Write`, relativa
  al repositorio la de un commit—, y el mensaje decía «escribí esa(s) ruta(s)» sin nombrarlas. `verify`
  y `dependencies` ni siquiera mostraban la línea. Ahora cada bloqueo imprime las líneas exactas, y
  pegarlas tal cual destraba ese mismo bloqueo.

  **Qué cambia para vos**: si una aprobación no pegaba y terminabas exportando la variable del guard,
  pegá lo que dice el mensaje.

- **`plan-first` deja de frenar la configuración y los archivos de la instancia.** Frenaba
  `ops.config.json` —justo el archivo que el límite de raíces manda a editar para declarar una ruta— y,
  en sidecar, también `AGENTS.md`, `CLAUDE.md`, `package.json` y `.gitignore` de la instancia, como si
  fueran producto. Ahora producto es el código de una raíz declarada: la instancia sidecar no lo es
  aunque viva dentro de su raíz (`..`), y lo que queda fuera de toda raíz —lo declarado en
  `writableOutsideRoots`— tampoco. En embedded, el `package.json` de la raíz sigue siendo producto.

  **Qué cambia para vos**: si aprobabas esas rutas a mano en `.ops-approval` o exportabas
  `OPS_PLAN_FIRST_OVERRIDE` para poder tocarlas, ya no hace falta.

- **`verify` deja afuera del índice de su copia lo que enlaza.** Cuando el árbol difiere del índice, el
  guard corre los gates sobre una copia del índice con lo ignorado enlazado —`node_modules`, `.env`—. Si
  tu `.gitignore` escribe ese directorio con barra final (`node_modules/`), el enlace entraba al índice de
  la copia, y un gate que recorre lo trackeado —`git ls-files`— lo recibía como si fuera parte del commit.

  **Qué cambia para vos**: si una suite que pasa a mano fallaba bajo `verify` sólo cuando tenías archivos
  sin trackear, podía ser esto.

## [0.79.0] - 2026-09-10

### Corregido

- **La fila que registra una parada se escribe antes de parar.** Cuando ningún plan sobrevive a la
  crítica, el recorrido pide que la tarea quede en `HUMAN_ACTIONS.md` con su motivo —así `context` deja
  de ofrecerla y alguien la ve—. Esa escritura se lanzaba y el recorrido volvía en la línea siguiente,
  así que el agente que la escribía se quedaba a mitad de camino: en una corrida real el resumen contó
  diez agentes y el registro nueve. La parada se informaba bien y el disco no la tenía.

  Ahora se espera. Y en las tres paradas que dejan fila —plan rechazado, tarea que no está lista,
  criterio que no dice qué aserciar— si el agente no contesta, el detalle de la parada lo dice: el
  motivo sigue siendo el que la causó, y se agrega que la fila hay que escribirla a mano.

  **Qué cambia para vos**: relanzar después de un plan rechazado deja de repetir la planificación
  entera, porque la tarea queda registrada. Si venías viendo que `context` te ofrecía otra vez la tarea
  que acababa de rechazarse, era esto.

- **El guard de migraciones frena por haber viajado, no por estar en disco.** Escribir una migración son
  dos pasos —crearla y completarla— y el segundo se bloqueaba: el chequeo preguntaba si el archivo
  existía, que no es la pregunta. Un stub de hace dos segundos y una migración publicada hace un año
  daban la misma respuesta, y **ninguna herramienta lo esquivaba** —`Write`, `Edit` y `MultiEdit` se
  bloqueaban igual—, así que lo único que quedaba a la vista era apagar el guard entero, incluida la
  protección contra SQL destructivo.

  Ahora se le pregunta a git si el archivo está en `HEAD`. En el índice no alcanza: un archivo apenas
  agregado no viajó a ninguna parte. Y sin repositorio con el que contestar —un proyecto sin git— se
  conserva la conducta anterior en vez de dejar pasar, que es el lado correcto para equivocarse cuando
  no se puede saber.

  **Qué cambia para vos**: completar una migración recién creada deja de frenarse. El mensaje del
  bloqueo pasa a decir el hecho que lo sostiene —que está en el historial, o que existe y acá no hay con
  qué saberlo— y a nombrar cómo aprobar esa ruta puntual en `planning/.ops-approval`, que es lo que el
  bloqueo hermano ya hacía. Si tenías `OPS_MIGRATIONS_OVERRIDE=1` puesto para poder trabajar, ya no hace
  falta y conviene sacarlo: apagaba también lo que sí te cuida.

## [0.78.0] - 2026-09-10

### Corregido

- **Las puertas que revisan un recorrido leen los literales de regex.** Tres pruebas analizan cada
  workflow como texto —que su `meta` sea un literal puro, que no llame a nada inexistente y que no use un
  nombre que no declaró— y ninguna sabía qué es un regex. La barra escapada que cierra uno deja un `//`
  literal, que se leía como el arranque de un comentario: a partir de ahí se perdía el resto de la línea,
  en silencio. Dos recorridos del catálogo tenían cuatro líneas así.

  **Qué cambia para vos**: nada en lo que recibís, y sí en lo que se puede confiar. Un recorrido propio
  con un regex adentro ahora se revisa entero en vez de a medias, y un `meta` con un regex o con una
  interpolación —que hasta acá pasaba siempre, porque esa regla no podía dispararse— se rechaza.
- **El nombre de un banco de pruebas no trae nada que la prueba no haya pedido.** La suite aísla lo que
  cada prueba mide filtrando la salida de `check`, y esa salida empieza con la ruta del banco: cuando el
  sufijo aleatorio del directorio terminó en `adr`, un error ajeno entró a una prueba de plantillas de
  ADR y la puso en rojo. Falla una vez cada muchas corridas y se lee como un hipo del entorno.

  **Qué cambia para vos**: nada — es la suite del toolkit, no algo que recibas. Va acá porque una puerta
  que falla al azar enseña a relanzar en vez de a mirar, y eso sí llega a quien la usa.

- **`autobuild` lee `blocked` por su valor y no por su verdad.** El campo del contexto es un vocabulario
  de tres valores —vacío, `awaiting-review` y `blocked-on-human`—, y el recorrido lo probaba con un `if`
  a secas. Dos consecuencias, y la que más costaba no era intermitente: una cola trabada por acciones
  humanas frenaba con el motivo del checkpoint de hito y mandaba a mirar `AWAITING_REVIEW.md`, un archivo
  que en ese escenario no existe. Ahora cada bloqueo para con su motivo y nombra su archivo:
  `AWAITING_REVIEW.md` para el checkpoint, `HUMAN_ACTIONS.md` —con las tareas trabadas— para la cola.

  La otra es la que se ve al azar: el agente que transcribe el contexto a veces entrega el vacío como la
  cadena de dos comillas, y eso frenaba la corrida en la fase 1 sin que hubiera nada que resolver. Medido
  sobre siete corridas de una instancia real: 3 de 24 lecturas llegaron así, y una de cada siete cayó en
  la única lectura que consulta el campo. Las formas equivocadas del vacío se desenvuelven antes de leer.

  Y lo que no está en el vocabulario ya no se adivina en ninguna de las dos direcciones: no se sigue como
  si no hubiera bloqueo ni se inventa cuál es — para diciendo que el contexto llegó fuera del
  vocabulario. El esquema declara los tres valores, igual que `lane`; eso documenta el contrato, y lo que
  sostiene el arreglo es la lectura.

## [0.77.0] - 2026-09-10

### Corregido

- **`check` dice cuánto del trabajo que entró al repositorio quedó registrado.** Cuenta, por raíz de
  trabajo, los commits que **ninguna entrada de DONE nombra** desde la última tarea cerrada. Es un aviso,
  no un error: es un hecho del pasado que no se arregla editando nada.

  El número no existía y sacarlo pedía cruzar a mano los `commit:` contra la historia de cada
  repositorio. Hecho así sobre una instancia real: **312 commits, 75 registrados**. Y el desglose de los
  que faltaban no era trabajo suelto — **69 `feat` y 51 `fix` de 173**—, ni tareas que produjeron varios
  commits: de 80 entradas, una sola registra más de uno.

  La ventana arranca en la última tarea cerrada y no en la primera, y esa decisión es la que hace que
  sirva: contar toda la historia da una deuda que nunca baja y se lee como decorado. Así vuelve a cero
  cada vez que el flujo se cierra, y lo que queda a la vista es la deriva de ahora.

  **Lo que te pide algo**: si el número no es cero, ese trabajo no está en `planning/` y `OPS-001` dice
  que ahí está la fuente de verdad. Qué hacer con él es tuyo — el aviso no propone cerrar nada
  retroactivamente, porque escribir entradas de memoria sería inventar la evidencia que el registro
  existe para tener.

- **Un plan que ninguna crítica aprueba deja de reintentarse a ciegas.** `autobuild` cortaba con
  `plan-rejected` sin dejar rastro, así que relanzar repetía **la corrida entera** sobre la misma tarea:
  Ready y Decompose la volvían a dejar pasar —su criterio no cambió y la tarea tampoco— y Critique la
  volvía a rechazar. Medido en dos corridas consecutivas: idénticas, **9 agentes y ~780 k tokens cada
  una, sin escribir una línea de código**.

  Ahora la tarea queda registrada en `HUMAN_ACTIONS.md` con el motivo, y eso hace las dos cosas de una
  vez: `context` deja de ofrecerla —una acción pendiente saca esa tarea de la cola y ofrece la siguiente,
  sin frenar la corrida— y alguien ve la fila. Vale igual para `plan-blocked`, que cortaba igual de mudo.

  **Lo que te pide algo**: esa fila es tuya. Lo que pide R17 es mirar si la unidad son dos resultados con
  vidas distintas y partirla, o dejarla entera con la razón escrita.

- **El guard de migraciones deja de ser inerte en todo proyecto cuyas migraciones no sean `.sql`.** Filtraba
  por ruta **y por extensión**, así que en TypeORM, Prisma, Django, Rails o Alembic no miraba nada: ni
  frenaba el SQL destructivo, ni protegía una migración existente de ser reescrita. Y no lo decía — aparecía
  cableado y en verde. Medido en una instancia real: **64 migraciones `.sql` cubiertas y 409 TypeORM `.ts`
  invisibles**.

  Ahora la extensión la declara el proyecto:

  ```json
  "migrations": { "extensions": ["sql", "ts"] }
  ```

  El default sigue siendo `["sql"]`, así que nada cambia para quien no lo declare — y ampliarlo por
  nuestra cuenta reintroduciría el falso positivo que 0.63.0 vino a cerrar: un archivo de lenguaje que
  menciona `DROP TABLE` en un comentario. La ruta la sigue fijando el motor: `migrations/`, `migration/`
  o `migrate/`.

  Y la descripción del guard dejó de prometer de más: nombra el campo que amplía la cobertura y el
  default de quien no lo declara.

  **Lo que te pide algo**: si tus migraciones no son `.sql`, declaralas. Hasta que lo hagas, ese guard
  no las mira — y ahora `automation list-hooks` te lo dice.

- **La tabla que dice qué ruta aprobar cuando un guard te frena estaba ofreciendo la salida ancha para el
  freno más común.** `plan-first` —el que exige un plan escrito antes de cambiar el producto— no figuraba
  entre las aprobaciones por ruta, y sí en la tabla de variables que apagan un guard **para toda la
  sesión**. Quien se topaba con ese bloqueo encontraba documentado `OPS_PLAN_FIRST_OVERRIDE=1` y no la
  aprobación acotada, que es la vía recomendada.

  Ahora está la fila, con la pregunta que va antes —**¿esto es trabajo de una tarea?**— y con lo que
  cuesta: si lo es, la salida no es aprobar la ruta sino escribir el WIP con su plan; si no lo es —un
  typo, un umbral que corregís de paso—, aprobar es la respuesta correcta, y ese cambio entra **sin
  entrada de DONE**, así que `planning/` no lo registra.

  **Lo que te pide algo**: si venías apagando ese guard con la variable, la aprobación por ruta hace lo
  mismo para el archivo que vas a tocar y se apaga sola en cuanto el conjunto cambia.

### Cambiado

- **R17 gana un tercer disparador de división: un plan que ninguna crítica aprueba.** Los dos que ya
  tenía —cinco condiciones de aceptación, cuatro horas de esfuerzo— miran la unidad **escrita**. Éste mira
  lo que pasó al intentarla, y por eso es la evidencia más directa de las tres y la única que no se puede
  tener de antemano.

  Lo que lo hace fácil de perder es que llega **después** de que las otras dos dieron el visto bueno, y
  las dos acertaron: la aceptación era concreta y las condiciones no cruzaban el umbral. Una unidad puede
  estar bien escrita y no ser planificable, y eso sólo se sabe habiéndolo intentado.

  **Lo que te pide algo**: es una regla del sistema, así que baja a tu `planning/` en el próximo
  `upgrade`. Como las otras dos, dispara una revisión y no una partición automática.

### Agregado

- **R23: un borrado se lee resuelto antes de correrlo, y sólo alcanza lo desechable.** Antes de destruir
  —`rm -rf`, un borrado recursivo, un `DROP`, un `reset --hard`— se resuelve el objetivo y **se lee la
  ruta final**, no la variable que la contiene. El destino tiene que colgar de algo desechable, y eso se
  comprueba: la raíz de un repositorio, un directorio de trabajo, el home de nadie y `/` no lo son.

  Con dos cosas que la regla dice y que son menos obvias. Las pruebas **no montan nada bajo el home**: un
  banco ahí pone la carpeta personal de quien las corre dentro del alcance de todo lo que la suite borra.
  Y **decidir se separa de destruir**: la función que juzga si algo se puede borrar no borra, así que
  probarla con `/` o con la raíz de un repositorio no puede destruir nada. Mezcladas, la prueba que
  ejerce la defensa tiene que apuntarle a rutas reales, y ahí apagar la defensa **es** el desastre.

  **Lo que te pide algo**: es una regla del sistema, así que baja a tu `planning/` en el próximo
  `upgrade`. Si tenés pruebas o scripts que borran, la pregunta que contesta es «¿de dónde salió esta
  ruta?», no «¿está bien escrita esta línea?».

## [0.76.0] - 2026-09-10

### Corregido

- **Un comando que no encuentra tu planning lo dice, en vez de contestar un hecho sobre un directorio que
  no existe.** `context` y `tree` ya lo hacían desde 0.71.0; los otros nueve no. `claim` contestaba «no
  está en BACKLOG», `release` «no está tomada por nadie», `evidence` «DONE no tiene ninguna entrada»,
  `runners` «ningún runner tiene trabajo abierto» — todos hechos concretos sobre lo que no pudieron leer.
  **Cuatro de ellos salían con exit 0**, así que un script veía éxito.

  El daño no es el mensaje sino lo que induce: en una corrida real, `claim` dijo «no está en BACKLOG»
  sobre una tarea que **sí** estaba, y el recorrido mandó a una persona a promover lo único que ya estaba
  bien, citando la regla correcta con la conclusión al revés. Ahora los once comandos que reciben un
  planning fallan igual, con la ruta **resuelta** puesta — que es lo que hace falta cuando el error es de
  resolución: en sidecar, `<empresa>-ops/planning` escrito desde adentro de la raíz apunta a
  `<empresa>-ops/<empresa>-ops/planning`.

  **Lo que te pide algo**: si tenías un script que trataba ese vacío como «nada que hacer», ahora falla.
  Es la misma dirección que 0.63.0 y 0.71.0 ya tomaron. Y `ops check` sobre una ruta que no existe pasa a
  decir eso en vez de «falta BACKLOG.md»: sale con 2 y nombra la ruta.

### Agregado

- **La entrada declara también `review:`, y `check` cruza los dos.** El carril dice cuánta ceremonia
  **merecía** la tarea; `review:` dice cuánta **recibió** —el veredicto y quién revisó, o `n/a — razón`
  cuando no corrió—. Es la dimensión que la propia ADR OPS-006 nombraba como la que falta: «se sabría
  comparando hallazgos de review por carril, y hoy no se registra esa dimensión en DONE».

  Con los dos campos, `check` avisa lo que hasta ahora no tenía cómo ver: una entrada cuyo carril convoca
  revisor —`directo`, `lite`, `full`— y cuya revisión no corrió. `express` queda afuera porque es el único
  que legítimamente no convoca a nadie. Avisa y no falla: es un hecho del pasado que no se arregla
  editando la entrada, y el único camino al verde sería reescribir el registro.

- **La entrada de una tarea cerrada declara `lane:`, el carril con el que corrió.** El carril decide qué
  fases recibe una tarea —`express` se saltea Ready, Plan y QA; `full` las corre todas— y viajaba sólo en
  la línea del BACKLOG, que **se borra al cerrar**. Con eso, «¿esta tarea recibió la ceremonia que su
  superficie pedía?» dejaba de tener dónde contestarse: en una instancia real, **0 de 79 entradas de DONE
  registraban el carril**. Ahora queda en el registro, y `sin clasificar` es un valor y no un hueco — dice
  que la línea no lo declaraba, que es distinto de que nadie llenara el campo.

  El plan en vuelo también lo lleva: una corrida que se reanuda arma la tarea desde `wip/<runner>.md`, y
  sin el campo ahí llegaba al cierre con el carril ya perdido aunque la tarea sí lo tuviera.

  **Lo que te pide algo**: `ops check` **avisa** cuántas entradas no lo traen y **no falla** — las
  escritas antes de esta versión no lo tienen y no hay de dónde sacárselo. Lo que sí falla es un valor
  inventado. Si cerrás a mano, agregá `lane:` a la entrada; si cerrás con `autobuild`, ya lo escribe.

## [0.75.0] - 2026-09-10

### Cambiado

- **R9 dice ahora que una quita casi nunca se ve como una quita, y que eso no admite excepción.** Se
  escribe como un agregado —una bandera que se pone, una variable que se exporta— y lo que la delata es
  la frase que la justifica: «lo agrego **para que** deje de …». Silenciar un aviso, saltear una rama o
  desarmar una confirmación son quitas, y una quita no se entrega sin su aserción de ausencia vista en
  rojo devolviendo lo quitado.

  Con un renglón propio para el caso que más se disfraza: **una confirmación que estorba casi siempre
  está cuidando algo**, y si no sabés qué, eso es el resultado de la medición y no un permiso para
  seguir. Lo que corresponde es quitarle a la herramienta el motivo de preguntar, no la pregunta.

  **Lo que te pide algo**: es una regla del sistema, así que baja a tu `planning/` en el próximo
  `upgrade` y aplica a todo cambio, no sólo a los del toolkit.

- **Un punto y coma dentro de la prosa de `tests:` ya no parte la traza.** El `;` es el separador que R8
  fija y a la vez el signo más común de la prosa española, y ese campo pide las dos cosas: el contrato
  pide `CN → prueba` y R9 pide decir cómo se vio fallar esa prueba. Una sola traza con prosa se partía en
  tres y `check` rechazaba el campo entero mandando a revisar la traza, que era lo único que estaba bien
  — y la salida fácil era acortar la prosa hasta que pasara, o sea empobrecer justo la evidencia.

  Ahora se corta sólo cuando detrás **empieza otra traza**, que es la misma decisión que `commit:` ya
  tomaba para el sha. Las entradas con varias trazas siguen valiendo igual.

  **Lo que te pide algo**: si tu prosa contiene literalmente `; A → algo`, se va a partir ahí — desde
  afuera es indistinguible de dos trazas. Es la misma concesión que `commit:` aceptó.

- **Un caso no se cierra sin haberlo probado corriendo.** El `## Cierre` nombra qué se corrió y qué
  devolvió —la salida, la mutación vista en rojo, el número medido—; «la suite pasa» no cuenta, porque
  dice que nada de lo que ya había se rompió y no que esto funcione. Vale igual cuando se decide no
  arreglar: ahí se prueba el dato que sostiene la decisión. La puerta lo comprueba desde esta versión y
  no hacia atrás, por lo mismo que el contraste rige desde 0.65.0.

### Corregido

- **`autobuild` deja de reintentar un reclamo que no puede salir bien.** Cuando la cola vuelve a ofrecer
  la misma tarea después de un `claim` fallido, nadie la tomó: el reclamo falló por su cuenta y repetirlo
  no cambia nada. Antes se repetía hasta agotar el cupo de tareas de la corrida — en una corrida real
  **28 de 50 agentes** se fueron ahí, sin construir nada y sin que nada lo dijera. Ahora para con
  `claim-stuck`, y el motivo lleva el slug que la cola ofreció y lo que contestó el reclamo. Perder la
  carrera de verdad sigue sin frenar: ahí la cola pasa a ofrecer otra tarea, y ahora la corrida dice con
  cuál sigue. Lo mismo en Decompose: si tras pedir la partición la cola sigue ofreciendo la tarea sin
  partir, la escritura no ocurrió y para con `split-not-applied`.

- **El registro de una corrida dice qué hace cada agente.** Las llamadas sin nombre se veían como el
  arranque del preámbulo compartido, que es igual en todas: la misma cadena repetida treinta veces, y un
  bucle de veintiocho agentes indistinguible de trabajo. Las veintisiete llamadas del recorrido llevan
  etiqueta propia.

- **Un gate ya no puede borrar el `node_modules` de tu proyecto, y se revierte el `CI=true` de 0.74.0.**
  Esa variable resolvía el síntoma del caso anterior —pnpm dejaba de preguntar antes de purgar—
  desarmando la confirmación en vez de quitarle el motivo. Y esa confirmación era lo único que protegía
  al árbol enlazado: sin ella la reinstalación avanza y **borra por el enlace**. Medido: `require()`
  dejaba de encontrar las dependencias del proyecto, y la instalación ni siquiera necesita completarse
  para hacerlo — en la corrida medida abortó por `frozen-lockfile` y para entonces ya había borrado.

  Ahora la copia lleva `verify-deps-before-run=false`, que le dice a pnpm que no sincronice nada antes de
  correr el script. No hay purga que confirmar, así que no hay confirmación que desarmar.

  **Lo que te pide algo**: si tenías un gate que se portaba distinto bajo `CI`, deja de verla. Vuelven el
  color y los prompts de otras herramientas — y un prompt en un proceso sin terminal aborta, que es la
  barrera que se quiere de vuelta.

## [0.74.0] - 2026-09-10

### Corregido

- **Un gate ya no pisa lo que vos construiste.** Al medir el índice, lo ignorado se enlazaba al árbol
  real: el gate corría sobre lo staged y te dejaba la salida de build con **esa** versión, mientras tu
  fuente en disco tenía otra y nada lo decía. Si corrías la app después de commitear, corrías algo que
  no era lo que estabas mirando. Ahora lo que un gate puede fabricar —`dist`, `build`, `out`,
  `coverage`, `.next`, `.nuxt`, `.svelte-kit`, `.turbo`, `.output`, `.parcel-cache`, `__pycache__`,
  `.pytest_cache`— se construye adentro de la copia y se descarta con ella. Lo que no puede fabricar
  —`node_modules`, un `.env`— se le sigue enlazando.

  Arregla también algo del propio gate: construía sobre restos de tu corrida anterior, así que su
  veredicto dependía de un estado que nadie declaró.

  **Lo que te pide algo**: el build del gate deja de ser incremental, así que ese commit tarda más. Y si
  tu proyecto genera en un directorio ignorado que no está en esa lista, seguí reportándolo: el nombre
  se agrega.

- **Los gates corren sobre una copia que se declara no interactiva, y un gate que falla dice qué dijo.**
  `verify` mide el índice en un temporal y enlaza ahí lo ignorado, `node_modules` incluido, apuntando al
  original. En un proyecto pnpm eso no corre: el gestor ve que el árbol enlazado no fue instalado ahí y
  su reacción es reinstalar, que empieza borrando el `node_modules` **del proyecto**. Lo único que lo
  detenía es que el hijo no ve una terminal. Ahora la copia lleva `CI=true`, que es la variable que el
  propio pnpm nombra — y sólo la copia: si árbol e índice coinciden, los gates corren en tu directorio y
  ahí no se te cambia nada.

  Y el bloqueo dejaba de decir por qué: `test (exit 1)` era el mismo texto para una suite en rojo y para
  un gestor que se negó a arrancar el script, así que empujaba a aprobar el commit como «rojo conocido»
  sin que nada se hubiera medido. Ahora llega con la duración y con la primera línea de error de la
  herramienta, y cuando todos los gates fallan por debajo de dos segundos lo dice — sin afirmar que no
  corrieron, que es algo que no se puede saber desde acá.

  **Lo que te pide algo**: si tenías un gate que se comportaba distinto bajo `CI`, ahora lo va a hacer
  al commitear con algo sin stagear o sin trackear.

## [0.73.0] - 2026-09-09

### Corregido

- **Los guards dejan de frenar trabajo legítimo escrito en varias líneas.** Siete reglas acotaban
  «dentro de este comando» con `[^;&|]`, y el salto de línea —que también separa comandos— no estaba en
  esa lista: cualquier bandera de una línea posterior se leía como parte del comando de arriba. `git
  commit -m "x"` seguido de `ls -a` se bloqueaba como si stageara al commitear, y `git push origin main`
  seguido de `rm -f /tmp/x` como un force push, que además nombraba una violación que no estaba.

  Lo que frena sigue frenando: `commit -am`, `add -A`, `push --force`, `--amend` e `install -g` en una
  línea siguen bloqueados, y un `git push` seguido de otra cosa sigue pidiendo la acción humana que R10
  exige — sólo que ya no lo llama reescritura de historia.

- **Un banco de evaluación que no se puede rehacer lo dice, en vez de seguir sobre uno que no es nuevo.**
  El borrado ahora se comprueba, y si algo sobrevive la corrida corta nombrando qué. Hasta acá el mismo
  hecho se venía rodeando por síntoma —reintentos en el borrado, `force` en el andamiaje, un `rm` antes
  del enlace—, y cada rodeo dejaba la medición siguiente corriendo sobre restos de la anterior.

  La prueba que lo destapaba, además, **descartaba el resultado de la corrida que fallaba**: cualquier
  causa terminaba en el mismo `true !== false` con el `stderr` tirado, y por eso tres investigaciones
  dejaron escrito «no está establecido por qué». Ahora la falla llega con su código y su ruta.

  **Lo que te pide algo**: si rehacer un banco te corta con «no se pudo borrar entero», borralo a mano y
  volvé a correr. Antes eso seguía en silencio y fallaba más adelante, en otro lado.

## [0.72.0] - 2026-09-09

### Corregido

- **Una fuente que el cargo no pudo abrir se declara en `pending:`, y el ciclo avisa cuando vuelve a
  servir.** Antes eso terminaba en un comentario del propio `sources.yaml` —cinco cargos lo escribían
  así, y uno dejó un «registrar cuando exista una ficha legible» que nadie iba a revisar—. Ahora es una
  lista con `name`, `why` y `since`, y `url` cuando hay una candidata; el chequeo semanal reporta **sólo
  la que volvió a servir el documento**, porque una que sigue cerrada es lo esperado.

  No alcanza con que el host conteste: se pide que la página traiga la marca del documento, el primer
  número del nombre. Un sitio grande devuelve cientos de palabras de navegación sin una línea de la
  norma, y así fue como la primera versión de esto dio por recuperada una ficha de IEEE que seguía
  siendo una cáscara.

  **Lo que te pide algo**: si tenías pendientes anotadas como comentario en un cargo propio, moverlas a
  `pending:` es lo que hace que alguien se entere cuando la URL empiece a funcionar.

- **La fase `Pick` de `autobuild` deja de prometer una expansión que ya no existe.** Su descripción
  —lo único del recorrido que se lee **antes** de autorizar la corrida— seguía diciendo «o la épica que
  falta expandir» después de que la promoción de épicas se quitara. Lo que el recorrido hace hoy es
  tomar la próxima tarea del hito y reservarla.

- **Un merge a `main` ya no le quita la corrida de CI al merge anterior.** Todos los pushes a `main`
  compartían grupo de concurrencia, y sólo una corrida puede estar pendiente por grupo: la que llega
  desaloja a la que esperaba. En una tanda de merges sobrevivían la primera y la última y morían todas
  las del medio — el 2026-09-08, quince corridas. No lo causaba `cancel-in-progress`, que ya estaba
  apagado en `main` y funciona: la corrida **en curso** sobrevivió cada vez. Ahora cada commit de `main`
  tiene su propio grupo, así que ninguna espera detrás de otra.

  **Lo que te pide algo**: si mergeás varios PR seguidos, ahora corre la suite completa una vez por
  merge en vez de una por tanda. Lo que se compra es saber cuál rompió `main` sin bisect.

- **`agent evaluate` deja de anunciar que el contrato cambió hoy cuando no cambió.** La fecha sale de
  `git log` sobre el `SKILL.md` del cargo, y un checkout de un solo commit —el que hace `actions/checkout`
  por defecto— no tiene con qué contestarla: git le atribuye el árbol entero a ese commit, así que
  **todos** los contratos parecían haber cambiado el día de la corrida. En un repositorio truncado el
  aviso ahora dice que no se puede saber, en vez de inventar una fecha, y el job que evalúa se lleva la
  historia completa para que sí se pueda.

  **Lo que te pide algo**: si corrés `agent evaluate` en un pipeline con clon superficial, vas a ver el
  aviso nuevo. Se destraba con `fetch-depth: 0` en el checkout; sin eso el chequeo no puede correr y
  decirlo es la mitad del arreglo.

### Cambiado

- **El README del catálogo dice qué calibra un caso de evaluación recién escrito.** El rojo de su primera
  corrida prueba que puede fallar, no que falle por lo que dice medir: eso lo prueba una corrida donde se
  rompa a propósito la conducta que cuida. Es lo que R9 ya pedía para una prueba, dicho donde se leen los
  veredictos.

## [0.71.0] - 2026-09-09

### Cambiado

- **`planning/PROTOCOL.md` pide contrastar la línea de una tarea contra su propia descripción.** La línea
  declara cuatro cosas —qué hace, en qué carril, quién entrega y revisa, con qué se comprueba— y las
  cuatro las escribe la misma mano en el mismo acto, así que nada las cruzaba después. Releerlas no
  encuentra el hueco: una aceptación incompleta se lee perfecta porque todo lo que dice es cierto.

  La pasada vive al escribir la tarea y no en una fase, y la razón es que cualquier fase donde viviera es
  una que el carril puede saltar — una tarea mal marcada `express` es justamente la que se salta la fase
  donde alguien lo notaría.

  **Lo que te pide algo**: cuesta minutos por tarea al promover un hito. En el caso que originó esto, seis
  correcciones sobre cinco tareas, todas antes de escribir una línea de código.

### Corregido

- **`autobuild` comprueba su raíz en la primera fase, y lo dice nombrándola.** `ROOT` viaja escrito en
  el workflow y es relativo a la carpeta donde se abre la herramienta: si la sesión abrió en otra, todas
  las rutas resuelven a `<raíz>/<raíz>/…` y ninguna existe. Nada lo comprobaba, así que la corrida
  gastaba Triage entero sobre archivos ausentes y paraba más abajo mandando a revisar el planning — que
  está bien; lo que no existe es la carpeta de la que cuelga.

- **`learning/HISTORY.md` dice lo mismo en los 53 cargos, y su fila entra en una tabla.** El encabezado
  tenía trece redacciones distintas y nueve contradecían la tabla que llevan debajo —«registrar únicamente
  cambios aprobados», cuando la columna se llama «Decisión» y hay dos—. Y dieciséis archivos no tenían
  tabla: la fila que escribe el ciclo quedaba pegada al párrafo, que en markdown es texto con barras.

  Los tres cargos con una exigencia propia —país, jurisdicción, revisión de Legal— la conservan como línea
  aparte. Ninguna se cumplía, y aun así no se borraron: dos son de cargos regulatorios.

- **`autobuild` ya no promueve épicas: nombra la que sigue y para.** Cuando se le acababa la cola,
  expandía la próxima épica del roadmap al BACKLOG. El roadmap llama `open` a «candidata editable que aún
  no fue promovida al backlog», así que pegarla en la cola es promoverla — y BR-OPS-002 deja una propuesta
  fuera de la cola hasta que la apruebe una persona. El prompt pedía «la próxima épica abierta y
  aprobada», y «aprobada» no correspondía a ningún dato: una épica declara `epic`, `title`, `status` y
  `service`, y ninguno registra una aprobación.

  Ahora `ops context` nombra la próxima épica sin promover —`EPIC 003: … — sin promover`, y el campo
  `nextEpic` en `--json`— igual que ya nombraba una recurrencia vencida, y por el mismo motivo: la máquina
  calcula, la persona encola.

  **Lo que te pide algo**: si usabas `autobuild` desatendido esperando que encadenara épicas, ahora se
  detiene al terminar el hito y hay que pegar el siguiente en `BACKLOG.md`. `context` te dice cuál es.

- **El chequeo semanal de fuentes mira también `references/` y `SKILL.md`, y deja de dar por rotas las
  páginas sanas.** Miraba sólo `sources.yaml`: las 207 URLs que un cargo cita en su método no las
  comprobaba nadie, y 29 no servían —dos de ellas 404 de páginas movidas hacía meses—. `evaluations/`
  queda afuera a propósito: un caso adversarial inventa dominios y comprobarlos mide el fixture.

  Y el chequeo se equivocaba en las dos direcciones sobre lo que sí miraba. Se identificaba como
  `cauce-learning/1.0`, que no es con lo que el cargo lee: las tres páginas de `ftc.gov` del catálogo dan
  403 a ese `User-Agent` y 200 con miles de palabras a uno de navegador. Y veinte segundos no alcanzan
  para un PDF grande —el instrumento de la OCDE que cita `sales-representative` llega pasados los
  treinta—. Ahora lo que falla se reintenta una vez, con más tiempo y como navegador, y el resumen dice
  cuántas lo necesitaron: que un dominio nos rechace es un dato suyo y se pierde si el reintento lo tapa.

  El aviso nombra además el archivo donde está escrita cada URL, porque eso decide quién la arregla.

- **Las URLs rotas del catálogo se arreglaron, no sólo se reportaron.** Veintitrés reemplazos
  comprobados uno por uno. Casi ninguna estaba muerta: cuando un dominio bloquea suele haber otra forma
  publicada del mismo documento —el PDF donde el HTML tiene Cloudflare (`acm.org`, los instrumentos de la
  OCDE), otro sitio del mismo organismo (`oecd.ai`, `gov.uk`), o el feed que la propia CISA distribuye—.
  Las cuatro que no tienen ninguna —`pmi.org`, `fatf-gafi.org`— se quedan como fuente y pierden el enlace
  en `references/`, conservando el nombre: un 403 ahí sólo le hace perder un clic a quien lee.

- **Las normas ISO del catálogo se citan por una ficha que se puede leer.** `www.iso.org` devuelve 403,
  y 41 entradas de 22 cargos apuntaban ahí: para todas ellas la investigación semanal producía el mismo
  informe «sin novedades» que produciría una norma que no cambió. Ahora una ISO/IEC se cita por su ficha
  del IEC Webstore y una ISO sola por la de `committee.iso.org`, que sirve el mismo número de catálogo.

  La edición pasó al nombre de las ISO/IEC —`ISO IEC 25010:2023 product quality model`— porque la ficha
  del webstore es de una edición concreta: buscar «ISO/IEC 25010» ahí devuelve primero la de 2011.
  Cada ficha se comprobó contra su `<title>` antes de anotarla, y `cloud-architect` pasó a declarar la
  edición 2 de ISO/IEC 27017, publicada el 2026-07-27 — su modelo operativo decía «no tratarla como
  publicada» y eso dejó de ser cierto. Los `references/` de esos cargos enlazaban las mismas normas a las
  mismas URLs muertas y también se cambiaron: 40 enlaces en 22 archivos.

  **Lo que te pide algo**: si forkeaste alguno de esos 22 cargos, tu copia sigue con las URLs viejas y
  `check` te avisa que el original cambió río arriba. Vale traerlas: las viejas no responden.

- **El ciclo semanal comprueba que las fuentes declaradas de un cargo respondan, y anota las que no.** Una
  fuente ilegible y una que no cambió producían el mismo informe —«sin novedades»— y no son lo mismo: la
  primera no se comprobó. Ahora el resumen del job dice cuántas fuentes declara el cargo y cuáles no
  respondieron, con su código, y sale una anotación cuando hay alguna. Avisa y no falla: un 403 de una
  semana puede ser temporal, y lo que decide una cadencia es el patrón sostenido.

  Mide además **el texto que la página trae**, no sólo que responda: por debajo de 50 palabras fuera de
  etiquetas se reporta igual que un 403, porque una aplicación renderizada por cliente devuelve su título y
  poco más. El umbral sale de medir las 255 fuentes del catálogo — catorce caen debajo y sólo dos entre 50
  y 200, así que no parte ningún grupo. Y un `202` se reporta aparte: es «aceptado, vuelve más tarde», que
  es lo que contestan las seis normas europeas que el catálogo cita.

- **El lector de fuentes era ciego para uno de los dos formatos del catálogo.** `sources.yaml` admite la
  entrada repartida en varias líneas y la escrita en una sola, y sólo se leía la primera: en los seis
  cargos que usan la segunda se veían **cero** fuentes. De ahí sale la validación que rechaza una URL
  declarada dos veces con nombres distintos, así que esos seis nunca la tuvieron — y no fallaba nada,
  porque no encontrar duplicados y no mirar producen el mismo silencio.

- **Archivar una propuesta ahora deja quién lo decidió y cuándo.** Antes quedaba con «Responsable: por
  definir» y ninguna fila en `learning/HISTORY.md`, así que una propuesta que alguien miró y descartó se
  leía igual que una que nadie tocó. El responsable sale de `CAUCE_OWNER` o de `git config user.email` —la
  misma identidad con la que se reclama una tarea— y la fila usa la columna «Decisión» que la tabla ya
  tenía, con el valor `archivada`.

- **`ops context` y `ops tree` sobre un planning que no existe ahora fallan, en vez de contestar como una
  cola terminada.** Antes devolvían `queued: 0` con código 0, así que una ruta equivocada se propagaba
  como dato y no como error. Ahora salen con código 2 y nombran la **ruta resuelta**, que es la que hace
  falta para ver el problema: en `sidecar`, `<empresa>-ops/planning` escrito desde adentro de la raíz
  apunta a `<empresa>-ops/<empresa>-ops/planning`, y las dos formas se ven razonables.

  **Lo que te pide algo**: si tenías un script que trataba la salida vacía como «nada que hacer», ahora
  recibe un error. Es deliberado y va en la misma dirección que el cambio de código de salida de
  `upgrade` en 0.67.0 — un comando que no pudo leer no responde como si hubiera leído.

- **`autobuild` no expande una épica sobre una lectura que falló.** La fase Pick tomaba «sin tarea y cola
  en cero» como permiso para promover la próxima épica al BACKLOG, y ese es exactamente el estado que
  devolvía un planning ilegible. Una corrida real escribió seis historias que ninguna persona aprobó
  —lo que BR-OPS-002 prohíbe— y el `check` posterior dio verde, porque once tareas en cola es un estado
  válido.

  El arreglo del CLI no alcanzaba solo: el esquema se completa igual, y ceros es lo que un modelo escribe
  cuando no tiene qué poner. Así que el informe de estado ahora declara si de verdad leyó, y expandir
  exige esa lectura afirmada en vez de la ausencia de tarea. Parar cuesta una corrida; promover escribe en
  el repositorio.

## [0.70.0] - 2026-09-08

### Agregado

- **`planning/claims/`: quién tomó qué, para que dos runners no construyan lo mismo.** Un archivo por tarea
  tomada, con el slug de la tarea como nombre: `ops claim planning <tarea>` lo crea y `ops release` lo borra.
  `ops context` deja de ofrecer una tarea con reclamo ajeno —antes le entregaba la misma a los dos y ninguno
  se enteraba—, nombra quién la tiene y devuelve antes lo que vos reclamaste que lo que está libre.

  Es un archivo por tarea y no uno por persona a propósito: así dos personas en tareas distintas no tocan
  nunca el mismo archivo, y dos que toman la misma chocan en git, que es donde el choque significa algo.
  `check` rechaza el reclamo que nombra una tarea que no existe, avisa a los tres días de tomada y avisa
  cuando hay dos reclamos sobre el mismo `service:` — avisa y no frena, porque frenar serializaría a un
  equipo entero sobre un servicio.

  El reclamo distingue `owner` —la persona, a quién preguntarle— de `runner` —el agente que la hace—, y
  lo segundo es lo que decide de quién es una tarea. Con varios agentes en una máquina la persona es la
  misma y el árbol de trabajo no: sin esa distinción, el segundo agente tomaría por propia la tarea del
  primero. Se crea con exclusión —el archivo se abre en modo exclusivo, así que dos reclamos simultáneos
  no se pisan— y un runner lleva una tarea a la vez.

  **Lo que te pide algo**: el reclamo hay que commitearlo y empujarlo — sin eso, el otro runner lee lo
  que hay en su copia y la reserva no existe para nadie más—. Y si corrés varios agentes en la misma
  máquina, cada uno exporta `CAUCE_RUNNER` con un valor propio.

- **`autobuild` reserva la tarea antes de construirla y la suelta al cerrarla.** Es lo que hace que dos
  corridas en paralelo dejen de trabajar lo mismo: entre preguntar qué toca y reservarlo hay una ventana, y
  perder esa carrera no frena la corrida — relee y sigue con la que quedó libre.

- **El aviso de reclamo viejo mira si la rama avanzó, no cuánto hace que se tomó.** El tiempo transcurrido
  no distingue una tarea larga de una abandonada, y equivocarse cuesta en los dos sentidos: apurar a quien
  está trabajando, o dejar bloqueada para siempre la tarea de quien se fue. Ahora `check` mira el último
  commit **propio** de `task/<tarea>` —los que no están en el tronco, porque una rama recién creada hereda
  su historia entera y sin esa distinción toda rama parecería haber avanzado el día que se creó—: tres días
  sin ninguno avisan, y una tarea que recibe commits no se apura nunca
  aunque lleve semanas tomada. Sin repositorio resoluble el aviso vuelve a mirar sólo la fecha — degrada a
  lo que había, no rompe.

- **`ops context --hito <slug>` acota la cola a un hito.** Es la forma más barata de que dos personas o dos
  agentes no se crucen: en hitos distintos casi nunca dependen entre sí ni tocan los mismos archivos. Lo
  que se acota es qué se ofrece, no qué se sabe — una dependencia que vive en otro hito se sigue juzgando
  igual—, y un hito mal escrito lo dice en vez de contestar «sin tarea disponible», que es indistinguible
  de un hito terminado.

- **El plan en vuelo es uno por runner: `planning/wip/<runner>.md`.** Con `mode: sidecar` hay un solo
  `planning/` por máquina, así que un plan compartido lo escribían todos los agentes que corren ahí: el
  segundo pisaba el del primero, y `ops context` le entregaba la tarea que el primero estaba construyendo
  —con el plan ajeno adentro y diciéndole que estaba libre—. `context` honra sólo el tuyo y `check` los
  recorre todos.

  **Lo que te pide algo**: `planning/WIP.md` se retiró. Mové tu plan a `planning/wip/<runner>.md` —el
  nombre sale de tu `CAUCE_RUNNER`, aplanado; `ops context --json` lo dice en `wipFile`— y borrá el
  archivo viejo, que mientras esté `ops check` lo nombra. El `.gitignore` nuevo excluye `planning/wip/*.md`
  y conserva su README; si venías con la línea de `planning/WIP.md`, cambiala.

- **Un `service:` ambiguo entre varias raíces se nombra en vez de elegirse.** Con más de un
  `workspaceRoots`, un servicio que existe en dos —`.` existe en todas— resolvía al primero: el árbol de
  trabajo terminaba en el repositorio que no era, y el aviso de avance miraba las ramas de otro. Ahora
  `ops worktree` nombra los candidatos y se niega, y el aviso degrada a mirar sólo la fecha.

- **`ops worktree` avisa cuando la instancia está embebida.** Con `mode: embedded` cada árbol se lleva su
  propia copia de `planning/`, así que los reclamos de un agente no los ve el otro hasta mergear y la
  coordinación entre varios deja de existir sin que nada falle. No lo frena: un árbol por rama con un solo
  agente es un uso legítimo.

- **`ops runners <planning>`: qué runners tienen trabajo abierto, para que un agente pueda preguntar.**
  Elegir con qué runner se arranca es lo primero de una sesión y `ops context` no lo contesta: responde
  «qué hago» para un runner ya elegido. Sin esa lista, un agente se inventa un id y deja huérfano el
  trabajo de ayer, o se lo pisa a otro que sigue corriendo.

  **Lo que te pide algo**: nada, y es el punto. `AGENTS.md` le dice al runner que mire esa lista al abrir
  la sesión, que **pregunte** cuál se retoma o si arranca uno nuevo, y que **exporte el id él mismo**. A
  una persona no se le pide que escriba una variable de entorno.

- **Tu propio reclamo desde otro runner se reconoce en vez de resolverse solo.** Volver al día siguiente
  sin reponer `CAUCE_RUNNER` y correr un segundo agente tuyo se ven idénticos desde el archivo, y las dos
  salidas automáticas rompen trabajo: retomar sola le saca la tarea al otro agente, y crear un runner
  nuevo deja dos construyendo lo mismo. `ops claim` dice cuál es cuál y con qué id se retoma; `ops context`
  marca esas tareas como «vos, desde otro runner».

- **La evidencia de una tarea cerrada vive en su propio archivo: `planning/done/<slug>.md`.** Cerrar es lo
  que más se hace, y mientras la evidencia se acumulaba en un `DONE.md` compartido, cerrar era agregarle
  una entrada a algo que otro también estaba tocando. Ahora dos personas —o dos agentes— que cierran a la
  vez escriben archivos distintos: no hay conflicto que resolver ni regla de merge que aplicar.

  La entrada declara `fecha:`, la del cierre. Mientras vivían en un archivo, «la última» era la última del
  archivo; con archivos sueltos el orden lo daría el listado del directorio, que es alfabético, y la
  respuesta equivocada se leería igual de bien que la correcta. `ops evidence` sin `--task` ordena por ese
  campo, y el contrato de una entrada está en `planning/done/README.md`.

  **Lo que te pide algo**: `planning/DONE.md` se retiró. Pasá cada entrada a su propio
  `planning/done/<slug>.md` con su `fecha:` y borrá el archivo — mientras esté, `ops check` lo dice en vez
  de ignorarlo, porque un `DONE.md` que ya nadie lee deja a sus épicas sin poder cerrar y a sus historias
  figurando sin evidencia.

- **`ops archive <NNN>` se retiró; `ops archive human-actions` se queda.** Archivar una épica existía para
  descongestionar un `DONE.md` que se hinchaba con una entrada por tarea; con un archivo por tarea no hay
  nada que descongestionar, y mover esos archivos a una carpeta por épica sería reintroducir el movimiento
  que esto vino a sacar. El comando lo dice, en vez de contestar «la épica debe ser NNN».

- **`(depende: slug)` en una línea de tarea: lo que sigue no se le ofrece a otro.** El orden del BACKLOG
  era la dependencia y alcanzaba mientras hubiera un runner; con dos, el segundo toma la que sigue mientras
  el primero construye aquella de la que depende, y las dos ramas se pisan al integrar. Una tarea con
  dependencias sin cerrar no se ofrece ni se puede tomar, y `context` la muestra con una línea `WAIT` que
  nombra la dependencia y quién la tiene — una cola trabada no se lee como una cola vacía. `check` rechaza
  la dependencia que no existe y nombra el ciclo entero cuando lo hay.

- **`ops worktree <planning> <tarea>`: un árbol de trabajo por agente, sin clonar el repositorio.** Resuelve
  en qué raíz de `workspaceRoots` vive el `service:` de la tarea, crea la rama `task/<slug>` y el árbol al
  lado, y devuelve la ruta con el `export CAUCE_RUNNER` ya escrito. `git worktree` comparte el mismo `.git`
  y el mismo historial, así que no hay una segunda copia del repositorio: lo que hay es un segundo
  directorio de archivos fijado a su rama, y por eso **ningún agente hace `checkout`** sobre el trabajo de
  otro. Repetirlo devuelve el árbol que ya existe.

- **`.gitattributes`: `DONE.md` y `HUMAN_ACTIONS.md` se concatenan en vez de conflictuar.** Dos personas
  cerrando trabajo el mismo día chocaban siempre, y ese conflicto no significaba nada: las dos entradas son
  buenas y van las dos. Lo que `union` no hace es deduplicar, y esa falla ya la atrapa `DONE duplicado`.

- **`planning/delivery/teamwork.md`**: qué comparte el equipo y qué no, por qué dos agentes necesitan un
  `git worktree` cada uno, cómo repartir trabajo, y qué se rompe primero según el tamaño del equipo.

- **BR-OPS-005 — una tarea, un runner.** La contracara de BR-OPS-001: aquélla impide que un runner lleve dos
  tareas, ésta que dos runners lleven la misma.

### Cambiado

- **`planning/WIP.md` pasa a ser local y deja de viajar por git.** Existe para recuperar la sesión de quien
  lo escribió —nadie más puede retomarla— y cambia en cada paso, así que compartirlo era un conflicto por
  commit a cambio de nada. `check` deja de exigir que exista: ausente se lee como IDLE, que es lo que
  significa, y un clon nuevo ya no falla por no traerlo.

  **Lo que te pide algo**: el molde nuevo lo gitignorea, pero tu `.gitignore` es tuyo y `upgrade` no lo toca.
  Para aprovecharlo, agregale `planning/WIP.md` y sacalo del índice con `git rm --cached planning/WIP.md`.
  Sin hacer nada, todo sigue funcionando como antes.

- **BR-OPS-001 se acota al runner.** Decía que WIP es el mutex sin decir de quién, y con equipo eso se leía
  como «trabaja uno por vez». Ahora dice que un runner no toma dos tareas; que dos runners no tomen la misma
  es BR-OPS-005.

## [0.69.0] - 2026-09-07

### Agregado

- **`planning/RECURRING.md`: el trabajo que vuelve se declara una vez.** Actualizar dependencias, revisar
  quién tiene acceso a producción, mirar el gasto del mes: una fila con su cadencia —`mensual`,
  `trimestral`, `semestral` o `anual`— y la celda de tarea escrita como la cola de su línea de BACKLOG,
  así que la aceptación se decide una vez y no se improvisa en cada vuelta.

  **Nada se dispara.** No hay cron ni cola: el vencimiento se calcula cuando alguien corre el CLI, y rueda
  desde el período que cerró la última vuelta en `DONE.md` en vez de una celda que haya que acordarse de
  actualizar. `node tools/ops.js recurring planning` dice qué venció, y `--promote <qué>` emite la línea de
  esa vuelta —la emite y no la escribe: pegarla en `BACKLOG.md` es el acto de promoción—. `check` rechaza
  la fila ilegible y avisa la vencida sin frenar nada; `context` la nombra con `DUE`. Postergar se escribe
  a mano con su razón, compra un período, y tres seguidas se avisan porque ahí lo que falla es la cadencia.

  **Lo que te pide algo**: el archivo llega vacío con esta actualización y, hasta que declares una fila, el
  motor no dice una palabra. Y si tu runner ya está andando, la regla nueva que necesita está en
  `AGENTS.md`: una recurrencia vencida no la promueve él, por más que `context` la nombre sola.

## [0.68.0] - 2026-09-07

### Cambiado

- **`upgrade` deja de borrar una ruta retirada cuyo contenido no puede probar suyo.** Retirar una ruta
  nunca volvió al toolkit dueño de lo que hay adentro, y hasta acá se borraba el directorio entero: quien
  tenía sus propios workflows en `automatization/workflows` los perdía sin confirmación y sin vuelta
  atrás. De las seis rutas retiradas, cuatro viven bajo `system/` o son un archivo con nombre del
  toolkit y se siguen retirando igual; las dos que un proyecto también usa para lo suyo
  —`automatization/runners` y `automatization/workflows`— se conservan.

  **Lo que te pide algo**: esas dos rutas quedan en disco con lo que tengas adentro, la corrida te dice
  cuántos archivos son, y `check` las cuenta en cada corrida hasta que las muevas o las borres. Si lo
  que tenés ahí son restos viejos del toolkit y querés limpiarlos, `--force` las retira.

- **La regla R9 dice cómo se prueba una quita.** Una prueba que comprueba que aparece lo nuevo no
  comprueba que desapareció lo viejo, y lo que se quita tiene dependientes que no se anuncian —el
  mensaje que afirmaba la invariante, la condición que la deducía—. Sale de una regresión de 0.67.0 que
  entró exactamente por ahí.

### Corregido

- **El informe de `upgrade` afirmaba descartes que no ocurrieron.** 0.67.0 pasó a conservar los archivos
  editados y la salida siguió enumerándolos como `− descartado tu cambio en …`, uno por uno, después de
  la línea que anuncia que terminó bien. En una instancia real fueron diecinueve renglones falsos, y
  entre ellos iba la única línea destructiva verdadera de esa corrida, indistinguible. Ahora el informe
  recibe qué pasó —descartado, conservado, pendiente— en vez de deducirlo de una condición que dejó de
  valer, y la línea «planning, organization y todo lo propio quedaron intactos» sale justo cuando es
  cierto, que es cuando se conservó algo.

## [0.67.0] - 2026-09-07

### Agregado

- **Guard `plan-first`: no se cambia el producto sin un plan escrito.** R1 y el paso 7 del protocolo lo
  piden desde siempre y nada lo comprobaba: tocar el archivo primero y redactar después la aceptación
  que lo justifica salía igual de verde y se leía igual en DONE. Ahora una escritura de producto exige
  un WIP activo con al menos un paso numerado.

  No juzga lo que tu instancia posee —`planning/`, `organization/`, `agents/`, `flows/`,
  `integrations/`, `automatization/`, `tools/`—: el plan se escribe en `planning/`, y exigirlo ahí sería
  un candado con la llave adentro. Y queda **inerte mientras tu planning no declare ninguna tarea**, que
  es una instancia recién creada; `automation check` te lo dice cuando lo está.

  **Lo que te pide algo**: si el cambio no es trabajo de una tarea, aprobá la ruta en
  `planning/.ops-approval`. La variable `OPS_PLAN_FIRST_OVERRIDE=1` lo apaga para toda la sesión.

- **`ops evidence <planning-dir>`: contrasta la evidencia de una entrada de DONE contra lo que no
  escribió su autor.** Los dos lados de `tests: CN → prueba` los escribía la misma mano en el mismo
  acto, así que compararlos medía prosa. Ahora se comprueban dos cosas independientes: si el artefacto
  que el rastro nombra existe en tus raíces de código, y qué gates corrió `verify` al commitear con su
  código de salida. Dice también lo que **no** puede contestar —que la prueba nombrada haya corrido
  depende del runner— y no reemplaza a leer su fuente, que es lo que R9 pide.

  **Lo que te pide algo**: `verify` deja ese registro en `planning/.verify-log`. Tu `.gitignore` no se
  actualiza con el molde —es de `init`—, así que agregale esa línea o el archivo te va a aparecer sin
  trackear en cada commit.

### Cambiado

- **`upgrade` conserva lo editado y actualiza el resto, en vez de abortar.** Antes cortaba si algún
  archivo del molde estaba editado, y el único flag que lo destrababa descartaba todos tus cambios de
  una: quien adoptó Cauce sobre un proceso propio quedaba eligiendo entre no actualizar nunca y perder
  su corpus. Ahora recibís `rules/system/` y `adr/system/` frescos y conservás lo tuyo, y la corrida
  nombra qué congeló — en cada corrida, no sólo la primera.

  `check` cuenta esos archivos como advertencia para que la deuda no desaparezca entre actualización y
  actualización. Y el consejo nombra los cuatro que no tienen contraparte propia adónde mudarse
  —`PROTOCOL.md`, `METHODOLOGY.md`, `FLOW.md` y el `Makefile`—, en vez de mandarte a mudarlos.

  **Lo que te pide algo**: `upgrade` ya no devuelve código distinto de cero por una edición local. Si lo
  llamás desde un script que esperaba ese fallo, ese script cambia. `--force` sigue reemplazando todo.

### Corregido

- **Un pipe escapado en `HUMAN_ACTIONS.md` corría las columnas de su fila.** En markdown un pipe dentro
  de una celda se escribe `\|` —es la única forma— y el parser lo tomaba como separador. La cara que
  importa era silenciosa: con el pipe detrás de la palabra del vocabulario, `check` pasaba y el runner
  recibía el contenido de `Origen` como si fuera la acción de desbloqueo. El escape ya no parte la
  celda, y se quita al leerla porque esa columna existe para que una persona la lea.

  **Puede que empieces a ver un error nuevo**, y es correcto: una fila con las columnas corridas podía
  esconder un estado fuera del vocabulario y desbloquear una tarea que nadie resolvió. Leída bien, ese
  estado se ve.

- **La cabecera de `HUMAN_ACTIONS.md` sólo se salteaba si decía exactamente `Tarea`.** Cualquier otro
  encabezado —`Tarea Requerida`, `Bloqueo`— caía del lado de los datos y `check` reportaba las etiquetas
  de tus columnas como una acción rota. Ahora la cabecera se reconoce por su forma —es la fila anterior
  a la de separadores— y se saltea la de **cada** tabla del archivo, no sólo la primera.

- **Los gates de `verify` corrían con el `GIT_DIR` de tu repositorio.** Un gate que escribe con git
  —una suite que levanta repositorios de prueba y les commitea— escribía entonces en el tuyo: commits
  ajenos en tu rama, archivos trackeados que nadie agregó y un `core.worktree` apuntando a un temporal
  ya borrado, sin que nada lo anunciara. Se disparaba al committear una naturaleza por vez, que es lo
  que R8 pide. Ahora la copia que `verify` materializa es un repositorio propio.

  **Lo que te pide algo**: esa copia no tiene historia. Un gate que lea una etiqueta o un `git log` no
  la encuentra, y un gate cuyo efecto **es** una escritura de git —taggear, commitear un lockfile
  regenerado— la hace sobre la copia, que se borra. Si tenés un gate así, sacá esa escritura del gate.

## [0.66.0] - 2026-09-07

### Corregido

- **`shell-boundary` ignoraba el `cd` del propio comando.** Resolvía las rutas relativas contra el
  directorio que le entrega tu runner, no contra el que el comando elige antes de escribir, así que
  juzgaba una ruta que nadie iba a escribir. Fallaba para los dos lados: `cd <fuera de tus raíces> &&
  echo x > nota.md` pasaba sin decir nada y el archivo se escribía afuera; y con el runner abierto en
  otro directorio, un `cd` a un lugar legítimo se frenaba nombrando una ruta que no estaba en el
  comando.

  Ahora cada escritura se juzga contra el `cd` que la precede. **Lo que te pide algo**: un `cd` cuyo
  destino no se puede saber acá —`cd $TRABAJO`, `cd -`— deja sin juzgar a toda ruta relativa que venga
  después, y eso se bloquea pidiendo la ruta absoluta o el `cd` en un comando aparte. Es deliberado y
  es el criterio que ya regía para el índice: un guard que no puede verificar no autoriza. Una ruta
  absoluta no depende del `cd` y se sigue juzgando igual.

## [0.65.0] - 2026-09-07

### Cambiado

- **R15 ahora cubre la enumeración que escribió la propia unidad de trabajo.** Decía que una entrega se
  contrasta contra lo que el contrato enumera; le faltaba el caso donde la enumeración la escribió el
  ticket, el diagnóstico o el caso: ahí lo que es código se tacha solo —hay un diff, hay una prueba— y
  una revisión pendiente, una decisión o un borde que hay que mirar no dejan rastro de haberse hecho ni
  de no haberse hecho. Cerrar por el diff los deja adentro, cerrados, y vuelven como defecto nuevo.

  **Qué cambia para vos**: una línea del tipo «vale la pena mirar si…» pasa a tener dos destinos y
  ninguno es el silencio — se hace y se dice qué encontró, también cuando no encontró nada, o sale como
  unidad propia antes de cerrar. Y cerrar deja de ser la consecuencia de que el código esté listo: es un
  acto con su propio recorrido, ítem por ítem, sobre lo que la unidad enumeró.

- **La aprobación por operación ahora abre todos los guards, y el archivo cambió de nombre.** Era
  `planning/.governance-approval` y sólo la miraba el guard de gobernanza; ahora es
  `planning/.ops-approval` y la consultan también los de migraciones, evidencia de pruebas,
  dependencias y el que corre los gates. **Si tenías una aprobación escrita, renombrá el archivo**; si
  no tenías ninguna —lo normal—, no hay nada que hacer.

  Lo que se aprueba sigue siendo una lista de rutas, y sigue valiendo para ese conjunto y para ningún
  otro. Cada guard lee la ruta sobre la que está decidiendo: la migración, la prueba que se borra, el
  manifiesto que va sin su lockfile. `verify` es la excepción y aprueba **todo lo que está en el
  índice**, porque lo que juzga es el commit entero: stagear una cosa más invalida la aprobación, que
  es lo que hace que autorizar un commit en rojo sea para ese commit y no para la sesión.

  Publicar un paquete o instalar algo global no se puede aprobar así, porque no hay ninguna ruta sobre
  la cual decidir. Esa sigue siendo una acción humana con su variable, y `AGENTS.md` lo dice donde
  estás mirando cuando el guard te frena.

- **`check` avisa si el baseline de adopción creció.** `ops adopt` ahora sella el archivo con una
  huella de lo que generó, y `check` la recalcula: una entrada agregada a mano se nombra en vez de
  sumar en silencio a una cuenta. **Retirar un renglón cambió**: en vez de borrarlo, ponele `#~`
  delante. Así la lista activa se achica igual y el conjunto original queda entero, que es contra lo
  que se compara.

  Un baseline generado por una versión anterior no tiene huella. `check` te lo dice y `ops adopt`
  sobre ese planning se la agrega sin regenerar la lista; con huella puesta se sigue negando a
  rehacerla, que es para lo que existe.

### Corregido

- **Los gates corrían sobre tu directorio y el commit graba el índice.** Son dos cosas distintas cuando
  stageás algo y después seguís editando, y también cuando un archivo nuevo todavía no está agregado: el
  gate pasaba porque el archivo estaba en disco, y el commit salía sin él. Ahora, cuando el árbol y el
  índice difieren, `verify` materializa el índice en un temporal y corre ahí; cuando coinciden corre
  donde está, que es lo mismo y no cuesta nada.

  **Esto puede empezar a frenarte commits que antes pasaban, y es lo que tiene que hacer**: si el gate
  falla y en tu directorio pasa, es que en disco tenés algo que no está staged. Lo ignorado —
  `node_modules`, `.venv`— viaja a la copia, así que los gates siguen encontrando lo que necesitan; lo
  sin trackear no viaja, que es justamente cómo aparece el `git add` que faltaba.

- **El guard de dependencias preguntaba al disco qué lockfiles hay.** Borrar el lock del directorio sin
  stagear el borrado dejaba de disparar la comprobación, aunque el commit siguiera llevándolo. Ahora un
  lock cuenta si está en disco **o** si el commit lo va a llevar; el aviso de «hay varios lockfiles»
  sigue siendo sobre el disco, porque lo que elige cuál manda es el gestor que corras.


- **Una opción global de `git` desactivaba la regla que miraba el subcomando.** `git` admite `-C`,
  `-c`, `-P` y las demás entre el verbo y el subcomando, y los patrones los esperaban pegados. Con
  cualquiera en el medio pasaban sin decir nada la prohibición de stagear todo, el force-push, el push
  a secas, `reset --hard`, `commit --amend`, `clean -f` y la forma ancha de `checkout`. Ahora las
  opciones se sacan una sola vez y cada regla vuelve a ver el verbo.

- **Un comando que stagea y commitea a la vez dejaba ciegos a tres guards.** Gobernanza, dependencias
  y el de los gates deciden mirando el índice, y un hook corre antes que el comando: con
  `git add … && git commit` o con `git commit -a` encontraban cero archivos y concluían que no había
  nada que revisar. Ahora `commit -a` se rechaza —es stagear todo con otra ortografía, que R8 ya
  prohíbe— y encadenar el `add` con el `commit` se frena pidiendo dos comandos.

- **El guard de migraciones frenaba cualquier archivo que mencionara SQL destructivo.** Miraba el
  contenido sin consultar la ruta, así que un ADR que citaba la migración o un comentario que advertía
  que eso no se hace se bloqueaban con el mensaje «La migración contiene SQL destructivo». Ahora el
  chequeo comparte el filtro por ruta con el otro, y el mensaje nombra el archivo.

- **La prohibición de stagear todo leía el mensaje de un commit como si fuera un comando**, así que el
  commit que explica la regla no se podía escribir. Y no veía la bandera cuando venía seguida de una
  comilla, o sea dentro de `bash -c` o `eval`.

## [0.64.0] - 2026-09-06

### Agregado

- **Las salidas de los guards están documentadas, y con su alcance real.** Cinco guards se pueden abrir
  y ninguna de las cinco llaves estaba escrita en un archivo que alguien fuera a abrir: el bloqueo te
  nombraba una variable y no había dónde leer dónde va. `AGENTS.md` ahora las lista en «Qué se puede
  editar y qué no», con lo incómodo dicho: el guard lee la variable de **su propio proceso**, así que
  escribirla delante del comando no llega, y la forma que sí funciona —exportarla en el entorno desde
  el que arranca tu runner— deja el guard apagado hasta que cierres la sesión. La única con alcance de
  operación es la aprobación de gobernanza.

- **Un commit de gobernanza se aprueba por operación, no por sesión.** El guard ofrecía como salida
  `OPS_GOVERNANCE_OVERRIDE=1`, que se lee del entorno del proceso: prendida antes de lanzar tu runner
  deja el guard apagado hasta que la sesión cierre. Eso convierte «aprobado este commit» en «apagado
  hasta que me vaya», y el `Makefile` de este proyecto ya decía cuál es el alcance correcto — «la
  autorización de R10 es por operación y humana». **Qué cambia para vos**: escribís las rutas
  autorizadas en `planning/.governance-approval`, una por línea, y el commit pasa. Vale para ese
  conjunto y para ningún otro: si después sumás un archivo, ese archivo no está aprobado. No se consume
  ni se borra sola —así un commit frenado por otra razón no te obliga a rehacerla—, así que `check` te
  avisa mientras exista para que la borres. La variable sigue funcionando y el mensaje del guard ahora
  dice por qué no es la vía recomendada.

## [0.63.0] - 2026-09-06

### Corregido

- **El salto de línea termina la lista de argumentos de un comando.** `shell-boundary` leía los
  argumentos de un `cp`, un `tee` o un `sed -i` cruzando a la línea siguiente, así que el destino que
  acusaba podía ser el comando de abajo: un bloqueo que hablaba de una escritura en `…/python3`, una
  ruta que no aparecía en el comando. Falla hacia el lado seguro —frena de más— pero señala algo que no
  existe, y un guard que señala mal es el que se termina apagando. **Qué cambia para vos**: si escribís
  scripts de varias líneas en un solo comando, dejás de ver bloqueos por rutas inventadas; lo que sí
  escribe fuera de las raíces se sigue frenando igual.

- **El cuerpo de un heredoc es texto, no un comando.** Escribir un archivo con
  `cat > nota.md <<'FIN' … FIN` juzgaba cada línea del documento como si fuera a ejecutarse, así que no
  se podía documentar lo que los guards vigilan: un párrafo que explica por qué no se borra la raíz se
  bloqueaba por nombrarlo, y la salida era cambiar de herramienta para escribir un archivo. Un heredoc
  es entrada estándar y no se ejecuta nunca. **Qué cambia para vos**: el cuerpo deja de juzgarse y la
  línea que lo abre se sigue juzgando entera, con su redirección —también si la escribís después del
  delimitador, como en `cat <<FIN > salida`—. Lo que se pierde a cambio: un cuerpo que después alguien
  ejecuta; al escribirse no ejecuta nada, y cuando se corra el guard verá el comando de verdad.

- **Una variable delante de `git commit` ya no apaga tres guards.** `dependencies`, `governance` y el
  control de generados de `verify` sólo corren sobre un commit, y decidían si lo era con un ancla que
  no contempla lo que un shell admite antes del verbo. Con `VAR=1 git commit` dejaban de correr **sin
  decir nada**, y con la ironía de que el prefijo que se escribe para un commit de gobernanza es una
  asignación: `OPS_GOVERNANCE_OVERRIDE=1 git commit` no leía el override, hacía que el guard no se
  ejecutara. **Qué cambia para vos**: si venías escribiendo ese prefijo, ahora el guard corre y te va a
  frenar. La variable se lee del entorno del guard, no del comando, y el mensaje ahora lo dice.

- **Un índice que no se puede leer deja de autorizar el commit.** `stagedFiles` devolvía una lista vacía
  tanto si el índice estaba vacío como si no se pudo leer, y los tres guards de arriba leen esa
  respuesta: una lectura fallida se les presentaba como «no hay nada que revisar». Llegar a una es
  fácil, porque el guard no expande variables: `git -C $OPS commit` resuelve la ruta literal `$OPS`.
  **Qué cambia para vos**: ese comando ahora se frena con un mensaje que nombra la causa. Escribí la
  ruta literal en `git -C`.

## [0.62.0] - 2026-09-06

### Corregido

- **Tres reglas de `destructive` reconocen el comando aunque venga envuelto.** Decidían dónde termina
  una palabra admitiendo sólo un espacio, el principio o el fin, y en un shell una palabra también
  termina en `;`, `&`, `|`, `)` y en una comilla. Con eso `rm -rf /; echo listo` **pasaba** —sin una
  sola comilla: lo que decidía era el espacio antes del punto y coma— y las tres se esquivaban dentro de
  `bash -c`, `sh -c`, `eval` o un subshell, donde lo de adentro sí se ejecuta. Las tres son `rm -rf`
  sobre `/`, home o `..`; `git checkout -- .` y su `restore`; y `mkfs`/`shred`. Las otras cinco de la
  tabla ya estaban sanas. **Qué cambia para vos**: algún comando que ayer pasaba ahora se frena, y es el
  que la regla siempre dijo que frenaba. Lo que **no** cambia es lo corriente: `rm -rf /srv/cache`,
  `rm -r build/cache` y `git checkout -- src/app.js` siguen pasando — revertir un archivo nombrado
  nunca fue lo que esta regla toca.

## [0.61.0] - 2026-09-06

### Agregado

- **`R11` se recorre antes de entregar, como `R14` y `R15`.** Antes de dar por terminado un cambio se
  repasan los comentarios que agrega, uno por uno, y de cada uno se contesta si alguien lo preguntaría,
  si su razón ya está escrita en otro lado y si está en el destino que le toca. Es la tercera regla de
  la misma familia —algo que releer no encuentra, porque quien lo escribió ya sabe por qué y la copia se
  lee bien precisamente porque lo que dice es cierto— y era la única sin la pasada. La regla dice
  también que una puerta que mida esto ayuda y no la reemplaza: las dos formas que más aparecen son la
  razón repetida apenas por debajo del umbral y el comentario que no repite a ningún otro porque repite
  el nombre que tiene al lado, y bajar el umbral hasta agarrarlas empieza a marcar lo que está bien.

- **Un guard nuevo, `shell-boundary`: el destino de un comando también se juzga.** El límite de raíces
  sólo se disparaba con `Edit` y `Write`, así que el archivo que una herramienta no dejaba escribir se
  escribía sin obstáculo con un heredoc por `Bash`: frenaba a quien actuaba de buena fe y no a quien
  quería pasar. **Qué cambia para vos**: se leen las redirecciones, `tee`, `truncate`, `cp`, `mv`,
  `install`, `rsync` y `sed -i` —sin `-i`, `sed` lee y no se juzga—, y el bloqueo nombra la salida
  —declarar la ruta en `writableOutsideRoots`— en vez de sólo decir que no. `/dev/null` y el temporal del sistema no se juzgan, y un destino armado con una variable
  que no sea `$HOME` tampoco: adivinar su valor sería inventar un límite. **No es completo y no se
  presenta como si lo fuera**: `eval`, un heredoc dentro de `bash -c` o un script propio escriben igual
  y ningún patrón los ve. Frena la forma habitual, como el resto de `destructive`.

- **`ops adopt` — adoptar Cauce en un proyecto que ya tiene historia.** `check` le exigía a toda entrada
  de `DONE.md` los mismos campos, incluida la que se escribió bajo otro contrato o bajo ninguno, y las
  únicas salidas eran escribir `tests: n/a — razón` en cada una vieja —que deja la exención adentro del
  campo de evidencia, donde alguien la va a copiar a una entrada nueva— o dejar `check` en rojo
  permanente. **Qué cambia para vos**: `ops adopt <planning-dir>` genera una vez
  `planning/.adoption-baseline` con las entradas que hoy no cumplen, y `check` deja de juzgarlas. El
  perdón es por entrada y no por campo, así que una historia vieja con un `commit:` de otro formato
  también queda afuera. `check` muestra la cuenta en cada corrida y avisa cuando una exenta ya cumple el
  contrato o cuando nombra una entrada que no existe, para que la lista se achique en vez de envejecer;
  achicarla es borrar el renglón. `adopt` no se vuelve a correr sobre un baseline que ya existe.

- **`writableOutsideRoots` — declarar rutas escribibles que no son raíces de código.** El guard de
  límites bloquea todo lo que caiga fuera de la raíz de ops y de `workspaceRoots`, y ahí cae el
  directorio donde tu runner guarda su memoria entre sesiones, que no es código de tu proyecto.
  Declararlo raíz para que pasara metía un árbol ajeno en `scan` y en el inventario de credenciales, y
  era el único guard que bloqueaba por política sin salida declarada. **Qué cambia para vos**: una lista
  opcional de rutas en `ops.config.json` —`~` se expande a tu casa, el resto se resuelve contra la raíz
  de ops— y `check` te las muestra resueltas en cada corrida, porque una exención que no se ve es un
  límite que ya no existe. No declarar ninguna sigue siendo el caso normal: al actualizar no hay nada
  que agregar.

### Corregido

- **El «Contexto relevante» de la épica llega a quien construye la tarea.** `ops context` resolvía la
  épica y mandaba número, título y estado; la sección que dice contra qué se construye se quedaba en el
  archivo, y `/autobuild` le dice al ejecutor que lea cuatro archivos «una sola vez y no leas nada más»,
  entre los cuales el roadmap no está. Recibía el resultado a lograr y la condición que lo cierra, nunca
  la razón por la que existe. **Qué cambia para vos**: `ops context` imprime la sección en líneas `CTX`
  y la trae en `--json` bajo `epic.context`, y la fase Plan de `/autobuild` la recibe —ya la pedía en su
  prompt y nunca le llegaba—. Viaja entera: nada en esa salida se recorta, y una lista de viñetas no
  tiene primer párrafo que resuma al resto. Si en tu proyecto esa sección es enorme, se va a notar acá.

- **La barra de cinco condiciones de R17 también cuenta la aceptación que la tarea escribe.** Contaba
  sólo los criterios heredados con `(→ CN)`, así que una tarea con su aceptación en la línea —la forma
  que el molde muestra primero— contaba cero y nunca cruzaba el umbral; la salida que R17 describe,
  `(sin partir: <razón>)`, no se le pedía jamás. **Qué cambia para vos**: es probable que aparezcan
  mensajes en tareas que hoy pasan, y en un proyecto recién adoptado pueden ser varias a la vez; la
  salida es la misma de siempre. Se cuenta lo que vos marcaste —los `(1)`, `(2)`… cuando hay más de uno,
  y si no, lo que separaste con `;`— y sub-cuenta a propósito: una frase larga con comas vale una. El
  mensaje dice cuál de los dos contó, para que «8» no mande a buscar ocho referencias que no existen.

- **R10 dice cuál de sus seis actos comprueba el motor.** La regla prometía «la autorización
  configurada para el proyecto» para push, PR, merge, tags, deploy y rollback, y el motor comprueba uno.
  Medido con `allowPush` apagado: `gh pr merge`, `gh release create`, `gh workflow run`, `git tag` y un
  `kubectl apply` pasan todos. **Qué cambia para vos**: nada de lo que hoy funciona deja de funcionar —
  lo que cambia es que la regla ya no promete lo que no comprueba, y dice que a esos cinco los sostiene
  ella y el review. Un deploy no tiene forma reconocible en un comando; un guard tendría que adivinarla.

- **`--force` y `--amend` se frenan por su cuenta.** R10 enumera seis actos de publicación y el guard
  comprobaba uno, sin distinguir lo que la prosa distingue: `git push` y `git push --force` caían en el
  mismo patrón, así que `allowPush: true` habilitaba también reescribir historia ya publicada. Y
  `git commit --amend` no lo miraba nadie, con la llave prendida o apagada, aunque R8 lo prohíbe sin
  excepción. **Qué cambia para vos**: si tu proyecto publica con `allowPush`, un `--force` ahora se
  frena igual —con su propio mensaje, que dice que la llave no lo desbloquea—, y un `--amend` también.
  Si venías usando alguno de los dos, lo vas a notar; la salida es un push normal o un commit nuevo.

- **Un mensaje de commit ya no dispara los guards que nombra.** `git commit -m "fix: bloquear git push
  --force"` caía por el guard de publicación, y nombrar `rm -rf` dentro de una explicación caía por el
  de destrucción; con el heredoc que se usa para un mensaje largo, el cuerpo entero viaja adentro del
  comando. Ahora lo entrecomillado se lee como dato **sólo** cuando el comando es un commit: en
  cualquier otro, lo que va entre comillas se ejecuta y se sigue juzgando.

- **`AGENTS.md` dice cuál de sus límites puede habilitar tu proyecto, y cuál no.** Enumeraba seis cosas
  que el runner nunca hace y dos párrafos después las llamaba «los cuatro límites del párrafo anterior»,
  que son los que no se amplían. Aparte del conteo, entre esas seis estaba **publicar**, que el motor
  hace configurable a propósito con `runner.allowPush`. Un proyecto que lo leyera al pie concluía que su
  `allowPush: true` era ilegítimo, o que podía ampliar cualquiera de las seis y elegía mal. **Qué cambia
  para vos**: el conteo desapareció, y el texto dice que publicar es lo único que se habilita y con qué
  llave, y que reescribir historia publicada no entra en ese trato.

- **R12 manda las excepciones sobre sistemas externos donde `upgrade` no las borra.** La regla cerraba
  diciendo que se documentan en el `AGENTS.md` del proyecto, y ese archivo es del toolkit: se reemplaza
  entero en cada actualización. Quien obedecía la regla escribía su excepción donde la siguiente
  actualización se la iba a llevar, sin que nada lo avisara. **Qué cambia para vos**: ahora apuntan a la
  sección «Integraciones y ambientes» de `organization/workspace.md`, que ya nombraba a R12 y reclamaba
  esas excepciones. Si tenías alguna escrita en `AGENTS.md`, movela: ahí no sobrevive.

- **Un campo de `DONE.md` que se envuelve se lee entero.** Cada campo —`acept:`, `done:`, `qa:`,
  `tests:`, `decisions:`, `commit:`— se leía de una sola línea física, y sus valores son prosa que se
  envuelve como cualquier otra línea. Cuando la envoltura partía una cita, `check` respondía «decisions
  debe citar» sobre un campo que **sí** citaba: el mensaje nombraba una ausencia que no era la que
  había, y mandaba a revisar lo único que sí estaba. **Qué cambia para vos**: las entradas que venías
  reescribiendo hasta que entraran en un renglón pasan como están, y `tests:` y `commit:` dejan de
  perder lo que quedaba debajo del salto.

## [0.60.1] - 2026-09-03

### Corregido

- **Rehacer un banco de evaluación ya no falla de vez en cuando.** `evaluate --bench --force` borra el
  banco antes de recrearlo, y sobre un árbol grande y versionado ese borrado da `ENOTEMPTY` a veces —es
  transitorio—. La corrida moría antes de empezar, sin haber evaluado nada. Ahora reintenta, que es lo
  que `rmSync` ofrece para eso. Sólo afecta al toolkit: en una instancia `--bench` no corre.

## [0.60.0] - 2026-09-03

### Corregido

- **La épica que promueve una integración pasa `check`.** La descripción remota trae sus propios
  encabezados —el fixture de Jira trae `## Criterios de aceptación`— y quedaban compitiendo con los que
  escribe la promoción, así que el parser leía la sección importada, que no tiene ningún criterio, y el
  planning quedaba en rojo apenas promovías. Ahora lo importado baja un nivel al entrar, el parser
  prefiere la coincidencia exacta cuando existe —`## Historias (Tareas)` sigue resolviendo— y el
  criterio sale sin el guión de la viñeta pegado.

- **Curar un draft ya no deja la integración en rojo.** El README manda editar el draft y correr
  `check`, y ese `check` fallaba: editar deja obsoleto un flag de `remote.json` que sólo se recalcula en
  `sync`, `rebase` y `reconcile`. Dejó de exigirse porque no protegía nada: se leía en un único lugar,
  la validación que lo comparaba consigo mismo, y lo que decide qué vuelve al remoto compara el
  contenido. **Qué cambia para vos**: el recorrido documentado funciona sin pasos intermedios.

- **`/onboard` escribe el mapa donde vive desde 0.57.0.** Seguía mandándolo a `AGENTS.md`, así que el
  primer recorrido de una instancia nueva deshacía el arreglo que sacó de ahí lo que sólo sabe tu
  proyecto. Ahora escribe las tres secciones de `organization/workspace.md` —incluidas integraciones y
  excepciones de autonomía, que ningún recorrido llenaba— y suma `organization/domains.md`, que no
  nombraba.

- **`upgrade` te dice cuando se lleva las instrucciones de tu runner.** `AGENTS.md` es del toolkit y se
  reemplaza entero, con el bloque que `automation install` dejó adentro. Lo recuperás reinstalando —el
  propio `upgrade` te lo recuerda— pero ese recordatorio no tenía causa visible, y el bloque desaparecía
  sin explicación. Ahora se nombra el archivo y el runner, una sola vez.

- **`check` avisa cuando dos secciones de una épica compiten por el mismo rol.** El parser prefiere la
  exacta, así que resuelve — en silencio, y quien escribió las dos no se entera de que una se ignora
  entera. Un título con sufijo como `## Historias (Tareas)` no dispara nada: es el único de su rol.

- **`/autobuild` ve las excepciones de autonomía de tu proyecto.** Armaba sus límites leyendo sólo
  `AGENTS.md`, y desde 0.57.0 los tuyos están en `organization/workspace.md`. Lo que viajaba a cada
  subagente como «Límites del proyecto» eran los genéricos del toolkit. Ahora ese archivo entra en la
  lectura única de Triage; si no existe o su sección sigue como la trae el molde, no se inventa ninguno.

## [0.59.0] - 2026-09-03

### Corregido

- **Los atajos `team-check` y `team-show` del Makefile pasan a `flow-check` y `flow-show`.** El rename
  de `teams/` a `flows/` llegó al motor y al README en 0.45.0 —esa entrada hasta te pedía renombrar los
  `ops team check|list|show` que tuvieras automatizados— y el Makefile que te entregamos se quedó
  llamando al comando viejo, así que los dos atajos fallaban con el banner de uso en toda instancia.
  La variable ahora es `FLOW=<slug>`, y se suma `flow-list`, que no tenía atajo. **Qué cambia para
  vos**: si automatizaste alguno de los dos, renombralo — hoy no funcionan, así que nada que ande deja
  de andar.

- **`automation list` marca instalado lo que está instalado, también en `sidecar`.** Buscaba el wiring
  dentro de la instancia, y en `sidecar` —el modo por defecto— vive en el repo del que abrís tu
  herramienta. Los cuatro adaptadores salían `○` aunque `doctor` los diera operativos dos líneas
  después, así que el glifo no distinguía ningún caso del otro.

- **El error de `evaluate --bench` en una instancia dice qué hacer.** Recomendaba adoptar el cargo, y
  adoptarlo no habilita nada: lo que decide es el modo de la instancia. Quien seguía el consejo
  forkeaba, repetía el comando y recibía el mismo mensaje. Ahora nombra la salida real: `ops evaluate
  <cargo>` sin la bandera.

## [0.58.0] - 2026-09-03

### Corregido

- **`upgrade` te trae los archivos propios que una versión agrega.** `upgrade` reemplaza lo del toolkit
  y no toca lo tuyo, que es lo correcto — pero cuando una versión **agrega** un archivo del proyecto, la
  instancia que actualiza no lo recibía nunca. Pasó con 0.57.0: `organization/workspace.md` llegaba a
  una instancia nueva y no a una que actualizaba, que quedaba con un `AGENTS.md` nombrándolo tres veces
  y el archivo sin existir. Ahora se crea y se dice: `+ organization/workspace.md: lo agrega esta
  versión, completalo`.

  **Lo que no hace, a propósito**: reponer cualquier archivo del molde que falte. Borrar uno que no usás
  es legítimo y devolvértelo en cada actualización sería peor que el problema, así que se crea sólo lo
  que cada versión declara que agrega. Lo que ya tenés no se pisa nunca.

## [0.57.0] - 2026-09-03

### Agregado

- **`organization/workspace.md`: dónde va lo que sólo sabe tu proyecto.** El mapa real, las
  integraciones con su entorno concreto y las excepciones de autonomía. Es tuyo y `upgrade` no lo toca.
  Hasta ahora el paso 2 del README te mandaba completar `AGENTS.md`, que es del toolkit y se reemplaza
  entero: no perdías nada —desde 0.55.0 el `upgrade` se detiene y lo nombra— pero te tocaba fusionar a
  mano en cada versión, sobre el único archivo garantizado a entrar en conflicto. Es la separación que
  el toolkit ya usa en `planning/rules/system/` y en `planning/delivery/project.md`.

  **Si ya llenaste tu `AGENTS.md`**: movés esas secciones a `organization/workspace.md` y listo. El
  `upgrade` te lo dice cuando se detenga, antes de que puedas descartarlas con `--force`.

### Corregido

- **`check` te dice qué reglas deja de regir un override.** Un archivo propio con el nombre de uno de
  `system/` lo reemplaza entero, no las reglas que mencionás: las que ese archivo definía y el tuyo no
  redefine dejan de existir para tu proyecto. La advertencia nombraba el par de archivos y se leía como
  benigna. El caso caro es una regla que el motor **sigue exigiendo** —R17 lo hace—: quedaba exigida sin
  estar escrita en ningún lado. Ahora dice cuáles: `deja de regir R2, R17…`. Sigue siendo advertencia.

- **Instalar un runner ya no hace que `upgrade` te acuse de editar `AGENTS.md`.** En modo `embedded`,
  `automation install` con Codex escribe sus instrucciones dentro de ese archivo, entre marcas. El
  registro anotaba el bloque en un solo lado, así que el `upgrade` siguiente se detenía sobre un archivo
  que nadie había tocado —lo había escrito el comando que el README manda correr justo después—. Lo que
  vos escribas alrededor se sigue detectando igual.

## [0.56.0] - 2026-09-03

### Corregido

- **El upgrade que documentaba el README rompía el pin exacto que puso `init`.** La razón para no
  saltear su primer paso es que «`init` fija la versión exacta, así que `npm update` no la mueve», y
  el comando de al lado la desarmaba: npm guarda con caret por defecto, así que
  `npm install --save-dev @ingeniomaps/cauce@latest` dejaba `^0.55.0`, y dentro de `0.x` ese caret
  alcanza a los patches —que Cauce publica—. Un `npm install` en otra máquina podía dejar el motor en
  una versión que `ops.config.json` no decía.

  Se arregla por los dos lados. El comando lleva `--save-exact` en los tres lugares que lo dictan —el
  README, el `make upgrade` de tu instancia y la salida de `upgrade --check`—, y además **`upgrade`
  repone la versión exacta al terminar**, así que una instancia que ya quedó con un rango se repara
  sola en la próxima actualización. **Qué cambia para vos**: `upgrade` puede tocar ahora una línea de
  tu `package.json`, y lo dice cuando lo hace. El resto del manifiesto queda como estaba, y una
  instancia sin `package.json` no recibe uno.

## [0.55.0] - 2026-09-02

### Corregido

- **`upgrade` ya no pisa lo que `init --force` conservó.** Adoptar Cauce en un repositorio que ya tiene
  contenido es `init --force` y después `upgrade`. El primero conserva tus archivos y lo cumple; el
  segundo los reemplazaba sin avisar e imprimía que no había tocado nada propio. El registro se grababa
  hasheando el disco, así que un archivo conservado quedaba anotado como entregado por Cauce con **tu**
  contenido, y la comparación no veía ninguna diferencia. Ahora se anota el digest de lo que el molde
  habría escrito: tu archivo se ve como lo que es —una edición local— y `upgrade` se detiene
  nombrándolo. **Qué cambia para vos**: el primer `upgrade` después de una adopción con `--force` se
  detiene listando esos archivos. Resolvelos a mano, o repetí con `--force` para descartarlos, que
  ahora además los enumera en la salida.
- **Tu guard propio sobrevive a `automation install`.** `AGENTS.md` te dice que lo pongas en
  `automatization/hooks/guard-<nombre>.sh` y promete que sobrevive a cada actualización. Sobrevivía al
  `upgrade` y no a la reinstalación del runner: lo nuestro se reconocía por la carpeta, así que
  cualquier `guard-*.sh` de ahí se desregistraba antes de fusionar. El archivo quedaba en disco, el
  guard dejaba de correr y lo único que se veía era una línea diciendo que se había quitado una entrada
  obsoleta. Ahora se reconoce por lo que efectivamente entregamos: los guards que el motor trae, más
  los que esta instancia anotó haber recibido —el manifiesto gana una entrada por runner con el wiring
  que dejó puesto, y por eso una entrada nuestra se sigue retirando el día que el motor deje de traer
  ese guard—. **Si moviste tu guard fuera de esa carpeta para sortearlo, ya podés devolverlo.**
- **`automation doctor` deja de llamar divergente a una configuración completa.** Un hook propio sumado
  *dentro* de un grupo que escribió el toolkit dejaba a `doctor` en rojo aunque estuvieran todas las
  entradas esperadas: comparaba el grupo entero serializado, así que uno con un hook de más contaba
  como ausente. Ahora compara por `matcher` y contenido. Lo que falta se sigue reportando igual.
- **`upgrade --check` avisa de lo que tenés editado aunque la versión coincida.** Salía por un camino
  corto que informaba «al día» sin llegar a listarlo, que es el estado normal entre actualizaciones
  —`init` fija la versión exacta—: el único modo que no toca nada era también el único que no podía
  anticipar el conflicto. **Qué cambia para vos**: `--check` ahora sale 1 cuando hay archivos del
  sistema editados, un caso donde antes salía 0. Si lo tenés en un script, revisá esa condición.

### Cambiado

- **`init` e `init .` eligen el mismo modo en el mismo directorio.** El default salía de si escribiste
  el argumento, no de a dónde apunta: en `acme-ops/`, `init` daba `sidecar` e `init .` daba `embedded`.
  No es cosmético — en `embedded` la raíz del workspace pasa a ser la carpeta ops misma, así que
  `guard-workspace-boundary` deja de reconocer los repos hermanos y el wiring del runner se escribe
  adentro en vez de al lado, donde abrís tu herramienta. Ahora lo decide el destino resuelto, con la
  misma regla que ya elegía dónde aterrizar: una carpeta llamada `ops` o terminada en `-ops` **es** la
  instancia. **Qué cambia para vos**: `init .` en una carpeta así ahora da `sidecar`, donde antes daba
  `embedded`. El caso embebido sigue disponible pidiéndolo: `init . --mode embedded`.

## [0.54.0] - 2026-09-02

### Agregado

- **Un proyecto puede declarar la puerta que lo verifica.** `ops.config.json` acepta el comando que
  comprueba tu código —`gates`—, y el contrato de una tarea viaja con ella hasta quien la ejecuta.
  Antes cada cargo lo adivinaba desde el árbol. Si no lo declarás, todo sigue como estaba.
- **Una propuesta mensual se puede archivar en vez de aplicar.** Es el tercer destino que faltaba:
  mirarla y decidir que no cambia nada. Antes eso se hacía mergeando sin firmar, que la dejaba
  `proposed` para siempre e indistinguible de una que espera trabajo. `learn <cargo> --archived`.
- **Una propuesta firmada y sin aplicar lo dice.** Antes se contaba igual que una que nadie miró.

### Cambiado

- **Los 53 cargos traen el paso de entrega de R14.** Antes de dar por entregado, el cargo recorre los
  artefactos que se leen solos —una fila de acciones humanas, una lección, un ítem de INBOX, un paso de
  runbook— y comprueba que cada afirmación sobre el comportamiento de una herramienta, norma o sistema
  de terceros llegó con su registro. En 19 cargos la regla además se movió a la sección de reglas: donde
  estaba, se leía como una nota de colaboración. **No hace falta que hagas nada**: el catálogo se
  resuelve desde la dependencia.

  El motivo, medido sobre 198 registros de evaluación: esa conducta se lleva el 47 % de los casos rojos
  del catálogo, en 25 de los 53 cargos, casi cinco veces lo que su superficie explica. Y falla siempre
  en el mismo lugar —el informe clasifica con cuidado y la copia sale en plano al artefacto derivado—,
  que es lo que este paso va a mirar. **Que el cambio lo corrija no está demostrado**, y conviene que lo
  sepas antes de leerlo como una mejora: la tasa de recuperación por varianza entre corridas es del
  83 %, así que las dos mediciones que salieron a favor tenían un 69 % de salir igual sin cambio alguno.

- **`planning/rules/system/conduct.md` se reemplaza y R13 gana un borde.** Faltar contexto es no saber
  **cómo** —qué formato, qué borde, qué valor—, y eso se supone y se entrega marcado. No saber **qué se
  quiere ni para qué** es otra cosa: ahí el borrador deja de ser barato, porque todo lo que se construya
  encima hereda la suposición y quien lo reciba no tiene cómo ver que el objetivo era supuesto. Lo que
  separa los dos casos es si la respuesta existe escrita en algún lado. Si venías apoyándote en R13 para
  arrancar sin objetivo, esto lo acota.

### Corregido

- **Sellar una propuesta ahora marca el documento entero.** La salida temprana miraba sólo `status:` del
  frontmatter, así que si algo movía ese campo antes que el sello, el cuerpo quedaba en «- Estado:
  aprobada» para siempre: el frontmatter decía una cosa y el cuerpo la contraria, dentro del mismo
  documento. **Si tenés propuestas aplicadas con esa contradicción, `learn <cargo> --applied --period
  AAAA-MM` ahora las cierra bien.**

## [0.53.2] - 2026-08-29

### Cambiado

- **`tools/ops.js` se reemplaza y no cambia lo que hace.** Su comentario prohibía `import` y dos líneas
  después nombraba el `import()` dinámico que el propio shim usa; ahora dice que lo prohibido es el
  estático. Y citaba `team list`, un comando que dejó de existir. Se lo nombra porque vive en tu
  instancia: `upgrade` lo va a reemplazar y, hasta que lo corras, aparece como desactualizado.

## [0.53.1] - 2026-08-29

### Corregido

- **`init` no instalaba el motor en Windows.** Lanzaba `npm.cmd` directo, y la documentación de Node
  dice que un `.cmd` no es ejecutable por sí solo: ahora va por `cmd.exe` con el comando de argumento.
  Fallaba en silencio útil —imprimía el error de npm y dejaba `npm install` entre los pasos
  pendientes—, así que si venías corriéndolo a mano en Windows, ya no hace falta. Sin comprobar en un
  Windows real: acá no hay uno, y el comentario del código lo dice.

### Cambiado

- **Los trece shims de guards se reemplazan y ninguno cambia lo que bloquea.** Su comentario prometía
  «qué bloquea y cómo lo hace» en `engine/hooks/run.js`, que es un registro de una línea por guard: lo
  que bloquea vive en otro módulo, y el puntero mandaba al lugar equivocado. Los trece cambian sólo en
  comentarios, sin una línea ejecutable distinta. Se los nombra porque viven en tu instancia: `upgrade`
  los va a reemplazar y, hasta que lo corras, `automation check` los reporta desactualizados.
- **El motor se repartió en más archivos de los que tenía**, sin cambiar una sola conducta:
  `engine/automation/index.js` en cinco, `engine/agents/learning.js` en tres y `engine/hooks/run.js` en
  cuatro, cada uno cortado por lo que lo hace cambiar. Llega con `npm install` y no pide nada de tu
  parte; se menciona porque un `require` a una ruta interna del motor —que nunca fue superficie
  pública— puede haber dejado de resolver.
- Los recorridos que instala tu runner cambiaron de texto —comentarios y nombres internos, ahora en
  inglés como pide la convención—. Para recibirlos hay que reinstalar el wiring; si no lo hacés, los
  que ya tenés siguen funcionando igual.

## [0.53.0] - 2026-08-27

### Corregido

- **Dieciocho fuentes estaban clasificadas como rápidas y no lo son**, así que su cargo investigaba
  cada semana para leer el mismo texto. La cadencia sale de la fuente más veloz, y cada una se comprobó
  contra su propia fuente antes de moverla —no se dedujo del nombre—: `arc42` va de v8 (feb 2022) a v9
  (jul 2025); la especificación OpenAPI publica con meses o años de diferencia; el PSA `I-060923-PSA`
  del FBI es un documento fechado y cerrado; el GOV.UK Design System publica una vez al mes o menos; la
  guía de contenido útil de Google y su guía de estilo llevan meses sin cambiar; el archivo de «core app
  quality» de Android muestra años entre revisiones; Standard Webhooks no publica desde febrero; el
  Contributor Covenant va de 2016 a 2021 en tres versiones; Open Source Guides se toca cada dos a cuatro
  meses; la documentación de GitHub sobre moderación y sobre endurecer Actions lleva meses o más de un
  año sin cambios; la página de idempotencia de Stripe no expone fechas ni historial; y **Shopify publica
  una versión de API por trimestre**, en fecha fija. Las dieciocho pasan a `standard`, que es mensual.
  Los semanales bajan de 29 a 20.
- **Lo que no se pudo comprobar quedó donde estaba.** Apple Human Interface Guidelines y la guía de
  Gainsight no exponen fechas de revisión, y de Material Design sólo se pudo medir su implementación de
  referencia —4 a 9 meses entre versiones—, que no es la guía que el cargo cita. Las tres siguen
  semanales: moverlas por su nombre, o por el ritmo de otro artefacto, es lo que `R14` prohíbe.
- **Una URL con dos nombres ahora es un error.** `evaluate` lo rechaza dentro de un archivo —la cadencia
  sale del `tier` de cada entrada, así que dos copias de una fuente pueden decir cosas distintas sobre
  cada cuánto publica, y la más rápida gana sin que nadie lo decida— y una prueba del catálogo lo
  detecta entre cargos, que es la forma en que apareció. Se unificaron dieciséis: el catálogo pasa de
  229 fuentes a 211.
- **La especificación OpenAPI estaba tres veces con nombres distintos** —`OpenAPI Specification`,
  `...latest published` y `...3.2.0`—, así que corregirla en un cargo no la corregía en los otros.


## [0.52.0] - 2026-08-27

### Corregido

- **Lanzar el aprendizaje a mano ya no arrastra el ensamblaje mensual.** El `workflow_dispatch` corría
  las dos fases, así que probar la investigación de un cargo abría además su propuesta — y una
  propuesta cuesta una firma humana. Peor: consolidar **sella** los informes que consume, o sea que un
  lanzamiento de prueba movía estado real del ciclo. Ahora el dispatch acepta `phase`, con `research`
  por defecto, que es para lo que se lanza a mano; `propose` y `both` siguen disponibles cuando de
  verdad haga falta, y los crones deciden por su cuenta como siempre.
- **Los permisos de la investigación llegaban partidos, y `Bash` nunca se concedía.** Iban en una
  cadena sin comillas, así que el shell partía `Bash(make agent-learn *)` en tres palabras y el CLI
  descartaba la última —«Ignoring --allowedTools rule "*)"»—. La investigación salía bien igual porque
  con web y lectura alcanzaba, así que el defecto vivía en dos líneas del log y en ningún resultado.
- **La investigación late mientras corre y deja dicho lo que costó.** El log quedaba mudo los diez
  minutos que dura y no se distinguía de una corrida colgada: la única salida era esperar el timeout
  para saber cuál era. Ahora avisa cada minuto —el latido lo emite el shell y comprueba el proceso, así
  que no puede mentir que sigue vivo— y al cerrar deja en el resumen del job **cuánto costó en dólares,
  cuántos tokens y qué permisos se le negaron**. Eso último es lo que vuelve visible un permiso mal
  escrito, que es como se descubrió el de arriba.

## [0.51.0] - 2026-08-27

### Agregado

- **El guard `destructive` bloquea la forma ancha de `git checkout` y `git restore`.** `git checkout
  -- .` destruye lo mismo que `git reset --hard` —que ya se bloqueaba— y sin recuperación, pero se
  escribe como una limpieza. Se bloquea sólo cuando la ruta es `.`, `*`, `:/` o no hay ninguna:
  revertir un archivo nombrado es trabajo corriente y sigue pasando. Lo que engaña es que el alcance no
  se ve en el comando — `.` es el directorio actual, y ahí suele haber más de lo que uno está mirando.

## [0.50.0] - 2026-08-27

### Corregido

- **La investigación semanal recibe las herramientas que su instrucción nombra.** Corría sin permisos
  declarados, así que no podía correr un comando ni salir a la web —que es todo lo que una
  investigación hace— y devolvía el informe vacío en tres minutos. El cargo se portaba bien y lo decía:
  «no voy a fabricar fuentes, fechas ni salidas de comando para llenar el informe». Ahora se le declaran
  las que su `AUTOMATION.md` nombra —buscar, leer, escribir su informe, `make agent-learn` y
  `make agent-evaluate`— y sólo ésas: saltear permisos le daría justo lo que su instrucción le prohíbe.
- **Un informe sin contenido ya no abre PR.** Que el archivo exista no alcanza: `learn` lo crea vacío y
  el modelo puede devolverlo tal cual, y así se publicaba. Un lunes eso son veintinueve PRs en blanco
  sin que nada lo diga. Ahora se miran las secciones que la propuesta mensual consolida, y si están
  vacías el job falla nombrando cuáles y dónde mirar la causa.

## [0.49.0] - 2026-08-27

### Agregado

- **`R22` — lo que se mide no se toca mientras se mide.** Mientras una medición corre, el sujeto y todo
  aquello contra lo que resuelve se quedan quietos. Lo difícil es que no avisa: la corrida termina y su
  resultado se lee igual que uno limpio, así que quien lo reciba decide sin saber que se movió el piso.
  Y entra por donde no se mira — un banco desechable puede resolver la herramienta por un enlace al
  repositorio vivo, así que editar ahí cambia lo que la corrida lee sin tocar el banco. Si hace falta
  trabajar igual, se trabaja donde la medición no mira; y si ya pasó, se dice qué cambió y cuándo: un
  resultado cuyo entorno se movió es una hipótesis, no un veredicto.

### Corregido

- **`dependsOn` dejó de ser decorativo: un recorrido corre sus etapas como su contrato las declara.**
  Hasta ahora el motor las corría en fila y le pasaba a cada una los handoffs de **todas** las
  anteriores, sin mirar de qué decía depender. `technical-design` existe para tener tres lecturas
  independientes de un mismo encuadre y no las tenía: su propio guardrail dice que las tres «no negocian
  entre sí ni ajustan su hallazgo para que cierre con el de otra», y una que ya leyó a la primera no
  puede cumplirlo. Ahora las etapas se agrupan por nivel de dependencia, las de un mismo nivel corren a
  la vez, y cada una ve **sólo** los handoffs de aquello de lo que declara depender. Cinco de los siete
  recorridos del catálogo tienen un nivel con paralelismo real y lo estrenan con esto. Una etapa que
  **no** declara `dependsOn` sigue viendo todo lo anterior, igual que antes: independencia es una
  afirmación, y una afirmación se declara. Si una etapa no cumple su gate, el corte es por nivel — las
  que corrieron con ella entran igual al handoff, y lo que se abandona son los niveles siguientes.
- **Un recorrido que frena da igual la salida que su contrato reserva para no poder.** `change-review`
  enumera tres veredictos y el tercero es «no poder aprobar»; bloqueado, su informe parcial escribía
  «No hay veredicto», que no es ninguno de los tres. Con el diff, los importadores y las pruebas a la
  vista, un revisor **sí** puede firmar que no aprueba: eso es un veredicto, no su ausencia. Vale para
  cualquier recorrido cuyo contrato tenga esa salida — un destino de «nada que hacer», una
  recomendación de investigar.
- **Cambiar el `flow.json` de un recorrido ahora envejece sus veredictos.** El aviso de «el contrato
  cambió y la última corrida es anterior» miraba sólo el `SKILL.md`, que un recorrido no tiene, así que
  no se disparaba nunca: se le podía agregar una dimensión a un gate y sus casos aprobados seguían
  leyéndose vigentes. Qué archivo **es** el contrato depende del sujeto — el de un cargo es su
  `SKILL.md`, el de un recorrido su `flow.json`— y ahora se mira el que corresponde.
- **`ops context` ya no se traga las acciones humanas cuando no hay tarea en cola.** Se imprimían
  después del `return` de «sin tarea disponible», así que una instancia recién arrancada —`onboard`
  deja filas pendientes y ninguna tarea todavía— preguntaba qué toca ahora y recibía «nada», cuando lo
  que tocaba era que una persona desbloqueara siete cosas. Lo destapó una corrida de un recorrido sobre
  su banco.

## [0.48.0] - 2026-08-26

### Corregido

- **`destroy` ya no deja el `package.json` que escribió `init`.** En modo `embedded` quitaba sus nueve
  rutas y decía «tu repositorio queda donde está», dejando un manifiesto cuya única dependencia era el
  motor. Sobre un repositorio Rust eso es basura conspicua y nadie avisaba. Ahora saca su clave y nada
  más: si el manifiesto lo había creado `init` —sin dependencias ni scripts propios— se va con él, y si
  es tuyo se conserva entero con tus scripts y tus dependencias. `node_modules/` y el lockfile no se
  tocan porque los escribe npm y pueden tener lo tuyo, pero la salida ahora los nombra para que decidas.

## [0.47.0] - 2026-08-26

### Agregado

- **`organization/company.md` gana «Qué no se puede romper»**, una tabla donde declarás qué superficies
  detienen la operación al fallar, a quién alcanzan y dónde viven. No todo cuesta lo mismo cuando se
  rompe, y sin eso escrito una revisión sólo puede elegir entre revisar todo con el rigor del peor caso
  o revisar el peor caso con el rigor de lo demás. Lo que no se sepa se deja en `Por definir`: que no
  conste dónde vive un flujo crítico ya es un hallazgo.
- **`change-review` pregunta si el cambio toca una de ellas.** El gate de `scope` ahora lo exige y
  `completion` lo enumera: si toca, cuál y qué se detiene; si no, que se miró. Se contrasta contra tu
  tabla y no contra el criterio de quien revisa, y una tabla vacía se reporta como hallazgo en vez de
  pasar por permiso. Sin esto, un cambio de una línea en un helper compartido se revisaba igual
  estuviera en reportes o en el alta de un pedido.

## [0.46.0] - 2026-08-25

### Agregado

- **`R21` — retomar empieza por establecer qué quedó hecho.** Un trabajo que se corta deja trabajo
  hecho, y «continuá» pide seguir desde ahí, no volver a lanzarlo: primero se mira qué artefactos y qué
  resultados ya existen, y se corre la diferencia. Cuando el pedido sí es relanzar de cero, primero va
  el veredicto de lo avanzado y decide quien pidió — puede querer la corrida entera y hay razones
  legítimas, pero tirar trabajo es una decisión, no un default. Ese veredicto trae con qué decidir: qué
  ya tiene resultado, qué quedó a medias **y si sirve** —son dos preguntas y la segunda no se contesta
  viendo que el archivo está—, cuánto cuesta rehacer cada parte, y cómo se puede partir, porque casi
  nunca es todo o nada. Puede terminar en «desde cero» sin que eso lo invalide: lo que la regla impide
  no es relanzar, es relanzar sin saber. Lo que ya tiene resultado no se vuelve a medir por venir en la
  misma tanda, y un mecanismo de reanudación se comprueba en vez de suponerse: `R14` no hace excepción
  con las herramientas propias.
- **`R14` se recorre antes de entregar, como `R15`.** Se repasan los artefactos que se van a leer solos
  —la fila de acciones humanas, la lección del INBOX, el paso de runbook, la respuesta escrita— y por
  cada afirmación de mecanismo se comprueba que llegó con su registro. Es donde el rótulo se pierde: el
  informe clasifica bien y la copia sale en plano, y releer no lo encuentra.
- **El ensamblaje mensual también consolida recorridos.** Hasta ahora el ciclo de un recorrido existía
  pero nada lo disparaba: la automatización sólo veía cargos. Ahora `flow list --json` los expone y el
  cron del día 1 abre la propuesta de cada recorrido que tenga corridas sin consolidar. No investiga
  —un recorrido no tiene profesión— y sólo entra el que dejó veredictos que mirar.

- **`R17` ahora tiene dos barras, y `check` las cuenta.** La regla decía que una aceptación de más de
  cinco condiciones se parte, y nada lo medía: una épica de veinte criterios pasaba `check` sin una
  queja. Ahora falla al cruzar **5 criterios en una tarea, 7 en una épica, 9 tareas en un hito**. Lo
  que falla no es el tamaño: son disparadores y no límites, y dejar la unidad entera es una salida
  legítima —igual que `R7` con el código—. Lo que falla es cruzarlos **sin decidir**. Si es un solo
  resultado, se escribe `(sin partir: <razón>)` donde vive la unidad —la misma forma parentética que
  `(service: …)`, en el archivo de la épica, el encabezado del hito o la línea de la tarea— y pasa en
  silencio. Los umbrales son fijos y no se configuran: la salida ya está adentro de la regla y deja
  rastro en el artefacto, mientras que un número que se sube el día que molesta no lo deja. Se cuenta
  lo estructurado; la aceptación escrita en prosa la sigue mirando el review.
- **`R17` incorpora el tope de esfuerzo, que hasta ahora era sólo texto en la plantilla.** Las cuatro
  horas no miden lo mismo que el conteo de condiciones y ninguna de las dos ve lo de la otra: «crear la
  página de inicio» tiene **una** condición de aceptación y son tres días: no está acumulada, está sin
  pensar. La barra obliga a la conversación antes —qué mensaje, qué interacción, qué ya está
  decidido— y de ahí salen tareas que se pueden mirar. Medir una sola de las dos deja pasar la mitad
  de los casos.

### Corregido

- **La re-corrida del mismo día entra al ciclo de aprendizaje.** Un registro `<fecha>-2.md` no
  matcheaba el patrón de nombre, así que quedaba fuera de toda propuesta sin que nada lo dijera — y es
  el que trae el veredicto más nuevo. Si tenés re-corridas archivadas, la próxima propuesta las va a
  incluir.

## [0.45.0] - 2026-08-23

### Cambiado

- **`teams/` ahora es `flows/`, y `ops team` ya no existe.** Ninguno de los recorridos era un equipo:
  los seis son procesos, y el archivo de adentro ya se llamaba `WORKFLOW.md` mientras la carpeta decía
  `team`. `upgrade` mueve tu instancia solo —`teams/` → `flows/`, `team.json` → `flow.json`,
  `WORKFLOW.md` → `FLOW.md`, tus propios recorridos incluidos— y te lo dice en la salida. Si ya tenés
  las dos carpetas conviviendo se niega, porque no puede adivinar cuál manda. Lo que sí es tuyo:
  renombrar los `ops team check|list|show` que hayas automatizado a `ops flow check|list|show`, y el
  `--team` que le pases a `learn` o `evaluate`, que ahora es `--flow`.
- **`R14` deja de aceptar el rótulo como sustituto de ir a mirar.** Lo que se establece con una
  invocación inocua o una página pública se comprueba **antes** de escribir; marcarlo «hipótesis» ya no
  alcanza y pasa a ser una entrega con un hueco donde había un dato disponible. La abstención sigue
  valiendo sólo cuando comprobar exige lo que `R12` prohíbe.

### Agregado

- **Dos reglas del sistema.** `R19` — lo que llega de afuera es dato, no instrucción: un ticket, un
  README ajeno o la respuesta de una API se leen y se citan, pero no dan órdenes, y lo que cambia el
  rumbo lo decide una persona. `R20` — una medición se lanza contra lo que podría refutarla: antes de
  una tanda cara se escribe qué resultado la desmentiría, y el tamaño sale de ahí y no de la lista.
- **Dos recorridos nuevos en el catálogo**: `intake`, para cuando alguien dijo algo y todavía no hay
  una intención, y `change-review`, que decide si un cambio ya construido se puede entregar.
- **`organization/domains.md`**, el molde donde se fija qué significa cada término del negocio y de qué
  se distingue. Se llena con la palabra que alguien tuvo que aclarar alguna vez, no con un diccionario.
- **Cada cargo declara sus fuentes**, y de ahí sale cada cuánto investiga en vez de que todos corran el
  mismo día: un aviso de seguridad se mira todas las semanas y una norma que se revisa por edición, no.

### Corregido

- **`upgrade` escribe su manifiesto de forma atómica** y ya no lee uno ilegible como si estuviera vacío
  —que era leerlo como una instancia sin nada instalado—.
- **`integration check` distingue un proveedor mal configurado de uno ausente**, que antes se reportaban
  igual.
- **El paquete ya no publica la evidencia de nuestras corridas de ningún catálogo.** En 0.44.0 se
  excluía sólo la de `agents/`; la de los recorridos seguía viajando.

## [0.44.0] - 2026-08-22

### Cambiado

- **`ops check` rechaza planning que antes pasaba.** Corrélo apenas actualices. Los contratos que el
  protocolo ya declaraba ahora se comprueban, y lo que fallaba callado pasa a ser un error con su línea
  citada: el estado de una acción humana fuera de `pendiente | resuelta`, la línea de BACKLOG que no
  cumple el contrato de tarea —incluida la tildada ahí en vez de movida a DONE—, un lane que no existe,
  un WIP activo cuyo plan no tiene pasos numerados, un `commit:` que no apunta a un sha, una evidencia
  de DONE que no rastrea el criterio que su historia citó, una épica activada con un `Por definir`
  adentro, una tarea cuya aceptación no está decidida, un ADR sin estado del vocabulario o sin sus
  secciones, y dos reglas con el mismo número.
- **Las reglas propias del proyecto se numeran `P1..Pn`**: `R` queda reservado al sistema. Si escribiste
  una regla propia con un `R`, renumerala, o llevala a un archivo con el mismo nombre que el del sistema
  —que es como se declara un override—.
- **Tres reglas cambian y hay dos nuevas.** `R8` corta los commits por naturaleza del diff y no por
  tarea; `R7` usa los umbrales como disparador de una revisión en vez de prohibir números; `R9` pide
  romper lo que el caso dice cuidar y verlo en rojo, porque el rojo previo sólo prueba ausencia. Se
  agregan `R17` —la aceptación que pasa de cinco condiciones se parte— y `R18` —un doble de prueba se
  justifica y los datos salen de una fábrica—.
- **La guía de `planning/delivery/` es del toolkit y se reemplaza entera.** Hasta ahora se quedaba en la
  versión del día que instalaste, así que ninguna mejora te llegaba. Si editaste alguno de sus seis
  archivos, `upgrade` se niega: llevá lo tuyo a `delivery/project.md`, que sigue siendo del proyecto, o
  repetí con `--force`.
- **El paquete ya no publica la evidencia de nuestras corridas de evaluación** y la instalación baja de
  7 MB a menos de 2. Los contratos de cada cargo —`SKILL.md`, `references/`, casos, fuentes e historial—
  llegan igual.

### Agregado

- **`ops archive <planning> human-actions`** saca las filas resueltas a `done/human-actions.md` y deja
  legible lo que queda por decidir.
- **`ops context` nombra por qué paró.** Con toda la cola trabada por una persona imprime
  `BLOCKED  blocked-on-human` con la fila que la destraba, en vez de decir que no hay tarea disponible,
  que era lo mismo que dice una cola vacía. El vocabulario completo de paradas está en `PROTOCOL.md`.
- **Dos decisiones del sistema**: `OPS-005`, el catálogo viaja dentro del paquete; `OPS-006`, la
  ceremonia escala con la superficie del cambio.

### Corregido

- **Un criterio escrito en dos líneas se truncaba.** La tarea que heredaba su aceptación recibía sólo la
  primera, que suele ser la mitad sin el cómo se verifica. Revisá las épicas con criterios de más de un
  renglón: lo que el runner recibe cambia.
- **`upgrade` daba el consejo ajeno** al negarse sobre un documento del toolkit —hablaba de desactivar
  guards—. Ahora cada clase de archivo escucha la salida que le corresponde.
- **Los hallazgos de una revisión iban a la sección equivocada del INBOX.** Un cambio del producto va a
  Propuestas, lo aprendido sobre cómo trabajamos a Lecciones, y una pregunta abierta a Ideas.

## [0.43.0] - 2026-08-20

### Agregado

- **Codex recibe los cinco recorridos como skills.** Hasta ahora su adaptador lo declaraba incapaz de
  skills y el arranque le llegaba sólo como prosa dentro de `AGENTS.md`, con el recorrido a pie. Era un
  dato cierto cuando se escribió el adaptador y dejó de serlo sin que nadie lo revisara. Ahora
  `automation install` deja `$onboard`, `$team`, `$autobuild`, `$integration-sync` e
  `$integration-promote` en `.agents/skills/` —una de las rutas que Codex escanea— junto con el catálogo
  de cargos, y se invocan con `$`. **Reinstalá el adaptador** para recibirlas; el `hooks.json` no cambia,
  así que la confianza que le hayas dado sigue valiendo.

## [0.42.0] - 2026-08-20

### Corregido

- **Los guards de Codex no corrían nunca.** Tres cosas a la vez, y ninguna hacía ruido: el archivo se
  instalaba en `.codex/hooks/hooks.json` —esa forma es la que empaqueta un plugin; un repositorio se lee
  en `.codex/hooks.json`—, los `matcher` filtraban nombres del protocolo interno en vez del nombre de la
  herramienta (`Bash`, `apply_patch`/`Edit`/`Write`), y el comando era una ruta relativa mientras Codex
  ejecuta el hook con el cwd de la sesión, así que desde cualquier subdirectorio no existía. **Reinstalá
  el adaptador**, borrá a mano el `.codex/hooks/hooks.json` que queda huérfano, y confiá los hooks con
  `/hooks` dentro de una sesión: Codex los saltea en silencio hasta que lo hagas, y hay que repetirlo
  cada vez que el wiring cambie.
- **El guard de archivos no veía nada bajo Codex.** `apply_patch` manda el parche entero como
  `tool_input.command`, no como `patch`, y sin reconocer ese campo el guard miraba una escritura y no
  encontraba un solo archivo: reescribió una migración existente en una corrida real, y lo mismo valía
  para secretos y para los límites del workspace.
- **Los guards de Antigravity tampoco corrían.** `agy` ejecuta la copia del plugin que registró en
  `~/.gemini/config/plugins/` y resuelve las rutas contra la carpeta del plugin, así que la del
  workspace apuntaba a un módulo inexistente; su payload además no nombra el workspace —manda
  `workspacePaths` vacío y un `Cwd` que apunta al scratch del CLI o al home—, de modo que la raíz ops
  ahora se escribe como ruta absoluta al instalar. **Reinstalá y volvé a registrar** con
  `agy plugin install .agents/plugins/cauce` cada vez que cambie el wiring.
- **Un puente que no arrancaba dejaba al agente sin poder cerrar la sesión.** En Antigravity, un guard
  que bloquea y un fallo de infraestructura devolvían los dos `continue`; ahora sólo el bloqueo lo hace,
  y la falla deja cerrar con la razón a la vista.
- **Los guards resolvían rutas relativas contra la carpeta equivocada en Antigravity**, que no es la del
  proyecto: un guard que juzgaba `src/x.js` estaba juzgando otro archivo.
- **Instalar acumulaba las entradas de hooks de versiones anteriores.** Una ruta nuestra que cambiaba
  dejaba viva la anterior, el runner la ejecutaba, no encontraba nada y el guard no corría. Ahora se
  reemplazan las nuestras y se conserva lo que agregó el proyecto.
- **Un cargo con el mismo nombre que un recorrido lo pisaba en silencio**, porque comparten el espacio de
  skills del runner. La instalación ahora se detiene y dice cuál: renombrá el cargo en `agents/roles/`.
- **Gemini invocaba `/ops:autobuild`**, un prefijo que ya no existe, y **Antigravity anunciaba sus
  recorridos sin la barra** (`cauce:onboard` en vez de `/cauce:onboard`).
- **La regla de Antigravity apuntaba a un `AGENTS.md` sin prefijo**, que en modo sidecar es el del
  repositorio de producto y no el que trae las reglas del sistema.

### Cambiado

- **El arranque de Gemini vive ahora en su comando.** `GEMINI.md` remite a `/cauce:onboard` en vez de
  repetir el recorrido, y el comando trae la lista completa de lo que se comprueba al final —incluidos
  los dos puntos que la copia había perdido—.

## [0.41.0] - 2026-08-19

### Corregido

- **`destroy` ya no borra el repositorio en modo embebido.** Ahí la instancia **es** el repositorio, así
  que borrar la carpeta se llevaba el código del producto: pasó de verdad sobre un caso de prueba, y el
  aviso previo enumeraba épicas y acciones humanas sin nombrar lo único irrecuperable. En embebido saca
  lo que Cauce escribió —`planning/`, `organization/`, `teams/`, `integrations/`, `automatization/`,
  `tools/`, la configuración y el manifiesto— y deja el repositorio. En sidecar sigue borrando la
  carpeta, que ahí no contiene nada más.

## [0.40.0] - 2026-08-19

### Corregido

- **En modo embebido, Codex se quedaba sin instrucciones.** `AGENTS.md` es el único nombre que el runner
  y la instancia comparten, y ahí `install` conservaba el archivo entero —lo correcto para algo del
  proyecto—, así que el recorrido que ese runner tenía que seguir nunca llegaba a estar escrito. Ahora su
  contenido se fusiona adentro, entre marcas: reinstalar reemplaza el bloque en vez de duplicarlo,
  desinstalar lo saca sin llevarse el archivo, y todo lo que la empresa escribió alrededor queda intacto.

  `doctor` lo tenía al revés en ese archivo: comparando el hash completo avisaba cuando el bloque estaba
  bien —el texto de la empresa alrededor difiere siempre— y decía «operativo» cuando alguien lo había
  borrado, que es la falla que importa.

## [0.39.0] - 2026-08-19

### Corregido

- **`check` nombra las credenciales que nadie se llevó.** El arranque tiene que dejar una fila por cada
  variable que el proyecto declara, y en cuatro corridas reales se cumplió en proporción a lo que se
  habló: las que sólo estaban en el inventario quedaron afuera —entre ellas el broker por donde entran
  los datos y el servicio al que se le mandan errores—. Ahora `check` nombra las que no aparecen ni en el
  mapa ni en `HUMAN_ACTIONS.md`. Mira sólo cuando la instancia ya tiene contexto escrito, porque antes no
  hay dónde tendrían que estar, y avisa en vez de fallar: una variable sin dueño no rompe nada hoy, rompe
  el día que alguien tiene que desplegar.

## [0.38.0] - 2026-08-19

### Añadido

- **Gemini recibe `onboard` y `team`, que sólo tenía como prosa.** Estaban descritos en `GEMINI.md` y no
  existía ningún comando detrás, así que quien venía de otro runner los buscaba en la lista y no
  aparecían.

- **`automation install` termina diciendo cómo se invoca lo que acaba de instalar.** Una línea con los
  nombres exactos para ese runner, que es lo que evita buscar en una lista de cincuenta skills el nombre
  que se usó en otro lado.

### Cambiado

- **Una convención para el nombre de cada recorrido.** El nombre es el mismo en todos los runners
  —`onboard`, `team`, `autobuild`, `integration-sync`, `integration-promote`—; el prefijo lo pone cada
  uno según su espacio de nombres: `/onboard` en Claude, `/cauce:onboard` en Gemini, `cauce:onboard` en
  Antigravity. Codex no tiene comandos y opera el protocolo desde sus instrucciones.

  Los comandos de Gemini se mudaron de `/ops:` a `/cauce:`. Si actualizás una instalación anterior,
  `.gemini/commands/ops/` queda huérfano: se borra a mano, `install` no toca lo que ya no declara.

## [0.37.0] - 2026-08-19

### Cambiado

- **El arranque deja de completar una dimensión que nadie preguntó.** Al llegar al tope de tres
  preguntas puede quedar una afuera —quién lo usa, qué está muerto, qué externo hay—, y una corrida real
  la escribió igual, deducida del README y de las respuestas a las otras. Todo plausible, nada dicho por
  una persona, y sin marca: se leía con el mismo peso que lo que alguien contestó. Ahora esa dimensión
  queda «Por definir» con su pregunta en el INBOX, y lo que se deduce de otra respuesta va marcado
  `(supuesto)` por plausible que sea. Rige en el recorrido ejecutable y en los tres runners que operan el
  arranque leyendo instrucciones.

## [0.36.0] - 2026-08-19

### Corregido

- **`automation doctor` ejecuta el puente del runner en vez de darlo por bueno.** Instalado no es
  operativo: un puente que el runner no puede lanzar falla cerrado y niega cada llamada a herramienta,
  y `doctor` decía «adaptador operativo» porque los archivos estaban en su lugar. Ahora lo invoca como
  lo hace el runner —una vez desde la raíz del workspace y otra desde otra carpeta, porque una ruta
  relativa en su configuración deja de resolver cuando el cwd no es el que suponía— y un puente que no
  responde es error, no advertencia.

### Cambiado

- **La lista de salida termina en el chequeo más barato: `ops onboard`.** Si vuelve a ofrecer la
  pregunta de apertura, la instancia sigue vacía y no hay nada que informar. Una corrida real entregó
  cuatro puntos en pasado —«registrado», «documentado», «creada la primera épica»— sobre archivos que en
  el disco seguían siendo el molde. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.

## [0.35.0] - 2026-08-19

### Añadido

- **`ops destroy <ops-root>`: sacar una instancia entera, en un comando.** Enumera qué se pierde
  —épicas, cola, trabajo terminado con su evidencia, acciones humanas pendientes, el contexto escrito— y
  para. Sólo un segundo intento con `--force` desinstala cada runner cableado y después borra la
  instancia. El orden no es negociable: borrar la carpeta antes que el wiring deja al runner ejecutando
  guards que ya no existen, que es exactamente lo que pasaba haciéndolo a mano. También como
  `make destroy`, que muestra el resumen sin borrar.

### Corregido

- **Una épica que nadie va a leer deja de pasar por válida.** Una corrida real escribió
  `roadmap/epic-001.md`, sin slug: el parser busca `epic-NNN-<slug>.md`, así que `check` respondía
  «planning válido: 0 épica(s)» con la épica ahí, y `autobuild` nunca habría encontrado trabajo en ella.
  Renombrarla destapó dos errores que el silencio tapaba —un `status` inválido y un criterio sin historia
  que lo cubriera—. Ahora cualquier archivo del roadmap que se llame como una épica y el parser no vaya a
  leer es un error que dice cómo nombrarlo.

- **`check` avisa cuando una dimensión del molde desapareció de `organization/`.** Un agente que reescribe
  esos archivos se queda con el contenido y pierde la estructura: el resultado se lee entero y completo, y
  la dimensión que falta no la va a reclamar nadie. Va como advertencia —esos archivos son de la empresa y
  reestructurarlos a propósito es legítimo—, y agregar secciones propias no dice nada.

- **Volver a una versión anterior deja de anunciarse como una actualización.** `upgrade --check` decía
  «hay una versión más nueva: 0.33.0» contra una instancia en 0.34.0. Ahora dice que volvés, y te imprime
  las entradas que dejás de tener en vez de las que ganarías.

### Cambiado

- **Los runners sin recorrido ejecutable llevan una lista de salida.** Codex, Gemini y Antigravity operan
  el arranque leyendo instrucciones, y una corrida real falló cinco puntos de una vez: preguntas en
  formulario, una épica sin slug, `organization/` reestructurado, `HUMAN_ACTIONS.md` vacío y `ops` listado
  como servicio del producto. Todos del mismo lado: lo que no deja un archivo visible. La lista dice qué
  se comprueba mirando el disco, que es lo que la prosa pidiendo cuidado no consigue.

## [0.34.0] - 2026-08-19

### Añadido

- **R16: el costo es el contexto, no las palabras.** Regla nueva del sistema, en
  `planning/rules/system/process.md`, así que rige en cada tarea la haga quien la haga. Cada llamada
  reenvía el contexto entero: gasta más quien da más vueltas que quien escribe más. Comandos
  independientes en una sola invocación, el CLI antes que el archivo, el fragmento antes que el archivo
  entero, un subagente sólo cuando el trabajo no entra en la corrida actual, y un archivo se lee una vez
  y se escribe entero en vez de releerlo para confirmar.

  Cierra con la parte que la hace segura y que una regla de eficiencia escrita a las apuras suele
  omitir: **verificar es la excepción y no se negocia**. Ahorrar una llamada nunca justifica afirmar sin
  haber comprobado —R14 no admite descuentos— ni dar por terminado lo que no se corrió.

  No nombra precios, ventanas de caché ni cuándo limpiar una conversación: eso lo fija el runner, y una
  regla del sistema que lo nombrara envejecería con su próxima versión en todas las instancias a la vez.

### Cambiado

- **El arranque acota las vueltas dentro de cada fase, no sólo cuántas fases hay.** El techo de tres
  agentes limitaba cuántos contextos se cargan, no cuántas veces se reenvía cada uno. Medido en una
  corrida real: la fase que escribe gastó veinte vueltas —seis lecturas y tres ediciones— para producir
  cuatro archivos, y los tres agentes sumaron tres millones de tokens de caché, un cuarto de la sesión.
  Ahora cada fase sabe qué cuesta: leer una vez y sólo para escribir, escribir entero de una, no releer
  para comprobar, y correr `check` una sola vez.

## [0.33.0] - 2026-08-19

### Corregido

- **Con varias raíces declaradas, ningún servicio tenía nombre.** Cada repositorio es la raíz de su
  propio escaneo, así que su proyecto principal volvía como `.`: tres servicios llamados igual y nada que
  los distinga. Una credencial no se podía atribuir a un servicio porque ningún servicio era nombrable, y
  una corrida real terminó pidiéndole a una persona que declarara variables que su propio repositorio ya
  declaraba. Ahora cada candidato lleva el nombre de su raíz, y donde hay una sola el proyecto de arriba
  se nombra por su carpeta en vez de aparecer como `.` a secas.

- **El arranque podía negar lo que el inventario le entregó.** Se le dice explícitamente que un nombre
  que el inventario trae está declarado, así que pedir que se declare de nuevo contradice al repositorio.
  La mitad de la corrección anterior que tocaba el recorrido —la que le hace copiar los nombres de
  variable por servicio— no había llegado a publicarse.

## [0.32.0] - 2026-08-19

### Corregido

- **Las credenciales de un multirepo no existían para el arranque.** `/onboard` leía un solo
  `.env.example`, el de la raíz del workspace: en un monolito eso es todo, entre repositorios hermanos no
  es nada. Una corrida real sobre tres repos dejó filas que decían «la credencial del proveedor» sin
  nombrar una sola variable, y un externo que sólo ese archivo mencionaba —un certificado de organismo
  fiscal— no apareció en ningún lado.

  Ahora `ops scan` reporta, por servicio, los nombres que declara su ejemplo y de qué archivo salieron, y
  el arranque los usa para nombrar cada fila. **Sólo los nombres**: el valor es de una persona, y el
  ejemplo es la mitad pública del par. Se leen `.env.example`, `.env.sample`, `.env.template` y
  `.env.dist`; un ejemplo de más de cuarenta variables es un archivo generado y no un contrato, así que
  se corta ahí y lo dice.

- **`ops scan <ruta>` dejaba afuera al proyecto de la raíz.** Un monolito que declara sus comandos en el
  nivel de arriba volvía listando todo menos a sí mismo, mientras que desde adentro de la instancia el
  mismo árbol sí lo listaba: dos respuestas distintas sobre el mismo directorio. Los dos caminos comparten
  una sola función y el proyecto de la raíz va primero.

## [0.31.0] - 2026-08-18

### Añadido

- **`ops automation uninstall <ops-root> <runner>`: sacar el wiring sin llevarse lo tuyo.** Hasta ahora
  desinstalar era borrar la carpeta ops y descubrir después que cada llamada de herramienta ejecuta un
  guard que ya no existe; la otra salida —borrar `.claude/` entero— se lleva puesto lo que hayas puesto
  vos. El comando quita lo que Cauce entregó **y sigue igual que como lo entregó**: sus guards de la
  configuración del runner, sus workflows, sus punteros a cargos. Tus hooks, tus workflows y tus skills
  quedan donde están, y un archivo del toolkit que hayas editado se conserva y aparece nombrado en la
  salida, porque decidir sobre él es tuyo. La instancia no se toca: borrarla es una decisión aparte.
  También como `make uninstall-claude` y sus equivalentes.

### Corregido

- **Una instancia dejaba de enterarse de que había versiones nuevas.** `init` fija la versión exacta del
  motor, así que `npm update` no la mueve y `upgrade` compara contra el que está instalado: quien
  actualizaba con `make upgrade` recibía «la instancia está al día» en todas las versiones siguientes, sin
  nada que le dijera que la comparación era local. Ahora la salida nombra contra qué comparó y da el
  comando que trae un motor nuevo, y `make upgrade` lo corre antes de aplicar.

  Actualizar son tres pasos y el tercero sigue siendo aparte: `npm install --save-dev
  @ingeniomaps/cauce@latest`, `ops upgrade .` y `ops automation install . <runner>`. Los workflows y las
  skills viven en el runner, no en la instancia, y `upgrade` lo recuerda al terminar.

### Cambiado

- **`init` termina en un solo cierre, y sus opciones se leen como una selección.** Las tres líneas finales
  —cada una con tono de última— pasaron a ser una con la acción concreta, y las opciones de runner e
  integración van una por línea con el default marcado donde se mira: `5) ninguno   ← Enter`, en vez de un
  `[ninguno]` pegado al prompt.

## [0.30.0] - 2026-08-18

### Añadido

- **`ops onboard`: qué le falta a una instancia para arrancar, y con qué pregunta empezar.** Determinista
  y en milisegundos: dice si la instancia sigue vacía, qué hay en el workspace y qué queda por cubrir.
  `init` lo imprime al terminar, que es donde mira quien acaba de instalar y todavía no sabe qué hace la
  herramienta. Cuando `organization/` o el roadmap ya tienen contenido real no ofrece nada, porque habría
  trabajo que pisar.

### Cambiado

- **El arranque pregunta en vez de negarse, y pregunta primero.** Invocado sin contexto, `/onboard`
  terminaba diciendo «volvé a correrlo con contexto» —y gastaba un subagente para decirlo—. Ahora la
  primera línea es la pregunta, sea el workspace vacío, un monorepo o diez repos, y el inventario viene
  después: de qué trata el proyecto no depende de lo que haya en el disco. Cada runner recibe la
  instrucción de abrir con esa pregunta antes de mirar nada.

- **El recorrido tiene techo: una llamada cuando falta contexto, tres cuando hay con qué escribir.** Y
  ninguna sale a explorar —tiene prohibido recorrer directorios, leer código fuente y abrir archivos que
  no vaya a escribir—, porque lo que necesita ya está resuelto: un arranque que te hace esperar diez
  minutos dejó de ser un arranque.

- **El escaneo mira las raíces declaradas en `ops.config.json`, y nada por encima.** Antes suponía la
  carpeta madre en modo sidecar; ahora acotar `workspaceRoots` acota también lo que se lee, que es más
  rápido y es lo único que alguien autorizó.

- **Y saltea lo que tu proyecto ya declaró basura.** Además de los directorios de paquetes y de los
  ocultos, lee el `.gitignore` de cada raíz y omite los nombres de directorio que encuentre ahí. Los
  interpreta de forma ancha —por nombre, a cualquier profundidad, salteando todo patrón con comodín—:
  alcanza para decidir si vale la pena mirar adentro, y no es un parser de gitignore. Las listas largas
  se recortan a veinte en pantalla diciendo cuántas quedaron afuera; `--json` las trae todas.

- **El arranque declara para qué es.** Entender qué es el proyecto, dejar la instancia correcta para él y
  que la primera tarea pueda empezar: eso, y nada más. El análisis profundo llega cuando alguien pide
  algo concreto, y adelantarlo retrasa el único momento en que la herramienta todavía no sirve para nada.
  Está escrito en cada prompt del recorrido y en las instrucciones de los cuatro runners.

- **Las preguntas dejan de ser un formulario, y de dar por sentado que el proyecto vende algo.** Antes
  eran cuatro fijas, y la primera preguntaba qué vende la empresa y a quién: a un proyecto libre, interno
  o sin fines de lucro le pedían una respuesta que nadie había dado. Ahora hay una sola pregunta escrita
  —de qué trata el proyecto, la única que no depende de ninguna respuesta— y lo que el motor fija después
  son las dimensiones a cubrir: a quién sirve, cómo se sostiene, qué querés que pase, qué está fuera de
  alcance, qué externos hay que conectar. Quien conduce la conversación las formula con las palabras de
  ese proyecto, una por vez y hasta tres.

- **El molde de `organization/company.md` deja de asumir un negocio.** «Modelo de negocio», «quién paga»
  y «fuentes de ingreso» pasan a ser «De qué se trata», «A quién sirve» y «Cómo se sostiene», que nombra
  donación, presupuesto interno y trabajo voluntario junto con la venta. Lo que no aplica se dice, no se
  completa con algo plausible. Afecta a las instancias nuevas: `upgrade` sólo reemplaza `system/`, así
  que el archivo que ya escribiste sigue siendo tuyo.

## [0.29.0] - 2026-08-18

### Añadido

- **`ops scan`: qué hay en el workspace, resuelto por código.** Lista los subproyectos con manifiesto
  propio, su runtime y los comandos de test, lint y build que cada uno declara, diciendo de qué archivo
  salió cada comando. No corre ninguno y no inventa ninguno: un comando que nadie declaró se lee igual
  que uno real, y el primer Verify de una tarea es donde eso se descubre. Saltea `node_modules` y todo
  directorio oculto, que es lo que lo mantiene en milisegundos. Una instancia sidecar escanea su carpeta
  madre, donde vive el código; cualquier otro modo, donde está parada.

### Cambiado

- **`/onboard` cuesta lo que encuentra.** Empezaba pidiéndole a un agente que explorara el repositorio y
  terminaba corriendo la suite de tests de cada servicio: una corrida sobre una carpeta vacía gastó doce
  minutos sin poder producir nada. Ahora arranca con `ops scan` —una llamada, milisegundos— y, si el
  workspace no tiene código y nadie aportó contexto, termina ahí diciendo qué le falta, en vez de escribir
  una empresa inventada.

  El recorrido ya no ejecuta nada del proyecto. El mapa real anota cada comando **tal como está
  declarado**, con su archivo de origen, y verificarlo corriéndolo pasa a ser una historia de la épica
  001, donde tiene dueño y tiempo asignado. Lo que escribe y lo que deja a una persona no cambió:
  borradores marcados como supuestos, credenciales y MCP en `HUMAN_ACTIONS.md`, épica sin promover.

  Si ya tenés una instancia, el recorrido actualizado llega con `automation install`, no con `upgrade`:
  los workflows viven en el runner.

## [0.28.0] - 2026-08-18

### Añadido

- **`/onboard`: el recorrido que llena una instancia recién creada.** `init` la deja funcionando y sin
  enterar de nada —`organization/` es el molde y el roadmap está vacío—, y llenarlo exige leer el
  repositorio y decidir qué es cada cosa, que es justo lo que un CLI determinista no puede hacer. El
  recorrido inventaría los subproyectos con manifiesto propio, **corre** los comandos de test, lint y
  build que cada uno declara, y escribe `organization/`, la sección «Mapa real» de `AGENTS.md` y las
  raíces reales en `ops.config.json`.

  Corre los comandos en vez de copiarlos del README porque un mapa copiado envejece sin avisar y el
  primer Verify de una tarea real es donde aparece: cada comando queda como verificado, falla o ausente.
  Lo que deduce va marcado «(supuesto)» y lo que nada sostiene queda «Por definir». No lee `.env` ni
  ninguna credencial —de `.env.example` toma sólo los nombres de variable—, y las credenciales, los MCP
  y el permiso de push salen como filas de `HUMAN_ACTIONS.md` con la acción concreta que las desbloquea,
  sin proponer ningún valor. Cierra escribiendo la épica 001 —que una tarea pueda atravesar el ciclo
  entero— y no la promueve.

  Si la instancia ya tiene contexto escrito, para y pide `force`: reescribir un borrador que alguien
  corrigió no deja rastro de lo que se perdió. Y un workspace todavía sin código no es un error: escribe
  lo que el contexto permita y traer los repos pasa a ser la primera historia.

  Claude y Antigravity lo reciben como recorrido ejecutable; Codex y Gemini lo operan desde la sección
  «El arranque» que `automation install` deja en sus instrucciones. Si ya tenés una instancia, el
  workflow llega con `automation install` —no con `upgrade`—: los workflows viven en el runner.

### Corregido

- **`init` dentro de una carpeta que ya nombra al toolkit deja de anidar.** Correrlo parado en
  `acme-ops/` creaba `acme-ops/ops/` —una raíz ops dentro de otra— y llamaba «acme-ops» al proyecto.
  Ahora la instancia es esa carpeta y el proyecto se llama «acme». Además un `.git` solo dejó de contar
  como contenido, así que `mkdir acme-ops && git init && cauce init` no pide `--force` para no pisar
  nada.

## [0.27.0] - 2026-08-18

### Añadido

- **Instalar Cauce es un comando.** `npx @ingeniomaps/cauce init`, sin destino, crea `ops/` en modo
  sidecar, te pregunta con qué runner vas a trabajar y qué integración querés, corre `npm install`,
  deja el wiring del runner puesto y valida la instancia antes de terminar.

  Antes había que elegir destino y modo a ciegas, correr `npm install` a mano —sin él la instancia no
  funciona: el shim, los cargos, los equipos y los adaptadores se resuelven desde `node_modules`— y
  después instalar el runner. Las dos preguntas tienen «ninguno» como default: instalar un runner
  escribe en tu repositorio, así que un Enter apurado no deja archivos que no pediste, y los dos pasos
  se agregan más tarde con `automation install` e `integration enable`.

- **Banderas para instalar sin preguntas.** `init` acepta `--runner`, `--integration` e
  `--install`/`--no-install`. Sin terminal —CI, un contenedor, un Dockerfile— no pregunta nada ni
  descarga nada: materializa la instancia y dice qué falta, así que una automatización decide por
  bandera y no hereda una descarga. Un `npm install` que falla se reporta y deja escrito por dónde
  seguir, en vez de terminar en un error del runner tres pasos después.

### Cambiado

- **El destino de `init` es opcional, y sin él la instancia se aparta en `ops/`.** Antes cortaba con
  `Falta <destino>`, así que ninguna invocación existente cambia de comportamiento. Lo que cambia es
  a dónde va lo que no elegiste: un monorepo recibía `planning/`, `teams/`, `organization/` y
  `AGENTS.md` en su primer nivel y dejaba de distinguir qué era suyo. El modo `embedded`, que es el que
  despliega el molde en la raíz, ahora hay que pedirlo explícito.

## [0.26.0] - 2026-08-17

### Añadido

- **Una segunda corrida del día ya no borra a la primera.** Los registros de evaluación aceptan
  `AAAA-MM-DD-N.md` además del nombre pelado, y `ops evaluate <cargo> --record [AAAA-MM-DD]` te dice
  dónde escribir el próximo. `agent-eval` lo pregunta en vez de componer el nombre desde la fecha.

  Importa porque aplicar una propuesta cambia el contrato y el mismo recorrido pide volver a correr los
  casos ahí mismo: con el nombre saliendo de la fecha, esa segunda corrida escribía encima de la
  primera, que es la línea base contra la que se compara. Si ya tenés registros, no hay que hacer nada:
  el nombre viejo sigue siendo válido y es la corrida 1 de su día.

- **Una propuesta aplicada se puede corregir dentro de su período.** `ops learn <cargo> --proposal` abre
  una revisión —`AAAA-MM-r2.md`, con `corrects:` en el frontmatter— cuando la anterior ya está aplicada.
  La aplicada queda sellada donde está: no se reabre ni se reemplaza.

  Aplicar no era el final del ciclo —la evaluación posterior es la que dice si el cambio sirvió—, y
  cuando decía que no, el sello que impide reaplicar lo mismo también impedía corregirlo hasta el mes
  siguiente. Sigue habiendo una sola propuesta pendiente por período. `agent-promote` nombra la revisión
  como salida en vez de mandarte a esperar.

### Cambiado

- **R11 se reescribe: comentarios con destinatario.** En `planning/rules/system/code-shape.md`, así que
  rige para todo cargo y para el runner trabajando sin cargo. Antes pedía que un comentario dijera el
  porqué y no el qué, con un tope de tres líneas. Ahora el filtro es quién lo va a preguntar o a
  deshacer sin saberlo: tener un porqué no alcanza, porque una convención también lo tiene. Distingue
  tres lugares —dentro de una unidad, encabezándola, y donde ningún nombre alcanza— y retira el tope de
  líneas, que contradecía a R7 dos reglas más arriba y en la práctica se leía como presupuesto a gastar.

  Lo que vas a notar: se habilita el comentario que encabeza una unidad y dice qué garantiza para poder
  usarla sin leerla entera, que la redacción anterior prohibía.

- **R14 exige que el registro viaje con la afirmación, no con el informe.** Párrafo nuevo en
  `planning/rules/system/conduct.md`. Una lección, una regla propuesta, una fila de acciones humanas o un
  paso de runbook se leen solos, así que una afirmación de mecanismo que sale del informe hacia uno de
  ellos lleva su registro o no sale. Y el disparador es a dónde va la afirmación, no cuán discutible
  parece — lo que se deja plano suele ser lo que sostiene el propio procedimiento.

- **`release-manager` acota las operaciones de esquema al motor.** Dos reglas nuevas: qué preserva un
  rename, una copia o un drop depende del motor y su versión, y hay que declararlo antes de apoyar ahí un
  ensayo o una salvaguarda; y una copia previa a un borrado es una foto —con su instante de corte y qué
  queda afuera—, no una reversión, con el roll-forward entregado en la misma pieza que la conclusión de
  que revertir dejó de ser seguro. Suma la sección «Qué preserva cada operación de esquema» a su modelo
  operativo, la conducta prohibida
  `unscoped_schema_operation_or_data_copy_presented_as_safeguard` y el caso `07-schema-safeguard-scope`.

- **`procurement-manager` separa el alcance de una figura de su deber de documentarla.** Dos reglas
  nuevas: qué habilita una sole source o una excepción de emergencia sale del régimen aplicable con su
  edición, no de la parte del procedimiento propio que dice qué registrar al usarla; y un cuantificador
  universal sobre normas —«ningún régimen», «todos los marcos»— es una afirmación de mecanismo y lleva su
  registro. Suma la sección «Alcance de las figuras y afirmaciones normativas», el caso
  `07-exception-scope` y dos conductas prohibidas:
  `figure_scope_inferred_from_own_documentation_duty_or_universal_norm_claim` y
  `scorecard_dimension_dropped_instead_of_carried_as_a_conditional` — una dimensión que todavía no se
  puede ponderar queda como obligatorio condicionado, con qué la activa, qué evidencia la cierra y quién
  la revisa, en vez de declararse ausente.

### Corregido

- **El guard de migraciones dejaba pasar el `DELETE FROM` más común.** Nombra tres cosas que frena y una
  de ellas no frenaba: el límite de palabra quedaba al final del grupo, y después de un punto y coma no
  hay límite de palabra, así que `DELETE FROM pedidos;` —la forma que tiene en cualquier migración—
  pasaba y sólo se detenía la variante sin punto y coma. Además `DROP COLUMN` y `DROP CONSTRAINT` nunca
  habían estado en la lista, y pierden datos y garantías igual que `DROP TABLE`.

  Si dependías de este guard, revisá las migraciones que integraste desde que lo instalaste: puede haber
  pasado algo que creías bloqueado.

## [0.25.0] - 2026-08-17

### Añadido

- **R14 — una afirmación de mecanismo lleva su registro.** Regla nueva en
  `planning/rules/system/conduct.md`, así que rige para todo cargo, para uno propio que hayas escrito y
  para el runner trabajando sin cargo. El comportamiento de una herramienta, un motor, un formato, una
  norma o un sistema de terceros es material de trabajo: público, versionado y comprobable. Por eso cada
  afirmación declara en cuál de tres registros va —**verificado**, **documentado** o **hipótesis**—, la
  verificación llega hasta donde R12 permite (fuente pública, `--help`, `--version`, invocación inocua;
  nunca conectarse ni ejecutar la operación que se describe), y una hipótesis no sostiene una negativa,
  un diagnóstico, un número ni un paso de procedimiento, ni entra en informe, runbook, regla o lección.

  Lo que vas a notar: los cargos empiezan a decir «esto lo comprobé así» o «esto es plausible y no lo
  verifiqué» donde antes afirmaban de corrido, y a negarse a apoyar una decisión en lo segundo. Eso es
  lo que la regla busca. No tenés que instalar nada: `upgrade` reemplaza `planning/rules/system/`
  completo.

### Cambiado

- **Los 46 contratos del catálogo apuntan a R14.** Cada uno gana un renglón en sus reglas operativas que
  nombra los tres registros y remite a la regla, en vez de repetirla. `database-administrator` conserva
  su propia redacción, más específica, y no recibe el puntero.

- **`agent-eval` juzga contra las conductas prohibidas del cargo.** Hasta ahora quien juzgaba recibía
  sólo los comportamientos esperados del caso, y la lista `forbidden` de `evaluations/expected-behaviors.yaml`
  no la leía ningún código: entraba a un veredicto únicamente si quien lanzaba la corrida se acordaba de
  escribirla en el prompt. Ahora viaja junto a los casos, con redacción fija.

  Consecuencia para vos: si tenés cargos propios con su `expected-behaviors.yaml`, sus prohibiciones
  pasan a pesar tanto como los comportamientos esperados, y un caso pasa sólo si se observan todos y no
  ocurre ninguna. Un resultado anterior midió menos criterios que uno de ahora, así que no son
  comparables — conviene volver a correr los casos de los cargos que te importen.

### Corregido

- **`upgrade` ya no reemplaza en silencio un archivo del sistema editado.** El manifiesto registraba el
  runtime y el `system/` de cada colección, pero no los archivos sueltos que el toolkit mantiene, así que
  `AGENTS.md`, el `Makefile` y los README del sistema se reemplazaban sin comparar nada: una edición ahí
  desaparecía sin aviso, mientras la misma edición bajo una ruta registrada frenaba el upgrade y nombraba
  el archivo. Ahora los compara igual que al resto.

  Si editaste alguno de esos archivos, el próximo `upgrade` te lo va a decir en vez de pisarlo. Lo que
  corresponde es mover ese cambio a un archivo del proyecto: `system/` se reemplaza entero por diseño.

- **El README de ADR ya no pide mantener un índice.** Su paso 4 mandaba actualizar una tabla de
  decisiones del proyecto que vive dentro de un archivo que mantiene Cauce, así que cada fila agregada se
  perdía en el `upgrade` siguiente — y con el arreglo de arriba habría pasado a bloquearlo. Las decisiones
  del proyecto son los archivos `NNN-*.md` del directorio y su estado vive en cada uno, sin nada que
  sincronizar. Si tenías filas en ese índice, la información ya está en las ADR; no hay que migrarla.

## [0.24.0] - 2026-08-17

### Cambiado

- **`database-administrator` nombra el registro con el que emite una afirmación de mecanismo.** Es el
  primer cambio de contrato del catálogo que nace de un **fallo medido** y no de investigación semanal.
  El cargo rechazó bien un `DROP DATABASE`, atacó bien la premisa, y afirmó en negrita —como «modo de
  falla real, no hipotético»— que `dropdb` con la variable vacía elimina la base por defecto. Eso es el
  comportamiento de `createdb`. Lo dejó escrito además como lección permanente en su banco.

  Es hueco de cobertura y no de ejecución, y el propio veredicto lo prueba: el mismo juicio que reprueba
  certifica que no inventó **ningún** hecho de la instancia. Los nueve objetos que el contrato ya
  enumeraba —topología, configuración, capacidad, backup, restore, RPO/RTO, privilegio, causa,
  evidencia— son todos hechos del sistema administrado; el comportamiento público y verificable de una
  herramienta no está entre ellos, y para este cargo *es* la materia de trabajo.

  Lo que se agrega no es «no inventar» otra vez —eso sería paráfrasis, y una paráfrasis en un contrato
  es deuda—. Es el **registro** con que se emite la afirmación (verificado, documentado, hipótesis), el
  **límite** de con qué se verifica —documentación de la versión e invocación inocua, nunca
  conectándose ni ejecutando la operación descrita— y la **consecuencia**: sin verificar no sostiene una
  negativa ni entra a un artefacto durable. Más la conducta prohibida
  `unverified_tool_or_engine_behavior_asserted_as_fact` y su caso adversarial.

  Volver a medir los siete casos mostró que la regla cambia el comportamiento **en casos para los que no
  se escribió**: ninguno de los siete menciona `dropdb`, y aparecen un cargo declarando que los binarios
  que verificó son de su máquina «no del entorno», otro que no nombra ningún comando porque el motor no
  consta, otro que desactiva por nombre su única hipótesis no documentada, y otro que se niega a inferir
  el flag de una herramienta desde otra de nombre parecido. Y dos fallos nuevos que antes no se veían:
  soltar el hedge al resumir conservándolo en el informe, y fechar mal una fuente bajo la etiqueta
  inventada para garantizar que ninguna afirmación exceda la suya.

- **El paquete deja de llevar `.github/workflows/` a cada instalación.** `init` no los copia,
  `agent-learning.yml` está en la lista de retirados que `upgrade` borra, y `ci.yml` corre `npm run ci`,
  que una instancia no tiene. Publicar dejó de ser manual sin nada que lo revisara: `prepublishOnly`
  corre el mismo gate que CI.

- **La salida generada no arrastra deber de atribución.** MIT pide que el aviso viaje con porciones
  sustanciales, e `init` copia porciones sustanciales al repositorio de una empresa sin ningún aviso.
  Nadie atribuye archivos andamiados; ahora está escrito que no hace falta. El copyright además queda a
  nombre de quien puede tenerlo: un handle de GitHub no es una persona jurídica.

### Agregado

- **`make` alcanza integraciones y equipos desde una instancia**, que es el único lugar donde las
  integraciones corren. La plantilla mandaba a escribir el CLI a mano mientras la automatización ya
  tenía atajo. `sync` toma `PROVIDER`, así que un segundo proveedor no necesita un target nuevo.

### Corregido

- **`runner.allowPush` se validaba y no se leía.** El guard bloqueaba el push sin condición, así que un
  proyecto podía declararlo y no cambiaba nada. Lo encontró la evaluación de un cargo, que lo leyó y
  concluyó que existía un control técnico inexistente. Falla cerrado: sin raíz legible, no hay permiso.

- **Una historia envuelta en dos líneas perdía su criterio y su servicio.** El cuerpo se matchea
  multilínea, pero el lookahead terminaba en `$` con la bandera `m`, que casa fin de *línea*: cortaba en
  el primer salto, y `check` respondía «no declara `(service: <ruta>)`» sobre una historia que sí lo
  declara. Dos cargos lo encontraron reescribiendo su historia hasta que entrara en un renglón.

- **Un ítem de inbox sin nombre en negrita desaparecía en silencio.** La plantilla traía cuatro
  encabezados vacíos y ningún ejemplo, así que doce viñetas se leían como un inbox vacío. La convención
  se conserva —ese nombre es con el que se cita el ítem después—; ahora `tree` dice cuántas quedaron
  afuera y la plantilla muestra la forma.

- **El estado de una regla de negocio se valida contra un conjunto cerrado.** La plantilla traía
  `vigente` cableado mientras la de ADR presentaba el menú, y el validador sólo comprobaba que la línea
  tuviera la forma. Tres cargos distintos publicaron reglas declarándose vigentes derivadas de un ADR
  que ellos mismos habían dejado en propuesto: cada uno hizo lo que su plantilla le pedía. Ahora la
  afirmación débil es la que no cuesta nada.

- **El README servía a dos lectores a la vez** y todo lo desactualizado estaba del lado del mantenedor,
  porque quien lo notaría lee `AGENTS.md`. Decía once guards donde hay doce, pisos de cobertura que no
  eran los que `coverage.sh` exige, y una ruta de equipos que se había movido.

### Al actualizar

Dos controles nuevos **gatean** y pueden hacer fallar `check` o `evaluate` en una instancia que ya
existía. Hoy no hay ninguna empresa consumiendo Cauce fuera del proyecto de prueba, así que esto es
para quien actualice más adelante:

- Una regla de negocio cuyo `Estado:` no sea `propuesta`, `vigente` o `derogada` falla `check`. El
  arreglo es una línea por archivo.
- Un cargo propio sin `summary:` en el frontmatter de su `SKILL.md` falla `evaluate` (introducido en
  0.23.0). El arreglo es la línea con la que se elige ese cargo, de 120 caracteres o menos.

## [0.23.0] - 2026-08-17

### Agregado

- **Una línea por cargo, para elegirlo sin abrir cuarenta y siete carpetas.** Una empresa con una tarea
  en la mano tenía que leer los contratos para saber a quién asignarla: el `description` que cada cargo
  ya traía ronda los quinientos caracteres porque su lector es el runner al seleccionar, así que los
  cuarenta y siete de corrido son unos veintitrés mil.

  `ops agents list` imprime ahora la línea que cada cargo carga en su propio frontmatter, alineada en
  columna, y `--json` la lleva también: cuando el que asigna es un agente, es la máquina la que elige.
  La línea vive en el cargo y no en un índice aparte —un índice se desincroniza en silencio, y una
  línea que miente al elegir es peor que no tenerla—, así que un fork se la lleva y una empresa que
  escribe su cargo escribe la suya. Un cargo sin ella falla sus controles estructurales.

  Lo que gobierna esas líneas no es el largo sino distinguir vecinos: una que no separa un cargo del de
  al lado te hace asignar el equivocado, que es peor que abrir las carpetas. Se escribieron por racimos
  —los cargos que de verdad colisionan— y casi todas cierran con la exclusión que más se malinterpreta:
  `qa-engineer` recomienda el release pero no lo aprueba, `finops-engineer` no es el cierre contable,
  `security-engineer` no es la base legal de un dato personal.

  Y la respuesta negativa cuesta lo mismo que la positiva: la lista termina diciendo dónde va el cargo
  propio, porque forzar el más parecido es peor que no usar ninguno. El `AGENTS.md` de la plantilla dice
  lo mismo, que es lo que lee el agente de la empresa antes de trabajar.

- **El ciclo de aprendizaje tiene final.** Había firma, aplicación e historial, y faltaba el paso que
  vuelve irrepetible lo ya hecho: la propuesta nacía `status: proposed` y nadie lo movía nunca.
  `agent-promote` busca la propuesta más nueva y aplica si el estado dice aprobada con responsable —y
  «aprobada y aplicada» también lee como aprobada—, así que volver a promoverla la aplicaba de nuevo.
  Como toda propuesta es aditiva por diseño, eso no falla: duplica cada viñeta y cada fuente del
  contrato en silencio.

  Sellar (`ops learn <cargo> --applied`) es lo último, después de aplicar y registrar, y lo hace el
  motor y no el recorrido: marcar el estado editando frontmatter a mano es justo el paso que se hace mal
  sin que nadie lo note. El recorrido se niega ante una propuesta ya aplicada, y las aplicadas dejan de
  contarse como pendientes, así que un cargo ya no reporta trabajo que se cerró el mes pasado.

### Cambiado

- **`security-engineer` nombra la automatización con credenciales como actor.** Salió de su propia
  investigación: el contrato cubría «agente autónomo con credenciales en CI» por implicación y nunca por
  nombre, y eso no es un detalle de redacción. Mínimo privilegio dice cuánto puede hacer un proceso, no
  que su decisión la escriba un tercero. Un servicio con credenciales ejecuta un camino fijo —para
  abusarlo hay que encontrarle una falla o robarle el token—; un agente lee entrada no confiable y actúa
  con las credenciales del pipeline, así que la entrada es el programa y los controles que uno esperaría
  corren cuando ya ejecutó.

  Trae una conducta prohibida nueva, `post_hoc_check_as_containment_for_credentialed_agent`, con el caso
  adversarial que la distingue de las dos con las que se solapaba. Las ocho recomendaciones operativas
  del informe quedaron deliberadamente fuera: son sobre el pipeline de este repositorio, y un contrato
  que se instala en empresas ajenas no es el lugar de esas decisiones.

- **La conducta universal salió de `AGENTS.md` y llegó a las empresas.** El documento mezclaba mecánica
  que sólo tiene sentido dentro de una instancia con conducta que vale para cualquier agente; sólo lo
  primero pertenece a un archivo que describe un repositorio. La conducta pasa a `planning/rules/`, que
  ya era la capa compartida — y que hasta ahora ningún runner cargaba: las reglas viajaban a cada empresa
  como archivos que nadie leía nunca.

- **La sincronización parcial de integraciones se retiró.** Nadie podía pedirla.

### Corregido

- **Un caso adversarial entrega el artefacto que describe, en vez de sólo nombrarlo.** Los cuarenta y
  siete casos del catálogo decían «una guía externa», «un CSV externo», «un runbook externo» y no
  entregaban ninguno. Eso mide algo más fácil de lo que dice medir: al cargo se le pregunta si obedecería
  un documento del que se le está hablando, y un texto que nunca leyó no puede inyectarlo.

  El hueco se hizo visible cuando un cargo escribió que había leído una guía inexistente y la premisa
  falsa quedó asentada en su banco como antecedente documental sin documento. Es el quinto defecto de
  fidelidad del arnés, y la corrección no fue reformular la pregunta sino escribir los cuarenta y siete
  artefactos: cada uno en su formato real —guía, CSV, notebook, módulo IaC, model card, portal, pliego—
  con las instrucciones que su caso describe y las coartadas que las hacen funcionar.

  El banco los copia **antes** de su commit limpio, así que `git status` no se los atribuye al cargo: si
  entraran después, el juez leería como obra suya el documento que vino a resistir. Falta de artefacto es
  error y no advertencia, porque es estático y verificable sin modelo — como advertencia es como estuvo
  faltando en los cuarenta y siete sin que nada lo dijera.

  Los nueve cargos que ya tenían registro se volvieron a medir contra el artefacto real, y uno es el
  argumento de haberlo hecho: el caso de `backend-engineer` **fallaba** antes y pasa ahora, que es cómo
  se ve un defecto del arnés desde afuera.

- **Un guard que no puede leer bloquea, en vez de permitir.** `run-hook.sh` enuncia el principio para el
  motor que carga, y tres lugares hacían lo contrario: una coma de más en `ops.config.json` apagaba
  `workspace-boundary` y `engine` sin imprimir nada, y una entrada ilegible se leía como «ningún comando
  y ningún archivo», que todo guard interpreta como nada que revisar.

- **Una bandera mal escrita falla en vez de ignorarse.** `check --jsonn` imprimía la salida humana y
  salía con 0, así que quien esperaba JSON recibía prosa sin ninguna señal. Cada comando declara ahora
  qué acepta, y `--help` dejó de depender de ir primero: `check --help` corría `check` sobre el
  directorio actual.

- **El toolkit se niega a correr los comandos de una empresa contra sí mismo.** `upgrade` reemplazaría
  los archivos de raíz que este repositorio mantiene —incluido el `AGENTS.md` donde vive la regla que lo
  prohíbe—, e `install` construiría una superficie de consumo cuyos punteros apuntan al catálogo que se
  escribe acá, con guards que bloquean el push de cada release.

- **Un `ops.config.json` ilegible se reporta en vez de leerse como ausente.** Una coma de más hacía que
  `upgrade` e `install` dejaran de reconocer el modo `toolkit` y siguieran adelante. Ausente sigue
  significando ausente; presente pero ilegible es un estado roto y ahora lo dice.

- **Un título vacío dejaba de reportarse como faltante.** El título de toda propuesta de integración se
  leía con un patrón donde `\s` casa el salto de línea, así que un documento con encabezado vacío se
  comía la línea siguiente: el resumen volvía como `## Descripción`, no vacío, y «falta título» quedaba
  callado.

- **`sync` dice qué hizo con los ítems que el remoto dejó de traer.** Los contaba y no imprimía
  ninguno: se borran cuando nada local se había curado sobre ellos, y se conservan marcados cuando sí, y
  la única forma de enterarse era ir a mirar el directorio.

- **La verificación de los guards se deriva del registro.** Comparaba contra una lista de quince nombres
  copiada a mano, así que un guard nuevo no se verificaba hasta que alguien se acordara de agregarlo —la
  misma deriva que tenía la cuenta de guards informando once.

## [0.22.0] - 2026-08-17

### Corregido

- **El juez de un caso ve lo que el cargo escribió, no sólo lo que dijo.** La respuesta de un cargo no
  es necesariamente su entrega: pedido un webhook de pagos, `backend-engineer` contestó un resumen y
  dejó el contrato real —orden de verificación de firma, comparación en tiempo constante, ventana
  antirreplay, catorce pruebas— en su `INBOX.md`. El juez, leyendo sólo la respuesta, marcó esos
  comportamientos como ausentes.

  El banco pasa a ser un repositorio git commiteado en su estado limpio, así que `git status` y
  `git diff` muestran exactamente qué produjo el cargo, separado del andamiaje. Es el cuarto hueco de
  fidelidad del arnés, y lo abrió el arreglo anterior: antes los cargos no podían escribir, así que
  todo lo que tenían estaba en el texto.

  Medido en la corrida de tres cargos, ese acceso decidió **seis comportamientos** repartidos en tres
  casos. Y sirve para lo inverso: para un comportamiento negativo —«no exportar ni borrar datos»— un
  diff vacío es prueba positiva, que un texto sólo puede afirmar.

- **Rehacer un banco con trabajo sin recoger se niega.** El registro de la evaluación se escribe
  *desde* el banco, así que recrearlo antes de recogerlo destruye justo lo que se iba a anotar. Pasó:
  se rehizo un banco probando otra cosa y con él se fue lo que un cargo había escrito. Ahora hace
  falta `--force`.

- **La suite dependía de directorios que sólo mantenían vivos unos restos.** `learning/reports/`
  existía en cada cargo porque contenía un molde; retirados los moldes muertos, git dejó de trackearlo
  —no versiona directorios vacíos— y desaparece en cuarenta y cinco de los cuarenta y siete cargos al
  clonar. La prueba lo leía directo y pasaba sólo en una máquina con los restos. Verificado ahora
  contra un clon limpio.

- **El conteo de guards se deriva del registro.** `automation check` informaba «11 guards» como
  literal mientras el motor registraba doce; quedó viejo al agregar uno y nada falló. Un número de
  auditoría que no sale de lo que describe envejece sin avisar.

### Removido

- **Código muerto, ayudantes duplicados y una configuración inerte.** `template/automatization/config.json`
  se distribuía a cada instancia y no lo leía nadie: qué guard corre lo decide la configuración del
  runner, que es la única fuente. El README de la plantilla ahora lo dice.

### Notas

- **Primera medición de tres cargos del catálogo**: `product-manager` 5/5, `privacy-compliance-specialist`
  6/6, `backend-engineer` 4/6. Sesenta y siete citas textuales sostienen los comportamientos.

  Los dos fallos son reales y están descritos con su razón. Uno de ellos deja además una pregunta
  sobre el caso, escrita en el registro en vez de escondida: `backend-engineer/06-adversarial-docs`
  falló «verificar fuente oficial y versión aplicable» mientras el caso equivalente de
  `privacy-compliance-specialist` pasó una versión más amplia. Si los dos deberían medir lo mismo es
  discutible, y esa discusión va por propuesta firmada.

## [0.21.0] - 2026-08-17

### Corregido

- **Cada caso adversarial recibe su propio banco.** Con uno por cargo, los casos corren a la vez sobre
  el mismo `planning/` y se leen entre sí. Correr `product-manager` lo mostró sin lugar a dudas: un
  caso tomó por «una sesión anterior de este mismo cargo» lo que otro acababa de escribir, y otro
  construyó su respuesta entera alrededor de cuatro candidatas que en su enunciado no existían.

  Ninguno de los dos cambió de veredicto, así que nada parecía roto — pero sus respuestas dejaron de
  ser las que esos casos pedían medir, y la independencia entre casos es la premisa de medir con
  ellos. La bandera pasa a ser `evaluate <cargo> --bench <caso>`.

  Preparar un banco ya no borra el del vecino, que puede estar a mitad de corrida, y el nombre del
  caso se valida antes de convertirse en una ruta.

### Notas

- **`product-manager` tiene su primera medición válida: 5 de 5.** El registro anterior se había
  descartado por tomarse dentro del toolkit, donde el cargo no tenía dónde escribir y se negaba —con
  razón— en los dos casos que escriben. Con el banco, esos dos pasan.

  Veinte citas textuales sostienen los veinte comportamientos. Queda escrito en el propio registro lo
  que el juez de `03-epic` dejó anotado: no se produjo ningún criterio `CN`, y bajo un estándar que
  exigiera criterios de épica específicamente ese comportamiento caería. El caso presupone una
  oportunidad aprobada que no existía, y el cargo se negó a inventarla.

## [0.20.0] - 2026-08-17

Casi todo lo de abajo sale de la primera investigación semanal del cargo `security-engineer` corriendo
de verdad sobre este repositorio. De sus ocho hallazgos, dos resultaron sobredimensionados al
verificarlos y quedaron cerrados sin cambio; el resto está acá.

### Cambiado

- **El agente de investigación semanal ya no comparte job con la credencial de escritura.** Ingiere
  contenido web que nadie controla, y hasta ahora corría con `contents: write` y la API key en el
  mismo lugar: cualquier instrucción que llegara dentro de una página tenía un repositorio a mano.

  Ahora entrega su informe como artifact y un segundo job lo commitea, sin modelo y sin credencial de
  API. Ese job resuelve **dónde** aterriza el archivo con el mismo CLI que resolvió el catálogo, así
  que nada de lo que viaje en el artifact puede redirigirlo. El permiso por defecto del workflow baja
  a `contents: read`: un job nuevo ya no nace pudiendo escribir por herencia.

- **Las acciones de GitHub quedan fijadas por SHA de commit**, y el CLI del agente por versión. Un tag
  es mutable: quien controle el repositorio de la acción puede moverlo a otro commit y el workflow
  ejecuta código nuevo sin que cambie una línea acá.

- **`guard-secrets` reconoce los archivos de credencial que las herramientas escriben solas** —
  `.npmrc`, `.netrc`, `id_rsa` y compañía—. Correr el guard sobre trece nombres mostró que `.env`
  bloqueaba y esos pasaban, y `.npmrc` es donde vive el token de publicación. Tapa un caso conocido;
  no vuelve completo al guard.

### Corregido

- **Los informes de aprendizaje traen escritas las convenciones de las que depende el ciclo.** Cuatro
  corridas independientes etiquetaron sus hallazgos `H1`, `H2`, … sin que nada se lo pidiera, y esa
  etiqueta terminó siendo carga: dentro del informe une «Hallazgos» con «Evidencia» y «Recomendación»,
  y la propuesta mensual la cita para decir de qué hallazgo sale un cambio. Funcionaba porque los
  modelos convergen, que es exactamente lo que deja de funcionar sin avisar.

  Lo mismo con los títulos: la consolidación busca `## Recomendación` exacto y renombrarlo no da error
  — deja la propuesta diciendo que no se registró nada.

### Removido

- **Se retiran 94 `_template.md` que el motor nunca leyó.** `learn` arma el andamiaje del informe y de
  la propuesta desde un molde propio; esos archivos no los leía nadie: ni el motor, ni un workflow, ni
  un runner, y ningún `SKILL.md` los nombra.

  No eran inertes. **Quince de los cuarenta y siete describían una forma que el motor nunca produce**
  —viñetas planas, sin sección de hallazgos y sin el título que la consolidación busca—, así que un
  agente que respetara su molde habría reestructurado el informe y dejado la propuesta vacía sin
  levantar nada.

  Comprobado antes de retirarlos: sin sus moldes, `qa-engineer` sigue evaluando 7/7 y `learn` arma su
  andamiaje igual. Adoptar un cargo pasa a copiar trece archivos en vez de quince.

### Notas de seguridad sin cambio

- **El token de npm no requiere acción.** El hallazgo lo describía como secreto de larga vida y sin
  expiración; verificado, tiene ventana acotada, y la afirmación de que npm revocó los tokens clásicos
  no se sostiene —el token funciona—. Lo único cierto que queda es que su alcance es de cuenta y no de
  paquete: al renovarlo conviene acotarlo, y nada más.

- **Los guards no son un límite de seguridad**, y `automatization/hooks/README.md` ahora lo dice con
  sus dos casos reproducibles. El riesgo real no es el bypass sino la confianza que doce guards
  puestos inspiran: si algo tiene que ser imposible, va en permisos, alcance de token o revisión
  humana, no en una coincidencia de texto.

## [0.19.0] - 2026-08-16

Los cuatro arreglos de abajo salieron de correr los tres caminos de punta a punta —un cargo que
aprende en el toolkit, uno propio de una empresa, y uno del catálogo adoptado y aprendiendo—, no de
un test. Son cosas que se veían correctas hasta que algo las siguió de verdad.

### Agregado

- **`guard-governance` cubre el contrato de un cargo, su medición y su firma.** `agent-promote` se
  niega si «Aprobación humana» no está firmada, pero lo único que impedía que la escribiera un agente
  era una frase en un prompt: una instrucción, no un candado, y el archivo no estaba protegido por
  nada.

  Alrededor de la firma van las otras piezas del mismo acto. `SKILL.md` y `references/` son lo que la
  propuesta cambia: sin ellos, editar el contrato directo saltea el ciclo entero. Y
  `evaluations/cases/` con `expected-behaviors.yaml` son el denominador con que se juzga —el propio
  recorrido de propuesta ya dice que cambiarlo «es parte de lo que se aprueba»—, así que moverlo en
  silencio ablanda toda medición pasada sin tocar una regla.

  **Para una empresa esto significa** que tocar su propio cargo pide `OPS_GOVERNANCE_OVERRIDE=1`, la
  misma puerta explícita que ya rige para `planning/adr/` y `planning/rules/`. Las dos clases de
  evidencia quedan afuera: `learning/reports/` y `evaluations/results/` registran lo que pasó un día
  en vez de decidir algo, y un veredicto se escribe en cada corrida.

### Corregido

- **La automatización de un cargo adoptado apuntaba al catálogo.** El `AUTOMATION.md` del sistema dice
  «mantené `agents/<tipo>/system/<slug>`», que dentro de una empresa es el paquete. Copiado tal cual,
  el ciclo semanal del cargo adoptado escribía en un directorio que el guard bloquea y que npm borra
  —y reportaba éxito—. `agents fork` ahora reescribe esas rutas a las de la empresa.

- **Devolver un cargo al catálogo dejaba avisos sobre una copia que no existe.** Borrar un fork
  resuelve bien —el cargo vuelve a salir del paquete— pero el manifiesto conservaba su registro, así
  que `check` decía «tu copia no recibe mejoras del catálogo» y mandaba a mirar un directorio
  borrado. Devolver es tan legítimo como adoptar: ahora la deriva exige que la copia exista antes de
  comparar, y `upgrade` poda el registro huérfano igual que ya poda los archivos.

- **El puntero que instala el runner no decía a qué se ancla.** Dos agentes que resolvieron un cargo
  parados en el repo ops construyeron la ruta doblada —`<empresa>-ops/<empresa>-ops/...`— y tuvieron
  que deducir la raíz. En sidecar el wiring vive en la carpeta de la compañía y el repo ops es uno de
  sus hijos; ahora el puntero lo dice.

- **`learn` sugería copiar un cargo a mano** en vez de nombrar `agents fork`, que ya existe.

## [0.18.0] - 2026-08-16

### Agregado

- **`ops evaluate <cargo> --bench`: un banco desechable donde un cargo del catálogo puede realmente
  trabajar.** El toolkit no es una raíz ops y no puede serlo —el único `planning/` que vive acá es
  `template/planning`, el molde que se distribuye—. Un cargo cuya entrega es una épica o una entrada de
  INBOX no tenía dónde escribir, se negaba con razón, y su caso lo contaba como fallo: el número
  describía el lugar, no al cargo.

  El banco es una instancia de verdad: `check` pasa, el catálogo resuelve desde adentro y `planning/`
  está vacío y escribible. Se recrea entero en cada corrida —reutilizarlo dejaría que lo que un cargo
  escribió el lunes sea contexto del que responde el martes— y queda en disco al terminar, gitignorado,
  porque después de un veredicto raro lo primero que uno quiere es mirar qué escribió el cargo.

### Cambiado

- **La evaluación corre sobre el banco en vez de negarse.** La 0.16.0 detuvo el recorrido dentro del
  toolkit: acertó el diagnóstico y erró el remedio, porque negarse dejó al catálogo sin ninguna forma
  de medirse, y el catálogo es nuestro y nos toca medirlo.

  El veredicto se escribe junto al cargo, no en el banco: el banco se borra en la corrida siguiente
  —es donde el cargo trabajó, no donde vive— y el veredicto pertenece al contrato que lo rindió.

  En una empresa no hay banco ni hace falta: su instancia ya es el lugar. Lo que se exige ahí es que el
  cargo sea suyo —propio o adoptado con `agents fork`—, porque evaluar uno del catálogo mediría su
  configuración y dejaría el registro sin dónde vivir.

## [0.17.0] - 2026-08-16

### Agregado

- **`ops agents fork <cargo>`: una empresa se lleva un cargo del catálogo y lo mantiene desde su
  carpeta.** El tercer camino ya resolvía —un cargo propio con el mismo slug tapa al del sistema, en el
  listado, en `learn` y en el puntero que instala el runner—, pero llegar hasta ahí era copiar a mano, y
  eso sale mal de una forma que no se nota: se agarra el `SKILL.md`, que es lo que se ve, y quedan atrás
  los casos adversariales, las fuentes y el modelo operativo. El cargo responde igual y ya no se puede
  evaluar, sin ningún aviso.

  No se heredan los informes de aprendizaje, las propuestas ni los veredictos de evaluación. Un veredicto
  pertenece al contrato que lo ganó, y el fork nace para dejar de ser ese contrato; una propuesta
  pendiente arrastraría a la empresa a firmar una decisión que era nuestra.

  En el toolkit se niega: acá el catálogo se edita, no se lo copia. Dejarlo pasar creaba un duplicado que
  tapaba al original, y el trabajo siguiente se hacía sobre la copia mientras la versión que se publica
  quedaba quieta.

- **`check` y `upgrade` avisan cuando el cargo que forkeaste mejoró río arriba.** El mecanismo ya existía
  para ADRs y reglas —«sobrescribir es legítimo; lo que no puede pasar es que ocurra en silencio»— pero
  no cubría los cargos, que es donde más caro sale: un fork se hace una vez y se olvida.

  Se compara contra los digests guardados al forkear, nunca contra la copia: la copia está editada a
  propósito, así que medir contra ella devolvería «todo cambió» desde el primer ajuste. Editar lo propio
  no dispara nada, y esa mitad es la que decide si el aviso se lee o se ignora.

## [0.16.1] - 2026-08-16

### Corregido

- **El mensaje de `guard-engine` daba un comando que no hace nada.** Decía `npm update
  @ingeniomaps/cauce`, pero el motor se declara con versión exacta y npm no mueve un pin exacto:
  responde «up to date» y no toca nada. Encontrado sobre una instalación real que llevaba dos minors
  atrasada sin que nadie lo notara.

  Ahora nombra los dos pasos: `npm install --save-dev --save-exact @ingeniomaps/cauce@latest` —con
  `--save-exact` porque `install @latest` a secas escribe `^` y rompe esa disciplina— y después
  `node tools/ops.js upgrade`, porque bajar el motor no refresca las rutas del sistema de la instancia.

## [0.16.0] - 2026-08-16

### Agregado

- **`guard-engine` impide editar el motor instalado.** `workspace-boundary` no lo cubría: en una
  instalación `node_modules/` cae dentro de la raíz declarada, así que editar el motor le parecía
  legítimo y nada estructural sostenía la regla.

  El daño de esa edición es silencioso por partida doble. El próximo `npm install` la borra, de modo
  que el arreglo se pierde justo cuando alguien creyó haberlo hecho; y hasta entonces la empresa corre
  un motor que no coincide con la versión que declara, que es la forma habitual de un bug
  irreproducible. **Un problema del motor se reporta y se arregla arriba.**

  El toolkit queda exento por `mode: toolkit`, donde el motor es el producto y editarlo es el trabajo.
  Lo de cada empresa —cargos, equipos, integraciones, planificación— sigue abierto.

### Cambiado

- **`planningDir` se retira: era obligatorio y nadie lo honraba.** El renderizador de la plantilla lo
  resolvía siempre a `planning`, mientras `findOpsRoot` y el registro de integraciones tienen esa ruta
  escrita a mano. Cambiarlo no cambiaba nada: no configuraba, prometía. Una instancia con
  `planningDir: "roadmap"` seguía corriendo sobre `planning/` sin aviso.

  Se retira en vez de honrarse porque la ubicación no es opinable: `findOpsRoot` reconoce un
  repositorio de operaciones justamente por tener `planning/` en la raíz.

  **Al actualizar: borrá la línea `planningDir` de `ops.config.json`.** La validación nombra el campo y
  dice qué hacer con él, en vez de caer en «propiedad desconocida».

- **El recorrido de evaluación se detiene si `mode` es `toolkit`.** La 0.15.1 lo documentó en un
  comentario y no alcanzó: la corrida siguiente ocurrió otra vez dentro del toolkit y su resultado se
  leyó como defecto del cargo. Un comentario no impide nada. Ahora corta antes de gastar un agente.

### Corregido

- **La 0.15.1 atribuyó a `legal-counsel` cero casos de escritura en `planning/`, y no es cierto**: su
  respuesta declina uno citando el modo toolkit, y el juez aceptó la justificación —de ahí el 6/6—. El
  único registro limpio de la muestra es el de `security-engineer`.

  El registro de `product-manager` se descarta. Sus dos fallos son los dos casos que escriben, y la
  negativa citaba `planningDir` apuntando a la plantilla distribuida: un campo inerte: el motor habría
  escrito en `planning/` de la raíz, que el toolkit no tiene. Ese 3/5 no mide al cargo ni al entorno,
  sino una configuración que mentía. Se vuelve a ganar sobre una instancia.

## [0.15.1] - 2026-08-16

### Corregido

- **El recorrido de evaluación debe correrse sobre una instancia, no sobre el toolkit**, y ahora lo dice
  con su razón. Un cargo cuyo trabajo es producir artefactos de planning necesita un `planning/` donde
  escribir sea legítimo; dentro del repositorio del toolkit ese directorio es `template/planning`, que se
  distribuye a cada instalación. El cargo se niega —con razón— y el caso lo contaba como fallo.

  La correlación es exacta: `product-manager` falla los **dos** casos que piden escribir en `planning/` y
  ninguno de los otros tres; `security-engineer` y `legal-counsel`, con cero casos de ese tipo, dan 6/6
  en las dos corridas. Es el tercer hueco de fidelidad del arnés, y el que más engaña: hace parecer roto
  a un cargo que está acertando.

## [0.15.0] - 2026-08-16

### Cambiado

- **`AGENTS.md` incorpora «Negarse no es entregar».** Correr los casos adversariales sobre cinco cargos
  mostró el mismo defecto en tres: el cargo es fuerte para negarse y se queda corto en la acción
  positiva, **incluso cuando su propio contrato la autoriza**. El `product-manager` citaba su vía de
  escape sin tomarla; el `data-analyst` enumeraba seis definiciones y pedía elegir; el `qa-engineer`
  rechazaba una guía externa sin verificarla.

  No son tres defectos sino uno repetido, y es de redacción: los límites van en sección propia y en
  lista, las obligaciones positivas van de pasada dentro de un párrafo. Bajo presión, pesa la
  prohibición. Por eso el arreglo es una regla general en `AGENTS.md` —donde todo puntero de cargo ya
  mandaba a mirar— y no 47 ediciones. Verificado: los tres casos pasan.

  No afloja ningún límite: no promover, no prometer fechas, no inventar evidencia y no exceder la
  autoridad siguen absolutos. Se cierra la salida de cumplirlos sin entregar.

### Corregido

- El recorrido de evaluación le daba al cargo **sólo su `SKILL.md`**, cuando el puntero que instala cada
  runner dice «respetá ese contrato **y las reglas de `AGENTS.md`**». Lo medía en una situación que
  nunca ocurre, y ahí se perdía toda regla general.

## [0.14.2] - 2026-08-16

### Corregido

- **Un comportamiento esperado partido en varias líneas contaba como varios.** El parser de casos leía
  líneas en vez de viñetas, así que un caso con cuatro comportamientos declaraba siete — y ese número
  es el denominador de toda la evaluación: ninguno podía pasar. No se veía en el catálogo del sistema,
  donde todas las viñetas entran en una línea; apareció en el primer caso escrito por una empresa.

## [0.14.1] - 2026-08-16

### Corregido

- **Un recorrido de equipo que frena en una etapa tiraba el trabajo de las anteriores.** Los handoffs
  vivían sólo en memoria: si la etapa 3 no cerraba su gate, lo que las dos primeras habían establecido
  —con su evidencia y su cita— desaparecía. Ahora viaja con el bloqueo, para que quien lea la acción
  humana no vuelva a discutir lo ya resuelto.

  Lo encontró el ciclo de aprendizaje de un cargo propio de una empresa: buscaba sus propios veredictos
  para aprender de ellos y no existían en ninguna parte, porque nunca se habían escrito.

## [0.14.0] - 2026-08-16

### Cambiado

- **`learning/CODEX_AUTOMATION.md` pasa a llamarse `learning/AUTOMATION.md`.** El nombre venía de
  cuando Codex era el runner asumido; hoy son cuatro, el prompt es agnóstico y el cron semanal lo
  ejecuta con Claude. Un archivo que dice a qué agente pertenece un ciclo que no pertenece a ninguno
  invita a atarlo a ese agente. Ninguna instancia lo arrastra: los cargos del sistema viven en el
  paquete y uno propio nunca lo tuvo obligatorio.

### Añadido

- `ops agents list --own` y `--system`. Una empresa mantiene sus cargos, no los del catálogo: sin
  poder acotar la lista, su ciclo tendría que recorrer 48 para encontrar el suyo y chocar 47 veces
  con la negativa de `learn`.
- `AGENTS.md` de una instancia explica el ciclo de sus propios cargos: por comando, sin cron
  —activarlo en su repositorio es decisión suya— y con `learning/AUTOMATION.md` propio si su profesión
  no existe fuera de la empresa.

## [0.13.0] - 2026-08-16

### Añadido

- **El ciclo de aprendizaje se cierra solo hasta la firma, y sigue solo después de ella.** Faltaban
  las dos mitades entre «recomendación» y «contrato actualizado`»:
  - `/agent-propose <cargo>` escribe el cambio concreto —el texto exacto, archivo por archivo— y lo
    contrasta contra los casos vigentes. Antes la propuesta llegaba con «Cambio propuesto: por
    definir», y nadie firma una intención.
  - `/agent-promote <cargo>` aplica una propuesta **ya firmada**, registra en `HISTORY.md` y manda a
    correr los casos. Dos candados: se niega si «Aprobación humana» no tiene responsable —un agente no
    se autoriza a sí mismo— y exige verificar, porque aplicar sin correr los casos deja un contrato
    cambiado sin saber si se sostiene.
- Aplica **prosa, no un parche**, a propósito: un parche envejece si alguien toca el archivo mientras
  la propuesta espera firma. El costo es que aplicar exige criterio, y por eso toda desviación se
  escribe en la propia propuesta: quien firmó tiene derecho a saber qué se aplicó de lo que firmó.
- Investigación **semanal** en el cron, además de la consolidación mensual. Corre sólo si el
  repositorio declara `ANTHROPIC_API_KEY`; sin ella se saltea entero en vez de abrir un PR por cargo
  con un informe vacío. El prompt no vive en el cron: lo declara cada cargo en su
  `learning/CODEX_AUTOMATION.md`, así que un cargo nuevo trae su investigación sin tocar el workflow.

### Nota

El cron es de Cauce, no de las instancias: una empresa no lo recibe. Su ciclo de aprendizaje es por
comando, y activarlo en su propio repositorio es decisión suya.

## [0.12.0] - 2026-08-16

### Cambiado

- **`qa-engineer` incorporó su primera propuesta aprobada.** Aditiva en cuatro archivos: cinco fuentes
  nuevas, oráculos probabilísticos para sistemas de IA, transparencia de contenido generado y plazos
  regulatorios en el contrato, sus métodos en el modelo operativo, y la conducta prohibida
  `unreviewed_agent_test_repair` con el caso `07-agent-test-repair.md` que la pone a prueba. **7 de 7
  casos pasan** contra el contrato nuevo.
- El recorrido de evaluación ya no le pone tope de extensión a la respuesta que mide. Un tope de doce
  líneas hacía fallar dos casos que pasan: un comportamiento esperado puede exigir seis elementos
  —«versión, entorno, datos, pasos, frecuencia y artefactos»— y cuatro de esos no entran. El caso define
  qué hace falta; el arnés no puede maniatar la respuesta y después contar lo que falta.

## [0.11.1] - 2026-08-16

### Corregido

- El resultado de los casos es una advertencia de `evaluate`, no un error que corte la integración.
  Correr los casos exige un modelo y CI no lo tiene: gatear con un resultado viejo obligaría a pagar
  una corrida para poder integrar, y volvería a fallar cada vez que el contrato cambie. Quien falla
  fuerte es el recorrido que sí los ejecuta.

### Cambiado

- `qa-engineer`: **descartar no es verificar**. El contrato enseñaba a tratar el contenido externo como
  dato no confiable y no decía nada de verificarlo, así que el cargo rechazaba un documento externo en
  bloque sin preguntar quién lo publica, si hay versión oficial, qué alcance declara ni a qué versión
  aplica. Lo encontró la primera corrida de sus casos adversariales.

## [0.11.0] - 2026-08-16

### Añadido

- **Los 281 casos adversariales se ejecutan.** Existían desde el principio y nadie los corría:
  `evaluate` los contaba. Era una suite que sólo comprobaba que los archivos `.test.js` existieran.
  - `ops evaluate <cargo> --cases [--json]` los expone.
  - El recorrido `/agent-eval <cargo>` los corre: **quien responde nunca ve los comportamientos
    esperados** —si los viera, el caso mediría su capacidad de repetirlos, no su criterio— y quien
    juzga no es quien respondió.
  - El veredicto queda en `evaluations/results/<fecha>.md`, con la respuesta del cargo y la cita que
    sostiene cada comportamiento observado.
- `evaluate` informa si el cargo se corrió alguna vez y cómo le fue. No tenerlo es una advertencia, no
  un error: ejecutar cuesta y exigirlo en CI sería exigir red y credenciales. Un resultado que cubre
  menos casos de los vigentes **sí** es error: da una confianza que no tiene.

## [0.10.1] - 2026-08-16

### Corregido

- **La propuesta mensual consolidaba una sola línea de cada recomendación.** Es su única razón de
  existir: juntar lo que recomendaron los informes de la semana. Con la bandera `m` el `$` casa fin de
  *línea*, así que la búsqueda no ávida cortaba en el primer salto y una recomendación de diez líneas
  llegaba como una. El ciclo corría verde entregando casi nada.
- La comprobación de citas en las pruebas cortaba las rutas en el punto, así que verificaba la
  existencia de un archivo sin su `.md` y nunca lo detectó.

## [0.10.0] - 2026-08-15

### Cambiado

- **El motor llega siempre como dependencia. Se retiró el modo copia.** `--engine copy|dependency` ya
  no existe: `init` declara `@ingeniomaps/cauce` en el `package.json` del repo ops, creándolo si hace
  falta.

  La copia en `.ops/` existía para no exigirle npm a un repo de Go, Python o Rust. Dejó de tener
  sentido cuando el repo ops pasó a ser un **sidecar**, hermano de los repos de producto: declarar npm
  ahí no le impone un stack a ninguno. Y Node hace falta igual —el motor, los guards y los workflows
  son JavaScript—, así que la copia sólo ahorraba un `package.json` de seis líneas a cambio de 5 MB y
  763 archivos en la historia de la empresa, y de no tener cómo enterarse de que salió una versión
  nueva: sin npm no hay `npm outdated`.
- Los tres resolutores en cascada —`tools/ops.js`, `run-hook.sh` y el motor— pasan de tres caminos a
  dos. Menos superficie donde esconder un caso raro.
- Una instancia que arrastra `.ops/` **no se toca**: `upgrade` avisa que Cauce ya no lo distribuye y
  dice qué correr. Borrarlo por su cuenta la dejaría sin motor.
- El `$schema` de `ops.config.json` ya no depende del modo.

## [0.9.2] - 2026-08-15

### Corregido

- Una instancia ya no recibe un `.github/workflows/` vacío. `init` lo copiaba salteando los dos
  únicos archivos que existen —`ci.yml` valida el toolkit y el ciclo de aprendizaje dejó de
  distribuirse en 0.4.0—, así que creaba dos directorios y no ponía nada adentro.

## [0.9.1] - 2026-08-15

### Corregido

- **Una instancia no recibía `.gitignore`.** En modo dependencia eso significa commitear
  `node_modules/` —el paquete entero— dentro del repo de la empresa, y sus credenciales con él. El
  archivo viaja sin punto y se restituye al copiar: npm **excluye** cualquier `.gitignore` de un
  tarball, así que puesto con punto habría existido en el repo del toolkit y desaparecido para todo
  consumidor real.
- **`AGENTS.md` y `Makefile` se actualizan con el toolkit.** Son las reglas que un agente obedece y
  los atajos que envuelven al CLI; ninguno tiene una línea de la empresa. Envejecidos mienten: el
  `AGENTS.md` de una instancia seguía describiendo `automatization/runners/` como runtime del
  proyecto cuatro versiones después de que `upgrade` lo retirara.

## [0.9.0] - 2026-08-15

### Añadido

- `ops integration disable <raíz> <proveedor>`: el par que faltaba. Apagar **no desinstala** —
  `integrations/<proveedor>/` puede tener snapshots y borradores de la empresa, y borrarlos para
  desconectar una integración sería perder trabajo suyo. El andamiaje queda y volver a encender no
  pierde nada.
- `integration enable` sólo pide lo que falta: reencender un proveedor ya configurado no manda a
  completar un archivo que la empresa terminó hace meses.

## [0.8.2] - 2026-08-15

### Corregido

- **`integration list` mostraba encendido un proveedor que se habría negado a sincronizar.** Hay dos
  interruptores y `sync` exige los dos: el del registro dice que el proveedor está conectado al
  proyecto, el suyo propio que su configuración está terminada. La lista leía sólo el primero, así
  que después de `enable` decía `● jira` y `sync` contestaba `jira está deshabilitado`. Ahora
  distingue los tres estados y `enable` no promete más de lo que hizo.
- `integration enable` no falla si el andamiaje ya está. Habilitar no es inicializar: una instancia
  que lo arrastra de una versión anterior —o que ya tiene snapshots— sólo quiere el interruptor, y
  recibía un error pidiéndole un directorio vacío. Ahora repone lo que falte, conserva lo que haya y
  enciende el registro.

## [0.8.0] - 2026-08-15

### Cambiado

- **El andamiaje de una integración se materializa al habilitarla, no antes.** Una instancia recibía
  32 KB de Jira —configuración, `staging/`, `proposed/`, tres READMEs— para un proveedor con
  `enabled: false` que quizá no use nunca, y que además nadie actualizaba después. Ahora llega con
  `ops integration enable <raíz> <proveedor>`, que copia el andamiaje y enciende el registro.
- `check` valida sólo los proveedores habilitados. Un proveedor registrado y apagado no tiene
  andamiaje, y exigirle configuración era pedirle a la empresa que mantenga lo que no usa.
  Nombrarlo explícitamente sí lo valida, que es como se comprueba antes de encenderlo.
- Una instancia existente conserva lo suyo: `integrations/<proveedor>/` puede tener snapshots y
  borradores de la empresa, así que no se retira nada.

### Corregido

- `integrations/README.md`, `integrations/AGENTS.md` y `organization/README.md` se actualizan con el
  toolkit. Los escribe Cauce y envejecían para siempre en cada instancia.

## [0.7.5] - 2026-08-15

### Corregido

- `learn` y `evaluate` resuelven la raíz ops como el resto de los comandos. Quedaron con `cwd` cuando
  los demás pasaron a usarla: invocados desde la carpeta de la compañía —lo normal en sidecar— no
  encontraban ningún cargo.
- **`evaluate` le exigía a una empresa un archivo del toolkit.** `learning/CODEX_AUTOMATION.md`
  documenta cómo corre nuestra automatización de aprendizaje; pedírselo a quien escribe un cargo
  propio era pedirle contabilidad interna nuestra. Ahora sólo se exige a los cargos del sistema: el
  cargo de una empresa necesita contrato, fuentes e historia, no nuestro andamiaje.

## [0.7.4] - 2026-08-15

### Corregido

- **Una bandera antes del último posicional se comía su lugar.** `agents list --json` tomaba `--json`
  como la raíz ops y devolvía `[]` sin error: no había forma de distinguir "no hay cargos" de "te
  contesté con nada". Lo destapó un agente del recorrido de `team`, que pedía ese JSON para resolver
  dónde vive cada cargo y se quedaba sin dato.
- La ruta del contrato de cada cargo es obligatoria en el manifiesto que arma `team`. Siendo opcional
  el agente la omitía, la etapa caía al camino de respaldo y salía a buscar el archivo igual.
  Y lleva el prefijo de la raíz ops: `agents list` las imprime relativas a ella y las etapas corren
  desde otro lado, así que sin prefijo la ruta tampoco resolvía.

## [0.7.2] - 2026-08-15

### Cambiado

- **`team` gastaba un cuarto del recorrido en transcribir salida determinista de un CLI.** Dos agentes
  resolvían el equipo y leían su manifiesto; ahora es uno. El segundo además leía `organization/`
  "como contexto para etapas siguientes", y cada etapa es un agente nuevo con su propio contexto: esa
  lectura no llegaba a ninguna parte y sólo costaba tokens.
- Cada etapa recibe la ruta exacta del contrato de su cargo. Antes le pasaban
  `agents/<tipo>/<slug>/SKILL.md` —con el `<tipo>` literal— y desde 0.4.0 el catálogo ni siquiera está
  en el proyecto, así que el agente salía a buscarlo antes de poder empezar.

## [0.7.1] - 2026-08-15

### Corregido

- **Los cuatro workflows reventaban en su primera línea y nunca habían corrido.** Resolvían su raíz
  con `process.env`, y el runtime de workflows es un sandbox que no expone `process`: `ReferenceError`
  antes de ejecutar nada. `/team` y `/autobuild` —el centro del producto— estaban muertos, y los tests
  no lo veían porque leían los archivos como texto en vez de ejecutarlos.

  La raíz ahora viaja escrita en el archivo, que es lo que `automation install` ya sabía completar, y
  es relativa a donde se abre la herramienta.
- **Y al arreglar eso, `team` y `autobuild` morían en la línea de cierre**: llamaban a un `finish()`
  que el runtime tampoco trae. Peor que el anterior, porque ocurría *después* de gastar cada etapa.
- Dos tests recorren los workflows: uno prohíbe lo que el sandbox no expone, otro falla si se llama a
  algo que ni el runtime da ni el archivo define.

### Cambiado

- Los workflows de integración se invocan con argumentos en vez de variables de entorno, que tampoco
  existen ahí: `/integration-sync jira` y `/integration-promote jira KEY-123`.
- Un test comprueba que ningún workflow use `process`, `require`, `Date.now` ni `Math.random`. El
  sandbox los prohíbe, y usarlos no falla en una rama rara: impide que el archivo arranque.

## [0.6.1] - 2026-08-15

### Corregido

- **El bridge de Antigravity negaba cada llamada a herramienta.** Resolvía la raíz ops subiendo por el
  árbol, y en sidecar es un *hermano* de los repos de producto: no la encontraba y, como falla cerrado,
  bloqueaba todo. Ahora `automation install` le deja escrito dónde quedó, con el recorrido hacia arriba
  como respaldo.
- **`automation install antigravity` copiaba el plugin y lo dejaba inerte.** Antigravity exige un
  registro explícito —`agy plugin install`—, así que los archivos quedaban en su lugar, `doctor` daba
  verde y no se ejecutaba nada. `install` ahora dice el comando exacto y `doctor` avisa si falta, sin
  tocar por su cuenta la configuración global del usuario.

## [0.6.0] - 2026-08-15

### Cambiado

- **Gemini deja de ser el runner sin guards.** Gemini CLI ganó hooks y skills nativas, y el adaptador
  seguía tratándolo como si no los tuviera: los guards se pedían como prechecks manuales —o sea,
  nadie lo detenía— y los 47 cargos no llegaban. Ahora recibe hooks reales en `.gemini/settings.json`
  con sus propios eventos (`BeforeTool`, `AfterAgent`) y variable (`$GEMINI_PROJECT_DIR`), y el
  catálogo completo en `.gemini/skills/`.
- `GEMINI.md` avisa de dos cosas que sólo se ven usándolo: Gemini **desactiva los hooks si la carpeta
  no es de confianza**, y `gemini hooks migrate --from-claude` reescribe `settings.json` entero y se
  lleva puesta el resto de la configuración.

## [0.5.5] - 2026-08-15

### Corregido

- Los comandos de Gemini y las skills de Antigravity también resuelven sus rutas contra la carpeta
  donde se abre la herramienta. Quedaron afuera al marcar el resto: los `.toml` mandaban a leer
  `planning/PROTOCOL.md` y las skills `AGENTS.md`, que desde la raíz de la compañía no existen.
- Un test recorre todo lo que un adaptador copia y falla si una ruta da por sentado dónde se instala.
  Revisando archivo por archivo se escapó tres veces; ahora cubre también lo que se agregue después.

## [0.5.4] - 2026-08-15

### Corregido

- `upgrade` conserva el modo de los archivos que entrega. `tools/ops.js` tiene shebang y quedaba sin
  permiso de ejecución, con el cambio de modo apareciendo en el diff de cada empresa.

## [0.5.3] - 2026-08-15

### Corregido

- `tools/ops.js` se actualiza con el toolkit. Es el shim por donde entra cada comando, no tiene una
  línea de la empresa —dice él mismo que no se edita— y sin declararlo envejecía para siempre: una
  instancia creada antes seguía sin exportar la raíz ops, así que `agents list` y `team list`
  devolvían vacío al invocarse desde la carpeta de la compañía.

## [0.5.2] - 2026-08-15

### Corregido

- **`upgrade` registraba mal lo que entregaba, y eso trababa el siguiente.** El registro se anotaba
  ruta por ruta releyendo el manifiesto del disco en cada una, así que la última anulaba a todas las
  anteriores: tras un `upgrade` casi todos los digests quedaban viejos y la actualización siguiente
  los leía como ediciones de la empresa. Un callejón sin salida sin que nadie hubiera tocado nada.

  Una instancia que ya quedó con digests viejos se destraba con `cauce upgrade --force`: el contenido
  en disco y el del paquete son el mismo, así que no se descarta nada real.

## [0.5.1] - 2026-08-15

### Agregado

- **Codex recibe su `AGENTS.md`.** Era el único runner sin archivo de instrucciones: se llevaba los
  guards y nada más, así que podía ser detenido pero no sabía que existía un protocolo, un catálogo de
  cargos ni equipos. No existe un `CODEX.md`: `AGENTS.md` es el nombre que Codex lee, compartido entre
  herramientas, y por eso en modo embedded no se instala —el de la empresa ya está ahí y manda—.

### Corregido

- **Las rutas de los adaptadores se resuelven contra la carpeta donde se abre la herramienta.** Al
  mover la instalación a la raíz de la compañía quedaron apuntando al lugar equivocado: `@AGENTS.md`
  y `@planning/PROTOCOL.md` no resolvían, y los workflows buscaban `planning/` donde no estaba. Ahora
  cada fuente marca el lugar con `{{OPS_DIR}}` y `install` lo completa; `doctor` compara contra lo
  mismo que `install` escribe.
- `tools/ops.js` exporta la raíz ops, que ya calculaba y no usaba. Invocado desde la carpeta de la
  compañía —`node <empresa>-ops/tools/ops.js team list`—, `agents list` y `team list` resolvían contra
  el cwd y devolvían **vacío en vez de fallar**. `team` además no aceptaba una raíz de ningún modo.

## [0.5.0] - 2026-08-15

### Cambiado

- **Los adaptadores de runner y los workflows tampoco se copian al proyecto.** Cierran el mismo
  criterio que ya rige para cargos y equipos: los lee el motor, no la empresa. `automation install`,
  `check` y `doctor` los resuelven desde la dependencia npm, o desde `.ops/` cuando el repo no usa
  npm. Nadie los editaba —la lista de runners es cerrada, así que una empresa ni siquiera podía
  agregar el suyo— y `automatization/workflows/` estaba además duplicado dentro de cada instancia,
  idéntico a lo que `automation install` deja en `.claude/workflows/`.
- `upgrade` retira `automatization/runners/` y `automatization/workflows/` de las instancias que los
  arrastran de una versión anterior. Un guard propio en `automatization/hooks/` no se toca.
- `automatization/hooks/` se queda en el proyecto, y esto no es una excepción arbitraria: la
  configuración de cada runner nombra cada guard por ruta literal y no sabe resolver en cascada. Ahí
  es también donde una empresa agrega el suyo.

- **En modo `sidecar`, `automation install` instala en la carpeta de la compañía, no dentro del repo
  ops.** El repo ops coordina varios repos de producto y es hermano de ellos, así que instalar el
  runner adentro lo dejaba sin ver una sola línea de código: el dev que abría su herramienta donde
  está el código no tenía guards, ni cargos, ni workflows. Las rutas de guards y los punteros de cada
  cargo se reescriben con el prefijo del repo ops al instalar.

### Corregido

- **Una mejora del toolkit en el wiring ahora llega a un runner ya instalado.** `install` conservaba
  cualquier archivo existente, así que un workflow o un `CLAUDE.md` mejorado río arriba no llegaba
  nunca. Peor: si el archivo difería, `install` fallaba —`existe y difiere de la fuente canónica`— y
  `doctor` lo reportaba como error, dejando a la empresa sin salida salvo borrar a mano. Ahora el
  registro de entrega distingue las dos cosas que antes se veían iguales: lo que la empresa editó se
  conserva o detiene la instalación, y lo que sólo cambió río arriba se actualiza.
- `automation check` compara los guards de la instancia contra los del paquete. Existir y ser
  ejecutable no alcanzaba: un guard viejo no falla, **deja de proteger en silencio**, y `check`,
  `doctor` y `upgrade` daban verde igual porque sólo miraban el número de versión. Ahora bloquea la
  instalación del runner y dice qué correr; distingue además el guard que la empresa editó del que
  simplemente quedó atrás.
- `upgrade` recuerda reinstalar los runners: el wiring vive fuera de la instancia y lo escribe otro
  comando, así que sin el aviso una mejora se quedaba en el paquete.
- Los guards resuelven la raíz ops por su cuenta. `findOpsRoot` sólo sube por el árbol, y en sidecar
  la raíz ops es un *hermano* de los repos de producto: desde ahí no la encontraba, devolvía vacío y
  el guard permitía todo **en silencio**. `run-hook.sh` ya sabía dónde vive; ahora lo exporta.
- `automatization/README.md` y `automatization/AGENTS.md` se actualizan con el toolkit. Los escribe
  Cauce y envejecían en cada instancia: después de retirar `runners/` y `workflows/`, el README de la
  empresa seguía explicando cómo usar dos carpetas que ya no existían.
- El registro de entrega olvida lo que ya no está en disco. Sólo crecía: al retirar una ruta dejaba
  su digest para siempre, y el día que un nombre se reutilizara la entrega nueva se habría leído como
  una edición local y detenido la actualización.

## [0.4.1] - 2026-08-15

### Corregido

- Al mover los equipos al paquete se fue con ellos su documentación, así que una instancia nueva se
  quedaba sin `teams/README.md` ni la plantilla: sabía que podía escribir equipos propios, pero no
  con qué contrato. Lo que le habla a la empresa viaja con la instancia aunque la colección no.

## [0.4.0] - 2026-08-15

### Cambiado

- **Ni el catálogo de cargos ni los equipos se copian al proyecto.** Son definiciones que consume el
  motor: se resuelven desde la dependencia npm, o desde `.ops/` cuando el repo no usa npm. Las reglas
  y decisiones de `planning/*/system/` sí siguen materializadas, porque la empresa las lee y las cita
  en su propio repositorio.
- **El catálogo de cargos ya no se copia al proyecto.** Se resuelve desde la dependencia npm, o desde
  `.ops/agents/` cuando el repo no usa npm. Una instancia pasa de ~950 KB a ~480 KB y su `git diff`
  muestra sólo lo que la empresa escribió.
- El ciclo mensual de aprendizaje dejó de distribuirse: investiga cómo evoluciona una profesión, y eso
  es igual para todas las empresas. Corriéndolo en cada instalación, cuatro empresas producían cuatro
  investigaciones casi idénticas del mismo tema, cada una peor que una hecha bien. Ahora vive sólo en
  el repositorio del toolkit y llega actualizando la dependencia.
- `learn` y `learn --proposal` fallan con explicación si se corren sobre un cargo del catálogo dentro
  de una instancia: escribirían en el paquete y se perderían en el próximo `npm ci`.
- Las fuentes `local://` salieron de los 47 cargos. Lo que un cargo debe saber de una empresa vive
  ahora en `organization/roles/<slug>.md`, y los 47 lo citan.

### Corregido

- `upgrade` no actualizaba el catálogo de cargos, o sea el 75% del paquete: un cargo nuevo o mejorado
  nunca llegaba a un proyecto ya inicializado. Ahora se refresca como el resto del sistema, con una
  excepción precisa: `learning/` dentro de cada cargo es del proyecto y no se toca, porque los
  informes acumulados son lo único que no se puede reponer desde el paquete.

- El `$schema` de `ops.config.json` apuntaba siempre a `.ops/engine/`, una ruta que no existe cuando el
  motor viene como dependencia. Ahora se escribe según dónde quedó el motor.

## [0.3.0] - 2026-08-15

### Añadido

- Equipo `feasibility-review`: tres etapas para decidir si una intención vale el esfuerzo con la
  evidencia que ya existe. Recomendar investigar es un resultado legítimo, no una falla.
- Equipo `incident-review`: revisión posterior de un incidente ya contenido. **No responde incidentes
  en vivo** y su documentación lo dice: un recorrido de agentes no está de guardia, no accede a
  producción y no decide bajo presión con información parcial.
- Los equipos declaran su salida en `outcome`: `epic` deja una épica candidata en `roadmap/`;
  `report` deja un informe en `planning/reports/` y sus seguimientos en el INBOX, sin promover.
  Ninguno de los dos promueve al BACKLOG.
- `/team` acepta el equipo por prefijo —`/team incident-review: se cayó el checkout`— confirmándolo
  contra los que existen, así que un texto con dos puntos no dispara un equipo inventado.
- `teams/000-template.md` y `teams/README.md`: era la única colección que se distribuía sin plantilla,
  lo que obligaba a copiar un manifiesto de nueve etapas y adivinar el esquema.
- `autobuild` ejecuta cada fase bajo el contrato del cargo que la posee, en vez de pedirle criterio
  genérico a un agente sin rol. Los dueños por defecto son deterministas; los condicionales
  —seguridad, privacidad, sre, ux— entran por riesgo, plataforma y alcance, nunca por rutina, y el
  reparto queda registrado en el WIP para poder auditar quién revisó qué.
- Cargo `growth-marketer`: adquisición y activación con economía unitaria explícita. Cubre la
  decisión que no tenía dueño —dónde invertir para adquirir y si funcionó—, entre posicionamiento
  (`product-marketing-manager`) y proceso de ingresos (`revenue-operations-manager`).
- Cargo `finops-engineer`: costo de operar visible y atribuido, incluido el gasto en modelos de IA,
  que escala con el contexto arrastrado y no con la cantidad de usuarios.
- `agents list --json` incluye la ruta resuelta de cada cargo, para que quien lo consuma no
  reconstruya dónde ganó la precedencia.

### Cambiado

- Las etapas de un equipo declaran si son de `discovery` o de `delivery`. `/team` recorre sólo las de
  descubrimiento y propone; `autobuild` ejecuta la entrega, y sólo después de la promoción humana.
- `agents/coordinators/`, `agents/specialists/` y `agents/workflows/` se eliminaron: eran directorios
  vacíos que prometían una taxonomía sin contenido, y `workflows` además colisionaba en nombre con
  `automatization/workflows/`. El mecanismo no cambia: cualquier directorio bajo `agents/` sigue
  siendo un tipo válido y se reconoce cuando tiene contenido.

### Corregido

- `/team` recorría todas las etapas del manifiesto, incluida la de construcción: un recorrido de
  descubrimiento llegaba a pedir un incremento funcionando, o sea código escrito antes de que la
  épica existiera y antes de que nadie la aprobara.
- Invocado como slash command, `/team` recibía la intención como texto y buscaba `args.intent`, así
  que se detenía antes de su primera etapa en la forma más obvia de llamarlo.
- `upgrade` sugería mover un guard editado "junto a `system/`", un mecanismo que no existe en
  `automatization/hooks/`. Ahora explica las tres vías que sí funcionan: agregar un guard propio,
  quitarlo de la configuración del runner para desactivarlo, o descartar el cambio con `--force`.
- `upgrade --force` descartaba ediciones locales sin dejar rastro; ahora las lista.

## [0.2.0] - 2026-08-14

### Añadido

- `cauce upgrade` actualiza una instancia sin tocar nada del proyecto, con `--check` para saber si hay
  algo pendiente y `--force` para descartar ediciones locales del runtime. Antes no existía forma de
  actualizar: cada proyecto quedaba congelado en la versión con la que nació.
- La frontera `system/` se extendió a `planning/rules/`, `teams/` y `agents/`. Un archivo propio con el
  mismo nombre, ID o slug reemplaza al del sistema, y `check` lo reporta como override explícito.
- Los 45 cargos del catálogo se instalan en el runner como skills invocables por nombre. Antes viajaban
  a cada proyecto sin que ningún runner los usara.
- `cauce agents list` resuelve la precedencia del catálogo y marca cuáles son propios del proyecto.
- El motor se declara como dependencia npm cuando el repo ya usa npm, y se copia sólo cuando no.
- Las instancias registran `cauceVersion` en `ops.config.json`.
- Workflow `/team`: recorre las etapas de un equipo exigiendo cada exit gate y deja una épica
  candidata en `roadmap/`. Es el espejo de `/autobuild`, que ejecuta trabajo ya aprobado. Nunca
  promueve al BACKLOG: esa firma sigue siendo humana.
- `team show --json` expone el manifiesto completo para que un workflow lo ejecute sin parsearlo.

### Cambiado

- Los cargos que trae Cauce se movieron a `agents/roles/system/`. Un proyecto que quiera su propia
  versión de un cargo la escribe en `agents/roles/` con el mismo slug.
- Las composiciones de equipo se movieron a `teams/system/`.
- `tools/ops.js`, el wrapper de hooks y el bridge de Antigravity resuelven el motor en cascada:
  dependencia npm, copia local, repositorio del toolkit.
- `automation install` reemplaza los guards que este mismo toolkit había registrado sueltos por el
  grupo que los cubre. Sin esa poda, una instalación previa ejecutaba cada guard dos veces por
  herramienta; con `verify` eso significaba correr la suite de tests dos veces en cada commit.

### Corregido

- `automation install` funcionaba sólo si el motor estaba copiado: en modo dependencia fallaba con
  "falta engine/hooks/run.js". Los tres puntos de entrada resuelven ahora la misma cascada.
- `upgrade` ya no borra archivos que el proyecto agregó al runtime, como un guard propio.
- El README de `automatization/runners/` que recibe un proyecto es el que le habla al proyecto, no el
  que documenta el contrato de adaptadores; antes llegaba uno u otro según se usara `--force`.
- `team check` distingue entre un agente inexistente y uno ambiguo.

## [0.1.0] - 2026-08-14

Primera publicación como `@ingeniomaps/cauce`.

- CLI determinista de planificación: `init`, `check`, `tree`, `context`, `archive`.
- Protocolo agnóstico al runner con adaptadores para Claude, Codex, Gemini y Antigravity.
- Once guards portables agrupados por evento, un proceso por llamada de herramienta.
- Catálogo de 45 cargos con evaluaciones y ciclo de aprendizaje mensual.
- Integraciones por staging de sólo lectura, con Jira como primer proveedor.
