@ingeniomaps/cauce 0.38.0 → 0.40.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 +26 -0
- package/engine/automation/index.js +69 -0
- package/engine/cli/ops.js +26 -0
- package/package.json +1 -1
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.40.0] - 2026-08-19
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **En modo embebido, Codex se quedaba sin instrucciones.** `AGENTS.md` es el único nombre que el runner
|
|
22
|
+
y la instancia comparten, y ahí `install` conservaba el archivo entero —lo correcto para algo del
|
|
23
|
+
proyecto—, así que el recorrido que ese runner tenía que seguir nunca llegaba a estar escrito. Ahora su
|
|
24
|
+
contenido se fusiona adentro, entre marcas: reinstalar reemplaza el bloque en vez de duplicarlo,
|
|
25
|
+
desinstalar lo saca sin llevarse el archivo, y todo lo que la empresa escribió alrededor queda intacto.
|
|
26
|
+
|
|
27
|
+
`doctor` lo tenía al revés en ese archivo: comparando el hash completo avisaba cuando el bloque estaba
|
|
28
|
+
bien —el texto de la empresa alrededor difiere siempre— y decía «operativo» cuando alguien lo había
|
|
29
|
+
borrado, que es la falla que importa.
|
|
30
|
+
|
|
31
|
+
## [0.39.0] - 2026-08-19
|
|
32
|
+
|
|
33
|
+
### Corregido
|
|
34
|
+
|
|
35
|
+
- **`check` nombra las credenciales que nadie se llevó.** El arranque tiene que dejar una fila por cada
|
|
36
|
+
variable que el proyecto declara, y en cuatro corridas reales se cumplió en proporción a lo que se
|
|
37
|
+
habló: las que sólo estaban en el inventario quedaron afuera —entre ellas el broker por donde entran
|
|
38
|
+
los datos y el servicio al que se le mandan errores—. Ahora `check` nombra las que no aparecen ni en el
|
|
39
|
+
mapa ni en `HUMAN_ACTIONS.md`. Mira sólo cuando la instancia ya tiene contexto escrito, porque antes no
|
|
40
|
+
hay dónde tendrían que estar, y avisa en vez de fallar: una variable sin dueño no rompe nada hoy, rompe
|
|
41
|
+
el día que alguien tiene que desplegar.
|
|
42
|
+
|
|
17
43
|
## [0.38.0] - 2026-08-19
|
|
18
44
|
|
|
19
45
|
### Añadido
|
|
@@ -438,6 +438,19 @@ function doctor(root, name, output = console) {
|
|
|
438
438
|
}
|
|
439
439
|
for (const item of runner.instructions || []) {
|
|
440
440
|
const resolved = { item, ...resolveItem(paths, root, name, item) }
|
|
441
|
+
// El archivo compartido no se compara entero: alrededor del bloque vive el texto de la empresa, así
|
|
442
|
+
// que su hash difiere siempre. Comparado como archivo, `doctor` avisaba cuando el bloque estaba bien
|
|
443
|
+
// y callaba cuando alguien lo había borrado, que es exactamente al revés.
|
|
444
|
+
if (esArchivoCompartido(root, resolved.target)) {
|
|
445
|
+
const contenido = render(resolved.source, opsPrefix(root))
|
|
446
|
+
if (!fs.existsSync(resolved.target)) errors.push(`falta ${item.target}`)
|
|
447
|
+
else if (!fs.readFileSync(resolved.target, 'utf8').includes(bloqueInicio(name))) {
|
|
448
|
+
errors.push(`${item.target}: no tiene las instrucciones de Cauce; reinstalá el adaptador`)
|
|
449
|
+
} else if (!bloqueAlDia(resolved.target, name, contenido)) {
|
|
450
|
+
warnings.push(`${item.target}: su bloque de Cauce quedó viejo; reinstalá el adaptador`)
|
|
451
|
+
}
|
|
452
|
+
continue
|
|
453
|
+
}
|
|
441
454
|
// `AGENTS.md` es un nombre compartido entre herramientas y la instancia ya tiene el suyo: en
|
|
442
455
|
// modo embedded el archivo del runner y el de la empresa son el mismo, y pedirle que se
|
|
443
456
|
// referencie a sí mismo no significa nada.
|
|
@@ -585,6 +598,16 @@ function uninstall(root, name, output = console) {
|
|
|
585
598
|
const resolved = { item, ...resolveItem(paths, root, name, item) }
|
|
586
599
|
const key = deliveryKey(name, item.target)
|
|
587
600
|
if (!fs.existsSync(resolved.target)) { delete entregado[key]; continue }
|
|
601
|
+
if (esArchivoCompartido(root, resolved.target)) {
|
|
602
|
+
const texto = fs.readFileSync(resolved.target, 'utf8')
|
|
603
|
+
const limpio = sinBloque(texto, name)
|
|
604
|
+
if (limpio !== texto.trimEnd()) {
|
|
605
|
+
F.atomicWrite(resolved.target, `${limpio}\n`)
|
|
606
|
+
quitados += 1
|
|
607
|
+
}
|
|
608
|
+
delete entregado[key]
|
|
609
|
+
continue
|
|
610
|
+
}
|
|
588
611
|
const situacion = deliveryState(recorded, name, resolved, prefix)
|
|
589
612
|
if (situacion === 'ajeno') { conservados.push(item.target); continue }
|
|
590
613
|
removeFile(resolved.target, paths.install)
|
|
@@ -623,6 +646,41 @@ function uninstall(root, name, output = console) {
|
|
|
623
646
|
return { removed: quitados, kept: conservados }
|
|
624
647
|
}
|
|
625
648
|
|
|
649
|
+
// `AGENTS.md` es el único nombre que el runner y la instancia comparten: en modo embebido el archivo de
|
|
650
|
+
// instrucciones de Codex es el mismo que el de la empresa. Conservarlo entero —lo correcto para un
|
|
651
|
+
// archivo del proyecto— dejaba a ese runner sin una sola línea de Cauce, así que su contenido se
|
|
652
|
+
// fusiona adentro, entre marcas, y todo lo demás del archivo queda intacto.
|
|
653
|
+
const bloqueInicio = (name) => `<!-- cauce:${name} inicio — lo reescribe "automation install", no editar -->`
|
|
654
|
+
const bloqueFin = (name) => `<!-- cauce:${name} fin -->`
|
|
655
|
+
|
|
656
|
+
function esArchivoCompartido(root, target) {
|
|
657
|
+
return path.resolve(target) === path.resolve(root, 'AGENTS.md')
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function sinBloque(text, name) {
|
|
661
|
+
const desde = text.indexOf(bloqueInicio(name))
|
|
662
|
+
if (desde === -1) return text
|
|
663
|
+
const hasta = text.indexOf(bloqueFin(name), desde)
|
|
664
|
+
if (hasta === -1) return text
|
|
665
|
+
return `${text.slice(0, desde)}${text.slice(hasta + bloqueFin(name).length)}`.trimEnd()
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function fusionarInstruccion(file, name, contenido) {
|
|
669
|
+
const actual = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
|
|
670
|
+
const cuerpo = sinBloque(actual, name).trimEnd()
|
|
671
|
+
const bloque = `${bloqueInicio(name)}\n\n${contenido.trim()}\n\n${bloqueFin(name)}\n`
|
|
672
|
+
F.atomicWrite(file, cuerpo ? `${cuerpo}\n\n${bloque}` : bloque)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function bloqueAlDia(file, name, contenido) {
|
|
676
|
+
if (!fs.existsSync(file)) return false
|
|
677
|
+
const texto = fs.readFileSync(file, 'utf8')
|
|
678
|
+
const desde = texto.indexOf(bloqueInicio(name))
|
|
679
|
+
const hasta = texto.indexOf(bloqueFin(name))
|
|
680
|
+
if (desde === -1 || hasta === -1) return false
|
|
681
|
+
return texto.slice(desde + bloqueInicio(name).length, hasta).trim() === contenido.trim()
|
|
682
|
+
}
|
|
683
|
+
|
|
626
684
|
function install(root, name, output = console, options = {}) {
|
|
627
685
|
// `install` arma la superficie de consumo de una empresa: punteros a cada cargo, una copia de los
|
|
628
686
|
// workflows y los guards. Acá los cargos y los workflows son el producto —la copia divergiría— y
|
|
@@ -677,6 +735,17 @@ function install(root, name, output = console, options = {}) {
|
|
|
677
735
|
for (const resolved of resolvedItems) {
|
|
678
736
|
const situacion = state.get(resolved)
|
|
679
737
|
const propio = runner.instructions.includes(resolved.item)
|
|
738
|
+
if (propio && esArchivoCompartido(root, resolved.target)) {
|
|
739
|
+
const contenido = render(resolved.source, opsPrefix(root))
|
|
740
|
+
if (bloqueAlDia(resolved.target, name, contenido)) {
|
|
741
|
+
output.log(`= ${name}: ${resolved.item.target} ya trae sus instrucciones`)
|
|
742
|
+
} else {
|
|
743
|
+
fusionarInstruccion(resolved.target, name, contenido)
|
|
744
|
+
output.log(`✓ ${name}: sus instrucciones quedaron dentro de ${resolved.item.target}`)
|
|
745
|
+
}
|
|
746
|
+
entregado[deliveryKey(name, resolved.item.target)] = M.digest(resolved.target)
|
|
747
|
+
continue
|
|
748
|
+
}
|
|
680
749
|
if (situacion === 'ajeno' && propio) {
|
|
681
750
|
output.log(`= ${name}: conservado ${resolved.item.target} (tiene cambios tuyos)`)
|
|
682
751
|
} else if (situacion === 'al día') {
|
package/engine/cli/ops.js
CHANGED
|
@@ -465,6 +465,31 @@ function seccionesPerdidas(root) {
|
|
|
465
465
|
return avisos
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
// Credenciales que el proyecto declara y que no aparecen en ningún contrato. El arranque tiene que
|
|
469
|
+
// dejar una fila por cada una —quién la carga y dónde— y en la práctica cubre las que se hablaron en la
|
|
470
|
+
// conversación: las que sólo estaban en el inventario se pierden, y con ellas el servicio externo que
|
|
471
|
+
// hay detrás. Una variable sin dueño no rompe nada hoy; rompe el día que alguien tiene que desplegar.
|
|
472
|
+
//
|
|
473
|
+
// Sólo cuando la instancia ya tiene contexto escrito: antes del arranque no hay dónde estuvieran.
|
|
474
|
+
function credencialesSinDueño(root) {
|
|
475
|
+
if (OB.guide(root).fresh) return []
|
|
476
|
+
const contratos = ['AGENTS.md', path.join('planning', 'HUMAN_ACTIONS.md')]
|
|
477
|
+
.map((file) => { try { return fs.readFileSync(path.join(root, file), 'utf8') } catch { return '' } })
|
|
478
|
+
.join('\n')
|
|
479
|
+
if (!contratos) return []
|
|
480
|
+
const huerfanas = []
|
|
481
|
+
for (const service of inventory(root)) {
|
|
482
|
+
for (const nombre of (service.env || {}).names || []) {
|
|
483
|
+
if (!contratos.includes(nombre)) huerfanas.push(`${nombre} (${service.path})`)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!huerfanas.length) return []
|
|
487
|
+
const lista = huerfanas.length > 4
|
|
488
|
+
? `${huerfanas.slice(0, 4).join(', ')} y ${huerfanas.length - 4} más`
|
|
489
|
+
: huerfanas.join(', ')
|
|
490
|
+
return [`el proyecto declara ${lista} y no aparecen en el mapa ni en HUMAN_ACTIONS: nadie las carga`]
|
|
491
|
+
}
|
|
492
|
+
|
|
468
493
|
function check(dir, cli) {
|
|
469
494
|
const root = path.resolve(dir || '.')
|
|
470
495
|
const errors = []
|
|
@@ -578,6 +603,7 @@ function check(dir, cli) {
|
|
|
578
603
|
for (const entry of FK.drift(path.resolve(root, '..'))) warnings.push(FK.driftLine(entry))
|
|
579
604
|
|
|
580
605
|
warnings.push(...seccionesPerdidas(path.resolve(root, '..')))
|
|
606
|
+
warnings.push(...credencialesSinDueño(path.resolve(root, '..')))
|
|
581
607
|
|
|
582
608
|
if (cli.has('--json')) {
|
|
583
609
|
console.log(JSON.stringify({
|