@ingeniomaps/cauce 0.39.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 CHANGED
@@ -14,6 +14,20 @@ 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
+
17
31
  ## [0.39.0] - 2026-08-19
18
32
 
19
33
  ### Corregido
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",