@ingeniomaps/cauce 0.30.0 → 0.31.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,38 @@ 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.31.0] - 2026-08-18
18
+
19
+ ### Añadido
20
+
21
+ - **`ops automation uninstall <ops-root> <runner>`: sacar el wiring sin llevarse lo tuyo.** Hasta ahora
22
+ desinstalar era borrar la carpeta ops y descubrir después que cada llamada de herramienta ejecuta un
23
+ guard que ya no existe; la otra salida —borrar `.claude/` entero— se lleva puesto lo que hayas puesto
24
+ vos. El comando quita lo que Cauce entregó **y sigue igual que como lo entregó**: sus guards de la
25
+ configuración del runner, sus workflows, sus punteros a cargos. Tus hooks, tus workflows y tus skills
26
+ quedan donde están, y un archivo del toolkit que hayas editado se conserva y aparece nombrado en la
27
+ salida, porque decidir sobre él es tuyo. La instancia no se toca: borrarla es una decisión aparte.
28
+ También como `make uninstall-claude` y sus equivalentes.
29
+
30
+ ### Corregido
31
+
32
+ - **Una instancia dejaba de enterarse de que había versiones nuevas.** `init` fija la versión exacta del
33
+ motor, así que `npm update` no la mueve y `upgrade` compara contra el que está instalado: quien
34
+ actualizaba con `make upgrade` recibía «la instancia está al día» en todas las versiones siguientes, sin
35
+ nada que le dijera que la comparación era local. Ahora la salida nombra contra qué comparó y da el
36
+ comando que trae un motor nuevo, y `make upgrade` lo corre antes de aplicar.
37
+
38
+ Actualizar son tres pasos y el tercero sigue siendo aparte: `npm install --save-dev
39
+ @ingeniomaps/cauce@latest`, `ops upgrade .` y `ops automation install . <runner>`. Los workflows y las
40
+ skills viven en el runner, no en la instancia, y `upgrade` lo recuerda al terminar.
41
+
42
+ ### Cambiado
43
+
44
+ - **`init` termina en un solo cierre, y sus opciones se leen como una selección.** Las tres líneas finales
45
+ —cada una con tono de última— pasaron a ser una con la acción concreta, y las opciones de runner e
46
+ integración van una por línea con el default marcado donde se mira: `5) ninguno ← Enter`, en vez de un
47
+ `[ninguno]` pegado al prompt.
48
+
17
49
  ## [0.30.0] - 2026-08-18
18
50
 
19
51
  ### Añadido
package/README.md CHANGED
@@ -31,12 +31,21 @@ instala la dependencia, deja el wiring del runner puesto y valida la instancia a
31
31
 
32
32
  ```text
33
33
  ¿Con qué runner vas a trabajar?
34
- 1) claude 2) codex 3) gemini 4) antigravity 5) ninguno
35
- [ninguno] > 1
34
+
35
+ 1) claude
36
+ 2) codex
37
+ 3) gemini
38
+ 4) antigravity
39
+ 5) ninguno ← Enter
40
+
41
+ > 1
36
42
 
37
43
  ¿Habilitar alguna integración?
38
- 1) jira 2) ninguna
39
- [ninguna] >
44
+
45
+ 1) jira
46
+ 2) ninguna ← Enter
47
+
48
+ >
40
49
 
41
50
  · npm install (el motor viene de la dependencia)
42
51
  ✓ claude: adaptador operativo (0 advertencia(s))
@@ -57,7 +66,8 @@ este proyecto, hasta cubrir lo que haga falta de esto:
57
66
 
58
67
  Mientras tanto, esto es lo que hay: apps/api, apps/web
59
68
 
60
- Abrí claude en este directorio para que las escriba por vos.
69
+ Abrí claude acá y contestale esa pregunta.
70
+ Con tus respuestas escribe organization/, el mapa real de AGENTS.md y la primera épica.
61
71
  El ciclo empieza en ops/planning/FLOW.md.
62
72
  ```
63
73
 
@@ -190,6 +200,7 @@ Lee [template/planning/PROTOCOL.md](template/planning/PROTOCOL.md) para el contr
190
200
  | `ops automation list-hooks <ops-root>` | Describe los guards portables disponibles. |
191
201
  | `ops automation check <ops-root>` | Valida guards, permisos y configuraciones. |
192
202
  | `ops automation install <ops-root> <runner>` | Instala el wiring de Claude, Codex, Antigravity o Gemini. |
203
+ | `ops automation uninstall <ops-root> <runner>` | Quita ese wiring y conserva lo que no escribió Cauce. |
193
204
  | `ops automation doctor <ops-root> <runner>` | Diagnostica una instalación materializada. |
194
205
 
195
206
  `ops --help` lista las banderas de cada uno. En un proyecto generado, `make help` muestra los atajos
@@ -230,7 +241,20 @@ y actualizar no exige resolver conflictos: se reemplaza `system/` entero y nada
230
241
  y sobrevive, desactivar uno del toolkit es quitarlo de la configuración del runner, y editar uno
231
242
  existente detiene el `upgrade` antes de pisarlo.
232
243
 
233
- ### Versionado
244
+ ### Actualizar
245
+
246
+ Son tres pasos y `make upgrade` hace los dos primeros:
247
+
248
+ ```bash
249
+ npm install --save-dev @ingeniomaps/cauce@latest # trae el motor nuevo
250
+ node tools/ops.js upgrade . # aplica system/ y el runtime
251
+ node tools/ops.js automation install . claude # el wiring del runner
252
+ ```
253
+
254
+ El primero no se puede saltear: `init` fija la versión exacta, así que `npm update` no la mueve y
255
+ `upgrade` compara contra el motor instalado —lo dice en su salida—. El tercero tampoco: los workflows y
256
+ las skills viven en el runner, no en la instancia, y `upgrade` no los toca. `upgrade` lo recuerda al
257
+ terminar.
234
258
 
235
259
  Como `upgrade` reemplaza `system/` sin pedir confirmación, un cambio en el protocolo, en una regla del
236
260
  sistema o en un guard es visible para el usuario y sube minor aunque no toque código. `upgrade` y
@@ -277,6 +301,12 @@ La instalación fusiona la configuración propia del runner y conserva las entra
277
301
  reemplaza los guards que el propio toolkit había registrado sueltos por el grupo que ahora los cubre, y
278
302
  lista cuáles quitó. Nada que no haya escrito el toolkit se toca.
279
303
 
304
+ Para sacarlo, `automation uninstall` quita exactamente lo que Cauce entregó y sigue igual que como lo
305
+ entregó: los guards de la configuración del runner, los workflows, los punteros a cargos. Lo que no
306
+ escribió —tus hooks, tus workflows, tus skills— queda donde está, y un archivo suyo que hayas editado se
307
+ conserva y se nombra en la salida. Borrar la carpeta ops sin esto deja al runner ejecutando guards que ya
308
+ no existen.
309
+
280
310
  Qué comprueba cada guard, qué no puede comprobar y cómo se agrupan por evento está en
281
311
  [automatization/hooks/README.md](automatization/hooks/README.md).
282
312
 
@@ -478,6 +478,108 @@ function deliveryState(recorded, name, resolved, prefix = '') {
478
478
  return delivered && delivered === current ? 'desactualizado' : 'ajeno'
479
479
  }
480
480
 
481
+ // Quita de una estructura de configuración exactamente lo que este adaptador habría puesto, y nada más.
482
+ // Es el inverso de `mergeConfig`: una entrada del usuario nunca coincide literalmente con la nuestra, así
483
+ // que sobrevive; una que editó tampoco coincide, y por eso se conserva y se avisa en vez de borrarse.
484
+ function unmergeConfig(current, incoming) {
485
+ if (Array.isArray(incoming)) {
486
+ if (!Array.isArray(current)) return current
487
+ const nuestras = new Set(incoming.map((value) => JSON.stringify(value)))
488
+ return current.filter((value) => !nuestras.has(JSON.stringify(value)))
489
+ }
490
+ if (incoming && typeof incoming === 'object') {
491
+ if (!current || typeof current !== 'object' || Array.isArray(current)) return current
492
+ const result = { ...current }
493
+ for (const [key, value] of Object.entries(incoming)) {
494
+ if (!(key in result)) continue
495
+ const limpio = unmergeConfig(result[key], value)
496
+ // Una clave que queda vacía por habernos ido no es del usuario: la creamos nosotros al instalar.
497
+ const vacia = limpio === undefined
498
+ || (Array.isArray(limpio) && !limpio.length)
499
+ || (limpio && typeof limpio === 'object' && !Array.isArray(limpio) && !Object.keys(limpio).length)
500
+ if (vacia) delete result[key]
501
+ else result[key] = limpio
502
+ }
503
+ return result
504
+ }
505
+ return JSON.stringify(current) === JSON.stringify(incoming) ? undefined : current
506
+ }
507
+
508
+ // Borra el archivo y, de paso, los directorios que quedaron vacíos por haberlo sacado. Nunca sube más
509
+ // allá del límite: `.claude/` puede tener cosas del usuario aunque `.claude/workflows/` quede vacío.
510
+ function removeFile(file, boundary) {
511
+ fs.rmSync(file, { force: true })
512
+ let dir = path.dirname(file)
513
+ while (dir.startsWith(boundary) && dir !== boundary) {
514
+ try { if (fs.readdirSync(dir).length) return } catch { return }
515
+ fs.rmdirSync(dir)
516
+ dir = path.dirname(dir)
517
+ }
518
+ }
519
+
520
+ // Saca el wiring de un runner dejando intacto lo que no escribimos nosotros.
521
+ //
522
+ // Existe porque desinstalar a mano es borrar `ops/` y descubrir después que cada llamada de herramienta
523
+ // ejecuta un guard que ya no está. Y porque la alternativa —borrar `.claude/` entero— se lleva puesto lo
524
+ // que el usuario haya puesto ahí, que es suyo y no tiene por qué desaparecer con el toolkit.
525
+ //
526
+ // La regla es una sola: se quita lo que Cauce entregó y sigue igual que como lo entregó. Un archivo con
527
+ // cambios propios se conserva y se nombra; decidir sobre él es de la persona, no de este comando.
528
+ function uninstall(root, name, output = console) {
529
+ if (O.mode(root) === 'toolkit') {
530
+ throw new Error('Acá se fabrica Cauce, no se lo consume.')
531
+ }
532
+ const runner = runnerManifest(root, name)
533
+ const paths = runnerPaths(root, name, runner)
534
+ const prefix = opsPrefix(root)
535
+ const recorded = M.readRunners(root)
536
+ const conservados = []
537
+ let quitados = 0
538
+
539
+ const items = [...(runner.instructions || []), ...(runner.artifacts || [])]
540
+ const entregado = { ...recorded }
541
+ for (const item of items) {
542
+ const resolved = { item, ...resolveItem(paths, root, name, item) }
543
+ const key = deliveryKey(name, item.target)
544
+ if (!fs.existsSync(resolved.target)) { delete entregado[key]; continue }
545
+ const situacion = deliveryState(recorded, name, resolved, prefix)
546
+ if (situacion === 'ajeno') { conservados.push(item.target); continue }
547
+ removeFile(resolved.target, paths.install)
548
+ delete entregado[key]
549
+ quitados += 1
550
+ }
551
+
552
+ // Los punteros a cargos no se registran uno por uno —son cuarenta y siete y se regeneran enteros—,
553
+ // así que se reconocen por contenido: sólo se va el que sigue siendo el que generamos.
554
+ if (runner.capabilities.nativeSkills && runner.roleSkills) {
555
+ const base = path.resolve(paths.install, runner.roleSkills)
556
+ for (const role of roleCatalog(root)) {
557
+ const file = path.join(base, role.slug, 'SKILL.md')
558
+ if (!fs.existsSync(file)) continue
559
+ if (M.digest(file) !== M.digestText(roleSkill(role))) {
560
+ conservados.push(path.relative(paths.install, file))
561
+ continue
562
+ }
563
+ removeFile(file, paths.install)
564
+ quitados += 1
565
+ }
566
+ }
567
+
568
+ if (fs.existsSync(paths.configTarget)) {
569
+ const current = JSON.parse(fs.readFileSync(paths.configTarget, 'utf8'))
570
+ const limpio = unmergeConfig(current, runnerConfig(paths, root))
571
+ if (limpio && Object.keys(limpio).length) F.atomicWriteJson(paths.configTarget, limpio)
572
+ else { removeFile(paths.configTarget, paths.install); quitados += 1 }
573
+ output.log(`✓ ${name}: ${runner.config.target} sin las entradas de Cauce`)
574
+ }
575
+
576
+ M.write(root, undefined, entregado)
577
+ output.log(`✓ ${name}: ${quitados} archivo(s) del toolkit quitados de ${paths.install}`)
578
+ for (const file of conservados) output.log(`= ${name}: conservado ${file} (tiene cambios tuyos)`)
579
+ if (runner.activation) output.log(` ${name}: si lo habías registrado a mano, quitalo también.`)
580
+ return { removed: quitados, kept: conservados }
581
+ }
582
+
481
583
  function install(root, name, output = console, options = {}) {
482
584
  // `install` arma la superficie de consumo de una empresa: punteros a cada cargo, una copia de los
483
585
  // workflows y los guards. Acá los cargos y los workflows son el producto —la copia divergiría— y
@@ -571,6 +673,7 @@ module.exports = {
571
673
  check,
572
674
  doctor,
573
675
  install,
676
+ uninstall,
574
677
  legacyGuardWiring,
575
678
  roleCatalog,
576
679
  roleSkill,
@@ -25,10 +25,14 @@ function terminal() {
25
25
  // dejar que ese rechazo suba terminaba la corrida con «Aborted with Ctrl+D» y la instancia recién creada
26
26
  // sin una línea que dijera cómo seguir. El default no hace nada, así que tomarlo no decide nada.
27
27
  async function elegir(deps, texto, opciones, fallback) {
28
- const listado = opciones.map((opcion, indice) => `${indice + 1}) ${opcion}`).join(' ')
28
+ // Una opción por línea y el default señalado donde se mira: apretar Enter es la respuesta más común, y
29
+ // en una sola línea apretada el `[ninguno]` del final no se lee como «esto pasa si no elegís nada».
30
+ const listado = opciones
31
+ .map((opcion, indice) => ` ${indice + 1}) ${opcion}${opcion === fallback ? ' ← Enter' : ''}`)
32
+ .join('\n')
29
33
  for (let intento = 0; intento < INTENTOS; intento += 1) {
30
34
  let dicho
31
- try { dicho = await deps.ask(`\n${texto}\n${listado}\n[${fallback}] > `) } catch {
35
+ try { dicho = await deps.ask(`\n${texto}\n\n${listado}\n\n> `) } catch {
32
36
  deps.log(`\n sin respuesta: sigo con ${fallback}.`)
33
37
  return fallback
34
38
  }
package/engine/cli/ops.js CHANGED
@@ -63,6 +63,7 @@ function usage() {
63
63
  ops automation check <ops-root>
64
64
  ops automation doctor <ops-root> claude|codex|gemini|antigravity
65
65
  ops automation install <ops-root> claude|codex|gemini|antigravity
66
+ ops automation uninstall <ops-root> claude|codex|gemini|antigravity
66
67
  ops learn <agent> [--proposal] [--applied [--period <AAAA-MM>]]
67
68
  ops evaluate <agent> [--cases [--json]] [--bench [caso]] [--record [AAAA-MM-DD]]
68
69
  ops agents list [ops-root] [--own|--system] [--json]
@@ -334,15 +335,8 @@ async function init(target, cli) {
334
335
  // corriendo `init`, no cuesta nada, y es lo único que le dice a alguien qué hacer con lo que acaba de
335
336
  // crear. Dejarlo adentro del camino feliz lo escondía justo de quien más lo necesita.
336
337
  console.log('')
337
- onboard(root, SIN_BANDERAS)
338
- if (resultado.instalado && resultado.runner !== BOOT.SIN_RUNNER) {
339
- console.log(`\nAbrí ${resultado.runner} en este directorio para que las escriba por vos.`)
340
- }
341
- console.log('')
338
+ onboard(root, SIN_BANDERAS, resultado.instalado ? resultado.runner : '')
342
339
  for (const paso of initSteps(enter, resultado)) console.log(paso)
343
- if (resultado.instalado) {
344
- console.log(`El ciclo empieza en ${path.join(relative || '.', 'planning', 'FLOW.md')}.`)
345
- }
346
340
  if (resultado.error) fail(`${resultado.error}: la instancia quedó creada pero todavía no funciona.`)
347
341
  }
348
342
 
@@ -399,7 +393,7 @@ function comandos(commands) {
399
393
 
400
394
  // La guía de arranque: qué hay, qué falta y qué preguntar. Determinista y en milisegundos, porque es lo
401
395
  // primero que ve alguien que acaba de instalar y todavía no sabe qué hace la herramienta.
402
- function onboard(rootArg, cli) {
396
+ function onboard(rootArg, cli, runner = '') {
403
397
  const root = path.resolve(rootArg || '.')
404
398
  const services = inventory(root)
405
399
  const state = OB.guide(root, services)
@@ -426,8 +420,12 @@ function onboard(rootArg, cli) {
426
420
  console.log(`Esta instancia ya tiene ${escrito} escrito: el arranque no la va a pisar.`)
427
421
  return
428
422
  }
429
- console.log('\nCon tus respuestas, el arranque escribe organization/, el mapa real de AGENTS.md y la')
430
- console.log('primera épica. Con un runner instalado: /onboard, que te las hace una por una.')
423
+ // Un solo cierre: tres líneas que suenan a final se leen como tres finales, y quien recién instaló
424
+ // termina sin saber cuál era el paso.
425
+ console.log(runner
426
+ ? `\n→ Abrí ${runner} acá y contestale esa pregunta.`
427
+ : '\n→ Contestá esa pregunta cuando corras el arranque.')
428
+ console.log(' Con tus respuestas escribe organization/, el mapa real de AGENTS.md y la primera épica.')
431
429
  }
432
430
 
433
431
  function check(dir, cli) {
@@ -743,7 +741,13 @@ function upgrade(dir, cli) {
743
741
  const overrides = O.overrides(root)
744
742
 
745
743
  if (dry) {
746
- if (from === to) return console.log(`= ${to}: la instancia está al día`)
744
+ if (from === to) {
745
+ // Contra el motor instalado, no contra lo publicado: la comparación es local y sin red. Decirlo
746
+ // importa porque `init` fija la versión exacta, así que el motor no se mueve solo y esta línea,
747
+ // a secas, se leía como «no hay nada nuevo» durante todas las versiones siguientes.
748
+ console.log(`= ${to}: la instancia está al día con el motor instalado`)
749
+ return console.log(' para traer una versión más nueva: npm install --save-dev @ingeniomaps/cauce@latest')
750
+ }
747
751
  console.log(`⚠ hay una versión más nueva: ${to} (la instancia tiene ${from || 'una previa'})`)
748
752
  printChangelog(from, to)
749
753
  for (const file of changed) console.log(` editado localmente: ${file}`)
@@ -1112,6 +1116,11 @@ function automation(action, rootArg, runnerName, cli) {
1112
1116
  console.log(`✓ ${runnerName}: adaptador operativo (${result.warnings.length} advertencia(s))`)
1113
1117
  return
1114
1118
  }
1119
+ if (action === 'uninstall') {
1120
+ try { A.uninstall(root, runnerName, console) } catch (error) { fail(error.message, 2) }
1121
+ console.log(' la instancia sigue en pie: borrar la carpeta ops es una decisión aparte.')
1122
+ return
1123
+ }
1115
1124
  if (action === 'install') {
1116
1125
  let runner
1117
1126
  const force = cli.has('--force')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.30.0",
3
+ "version": "0.31.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",
package/template/Makefile CHANGED
@@ -4,6 +4,7 @@
4
4
  .PHONY: integration-check integration-sync require-key integration-promote
5
5
  .PHONY: require-agent agent-learn agent-propose agent-evaluate require-team team-check team-show
6
6
  .PHONY: install-claude install-codex install-gemini install-antigravity
7
+ .PHONY: uninstall-claude uninstall-codex uninstall-gemini uninstall-antigravity
7
8
  .PHONY: doctor-claude doctor-codex doctor-gemini doctor-antigravity
8
9
 
9
10
  # Jira es hoy el único proveedor; el día que haya otro se pasa PROVIDER=<slug>.
@@ -22,7 +23,10 @@ tree: ## Muestra roadmap, backlog, WIP y Done
22
23
  context: ## Muestra el contexto mínimo de la tarea vigente
23
24
  @node tools/ops.js context planning
24
25
 
25
- upgrade: ## Actualiza Cauce sin tocar lo del proyecto
26
+ # `init` fija la versión exacta del motor, así que `npm update` no la mueve: hay que pedir @latest.
27
+ # Sin este primer paso `upgrade` compara contra el motor instalado y contesta «al día» para siempre.
28
+ upgrade: ## Trae la última versión de Cauce y la aplica, sin tocar lo del proyecto
29
+ @npm install --save-dev @ingeniomaps/cauce@latest
26
30
  @node tools/ops.js upgrade .
27
31
 
28
32
  automation-check: ## Comprueba hooks, workflows y adaptadores
@@ -73,6 +77,18 @@ install-gemini: ## Instala contexto, comandos y configuración para Gemini
73
77
  install-antigravity: ## Instala el plugin Cauce para Antigravity CLI
74
78
  @node tools/ops.js automation install . antigravity
75
79
 
80
+ uninstall-claude: ## Quita de Claude el wiring de Cauce, conservando lo tuyo
81
+ @node tools/ops.js automation uninstall . claude
82
+
83
+ uninstall-codex: ## Quita de Codex el wiring de Cauce, conservando lo tuyo
84
+ @node tools/ops.js automation uninstall . codex
85
+
86
+ uninstall-gemini: ## Quita de Gemini el wiring de Cauce, conservando lo tuyo
87
+ @node tools/ops.js automation uninstall . gemini
88
+
89
+ uninstall-antigravity: ## Quita de Antigravity el wiring de Cauce, conservando lo tuyo
90
+ @node tools/ops.js automation uninstall . antigravity
91
+
76
92
  doctor-claude: ## Diagnostica la instalación de Claude
77
93
  @node tools/ops.js automation doctor . claude
78
94