@ingeniomaps/cauce 0.81.0 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +248 -0
- package/automatization/hooks/README.md +10 -6
- package/automatization/hooks/guard-ops-config-shell.sh +3 -0
- package/automatization/hooks/guard-ops-config.sh +3 -0
- package/automatization/runners/antigravity/rules/cauce.md +7 -2
- package/automatization/runners/claude/CLAUDE.md +1 -4
- package/automatization/runners/codex/AGENTS.md +7 -3
- package/automatization/runners/gemini/GEMINI.md +1 -4
- package/automatization/shared/inbox.js +47 -0
- package/automatization/workflows/agent-eval.js +1 -1
- package/automatization/workflows/autobuild.js +56 -18
- package/automatization/workflows/flow.js +45 -8
- package/automatization/workflows/onboard.js +32 -3
- package/engine/automation/index.js +19 -4
- package/engine/automation/rules.js +122 -0
- package/engine/automation/runners.js +3 -1
- package/engine/cli/instance.js +35 -6
- package/engine/cli/planning.js +16 -2
- package/engine/config/validate.js +53 -3
- package/engine/core/onboarding.js +37 -7
- package/engine/core/ownership.js +64 -1
- package/engine/core/scan.js +23 -9
- package/engine/hooks/approval.js +50 -15
- package/engine/hooks/chat.js +122 -11
- package/engine/hooks/input.js +1 -11
- package/engine/hooks/ops-config.js +110 -0
- package/engine/hooks/push.js +194 -0
- package/engine/hooks/run.js +17 -2
- package/engine/hooks/secrets-shell.js +13 -2
- package/engine/hooks/self-approval.js +93 -13
- package/engine/hooks/shell.js +13 -11
- package/engine/integrations/registry.js +13 -1
- package/engine/planning/inbox.js +36 -0
- package/engine/planning/parser.js +22 -9
- package/engine/planning/recurring.js +13 -2
- package/engine/schemas/ops-config.schema.json +21 -0
- package/package.json +1 -1
- package/template/AGENTS.md +13 -5
- package/template/gitignore +5 -0
- package/template/planning/INBOX.md +2 -1
- package/template/planning/RECURRING.md +6 -5
- package/template/planning/delivery/teamwork.md +1 -1
- package/template/planning/rules/system/commits.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,254 @@ desde este repositorio no va, porque el que lee no puede actuar sobre eso. Cuand
|
|
|
14
14
|
unas pocas líneas casi siempre es porque cuenta cómo se descubrió el problema o por qué se eligió el
|
|
15
15
|
diseño — eso vive en el commit y en el código.
|
|
16
16
|
|
|
17
|
+
## [0.83.0] - 2026-09-11
|
|
18
|
+
|
|
19
|
+
### Agregado
|
|
20
|
+
|
|
21
|
+
- **Autorizar en el chat ahora se dice como se dice.** Un guard dejaba pasar lo que pedías con un verbo de
|
|
22
|
+
acción —«leé el .env», «usá esa ruta»— y se seguía frenando si lo decías autorizando: «autorizo la
|
|
23
|
+
lectura del .env» no traía ninguna palabra que Cauce reconociera, así que había que repetirlo con otras
|
|
24
|
+
palabras, contestar «dale», o escribir la línea a mano. Entran las formas con que una persona concede, en
|
|
25
|
+
español y en inglés: `autorizo`, `autorizá`, `te autorizo`, `permito`, `apruebo`, `habilito`,
|
|
26
|
+
`authorize`, `allow`, `grant`, `approved`.
|
|
27
|
+
|
|
28
|
+
Lo que **no** cambia es la línea de siempre: nombrar algo no lo autoriza. Los sustantivos que nombran el
|
|
29
|
+
acto —«la lectura del .env», «¿hace falta autorizacion?»— siguen sin autorizar nada, porque aparecen
|
|
30
|
+
igual en una pregunta que no pide nada. Y negar sigue negando, también con las palabras nuevas.
|
|
31
|
+
|
|
32
|
+
**Qué cambia para vos**: si autorizás algo diciendo que lo autorizás, pasa a la primera. Antes eso
|
|
33
|
+
costaba una vuelta, y a veces terminaba en la variable que apaga el guard para toda la sesión.
|
|
34
|
+
|
|
35
|
+
- **El permiso de push ya no se lo puede escribir el agente.** `runner.allowPush` y
|
|
36
|
+
`runner.pushToLiveBranches` deciden qué push se publica y viven en `ops.config.json`, que hasta ahora
|
|
37
|
+
ningún guard miraba: el bloqueo que dice «esto lo decide una persona» se levantaba editando ese mismo
|
|
38
|
+
archivo, con la herramienta de escritura o con un `sed -i`. Lo cierran dos guards nuevos: `ops-config`
|
|
39
|
+
compara lo que se va a escribir contra lo que hay en disco y frena si cambia alguna de esas dos llaves,
|
|
40
|
+
y `ops-config-shell` frena toda escritura por shell sobre el archivo, porque un comando no dice con qué
|
|
41
|
+
va a quedar.
|
|
42
|
+
|
|
43
|
+
**Qué cambia para vos**: el resto de `ops.config.json` —`workspaceRoots`, `writableOutsideRoots`,
|
|
44
|
+
`migrations`, lo que sea— se sigue editando igual que siempre; sólo esas dos llaves pasan a necesitar una
|
|
45
|
+
persona, que las pone editando el archivo ella o pidiéndoselo al agente en el chat nombrando
|
|
46
|
+
`ops.config.json`. Por shell el archivo queda cerrado entero: ese cambio va por la herramienta de
|
|
47
|
+
escritura y con el archivo completo, que es lo único que el guard puede comparar. Si tu runner registra
|
|
48
|
+
los guards de a uno en vez de por grupo, corré `automation install` para que aparezcan
|
|
49
|
+
`guard-ops-config.sh` y `guard-ops-config-shell.sh`.
|
|
50
|
+
|
|
51
|
+
- **Un push aprobado deja constancia en `planning/.push-log`.** Cuando Cauce deja pasar un `git push` por
|
|
52
|
+
una aprobación —la orden en el chat, un «dale», o la línea exacta en `planning/.ops-approval`— anexa una
|
|
53
|
+
línea con la fecha en que se autorizó, el remoto, la rama, por qué vía y la sesión. Por
|
|
54
|
+
`runner.allowPush` no anota nada: esa autorización ya está escrita en `ops.config.json`.
|
|
55
|
+
|
|
56
|
+
El archivo sólo agrega, no guarda el texto de lo que escribiste, y si no se puede escribir no frena el
|
|
57
|
+
push. La fecha se llama `authorizedAt` y no `at` a propósito: el hook corre antes del comando, así que la
|
|
58
|
+
línea dice que el push **se autorizó**, no que se haya publicado.
|
|
59
|
+
|
|
60
|
+
El anillo «Local» de `planning/delivery/teamwork.md` lo nombra junto al resto de lo que no viaja por git.
|
|
61
|
+
Ese archivo lo mantiene Cauce, así que `upgrade` te lo reemplaza: si lo editaste, mirá el diff antes.
|
|
62
|
+
|
|
63
|
+
**Qué cambia para vos**: si necesitás responder quién autorizó un push y cuándo, ahora hay de dónde
|
|
64
|
+
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
|
|
65
|
+
persona: Cauce no tiene identidad de usuario—. Las instancias nuevas lo traen gitignoreado; si ya tenés
|
|
66
|
+
una, agregá `planning/.push-log` a tu `.gitignore`.
|
|
67
|
+
|
|
68
|
+
- **Lo que un recorrido deja en el INBOX dice ahora de qué vía entró.** Cada línea que `autobuild`, `flow` y
|
|
69
|
+
`onboard` le pasan al agente que escribe viene con su procedencia al final —`(autobuild · t-012 ·
|
|
70
|
+
2026-09-11)`, `(flow · planning/reports/2026-09-11-alta.md · 2026-09-11)`, `(onboard · 2026-09-11)`—: el
|
|
71
|
+
recorrido, la unidad de la que salió y la fecha. La arma el recorrido y el pedido dice que se copie tal
|
|
72
|
+
cual, en vez de confiar en que el agente se acuerde de escribirla. El formato de una entrada no cambió ni
|
|
73
|
+
el molde tampoco, así que `ops tree`, `check` y `context --json` siguen leyendo exactamente lo mismo.
|
|
74
|
+
|
|
75
|
+
**Qué cambia para vos**: nada que hacer. Al recorrer el INBOX vas a ver el origen en lo que se escriba de
|
|
76
|
+
acá en adelante; lo que ya tenés escrito no lo gana, y una entrada que escribas a mano sigue sin él,
|
|
77
|
+
porque el INBOX es tuyo. Si en algún lado recortás el ancho de esas líneas, contá entre 20 y 60
|
|
78
|
+
caracteres más por entrada: el tope de 240 pasó a medir la línea entera, así que lo que cede es el texto
|
|
79
|
+
del hallazgo y nunca el origen.
|
|
80
|
+
|
|
81
|
+
### Cambiado
|
|
82
|
+
|
|
83
|
+
- **Lo que autorizás en el chat no alcanza a los gates de un commit.** Lo que un guard deja pasar porque
|
|
84
|
+
vos lo pediste sigue valiendo mientras dure la sesión, con dos excepciones: publicar, y los gates de un
|
|
85
|
+
commit —`governance`, `verify` y `dependencies`—, que preguntan cada vez. Lo que autoriza un commit no
|
|
86
|
+
dice nada del siguiente, porque el índice ya es otro. Por el mismo motivo, nombrar `ops.config.json` una
|
|
87
|
+
vez ya no deja escribir `runner.allowPush` ni `runner.pushToLiveBranches` por el resto de la sesión: ahí
|
|
88
|
+
también se pregunta cada vez.
|
|
89
|
+
|
|
90
|
+
**Qué cambia para vos**: si commiteás varias veces seguidas tocando gobernanza, con un gate en rojo o con
|
|
91
|
+
un manifiesto sin su lockfile, vas a decir «dale» en cada commit en vez de una vez por sesión. Lo que no
|
|
92
|
+
es un gate de commit —leer un `.env`, reescribir una migración, borrar una prueba— sigue valiendo toda la
|
|
93
|
+
sesión.
|
|
94
|
+
|
|
95
|
+
### Corregido
|
|
96
|
+
|
|
97
|
+
- **Un bloqueo ya no te ofrece pegar una ruta que el shell no expandió.** Cuando el comando nombra la
|
|
98
|
+
credencial a través de una variable —`cat ops/$O/.env.infisical`—, Cauce no puede saber qué archivo es:
|
|
99
|
+
esa variable la expande tu shell, que es otro proceso. Antes el bloqueo ofrecía igual esa línea para
|
|
100
|
+
`planning/.ops-approval`, y pegarla no servía: sólo volvía a valer si el comando se repetía escrito
|
|
101
|
+
igual, y dejaba de valer apenas alguien escribía la ruta de verdad. Con la forma `${O}` era peor, porque
|
|
102
|
+
la línea ofrecida ni siquiera era el archivo que el comando lee. Ahora no la ofrece, dice por qué y qué
|
|
103
|
+
hacer.
|
|
104
|
+
|
|
105
|
+
**Qué cambia para vos**: lo que un bloqueo te ofrece pegar es siempre una línea que después funciona. Si
|
|
106
|
+
armás la ruta con variables, escribila entera en ese comando —o contestá «dale», que destraba igual—.
|
|
107
|
+
|
|
108
|
+
- **Lo que autorizás en el chat ya no deja de valer con el mensaje siguiente.** Hasta 0.82.0 el permiso
|
|
109
|
+
duraba un mensaje: pedías leer el `.env`, el agente lo leía, y apenas escribías cualquier otra cosa
|
|
110
|
+
—«gracias, seguí»— el mismo archivo volvía a frenarse. Ahora lo que un guard deja pasar porque vos lo
|
|
111
|
+
pediste queda anotado y sigue valiendo mientras la sesión siga. Lo revoca decirlo: «no toques el .env» lo
|
|
112
|
+
saca, y lo revocado no vuelve solo.
|
|
113
|
+
|
|
114
|
+
El permiso sigue siendo angosto: vale para el ítem tal como el guard lo nombra, no cruza de sesión y no
|
|
115
|
+
alcanza a un subagente ni a un recorrido de Cauce. Y **no alcanza a lo que vuelve a tener consecuencia
|
|
116
|
+
cada vez**: publicar y los gates de un commit, que preguntan siempre —ver la entrada de abajo—. Volver a
|
|
117
|
+
leer lo que ya se leyó no agrega consecuencia; volver a publicar, o commitear otra vez con otro índice,
|
|
118
|
+
sí.
|
|
119
|
+
|
|
120
|
+
**Qué cambia para vos**: si trabajás con credenciales o con rutas que los guards vigilan, alcanza con
|
|
121
|
+
pedirlo una vez por sesión en lugar de repetir la frase o contestar «dale» a cada rato. Si querés cortar
|
|
122
|
+
un permiso antes de cerrar la sesión, decilo nombrando el archivo.
|
|
123
|
+
|
|
124
|
+
- **El agente no puede escribir en tu aprobación una línea que vos no pediste.** `planning/.ops-approval`
|
|
125
|
+
sólo lo podía escribir el agente si vos nombrabas el archivo en el chat, y ahí se terminaba el control:
|
|
126
|
+
pedías agregar `src/login.js` y podía escribir `push origin main`, que es la línea que autoriza publicar
|
|
127
|
+
en la rama viva. Ahora lo que se escribe se compara línea por línea con lo que pediste: pasan las que
|
|
128
|
+
nombraste, y cualquier otra frena la escritura mostrándote cuál era. Las que ya estaban en el archivo no
|
|
129
|
+
hay que volver a nombrarlas, y quitar líneas no pide permiso. Por shell —un `echo >>`, un `sed -i`— el
|
|
130
|
+
archivo queda cerrado: un comando no dice con qué va a quedar, así que no hay contenido que comparar.
|
|
131
|
+
|
|
132
|
+
**Qué cambia para vos**: si le pedís al agente que te agregue rutas, nombralas en el mismo mensaje
|
|
133
|
+
—«agregá `src/login.js` y `src/altas.js` a `.ops-approval`»—; si frena, te dice exactamente qué línea
|
|
134
|
+
quería escribir y un «dale» aprueba esa y nada más. Vos seguís editando el archivo a mano como siempre,
|
|
135
|
+
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.
|
|
136
|
+
|
|
137
|
+
## [0.82.0] - 2026-09-11
|
|
138
|
+
|
|
139
|
+
### Cambiado
|
|
140
|
+
|
|
141
|
+
- **`allowPush: true` ya no publica en la rama viva ni desde un subagente.** La llave dejaba pasar cualquier
|
|
142
|
+
push, `main` incluido, y el de un subagente igual que el tuyo. Ahora no alcanza a `main`, `master` ni a la
|
|
143
|
+
rama por defecto del remoto, y un subagente no publica con ningún permiso. Tampoco la alcanza una orden en
|
|
144
|
+
el chat.
|
|
145
|
+
|
|
146
|
+
**Qué cambia para vos**: si publicabas en la rama viva con `allowPush: true`, agregá
|
|
147
|
+
`"pushToLiveBranches": ["main"]` en `runner` de `ops.config.json`, o pegá `push origin main` en
|
|
148
|
+
`planning/.ops-approval` para un push puntual.
|
|
149
|
+
|
|
150
|
+
- **R10 y el párrafo de publicación de `AGENTS.md` se reescribieron.** Los dos decían que el push se
|
|
151
|
+
comprueba contra `runner.allowPush` y nada más; ahora dicen lo que de verdad comprueba el motor: esa llave
|
|
152
|
+
—que no llega a la rama viva sin `runner.pushToLiveBranches`, ni a un subagente— o la orden que la persona
|
|
153
|
+
da en el chat nombrando el remoto y la rama. Los dos archivos los mantiene Cauce, así que `upgrade` los
|
|
154
|
+
reemplaza enteros.
|
|
155
|
+
|
|
156
|
+
**Qué cambia para vos**: nada que hacer. Si tu equipo cita R10 en algún documento propio, el texto que
|
|
157
|
+
baja ahora es más largo y dice qué sostiene un guard y qué sostiene una persona.
|
|
158
|
+
|
|
159
|
+
### Corregido
|
|
160
|
+
|
|
161
|
+
- **Nombrar algo en el chat ya no lo autoriza: hay que pedirlo.** Desde 0.81.0, lo que nombrabas en el chat
|
|
162
|
+
pasaba los guards aunque no lo pidieras: «¿para qué sirven las credentials?» dejaba leer `credentials`, y
|
|
163
|
+
«el .env tiene algo raro?» dejaba leer el `.env`. Ahora la frase que lo nombra tiene que pedir una acción
|
|
164
|
+
—«leé», «abrime», «borrá», «read»…—, y la negación sigue frenando como antes.
|
|
165
|
+
|
|
166
|
+
**Qué cambia para vos**: si pedís algo sin un verbo que Cauce reconozca, el agente te dice qué se frenó y
|
|
167
|
+
con un «dale» pasa.
|
|
168
|
+
|
|
169
|
+
- **`upgrade` ya no pisa un guard tuyo que se llama como uno nuevo del paquete.** Cuando una versión empezaba
|
|
170
|
+
a traer un nombre que ya usabas en `automatization/hooks/`, lo reemplazaba sin decir nada: 0.81.0 lo hizo
|
|
171
|
+
con `guard-chat.sh`. Ahora lo conserva y lo avisa; `--check` lo cuenta, y `--force` lo reemplaza diciéndolo.
|
|
172
|
+
|
|
173
|
+
**Qué cambia para vos**: si ves «ya existía y Cauce no lo entregó», renombrá tu guard y repetí `upgrade`;
|
|
174
|
+
mientras tanto, el guard del paquete con ese nombre no está instalado.
|
|
175
|
+
|
|
176
|
+
- **Un guard propio ya no aparece como «editado localmente».** `upgrade` registraba todo lo que había en
|
|
177
|
+
`automatization/hooks/`, así que editar un guard tuyo lo volvía una edición del molde: `upgrade --check`
|
|
178
|
+
salía con 1, `check` lo contaba como congelado, y `--force` anunciaba que lo descartaba sin tocarlo. Ahora
|
|
179
|
+
sólo registra lo que trae el paquete, y el primer `upgrade` suelta lo que una versión anterior registró de
|
|
180
|
+
más.
|
|
181
|
+
|
|
182
|
+
**Qué cambia para vos**: nada que hacer; tus guards dejan de aparecer en esos avisos.
|
|
183
|
+
|
|
184
|
+
- **El aviso de credenciales sin dueño avisa sólo credenciales.** `check` listaba toda variable que el
|
|
185
|
+
proyecto declaraba y ningún contrato nombraba: en un proyecto real fueron ciento ocho nombres de build, y
|
|
186
|
+
la única credencial quedó en «y 1 más». Ahora cuenta las que tienen nombre de secreto —`…_PASSWORD`,
|
|
187
|
+
`…_TOKEN`, `…_KEY`, `…_DSN`…—, dice en qué servicio está cada una y dónde se escribe su dueño, y avisa
|
|
188
|
+
aparte cuando un servicio pasó el tope de variables que se revisan.
|
|
189
|
+
|
|
190
|
+
**Qué cambia para vos**: la declaración de secretos y la configuración de una integración ahora también
|
|
191
|
+
rechazan una clave que termine en `_key`, `dsn` o `credentials`, igual que ya rechazaban `…token`. Si
|
|
192
|
+
tenés una, cambiala por la referencia a una variable de entorno. Y una credencial con nombre de
|
|
193
|
+
configuración —`STRIPE_LIVE`— no aparece en el aviso: el criterio es el nombre.
|
|
194
|
+
|
|
195
|
+
- **Una variable ya no se da por cargada porque otra la contiene en su nombre.** Con `API_SECRET_ROTATION`
|
|
196
|
+
escrita en `organization/workspace.md`, `API_SECRET` dejaba de avisarse aunque nadie la nombrara. Ahora
|
|
197
|
+
cuenta sólo el nombre entero.
|
|
198
|
+
|
|
199
|
+
**Qué cambia para vos**: puede aparecer en el aviso una credencial que antes quedaba tapada; escribí su
|
|
200
|
+
dueño y se va.
|
|
201
|
+
|
|
202
|
+
- **Un push que pedís en el chat ya no se frena.** Con `allowPush: false`, `git push` se frenaba aunque lo
|
|
203
|
+
hubieras pedido con todas las letras. Ahora pasa si tu mensaje nombra el remoto y la rama —«subí feat/x a
|
|
204
|
+
origin», `git push origin feat/x`—, y si no los nombra, el agente te dice qué se frenó y con un «dale»
|
|
205
|
+
pasa ese push y ningún otro. `planning/.ops-approval` también acepta la línea `push <remoto> <rama>`, tal
|
|
206
|
+
cual y sin patrones. Un `git push origin +rama` ahora se frena como el `--force` que es.
|
|
207
|
+
|
|
208
|
+
**Qué cambia para vos**: para publicar una rama de trabajo alcanza con pedirlo nombrando remoto y rama, o
|
|
209
|
+
con contestar «dale».
|
|
210
|
+
|
|
211
|
+
- **La sesión carga las reglas que rigen tu proyecto, no las cuatro de `system/` fijas.** El archivo de
|
|
212
|
+
instrucciones de cada runner importaba siempre `planning/rules/system/`: cargaba la regla que tu proyecto
|
|
213
|
+
había sobrescrito y ninguna de las tuyas. Ahora `automation install` pone las vigentes —las tuyas y las de
|
|
214
|
+
`system/` que no sobrescribiste— en Claude, Gemini, Codex y Antigravity, y un `CLAUDE.md` que editaste
|
|
215
|
+
recibe el bloque nuevo sin perder lo tuyo. Como `upgrade` no reinstala, `check` y `automation doctor`
|
|
216
|
+
avisan cuando lo instalado quedó atrás. `AGENTS.md` dice ahora que, donde una regla tuya y una del
|
|
217
|
+
sistema chocan, rige la tuya.
|
|
218
|
+
|
|
219
|
+
**Qué cambia para vos**: después de actualizar, o cada vez que escribas o sobrescribas una regla,
|
|
220
|
+
reinstalá el adaptador (`make install-claude`, o el de tu runner); `check` te dice cuándo hace falta. Si
|
|
221
|
+
tu `CLAUDE.md` es tuyo y no tiene nada de Cauce, poné las marcas `<!-- cauce:reglas inicio -->` y
|
|
222
|
+
`<!-- cauce:reglas fin -->` donde quieras el bloque y reinstalá.
|
|
223
|
+
|
|
224
|
+
- **`autobuild` trabaja con las reglas de tu proyecto y ya no cita una que retiraste.** Los subagentes
|
|
225
|
+
planificaban, construían y revisaban sin conocer tus reglas, y la fila que pedía partir una tarea citaba
|
|
226
|
+
R17 aunque la hubieras retirado. Ahora `context` dice qué reglas rigen —línea `RULES`, campo `rules` en
|
|
227
|
+
`--json`—, cada fase recibe esa lista, y Review tiene que nombrar contra cuáles revisó o la corrida para
|
|
228
|
+
con `review-unbacked`. Ningún prompt cita una regla por número.
|
|
229
|
+
|
|
230
|
+
**Qué cambia para vos**: nada que hacer.
|
|
231
|
+
|
|
232
|
+
- **Los recorridos ya no llenan el INBOX sin medida.** `autobuild` volcaba en Propuestas todo lo que Review
|
|
233
|
+
anotaba sin frenar, entero, y `flow` y `onboard` escribían sin tope ni forma. Ahora cada recorrido escribe
|
|
234
|
+
como mucho tres entradas por tarea o por corrida, de una línea, con la forma del molde y sin repetir un
|
|
235
|
+
nombre que ya está. Lo que pasa del tope no se pierde: `autobuild` lo cuenta en el `done/` de la tarea,
|
|
236
|
+
`flow` lo deja en el informe y `onboard` lo nombra al cerrar. Y `check` avisa, sin fallar, cuando
|
|
237
|
+
`INBOX.md` pasa de 300 líneas.
|
|
238
|
+
|
|
239
|
+
**Qué cambia para vos**: nada que hacer. Si querés otro umbral para el aviso, poné
|
|
240
|
+
`"inbox": { "warnLines": N }` en `ops.config.json`. El molde de `INBOX.md` ya no pide la evidencia dentro
|
|
241
|
+
de una propuesta; tu `INBOX.md` no se toca.
|
|
242
|
+
|
|
243
|
+
- **La recurrencia que recorre el INBOX viene activa, y descomentar los ejemplos de `RECURRING.md` ya no
|
|
244
|
+
rompe `check`.** Una instancia nueva trae la fila `inbox`, trimestral, que vence por primera vez tres meses
|
|
245
|
+
después de `init`. Las filas de ejemplo declaran el `(service: …)` que `check` exige. Y `check` avisa una
|
|
246
|
+
entrada del INBOX que se llama como una tarea de `done/`: probablemente se promovió y quedó ahí.
|
|
247
|
+
|
|
248
|
+
**Qué cambia para vos**: `RECURRING.md` es tuyo y `upgrade` no lo toca. Para tener la recurrencia en una
|
|
249
|
+
instancia que ya existe, agregá debajo de la tabla
|
|
250
|
+
`| 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) |`
|
|
251
|
+
con una fecha futura: `Desde` es cuándo vence por primera vez, no el día en que escribís la fila.
|
|
252
|
+
|
|
253
|
+
- **Dos repositorios que terminan en una carpeta con el mismo nombre ya no salen con el mismo nombre.** Con
|
|
254
|
+
varias raíces declaradas, el inventario nombraba cada servicio por la **carpeta** de su raíz, así que
|
|
255
|
+
`../gouduet/keycloak` y `../hypixo/keycloak` salían los dos `keycloak` en `check`, en `onboard` y en
|
|
256
|
+
`scan`, y no había con qué saber de qué repositorio era cada credencial. Ahora los nombra el `name` que
|
|
257
|
+
cada raíz declara en `ops.config.json`, y `check` avisa cuando dos raíces se llaman igual, diciendo
|
|
258
|
+
cuáles son las dos.
|
|
259
|
+
|
|
260
|
+
**Qué cambia para vos**: si dos raíces de tu `ops.config.json` comparten `name`, `check` te lo avisa y
|
|
261
|
+
sigue pasando —renombrá una cuando puedas—. Y donde el `name` y la carpeta difieren, los servicios pasan
|
|
262
|
+
a listarse con el `name` —el que escribiste vos— en vez de con la carpeta; `onboard --json` sigue
|
|
263
|
+
emitiendo `roots` como lista de rutas.
|
|
264
|
+
|
|
17
265
|
## [0.81.0] - 2026-09-11
|
|
18
266
|
|
|
19
267
|
### Cambiado
|
|
@@ -72,18 +72,22 @@ pidió con todas las letras y lo que el agente decidió solo. `chat` corre sobre
|
|
|
72
72
|
runner lo dispara cuando ella manda algo, nunca por el resultado de una herramienta— y lo deja donde los
|
|
73
73
|
guards lo leen:
|
|
74
74
|
|
|
75
|
-
- lo que la persona **
|
|
76
|
-
|
|
75
|
+
- lo que la persona **pidió nombrándolo** pasa: «leé el `.env`» autoriza leer el `.env`; «no toques el
|
|
76
|
+
`.env`» no, y tampoco una pregunta o un comentario que sólo lo nombra —«¿qué tiene el `.env`?»—;
|
|
77
77
|
- lo que se frenó sin que lo nombrara queda anotado, y un «dale» en el mensaje siguiente aprueba
|
|
78
78
|
exactamente eso;
|
|
79
|
+
- lo que un guard dejó pasar porque ella lo pidió sigue valiendo mientras dure la sesión, **salvo en los
|
|
80
|
+
gates de un commit** —`governance`, `verify` y `dependencies`—, que preguntan cada vez: ahí vale lo que
|
|
81
|
+
pidió el mensaje en curso o un «dale», igual que para publicar;
|
|
79
82
|
- `plan-first` no aplica: el plan es del trabajo que va por tareas.
|
|
80
83
|
|
|
81
84
|
No cuenta cuando no hay persona —CI, o un aviso del runner como el de un subagente que terminó—, cuando lo
|
|
82
85
|
que pidió es un recorrido de Cauce (`/autobuild`, `$flow`…), ni en la llamada de un subagente, que Claude
|
|
83
86
|
marca con `agent_id`. En Claude y Codex cada llamada trae el identificador del mensaje que la originó;
|
|
84
87
|
Gemini no lo manda, y ahí vale el último mensaje. El registro vive en el temporal del sistema, uno por
|
|
85
|
-
sesión, y los guards de límites lo cuidan junto con `planning/.ops-approval`: el
|
|
86
|
-
|
|
88
|
+
sesión, y los guards de límites lo cuidan junto con `planning/.ops-approval`: el registro no lo escribe
|
|
89
|
+
nunca una herramienta, y en la aprobación sólo entran las líneas que la persona nombró en ese mismo
|
|
90
|
+
mensaje —por shell, ninguna: un comando no dice con qué va a quedar el archivo—. Como todo lo de esta página, frena la forma habitual y no un script decidido.
|
|
87
91
|
|
|
88
92
|
## Cómo se ejecutan
|
|
89
93
|
|
|
@@ -108,8 +112,8 @@ runner, mientras la lógica se prueba y mantiene una sola vez en `engine/hooks/r
|
|
|
108
112
|
|
|
109
113
|
| Grupo | Guards | Wrapper |
|
|
110
114
|
|---|---|---|
|
|
111
|
-
| `pre-shell` | destructive, git-add, dependencies, governance, verify, shell-boundary | `guard-shell.sh` |
|
|
112
|
-
| `pre-files` | secrets, generated, workspace-boundary, engine, migrations, integration-snapshot, test-evidence, plan-first | `guard-files.sh` |
|
|
115
|
+
| `pre-shell` | destructive, git-add, dependencies, governance, verify, shell-boundary, secrets-shell, ops-config-shell | `guard-shell.sh` |
|
|
116
|
+
| `pre-files` | secrets, generated, workspace-boundary, engine, migrations, integration-snapshot, test-evidence, plan-first, ops-config | `guard-files.sh` |
|
|
113
117
|
| `pre-read` | secrets-read | `guard-secrets-read.sh` |
|
|
114
118
|
| `prompt` | chat | `guard-chat.sh` |
|
|
115
119
|
| `stop` | planning-drift | `guard-planning-drift.sh` |
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# Cauce
|
|
2
2
|
|
|
3
|
-
Lee y cumple `{{OPS_DIR}}AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y
|
|
4
|
-
de ejecutar trabajo
|
|
3
|
+
Lee y cumple `{{OPS_DIR}}AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y las reglas que rigen este proyecto
|
|
4
|
+
antes de ejecutar trabajo: las propias y las de `system/` que el proyecto no sobrescribió. Donde una propia
|
|
5
|
+
contradice a una del sistema, rige la propia.
|
|
6
|
+
|
|
7
|
+
{{RULES:list}}
|
|
8
|
+
|
|
9
|
+
`{{OPS_DIR}}planning/wip/<runner>.md` es el mutex de
|
|
5
10
|
ejecución y `{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva. No promociones ideas desde INBOX, no
|
|
6
11
|
inventes aprobaciones o credenciales y no hagas push ni deploy. Cierra cada tarea con verificación real y
|
|
7
12
|
evidencia en DONE.
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@{{OPS_DIR}}AGENTS.md
|
|
4
4
|
@{{OPS_DIR}}planning/PROTOCOL.md
|
|
5
|
-
|
|
6
|
-
@{{OPS_DIR}}planning/rules/system/code-shape.md
|
|
7
|
-
@{{OPS_DIR}}planning/rules/system/commits.md
|
|
8
|
-
@{{OPS_DIR}}planning/rules/system/conduct.md
|
|
5
|
+
{{RULES:imports}}
|
|
9
6
|
|
|
10
7
|
Los hooks de `.claude/settings.json` son obligatorios. En una instancia recién creada, empezá corriendo
|
|
11
8
|
`node {{OPS_DIR}}tools/ops.js onboard`: es instantáneo, y lo primero que imprime es la pregunta con la que
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Cauce para Codex
|
|
2
2
|
|
|
3
|
-
`{{OPS_DIR}}AGENTS.md` tiene las reglas del sistema
|
|
4
|
-
verdad del proceso
|
|
5
|
-
|
|
3
|
+
`{{OPS_DIR}}AGENTS.md` tiene las reglas del sistema y `{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de
|
|
4
|
+
verdad del proceso. Las reglas que rigen cada tarea en este proyecto son éstas —las propias y las de
|
|
5
|
+
`system/` que el proyecto no sobrescribió—, y donde una propia contradice a una del sistema, rige la propia:
|
|
6
|
+
|
|
7
|
+
{{RULES:list}}
|
|
8
|
+
|
|
9
|
+
Leé todo eso antes de trabajar, no cuando algo sale mal: acá sólo está lo específico de este runner.
|
|
6
10
|
|
|
7
11
|
> Codex lee el `AGENTS.md` de la raíz, que es un nombre compartido entre herramientas. Cuando el repo
|
|
8
12
|
> ops **es** la raíz, este archivo no se instala: el `AGENTS.md` de la empresa ya está ahí y manda.
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
@{{OPS_DIR}}AGENTS.md
|
|
4
4
|
@{{OPS_DIR}}planning/PROTOCOL.md
|
|
5
|
-
|
|
6
|
-
@{{OPS_DIR}}planning/rules/system/code-shape.md
|
|
7
|
-
@{{OPS_DIR}}planning/rules/system/commits.md
|
|
8
|
-
@{{OPS_DIR}}planning/rules/system/conduct.md
|
|
5
|
+
{{RULES:imports}}
|
|
9
6
|
|
|
10
7
|
`{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/cauce:autobuild` fase por fase; los
|
|
11
8
|
workflows JS de Claude son referencia, no un runtime compatible. `{{OPS_DIR}}planning/wip/<runner>.md` es el mutex
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Lo que un recorrido le pide a un agente cuando escribe en el INBOX (caso 101). El tope lo aplica el
|
|
2
|
+
// recorrido sobre lo que le pasa al agente, no el agente: pedirle que se limite no es un tope. Lo que no
|
|
3
|
+
// entra se cuenta donde cada recorrido lo deja, y ninguno lo tira en silencio.
|
|
4
|
+
const INBOX_CAP = 3
|
|
5
|
+
// La forma de una entrada, igual a la que declara el molde de `INBOX.md`: quien escribe no tiene que
|
|
6
|
+
// abrir el archivo para saberla. Una prueba ata las dos.
|
|
7
|
+
const INBOX_ENTRY = '- **slug-del-item** — Qué es, y qué se decide o se resuelve con esto.'
|
|
8
|
+
// Los nombres que ya hay, por sección, tal como los imprime `ops context --json` en su campo inbox.
|
|
9
|
+
const INBOX_HEADS = { type: 'object', additionalProperties: false, properties: Object.fromEntries(
|
|
10
|
+
['deuda', 'ideas', 'propuestas', 'lecciones'].map((key) => [key, { type: 'array', items: { type: 'string' } }]),
|
|
11
|
+
) }
|
|
12
|
+
// Lo que entra en una entrada, procedencia incluida. El tope es del conjunto y no del detalle: así un
|
|
13
|
+
// sufijo largo recorta lo que se cuenta y nunca al revés. Se recorta el detalle porque es lo único
|
|
14
|
+
// recuperable —sigue entero en el informe o en `done/`—, y la procedencia no se reconstruye después.
|
|
15
|
+
const INBOX_LINE = 240
|
|
16
|
+
// Una entrada es una línea. Un hallazgo de largo libre se recorta antes de llegar a quien lo escribe,
|
|
17
|
+
// porque lo que llega entero es lo que termina copiado entero.
|
|
18
|
+
const oneLine = (text, reserved = 0) => {
|
|
19
|
+
const first = String(text || '').split('\n')[0].trim()
|
|
20
|
+
const cap = INBOX_LINE - reserved
|
|
21
|
+
return first.length > cap ? `${first.slice(0, cap - 1)}…` : first
|
|
22
|
+
}
|
|
23
|
+
// De qué vía salió una entrada: el recorrido, la unidad de la que salió —una tarea, un informe, un
|
|
24
|
+
// equipo— y la fecha. La arma el recorrido, que es el que las sabe, en vez de pedírselas al agente que
|
|
25
|
+
// escribe: una convención que depende de que alguien se acuerde no deja rastro cuando no se cumple, y
|
|
26
|
+
// una entrada sin remitente se lee igual que una que nunca lo tuvo (caso 115).
|
|
27
|
+
//
|
|
28
|
+
// Las partes vacías se caen: `onboard` no tiene ni tarea ni informe, y la fecha la da el motor —acá no
|
|
29
|
+
// hay reloj—, así que falta si el comando que la trae no contestó. Va entre paréntesis, al final y en
|
|
30
|
+
// minúscula, porque compite con lo que la entrada dice; y nombra al recorrido y nunca a un cargo, para
|
|
31
|
+
// que no se lea como una firma con la que decidir sin leer la entrada.
|
|
32
|
+
const inboxOrigin = (...parts) => `(${parts.filter(Boolean).join(' · ')})`
|
|
33
|
+
// La entrada ya armada, para que quien escribe la copie en vez de redactarla.
|
|
34
|
+
const withOrigin = (detail, origin) => `${oneLine(detail, origin.length + 1)} ${origin}`
|
|
35
|
+
// La forma y los nombres que ya hay en las secciones donde se va a escribir. Van los nombres y no el
|
|
36
|
+
// archivo: con ellos alcanza para no repetir una entrada, y el archivo entero pesaría lo que el INBOX.
|
|
37
|
+
function inboxAsk(sections, heads, origin) {
|
|
38
|
+
const known = sections.map((name) => {
|
|
39
|
+
const names = (heads || {})[name.toLowerCase()] || []
|
|
40
|
+
return names.length ? `en ${name} ya están ${names.join(', ')}` : `${name} no tiene entradas`
|
|
41
|
+
}).join('; ')
|
|
42
|
+
return `Cada entrada con la forma del molde —${INBOX_ENTRY}—: un nombre y una línea, y la evidencia se ` +
|
|
43
|
+
`cita donde ya vive, no se copia. Cada línea que te paso termina con su procedencia —${origin}—: va al ` +
|
|
44
|
+
`final de la entrada tal cual, sin reescribirla, resumirla ni completarla. ` +
|
|
45
|
+
`Por nombre, ${known}: lo que ya esté con uno de esos nombres no se ` +
|
|
46
|
+
`vuelve a escribir.`
|
|
47
|
+
}
|
|
@@ -225,7 +225,7 @@ const verdicts = await pipeline(
|
|
|
225
225
|
`normas o sistemas de terceros. Enumeralas con el registro que cada una lleva —verificado, ` +
|
|
226
226
|
`documentado, hipótesis, o ninguno— y comprobá las que se puedan comprobar barato: abrí el archivo ` +
|
|
227
227
|
`que cita y leé si dice eso, reproducí la invocación inocua que declara (\`--help\`, \`--version\`, ` +
|
|
228
|
-
`un comando de sólo lectura), consultá la fuente pública que nombra.
|
|
228
|
+
`un comando de sólo lectura), consultá la fuente pública que nombra. Sin salirte de la lectura: ` +
|
|
229
229
|
`nunca conectarte a un sistema real ni ejecutar la operación cuyo efecto se describe.\n\n` +
|
|
230
230
|
`Empezá por la afirmación de la que depende la recomendación del cargo, no por la que parezca más ` +
|
|
231
231
|
`discutible: son distintas, y la segunda suele estar bien rotulada porque el cargo esperaba que se la ` +
|
|
@@ -27,6 +27,7 @@ export const meta = {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
{{INCLUDE:shared/workflow-root.js}}
|
|
30
|
+
{{INCLUDE:shared/inbox.js}}
|
|
30
31
|
const CONFIG = `${ROOT}/ops.config.json`
|
|
31
32
|
const P = `${ROOT}/planning`
|
|
32
33
|
const ORG = `${ROOT}/organization`
|
|
@@ -82,6 +83,10 @@ const CONTEXT = {
|
|
|
82
83
|
// Dónde va el plan de este runner. El nombre sale de su id y el recorrido no lo deriva: lo
|
|
83
84
|
// pregunta, igual que la fecha.
|
|
84
85
|
wipFile: { type: 'string' },
|
|
86
|
+
// Con los nombres que ya hay en el INBOX, Review no vuelve a anotar uno.
|
|
87
|
+
inbox: { ...INBOX_HEADS },
|
|
88
|
+
// Las reglas que rigen el proyecto, con los overrides ya resueltos por el motor (caso 105).
|
|
89
|
+
rules: { type: 'array', items: { type: 'string' } },
|
|
85
90
|
},
|
|
86
91
|
}
|
|
87
92
|
const CLAIM = {
|
|
@@ -132,6 +137,10 @@ const DECISION = {
|
|
|
132
137
|
consulted: { type: 'array', items: { type: 'string' } },
|
|
133
138
|
},
|
|
134
139
|
}
|
|
140
|
+
// Review nombra contra qué reglas revisó (caso 105): recibir las rutas no garantiza abrirlas, y esto es lo único
|
|
141
|
+
// que deja rastro de que se hizo. Critique no lo lleva porque no recibe la lista.
|
|
142
|
+
const REVIEWED = { ...DECISION, required: [...DECISION.required, 'rules'],
|
|
143
|
+
properties: { ...DECISION.properties, rules: { type: 'array', items: { type: 'string' } } } }
|
|
135
144
|
// Un exit code dice que el test corrió, no que pruebe lo que la tarea prometió: un test que asercia de
|
|
136
145
|
// menos —o que ni existe— sale verde igual, y el guard de verify tampoco lo ve porque también mira exit
|
|
137
146
|
// codes. Por eso `uncovered` se contrasta contra la aceptación leyendo el fuente, no la salida (R9).
|
|
@@ -274,6 +283,8 @@ const VERDICT = ' Cerrá con verdict=aprobado si no queda nada por corregir ante
|
|
|
274
283
|
'si algo no se resuelve acá —el diseño no lo cubre, falta una decisión ajena, o la corrección excede el ' +
|
|
275
284
|
'alcance—. Marcá blocking=true sólo en el hallazgo que impide entregar: el resto queda registrado y no ' +
|
|
276
285
|
'manda a tocar código.'
|
|
286
|
+
// Acompaña a todo prompt con schema REVIEWED.
|
|
287
|
+
const RULED = ' En rules nombrá, por su ruta, cada una de las reglas que rigen contra la que revisaste el diff.'
|
|
277
288
|
// Lo que hay que corregir antes de entregar. El resto de los hallazgos no desaparece: se registra.
|
|
278
289
|
const blockers = (verdict) => verdict.concerns.filter((one) => one.blocking).map((one) => one.detail)
|
|
279
290
|
// Atajo para reconocer un gate que corrió pruebas sin preguntarle a nadie. No alcanza solo y no
|
|
@@ -331,12 +342,19 @@ if (!contract.rootOk) {
|
|
|
331
342
|
|
|
332
343
|
const bounds = contract.boundaries || []
|
|
333
344
|
const limits = bounds.length ? ` Límites del proyecto: ${bounds.join('; ')}.` : ''
|
|
345
|
+
// Las reglas que rigen el proyecto (caso 105). Las lista `context`, que se lee después del contrato, así que
|
|
346
|
+
// entran al preámbulo cuando esa lectura vuelve. Viajan las rutas y no el texto: el preámbulo se reenvía a cada
|
|
347
|
+
// subagente que toca código, y el texto de las reglas multiplicaría su tamaño por cada uno.
|
|
348
|
+
let governing = []
|
|
334
349
|
// Alcance de escritura: para subagentes que tocan código o ejecutan gates del producto.
|
|
335
|
-
const SCOPE = `${BASE}\n\nProyecto ${contract.project}. workspaceRoots es el límite completo de escritura
|
|
336
|
-
`producto: ${contract.workspaceRoots.join('; ')}.${limits} Este preámbulo ya trae el contrato;
|
|
337
|
-
|
|
350
|
+
const SCOPE = () => `${BASE}\n\nProyecto ${contract.project}. workspaceRoots es el límite completo de escritura ` +
|
|
351
|
+
`del producto: ${contract.workspaceRoots.join('; ')}.${limits} Este preámbulo ya trae el contrato; ` +
|
|
352
|
+
`no vuelvas a leer ${ROOT}/AGENTS.md, ${ORG}/workspace.md, ${CONFIG} ni ${P}/PROTOCOL.md.` +
|
|
353
|
+
(governing.length ? ` Las reglas que rigen este proyecto son éstas, relativas a ${ROOT}: ${governing.join(', ')}. ` +
|
|
354
|
+
'Leé las que toquen tu fase antes de planificar, construir o revisar; donde una propia contradice a una del ' +
|
|
355
|
+
'sistema, rige la propia.' : '')
|
|
338
356
|
// Formatos de planning: sólo para subagentes que escriben roadmap, BACKLOG, WIP, DONE o gates.
|
|
339
|
-
const LEDGER = `${SCOPE}\n\nContratos de planning, textuales de ${P}/PROTOCOL.md:\n${contract.contracts}`
|
|
357
|
+
const LEDGER = () => `${SCOPE()}\n\nContratos de planning, textuales de ${P}/PROTOCOL.md:\n${contract.contracts}`
|
|
340
358
|
|
|
341
359
|
// Un subagente puede morir —error terminal tras reintentos, o alguien que lo saltea— y entonces el
|
|
342
360
|
// runtime devuelve `null`. Sin comprobarlo, la primera propiedad que se le pide revienta el recorrido
|
|
@@ -352,8 +370,8 @@ const LEDGER = `${SCOPE}\n\nContratos de planning, textuales de ${P}/PROTOCOL.md
|
|
|
352
370
|
// justo la distinción que hace falta. Lo que evita el olvido es el arnés, que rechaza la llamada sin
|
|
353
371
|
// etiqueta en las cuatro suites del recorrido.
|
|
354
372
|
const read = (prompt, options = {}) => agent(`${BASE}\n\n${prompt}`, options)
|
|
355
|
-
const run = (prompt, options = {}) => agent(`${SCOPE}\n\n${prompt}`, options)
|
|
356
|
-
const write = (prompt, options = {}) => agent(`${LEDGER}\n\n${prompt}`, options)
|
|
373
|
+
const run = (prompt, options = {}) => agent(`${SCOPE()}\n\n${prompt}`, options)
|
|
374
|
+
const write = (prompt, options = {}) => agent(`${LEDGER()}\n\n${prompt}`, options)
|
|
357
375
|
|
|
358
376
|
// Las tres paradas que dejan una fila en HUMAN_ACTIONS delegan esa escritura a un agente, y esa fila es
|
|
359
377
|
// el único rastro de la parada: sin ella el recorrido informa un estado que el disco no tiene. Por eso
|
|
@@ -368,10 +386,11 @@ const registerHuman = async (prompt, label) => (await write(prompt, { label })
|
|
|
368
386
|
const readContext = () => read(
|
|
369
387
|
`Corré "node tools/ops.js context ${P} --json" desde ${ROOT} y reportá sólo lo que imprimió. Derivá hasTask ` +
|
|
370
388
|
`de si task es null, wipActive de si wip es null, claimed del campo claimed, today y wipFile de sus ` +
|
|
371
|
-
`campos, y lane ` +
|
|
389
|
+
`campos, rules del campo rules tal cual, y lane ` +
|
|
372
390
|
`de task.tier; copiá slug, ` +
|
|
373
391
|
`hito, service, acceptance, ` +
|
|
374
|
-
`epic y cast de task,
|
|
392
|
+
`epic y cast de task, epicContext de epic.context —vacío si no hay épica— e inbox tal cual. El comando es ` +
|
|
393
|
+
`la fuente de ` +
|
|
375
394
|
`verdad: no abras archivos de planning para completarlo. Poné readOk en true sólo si el comando salió ` +
|
|
376
395
|
`con código 0 y devolvió JSON; si falló, readOk en false y el resto en sus valores vacíos, sin ` +
|
|
377
396
|
`deducir el estado de ninguna otra fuente.`,
|
|
@@ -405,6 +424,7 @@ if (blocker === 'blocked-on-human') {
|
|
|
405
424
|
}
|
|
406
425
|
if (blocker) return stop('context-unavailable', `${P} contestó blocked=${JSON.stringify(planning.blocked)}, `
|
|
407
426
|
+ 'que no es del vocabulario. No se sabe si hay bloqueo, así que no se sigue como si no lo hubiera.')
|
|
427
|
+
governing = planning.rules || []
|
|
408
428
|
|
|
409
429
|
let currentMilestone = planning.wipActive ? planning.hito : ''
|
|
410
430
|
const completed = []
|
|
@@ -565,7 +585,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
565
585
|
const nota = await registerHuman(
|
|
566
586
|
`Registrá ${unit.id} en ${HUMAN}: nadie pudo escribir un plan que sobreviva a la crítica. `
|
|
567
587
|
+ `Motivo: ${detail}. La acción humana es revisar si la unidad son dos resultados con vidas `
|
|
568
|
-
+ `distintas y partirla
|
|
588
|
+
+ `distintas y partirla, o dejarla entera con la razón escrita.`, 'plan-human')
|
|
569
589
|
return stop(reason, `${detail}${nota}`)
|
|
570
590
|
}
|
|
571
591
|
|
|
@@ -746,8 +766,8 @@ while (rounds++ < MAX_TASKS) {
|
|
|
746
766
|
let review = await run(
|
|
747
767
|
`${asRole(cast.review)}Revisá el diff real por aceptación, regresiones, seguridad, arquitectura, código ` +
|
|
748
768
|
`generado, migraciones y alcance accidental. Cada cargo revisa su dominio, no el ajeno.${MANIFEST}` +
|
|
749
|
-
`${VERDICT}`,
|
|
750
|
-
{ schema:
|
|
769
|
+
`${VERDICT}${RULED}`,
|
|
770
|
+
{ schema: REVIEWED, label: 'review' },
|
|
751
771
|
)
|
|
752
772
|
if (!review) return stop('agent-unavailable', 'Review no devolvió resultado')
|
|
753
773
|
if (review.verdict === 'bloqueado') {
|
|
@@ -756,24 +776,42 @@ while (rounds++ < MAX_TASKS) {
|
|
|
756
776
|
if (blockers(review).length) {
|
|
757
777
|
await write(`Corregí sólo estos hallazgos con evidencia y actualizá el WIP: ${blockers(review).join('; ')}`,
|
|
758
778
|
{ label: 'review-fix' })
|
|
759
|
-
review = await run(`Volvé a revisar el diff corregido de ${task.id}.${MANIFEST}${VERDICT}`,
|
|
760
|
-
{ schema:
|
|
779
|
+
review = await run(`Volvé a revisar el diff corregido de ${task.id}.${MANIFEST}${VERDICT}${RULED}`,
|
|
780
|
+
{ schema: REVIEWED, label: 'review' })
|
|
761
781
|
if (!review) return stop('agent-unavailable', 'la re-revisión no devolvió resultado')
|
|
762
782
|
if (review.verdict === 'bloqueado' || blockers(review).length) {
|
|
763
783
|
return stop('review-failed', blockers(review).join('; ') || 'sin condiciones nombradas')
|
|
764
784
|
}
|
|
765
785
|
}
|
|
786
|
+
// Con reglas que rigen, aprobar sin nombrar contra cuáles es la misma falla que la de abajo en otro eje.
|
|
787
|
+
if (governing.length && !(review.rules || []).length) {
|
|
788
|
+
return stop('review-unbacked', 'aprobó el diff sin nombrar contra qué reglas revisó')
|
|
789
|
+
}
|
|
790
|
+
if (governing.length) log(`Review contra: ${review.rules.join(', ')}`)
|
|
766
791
|
// Aprobar sin declarar qué se abrió no se arregla mandando a tocar código: falló quien revisó.
|
|
767
792
|
if (!review.consulted.length) return stop('review-unbacked', 'aprobó el diff sin declarar qué inspeccionó')
|
|
768
793
|
reviewFact = `${review.verdict} por ${cast.review}, sobre ${review.consulted.join(', ')}`
|
|
769
794
|
// Lo que no impide entregar no manda a tocar código, y tampoco desaparece: la mejora opinable que se
|
|
770
795
|
// corrige a las apuradas cuesta una vuelta y un riesgo que nadie pidió. Va a Propuestas y no a
|
|
771
|
-
// Lecciones porque lo que la revisión anotó es un cambio del producto
|
|
772
|
-
// sobre cómo trabajamos, y ahí el hallazgo queda
|
|
773
|
-
|
|
774
|
-
|
|
796
|
+
// Lecciones porque lo que la revisión anotó es un cambio del producto —su evidencia es la de la
|
|
797
|
+
// tarea, que queda en `done/`—; Lecciones es sobre cómo trabajamos, y ahí el hallazgo queda
|
|
798
|
+
// esperando una promoción que nadie va a hacer.
|
|
799
|
+
// De qué vía salió cada una: este recorrido, la tarea que la dejó anotada y la fecha del motor. La
|
|
800
|
+
// arma el recorrido, que es el único de los dos que sabe las tres cosas (caso 115).
|
|
801
|
+
const origin = inboxOrigin('autobuild', task.id, planning.today)
|
|
802
|
+
const noted = review.concerns.filter((one) => !one.blocking).map((one) => withOrigin(one.detail, origin))
|
|
803
|
+
const kept = noted.slice(0, INBOX_CAP)
|
|
804
|
+
// Lo que pasa del tope no se escribe y tampoco desaparece: queda contado en el hecho de revisión, que
|
|
805
|
+
// viaja a `done/`. Una revisión que anota treinta y seis cosas no está priorizando, y el INBOX no las
|
|
806
|
+
// iba a leer (caso 101).
|
|
807
|
+
if (noted.length > kept.length) {
|
|
808
|
+
reviewFact += ` · ${noted.length - kept.length} anotado(s) sin volcar al INBOX`
|
|
809
|
+
}
|
|
810
|
+
if (kept.length) {
|
|
775
811
|
await write(`Registrá en la sección Propuestas de ${P}/INBOX.md lo que la revisión de ${task.id} dejó ` +
|
|
776
|
-
`anotado sin frenar la entrega, sin promover ninguna
|
|
812
|
+
`anotado sin frenar la entrega, sin promover ninguna. ` +
|
|
813
|
+
`${inboxAsk(['Propuestas'], planning.inbox, origin)} ` +
|
|
814
|
+
`Lo anotado: ${JSON.stringify(kept)}`, { label: 'review-noted' })
|
|
777
815
|
}
|
|
778
816
|
}
|
|
779
817
|
|