@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 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
- write(`Registrá ${unit.id} en ${HUMAN}: nadie pudo escribir un plan que sobreviva a la crítica. `
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.`, { label: 'plan-human' })
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
- await write(`Registrá ${task.id} en ${HUMAN} con el motivo y una acción humana exacta: ${ready.reason}.`,
575
- { label: 'ready-human' })
576
- return stop('not-ready', ready.reason)
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
- await write(`Registrá ${task.id} en ${HUMAN}: el criterio "${ambiguous.criterion}" no dice qué habría ` +
797
- `que aserciar, y hace falta la decisión que lo fija.`, { label: 'verify-human' })
798
- return stop('acceptance-ambiguous', ambiguous.criterion)
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 ` +
@@ -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
- if (fs.existsSync(file)) {
237
- block(`${raw} es una migración existente. Crea una nueva en vez de reescribir historial.`)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",