@ingeniomaps/cauce 0.61.0 → 0.63.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 +51 -0
- package/engine/cli/catalog.js +9 -1
- package/engine/hooks/input.js +63 -6
- package/engine/hooks/shell.js +35 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,57 @@ 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.63.0] - 2026-09-06
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **El salto de línea termina la lista de argumentos de un comando.** `shell-boundary` leía los
|
|
22
|
+
argumentos de un `cp`, un `tee` o un `sed -i` cruzando a la línea siguiente, así que el destino que
|
|
23
|
+
acusaba podía ser el comando de abajo: un bloqueo que hablaba de una escritura en `…/python3`, una
|
|
24
|
+
ruta que no aparecía en el comando. Falla hacia el lado seguro —frena de más— pero señala algo que no
|
|
25
|
+
existe, y un guard que señala mal es el que se termina apagando. **Qué cambia para vos**: si escribís
|
|
26
|
+
scripts de varias líneas en un solo comando, dejás de ver bloqueos por rutas inventadas; lo que sí
|
|
27
|
+
escribe fuera de las raíces se sigue frenando igual.
|
|
28
|
+
|
|
29
|
+
- **El cuerpo de un heredoc es texto, no un comando.** Escribir un archivo con
|
|
30
|
+
`cat > nota.md <<'FIN' … FIN` juzgaba cada línea del documento como si fuera a ejecutarse, así que no
|
|
31
|
+
se podía documentar lo que los guards vigilan: un párrafo que explica por qué no se borra la raíz se
|
|
32
|
+
bloqueaba por nombrarlo, y la salida era cambiar de herramienta para escribir un archivo. Un heredoc
|
|
33
|
+
es entrada estándar y no se ejecuta nunca. **Qué cambia para vos**: el cuerpo deja de juzgarse y la
|
|
34
|
+
línea que lo abre se sigue juzgando entera, con su redirección —también si la escribís después del
|
|
35
|
+
delimitador, como en `cat <<FIN > salida`—. Lo que se pierde a cambio: un cuerpo que después alguien
|
|
36
|
+
ejecuta; al escribirse no ejecuta nada, y cuando se corra el guard verá el comando de verdad.
|
|
37
|
+
|
|
38
|
+
- **Una variable delante de `git commit` ya no apaga tres guards.** `dependencies`, `governance` y el
|
|
39
|
+
control de generados de `verify` sólo corren sobre un commit, y decidían si lo era con un ancla que
|
|
40
|
+
no contempla lo que un shell admite antes del verbo. Con `VAR=1 git commit` dejaban de correr **sin
|
|
41
|
+
decir nada**, y con la ironía de que el prefijo que se escribe para un commit de gobernanza es una
|
|
42
|
+
asignación: `OPS_GOVERNANCE_OVERRIDE=1 git commit` no leía el override, hacía que el guard no se
|
|
43
|
+
ejecutara. **Qué cambia para vos**: si venías escribiendo ese prefijo, ahora el guard corre y te va a
|
|
44
|
+
frenar. La variable se lee del entorno del guard, no del comando, y el mensaje ahora lo dice.
|
|
45
|
+
|
|
46
|
+
- **Un índice que no se puede leer deja de autorizar el commit.** `stagedFiles` devolvía una lista vacía
|
|
47
|
+
tanto si el índice estaba vacío como si no se pudo leer, y los tres guards de arriba leen esa
|
|
48
|
+
respuesta: una lectura fallida se les presentaba como «no hay nada que revisar». Llegar a una es
|
|
49
|
+
fácil, porque el guard no expande variables: `git -C $OPS commit` resuelve la ruta literal `$OPS`.
|
|
50
|
+
**Qué cambia para vos**: ese comando ahora se frena con un mensaje que nombra la causa. Escribí la
|
|
51
|
+
ruta literal en `git -C`.
|
|
52
|
+
|
|
53
|
+
## [0.62.0] - 2026-09-06
|
|
54
|
+
|
|
55
|
+
### Corregido
|
|
56
|
+
|
|
57
|
+
- **Tres reglas de `destructive` reconocen el comando aunque venga envuelto.** Decidían dónde termina
|
|
58
|
+
una palabra admitiendo sólo un espacio, el principio o el fin, y en un shell una palabra también
|
|
59
|
+
termina en `;`, `&`, `|`, `)` y en una comilla. Con eso `rm -rf /; echo listo` **pasaba** —sin una
|
|
60
|
+
sola comilla: lo que decidía era el espacio antes del punto y coma— y las tres se esquivaban dentro de
|
|
61
|
+
`bash -c`, `sh -c`, `eval` o un subshell, donde lo de adentro sí se ejecuta. Las tres son `rm -rf`
|
|
62
|
+
sobre `/`, home o `..`; `git checkout -- .` y su `restore`; y `mkfs`/`shred`. Las otras cinco de la
|
|
63
|
+
tabla ya estaban sanas. **Qué cambia para vos**: algún comando que ayer pasaba ahora se frena, y es el
|
|
64
|
+
que la regla siempre dijo que frenaba. Lo que **no** cambia es lo corriente: `rm -rf /srv/cache`,
|
|
65
|
+
`rm -r build/cache` y `git checkout -- src/app.js` siguen pasando — revertir un archivo nombrado
|
|
66
|
+
nunca fue lo que esta regla toca.
|
|
67
|
+
|
|
17
68
|
## [0.61.0] - 2026-09-06
|
|
18
69
|
|
|
19
70
|
### Agregado
|
package/engine/cli/catalog.js
CHANGED
|
@@ -102,7 +102,15 @@ 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
|
-
|
|
105
|
+
// Con `force`: el banco es desechable y se acaba de borrar, así que lo que sobreviva al `rmSync` se
|
|
106
|
+
// pisa en vez de cortar la corrida. Sin esto, `copyTemplate` se niega ante cualquier archivo que
|
|
107
|
+
// quede —«El destino contiene …/AGENTS.md»— y el mismo test falló así tres veces en un día, en las
|
|
108
|
+
// dos patas de la matriz. Por qué algo sobrevive a un borrado que no lanzó no está establecido.
|
|
109
|
+
//
|
|
110
|
+
// No ablanda ninguna protección: la pregunta «¿acá alguien trabajó?» la contesta el `git status` de
|
|
111
|
+
// arriba, que exige `--force` explícito para seguir. Esta segunda puerta no la eligió nadie y sólo
|
|
112
|
+
// se cerraba a veces, que es la clase de freno que enseña a re-correr sin leer.
|
|
113
|
+
IN.scaffold(dir, { name: 'Banco de evaluación', mode: 'sidecar', quiet: true, force: true })
|
|
106
114
|
// El motor por symlink: la misma resolución que en una instancia real —`node_modules/@ingeniomaps`—
|
|
107
115
|
// sin pagar un `npm install` por corrida. El cargo llega a un banco donde el CLI funciona.
|
|
108
116
|
const scope = path.join(dir, 'node_modules', '@ingeniomaps')
|
package/engine/hooks/input.js
CHANGED
|
@@ -22,10 +22,27 @@ function readInput() {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// El cuerpo de un heredoc es entrada estándar: no se ejecuta, se escribe. Juzgarlo como comando frenaba
|
|
26
|
+
// documentar lo que los guards vigilan — escribir un archivo que explica por qué borrar la raíz es
|
|
27
|
+
// catastrófico se bloqueaba por nombrarlo—, y la salida era cambiar de herramienta, que es el rodeo que
|
|
28
|
+
// un guard no debería enseñar.
|
|
29
|
+
//
|
|
30
|
+
// La línea de apertura se conserva **entera**, porque sí es comando y `shell-boundary` tiene que seguir
|
|
31
|
+
// viendo dónde escribe. Entera incluye lo que va después del delimitador: `cat <<FIN > salida` es una
|
|
32
|
+
// forma válida y su destino está ahí. El cuerpo empieza en el salto de línea, no en el delimitador —
|
|
33
|
+
// recortar desde el delimitador se llevaba esa redirección, y nada lo notaba porque en la forma común
|
|
34
|
+
// el destino va antes del `<<`.
|
|
35
|
+
//
|
|
36
|
+
// Lo que se pierde: un cuerpo que después alguien ejecuta. `cat > script.sh <<EOF` no ejecuta nada al
|
|
37
|
+
// escribirse y el guard verá el comando de verdad cuando alguien corra el script; el borde filoso es
|
|
38
|
+
// `$(cat <<EOF …)`, donde el cuerpo sí corre y ya no se mira. Es el mismo trato que con el mensaje de un
|
|
39
|
+
// commit: se frena la forma habitual, no al que quiere pasar.
|
|
40
|
+
const HEREDOC = /<<(-?)\s*(['"]?)([A-Za-z_][A-Za-z0-9_]*)\2([^\n]*)\n[\s\S]*?^\s*\3\s*$/gm
|
|
41
|
+
|
|
25
42
|
function commandOf(input) {
|
|
26
43
|
const value = input.tool_input && (input.tool_input.command || input.tool_input.cmd)
|
|
27
44
|
|| input.command || input.input && input.input.command || process.env.OPS_HOOK_COMMAND || ''
|
|
28
|
-
return Array.isArray(value) ? value.join(' ') :
|
|
45
|
+
return String(Array.isArray(value) ? value.join(' ') : value).replace(HEREDOC, '<<$1$2$3$2$4')
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
function fileOf(input) {
|
|
@@ -86,19 +103,59 @@ function configOf(root) {
|
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
|
|
106
|
+
// Vacía lo que va entre comillas, dejando una marca que ningún patrón confunde con una ruta ni con un
|
|
107
|
+
// comando. Vive acá porque la usan tres lugares por razones distintas, y cada uno explica la suya donde
|
|
108
|
+
// la llama.
|
|
109
|
+
const unquoted = (command) => String(command).replace(/'[^']*'|"[^"]*"/g, '\u0000')
|
|
110
|
+
|
|
111
|
+
// Sobre qué repositorio se lee el índice. En un commit se mira el comando con el mensaje vaciado, por
|
|
112
|
+
// la misma razón por la que `destructive` lo hace: un mensaje que menciona `git -C $VAR` no está
|
|
113
|
+
// eligiendo un repositorio, lo está citando. Sin esto, el commit que explica este arreglo se bloquea a
|
|
114
|
+
// sí mismo — pasó al escribirlo.
|
|
115
|
+
//
|
|
116
|
+
// El precio es una ruta entrecomillada en el propio `-C` de un commit —`git -C "mi carpeta" commit`—,
|
|
117
|
+
// que se pierde y cae al cwd. Es más raro que un mensaje que cita un comando, y el cwd de un commit
|
|
118
|
+
// suele ser el repositorio correcto; el caso contrario deja al guard leyendo un índice ajeno.
|
|
89
119
|
function gitDirectory(command, cwd) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
120
|
+
const text = isCommit(command) ? unquoted(command) : command
|
|
121
|
+
const flag = text.match(/(?:^|\s)git\s+-C\s+(['"]?)([^\s'";&|]+)\1/)
|
|
122
|
+
const cd = text.match(/(?:^|[;&|]\s*)cd\s+(['"]?)([^\s'";&|]+)\1/)
|
|
92
123
|
return path.resolve(cwd, flag ? flag[2] : cd ? cd[2] : '.')
|
|
93
124
|
}
|
|
94
125
|
|
|
126
|
+
// Lo que un shell admite delante del verbo: asignaciones de entorno, `env` y `sudo`. `VAR=1 git commit`
|
|
127
|
+
// empieza por la asignación, así que un ancla que sólo acepta el principio del comando o un separador
|
|
128
|
+
// no ve el `git` que viene después.
|
|
129
|
+
//
|
|
130
|
+
// Falla en los dos sentidos y uno no avisa. Del lado ruidoso, el mensaje del commit vuelve a juzgarse
|
|
131
|
+
// como comando. Del silencioso —el que importa— los tres guards que sólo corren sobre un commit dejan
|
|
132
|
+
// de correr: gobernanza, dependencias y generados. Cualquier variable delante alcanza, y la ironía es
|
|
133
|
+
// que el prefijo que el procedimiento manda escribir para un commit de gobernanza es
|
|
134
|
+
// `OPS_GOVERNANCE_OVERRIDE=1`: escrito ahí, el guard no lee el override, directamente no se ejecuta.
|
|
135
|
+
const PREFIX = String.raw`(?:^|[;&|]\s*)(?:(?:env|sudo)\s+)*`
|
|
136
|
+
+ String.raw`(?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S*)\s+)*`
|
|
137
|
+
const COMMIT = new RegExp(PREFIX + String.raw`git(?:\s+-C\s+\S+)?\s+commit(?:\s|$)`)
|
|
138
|
+
|
|
95
139
|
function isCommit(command) {
|
|
96
|
-
return
|
|
140
|
+
return COMMIT.test(command)
|
|
97
141
|
}
|
|
98
142
|
|
|
143
|
+
// Un índice vacío y un índice ilegible no son la misma respuesta: la primera autoriza a seguir, la
|
|
144
|
+
// segunda no autoriza nada. Devolviendo `[]` en los dos casos, los tres guards que preguntan acá se
|
|
145
|
+
// apagaban en silencio ante cualquier lectura fallida — y llegar a una es fácil, porque `gitDirectory`
|
|
146
|
+
// no expande variables: `git -C $OPS commit` resuelve la ruta literal `$OPS`, que no existe.
|
|
147
|
+
//
|
|
148
|
+
// Es la regla que el propio shim ya tiene escrita —«Un guard que no encuentra su motor bloquea, nunca
|
|
149
|
+
// permite»—, aplicada donde faltaba. Bloquear desde acá es seguro: los tres llamadores son guards, así
|
|
150
|
+
// que no hay ningún consumidor que sólo quiera consultar el índice.
|
|
99
151
|
function stagedFiles(dir) {
|
|
100
152
|
const result = spawnSync('git', ['-C', dir, 'diff', '--cached', '--name-only'], { encoding: 'utf8' })
|
|
101
|
-
|
|
153
|
+
if (result.status !== 0) {
|
|
154
|
+
const why = (result.stderr || '').trim() || (result.error && result.error.message) || 'git falló'
|
|
155
|
+
block(`no se pudo leer el índice de ${dir} (${why}). Un guard que no puede verificar no autoriza. `
|
|
156
|
+
+ 'Si usaste una variable en `git -C`, escribí la ruta literal.')
|
|
157
|
+
}
|
|
158
|
+
return result.stdout.trim().split('\n').filter(Boolean)
|
|
102
159
|
}
|
|
103
160
|
|
|
104
161
|
// R10 pide «la autorización configurada para el proyecto» y `runner.allowPush` es esa configuración:
|
|
@@ -157,5 +214,5 @@ const DECLARE_IT = 'Si el proyecto necesita escribir ahí, declaralo en writable
|
|
|
157
214
|
module.exports = {
|
|
158
215
|
readInput, commandOf, patchOf, filesOf, contentOf, cwdOf, block, configOf,
|
|
159
216
|
gitDirectory, isCommit, stagedFiles, pushAllowed, findOpsRoot,
|
|
160
|
-
writableRoots, outsideRoots, DECLARE_IT,
|
|
217
|
+
writableRoots, outsideRoots, DECLARE_IT, unquoted,
|
|
161
218
|
}
|
package/engine/hooks/shell.js
CHANGED
|
@@ -10,9 +10,26 @@ const path = require('node:path')
|
|
|
10
10
|
const { spawnSync } = require('node:child_process')
|
|
11
11
|
const {
|
|
12
12
|
commandOf, cwdOf, block, gitDirectory, isCommit, stagedFiles, pushAllowed,
|
|
13
|
-
writableRoots, outsideRoots, DECLARE_IT,
|
|
13
|
+
writableRoots, outsideRoots, DECLARE_IT, unquoted,
|
|
14
14
|
} = require('./input')
|
|
15
15
|
|
|
16
|
+
// Dónde empieza y dónde termina una palabra dentro de un comando. Tres reglas de la tabla de abajo lo
|
|
17
|
+
// decidían por su cuenta admitiendo sólo un espacio, el principio o el fin, y en un shell una palabra
|
|
18
|
+
// también termina en `;`, `&`, `|`, `)` y en una comilla. Con eso `rm -rf /; echo listo` pasaba —sin una
|
|
19
|
+
// sola comilla, porque lo que decidía era el espacio antes del punto y coma— y las tres se esquivaban
|
|
20
|
+
// envueltas en `bash -c`, `sh -c`, `eval` o un subshell, donde lo de adentro sí se ejecuta.
|
|
21
|
+
//
|
|
22
|
+
// Son dos cierres y no uno, y la diferencia es lo que evita frenar de más. PALABRA termina una palabra:
|
|
23
|
+
// el espacio cuenta, porque después de una ruta un espacio la termina. COMANDO termina el comando: ahí
|
|
24
|
+
// el espacio **no** cuenta, porque `--` seguido de un espacio significa que viene un archivo nombrado, y
|
|
25
|
+
// revertir un archivo nombrado es trabajo corriente que la regla no toca.
|
|
26
|
+
//
|
|
27
|
+
// Y el `\s*` va adentro del lookahead. Afuera, el cuantificador retrocede a vacío y el lookahead ve el
|
|
28
|
+
// espacio que él mismo habría consumido, así que `git checkout -- src/app.js` empieza a caer.
|
|
29
|
+
const ANTES = String.raw`(?:^|[\s;&|('"\`])`
|
|
30
|
+
const PALABRA = String.raw`$|[\s;&|)'"\`]`
|
|
31
|
+
const COMANDO = String.raw`$|[;&|)'"\`]`
|
|
32
|
+
|
|
16
33
|
// Un mensaje de commit es dato, no código. `git commit -m "fix: bloquear git push --force"` disparaba
|
|
17
34
|
// el guard de publicación, y lo mismo `rm -rf /` nombrado en una explicación; con el heredoc que se usa
|
|
18
35
|
// para un mensaje largo, el cuerpo entero entra en el comando, así que la línea que arregla esto no se
|
|
@@ -61,7 +78,8 @@ function destructive(input) {
|
|
|
61
78
|
[
|
|
62
79
|
// `git restore .` no lleva `--` y destruye igual: comprobado en `git restore --help` (git 2.43.0),
|
|
63
80
|
// que restaura el working tree por defecto y toma el pathspec sin separador.
|
|
64
|
-
|
|
81
|
+
new RegExp(String.raw`\bgit\s+(?:checkout|restore)\s+(?:[^;&|]*?\s)?`
|
|
82
|
+
+ String.raw`(?:--(?=\s*(?:${COMANDO}))|(?:--\s+)?(?:\.|\*|:\/)(?=\s*(?:${COMANDO})))`),
|
|
65
83
|
"'git checkout -- .' revierte todo lo no commiteado del directorio, no sólo lo que estás mirando. "
|
|
66
84
|
+ 'Nombrá el archivo, o commiteá lo que quieras conservar antes.',
|
|
67
85
|
],
|
|
@@ -74,11 +92,12 @@ function destructive(input) {
|
|
|
74
92
|
'Detener un stack Compose puede interrumpir servicios compartidos.',
|
|
75
93
|
],
|
|
76
94
|
[
|
|
77
|
-
|
|
95
|
+
new RegExp(ANTES + String.raw`(?:mkfs\S*|shred)\s`
|
|
96
|
+
+ String.raw`|\bdd\s+[^;&|]*\bof=\/dev\/|>\s*\/dev\/(?:sd|nvme|disk)`),
|
|
78
97
|
'Operación destructiva sobre disco o dispositivo.',
|
|
79
98
|
],
|
|
80
99
|
[
|
|
81
|
-
|
|
100
|
+
new RegExp(String.raw`\brm\s+(?:-[^\s]*r[^\s]*\s+)+(?:\/\*?|~\/?|\$HOME|\.\.)(?=${PALABRA})`),
|
|
82
101
|
"'rm -r' sobre /, home o el directorio padre es catastrófico.",
|
|
83
102
|
],
|
|
84
103
|
]
|
|
@@ -150,10 +169,17 @@ function dependencies(input) {
|
|
|
150
169
|
// Tres familias, porque los comandos no nombran su destino igual: `tee` y `truncate` escriben en cada
|
|
151
170
|
// argumento, `cp` y sus hermanos en el último, y `sed` sólo escribe con `-i` —sin él lee y manda a
|
|
152
171
|
// stdout, y esa redirección la ve REDIRECT—.
|
|
172
|
+
//
|
|
173
|
+
// El salto de línea termina una lista de argumentos igual que `;`. Sin excluirlo, la de un `cp` seguía
|
|
174
|
+
// leyendo la línea de abajo y el destino terminaba siendo el comando siguiente: un bloqueo que nombraba
|
|
175
|
+
// `…/python3`, una ruta que no aparecía en el comando. Se veía con un heredoc debajo, pero el heredoc no
|
|
176
|
+
// era la causa —sólo hacía que la lectura frenara en un token que sobrevive—: sin él la lista cruzaba
|
|
177
|
+
// igual y el último token era la marca de lo entrecomillado, que el filtro final descarta. O sea que
|
|
178
|
+
// pasaba de casualidad, y aserciar que pasa no fijaba nada.
|
|
153
179
|
const REDIRECT = /(?:^|[\s(])&?\d*>>?\s*(?![&(])([^\s;|&<>()]+)/g
|
|
154
|
-
const EVERY_ARG = /(?:^|[\s;|&(])(tee|truncate)\s+([^;|&<>()]+)/g
|
|
155
|
-
const LAST_ARG = /(?:^|[\s;|&(])(cp|mv|install|rsync)\s+([^;|&<>()]+)/g
|
|
156
|
-
const SED = /(?:^|[\s;|&(])sed\s+([^;|&<>()]+)/g
|
|
180
|
+
const EVERY_ARG = /(?:^|[\s;|&(])(tee|truncate)\s+([^;|&<>()\n]+)/g
|
|
181
|
+
const LAST_ARG = /(?:^|[\s;|&(])(cp|mv|install|rsync)\s+([^;|&<>()\n]+)/g
|
|
182
|
+
const SED = /(?:^|[\s;|&(])sed\s+([^;|&<>()\n]+)/g
|
|
157
183
|
const IN_PLACE = /(?:^|\s)-{1,2}i/
|
|
158
184
|
|
|
159
185
|
// Los argumentos que no son flags. El valor de un flag se cuela —`truncate -s 0 log` trae el `0`— y no
|
|
@@ -161,10 +187,6 @@ const IN_PLACE = /(?:^|\s)-{1,2}i/
|
|
|
161
187
|
// decide un bloqueo. Filtrarlo sería una rama que ninguna prueba puede ver caer.
|
|
162
188
|
const positional = (text) => text.trim().split(/\s+/).filter((one) => one && !one.startsWith('-'))
|
|
163
189
|
|
|
164
|
-
// Vacía lo que va entre comillas, dejando una marca que ningún patrón confunde con una ruta ni con un
|
|
165
|
-
// comando. Lo usan dos guards por razones distintas, y cada uno explica la suya donde lo llama.
|
|
166
|
-
const unquoted = (command) => String(command).replace(/'[^']*'|"[^"]*"/g, '\u0000')
|
|
167
|
-
|
|
168
190
|
// Un `>` adentro de una cadena no redirige nada. Pierde el destino entrecomillado, que es un falso
|
|
169
191
|
// negativo — el error barato en un guard que ya es incompleto, porque el caro es frenar un comando
|
|
170
192
|
// legítimo y que alguien apague el guard entero.
|
|
@@ -231,7 +253,8 @@ function governance(input) {
|
|
|
231
253
|
if (governed.length) {
|
|
232
254
|
const files = governed.map((file) => ` - ${file}`).join('\n')
|
|
233
255
|
block(`El commit toca gobernanza protegida:\n${files}\n` +
|
|
234
|
-
'Usa OPS_GOVERNANCE_OVERRIDE=1 solo con aprobación
|
|
256
|
+
'Usa OPS_GOVERNANCE_OVERRIDE=1 solo con aprobación, en el entorno del guard: escrita delante '
|
|
257
|
+
+ 'del comando no llega hasta acá.')
|
|
235
258
|
}
|
|
236
259
|
}
|
|
237
260
|
|