@ingeniomaps/cauce 0.72.0 → 0.73.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,32 @@ 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.73.0] - 2026-09-09
18
+
19
+ ### Corregido
20
+
21
+ - **Los guards dejan de frenar trabajo legítimo escrito en varias líneas.** Siete reglas acotaban
22
+ «dentro de este comando» con `[^;&|]`, y el salto de línea —que también separa comandos— no estaba en
23
+ esa lista: cualquier bandera de una línea posterior se leía como parte del comando de arriba. `git
24
+ commit -m "x"` seguido de `ls -a` se bloqueaba como si stageara al commitear, y `git push origin main`
25
+ seguido de `rm -f /tmp/x` como un force push, que además nombraba una violación que no estaba.
26
+
27
+ Lo que frena sigue frenando: `commit -am`, `add -A`, `push --force`, `--amend` e `install -g` en una
28
+ línea siguen bloqueados, y un `git push` seguido de otra cosa sigue pidiendo la acción humana que R10
29
+ exige — sólo que ya no lo llama reescritura de historia.
30
+
31
+ - **Un banco de evaluación que no se puede rehacer lo dice, en vez de seguir sobre uno que no es nuevo.**
32
+ El borrado ahora se comprueba, y si algo sobrevive la corrida corta nombrando qué. Hasta acá el mismo
33
+ hecho se venía rodeando por síntoma —reintentos en el borrado, `force` en el andamiaje, un `rm` antes
34
+ del enlace—, y cada rodeo dejaba la medición siguiente corriendo sobre restos de la anterior.
35
+
36
+ La prueba que lo destapaba, además, **descartaba el resultado de la corrida que fallaba**: cualquier
37
+ causa terminaba en el mismo `true !== false` con el `stderr` tirado, y por eso tres investigaciones
38
+ dejaron escrito «no está establecido por qué». Ahora la falla llega con su código y su ruta.
39
+
40
+ **Lo que te pide algo**: si rehacer un banco te corta con «no se pudo borrar entero», borralo a mano y
41
+ volvé a correr. Antes eso seguía en silencio y fallaba más adelante, en otro lado.
42
+
17
43
  ## [0.72.0] - 2026-09-09
18
44
 
19
45
  ### Corregido
@@ -102,6 +102,29 @@ function evaluationBench(root, agent, caso, force, kind) {
102
102
  // reintentos, rehacer un banco es una operación que falla de vez en cuando y deja la corrida sin
103
103
  // empezar.
104
104
  fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 })
105
+ // Y se comprueba que haya borrado. `rmSync` puede volver sin lanzar y dejar cosas —pasó en CI y no
106
+ // está establecido por qué—, y hasta acá cada síntoma se rodeaba por separado: `force` en el
107
+ // andamiaje, un `rm` antes del enlace. Rodearlo deja la corrida siguiendo sobre un banco que no es
108
+ // nuevo, y lo que falla después no dice nada del borrado: el test que lo destapó reportaba
109
+ // `true !== false` sobre un archivo de la corrida anterior, sin nombrar de dónde salía.
110
+ //
111
+ // Falla en vez de seguir, porque un banco a medio borrar contamina la medición que viene, que es lo
112
+ // que la recreación existe para evitar. Y nombra lo que sobrevivió: es lo único que va a permitir
113
+ // establecer la causa la próxima vez que ocurra.
114
+ if (fs.existsSync(dir)) {
115
+ const sobreviven = []
116
+ const recorrer = (base, relative = '') => {
117
+ for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
118
+ if (sobreviven.length >= 5) return
119
+ const next = relative ? `${relative}/${entry.name}` : entry.name
120
+ if (entry.isDirectory()) recorrer(path.join(base, entry.name), next)
121
+ else sobreviven.push(next)
122
+ }
123
+ }
124
+ try { recorrer(dir) } catch { /* el listado es la explicación, no la comprobación */ }
125
+ fail(`${dir} no se pudo borrar entero y el banco tiene que ser nuevo. Sobrevivieron al borrado: `
126
+ + `${sobreviven.join(', ') || '(sólo directorios)'}. Borralo a mano y volvé a correr.`, 2)
127
+ }
105
128
  // Con `force`: el banco es desechable y se acaba de borrar, así que lo que sobreviva al `rmSync` se
106
129
  // pisa en vez de cortar la corrida. Sin esto, `copyTemplate` se niega ante cualquier archivo que
107
130
  // quede —«El destino contiene …/AGENTS.md»— y el mismo test falló así tres veces en un día, en las
@@ -31,6 +31,13 @@ const EV = require('../core/evidence')
31
31
  const ANTES = String.raw`(?:^|[\s;&|('"\`])`
32
32
  const PALABRA = String.raw`$|[\s;&|)'"\`]`
33
33
  const COMANDO = String.raw`$|[;&|)'"\`]`
34
+ // Lo que puede haber **entre** un comando y su bandera sin salir de ese comando. El salto de línea va
35
+ // adentro de lo excluido porque también separa comandos, exactamente igual que `;`, `&` y `|`: sin él,
36
+ // una bandera escrita en una línea posterior se leía como parte del primer comando. Es la causa de los
37
+ // falsos positivos del caso 067, en siete reglas a la vez — `git push origin main` seguido de
38
+ // `rm -f /tmp/x` se bloqueaba anunciando «`git push --force` reescribe historia publicada», que además
39
+ // de frenar trabajo legítimo nombraba una violación que no estaba.
40
+ const MISMO = String.raw`[^;&|\n]`
34
41
 
35
42
  // Un mensaje de commit es dato, no código. `git commit -m "fix: bloquear git push --force"` disparaba
36
43
  // el guard de publicación, y lo mismo `rm -rf /` nombrado en una explicación; con el heredoc que se usa
@@ -62,7 +69,8 @@ function destructive(input) {
62
69
  // matchea igual las dos formas, así que `allowPush` habilitaba el force-push sin que nadie lo decidiera
63
70
  // y el párrafo de autonomía de `AGENTS.md` tenía que confesarlo. R8 prohíbe `force` sin excepción
64
71
  // configurable, así que esta rama va antes del permiso y no lo consulta.
65
- if (/\bgit\s+push\b[^;&|]*\s(?:-f|--force(?:-with-lease|-if-includes)?)\b/.test(command)) {
72
+ if (new RegExp(String.raw`\bgit\s+push\b${MISMO}*\s(?:-f|--force(?:-with-lease|-if-includes)?)\b`)
73
+ .test(command)) {
66
74
  block("'git push --force' reescribe historia ya publicada. R8 lo prohíbe y runner.allowPush no lo "
67
75
  + 'habilita: publicá con un push normal, o registrá una acción humana.')
68
76
  }
@@ -75,7 +83,7 @@ function destructive(input) {
75
83
  // no devolvía una línea. Se bloquea por política y no por daño —un `--amend` sobre algo que nadie vio
76
84
  // no rompe nada—, así que el mensaje manda a lo que sí corresponde: otro commit.
77
85
  [
78
- /\bgit\s+commit\b[^;&|]*\s--amend\b/,
86
+ new RegExp(String.raw`\bgit\s+commit\b${MISMO}*\s--amend\b`),
79
87
  "'git commit --amend' reescribe un commit ya creado. R8 pide uno nuevo en su lugar.",
80
88
  ],
81
89
  [/\bgit\s+clean\s+-[^\s]*f/, "'git clean -f' borra archivos sin seguimiento."],
@@ -89,7 +97,7 @@ function destructive(input) {
89
97
  [
90
98
  // `git restore .` no lleva `--` y destruye igual: comprobado en `git restore --help` (git 2.43.0),
91
99
  // que restaura el working tree por defecto y toma el pathspec sin separador.
92
- new RegExp(String.raw`\bgit\s+(?:checkout|restore)\s+(?:[^;&|]*?\s)?`
100
+ new RegExp(String.raw`\bgit\s+(?:checkout|restore)\s+(?:${MISMO}*?\s)?`
93
101
  + String.raw`(?:--(?=\s*(?:${COMANDO}))|(?:--\s+)?(?:\.|\*|:\/)(?=\s*(?:${COMANDO})))`),
94
102
  "'git checkout -- .' revierte todo lo no commiteado del directorio, no sólo lo que estás mirando. "
95
103
  + 'Nombrá el archivo, o commiteá lo que quieras conservar antes.',
@@ -104,7 +112,7 @@ function destructive(input) {
104
112
  ],
105
113
  [
106
114
  new RegExp(ANTES + String.raw`(?:mkfs\S*|shred)\s`
107
- + String.raw`|\bdd\s+[^;&|]*\bof=\/dev\/|>\s*\/dev\/(?:sd|nvme|disk)`),
115
+ + String.raw`|\bdd\s+${MISMO}*\bof=\/dev\/|>\s*\/dev\/(?:sd|nvme|disk)`),
108
116
  'Operación destructiva sobre disco o dispositivo.',
109
117
  ],
110
118
  [
@@ -124,14 +132,14 @@ function gitAdd(input) {
124
132
  // Dónde termina la palabra lo decide PALABRA y no un espacio: `bash -c "git add -A"` y
125
133
  // `eval 'git add -A'` pasaban porque después de la bandera venía una comilla. Es el hueco que 028
126
134
  // cerró en las reglas de `destructive`, y esta regla se quedó afuera de aquel arreglo.
127
- if (new RegExp(String.raw`\bgit\s+add\s+(?:[^;&|]*\s)?(?:-A|--all|\.)(?=${PALABRA})`)
135
+ if (new RegExp(String.raw`\bgit\s+add\s+(?:${MISMO}*\s)?(?:-A|--all|\.)(?=${PALABRA})`)
128
136
  .test(command)) {
129
137
  block("'git add -A/--all/.' está prohibido. Stagea rutas explícitas.")
130
138
  }
131
139
  // La misma regla con otra ortografía: `-a` stagea todo lo seguido sin nombrar una ruta, y encima lo
132
140
  // hace al commitear —después de este hook—, así que los guards que leen el índice tampoco lo ven.
133
141
  // `--amend` queda afuera: empieza con dos guiones y lo frena `destructive`, por otra razón.
134
- if (/\bgit\s+commit\b[^;&|]*\s(?:-[a-z]*a[a-z]*|--all)\b/.test(command)) {
142
+ if (new RegExp(String.raw`\bgit\s+commit\b${MISMO}*\s(?:-[a-z]*a[a-z]*|--all)\b`).test(command)) {
135
143
  block("'git commit -a' stagea al commitear, después de este guard: nadie llega a revisar el diff "
136
144
  + 'staged, ni vos ni los guards que lo miran. Stageá las rutas por nombre y commiteá aparte.')
137
145
  }
@@ -141,7 +149,8 @@ function dependencies(input) {
141
149
  if (process.env.OPS_DEPENDENCIES_OVERRIDE === '1') return
142
150
  const command = commandOf(input)
143
151
  const unsafePackageCommand = /\b(?:npm|pnpm|yarn|bun)\s+publish\b/.test(command)
144
- || /\b(?:npm|pnpm|yarn)\s+(?:install|add)\b[^;&|]*(?:\s-g\b|\s--global\b)/.test(command)
152
+ || new RegExp(String.raw`\b(?:npm|pnpm|yarn)\s+(?:install|add)\b${MISMO}*(?:\s-g\b|\s--global\b)`)
153
+ .test(command)
145
154
  if (unsafePackageCommand) {
146
155
  block('Publicar paquetes o instalar dependencias globales requiere una acción humana explícita.')
147
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.72.0",
3
+ "version": "0.73.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",