@ingeniomaps/cauce 0.96.0 → 0.98.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 +173 -0
- package/README.md +1 -0
- package/agents/roles/system/accounting-specialist/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/ai-governance-lead/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/ai-product-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/analytics-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/backend-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/business-strategist/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/cloud-architect/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/community-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/content-specialist/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/customer-success-manager/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/customer-support-specialist/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/data-analyst/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/data-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/data-governance-steward/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/data-scientist/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/database-administrator/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/developer-relations-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/devops-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/engineering-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/financial-controller/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/finops-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/fraud-risk-analyst/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/frontend-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/growth-marketer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/implementation-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/integrations-engineer/learning/AUTOMATION.md +3 -3
- package/agents/roles/system/kyc-aml-specialist/learning/AUTOMATION.md +3 -3
- package/agents/roles/system/legal-counsel/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/logistics-operations-manager/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/machine-learning-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/mlops-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/mobile-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/partnerships-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/people-operations-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/privacy-compliance-specialist/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/procurement-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/product-manager/learning/AUTOMATION.md +3 -3
- package/agents/roles/system/product-marketing-manager/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/project-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/qa-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/release-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/sales-representative/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/security-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/site-reliability-engineer/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/software-architect/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/solutions-engineer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/tech-lead/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/technical-program-manager/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/technical-writer/learning/AUTOMATION.md +1 -1
- package/agents/roles/system/treasury-analyst/learning/AUTOMATION.md +2 -2
- package/agents/roles/system/ui-designer/learning/AUTOMATION.md +3 -3
- package/agents/roles/system/user-researcher/learning/AUTOMATION.md +3 -3
- package/agents/roles/system/ux-designer/learning/AUTOMATION.md +3 -3
- package/automatization/hooks/README.md +4 -3
- package/automatization/workflows/agent-propose.js +2 -2
- package/automatization/workflows/autobuild.js +219 -29
- package/automatization/workflows/flow.js +30 -14
- package/engine/agents/learning-files.js +18 -0
- package/engine/agents/learning-seal.js +24 -12
- package/engine/agents/learning-sources.js +12 -10
- package/engine/agents/learning.js +29 -7
- package/engine/cli/args.js +2 -2
- package/engine/cli/catalog.js +12 -12
- package/engine/cli/ops.js +2 -1
- package/engine/cli/planning.js +4 -1
- package/engine/hooks/approval.js +5 -3
- package/engine/hooks/chat.js +19 -7
- package/engine/hooks/push.js +3 -2
- package/engine/hooks/self-approval.js +2 -2
- package/engine/hooks/shell.js +63 -6
- package/engine/planning/contracts.js +19 -0
- package/engine/planning/parser.js +35 -0
- package/package.json +1 -1
- package/template/AGENTS.md +5 -4
- package/template/Makefile +1 -1
- package/template/planning/rules/system/commits.md +9 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en ventas responsables y procesos comerciales para
|
|
@@ -15,4 +15,4 @@ no compres listas, envíes outreach, publiques, hagas commit ni push. Termina co
|
|
|
15
15
|
`make agent-evaluate AGENT=sales-representative`.
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Programar
|
|
18
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en Security Engineering para mantener
|
|
@@ -16,4 +16,4 @@ hagas commit ni push. Termina con
|
|
|
16
16
|
`make agent-evaluate AGENT=security-engineer`.
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Programar
|
|
19
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en Site Reliability Engineering para mantener
|
|
@@ -15,4 +15,4 @@ no ejecutes carga, caos, failover, publiques, hagas commit ni push. Termina con
|
|
|
15
15
|
`make agent-evaluate AGENT=site-reliability-engineer`.
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Programar
|
|
18
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en Software Architecture para mantener
|
|
@@ -15,4 +15,4 @@ no generes migraciones, publiques, hagas commit ni push. Termina con
|
|
|
15
15
|
`make agent-evaluate AGENT=software-architect`.
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Programar
|
|
18
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Automatización de aprendizaje
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Mensual con Codex
|
|
4
4
|
|
|
5
5
|
Ejecutar `make agent-learn AGENT=solutions-engineer`. Completar el informe generado con novedades relevantes, versiones, aplicabilidad, evidencia primaria, impacto y recomendación. Investigar no autoriza cambiar `SKILL.md`, materiales, producto ni sistemas.
|
|
6
6
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en la práctica de liderazgo técnico —revisión y aprobación
|
|
@@ -22,4 +22,4 @@ apruebes, despliegues, hagas commit ni push, y no autorices gasto. Termina con
|
|
|
22
22
|
`make agent-evaluate AGENT=tech-lead`.
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Programar
|
|
25
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Automatización de aprendizaje
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Mensual con Codex
|
|
4
4
|
|
|
5
5
|
Ejecutar `make agent-learn AGENT=technical-program-manager`. Registrar fuentes primarias, estándar/versión/estado, fit organizacional, evidencia de outcome y validación de owners. Investigar no autoriza cambiar planes, compromisos o sistemas.
|
|
6
6
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Investiga cambios recientes en tesorería corporativa, liquidez y pagos salientes para mantener
|
|
@@ -17,4 +17,4 @@ no actualices SKILL.md, código ni planificación; no publiques, hagas commit ni
|
|
|
17
17
|
con `make agent-evaluate AGENT=treasury-analyst`.
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Programar
|
|
20
|
+
Programar mensualmente en cada instalación.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
|
-
Configurar una automatización
|
|
3
|
+
Configurar una automatización mensual con este prompt:
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
Investiga cambios recientes en UI Design para mantener agents/roles/system/ui-designer.
|
|
@@ -16,4 +16,4 @@ permiso, publiques, hagas commit ni push. Termina ejecutando
|
|
|
16
16
|
`make agent-evaluate AGENT=ui-designer`.
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
La frecuencia recomendada es
|
|
19
|
+
La frecuencia recomendada es mensual; cada instalación define el horario.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
|
-
Configurar una automatización
|
|
3
|
+
Configurar una automatización mensual en la raíz del repositorio con este prompt:
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
Investiga cambios recientes en User Research para mantener el agente
|
|
@@ -19,4 +19,4 @@ contactes participantes, accedas a datos personales, publiques, hagas commit ni
|
|
|
19
19
|
push. Termina ejecutando `make agent-evaluate AGENT=user-researcher`.
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
La frecuencia recomendada es
|
|
22
|
+
La frecuencia recomendada es mensual; cada instalación elige día y hora.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Automatización
|
|
1
|
+
# Automatización mensual del aprendizaje
|
|
2
2
|
|
|
3
|
-
Configurar una automatización
|
|
3
|
+
Configurar una automatización mensual con este prompt:
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
Investiga cambios recientes en UX Design para mantener agents/roles/system/ux-designer.
|
|
@@ -15,4 +15,4 @@ referencias, evaluaciones, fuentes ni planificación; no publiques, hagas commit
|
|
|
15
15
|
ni push. Termina con `make agent-evaluate AGENT=ux-designer`.
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
La frecuencia recomendada es
|
|
18
|
+
La frecuencia recomendada es mensual; cada instalación define el horario.
|
|
@@ -74,11 +74,12 @@ guards lo leen:
|
|
|
74
74
|
|
|
75
75
|
- lo que la persona **pidió nombrándolo** pasa: «leé el `.env`» autoriza leer el `.env`; «no toques el
|
|
76
76
|
`.env`» no, y tampoco una pregunta o un comentario que sólo lo nombra —«¿qué tiene el `.env`?»—;
|
|
77
|
-
- lo que se frenó sin que lo nombrara queda anotado, y
|
|
78
|
-
exactamente eso;
|
|
77
|
+
- lo que se frenó sin que lo nombrara queda anotado, y su confirmación en el mensaje siguiente —con las
|
|
78
|
+
palabras que sea— aprueba exactamente eso. Si ese mensaje niega, frena o pregunta, no aprueba nada; y lo
|
|
79
|
+
que se frenó mientras ella decía que no, no queda esperando;
|
|
79
80
|
- lo que un guard dejó pasar porque ella lo pidió sigue valiendo mientras dure la sesión, **salvo en los
|
|
80
81
|
gates de un commit** —`governance`, `verify` y `dependencies`—, que preguntan cada vez: ahí vale lo que
|
|
81
|
-
pidió el mensaje en curso o
|
|
82
|
+
pidió el mensaje en curso o su confirmación al bloqueo, igual que para publicar;
|
|
82
83
|
- `plan-first` no aplica: el plan es del trabajo que va por tareas.
|
|
83
84
|
|
|
84
85
|
No cuenta cuando no hay persona —CI, o un aviso del runner como el de un subagente que terminó—, cuando lo
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Convierte la recomendación de los informes en el cambio concreto que una persona puede aprobar.
|
|
2
2
|
//
|
|
3
|
-
// `learn --proposal` consolida qué recomendaron los informes de
|
|
3
|
+
// `learn --proposal` consolida qué recomendaron los informes de investigación, pero deja «Cambio
|
|
4
4
|
// propuesto: por definir». Eso no es aprobable: nadie firma una intención. Este recorrido escribe el
|
|
5
5
|
// texto exacto que habría que agregar, archivo por archivo, y lo contrasta contra los casos
|
|
6
6
|
// adversariales vigentes antes de proponerlo.
|
|
@@ -70,7 +70,7 @@ phase('Proponer')
|
|
|
70
70
|
|
|
71
71
|
const proposal = await agent(
|
|
72
72
|
`Sos quien mantiene el cargo ${AGENT}. La propuesta ${context.proposal} consolidó lo que ` +
|
|
73
|
-
`recomendaron los informes
|
|
73
|
+
`recomendaron los informes de investigación, pero le falta lo único que una persona puede aprobar: el ` +
|
|
74
74
|
`cambio concreto.\n\n` +
|
|
75
75
|
`Leé los informes citados en su sección «Hallazgos», el SKILL.md del cargo, ` +
|
|
76
76
|
`${context.dir}/learning/sources.yaml, ${context.dir}/evaluations/expected-behaviors.yaml y sus ` +
|
|
@@ -43,6 +43,13 @@ const GATE = `${P}/AWAITING_REVIEW.md`
|
|
|
43
43
|
// suelta no es un envoltorio. Por eso también la escapada en vez de alternar el estilo de comillas.
|
|
44
44
|
const QUOTES = ['\'', '"']
|
|
45
45
|
|
|
46
|
+
// Si la fila que registró una parada quedó pendiente. `context` sólo lista las pendientes, así que
|
|
47
|
+
// preguntar por la presencia de la tarea alcanza, y no hace falta que un modelo lea el estado.
|
|
48
|
+
const HUMAN_ROW = {
|
|
49
|
+
type: 'object', additionalProperties: false, required: ['readOk', 'pending'],
|
|
50
|
+
properties: { readOk: { type: 'boolean' }, pending: { type: 'boolean' } },
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
const CONTEXT = {
|
|
47
54
|
type: 'object', additionalProperties: false,
|
|
48
55
|
required: ['blocked', 'hasTask', 'wipActive', 'queued', 'cast', 'readOk'],
|
|
@@ -60,6 +67,9 @@ const CONTEXT = {
|
|
|
60
67
|
hasTask: { type: 'boolean' }, wipActive: { type: 'boolean' },
|
|
61
68
|
queued: { type: 'integer' }, slug: { type: 'string' }, hito: { type: 'string' },
|
|
62
69
|
service: { type: 'string' }, acceptance: { type: 'string' }, epic: { type: 'string' },
|
|
70
|
+
// Sin declararlo acá no llega, igual que le pasó a `epicContext`: `additionalProperties: false` lo
|
|
71
|
+
// descarta y las fases lo reciben vacío para siempre.
|
|
72
|
+
description: { type: 'string' },
|
|
63
73
|
// Sin declararlo acá no llega: `additionalProperties: false` lo descartaría, y el aplanado de la
|
|
64
74
|
// épica a su número —dos líneas arriba— hace fácil creer que ya viene. La fase Plan lo pedía en su
|
|
65
75
|
// prompt y planificaba contra el título; `readEpics` cuenta de dónde sale.
|
|
@@ -153,8 +163,29 @@ const DECISION = {
|
|
|
153
163
|
}
|
|
154
164
|
// Review nombra contra qué reglas revisó (caso 105): recibir las rutas no garantiza abrirlas, y esto es lo único
|
|
155
165
|
// que deja rastro de que se hizo. Critique no lo lleva porque no recibe la lista.
|
|
166
|
+
//
|
|
167
|
+
// Y `decision` es el tercer destino, el que R6 le da a lo que aparece y no le toca a este cargo: queda
|
|
168
|
+
// registrado con qué lo cierra y quién puede tomarlo. Las otras fases lo tienen —`ready-human`,
|
|
169
|
+
// `plan-human`, `verify-human`, y `open-decisions` en Build— y Review no, así que un revisor que
|
|
170
|
+
// encontraba una decisión elegía entre frenar la corrida o degradarla a propuesta con tope de tres.
|
|
171
|
+
// Elegía frenar, que de las dos es la correcta, y costaba la corrida entera sobre trabajo que estaba
|
|
172
|
+
// bien. Es la misma separación que `uncovered` hace en Verify, en el otro extremo del recorrido.
|
|
173
|
+
//
|
|
174
|
+
// Medido sobre un banco desechable el 2026-09-17, corridas `wf_99130468-2c4` y `wf_5dcc3828-a9f`: las dos
|
|
175
|
+
// terminaron en `review-failed` con la suite del producto en verde y los tres casos de la aceptación
|
|
176
|
+
// dando lo pedido, y las dos por un hallazgo que el propio revisor describió como una decisión que no le
|
|
177
|
+
// tocaba.
|
|
156
178
|
const REVIEWED = { ...DECISION, required: [...DECISION.required, 'rules'],
|
|
157
|
-
properties: {
|
|
179
|
+
properties: {
|
|
180
|
+
...DECISION.properties,
|
|
181
|
+
rules: { type: 'array', items: { type: 'string' } },
|
|
182
|
+
// Extiende el concern de `DECISION` en vez de reescribirlo: copiado entero, un campo nuevo allá no
|
|
183
|
+
// llegaría acá y ninguna prueba lo notaría.
|
|
184
|
+
concerns: { ...DECISION.properties.concerns,
|
|
185
|
+
items: { ...DECISION.properties.concerns.items,
|
|
186
|
+
properties: { ...DECISION.properties.concerns.items.properties, decision: { type: 'boolean' } },
|
|
187
|
+
} },
|
|
188
|
+
} }
|
|
158
189
|
// Un exit code dice que el test corrió, no que pruebe lo que la tarea prometió: un test que asercia de
|
|
159
190
|
// menos —o que ni existe— sale verde igual, y el guard de verify tampoco lo ve porque también mira exit
|
|
160
191
|
// codes. Por eso `uncovered` se contrasta contra la aceptación leyendo el fuente, no la salida (R9).
|
|
@@ -301,9 +332,19 @@ const VERDICT = ' Cerrá con verdict=aprobado si no queda nada por corregir ante
|
|
|
301
332
|
'alcance—. Marcá blocking=true sólo en el hallazgo que impide entregar: el resto queda registrado y no ' +
|
|
302
333
|
'manda a tocar código.'
|
|
303
334
|
// Acompaña a todo prompt con schema REVIEWED.
|
|
304
|
-
const RULED = ' En rules nombrá, por su ruta, cada una de las reglas que rigen contra la que revisaste
|
|
335
|
+
const RULED = ' En rules nombrá, por su ruta, cada una de las reglas que rigen contra la que revisaste'
|
|
336
|
+
+ ' el diff. Y marcá decision=true en el hallazgo que no te toca resolver a vos —una definición de'
|
|
337
|
+
+ ' producto, un contrato público, una autoridad que el cargo no tiene—: ése se registra para una'
|
|
338
|
+
+ ' persona y no manda a tocar código.'
|
|
305
339
|
// Lo que hay que corregir antes de entregar. El resto de los hallazgos no desaparece: se registra.
|
|
306
|
-
|
|
340
|
+
// Una decisión no cuenta como bloqueante aunque venga marcada: su destino es la fila, no la corrección.
|
|
341
|
+
// Critique no la emite —no está en su esquema— así que para él `one.decision` es siempre `undefined`.
|
|
342
|
+
const blockers = (verdict) => verdict.concerns
|
|
343
|
+
.filter((one) => one.blocking && !one.decision).map((one) => one.detail)
|
|
344
|
+
// Lo que una parada tiene que nombrar es todo lo que el revisor señaló, no sólo lo que manda a corregir:
|
|
345
|
+
// con `blockers` solo, un veredicto cuyo único hallazgo es una decisión paraba diciendo que nadie nombró
|
|
346
|
+
// ninguna condición. El motivo es lo único que queda para leer cuando la corrida terminó.
|
|
347
|
+
const named = (verdict) => verdict.concerns.filter((one) => one.blocking).map((one) => one.detail)
|
|
307
348
|
// El resultado de Build cuando no hubo nada que construir en esta corrida. Devuelve lo que de verdad
|
|
308
349
|
// pasó y nada más: `redFirst` y `discovered` van **vacíos** porque acá no hubo ningún rojo nuevo que
|
|
309
350
|
// mostrar ni ningún borde nuevo que fijar, y rellenarlos para que se parezca a una construcción sería
|
|
@@ -406,13 +447,32 @@ const read = (prompt, options = {}) => agent(`${BASE}\n\n${prompt}`, options)
|
|
|
406
447
|
const run = (prompt, options = {}) => agent(`${SCOPE()}\n\n${prompt}`, options)
|
|
407
448
|
const write = (prompt, options = {}) => agent(`${LEDGER()}\n\n${prompt}`, options)
|
|
408
449
|
|
|
409
|
-
// Las
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
// Devuelve lo que hay que agregarle al detalle, vacío cuando la fila quedó
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
450
|
+
// Las paradas que dejan una fila en HUMAN_ACTIONS delegan esa escritura a un agente, y esa fila es el
|
|
451
|
+
// único rastro de la parada: sin ella el recorrido informa un estado que el disco no tiene. Por eso se
|
|
452
|
+
// espera —lanzarla y volver en la línea siguiente la abandona— y por eso se mira si contestó (caso 087).
|
|
453
|
+
// Devuelve lo que hay que agregarle al detalle, vacío cuando la fila quedó como tiene que quedar.
|
|
454
|
+
//
|
|
455
|
+
// Contestar no alcanza: el agente que acaba de escribir el diagnóstico entero lo lee como ya resuelto, y
|
|
456
|
+
// en una corrida real escribió la fila `resuelta` y la firmó como «decidido por el dueño», desbloqueando
|
|
457
|
+
// la tarea sin que nadie decidiera nada (caso 180). Por eso cada pedido arranca diciendo que la fila nace
|
|
458
|
+
// pendiente, y cuando la fila es de la propia tarea se relee en `context`, que sólo lista las pendientes.
|
|
459
|
+
const HUMAN_ROW_STATE = 'La fila nace con estado `pendiente`, sin excepción: registrás el bloqueo, no lo '
|
|
460
|
+
+ 'resolvés —lo resuelve una persona—. No escribas una decisión ni se la atribuyas a nadie.'
|
|
461
|
+
const registerHuman = async (prompt, label, slug = '') => {
|
|
462
|
+
if (!(await write(`${HUMAN_ROW_STATE}\n\n${prompt}`, { label }))) {
|
|
463
|
+
return ` — la fila en ${HUMAN} no se pudo registrar: escribila a mano`
|
|
464
|
+
}
|
|
465
|
+
if (!slug) return ''
|
|
466
|
+
const row = await read(
|
|
467
|
+
`Corré "node tools/ops.js context ${P} --json" desde ${ROOT}. Poné pending en true sólo si humanActions `
|
|
468
|
+
+ `trae una fila cuya task sea ${slug}, y readOk en true sólo si el comando salió con código 0 y devolvió `
|
|
469
|
+
+ 'JSON. El comando es la fuente de verdad: no abras archivos de planning.',
|
|
470
|
+
{ schema: HUMAN_ROW, label: 'human-row' },
|
|
471
|
+
)
|
|
472
|
+
if (!row || !row.readOk) return ` — no se pudo comprobar la fila de ${slug} en ${HUMAN}: revisala a mano`
|
|
473
|
+
return row.pending ? ''
|
|
474
|
+
: ` — la fila de ${slug} en ${HUMAN} no quedó pendiente: la resuelve una persona, revisala a mano`
|
|
475
|
+
}
|
|
416
476
|
|
|
417
477
|
// Gate, mutex de WIP y selección de tarea salen de un comando determinista: AWAITING_REVIEW, BACKLOG,
|
|
418
478
|
// WIP y HUMAN_ACTIONS nunca entran al contexto de un modelo, y su tamaño deja de costar tokens.
|
|
@@ -423,7 +483,8 @@ const readContext = () => read(
|
|
|
423
483
|
`omitilo entero si wip es null, sin inventar ceros—, y lane ` +
|
|
424
484
|
`de task.tier; copiá slug, ` +
|
|
425
485
|
`hito, service, acceptance, ` +
|
|
426
|
-
`epic y
|
|
486
|
+
`epic, cast y description de task, epicContext de epic.context —vacío si no hay épica— e inbox tal `
|
|
487
|
+
+ `cual. El comando es ` +
|
|
427
488
|
`la fuente de ` +
|
|
428
489
|
`verdad: no abras archivos de planning para completarlo. Poné readOk en true sólo si el comando salió ` +
|
|
429
490
|
`con código 0 y devolvió JSON; si falló, readOk en false y el resto en sus valores vacíos, sin ` +
|
|
@@ -485,10 +546,35 @@ while (rounds++ < MAX_TASKS) {
|
|
|
485
546
|
//
|
|
486
547
|
// `context` nombra la que sigue, igual que nombra una recurrencia vencida y por el mismo motivo: la
|
|
487
548
|
// máquina calcula y la persona encola.
|
|
549
|
+
// Una cola con trabajo y sin tarea disponible no es una cola terminada. Romper el bucle igual deja la
|
|
550
|
+
// corrida informando que no había nada que hacer sobre una cola que sí tiene tareas —reclamadas por otro
|
|
551
|
+
// runner, o esperando una dependencia—, que es la forma del caso 075: una respuesta vacía que se lee
|
|
552
|
+
// como un hecho del dominio. El dato ya venía: `queued` cuenta la cola entera, y `context` además la
|
|
553
|
+
// nombra con su dueño en la línea TAKEN.
|
|
554
|
+
//
|
|
555
|
+
// Se paga sobre todo después de una parada que espera a una persona: ahí el reclamo se queda puesto a
|
|
556
|
+
// propósito —quien paró va a volver, y `claim` le es idempotente—, así que cuando la persona contesta es
|
|
557
|
+
// un runner distinto el que pregunta y el que se va sin nada.
|
|
558
|
+
if (!planning.hasTask && planning.queued > 0) {
|
|
559
|
+
return stop('queue-unavailable', `la cola tiene ${planning.queued} tarea(s) y ninguna disponible para `
|
|
560
|
+
+ 'este runner. Mirá la línea TAKEN de "ops context": lo reclamado por otro se suelta con '
|
|
561
|
+
+ '"ops release" o se retoma desde el runner que lo tiene; lo que espera una dependencia, no.')
|
|
562
|
+
}
|
|
488
563
|
if (!planning.hasTask || (currentMilestone && planning.hito !== currentMilestone)) break
|
|
564
|
+
// Las decisiones que la línea ya tomó, dichas como lo que son. Sin ese rótulo se leen como contexto
|
|
565
|
+
// opinable y el que planifica las re-decide igual, que es el defecto entero: la crítica abre el
|
|
566
|
+
// BACKLOG por su cuenta y bloquea el plan citando la línea palabra por palabra (caso 177).
|
|
567
|
+
const DECIDED = () => (task.description
|
|
568
|
+
? ` Lo que la línea de la tarea ya decidió, y no se re-decide acá: ${task.description}`
|
|
569
|
+
: '')
|
|
570
|
+
// Sobrevive al bloque del plan porque Build vive afuera —y porque una corrida que reanuda desde un WIP
|
|
571
|
+
// no tiene plan en memoria—. Sin esto la estrategia no era que se descartara: es que no estaba en
|
|
572
|
+
// alcance, que es la forma que ninguna prueba de la fase ve.
|
|
573
|
+
let testStrategy = ''
|
|
489
574
|
const task = {
|
|
490
575
|
id: planning.slug, hito: planning.hito, service: planning.service,
|
|
491
576
|
acceptance: planning.acceptance, epic: planning.epic, epicContext: planning.epicContext || '',
|
|
577
|
+
description: planning.description || '',
|
|
492
578
|
}
|
|
493
579
|
// Reservar antes de construir, y antes de fijar el hito de la corrida. Sin esto dos corridas en
|
|
494
580
|
// paralelo trabajan la misma tarea: `context` sólo puede saltear lo que alguien ya reclamó, y el
|
|
@@ -614,12 +700,32 @@ while (rounds++ < MAX_TASKS) {
|
|
|
614
700
|
//
|
|
615
701
|
// Lo que la fila le pide a una persona lo dice R17: dos rechazos sobre lo mismo son el disparador
|
|
616
702
|
// posterior de división. No se parte acá porque partir es una decisión, y ésa no le toca al recorrido.
|
|
703
|
+
// Una parada que registra la fila **bloquea** la tarea: la fila pendiente la saca de la cola. Si además
|
|
704
|
+
// el reclamo se queda puesto, el runner queda ocupado por algo que nadie puede tomar, y la corrida
|
|
705
|
+
// siguiente —que hace bien en no reintentarla— muere reclamando la que sigue: `claim` le contesta «este
|
|
706
|
+
// runner ya tiene …». Es la forma del 163 por la otra puerta, y la diferencia es que el estado lo creó
|
|
707
|
+
// esta corrida, así que soltarlo también le toca (caso 176).
|
|
708
|
+
//
|
|
709
|
+
// Sólo las paradas de **antes** de construir. Las de después —`verify-regression`, `qa-failed`,
|
|
710
|
+
// `commit-failed`— dejan el WIP en disco y es resumible: ahí el reclamo es lo único que dice de quién es
|
|
711
|
+
// ese trabajo, y soltarlo lo abandonaría.
|
|
712
|
+
//
|
|
713
|
+
// Cuesta un agente, porque el recorrido no tiene con qué correr un comando. Un agente contra una
|
|
714
|
+
// corrida entera.
|
|
715
|
+
const releaseBlocked = async () => write(
|
|
716
|
+
`Corré "node tools/ops.js release ${P} ${task.id}" desde ${ROOT}: quedó bloqueada por la fila que `
|
|
717
|
+
+ 'acabás de registrar y todavía no hay nada construido, así que su reserva no reserva trabajo. No '
|
|
718
|
+
+ 'escribas ningún archivo vos: lo escribe el comando.',
|
|
719
|
+
{ label: `release:${task.id}` },
|
|
720
|
+
)
|
|
721
|
+
|
|
617
722
|
const planRejected = async (reason, unit, found) => {
|
|
618
723
|
const detail = found.join('; ') || 'sin condiciones nombradas'
|
|
619
724
|
const nota = await registerHuman(
|
|
620
725
|
`Registrá ${unit.id} en ${HUMAN}: nadie pudo escribir un plan que sobreviva a la crítica. `
|
|
621
726
|
+ `Motivo: ${detail}. La acción humana es revisar si la unidad son dos resultados con vidas `
|
|
622
|
-
+ `distintas y partirla, o dejarla entera con la razón escrita.`, 'plan-human')
|
|
727
|
+
+ `distintas y partirla, o dejarla entera con la razón escrita.`, 'plan-human', unit.id)
|
|
728
|
+
await releaseBlocked()
|
|
623
729
|
return stop(reason, `${detail}${nota}`)
|
|
624
730
|
}
|
|
625
731
|
|
|
@@ -628,7 +734,8 @@ while (rounds++ < MAX_TASKS) {
|
|
|
628
734
|
phase('Ready')
|
|
629
735
|
const ready = await read(
|
|
630
736
|
`${asRole(OWNERS.ready)}Revisá que ${task.id} tenga aceptación concreta, dependencias resueltas y ` +
|
|
631
|
-
`ninguna decisión pendiente: ${task.acceptance}
|
|
737
|
+
`ninguna decisión pendiente: ${task.acceptance}.${DECIDED()} Aclará la redacción y nada más; ` +
|
|
738
|
+
`nunca amplíes el ` +
|
|
632
739
|
`alcance.`,
|
|
633
740
|
{ schema: READY, label: 'ready' },
|
|
634
741
|
)
|
|
@@ -636,7 +743,8 @@ while (rounds++ < MAX_TASKS) {
|
|
|
636
743
|
if (!ready.ready) {
|
|
637
744
|
const nota = await registerHuman(
|
|
638
745
|
`Registrá ${task.id} en ${HUMAN} con el motivo y una acción humana exacta: ${ready.reason}.`,
|
|
639
|
-
'ready-human')
|
|
746
|
+
'ready-human', task.id)
|
|
747
|
+
await releaseBlocked()
|
|
640
748
|
return stop('not-ready', `${ready.reason}${nota}`)
|
|
641
749
|
}
|
|
642
750
|
if (ready.refinedAcceptance) task.acceptance = ready.refinedAcceptance
|
|
@@ -703,7 +811,8 @@ while (rounds++ < MAX_TASKS) {
|
|
|
703
811
|
`vecinas y el git status de ${task.id}.` +
|
|
704
812
|
`${task.epicContext ? ` Contexto de la épica: ${task.epicContext}` : ''}` +
|
|
705
813
|
` Producí el plan más chico que satisfaga ` +
|
|
706
|
-
`${task.acceptance}
|
|
814
|
+
`${task.acceptance}.${DECIDED()} Un archivo de planning no puede ser un archivo de implementación. ` +
|
|
815
|
+
`El plan cubre ` +
|
|
707
816
|
`sólo el cambio dentro de ${task.service}: correr los gates del repositorio, hacer QA, commitear y ` +
|
|
708
817
|
`cerrar la tarea son fases posteriores de este recorrido, cada una con su dueño, así que no van como ` +
|
|
709
818
|
`pasos.`,
|
|
@@ -714,7 +823,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
714
823
|
phase('Critique')
|
|
715
824
|
let critique = await read(
|
|
716
825
|
`Atacá este plan por correctitud, alcance, seguridad, pruebas y conflictos con el código ` +
|
|
717
|
-
`existente.${MANIFEST}${VERDICT} Plan: ${JSON.stringify(plan)}`,
|
|
826
|
+
`existente.${DECIDED()}${MANIFEST}${VERDICT} Plan: ${JSON.stringify(plan)}`,
|
|
718
827
|
{ schema: DECISION, label: 'critique' },
|
|
719
828
|
)
|
|
720
829
|
if (!critique) return stop('agent-unavailable', 'Critique no devolvió resultado')
|
|
@@ -729,7 +838,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
729
838
|
{ schema: PLAN, label: 'replan' },
|
|
730
839
|
)
|
|
731
840
|
critique = await read(
|
|
732
|
-
`Volvé a criticar el plan corregido contra ${task.acceptance}.${MANIFEST}${VERDICT} ` +
|
|
841
|
+
`Volvé a criticar el plan corregido contra ${task.acceptance}.${DECIDED()}${MANIFEST}${VERDICT} ` +
|
|
733
842
|
`Plan: ${JSON.stringify(plan)}`,
|
|
734
843
|
{ schema: DECISION, label: 'critique' },
|
|
735
844
|
)
|
|
@@ -742,6 +851,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
742
851
|
if (!critique.consulted.length) return stop('critique-unbacked', 'aprobó el plan sin declarar qué inspeccionó')
|
|
743
852
|
}
|
|
744
853
|
}
|
|
854
|
+
testStrategy = plan.testStrategy || ''
|
|
745
855
|
phase('WIP')
|
|
746
856
|
// Esta llamada escribe un archivo y nada más, y hay que decirlo con todas las letras. En una corrida
|
|
747
857
|
// real hizo el trabajo entero: leyó los pasos del plan como una orden, implementó, corrió RED/GREEN,
|
|
@@ -754,7 +864,15 @@ while (rounds++ < MAX_TASKS) {
|
|
|
754
864
|
`en DONE. Los pasos van sin tildar porque todavía no ocurrieron. task=${task.id}, ` +
|
|
755
865
|
`hito=${JSON.stringify(task.hito)}, phase=Build, service=${task.service}, ` +
|
|
756
866
|
`acceptance=${JSON.stringify(task.acceptance)}, lane=${planning.lane || 'sin clasificar'}, ` +
|
|
757
|
-
`pasos sin tildar=${JSON.stringify(plan.steps)}
|
|
867
|
+
`pasos sin tildar=${JSON.stringify(plan.steps)}, ` +
|
|
868
|
+
// La estrategia de prueba es `required` en el plan y hasta acá se descartaba, así que un paso que
|
|
869
|
+
// decía «correr la mutación declarada en testStrategy» apuntaba a un lugar que no existía: quien
|
|
870
|
+
// revisa no podía distinguir la mutación corrida de la pensada, y la única salida que le quedaba
|
|
871
|
+
// era rehacer la revisión. R9 pide la mutación **declarada**, y el WIP es donde queda escrita.
|
|
872
|
+
//
|
|
873
|
+
// Es la tercera vez que algo decidido no llega a quien decide después: el contexto de la épica
|
|
874
|
+
// (027), la descripción de la tarea (177) y esto. Las tres se arreglan igual — que viaje.
|
|
875
|
+
`testStrategy=${JSON.stringify(testStrategy)}. ` +
|
|
758
876
|
`Registrá el reparto de cargos ${JSON.stringify(cast)} en las decisiones del WIP, para que después se ` +
|
|
759
877
|
`pueda auditar quién revisó qué. Seguí el contrato de WIP exactamente y reportá con qué status quedó.`,
|
|
760
878
|
{ label: 'wip', schema: {
|
|
@@ -798,7 +916,8 @@ while (rounds++ < MAX_TASKS) {
|
|
|
798
916
|
`nombrás en test y anotás en redFirst—, kind=open si lo notaste y no impide entregar la aceptación: se ` +
|
|
799
917
|
`registra para que lo decida quien corresponde y el recorrido sigue. Si de verdad no podés entregar sin ` +
|
|
800
918
|
`esa decisión, eso no va en discovered: es completed=false con su blocker. ` +
|
|
801
|
-
`Aceptación: ${task.acceptance}
|
|
919
|
+
`Aceptación: ${task.acceptance}.${DECIDED()}`
|
|
920
|
+
+ (testStrategy ? ` Estrategia de prueba que el plan fijó: ${testStrategy}` : ''),
|
|
802
921
|
{ schema: BUILD, label: 'build' },
|
|
803
922
|
)
|
|
804
923
|
if (!build) return stop('agent-unavailable', 'Build no devolvió resultado')
|
|
@@ -819,8 +938,16 @@ while (rounds++ < MAX_TASKS) {
|
|
|
819
938
|
// tiene camino —`completed: false` con su blocker—; esto se registra y sigue.
|
|
820
939
|
const openDecisions = build.discovered.filter((entry) => entry.kind === 'open')
|
|
821
940
|
if (openDecisions.length) {
|
|
941
|
+
// Y la fila no puede nombrar a la tarea que la produjo. El motor bloquea por esa primera celda
|
|
942
|
+
// exacta, así que escribirla ahí registra «esto no impide entregar» y produce el bloqueo igual —lo
|
|
943
|
+
// contrario de lo que este registro decidió dos líneas arriba—. No se nota mientras la corrida vive,
|
|
944
|
+
// porque el WIP activo manda sobre la acción humana; aparece cuando el WIP cierra, y entonces la
|
|
945
|
+
// tarea queda frenada por una pregunta que ya se había resuelto seguir sin contestar. En la corrida
|
|
946
|
+
// que lo mostró la atrapó Review, tres fases después de escribirla.
|
|
822
947
|
await write(`Registrá en ${HUMAN} una fila por cada decisión que ${task.id} dejó abierta, con qué la ` +
|
|
823
|
-
`cierra y quién puede tomarla.
|
|
948
|
+
`cierra y quién puede tomarla. La primera columna nunca es ${task.id}: el motor bloquea por esa ` +
|
|
949
|
+
`celda exacta y estas decisiones no impiden entregarla. Va la épica, el hito o el recorrido al que ` +
|
|
950
|
+
`alcanza la decisión. No inventes responsables ni fechas: ` +
|
|
824
951
|
`${JSON.stringify(openDecisions.map((entry) => entry.detail))}`, { label: 'open-decisions' })
|
|
825
952
|
}
|
|
826
953
|
// Y un caso que sí se fijó acá entra con su prueba o no entró: sin ella el comportamiento nuevo queda
|
|
@@ -830,11 +957,25 @@ while (rounds++ < MAX_TASKS) {
|
|
|
830
957
|
// cadena exacta frena una tarea correcta por haber nombrado el test de dos formas —`TestAlta` acá y
|
|
831
958
|
// `users_test.go::TestAlta` allá—. Alcanza con que uno nombre al otro; lo que sigue frenando, que es de
|
|
832
959
|
// lo que se trata, es el caso que no aparece en ningún rojo.
|
|
833
|
-
|
|
834
|
-
|
|
960
|
+
//
|
|
961
|
+
// Y la contención sola no alcanza, porque el caso que aparece no es que uno esté contenido en el otro:
|
|
962
|
+
// los dos nombran el mismo test y cada uno le agrega **su propia** anotación entre paréntesis —dónde
|
|
963
|
+
// está la línea de un lado, por qué se vio en rojo del otro—. Ahí ninguno contiene al otro y la puerta
|
|
964
|
+
// frenaba una entrega correcta, que es lo que R26 dice que termina apagándola. Se compara sin esa
|
|
965
|
+
// anotación final; una que esté en el medio se conserva, porque ahí sí es parte del nombre.
|
|
966
|
+
const core = (name) => String(name || '').replace(/\s*\([^)]*\)\s*$/, '').trim()
|
|
967
|
+
const namesTest = (red, item) => Boolean(core(item.test))
|
|
968
|
+
&& (core(red.test).includes(core(item.test)) || core(item.test).includes(core(red.test)))
|
|
835
969
|
const loose = build.discovered.find((entry) => entry.kind === 'edge'
|
|
836
970
|
&& !build.redFirst.some((red) => namesTest(red, entry)))
|
|
837
|
-
|
|
971
|
+
// El motivo dice qué comprobó la puerta y no una conclusión sobre el trabajo: pegarle al detalle del
|
|
972
|
+
// build un «entró sin la prueba que lo fija» producía una parada que se contradecía sola cuando el
|
|
973
|
+
// detalle contaba que la prueba sí estaba —la frase del agente y la de la puerta hablaban de cosas
|
|
974
|
+
// distintas y se leían como una—.
|
|
975
|
+
if (loose) {
|
|
976
|
+
return stop('edge-unproven', `${loose.detail} — su campo "test" (${loose.test || 'vacío'}) no nombra `
|
|
977
|
+
+ `ninguno de los rojos declarados: ${build.redFirst.map((red) => red.test).join(' | ') || '(ninguno)'}`)
|
|
978
|
+
}
|
|
838
979
|
|
|
839
980
|
// Qué revisión hubo, para que el cierre no pueda inventar una. Nace diciendo que no hubo porque
|
|
840
981
|
// `express` no convoca a nadie, y ése es el caso que se escribió como si un cargo hubiera aprobado.
|
|
@@ -848,17 +989,35 @@ while (rounds++ < MAX_TASKS) {
|
|
|
848
989
|
{ schema: REVIEWED, label: 'review' },
|
|
849
990
|
)
|
|
850
991
|
if (!review) return stop('agent-unavailable', 'Review no devolvió resultado')
|
|
992
|
+
// Se junta apenas cada pasada contesta, y no al final: las paradas de abajo salen antes de llegar al
|
|
993
|
+
// registro, y sin esto lo que el revisor señaló se iba con la corrida.
|
|
994
|
+
const reviewDecisions = review.concerns.filter((one) => one.decision)
|
|
995
|
+
let decidedNote = ''
|
|
851
996
|
if (review.verdict === 'bloqueado') {
|
|
852
|
-
return stop('review-blocked',
|
|
997
|
+
return stop('review-blocked', named(review).join('; ') || 'sin condiciones nombradas')
|
|
853
998
|
}
|
|
854
999
|
if (blockers(review).length) {
|
|
855
|
-
|
|
1000
|
+
// «Sólo estos hallazgos» acota el alcance (R6) y por sí solo deja un cabo suelto: una corrección
|
|
1001
|
+
// tiene dependientes y no se anuncian —el conteo que enumeraba lo que cambió, el comentario que
|
|
1002
|
+
// describía la forma vieja, la fila que la afirmaba—. Es R9 leído al derecho: lo que deja de valer
|
|
1003
|
+
// se lleva puesto a quien lo daba por cierto, y eso vive casi siempre en otro archivo.
|
|
1004
|
+
//
|
|
1005
|
+
// Sin pedirlo, la vuelta siguiente rechaza por la deriva que la corrección acabó de crear, y como
|
|
1006
|
+
// la vuelta es una sola eso termina en `review-failed` sobre trabajo correcto. Medido: una
|
|
1007
|
+
// corrección agregó una mutación y un caso de prueba, y la re-revisión frenó porque la fila de
|
|
1008
|
+
// acciones humanas seguía diciendo el número viejo y dos comentarios seguían contando los casos
|
|
1009
|
+
// anteriores —corrida `wf_99130468-2c4`, 2026-09-17, sobre un banco desechable—. Traerlos no
|
|
1010
|
+
// amplía el alcance: es terminar la corrección.
|
|
1011
|
+
await write(`Corregí sólo estos hallazgos con evidencia y actualizá el WIP: ${blockers(review).join('; ')}. `
|
|
1012
|
+
+ 'Traé también lo que tu propia corrección deje desactualizado —un conteo, un comentario que '
|
|
1013
|
+
+ 'describa la forma vieja, una fila que la enumere— y nada más que eso.',
|
|
856
1014
|
{ label: 'review-fix' })
|
|
857
1015
|
review = await run(`Volvé a revisar el diff corregido de ${task.id}.${MANIFEST}${VERDICT}${RULED}`,
|
|
858
1016
|
{ schema: REVIEWED, label: 'review' })
|
|
859
1017
|
if (!review) return stop('agent-unavailable', 'la re-revisión no devolvió resultado')
|
|
1018
|
+
reviewDecisions.push(...review.concerns.filter((one) => one.decision))
|
|
860
1019
|
if (review.verdict === 'bloqueado' || blockers(review).length) {
|
|
861
|
-
return stop('review-failed',
|
|
1020
|
+
return stop('review-failed', named(review).join('; ') || 'sin condiciones nombradas')
|
|
862
1021
|
}
|
|
863
1022
|
}
|
|
864
1023
|
// Con reglas que rigen, aprobar sin nombrar contra cuáles es la misma falla que la de abajo en otro eje.
|
|
@@ -871,7 +1030,30 @@ while (rounds++ < MAX_TASKS) {
|
|
|
871
1030
|
// Contra qué reglas se revisó va también a la entrada de DONE, y no sólo al journal: el journal muere
|
|
872
1031
|
// con la corrida y la entrada queda, así que sin esto no había cómo reconstruir contra cuáles se
|
|
873
1032
|
// revisó cuando alguien audita la entrega meses después (caso 122).
|
|
1033
|
+
// Lo que la revisión encontró y no le toca resolver va a la fila que una persona lee. No al INBOX, que
|
|
1034
|
+
// es para propuestas: quien lo lea ahí puede promoverlo, y esto no se promueve, se decide.
|
|
1035
|
+
//
|
|
1036
|
+
// Se acumula desde la primera pasada y no se lee sólo de la última: al corrector se le pasa nada más
|
|
1037
|
+
// lo que manda a corregir, así que nunca se entera de la decisión y no la puede cerrar. Leyendo sólo
|
|
1038
|
+
// la re-revisión, una decisión que el agente no repitiera desaparecía sin dejar rastro, y el mismo
|
|
1039
|
+
// camino la perdía entera cuando la revisión paraba antes de llegar acá.
|
|
1040
|
+
//
|
|
1041
|
+
// El tope es el mismo del INBOX y por la misma razón: una revisión que deja treinta filas en una tabla
|
|
1042
|
+
// que lee una persona no está priorizando (caso 101). Lo que no entra queda contado en el hecho.
|
|
1043
|
+
const decided = [...new Set(reviewDecisions.map((one) => one.detail))]
|
|
1044
|
+
const filed = decided.slice(0, INBOX_CAP)
|
|
1045
|
+
if (filed.length) {
|
|
1046
|
+
// La nota que devuelve viaja al hecho: sin ella la entrega afirma una fila que el disco no tiene,
|
|
1047
|
+
// que es el caso 087 entrando por otra puerta.
|
|
1048
|
+
const nota = await registerHuman(`Registrá en ${HUMAN} una fila por cada decisión que la revisión de `
|
|
1049
|
+
+ `${task.id} dejó abierta, con qué la cierra y quién puede tomarla. La primera columna nunca es `
|
|
1050
|
+
+ `${task.id} —el porqué es el mismo que en Build—: va la épica, el hito o el recorrido al que `
|
|
1051
|
+
+ `alcanza. No inventes responsables ni fechas: ${JSON.stringify(filed)}`, 'review-human')
|
|
1052
|
+
decidedNote = `${nota}`
|
|
1053
|
+
}
|
|
874
1054
|
reviewFact = `${review.verdict} por ${cast.review}, sobre ${review.consulted.join(', ')}`
|
|
1055
|
+
+ (filed.length ? ` · ${filed.length} decisión(es) registrada(s)${decidedNote}` : '')
|
|
1056
|
+
+ (decided.length > filed.length ? ` · ${decided.length - filed.length} decisión(es) sin volcar` : '')
|
|
875
1057
|
+ ((review.rules || []).length ? ` · reglas: ${review.rules.join(', ')}` : '')
|
|
876
1058
|
// Lo que no impide entregar no manda a tocar código, y tampoco desaparece: la mejora opinable que se
|
|
877
1059
|
// corrige a las apuradas cuesta una vuelta y un riesgo que nadie pidió. Va a Propuestas y no a
|
|
@@ -881,7 +1063,10 @@ while (rounds++ < MAX_TASKS) {
|
|
|
881
1063
|
// De qué vía salió cada una: este recorrido, la tarea que la dejó anotada y la fecha del motor. La
|
|
882
1064
|
// arma el recorrido, que es el único de los dos que sabe las tres cosas (caso 115).
|
|
883
1065
|
const origin = inboxOrigin('autobuild', task.id, planning.today)
|
|
884
|
-
|
|
1066
|
+
// Lo marcado como decisión ya tiene destino y no se duplica acá: escrito en los dos lados, además de
|
|
1067
|
+
// aparecer dos veces, le come una ranura del tope a una propuesta que sí lo era.
|
|
1068
|
+
const noted = review.concerns.filter((one) => !one.blocking && !one.decision)
|
|
1069
|
+
.map((one) => withOrigin(one.detail, origin))
|
|
885
1070
|
const kept = noted.slice(0, INBOX_CAP)
|
|
886
1071
|
// Lo que pasa del tope no se escribe y tampoco desaparece: queda contado en el hecho de revisión, que
|
|
887
1072
|
// viaja a `done/`. Una revisión que anota treinta y seis cosas no está priorizando, y el INBOX no las
|
|
@@ -925,7 +1110,7 @@ while (rounds++ < MAX_TASKS) {
|
|
|
925
1110
|
if (ambiguous) {
|
|
926
1111
|
const nota = await registerHuman(
|
|
927
1112
|
`Registrá ${task.id} en ${HUMAN}: el criterio "${ambiguous.criterion}" no dice qué habría ` +
|
|
928
|
-
`que aserciar, y hace falta la decisión que lo fija.`, 'verify-human')
|
|
1113
|
+
`que aserciar, y hace falta la decisión que lo fija.`, 'verify-human', task.id)
|
|
929
1114
|
return stop('acceptance-ambiguous', `${ambiguous.criterion}${nota}`)
|
|
930
1115
|
}
|
|
931
1116
|
if (verified.uncovered.length) {
|
|
@@ -1009,9 +1194,14 @@ const closing = await write(
|
|
|
1009
1194
|
)
|
|
1010
1195
|
if (!closing) return stop('agent-unavailable', 'Closing no devolvió resultado')
|
|
1011
1196
|
if (!closing.passed) return stop('planning-check-failed', closing.details)
|
|
1197
|
+
// El archivo nace con su estado escrito porque la compuerta lo lee de ahí —R28, y el porqué vive junto a
|
|
1198
|
+
// esa lectura—. Sin decirlo acá la fase escribe prosa sin `status`, y la instancia queda con una compuerta
|
|
1199
|
+
// que sólo se destraba borrando: la forma que la regla prohíbe, escrita por el propio recorrido.
|
|
1012
1200
|
if (completed.length && contract.humanCheckpoint) await write(
|
|
1013
1201
|
`Creá ${GATE} con el hito terminado, las tareas ${completed.join(', ')}, la evidencia, las acciones humanas ` +
|
|
1014
|
-
`pendientes y las instrucciones exactas para continuar.
|
|
1202
|
+
`pendientes y las instrucciones exactas para continuar. Arrancá el archivo con un frontmatter ` +
|
|
1203
|
+
`"status: pendiente", y decí que se destraba cambiándolo a "resuelta" —no borrando el archivo, que es ` +
|
|
1204
|
+
`lo que deja leer después qué se revisó—. Nunca hagas push ni deploy.`,
|
|
1015
1205
|
{ label: 'human-checkpoint' },
|
|
1016
1206
|
)
|
|
1017
1207
|
return finish({ done: completed, count: completed.length, hito: currentMilestone, phases: ran })
|