@ingeniomaps/cauce 0.78.0 → 0.79.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 +36 -0
- package/automatization/workflows/autobuild.js +21 -10
- package/engine/hooks/files.js +32 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,42 @@ 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.79.0] - 2026-09-10
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **La fila que registra una parada se escribe antes de parar.** Cuando ningún plan sobrevive a la
|
|
22
|
+
crítica, el recorrido pide que la tarea quede en `HUMAN_ACTIONS.md` con su motivo —así `context` deja
|
|
23
|
+
de ofrecerla y alguien la ve—. Esa escritura se lanzaba y el recorrido volvía en la línea siguiente,
|
|
24
|
+
así que el agente que la escribía se quedaba a mitad de camino: en una corrida real el resumen contó
|
|
25
|
+
diez agentes y el registro nueve. La parada se informaba bien y el disco no la tenía.
|
|
26
|
+
|
|
27
|
+
Ahora se espera. Y en las tres paradas que dejan fila —plan rechazado, tarea que no está lista,
|
|
28
|
+
criterio que no dice qué aserciar— si el agente no contesta, el detalle de la parada lo dice: el
|
|
29
|
+
motivo sigue siendo el que la causó, y se agrega que la fila hay que escribirla a mano.
|
|
30
|
+
|
|
31
|
+
**Qué cambia para vos**: relanzar después de un plan rechazado deja de repetir la planificación
|
|
32
|
+
entera, porque la tarea queda registrada. Si venías viendo que `context` te ofrecía otra vez la tarea
|
|
33
|
+
que acababa de rechazarse, era esto.
|
|
34
|
+
|
|
35
|
+
- **El guard de migraciones frena por haber viajado, no por estar en disco.** Escribir una migración son
|
|
36
|
+
dos pasos —crearla y completarla— y el segundo se bloqueaba: el chequeo preguntaba si el archivo
|
|
37
|
+
existía, que no es la pregunta. Un stub de hace dos segundos y una migración publicada hace un año
|
|
38
|
+
daban la misma respuesta, y **ninguna herramienta lo esquivaba** —`Write`, `Edit` y `MultiEdit` se
|
|
39
|
+
bloqueaban igual—, así que lo único que quedaba a la vista era apagar el guard entero, incluida la
|
|
40
|
+
protección contra SQL destructivo.
|
|
41
|
+
|
|
42
|
+
Ahora se le pregunta a git si el archivo está en `HEAD`. En el índice no alcanza: un archivo apenas
|
|
43
|
+
agregado no viajó a ninguna parte. Y sin repositorio con el que contestar —un proyecto sin git— se
|
|
44
|
+
conserva la conducta anterior en vez de dejar pasar, que es el lado correcto para equivocarse cuando
|
|
45
|
+
no se puede saber.
|
|
46
|
+
|
|
47
|
+
**Qué cambia para vos**: completar una migración recién creada deja de frenarse. El mensaje del
|
|
48
|
+
bloqueo pasa a decir el hecho que lo sostiene —que está en el historial, o que existe y acá no hay con
|
|
49
|
+
qué saberlo— y a nombrar cómo aprobar esa ruta puntual en `planning/.ops-approval`, que es lo que el
|
|
50
|
+
bloqueo hermano ya hacía. Si tenías `OPS_MIGRATIONS_OVERRIDE=1` puesto para poder trabajar, ya no hace
|
|
51
|
+
falta y conviene sacarlo: apagaba también lo que sí te cuida.
|
|
52
|
+
|
|
17
53
|
## [0.78.0] - 2026-09-10
|
|
18
54
|
|
|
19
55
|
### Corregido
|
|
@@ -355,6 +355,14 @@ const read = (prompt, options = {}) => agent(`${BASE}\n\n${prompt}`, options)
|
|
|
355
355
|
const run = (prompt, options = {}) => agent(`${SCOPE}\n\n${prompt}`, options)
|
|
356
356
|
const write = (prompt, options = {}) => agent(`${LEDGER}\n\n${prompt}`, options)
|
|
357
357
|
|
|
358
|
+
// Las tres paradas que dejan una fila en HUMAN_ACTIONS delegan esa escritura a un agente, y esa fila es
|
|
359
|
+
// el único rastro de la parada: sin ella el recorrido informa un estado que el disco no tiene. Por eso
|
|
360
|
+
// se espera —lanzarla y volver en la línea siguiente la abandona— y por eso se mira si contestó.
|
|
361
|
+
// Devuelve lo que hay que agregarle al detalle, vacío cuando la fila quedó pedida. Caso 087.
|
|
362
|
+
const registerHuman = async (prompt, label) => (await write(prompt, { label })
|
|
363
|
+
? ''
|
|
364
|
+
: ` — la fila en ${HUMAN} no se pudo registrar: escribila a mano`)
|
|
365
|
+
|
|
358
366
|
// Gate, mutex de WIP y selección de tarea salen de un comando determinista: AWAITING_REVIEW, BACKLOG,
|
|
359
367
|
// WIP y HUMAN_ACTIONS nunca entran al contexto de un modelo, y su tamaño deja de costar tokens.
|
|
360
368
|
const readContext = () => read(
|
|
@@ -552,12 +560,13 @@ while (rounds++ < MAX_TASKS) {
|
|
|
552
560
|
//
|
|
553
561
|
// Lo que la fila le pide a una persona lo dice R17: dos rechazos sobre lo mismo son el disparador
|
|
554
562
|
// posterior de división. No se parte acá porque partir es una decisión, y ésa no le toca al recorrido.
|
|
555
|
-
const planRejected = (reason, unit, found) => {
|
|
563
|
+
const planRejected = async (reason, unit, found) => {
|
|
556
564
|
const detail = found.join('; ') || 'sin condiciones nombradas'
|
|
557
|
-
|
|
565
|
+
const nota = await registerHuman(
|
|
566
|
+
`Registrá ${unit.id} en ${HUMAN}: nadie pudo escribir un plan que sobreviva a la crítica. `
|
|
558
567
|
+ `Motivo: ${detail}. La acción humana es revisar si la unidad son dos resultados con vidas `
|
|
559
|
-
+ `distintas y partirla —R17—, o dejarla entera con la razón escrita.`,
|
|
560
|
-
return stop(reason, detail)
|
|
568
|
+
+ `distintas y partirla —R17—, o dejarla entera con la razón escrita.`, 'plan-human')
|
|
569
|
+
return stop(reason, `${detail}${nota}`)
|
|
561
570
|
}
|
|
562
571
|
|
|
563
572
|
if (!planning.wipActive) {
|
|
@@ -571,9 +580,10 @@ while (rounds++ < MAX_TASKS) {
|
|
|
571
580
|
)
|
|
572
581
|
if (!ready) return stop('agent-unavailable', 'Ready no devolvió resultado')
|
|
573
582
|
if (!ready.ready) {
|
|
574
|
-
|
|
575
|
-
{
|
|
576
|
-
|
|
583
|
+
const nota = await registerHuman(
|
|
584
|
+
`Registrá ${task.id} en ${HUMAN} con el motivo y una acción humana exacta: ${ready.reason}.`,
|
|
585
|
+
'ready-human')
|
|
586
|
+
return stop('not-ready', `${ready.reason}${nota}`)
|
|
577
587
|
}
|
|
578
588
|
if (ready.refinedAcceptance) task.acceptance = ready.refinedAcceptance
|
|
579
589
|
}
|
|
@@ -793,9 +803,10 @@ while (rounds++ < MAX_TASKS) {
|
|
|
793
803
|
// hacer parar a una persona por eso le cobra una interrupción por algo que se resolvía solo.
|
|
794
804
|
const ambiguous = verified.uncovered.find((entry) => entry.cause === 'ambiguous')
|
|
795
805
|
if (ambiguous) {
|
|
796
|
-
|
|
797
|
-
`
|
|
798
|
-
|
|
806
|
+
const nota = await registerHuman(
|
|
807
|
+
`Registrá ${task.id} en ${HUMAN}: el criterio "${ambiguous.criterion}" no dice qué habría ` +
|
|
808
|
+
`que aserciar, y hace falta la decisión que lo fija.`, 'verify-human')
|
|
809
|
+
return stop('acceptance-ambiguous', `${ambiguous.criterion}${nota}`)
|
|
799
810
|
}
|
|
800
811
|
if (verified.uncovered.length) {
|
|
801
812
|
await run(`${asRole(cast.build)}Escribí sólo las pruebas que faltan en ${task.id}, con el mismo rojo ` +
|
package/engine/hooks/files.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('node:fs')
|
|
8
8
|
const path = require('node:path')
|
|
9
|
+
const { spawnSync } = require('node:child_process')
|
|
9
10
|
const {
|
|
10
11
|
patchOf, filesOf, contentOf, cwdOf, block, configOf, findOpsRoot,
|
|
11
12
|
writableRoots, outsideRoots, DECLARE_IT,
|
|
@@ -26,6 +27,29 @@ function opsRoot(input) {
|
|
|
26
27
|
// el guard hasta que cierre la sesión.
|
|
27
28
|
const approved = (input, file) => !AP.pending(opsRoot(input), [file]).length
|
|
28
29
|
|
|
30
|
+
// Si la migración ya viajó a otra copia, que es lo que el bloqueo de abajo quiere saber y `existsSync`
|
|
31
|
+
// no contesta. Devuelve el motivo del bloqueo o cadena vacía.
|
|
32
|
+
//
|
|
33
|
+
// **`HEAD` y no el índice**: un archivo apenas `git add`eado no viajó a ninguna parte, y `git ls-files`
|
|
34
|
+
// lo daría por historial. Y **resolver la raíz es una pregunta aparte** de si el archivo está en `HEAD`:
|
|
35
|
+
// las dos fallan con 128 y confundirlas repite el error que este caso arregla —decidir por la respuesta
|
|
36
|
+
// equivocada—. Sin raíz resoluble se degrada a la conducta de antes, que bloquea de más, porque cuando
|
|
37
|
+
// no se puede saber ése es el lado correcto para equivocarse. Es la degradación que `check` ya declara
|
|
38
|
+
// cuando no puede resolver el repositorio de un servicio. Caso 086.
|
|
39
|
+
function alreadyShipped(file) {
|
|
40
|
+
if (!fs.existsSync(file)) return ''
|
|
41
|
+
const cwd = path.dirname(file)
|
|
42
|
+
const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd, encoding: 'utf8' })
|
|
43
|
+
if (top.status !== 0) {
|
|
44
|
+
return 'existe, y acá no hay repositorio con el que saber si ya viajó a otra copia'
|
|
45
|
+
}
|
|
46
|
+
const rel = path.relative(top.stdout.trim(), file).split(path.sep).join('/')
|
|
47
|
+
return spawnSync('git', ['cat-file', '-e', `HEAD:${rel}`], { cwd }).status === 0
|
|
48
|
+
? 'ya está en el historial del repositorio'
|
|
49
|
+
: ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
29
53
|
function secrets(input) {
|
|
30
54
|
for (const file of filesOf(input)) {
|
|
31
55
|
const base = path.basename(file)
|
|
@@ -232,9 +256,15 @@ function migrations(input) {
|
|
|
232
256
|
if (destructiveSql.test(contentOf(input))) {
|
|
233
257
|
block(`${raw} contiene SQL destructivo.\n${AP.HOW('OPS_MIGRATIONS_OVERRIDE')}`)
|
|
234
258
|
}
|
|
259
|
+
// El mensaje nombra el hecho que sostiene el bloqueo y no su interpretación: «historial» era una
|
|
260
|
+
// lectura que `existsSync` no podía dar, y se la daba igual sobre stubs de la misma sesión. Y lleva
|
|
261
|
+
// la salida angosta, que hasta 0.79.0 sólo tenía el bloqueo hermano: éste es el que aparece en el
|
|
262
|
+
// flujo normal de escribir una migración, así que era justo el que no podía quedarse sin decirla.
|
|
235
263
|
const file = path.resolve(cwdOf(input), raw)
|
|
236
|
-
|
|
237
|
-
|
|
264
|
+
const shipped = alreadyShipped(file)
|
|
265
|
+
if (shipped) {
|
|
266
|
+
block(`${raw} ${shipped}. Crea una nueva en vez de reescribirla.\n`
|
|
267
|
+
+ AP.HOW('OPS_MIGRATIONS_OVERRIDE'))
|
|
238
268
|
}
|
|
239
269
|
}
|
|
240
270
|
}
|