@ingeniomaps/cauce 0.5.0 → 0.5.1
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 +20 -0
- package/automatization/runners/antigravity/rules/cauce.md +2 -2
- package/automatization/runners/claude/CLAUDE.md +4 -4
- package/automatization/runners/claude/settings.json +3 -3
- package/automatization/runners/codex/AGENTS.md +47 -0
- package/automatization/runners/codex/hooks.json +3 -3
- package/automatization/runners/codex/manifest.json +6 -1
- package/automatization/runners/gemini/GEMINI.md +6 -6
- package/automatization/workflows/autobuild.js +4 -1
- package/automatization/workflows/integrations/promote.js +4 -1
- package/automatization/workflows/integrations/sync.js +4 -1
- package/automatization/workflows/team.js +4 -1
- package/engine/automation/index.js +26 -14
- package/engine/cli/ops.js +10 -4
- package/engine/core/manifest.js +6 -4
- package/package.json +1 -1
- package/template/tools/ops.js +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ esa operación sea confiable en vez de sólo cómoda: acá se lee qué cambió a
|
|
|
8
8
|
un cambio en el protocolo, en las reglas del sistema o en un guard es visible para el usuario y sube
|
|
9
9
|
minor aunque no toque una sola línea de código.
|
|
10
10
|
|
|
11
|
+
## [0.5.1] - 2026-08-15
|
|
12
|
+
|
|
13
|
+
### Agregado
|
|
14
|
+
|
|
15
|
+
- **Codex recibe su `AGENTS.md`.** Era el único runner sin archivo de instrucciones: se llevaba los
|
|
16
|
+
guards y nada más, así que podía ser detenido pero no sabía que existía un protocolo, un catálogo de
|
|
17
|
+
cargos ni equipos. No existe un `CODEX.md`: `AGENTS.md` es el nombre que Codex lee, compartido entre
|
|
18
|
+
herramientas, y por eso en modo embedded no se instala —el de la empresa ya está ahí y manda—.
|
|
19
|
+
|
|
20
|
+
### Corregido
|
|
21
|
+
|
|
22
|
+
- **Las rutas de los adaptadores se resuelven contra la carpeta donde se abre la herramienta.** Al
|
|
23
|
+
mover la instalación a la raíz de la compañía quedaron apuntando al lugar equivocado: `@AGENTS.md`
|
|
24
|
+
y `@planning/PROTOCOL.md` no resolvían, y los workflows buscaban `planning/` donde no estaba. Ahora
|
|
25
|
+
cada fuente marca el lugar con `{{OPS_DIR}}` y `install` lo completa; `doctor` compara contra lo
|
|
26
|
+
mismo que `install` escribe.
|
|
27
|
+
- `tools/ops.js` exporta la raíz ops, que ya calculaba y no usaba. Invocado desde la carpeta de la
|
|
28
|
+
compañía —`node <empresa>-ops/tools/ops.js team list`—, `agents list` y `team list` resolvían contra
|
|
29
|
+
el cwd y devolvían **vacío en vez de fallar**. `team` además no aceptaba una raíz de ningún modo.
|
|
30
|
+
|
|
11
31
|
## [0.5.0] - 2026-08-15
|
|
12
32
|
|
|
13
33
|
### Cambiado
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cauce
|
|
2
2
|
|
|
3
|
-
Lee y cumple `AGENTS.md` y `planning/PROTOCOL.md` antes de ejecutar trabajo. `planning/WIP.md` es el mutex de
|
|
4
|
-
ejecución y `planning/AWAITING_REVIEW.md` bloquea una corrida nueva. No promociones ideas desde INBOX, no
|
|
3
|
+
Lee y cumple `AGENTS.md` y `{{OPS_DIR}}planning/PROTOCOL.md` antes de ejecutar trabajo. `{{OPS_DIR}}planning/WIP.md` es el mutex de
|
|
4
|
+
ejecución y `{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva. No promociones ideas desde INBOX, no
|
|
5
5
|
inventes aprobaciones o credenciales y no hagas push ni deploy. Cierra cada tarea con verificación real y
|
|
6
6
|
evidencia en DONE.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Cauce para Claude Code
|
|
2
2
|
|
|
3
|
-
@AGENTS.md
|
|
4
|
-
@planning/PROTOCOL.md
|
|
3
|
+
@{{OPS_DIR}}AGENTS.md
|
|
4
|
+
@{{OPS_DIR}}planning/PROTOCOL.md
|
|
5
5
|
|
|
6
6
|
Los hooks de `.claude/settings.json` son obligatorios. Usa `/team` para evaluar si una intención es viable
|
|
7
7
|
y proponer una épica, y `/autobuild` para ejecutar trabajo ya promovido; `/integration-sync` e
|
|
8
8
|
`/integration-promote` gestionan staging local sin escritura remota. Ninguno promueve al BACKLOG.
|
|
9
9
|
|
|
10
|
-
Antes de iniciar, respeta `planning/AWAITING_REVIEW.md` y el mutex de `planning/WIP.md`. Si el protocolo y
|
|
11
|
-
un workflow difieren, manda el protocolo y la diferencia se registra en `planning/INBOX.md`.
|
|
10
|
+
Antes de iniciar, respeta `{{OPS_DIR}}planning/AWAITING_REVIEW.md` y el mutex de `{{OPS_DIR}}planning/WIP.md`. Si el protocolo y
|
|
11
|
+
un workflow difieren, manda el protocolo y la diferencia se registra en `{{OPS_DIR}}planning/INBOX.md`.
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
{
|
|
5
5
|
"matcher": "Bash",
|
|
6
6
|
"hooks": [
|
|
7
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/automatization/hooks/guard-shell.sh" }
|
|
7
|
+
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/{{OPS_DIR}}automatization/hooks/guard-shell.sh" }
|
|
8
8
|
]
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"matcher": "Edit|Write",
|
|
12
12
|
"hooks": [
|
|
13
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/automatization/hooks/guard-files.sh" }
|
|
13
|
+
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/{{OPS_DIR}}automatization/hooks/guard-files.sh" }
|
|
14
14
|
]
|
|
15
15
|
}
|
|
16
16
|
],
|
|
17
17
|
"Stop": [
|
|
18
18
|
{
|
|
19
19
|
"hooks": [
|
|
20
|
-
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/automatization/hooks/guard-planning-drift.sh" }
|
|
20
|
+
{ "type": "command", "command": "$CLAUDE_PROJECT_DIR/{{OPS_DIR}}automatization/hooks/guard-planning-drift.sh" }
|
|
21
21
|
]
|
|
22
22
|
}
|
|
23
23
|
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Cauce para Codex
|
|
2
|
+
|
|
3
|
+
`{{OPS_DIR}}AGENTS.md` tiene las reglas del sistema y `{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de
|
|
4
|
+
verdad del proceso. Leelos antes de trabajar: acá sólo está lo específico de este runner.
|
|
5
|
+
|
|
6
|
+
> Codex lee el `AGENTS.md` de la raíz, que es un nombre compartido entre herramientas. Cuando el repo
|
|
7
|
+
> ops **es** la raíz, este archivo no se instala: el `AGENTS.md` de la empresa ya está ahí y manda.
|
|
8
|
+
|
|
9
|
+
Los hooks de `.codex/hooks/hooks.json` son obligatorios y bloquean por su cuenta. Codex no ejecuta los
|
|
10
|
+
workflows JS de Claude —son referencia, no un runtime compatible—, así que el recorrido se hace fase por
|
|
11
|
+
fase siguiendo el protocolo.
|
|
12
|
+
|
|
13
|
+
`{{OPS_DIR}}planning/WIP.md` es el mutex: una sola tarea activa. `{{OPS_DIR}}planning/AWAITING_REVIEW.md`
|
|
14
|
+
bloquea una corrida nueva hasta que un humano revise. Nada se promueve desde `INBOX.md` sin aprobación.
|
|
15
|
+
|
|
16
|
+
Antes de cerrar, corré `node {{OPS_DIR}}tools/ops.js check {{OPS_DIR}}planning`.
|
|
17
|
+
|
|
18
|
+
## Los cargos
|
|
19
|
+
|
|
20
|
+
El catálogo está en `{{OPS_DIR}}node_modules/@ingeniomaps/cauce/agents/` —o en
|
|
21
|
+
`{{OPS_DIR}}.ops/agents/` si este repo no usa npm— y los propios de la empresa en
|
|
22
|
+
`{{OPS_DIR}}agents/`, que mandan sobre los del sistema con el mismo nombre.
|
|
23
|
+
|
|
24
|
+
Cada cargo tiene un `SKILL.md` con su contrato: cuándo actúa, qué decide, qué no le corresponde y cuál es
|
|
25
|
+
su entrega mínima. Para ver la lista con una línea por cargo:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node {{OPS_DIR}}tools/ops.js agents list
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Leé el `SKILL.md` del cargo que corresponda antes de actuar en su terreno, y respetá sus límites. Lo que
|
|
32
|
+
ese cargo debe saber de esta empresa está en `{{OPS_DIR}}organization/roles/<slug>.md`.
|
|
33
|
+
|
|
34
|
+
## Los equipos
|
|
35
|
+
|
|
36
|
+
Un equipo es una secuencia de cargos con etapas y exit gates, para evaluar una intención antes de que
|
|
37
|
+
exista una épica. Están en `{{OPS_DIR}}node_modules/@ingeniomaps/cauce/teams/system/` y los propios en
|
|
38
|
+
`{{OPS_DIR}}teams/`.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node {{OPS_DIR}}tools/ops.js team list
|
|
42
|
+
node {{OPS_DIR}}tools/ops.js team show <slug>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Ningún equipo promueve trabajo al BACKLOG: escribe la épica o el informe y para.
|
|
46
|
+
|
|
47
|
+
Nunca omitas aprobaciones, inventes credenciales, escribas en un sistema remoto, hagas push o deploy.
|
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
{
|
|
5
5
|
"matcher": "shell|unified_exec",
|
|
6
6
|
"hooks": [
|
|
7
|
-
{ "type": "command", "command": "automatization/hooks/guard-shell.sh" }
|
|
7
|
+
{ "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-shell.sh" }
|
|
8
8
|
]
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"matcher": "apply_patch|files",
|
|
12
12
|
"hooks": [
|
|
13
|
-
{ "type": "command", "command": "automatization/hooks/guard-files.sh" }
|
|
13
|
+
{ "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-files.sh" }
|
|
14
14
|
]
|
|
15
15
|
}
|
|
16
16
|
],
|
|
17
17
|
"SessionEnd": [
|
|
18
18
|
{
|
|
19
19
|
"hooks": [
|
|
20
|
-
{ "type": "command", "command": "automatization/hooks/guard-planning-drift.sh" }
|
|
20
|
+
{ "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-planning-drift.sh" }
|
|
21
21
|
]
|
|
22
22
|
}
|
|
23
23
|
]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Cauce para Gemini CLI
|
|
2
2
|
|
|
3
|
-
@AGENTS.md
|
|
4
|
-
@planning/PROTOCOL.md
|
|
3
|
+
@{{OPS_DIR}}AGENTS.md
|
|
4
|
+
@{{OPS_DIR}}planning/PROTOCOL.md
|
|
5
5
|
|
|
6
|
-
`planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/ops:autobuild` fase por fase; los workflows JS de
|
|
7
|
-
Claude son referencia, no un runtime compatible. `planning/WIP.md` es el mutex y
|
|
8
|
-
`planning/AWAITING_REVIEW.md` bloquea una corrida nueva.
|
|
6
|
+
`{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/ops:autobuild` fase por fase; los workflows JS de
|
|
7
|
+
Claude son referencia, no un runtime compatible. `{{OPS_DIR}}planning/WIP.md` es el mutex y
|
|
8
|
+
`{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva.
|
|
9
9
|
|
|
10
10
|
Gemini no tiene guards nativos configurados por este toolkit. Antes de cada commit ejecuta los wrappers de
|
|
11
|
-
`automatization/hooks/` como prechecks y al cerrar ejecuta `node tools/ops.js check planning`. Nunca omitas
|
|
11
|
+
`{{OPS_DIR}}automatization/hooks/` como prechecks y al cerrar ejecuta `node {{OPS_DIR}}tools/ops.js check planning`. Nunca omitas
|
|
12
12
|
aprobaciones, inventes credenciales, escribas remoto, hagas push/deploy o promociones trabajo desde INBOX.
|
|
@@ -10,7 +10,10 @@ export const meta = {
|
|
|
10
10
|
].map((title) => ({ title, detail: `Fase ${title} del protocolo agnóstico` })),
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// El prefijo lo completa `automation install`: en modo sidecar la herramienta se abre en la carpeta
|
|
14
|
+
// de la compañía y la raíz ops es uno de sus hijos, no la carpeta misma.
|
|
15
|
+
const ROOT = (process.env.OPS_ROOT
|
|
16
|
+
|| `${process.env.CLAUDE_PROJECT_DIR || '.'}/{{OPS_DIR}}`).replace(/\/+$/, '')
|
|
14
17
|
const CONFIG = `${ROOT}/ops.config.json`
|
|
15
18
|
const P = `${ROOT}/planning`
|
|
16
19
|
const BACKLOG = `${P}/BACKLOG.md`
|
|
@@ -10,7 +10,10 @@ export const meta = {
|
|
|
10
10
|
],
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// El prefijo lo completa `automation install`: en modo sidecar la herramienta se abre en la carpeta
|
|
14
|
+
// de la compañía y la raíz ops es uno de sus hijos, no la carpeta misma.
|
|
15
|
+
const ROOT = (process.env.OPS_ROOT
|
|
16
|
+
|| `${process.env.CLAUDE_PROJECT_DIR || '.'}/{{OPS_DIR}}`).replace(/\/+$/, '')
|
|
14
17
|
const PROVIDER = process.env.OPS_INTEGRATION_PROVIDER || ''
|
|
15
18
|
const KEY = process.env.OPS_INTEGRATION_KEY || ''
|
|
16
19
|
const RESULT = {
|
|
@@ -10,7 +10,10 @@ export const meta = {
|
|
|
10
10
|
],
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// El prefijo lo completa `automation install`: en modo sidecar la herramienta se abre en la carpeta
|
|
14
|
+
// de la compañía y la raíz ops es uno de sus hijos, no la carpeta misma.
|
|
15
|
+
const ROOT = (process.env.OPS_ROOT
|
|
16
|
+
|| `${process.env.CLAUDE_PROJECT_DIR || '.'}/{{OPS_DIR}}`).replace(/\/+$/, '')
|
|
14
17
|
const REQUESTED = process.env.OPS_INTEGRATION_PROVIDER || ''
|
|
15
18
|
const RESULT = {
|
|
16
19
|
type: 'object', additionalProperties: false, required: ['passed', 'provider', 'details'],
|
|
@@ -15,7 +15,10 @@ export const meta = {
|
|
|
15
15
|
],
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
// El prefijo lo completa `automation install`: en modo sidecar la herramienta se abre en la carpeta
|
|
19
|
+
// de la compañía y la raíz ops es uno de sus hijos, no la carpeta misma.
|
|
20
|
+
const ROOT = (process.env.OPS_ROOT
|
|
21
|
+
|| `${process.env.CLAUDE_PROJECT_DIR || '.'}/{{OPS_DIR}}`).replace(/\/+$/, '')
|
|
19
22
|
const P = `${ROOT}/planning`
|
|
20
23
|
const ROADMAP = `${P}/roadmap`
|
|
21
24
|
const HUMAN = `${P}/HUMAN_ACTIONS.md`
|
|
@@ -122,13 +122,20 @@ function resolveItem(paths, root, name, item) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
125
|
+
// Todo lo que un adaptador copia —configuración, instrucciones, workflows— nombra rutas relativas a
|
|
126
|
+
// la carpeta donde se abre la herramienta. Cuando la raíz ops no es esa carpeta, cada una necesita el
|
|
127
|
+
// prefijo. El marcador es explícito en la fuente en vez de adivinarse con reemplazos de texto:
|
|
128
|
+
// `{{OPS_DIR}}` significa "acá va la raíz ops, o nada si coinciden".
|
|
129
|
+
//
|
|
130
|
+
// Un solo render, y `install` escribe exactamente lo que `doctor` compara.
|
|
131
|
+
const OPS_DIR = '{{OPS_DIR}}'
|
|
132
|
+
|
|
133
|
+
function render(file, prefix) {
|
|
134
|
+
return fs.readFileSync(file, 'utf8').split(OPS_DIR).join(prefix)
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
function runnerConfig(paths, root) {
|
|
129
|
-
|
|
130
|
-
const prefix = opsPrefix(root)
|
|
131
|
-
return JSON.parse(prefix ? raw.split('automatization/hooks/').join(`${prefix}automatization/hooks/`) : raw)
|
|
138
|
+
return JSON.parse(render(paths.configSource, opsPrefix(root)))
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
// Cargos del catálogo, con el frontmatter que el runner indexa para elegir a quién invocar.
|
|
@@ -368,11 +375,15 @@ function doctor(root, name, output = console) {
|
|
|
368
375
|
}
|
|
369
376
|
for (const item of runner.instructions || []) {
|
|
370
377
|
const resolved = { item, ...resolveItem(paths, root, name, item) }
|
|
378
|
+
// `AGENTS.md` es un nombre compartido entre herramientas y la instancia ya tiene el suyo: en
|
|
379
|
+
// modo embedded el archivo del runner y el de la empresa son el mismo, y pedirle que se
|
|
380
|
+
// referencie a sí mismo no significa nada.
|
|
381
|
+
const propio = path.basename(resolved.target) === 'AGENTS.md'
|
|
371
382
|
if (!fs.existsSync(resolved.target)) errors.push(`falta ${item.target}`)
|
|
372
|
-
else if (!fs.readFileSync(resolved.target, 'utf8').includes('AGENTS.md')) {
|
|
383
|
+
else if (!propio && !fs.readFileSync(resolved.target, 'utf8').includes('AGENTS.md')) {
|
|
373
384
|
warnings.push(`${item.target}: no referencia AGENTS.md; verifica las reglas globales`)
|
|
374
385
|
}
|
|
375
|
-
if (deliveryState(M.readRunners(root), name, resolved) === 'desactualizado') {
|
|
386
|
+
if (deliveryState(M.readRunners(root), name, resolved, opsPrefix(root)) === 'desactualizado') {
|
|
376
387
|
warnings.push(`${item.target}: Cauce trae una versión más nueva y vos no lo tocaste; reinstalá`)
|
|
377
388
|
}
|
|
378
389
|
}
|
|
@@ -381,7 +392,7 @@ function doctor(root, name, output = console) {
|
|
|
381
392
|
const recorded = M.readRunners(root)
|
|
382
393
|
for (const item of runner.artifacts || []) {
|
|
383
394
|
const resolved = { item, ...resolveItem(paths, root, name, item) }
|
|
384
|
-
const situacion = deliveryState(recorded, name, resolved)
|
|
395
|
+
const situacion = deliveryState(recorded, name, resolved, opsPrefix(root))
|
|
385
396
|
if (situacion === 'nuevo') errors.push(`falta ${item.target}`)
|
|
386
397
|
else if (situacion === 'desactualizado') {
|
|
387
398
|
warnings.push(`${item.target}: hay una versión más nueva en Cauce; reinstalá el adaptador`)
|
|
@@ -432,10 +443,10 @@ function deliveryKey(name, target) {
|
|
|
432
443
|
//
|
|
433
444
|
// Sin el registro de entrega, `desactualizado` y `ajeno` se ven igual. `install` resolvía esa duda
|
|
434
445
|
// conservando siempre, así que ninguna mejora del toolkit llegaba nunca a un runner ya instalado.
|
|
435
|
-
function deliveryState(recorded, name, resolved) {
|
|
446
|
+
function deliveryState(recorded, name, resolved, prefix = '') {
|
|
436
447
|
if (!fs.existsSync(resolved.target)) return 'nuevo'
|
|
437
448
|
const current = M.digest(resolved.target)
|
|
438
|
-
if (current === M.
|
|
449
|
+
if (current === M.digestText(render(resolved.source, prefix))) return 'al día'
|
|
439
450
|
const delivered = recorded[deliveryKey(name, resolved.item.target)]
|
|
440
451
|
return delivered && delivered === current ? 'desactualizado' : 'ajeno'
|
|
441
452
|
}
|
|
@@ -458,7 +469,8 @@ function install(root, name, output = console, options = {}) {
|
|
|
458
469
|
for (const resolved of resolvedItems) F.assertNoSymlinkPath(paths.install, resolved.target)
|
|
459
470
|
|
|
460
471
|
const recorded = M.readRunners(root)
|
|
461
|
-
const
|
|
472
|
+
const prefix = opsPrefix(root)
|
|
473
|
+
const state = new Map(resolvedItems.map((r) => [r, deliveryState(recorded, name, r, prefix)]))
|
|
462
474
|
// Un archivo de instrucciones es del proyecto y se conserva; uno ejecutable es del toolkit y se
|
|
463
475
|
// reemplaza. Editarlo detiene la instalación antes de pisarlo, igual que hace `upgrade`.
|
|
464
476
|
const editados = resolvedItems.filter((resolved) => {
|
|
@@ -480,7 +492,7 @@ function install(root, name, output = console, options = {}) {
|
|
|
480
492
|
// Dónde aterrizó, no sólo qué archivo: en sidecar el destino no es el repo desde el que se corrió
|
|
481
493
|
// el comando, y descubrirlo por sorpresa es la diferencia entre confiar y adivinar.
|
|
482
494
|
if (paths.install !== root) {
|
|
483
|
-
output.log(` ${name}: el runner se abre en ${paths.install} — ahí
|
|
495
|
+
output.log(` ${name}: el runner se abre en ${paths.install} — ahí queda su configuración`)
|
|
484
496
|
}
|
|
485
497
|
output.log(`✓ ${name}: configuración instalada en ${runner.config.target}`)
|
|
486
498
|
const entregado = { ...recorded }
|
|
@@ -493,7 +505,7 @@ function install(root, name, output = console, options = {}) {
|
|
|
493
505
|
output.log(`= ${name}: ${resolved.item.target} ya está al día`)
|
|
494
506
|
} else {
|
|
495
507
|
fs.mkdirSync(path.dirname(resolved.target), { recursive: true })
|
|
496
|
-
|
|
508
|
+
F.atomicWrite(resolved.target, render(resolved.source, opsPrefix(root)))
|
|
497
509
|
const verbo = situacion === 'nuevo' ? 'instalado' : 'actualizado'
|
|
498
510
|
output.log(`✓ ${name}: ${verbo} ${resolved.item.target}`)
|
|
499
511
|
}
|
package/engine/cli/ops.js
CHANGED
|
@@ -594,9 +594,15 @@ function upgrade(dir) {
|
|
|
594
594
|
|
|
595
595
|
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
596
596
|
// reimplemente el recorrido del catálogo.
|
|
597
|
+
// La raíz ops de un comando que no la recibe. El shim `tools/ops.js` la exporta porque sabe dónde
|
|
598
|
+
// vive: sin eso, invocarlo desde otra carpeta —lo normal en sidecar— la resolvía contra el cwd.
|
|
599
|
+
function opsRoot(dir) {
|
|
600
|
+
return path.resolve(dir || process.env.OPS_ROOT || '.')
|
|
601
|
+
}
|
|
602
|
+
|
|
597
603
|
function agents(action, dir) {
|
|
598
604
|
if (action !== 'list') fail(`Acción de agents desconocida: ${action || '(vacía)'}`, 2)
|
|
599
|
-
const root =
|
|
605
|
+
const root = opsRoot(dir)
|
|
600
606
|
const roles = AG.list(root)
|
|
601
607
|
if (process.argv.includes('--json')) {
|
|
602
608
|
// `path` viene resuelto: quien consuma esto no debería reconstruir dónde ganó la precedencia.
|
|
@@ -685,7 +691,7 @@ async function integration(action, rootArg, provider, key) {
|
|
|
685
691
|
}
|
|
686
692
|
|
|
687
693
|
function automation(action, rootArg, runnerName) {
|
|
688
|
-
const root =
|
|
694
|
+
const root = opsRoot(rootArg)
|
|
689
695
|
if (action === 'list-hooks') return A.listHooks()
|
|
690
696
|
if (action === 'list') {
|
|
691
697
|
for (const name of A.RUNNER_NAMES) {
|
|
@@ -756,12 +762,12 @@ function evaluate(agent) {
|
|
|
756
762
|
|
|
757
763
|
function team(action, slug) {
|
|
758
764
|
if (action === 'list') {
|
|
759
|
-
for (const name of T.list(
|
|
765
|
+
for (const name of T.list(opsRoot())) console.log(name)
|
|
760
766
|
return
|
|
761
767
|
}
|
|
762
768
|
if (!['check', 'show'].includes(action)) fail(`Acción de team desconocida: ${action || '(vacía)'}`, 2)
|
|
763
769
|
try {
|
|
764
|
-
const result = T.validate(
|
|
770
|
+
const result = T.validate(opsRoot(), slug)
|
|
765
771
|
for (const error of result.errors) console.error(`✗ ${error}`)
|
|
766
772
|
if (result.errors.length) fail(`${slug}: ${result.errors.length} error(es)`, 1)
|
|
767
773
|
if (action === 'show') {
|
package/engine/core/manifest.js
CHANGED
|
@@ -13,10 +13,12 @@ const path = require('node:path')
|
|
|
13
13
|
|
|
14
14
|
const FILE = path.join('.cauce', 'manifest.json')
|
|
15
15
|
|
|
16
|
+
function digestText(content) {
|
|
17
|
+
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16)
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
function digest(file) {
|
|
17
|
-
try {
|
|
18
|
-
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex').slice(0, 16)
|
|
19
|
-
} catch { return '' }
|
|
21
|
+
try { return digestText(fs.readFileSync(file)) } catch { return '' }
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// Dos secciones porque son dos entregas distintas: `files` es lo que se materializó dentro de la
|
|
@@ -80,4 +82,4 @@ function edited(root, relative, files) {
|
|
|
80
82
|
})
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
module.exports = { FILE, digest, edited, prune, read, readRunners, record, write }
|
|
85
|
+
module.exports = { FILE, digest, digestText, edited, prune, read, readRunners, record, write }
|
package/package.json
CHANGED
package/template/tools/ops.js
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
const path = require('path')
|
|
9
9
|
|
|
10
10
|
const root = path.join(__dirname, '..')
|
|
11
|
+
|
|
12
|
+
// El shim sabe dónde vive; quien lo invoca, no. En modo sidecar se lo llama desde la carpeta de la
|
|
13
|
+
// compañía —`node <empresa>-ops/tools/ops.js …`— y sin esto cada comando resolvería su raíz contra
|
|
14
|
+
// el cwd: `agents list` y `team list` devolvían vacío en vez de fallar.
|
|
15
|
+
process.env.OPS_ROOT = process.env.OPS_ROOT || root
|
|
16
|
+
|
|
11
17
|
const candidates = [
|
|
12
18
|
() => require.resolve('@ingeniomaps/cauce/engine/cli/ops.js', { paths: [root] }),
|
|
13
19
|
() => require.resolve(path.join(root, '.ops', 'engine', 'cli', 'ops.js')),
|