@ingeniomaps/cauce 0.40.0 → 0.42.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,60 @@ 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.42.0] - 2026-08-20
18
+
19
+ ### Corregido
20
+
21
+ - **Los guards de Codex no corrían nunca.** Tres cosas a la vez, y ninguna hacía ruido: el archivo se
22
+ instalaba en `.codex/hooks/hooks.json` —esa forma es la que empaqueta un plugin; un repositorio se lee
23
+ en `.codex/hooks.json`—, los `matcher` filtraban nombres del protocolo interno en vez del nombre de la
24
+ herramienta (`Bash`, `apply_patch`/`Edit`/`Write`), y el comando era una ruta relativa mientras Codex
25
+ ejecuta el hook con el cwd de la sesión, así que desde cualquier subdirectorio no existía. **Reinstalá
26
+ el adaptador**, borrá a mano el `.codex/hooks/hooks.json` que queda huérfano, y confiá los hooks con
27
+ `/hooks` dentro de una sesión: Codex los saltea en silencio hasta que lo hagas, y hay que repetirlo
28
+ cada vez que el wiring cambie.
29
+ - **El guard de archivos no veía nada bajo Codex.** `apply_patch` manda el parche entero como
30
+ `tool_input.command`, no como `patch`, y sin reconocer ese campo el guard miraba una escritura y no
31
+ encontraba un solo archivo: reescribió una migración existente en una corrida real, y lo mismo valía
32
+ para secretos y para los límites del workspace.
33
+ - **Los guards de Antigravity tampoco corrían.** `agy` ejecuta la copia del plugin que registró en
34
+ `~/.gemini/config/plugins/` y resuelve las rutas contra la carpeta del plugin, así que la del
35
+ workspace apuntaba a un módulo inexistente; su payload además no nombra el workspace —manda
36
+ `workspacePaths` vacío y un `Cwd` que apunta al scratch del CLI o al home—, de modo que la raíz ops
37
+ ahora se escribe como ruta absoluta al instalar. **Reinstalá y volvé a registrar** con
38
+ `agy plugin install .agents/plugins/cauce` cada vez que cambie el wiring.
39
+ - **Un puente que no arrancaba dejaba al agente sin poder cerrar la sesión.** En Antigravity, un guard
40
+ que bloquea y un fallo de infraestructura devolvían los dos `continue`; ahora sólo el bloqueo lo hace,
41
+ y la falla deja cerrar con la razón a la vista.
42
+ - **Los guards resolvían rutas relativas contra la carpeta equivocada en Antigravity**, que no es la del
43
+ proyecto: un guard que juzgaba `src/x.js` estaba juzgando otro archivo.
44
+ - **Instalar acumulaba las entradas de hooks de versiones anteriores.** Una ruta nuestra que cambiaba
45
+ dejaba viva la anterior, el runner la ejecutaba, no encontraba nada y el guard no corría. Ahora se
46
+ reemplazan las nuestras y se conserva lo que agregó el proyecto.
47
+ - **Un cargo con el mismo nombre que un recorrido lo pisaba en silencio**, porque comparten el espacio de
48
+ skills del runner. La instalación ahora se detiene y dice cuál: renombrá el cargo en `agents/roles/`.
49
+ - **Gemini invocaba `/ops:autobuild`**, un prefijo que ya no existe, y **Antigravity anunciaba sus
50
+ recorridos sin la barra** (`cauce:onboard` en vez de `/cauce:onboard`).
51
+ - **La regla de Antigravity apuntaba a un `AGENTS.md` sin prefijo**, que en modo sidecar es el del
52
+ repositorio de producto y no el que trae las reglas del sistema.
53
+
54
+ ### Cambiado
55
+
56
+ - **El arranque de Gemini vive ahora en su comando.** `GEMINI.md` remite a `/cauce:onboard` en vez de
57
+ repetir el recorrido, y el comando trae la lista completa de lo que se comprueba al final —incluidos
58
+ los dos puntos que la copia había perdido—.
59
+
60
+ ## [0.41.0] - 2026-08-19
61
+
62
+ ### Corregido
63
+
64
+ - **`destroy` ya no borra el repositorio en modo embebido.** Ahí la instancia **es** el repositorio, así
65
+ que borrar la carpeta se llevaba el código del producto: pasó de verdad sobre un caso de prueba, y el
66
+ aviso previo enumeraba épicas y acciones humanas sin nombrar lo único irrecuperable. En embebido saca
67
+ lo que Cauce escribió —`planning/`, `organization/`, `teams/`, `integrations/`, `automatization/`,
68
+ `tools/`, la configuración y el manifiesto— y deja el repositorio. En sidecar sigue borrando la
69
+ carpeta, que ahí no contiene nada más.
70
+
17
71
  ## [0.40.0] - 2026-08-19
18
72
 
19
73
  ### Corregido
@@ -1,25 +1,42 @@
1
1
  # Adaptadores de runners
2
2
 
3
- Un adaptador declara:
3
+ Cada adaptador traduce el mismo toolkit al espacio de nombres de su herramienta. Lo que declara su
4
+ `manifest.json`:
4
5
 
5
- 1. Eventos soportados (`pre-tool`, `post-tool`, `stop`, workflow invocable).
6
- 2. Cómo localiza la raíz Ops y ejecuta `engine/cli/ops.js`.
7
- 3. Qué fases ejecuta realmente y cuáles quedan como protocolo manual.
8
- 4. Cómo instala y desinstala su wiring sin duplicar fuentes de verdad.
9
- 5. Qué permisos o acciones externas requieren confirmación.
6
+ | Campo | Qué resuelve |
7
+ |---|---|
8
+ | `command` | el ejecutable con el que se comprueba que la herramienta esté disponible |
9
+ | `config` | de qué archivo sale el wiring de hooks y en qué ruta del proyecto se instala |
10
+ | `instructions` | qué archivo de contexto se copia, si la herramienta lee uno propio |
11
+ | `artifacts` | los recorridos y cualquier archivo que el adaptador aporte |
12
+ | `capabilities` | qué ejecuta de verdad —`nativeHooks`, `nativeWorkflows`, `nativeSkills`, `checkpointing`, `projectInstructions`— y qué queda como protocolo manual |
13
+ | `roleSkills` | dónde se instala el catálogo de cargos, cuando la herramienta tiene skills |
14
+ | `activation` | el paso manual que lo pone a correr, cómo verificarlo y qué se espera ver |
15
+ | `commands` | los nombres de los recorridos y con qué prefijo se los invoca acá |
16
+
17
+ Lo que un adaptador **no** declara es qué acciones requieren confirmación humana: eso no varía por
18
+ runner. Lo fijan los guards de `automatization/hooks/` y las reglas de `planning/rules/system/`, iguales
19
+ para los cuatro.
10
20
 
11
21
  ```text
12
22
  runners/<nombre>/
13
23
  ├── README.md
14
24
  ├── manifest.json
15
- ├── settings.json
16
- ├── archivo de instrucciones (si aplica)
17
- └── comandos o artefactos propios (si aplica)
25
+ ├── settings.json | hooks.json configuración del runner, según lo que lea cada uno
26
+ ├── archivo de instrucciones CLAUDE.md, GEMINI.md, AGENTS.md (si aplica)
27
+ └── artefactos propios comandos, skills, reglas, puentes (si aplica)
18
28
  ```
19
29
 
20
30
  Adaptadores incluidos: `claude`, `codex`, `antigravity` y `gemini`. Antigravity es la opción Google
21
- recomendada para cuentas individuales y proyectos nuevos; Gemini se conserva para Enterprise, Google Cloud
22
- y autenticación mediante API keys. Se instalan explícitamente con
23
- `ops automation install`; crear el proyecto no modifica la configuración activa del usuario.
24
- Después de instalar, `ops automation doctor <raíz> <runner>` verifica configuración, instrucciones,
25
- artefactos y disponibilidad del CLI sin requerir autenticación.
31
+ recomendada para cuentas individuales y proyectos nuevos; Gemini se conserva para Enterprise, Google
32
+ Cloud y autenticación mediante API keys.
33
+
34
+ Se instalan explícitamente —crear el proyecto no toca la configuración activa del usuario— y se
35
+ comprueban sin autenticar:
36
+
37
+ ```bash
38
+ node tools/ops.js automation install . <runner>
39
+ node tools/ops.js automation doctor . <runner>
40
+ ```
41
+
42
+ `doctor` verifica configuración, instrucciones, artefactos, cargos y disponibilidad del CLI.
@@ -1,16 +1,30 @@
1
1
  # Antigravity CLI
2
2
 
3
- Runner Google recomendado para cuentas individuales y proyectos nuevos. Usa el ejecutable `agy` y se instala
4
- como plugin nativo de workspace en `.agents/plugins/cauce/`:
3
+ Runner Google recomendado para cuentas individuales y proyectos nuevos. Usa el ejecutable `agy` y se
4
+ instala como plugin nativo de workspace en `.agents/plugins/cauce/`:
5
5
 
6
6
  ```bash
7
- make install-antigravity
8
- make doctor-antigravity
7
+ node tools/ops.js automation install . antigravity
8
+ node tools/ops.js automation doctor . antigravity
9
9
  ```
10
10
 
11
- El plugin aporta hooks `PreToolUse` y `Stop`, reglas Cauce y skills `/autobuild`,
12
- `/integration-sync` y `/integration-promote`. El bridge convierte el contrato JSON camelCase de Antigravity
13
- al motor compartido de guards y devuelve decisiones nativas `allow`, `deny` o `continue`.
11
+ `automation install` deja los archivos y no autentica. El paso que los pone a correr es
12
+ `agy plugin install .agents/plugins/cauce`, que el instalador imprime cuando falta. Ese paso **copia el
13
+ plugin a `~/.gemini/config/plugins/cauce/` y es esa copia la que `agy` ejecuta**, con un registro por
14
+ usuario y no por workspace. De ahí salen tres consecuencias que conviene tener presentes:
14
15
 
15
- La instalación no autentica ni modifica preferencias globales. Antigravity también lee el `AGENTS.md` del
16
- workspace. El runner `gemini` permanece disponible por separado para Enterprise, Google Cloud y API keys.
16
+ - Las rutas del `hooks.json` se resuelven contra la carpeta del plugin, no contra el workspace.
17
+ - El payload no nombra el workspace —manda `workspacePaths` vacío y un `Cwd` que apunta al scratch del
18
+ CLI o al home—, así que el puente lleva la raíz ops escrita como ruta absoluta al instalar.
19
+ - Registrar desde otro proyecto reemplaza el plugin del anterior.
20
+
21
+ Volvé a registrar cada vez que `automation install` cambie el wiring o el proyecto se mueva de lugar;
22
+ `agy plugin validate .agents/plugins/cauce` comprueba la copia del repo antes de registrarla.
23
+
24
+ El plugin aporta hooks `PreToolUse` y `Stop`, reglas Cauce y los cinco recorridos —`/cauce:onboard`,
25
+ `/cauce:team`, `/cauce:autobuild`, `/cauce:integration-sync` y `/cauce:integration-promote`— más el
26
+ catálogo de cargos como skills. El bridge convierte el contrato JSON camelCase de Antigravity al motor
27
+ compartido de guards y devuelve decisiones nativas `allow`, `deny` o `continue`.
28
+
29
+ Antigravity también lee el `AGENTS.md` del workspace. El runner `gemini` permanece disponible por
30
+ separado para Enterprise, Google Cloud y API keys.
@@ -17,24 +17,59 @@ function readInput() {
17
17
  // no hallaba `ops.config.json` y, como falla cerrado, negaba cada llamada a herramienta.
18
18
  const OPS_DIR = '{{OPS_DIR}}'
19
19
 
20
- function declaredRoot() {
21
- // El plugin vive en <raíz-de-instalación>/.agents/plugins/cauce/.
22
- const installed = path.resolve(__dirname, '..', '..', '..')
23
- const root = OPS_DIR.startsWith('{{') ? installed : path.join(installed, OPS_DIR)
20
+ // Y la ruta absoluta, porque con Antigravity no hay de dónde deducirla. Su payload manda
21
+ // `workspacePaths` vacío y un `Cwd` que apunta al scratch del CLI o al home; el hook lo ejecuta `agy`
22
+ // desde la copia que registró en `~/.gemini/config/plugins/`, cuyo `__dirname` no lleva a ningún
23
+ // proyecto. Nada de eso nombra el workspace, así que el ancla se escribe al instalar o no existe.
24
+ const OPS_ROOT = '{{OPS_ROOT}}'
25
+
26
+ // Los dos marcadores y la carpeta desde la que corre el puente, juntos y pasables como argumento. El
27
+ // default es lo que `automation install` deja escrito; poder reemplazarlo es lo que permite ejercer
28
+ // desde el repositorio lo que sólo existe instalado. Sin eso, la resolución de la raíz —donde ya se
29
+ // escondieron dos fallas que negaban cada llamada a herramienta— sólo se puede probar sobre una copia,
30
+ // y una copia no la mide ninguna cobertura.
31
+ const MARCAS = { dir: OPS_DIR, root: OPS_ROOT, plugin: __dirname }
32
+
33
+ function isRoot(dir) {
34
+ const instance = fs.existsSync(path.join(dir, 'planning'))
35
+ const toolkit = fs.existsSync(path.join(dir, 'engine', 'hooks', 'run.js'))
36
+ return fs.existsSync(path.join(dir, 'ops.config.json')) && (instance || toolkit)
37
+ }
38
+
39
+ function declaredRoot(marcas) {
40
+ if (!marcas.root.startsWith('{{') && isRoot(marcas.root)) return marcas.root
41
+ // El plugin corriendo desde donde `automation install` lo dejó, que es el caso sin registrar.
42
+ const installed = path.resolve(marcas.plugin, '..', '..', '..')
43
+ const root = marcas.dir.startsWith('{{') ? installed : path.join(installed, marcas.dir)
24
44
  return fs.existsSync(path.join(root, 'ops.config.json')) ? root : ''
25
45
  }
26
46
 
27
- function findRoot(input) {
28
- const declared = declaredRoot()
47
+ // En sidecar se abre la carpeta de la compañía y la raíz ops es una de sus hijas, así que buscar sólo
48
+ // hacia arriba no la encuentra nunca: el puente fallaba cerrado y negaba cada llamada a herramienta.
49
+ // Un nivel hacia abajo alcanza para los dos modos y es determinista; recorrer el árbol del producto,
50
+ // no. Dos candidatas hermanas es una ambigüedad que nadie puede resolver acá: se abstiene.
51
+ function childRoot(dir) {
52
+ let entries = []
53
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return '' }
54
+ const roots = entries
55
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
56
+ .map((entry) => path.join(dir, entry.name))
57
+ .filter(isRoot)
58
+ return roots.length === 1 ? roots[0] : ''
59
+ }
60
+
61
+ function findRoot(input, marcas = MARCAS) {
62
+ const declared = declaredRoot(marcas)
29
63
  if (declared) return declared
30
64
  const args = input.toolCall && input.toolCall.args || {}
31
65
  const starts = [args.Cwd, process.cwd(), ...(input.workspacePaths || [])].filter(Boolean)
32
66
  for (const start of starts) {
33
- let current = path.resolve(start)
67
+ const base = path.resolve(start)
68
+ const child = isRoot(base) ? base : childRoot(base)
69
+ if (child) return child
70
+ let current = path.dirname(base)
34
71
  while (true) {
35
- const instance = fs.existsSync(path.join(current, 'planning'))
36
- const toolkit = fs.existsSync(path.join(current, 'engine', 'hooks', 'run.js'))
37
- if (fs.existsSync(path.join(current, 'ops.config.json')) && (instance || toolkit)) return current
72
+ if (isRoot(current)) return current
38
73
  const parent = path.dirname(current)
39
74
  if (parent === current) break
40
75
  current = parent
@@ -56,14 +91,38 @@ function runtimeAt(root) {
56
91
  return require(runtime)
57
92
  }
58
93
 
59
- function normalize(input) {
94
+ // La carpeta que el runner abrió, deducida de la raíz: en sidecar la raíz ops es su hija, y en modo
95
+ // embebido son la misma.
96
+ function workspaceOf(root, marcas) {
97
+ const relative = marcas.dir.startsWith('{{') ? '' : marcas.dir.replace(/\/+$/, '')
98
+ if (!relative) return root
99
+ return path.resolve(root, ...relative.split('/').map(() => '..'))
100
+ }
101
+
102
+ function within(dir, base) {
103
+ const relative = path.relative(base, dir)
104
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
105
+ }
106
+
107
+ // Contra qué resuelven los guards una ruta relativa o el directorio git. El `Cwd` de Antigravity no
108
+ // sirve para eso: apunta al scratch del CLI o al home, así que un guard que juzgue `src/x.js` estaría
109
+ // juzgando otro archivo, y uno que busque el repo git lo buscaría fuera del proyecto. Se respeta el
110
+ // que manda sólo si cae adentro del workspace —si algún día manda uno real, es mejor que el nuestro—;
111
+ // si no, el workspace, que es donde el runner dice estar trabajando.
112
+ function cwdFor(args, root, marcas) {
113
+ const declared = args.Cwd && path.resolve(String(args.Cwd))
114
+ const workspace = workspaceOf(root, marcas)
115
+ return declared && within(declared, workspace) ? declared : workspace
116
+ }
117
+
118
+ function normalize(input, root, marcas = MARCAS) {
60
119
  const args = input.toolCall && input.toolCall.args || {}
61
120
  const file = args.TargetFile || args.AbsolutePath || ''
62
121
  const content = args.CodeContent || args.ReplacementContent
63
122
  || (args.ReplacementChunks && JSON.stringify(args.ReplacementChunks)) || ''
64
123
  return {
65
124
  sessionId: input.conversationId,
66
- cwd: args.Cwd || (input.workspacePaths || [])[0] || process.cwd(),
125
+ cwd: cwdFor(args, root, marcas),
67
126
  tool_input: {
68
127
  command: args.CommandLine || '',
69
128
  file_path: file,
@@ -81,13 +140,18 @@ function evaluate(event, input) {
81
140
  const root = findRoot(input)
82
141
  process.env.OPS_ROOT = root
83
142
  const hooks = runtimeAt(root)
84
- const normalized = normalize(input)
143
+ const normalized = normalize(input, root)
85
144
  if (!hooks.hookGroups[event]) throw new Error(`Evento Antigravity desconocido: ${event || '(vacío)'}`)
86
145
  hooks.executeAll([event], normalized)
87
146
  return event === 'stop' ? { decision: 'stop' } : { decision: 'allow' }
88
147
  } catch (error) {
89
- if (event === 'stop') return { decision: 'continue', reason: `Cauce: ${error.message}` }
90
- return { decision: 'deny', reason: `Cauce: ${error.message}` }
148
+ const reason = `Cauce: ${error.message}`
149
+ if (event !== 'stop') return { decision: 'deny', reason }
150
+ // Un guard que bloquea marca su error con `blocked` (engine/hooks/run.js); cualquier otro es que el
151
+ // puente no llegó a juzgar nada. En `stop` los dos devolvían `continue`, y eso ata al agente: la
152
+ // raíz que no resuelve no se arregla sola, así que cada intento de cerrar repite el mismo error.
153
+ // El bloqueo sigue dando `continue` —es el mecanismo funcionando—; la falla deja cerrar y avisa.
154
+ return error.blocked ? { decision: 'continue', reason } : { decision: 'stop', reason }
91
155
  }
92
156
  }
93
157
 
@@ -4,18 +4,18 @@
4
4
  {
5
5
  "matcher": "run_command",
6
6
  "hooks": [
7
- { "type": "command", "command": "node .agents/plugins/cauce/hook.js pre-shell", "timeout": 120 }
7
+ { "type": "command", "command": "node hook.js pre-shell", "timeout": 120 }
8
8
  ]
9
9
  },
10
10
  {
11
11
  "matcher": "write_to_file|replace_file_content|multi_replace_file_content",
12
12
  "hooks": [
13
- { "type": "command", "command": "node .agents/plugins/cauce/hook.js pre-files", "timeout": 30 }
13
+ { "type": "command", "command": "node hook.js pre-files", "timeout": 30 }
14
14
  ]
15
15
  }
16
16
  ],
17
17
  "Stop": [
18
- { "type": "command", "command": "node .agents/plugins/cauce/hook.js stop", "timeout": 120 }
18
+ { "type": "command", "command": "node hook.js stop", "timeout": 120 }
19
19
  ]
20
20
  }
21
21
  }
@@ -2,13 +2,10 @@
2
2
  "schemaVersion": 1,
3
3
  "name": "antigravity",
4
4
  "command": "agy",
5
- "lifecycle": {
6
- "recommendedForNewProjects": true,
7
- "audience": "individual-and-new-google-projects"
8
- },
9
5
  "config": {
10
6
  "source": "hooks.json",
11
- "target": ".agents/plugins/cauce/hooks.json"
7
+ "target": ".agents/plugins/cauce/hooks.json",
8
+ "owned": true
12
9
  },
13
10
  "instructions": [],
14
11
  "artifacts": [
@@ -62,7 +59,7 @@
62
59
  "expect": "cauce"
63
60
  },
64
61
  "commands": {
65
- "invocation": "cauce:{name}",
62
+ "invocation": "/cauce:{name}",
66
63
  "names": [
67
64
  "onboard",
68
65
  "team",
@@ -1,6 +1,6 @@
1
1
  # Cauce
2
2
 
3
- Lee y cumple `AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y `{{OPS_DIR}}planning/rules/system/` antes
3
+ Lee y cumple `{{OPS_DIR}}AGENTS.md`, `{{OPS_DIR}}planning/PROTOCOL.md` y `{{OPS_DIR}}planning/rules/system/` antes
4
4
  de ejecutar trabajo. `{{OPS_DIR}}planning/WIP.md` es el mutex de
5
5
  ejecución y `{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva. No promociones ideas desde INBOX, no
6
6
  inventes aprobaciones o credenciales y no hagas push ni deploy. Cierra cada tarea con verificación real y
@@ -3,72 +3,4 @@ name: onboard
3
3
  description: Escanea el repositorio y deja escrito el contexto de la empresa y la primera épica.
4
4
  ---
5
5
 
6
- Es el arranque de una instancia recién creada: `init` la instaló, pero nadie le explicó todavía qué es
7
- este proyecto.
8
-
9
- Empezá por `node {{OPS_DIR}}tools/ops.js onboard`, que es instantáneo y te dice tres cosas: si la
10
- instancia sigue vacía, qué hay en el workspace y con qué pregunta empezar. Si ya tiene contexto escrito,
11
- no la pises: reescribir lo que alguien corrigió no deja rastro de lo que se perdió.
12
-
13
- La conversación empieza por una sola pregunta —de qué trata el proyecto, la primera línea que ese comando
14
- imprime— y la hacés antes de mirar el inventario, sea el workspace vacío, un monorepo o diez repos.
15
- Sigue según lo que conteste: hasta tres más, formuladas con las palabras de ese proyecto, hasta cubrir
16
- las dimensiones que el comando haya listado. Una por vez, esperando respuesta. El inventario ya viene
17
- resuelto: no recorras el árbol ni leas código para completarlo. No des por sentado que vende algo: puede sostenerse con
18
- donaciones, presupuesto interno o trabajo voluntario, y preguntarle a un proyecto libre quién le paga es
19
- empezar por una respuesta que nadie dio.
20
-
21
- El inventario no lo hagas a mano: `node {{OPS_DIR}}tools/ops.js scan --json` devuelve los subproyectos con
22
- manifiesto propio, su runtime y los comandos que cada uno declara, con el archivo del que salieron.
23
- Recorrer directorios es determinista y cuesta milisegundos; explorarlo vos cuesta minutos y encuentra lo
24
- mismo. No corras ningún comando del proyecto: el mapa dice lo que está declarado y de dónde, y
25
- verificarlo corriéndolo es una historia de la épica, con dueño y tiempo asignado.
26
-
27
- Con eso escribí `{{OPS_DIR}}organization/company.md` y `product.md`, la sección «Mapa real» de
28
- `{{OPS_DIR}}AGENTS.md` con el resultado que obtuviste por comando, y las raíces reales en
29
- `workspaceRoots` de `{{OPS_DIR}}ops.config.json`, que es lo que un guard usa para bloquear una escritura
30
- fuera de lugar. Lo deducido va marcado `(supuesto)` y lo que nada sostiene queda «Por definir»: no
31
- inventes clientes, ingresos ni objetivos.
32
-
33
- Credenciales, MCP y el permiso de push no te corresponden. Cada uno va como fila en
34
- `{{OPS_DIR}}planning/HUMAN_ACTIONS.md` con la acción concreta que lo desbloquea y sin proponer ningún
35
- valor; las preguntas abiertas, a la sección Ideas de `{{OPS_DIR}}planning/INBOX.md`.
36
-
37
- El arranque tiene tres objetivos y ninguno más: entender qué es el proyecto, dejar la instancia correcta
38
- para él y que la primera tarea pueda empezar. El análisis profundo viene después, cuando la persona pida
39
- algo concreto.
40
-
41
- Cerrá escribiendo la épica en `{{OPS_DIR}}planning/roadmap/`: su resultado es que una tarea pueda
42
- atravesar el ciclo entero, y sus criterios salen de lo que hoy falta —contexto sin supuestos, cada
43
- comando en verde, el guard de límites probado en las dos direcciones, una tarea piloto en DONE—.
44
- **Nunca promuevas al BACKLOG**: esa firma es humana.
45
-
46
- ## Antes de decir que terminaste
47
-
48
- Esta lista no es un resumen de lo de arriba: es lo que se comprueba mirando el disco. Una corrida real
49
- falló los cinco puntos sin darse cuenta, y todos del mismo lado —lo que no produce un archivo visible—.
50
-
51
- 1. **Una pregunta por vez.** Si mandaste dos o más juntas, numeradas, hiciste un formulario. La segunda
52
- pregunta depende de la primera respuesta: por eso se hacen de a una.
53
- 2. **`epic-NNN-<slug>.md`**, con slug en el nombre y `status: open`. `epic-001.md` no lo lee nadie: ni
54
- `check`, ni `tree`, ni el runner que busca trabajo. Existe y no existe a la vez.
55
- 3. **Las secciones de `{{OPS_DIR}}organization/` son las del molde.** Escribí adentro de ellas; agregá las tuyas
56
- abajo si hacen falta. Reemplazar la estructura pierde dimensiones que nadie va a reclamar después,
57
- porque el archivo se lee completo.
58
- 4. **`HUMAN_ACTIONS.md` tiene filas.** Una por credencial nombrada en el inventario, una por sistema
59
- externo o MCP, una por la autoridad de push. Si quedó vacío, no es que no hubiera nada: es que lo que
60
- no te corresponde se perdió en vez de quedar escrito para alguien.
61
- 5. **`ops` no es un servicio del producto.** No va en el mapa real: es la instancia desde la que trabajás.
62
-
63
- 6. **Ninguna dimensión se completa sin haberla preguntado.** Si llegaste al tope de preguntas y una
64
- quedó afuera —quién lo usa, qué está muerto, qué externo hay— va «Por definir» con su pregunta en
65
- `{{OPS_DIR}}planning/INBOX.md`, aunque puedas imaginar la respuesta. Y lo que deducís de otra
66
- respuesta va marcado `(supuesto)`, por plausible que sea: sin la marca se lee con el mismo peso que
67
- lo que la persona dijo, y nadie va a volver a preguntarlo.
68
- 7. **Corré `node {{OPS_DIR}}tools/ops.js onboard`.** Si te vuelve a ofrecer la pregunta de apertura, la
69
- instancia sigue vacía: no escribiste nada, y no hay nada que informar. Una corrida real entregó un
70
- resumen en pasado —«registrado», «documentado», «creada la primera épica»— sobre archivos que en el
71
- disco seguían siendo el molde. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.
72
-
73
- Recién ahí, `node {{OPS_DIR}}tools/ops.js check planning`. Si sale con advertencias, leelas: son
74
- exactamente estas cosas.
6
+ {{INCLUDE:shared/onboard.md}}
@@ -6,8 +6,14 @@ Adaptador nativo mediante `PreToolUse` y `Stop`. Instalar con:
6
6
  node tools/ops.js automation install . claude
7
7
  ```
8
8
 
9
- El instalador fusiona la sección `hooks` en `.claude/settings.json` y conserva otras claves.
10
- También instala `autobuild`, `integration-sync` e `integration-promote` en `.claude/workflows/`. Si ya existe
11
- una versión distinta, se detiene sin sobrescribirla para no destruir personalizaciones del proyecto.
12
- También crea `CLAUDE.md` cuando no existe y conserva uno existente. `manifest.json` declara destinos y
13
- capacidades. Comprueba todo con `node tools/ops.js automation doctor . claude`.
9
+ El instalador fusiona la sección `hooks` en `.claude/settings.json` y conserva otras claves. Si ya
10
+ existe una versión distinta de un archivo, se detiene sin sobrescribirla para no destruir
11
+ personalizaciones del proyecto. También crea `CLAUDE.md` cuando no existe y conserva uno existente.
12
+
13
+ En `.claude/workflows/` deja los cinco recorridos que anuncia —`/onboard`, `/team`, `/autobuild`,
14
+ `/integration-sync` e `/integration-promote`— y tres del ciclo de cargos que se invocan igual pero no
15
+ figuran en la lista de recorridos: `/agent-eval`, `/agent-propose` y `/agent-promote`. El catálogo de
16
+ cargos llega como skills en `.claude/skills/`.
17
+
18
+ `manifest.json` declara destinos y capacidades. Comprueba todo con
19
+ `node tools/ops.js automation doctor . claude`.
@@ -7,7 +7,7 @@ antes de trabajar —los tres, no cuando algo sale mal—: acá sólo está lo e
7
7
  > Codex lee el `AGENTS.md` de la raíz, que es un nombre compartido entre herramientas. Cuando el repo
8
8
  > ops **es** la raíz, este archivo no se instala: el `AGENTS.md` de la empresa ya está ahí y manda.
9
9
 
10
- Los hooks de `.codex/hooks/hooks.json` son obligatorios y bloquean por su cuenta. Codex no ejecuta los
10
+ Los hooks de `.codex/hooks.json` son obligatorios y bloquean por su cuenta. Codex no ejecuta los
11
11
  workflows JS de Claude —son referencia, no un runtime compatible—, así que el recorrido se hace fase por
12
12
  fase siguiendo el protocolo.
13
13
 
@@ -33,35 +33,7 @@ ese cargo debe saber de esta empresa está en `{{OPS_DIR}}organization/roles/<sl
33
33
 
34
34
  ## El arranque
35
35
 
36
- En una instancia recién creada nadie le explicó todavía al toolkit qué es este proyecto:
37
- `{{OPS_DIR}}organization/` llega como molde y el roadmap está vacío. El primer recorrido lo llena, y una vez: reescribir un contexto
38
- que alguien ya corrigió no deja rastro de lo que se perdió.
39
-
40
- Empezá por `node {{OPS_DIR}}tools/ops.js onboard`, que es instantáneo: la primera línea que imprime es la
41
- pregunta con la que tenés que abrir —de qué trata el proyecto—, y después vienen el inventario y las
42
- dimensiones. Hacé esa pregunta tal cual antes de mirar nada, sea el workspace vacío, un monorepo o diez
43
- repos, y según lo que conteste formulá hasta tres más con las palabras de ese proyecto, una por vez. El
44
- inventario ya viene resuelto ahí: no recorras el árbol ni leas código para completarlo. No des por
45
- sentado que vende algo: puede sostenerse con donaciones, presupuesto interno o trabajo voluntario. Con eso escribí `{{OPS_DIR}}organization/`, la sección «Mapa real» de
46
- `{{OPS_DIR}}AGENTS.md` con cada comando tal como está declarado y de qué archivo salió —sin correrlo—, y
47
- las raíces reales en `workspaceRoots`. Lo deducido va marcado `(supuesto)`. Credenciales, MCP y el permiso de push van como
48
- filas en `{{OPS_DIR}}planning/HUMAN_ACTIONS.md`, sin proponer valores. Cerrá con `epic-001` en
49
- `{{OPS_DIR}}planning/roadmap/`: que una tarea pueda atravesar el ciclo entero, con criterios que salen de
50
- lo que falta. Nunca la promuevas.
51
-
52
- El arranque tiene tres objetivos y ninguno más: entender qué es el proyecto, dejar la instancia correcta
53
- para él y que la primera tarea pueda empezar. El análisis profundo viene después, cuando la persona pida
54
- algo concreto.
55
-
56
- Antes de darlo por terminado, comprobá cinco cosas mirando el disco: preguntaste de a una y no en
57
- formulario; la épica se llama `epic-NNN-<slug>.md` con `status: open` —`epic-001.md` no lo lee nadie—;
58
- las secciones de `{{OPS_DIR}}organization/` siguen siendo las del molde y lo tuyo se agregó adentro;
59
- `{{OPS_DIR}}planning/HUMAN_ACTIONS.md` tiene una fila por credencial, por externo y por la autoridad de
60
- push; y `ops` no figura como servicio del producto en el mapa. una dimensión que no
61
- llegaste a preguntar va «Por definir» con su pregunta en `{{OPS_DIR}}planning/INBOX.md` en vez de
62
- completarse deduciéndola, y lo que deducís de otra respuesta va marcado `(supuesto)`. Cerrá corriendo
63
- `node {{OPS_DIR}}tools/ops.js onboard`: si te vuelve a ofrecer la pregunta de apertura, no escribiste
64
- nada y no hay nada que informar. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.
36
+ {{INCLUDE:shared/onboard.md}}
65
37
 
66
38
  ## Los equipos
67
39
 
@@ -1,15 +1,26 @@
1
1
  # Codex CLI
2
2
 
3
- Codex CLI actual expone hooks nativos y pide confianza al detectar hooks nuevos o modificados. Instalar la
4
- configuración del proyecto con:
3
+ Codex CLI expone hooks nativos. Instalar la configuración del proyecto con:
5
4
 
6
5
  ```bash
7
6
  node tools/ops.js automation install . codex
8
7
  ```
9
8
 
10
- El archivo se instala en `.codex/hooks/hooks.json`. La primera sesión revisa y solicita confiar en los hooks;
11
- no se usa `--dangerously-bypass-hook-trust`.
9
+ El archivo se instala en `.codex/hooks.json`. Es una de las cuatro ubicaciones que Codex lee —las otras
10
+ son `.codex/config.toml` y las dos equivalentes bajo `~/.codex/`—; la forma `hooks/hooks.json` es la que
11
+ usa un plugin y no la que lee un repositorio.
12
12
 
13
- Codex carga las instrucciones de proyecto desde el `AGENTS.md` canónico, por lo que no se instala una copia
14
- adicional. `manifest.json` declara esta capacidad y `node tools/ops.js automation doctor . codex` valida el
15
- wiring. Los workflows JS de Claude no se presentan como compatibles con Codex.
13
+ **Instalar no alcanza.** Un hook no gestionado queda registrado pero **no corre** hasta que se lo confía:
14
+ Codex guarda la confianza contra el hash del archivo y saltea en silencio lo nuevo o lo modificado. Abrí
15
+ una sesión y usá `/hooks` para revisarlos y confiarlos; hay que repetirlo cada vez que el wiring cambie,
16
+ porque cambia el hash. No se usa `--dangerously-bypass-hook-trust`.
17
+
18
+ Los `matcher` filtran el **nombre de la herramienta**: los comandos de shell llegan como `Bash` y las
19
+ ediciones como `apply_patch`, `Edit` o `Write`. No son los nombres internos del protocolo.
20
+
21
+ Si actualizás una instalación anterior a este cambio, `.codex/hooks/hooks.json` queda huérfano —Codex
22
+ nunca lo leyó— y se borra a mano.
23
+
24
+ Codex carga las instrucciones de proyecto desde el `AGENTS.md` canónico, por lo que no se instala una
25
+ copia adicional. `manifest.json` declara esta capacidad y `node tools/ops.js automation doctor . codex`
26
+ valida el wiring. Los workflows JS de Claude no se presentan como compatibles con Codex.
@@ -2,22 +2,22 @@
2
2
  "hooks": {
3
3
  "PreToolUse": [
4
4
  {
5
- "matcher": "shell|unified_exec",
5
+ "matcher": "Bash",
6
6
  "hooks": [
7
- { "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-shell.sh" }
7
+ { "type": "command", "command": "{{OPS_ROOT}}/automatization/hooks/guard-shell.sh" }
8
8
  ]
9
9
  },
10
10
  {
11
- "matcher": "apply_patch|files",
11
+ "matcher": "apply_patch|Edit|Write",
12
12
  "hooks": [
13
- { "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-files.sh" }
13
+ { "type": "command", "command": "{{OPS_ROOT}}/automatization/hooks/guard-files.sh" }
14
14
  ]
15
15
  }
16
16
  ],
17
17
  "SessionEnd": [
18
18
  {
19
19
  "hooks": [
20
- { "type": "command", "command": "{{OPS_DIR}}automatization/hooks/guard-planning-drift.sh" }
20
+ { "type": "command", "command": "{{OPS_ROOT}}/automatization/hooks/guard-planning-drift.sh" }
21
21
  ]
22
22
  }
23
23
  ]
@@ -4,7 +4,7 @@
4
4
  "command": "codex",
5
5
  "config": {
6
6
  "source": "hooks.json",
7
- "target": ".codex/hooks/hooks.json"
7
+ "target": ".codex/hooks.json"
8
8
  },
9
9
  "instructions": [
10
10
  {
@@ -7,7 +7,7 @@
7
7
  @{{OPS_DIR}}planning/rules/system/commits.md
8
8
  @{{OPS_DIR}}planning/rules/system/conduct.md
9
9
 
10
- `{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/ops:autobuild` fase por fase; los
10
+ `{{OPS_DIR}}planning/PROTOCOL.md` es la fuente de verdad. Ejecuta `/cauce:autobuild` fase por fase; los
11
11
  workflows JS de Claude son referencia, no un runtime compatible. `{{OPS_DIR}}planning/WIP.md` es el mutex
12
12
  y `{{OPS_DIR}}planning/AWAITING_REVIEW.md` bloquea una corrida nueva.
13
13
 
@@ -27,34 +27,18 @@ node {{OPS_DIR}}tools/ops.js team list
27
27
  ## El arranque
28
28
 
29
29
  En una instancia recién creada nadie le explicó todavía al toolkit qué es este proyecto:
30
- `{{OPS_DIR}}organization/` llega como molde y el roadmap está vacío. El primer recorrido lo llena, y una vez: reescribir un contexto
31
- que alguien ya corrigió no deja rastro de lo que se perdió.
30
+ `{{OPS_DIR}}organization/` llega como molde y el roadmap está vacío. El primer recorrido lo llena, y una
31
+ vez: reescribir un contexto que alguien ya corrigió no deja rastro de lo que se perdió.
32
32
 
33
33
  Empezá por `node {{OPS_DIR}}tools/ops.js onboard`, que es instantáneo: la primera línea que imprime es la
34
- pregunta con la que tenés que abrir —de qué trata el proyecto—, y después vienen el inventario y las
35
- dimensiones. Hacé esa pregunta tal cual antes de mirar nada, sea el workspace vacío, un monorepo o diez
36
- repos, y según lo que conteste formulá hasta tres más con las palabras de ese proyecto, una por vez. El
37
- inventario ya viene resuelto ahí: no recorras el árbol ni leas código para completarlo. No des por
38
- sentado que vende algo: puede sostenerse con donaciones, presupuesto interno o trabajo voluntario. Con eso escribí `{{OPS_DIR}}organization/`, la sección «Mapa real» de
39
- `{{OPS_DIR}}AGENTS.md` con cada comando tal como está declarado y de qué archivo salió —sin correrlo—, y
40
- las raíces reales en `workspaceRoots`. Lo deducido va marcado `(supuesto)`. Credenciales, MCP y el permiso de push van como
41
- filas en `{{OPS_DIR}}planning/HUMAN_ACTIONS.md`, sin proponer valores. Cerrá con `epic-001` en
42
- `{{OPS_DIR}}planning/roadmap/`: que una tarea pueda atravesar el ciclo entero, con criterios que salen de
43
- lo que falta. Nunca la promuevas.
34
+ pregunta con la que tenés que abrir —de qué trata el proyecto—. Hacésela tal cual y esperá la respuesta
35
+ antes de mirar el inventario, sea el workspace vacío, un monorepo o diez repos. Con lo que te conteste
36
+ invocá `/cauce:onboard`, que lleva el recorrido entero y la lista de lo que se comprueba al final;
37
+ invocarlo antes sólo devuelve la misma pregunta más caro.
44
38
 
45
39
  El arranque tiene tres objetivos y ninguno más: entender qué es el proyecto, dejar la instancia correcta
46
40
  para él y que la primera tarea pueda empezar. El análisis profundo viene después, cuando la persona pida
47
- algo concreto.
48
-
49
- Antes de darlo por terminado, comprobá cinco cosas mirando el disco: preguntaste de a una y no en
50
- formulario; la épica se llama `epic-NNN-<slug>.md` con `status: open` —`epic-001.md` no lo lee nadie—;
51
- las secciones de `{{OPS_DIR}}organization/` siguen siendo las del molde y lo tuyo se agregó adentro;
52
- `{{OPS_DIR}}planning/HUMAN_ACTIONS.md` tiene una fila por credencial, por externo y por la autoridad de
53
- push; y `ops` no figura como servicio del producto en el mapa. una dimensión que no
54
- llegaste a preguntar va «Por definir» con su pregunta en `{{OPS_DIR}}planning/INBOX.md` en vez de
55
- completarse deduciéndola, y lo que deducís de otra respuesta va marcado `(supuesto)`. Cerrá corriendo
56
- `node {{OPS_DIR}}tools/ops.js onboard`: si te vuelve a ofrecer la pregunta de apertura, no escribiste
57
- nada y no hay nada que informar. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.
41
+ algo concreto. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.
58
42
 
59
43
  Nunca omitas aprobaciones, inventes credenciales, escribas remoto, hagas push/deploy o promociones
60
44
  trabajo desde INBOX.
@@ -10,13 +10,15 @@ node tools/ops.js automation install . gemini
10
10
  ```
11
11
 
12
12
  Instala `.gemini/settings.json`, el contexto raíz `GEMINI.md` y los comandos `/cauce:onboard`,
13
- `/cauce:team`, `/cauce:autobuild`, `/cauce:integration-sync` y `/cauce:integration-promote`.
13
+ `/cauce:team`, `/cauce:autobuild`, `/cauce:integration-sync` y `/cauce:integration-promote`, más el
14
+ catálogo de cargos como skills en `.gemini/skills/`.
14
15
 
15
16
  Antes vivían bajo `/ops:` y eran tres: el arranque y el recorrido de equipo le llegaban sólo como prosa,
16
17
  así que alguien que venía de otro runner los buscaba en la lista y no estaban. Si actualizás una
17
18
  instalación vieja, `.gemini/commands/ops/` queda huérfano y se borra a mano.
18
19
 
19
- Gemini no anuncia hooks nativos en este adaptador: los guards se ejecutan como prechecks indicados por el
20
- protocolo y los comandos. Esta degradación está declarada en `manifest.json`, no se simula protección inexistente.
20
+ Gemini CLI tiene hooks nativos y el adaptador los usa: `BeforeTool` y `AfterAgent` en
21
+ `.gemini/settings.json`, declarados en `manifest.json`. Sólo corren si la carpeta está marcada como
22
+ confiable —`GEMINI.md` explica qué avisa Gemini cuando no lo está—.
21
23
 
22
24
  Comprueba la instalación con `node tools/ops.js automation doctor . gemini`.
@@ -1,21 +1,4 @@
1
1
  description = "Arranca una instancia recién creada: pregunta, escribe el contexto y la primera épica"
2
2
  prompt = """
3
- Seguí la sección «El arranque» de {{OPS_DIR}}AGENTS.md y de este contexto raíz.
4
-
5
- Empezá por `node {{OPS_DIR}}tools/ops.js onboard`: es instantáneo, y la primera línea que imprime es la
6
- pregunta con la que tenés que abrir. Hacésela tal cual y esperá la respuesta antes de mirar el
7
- inventario, sea el workspace vacío, un monolito o diez repos. Según lo que conteste, hasta tres
8
- preguntas más con las palabras de ese proyecto, una por vez. El inventario ya viene resuelto en esa
9
- salida —servicios, comandos declarados y las variables que cada uno espera—: no recorras el árbol ni
10
- corras comandos del proyecto para completarlo.
11
-
12
- Con eso escribí {{OPS_DIR}}organization/, la sección «Mapa real» de {{OPS_DIR}}AGENTS.md con cada
13
- comando tal como está declarado y de qué archivo salió, y las raíces reales en workspaceRoots. Las
14
- credenciales, los sistemas externos y el permiso de push van como filas de
15
- {{OPS_DIR}}planning/HUMAN_ACTIONS.md, sin proponer ningún valor. Una dimensión que no llegaste a
16
- preguntar queda «Por definir» con su pregunta en {{OPS_DIR}}planning/INBOX.md, y lo que deducís de otra
17
- respuesta va marcado «(supuesto)».
18
-
19
- Cerrá escribiendo la épica como epic-NNN-<slug>.md con status: open —sin slug no la lee nadie— y corré
20
- `node {{OPS_DIR}}tools/ops.js check planning`. Nunca promuevas al BACKLOG: esa firma es humana.
3
+ {{INCLUDE:shared/onboard.md}}
21
4
  """
@@ -2,10 +2,6 @@
2
2
  "schemaVersion": 1,
3
3
  "name": "gemini",
4
4
  "command": "gemini",
5
- "lifecycle": {
6
- "recommendedForNewProjects": false,
7
- "audience": "enterprise-cloud-api-key"
8
- },
9
5
  "config": {
10
6
  "source": "settings.json",
11
7
  "target": ".gemini/settings.json"
@@ -0,0 +1,69 @@
1
+ Es el arranque de una instancia recién creada: `init` la instaló, pero nadie le explicó todavía qué es
2
+ este proyecto.
3
+
4
+ Empezá por `node {{OPS_DIR}}tools/ops.js onboard`, que es instantáneo y te dice tres cosas: si la
5
+ instancia sigue vacía, qué hay en el workspace y con qué pregunta empezar. Si ya tiene contexto escrito,
6
+ no la pises: reescribir lo que alguien corrigió no deja rastro de lo que se perdió.
7
+
8
+ La conversación empieza por una sola pregunta —de qué trata el proyecto, la primera línea que ese comando
9
+ imprime— y la hacés antes de mirar el inventario, sea el workspace vacío, un monorepo o diez repos.
10
+ Sigue según lo que conteste: hasta tres más, formuladas con las palabras de ese proyecto, hasta cubrir
11
+ las dimensiones que el comando haya listado. Una por vez, esperando respuesta. El inventario ya viene
12
+ resuelto: no recorras el árbol ni leas código para completarlo. No des por sentado que vende algo: puede sostenerse con
13
+ donaciones, presupuesto interno o trabajo voluntario, y preguntarle a un proyecto libre quién le paga es
14
+ empezar por una respuesta que nadie dio.
15
+
16
+ El inventario no lo hagas a mano: `node {{OPS_DIR}}tools/ops.js scan --json` devuelve los subproyectos con
17
+ manifiesto propio, su runtime y los comandos que cada uno declara, con el archivo del que salieron.
18
+ Recorrer directorios es determinista y cuesta milisegundos; explorarlo vos cuesta minutos y encuentra lo
19
+ mismo. No corras ningún comando del proyecto: el mapa dice lo que está declarado y de dónde, y
20
+ verificarlo corriéndolo es una historia de la épica, con dueño y tiempo asignado.
21
+
22
+ Con eso escribí `{{OPS_DIR}}organization/company.md` y `product.md`, la sección «Mapa real» de
23
+ `{{OPS_DIR}}AGENTS.md` con el resultado que obtuviste por comando, y las raíces reales en
24
+ `workspaceRoots` de `{{OPS_DIR}}ops.config.json`, que es lo que un guard usa para bloquear una escritura
25
+ fuera de lugar. Lo deducido va marcado `(supuesto)` y lo que nada sostiene queda «Por definir»: no
26
+ inventes clientes, ingresos ni objetivos.
27
+
28
+ Credenciales, MCP y el permiso de push no te corresponden. Cada uno va como fila en
29
+ `{{OPS_DIR}}planning/HUMAN_ACTIONS.md` con la acción concreta que lo desbloquea y sin proponer ningún
30
+ valor; las preguntas abiertas, a la sección Ideas de `{{OPS_DIR}}planning/INBOX.md`.
31
+
32
+ El arranque tiene tres objetivos y ninguno más: entender qué es el proyecto, dejar la instancia correcta
33
+ para él y que la primera tarea pueda empezar. El análisis profundo viene después, cuando la persona pida
34
+ algo concreto.
35
+
36
+ Cerrá escribiendo la épica en `{{OPS_DIR}}planning/roadmap/`: su resultado es que una tarea pueda
37
+ atravesar el ciclo entero, y sus criterios salen de lo que hoy falta —contexto sin supuestos, cada
38
+ comando en verde, el guard de límites probado en las dos direcciones, una tarea piloto en DONE—.
39
+ **Nunca promuevas al BACKLOG**: esa firma es humana.
40
+
41
+ ## Antes de decir que terminaste
42
+
43
+ Esta lista no es un resumen de lo de arriba: es lo que se comprueba mirando el disco. Una corrida real
44
+ falló los cinco puntos sin darse cuenta, y todos del mismo lado —lo que no produce un archivo visible—.
45
+
46
+ 1. **Una pregunta por vez.** Si mandaste dos o más juntas, numeradas, hiciste un formulario. La segunda
47
+ pregunta depende de la primera respuesta: por eso se hacen de a una.
48
+ 2. **`epic-NNN-<slug>.md`**, con slug en el nombre y `status: open`. `epic-001.md` no lo lee nadie: ni
49
+ `check`, ni `tree`, ni el runner que busca trabajo. Existe y no existe a la vez.
50
+ 3. **Las secciones de `{{OPS_DIR}}organization/` son las del molde.** Escribí adentro de ellas; agregá las tuyas
51
+ abajo si hacen falta. Reemplazar la estructura pierde dimensiones que nadie va a reclamar después,
52
+ porque el archivo se lee completo.
53
+ 4. **`HUMAN_ACTIONS.md` tiene filas.** Una por credencial nombrada en el inventario, una por sistema
54
+ externo o MCP, una por la autoridad de push. Si quedó vacío, no es que no hubiera nada: es que lo que
55
+ no te corresponde se perdió en vez de quedar escrito para alguien.
56
+ 5. **`ops` no es un servicio del producto.** No va en el mapa real: es la instancia desde la que trabajás.
57
+
58
+ 6. **Ninguna dimensión se completa sin haberla preguntado.** Si llegaste al tope de preguntas y una
59
+ quedó afuera —quién lo usa, qué está muerto, qué externo hay— va «Por definir» con su pregunta en
60
+ `{{OPS_DIR}}planning/INBOX.md`, aunque puedas imaginar la respuesta. Y lo que deducís de otra
61
+ respuesta va marcado `(supuesto)`, por plausible que sea: sin la marca se lee con el mismo peso que
62
+ lo que la persona dijo, y nadie va a volver a preguntarlo.
63
+ 7. **Corré `node {{OPS_DIR}}tools/ops.js onboard`.** Si te vuelve a ofrecer la pregunta de apertura, la
64
+ instancia sigue vacía: no escribiste nada, y no hay nada que informar. Una corrida real entregó un
65
+ resumen en pasado —«registrado», «documentado», «creada la primera épica»— sobre archivos que en el
66
+ disco seguían siendo el molde. Estar bloqueado es un resultado legítimo; narrarlo como entrega, no.
67
+
68
+ Recién ahí, `node {{OPS_DIR}}tools/ops.js check planning`. Si sale con advertencias, leelas: son
69
+ exactamente estas cosas.
@@ -64,6 +64,55 @@ function mergeConfig(current, incoming) {
64
64
  return incoming
65
65
  }
66
66
 
67
+ // Una entrada de hook que puso Cauce se reconoce por el guard al que apunta: `automatization/hooks/`
68
+ // es nuestro y ninguna empresa escribe ahí. Se saca del archivo del usuario antes de fusionar para que
69
+ // el merge deje exactamente las de esta versión, ni una más.
70
+ const ENTREGADO = /automatization\/hooks\/guard-[a-z-]+\.sh/
71
+
72
+ function withoutDeliveredHooks(config, vigentes) {
73
+ const dropped = []
74
+ const walk = (node) => {
75
+ if (Array.isArray(node)) {
76
+ return node
77
+ .filter((item) => {
78
+ const command = item && typeof item === 'object' ? String(item.command || '') : ''
79
+ if (!ENTREGADO.test(command)) return true
80
+ // Sólo se anuncia lo que ya no vuelve: una entrada que el merge repone quedó igual, y decir
81
+ // que se quitó y se puso la misma línea es ruido que esconde el caso que sí importa.
82
+ if (!vigentes.has(command)) dropped.push(command)
83
+ return false
84
+ })
85
+ .map(walk)
86
+ .filter((item) => !(item && typeof item === 'object' && Array.isArray(item.hooks) && !item.hooks.length))
87
+ }
88
+ if (node && typeof node === 'object') {
89
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, walk(value)]))
90
+ }
91
+ return node
92
+ }
93
+ return { config: walk(config), dropped }
94
+ }
95
+
96
+ // Por qué se fue cada entrada nuestra. Un guard suelto que ahora cubre un grupo no es lo mismo que una
97
+ // ruta que dejó de existir: el primero se ejecutaba dos veces por herramienta —con `verify`, la suite
98
+ // entera del proyecto dos veces por commit—, y el segundo no se ejecutaba nunca. Decirlo distinto es lo
99
+ // único que le permite a alguien darse cuenta de cuál de los dos tenía.
100
+ function reportarQuitadas(name, dropped, vigentes, output) {
101
+ const wrappers = new Map()
102
+ const sueltas = []
103
+ for (const command of dropped) {
104
+ const hit = supersededGuards().find(
105
+ (entry) => command.endsWith(entry.file) && [...vigentes].some((v) => v.endsWith(entry.wrapper)),
106
+ )
107
+ if (hit) wrappers.set(hit.wrapper, [...(wrappers.get(hit.wrapper) || []), hit.file])
108
+ else sueltas.push(command)
109
+ }
110
+ for (const [wrapper, files] of wrappers) {
111
+ output.log(`− ${name}: reemplazado ${[...new Set(files)].join(', ')} por ${wrapper}`)
112
+ }
113
+ for (const command of sueltas) output.log(`− ${name}: quitada una entrada obsoleta (${command})`)
114
+ }
115
+
67
116
  function includesConfig(actual, expected) {
68
117
  if (Array.isArray(expected)) {
69
118
  return Array.isArray(actual) && expected.every((item) => {
@@ -79,7 +128,7 @@ function includesConfig(actual, expected) {
79
128
  }
80
129
 
81
130
  // Dónde abre el dev su herramienta, que no siempre es la raíz ops. En modo sidecar el repo ops es
82
- // un hermano de los repos de producto: `aparatejo-ops/` coordina, `aparatejo/` es lo que se abre.
131
+ // un hermano de los repos de producto: `<empresa>-ops/` coordina, `<empresa>/` es lo que se abre.
83
132
  // Instalar dentro del sidecar dejaría al runner sin ver una sola línea de código.
84
133
  function installRoot(root) {
85
134
  try {
@@ -89,7 +138,7 @@ function installRoot(root) {
89
138
  return root
90
139
  }
91
140
 
92
- // Cómo se nombra la raíz ops desde ahí: `aparatejo-ops/` en sidecar, vacío cuando coinciden.
141
+ // Cómo se nombra la raíz ops desde ahí: `<empresa>-ops/` en sidecar, vacío cuando coinciden.
93
142
  function opsPrefix(root) {
94
143
  const relative = path.relative(installRoot(root), root)
95
144
  return relative ? `${relative.split(path.sep).join('/')}/` : ''
@@ -114,6 +163,8 @@ function runnerPaths(root, name, runner) {
114
163
 
115
164
  function resolveItem(paths, root, name, item) {
116
165
  return {
166
+ automationRoot: paths.automationRoot,
167
+ opsRoot: root,
117
168
  source: F.assertWithin(
118
169
  paths.automationRoot,
119
170
  path.resolve(paths.sourceDir, item.source),
@@ -135,12 +186,42 @@ function resolveItem(paths, root, name, item) {
135
186
  // Un solo render, y `install` escribe exactamente lo que `doctor` compara.
136
187
  const OPS_DIR = '{{OPS_DIR}}'
137
188
 
138
- function render(file, prefix) {
139
- return fs.readFileSync(file, 'utf8').split(OPS_DIR).join(prefix)
189
+ // Un fragmento que varios adaptadores comparten, resuelto contra la raíz de `automatization/`. El
190
+ // arranque es el mismo trabajo en tres formatos —la sección de un `AGENTS.md`, el cuerpo de un
191
+ // `SKILL.md`, el prompt de un `.toml`—, y escrito tres veces hizo lo que hace siempre una copia: dos
192
+ // de ellas anunciaban cinco puntos y enumeraban seis, con el sexto doblado dentro del quinto.
193
+ //
194
+ // El workflow de Claude queda afuera a propósito: es un programa con fases y esquemas, no una prosa
195
+ // enmarcada, así que su arranque no es una copia de éste sino otra cosa.
196
+ //
197
+ // Se resuelve antes que `{{OPS_DIR}}` para que el fragmento también reciba el prefijo, y no anida:
198
+ // lo incluido se copia tal cual.
199
+ const INCLUDE = /\{\{INCLUDE:([^}]+)\}\}/g
200
+
201
+ function inline(text, automationRoot) {
202
+ return text.replace(INCLUDE, (_, relative) => {
203
+ const shared = F.assertWithin(
204
+ automationRoot,
205
+ path.resolve(automationRoot, relative.trim()),
206
+ 'INCLUDE',
207
+ )
208
+ return fs.readFileSync(shared, 'utf8').trimEnd()
209
+ })
210
+ }
211
+
212
+ // `{{OPS_ROOT}}` es la raíz absoluta. La necesita quien no puede deducirla de dónde lo ejecutaron
213
+ // —el puente de Antigravity—, y por eso no reemplaza a `{{OPS_DIR}}`: una ruta absoluta escrita en un
214
+ // archivo se rompe si el proyecto se mueve, así que la lleva sólo el que se queda sin alternativa.
215
+ const OPS_ROOT = '{{OPS_ROOT}}'
216
+
217
+ function render(file, prefix, automationRoot, opsRoot = '') {
218
+ return inline(fs.readFileSync(file, 'utf8'), automationRoot)
219
+ .split(OPS_ROOT).join(opsRoot)
220
+ .split(OPS_DIR).join(prefix)
140
221
  }
141
222
 
142
223
  function runnerConfig(paths, root) {
143
- return JSON.parse(render(paths.configSource, opsPrefix(root)))
224
+ return JSON.parse(render(paths.configSource, opsPrefix(root), paths.automationRoot, root))
144
225
  }
145
226
 
146
227
  // Cargos del catálogo, con el frontmatter que el runner indexa para elegir a quién invocar.
@@ -181,6 +262,18 @@ function installRoleSkills(root, runner, output) {
181
262
  const install = installRoot(root)
182
263
  const base = F.assertWithin(install, path.resolve(install, runner.roleSkills), `${runner.name}: roleSkills`)
183
264
  const roles = roleCatalog(root)
265
+ // Los cargos y los recorridos comparten el espacio de nombres de skills del runner, así que un cargo
266
+ // que se llame como un recorrido lo pisa. Hoy no pasa, y por eso mismo hay que detenerlo acá: el
267
+ // catálogo de una empresa es suyo, nadie le prohíbe un cargo `team`, y el daño sería que `/cauce:team`
268
+ // deje de existir sin que nada falle. Renombrar el cargo es la salida, y sólo la puede tomar alguien.
269
+ const recorridos = new Set((runner.commands && runner.commands.names) || [])
270
+ const chocan = roles.filter((role) => recorridos.has(role.slug)).map((role) => role.slug)
271
+ if (chocan.length) {
272
+ throw new Error(
273
+ `${runner.name}: ${chocan.join(', ')} es a la vez un cargo y un recorrido, y comparten `
274
+ + `${runner.roleSkills}. Renombrá el cargo en agents/roles/ antes de instalar.`,
275
+ )
276
+ }
184
277
  for (const role of roles) {
185
278
  const file = path.join(base, role.slug, 'SKILL.md')
186
279
  F.assertNoSymlinkPath(install, file)
@@ -329,42 +422,6 @@ function supersededGuards() {
329
422
  return entries
330
423
  }
331
424
 
332
- // Reemplaza las entradas sueltas que este mismo toolkit escribió por el grupo que ya las cubre.
333
- // Sin esto, una instalación previa ejecuta cada guard dos veces por herramienta: con `verify` eso
334
- // significa correr la suite de tests del proyecto dos veces en cada commit.
335
- function pruneSupersededHooks(config) {
336
- const registered = JSON.stringify(config)
337
- const targets = supersededGuards().filter((entry) => registered.includes(entry.wrapper))
338
- const replaced = new Map()
339
- if (!targets.length) return { config, replaced: [] }
340
-
341
- const isEmptyEntry = (item) => item && typeof item === 'object'
342
- && Array.isArray(item.hooks) && !item.hooks.length
343
- const walk = (node) => {
344
- if (Array.isArray(node)) {
345
- const kept = []
346
- for (const item of node) {
347
- const command = item && typeof item === 'object' ? String(item.command || '') : ''
348
- const hit = targets.find((entry) => command.endsWith(entry.file))
349
- if (hit) {
350
- replaced.set(hit.wrapper, [...(replaced.get(hit.wrapper) || []), hit.file])
351
- continue
352
- }
353
- const walked = walk(item)
354
- if (!isEmptyEntry(walked)) kept.push(walked)
355
- }
356
- return kept
357
- }
358
- if (node && typeof node === 'object') {
359
- return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, walk(value)]))
360
- }
361
- return node
362
- }
363
-
364
- const pruned = walk(config)
365
- return { config: pruned, replaced: [...replaced].map(([wrapper, files]) => ({ wrapper, files })) }
366
- }
367
-
368
425
  // Wiring heredado: guards que ahora corren agrupados pero siguen registrados uno por uno.
369
426
  // Conviven sin romper nada, a costa de ejecutar el guard dos veces por herramienta.
370
427
  function legacyGuardWiring(config) {
@@ -406,8 +463,10 @@ function probeBridge(paths, runner) {
406
463
  // Cada evento tiene su respuesta sana: `stop` cierra la sesión —eso es funcionar— y el resto deja
407
464
  // pasar. El puente falla cerrado, así que `deny` en una llamada inocua es que algo se rompió antes
408
465
  // de poder juzgarla, y `continue` es el camino de error del propio `stop`.
466
+ // Y sin `reason`: el puente sólo la manda cuando algo falló, así que un `stop` que la trae es
467
+ // una falla de infraestructura que se dejó cerrar la sesión, no un arranque sano.
409
468
  const sana = evento === 'stop' ? 'stop' : 'allow'
410
- if (respuesta.decision === sana) continue
469
+ if (respuesta.decision === sana && !respuesta.reason) continue
411
470
  const salida = (result.stderr || 'sin respuesta').trim().split('\n')[0]
412
471
  const motivo = respuesta.reason
413
472
  || (respuesta.decision ? `respondió ${respuesta.decision}` : salida)
@@ -442,7 +501,7 @@ function doctor(root, name, output = console) {
442
501
  // que su hash difiere siempre. Comparado como archivo, `doctor` avisaba cuando el bloque estaba bien
443
502
  // y callaba cuando alguien lo había borrado, que es exactamente al revés.
444
503
  if (esArchivoCompartido(root, resolved.target)) {
445
- const contenido = render(resolved.source, opsPrefix(root))
504
+ const contenido = render(resolved.source, opsPrefix(root), resolved.automationRoot, resolved.opsRoot)
446
505
  if (!fs.existsSync(resolved.target)) errors.push(`falta ${item.target}`)
447
506
  else if (!fs.readFileSync(resolved.target, 'utf8').includes(bloqueInicio(name))) {
448
507
  errors.push(`${item.target}: no tiene las instrucciones de Cauce; reinstalá el adaptador`)
@@ -529,7 +588,8 @@ function deliveryKey(name, target) {
529
588
  function deliveryState(recorded, name, resolved, prefix = '') {
530
589
  if (!fs.existsSync(resolved.target)) return 'nuevo'
531
590
  const current = M.digest(resolved.target)
532
- if (current === M.digestText(render(resolved.source, prefix))) return 'al día'
591
+ const esperado = render(resolved.source, prefix, resolved.automationRoot, resolved.opsRoot)
592
+ if (current === M.digestText(esperado)) return 'al día'
533
593
  const delivered = recorded[deliveryKey(name, resolved.item.target)]
534
594
  return delivered && delivered === current ? 'desactualizado' : 'ajeno'
535
595
  }
@@ -720,11 +780,15 @@ function install(root, name, output = console, options = {}) {
720
780
  + 'configuración de tu runner. Si el cambio ya no te sirve, repetí con --force.',
721
781
  )
722
782
  }
723
- const merged = pruneSupersededHooks(mergeConfig(current, incoming))
724
- F.atomicWriteJson(paths.configTarget, merged.config)
725
- for (const { wrapper, files } of merged.replaced) {
726
- output.log(`− ${name}: reemplazado ${[...new Set(files)].join(', ')} por ${wrapper}`)
727
- }
783
+ // Un archivo que existe sólo porque Cauce lo creó se escribe entero. Los `settings.json` de Claude y
784
+ // Gemini son del usuario, así que ahí se fusiona; pero fusionar conserva también lo que pusimos en
785
+ // una versión anterior, y una entrada nuestra que quedó viva apuntando a donde ya no hay nada es un
786
+ // guard que el runner intenta ejecutar y falla. Se quitan las nuestras y las vuelve a poner el merge.
787
+ const vigentes = new Set((JSON.stringify(incoming).match(/"command":"[^"]*"/g) || [])
788
+ .map((entry) => JSON.parse(`{${entry}}`).command))
789
+ const limpio = runner.config.owned ? { config: {}, dropped: [] } : withoutDeliveredHooks(current, vigentes)
790
+ reportarQuitadas(name, limpio.dropped, vigentes, output)
791
+ F.atomicWriteJson(paths.configTarget, mergeConfig(limpio.config, incoming))
728
792
  // Dónde aterrizó, no sólo qué archivo: en sidecar el destino no es el repo desde el que se corrió
729
793
  // el comando, y descubrirlo por sorpresa es la diferencia entre confiar y adivinar.
730
794
  if (paths.install !== root) {
@@ -736,7 +800,7 @@ function install(root, name, output = console, options = {}) {
736
800
  const situacion = state.get(resolved)
737
801
  const propio = runner.instructions.includes(resolved.item)
738
802
  if (propio && esArchivoCompartido(root, resolved.target)) {
739
- const contenido = render(resolved.source, opsPrefix(root))
803
+ const contenido = render(resolved.source, opsPrefix(root), resolved.automationRoot, resolved.opsRoot)
740
804
  if (bloqueAlDia(resolved.target, name, contenido)) {
741
805
  output.log(`= ${name}: ${resolved.item.target} ya trae sus instrucciones`)
742
806
  } else {
@@ -752,7 +816,8 @@ function install(root, name, output = console, options = {}) {
752
816
  output.log(`= ${name}: ${resolved.item.target} ya está al día`)
753
817
  } else {
754
818
  fs.mkdirSync(path.dirname(resolved.target), { recursive: true })
755
- F.atomicWrite(resolved.target, render(resolved.source, opsPrefix(root)))
819
+ const escrito = render(resolved.source, opsPrefix(root), resolved.automationRoot, resolved.opsRoot)
820
+ F.atomicWrite(resolved.target, escrito)
756
821
  const verbo = situacion === 'nuevo' ? 'instalado' : 'actualizado'
757
822
  output.log(`✓ ${name}: ${verbo} ${resolved.item.target}`)
758
823
  }
@@ -798,6 +863,7 @@ module.exports = {
798
863
  legacyGuardWiring,
799
864
  roleCatalog,
800
865
  roleSkill,
866
+ render,
801
867
  listHooks,
802
868
  runnerManifest,
803
869
  }
package/engine/cli/ops.js CHANGED
@@ -831,13 +831,19 @@ function destroy(dir, cli) {
831
831
  perdida.contexto && 'el contexto escrito en organization/',
832
832
  ].filter(Boolean)
833
833
 
834
+ const embebido = O.mode(root) === 'embedded'
834
835
  if (!cli.has('--force')) {
835
- console.log(`Borrar ${root} se lleva:`)
836
+ console.log(embebido
837
+ ? `Sacar Cauce de ${root} se lleva:`
838
+ : `Borrar ${root} se lleva:`)
836
839
  for (const linea of lineas) console.log(` − ${linea}`)
837
840
  if (!lineas.length) console.log(' − nada escrito todavía: la instancia está como salió de init')
838
841
  if (perdida.runners.length) {
839
842
  console.log(` y saca el wiring de: ${perdida.runners.join(', ')} (lo tuyo queda donde está)`)
840
843
  }
844
+ if (embebido) {
845
+ console.log(' el código del repositorio no se toca: en modo embebido la instancia es él mismo.')
846
+ }
841
847
  console.log('\nNada de esto lo repone un init. Si es lo que querés: repetí con --force.')
842
848
  process.exit(1)
843
849
  }
@@ -847,6 +853,17 @@ function destroy(dir, cli) {
847
853
  }
848
854
  // El orden no es negociable: primero el wiring, después la carpeta. Al revés, cada llamada de
849
855
  // herramienta del runner queda ejecutando un guard que ya no está.
856
+ // En modo embebido la instancia **es** el repositorio: borrar la carpeta se lleva el código del
857
+ // producto, que Cauce nunca escribió y no repone nadie. Ahí se saca lo del toolkit y se deja el repo.
858
+ if (O.mode(root) === 'embedded') {
859
+ const suyo = [
860
+ 'planning', 'organization', 'teams', 'integrations', 'automatization', 'tools',
861
+ 'ops.config.json', '.cauce', 'AGENTS.md',
862
+ ]
863
+ for (const relative of suyo) fs.rmSync(path.join(root, relative), { recursive: true, force: true })
864
+ console.log(`✓ ${suyo.length} ruta(s) de Cauce quitadas de ${root}`)
865
+ return console.log(' tu repositorio queda donde está: en modo embebido la instancia era él mismo.')
866
+ }
850
867
  const adentro = process.cwd() === root || process.cwd().startsWith(`${root}${path.sep}`)
851
868
  fs.rmSync(root, { recursive: true, force: true })
852
869
  console.log(`✓ ${root} borrado`)
@@ -1269,7 +1286,8 @@ function automation(action, rootArg, runnerName, cli) {
1269
1286
  const force = cli.has('--force')
1270
1287
  try { runner = A.install(root, runnerName, console, { force }) } catch (error) { fail(error.message, 2) }
1271
1288
  if (runnerName === 'codex') {
1272
- console.log(' Codex pedirá revisar y confiar en hooks nuevos al iniciar sesión.')
1289
+ console.log(' Codex deja los hooks nuevos sin correr hasta que los confíes: abrí una sesión')
1290
+ console.log(' y usá /hooks para revisarlos y marcarlos como confiables.')
1273
1291
  }
1274
1292
  if (!runner.capabilities.nativeHooks) {
1275
1293
  console.log(` ${runnerName} no expone hooks nativos; aplica guards como prechecks.`)
@@ -29,12 +29,23 @@ function fileOf(input) {
29
29
  || input.file_path || input.path || process.env.OPS_HOOK_FILE || '')
30
30
  }
31
31
 
32
+ // El sobre de `apply_patch`, venga por donde venga. Codex lo manda entero como `command` en vez de
33
+ // `patch`, y sin reconocerlo ahí el guard de archivos no ve ni un archivo: mira una escritura que
34
+ // reemplaza una migración o filtra una credencial y la deja pasar sin decir nada. Se exige el
35
+ // encabezado en vez de aceptar cualquier `command`, para no leer un comando de shell como si fuera
36
+ // contenido de archivo.
37
+ function patchOf(input) {
38
+ const campos = input.tool_input || {}
39
+ const command = String(campos.command || '')
40
+ const sobre = command.startsWith('*** Begin Patch') ? command : ''
41
+ return String(campos.patch || campos.input || input.patch || sobre || '')
42
+ }
43
+
32
44
  function filesOf(input) {
33
45
  const files = new Set()
34
46
  const direct = fileOf(input)
35
47
  if (direct) files.add(direct)
36
- const patch = String(input.tool_input && (input.tool_input.patch || input.tool_input.input)
37
- || input.patch || '')
48
+ const patch = patchOf(input)
38
49
  for (const match of patch.matchAll(/^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$/gm)) files.add(match[1].trim())
39
50
  return [...files]
40
51
  }
@@ -46,7 +57,7 @@ function contentOf(input) {
46
57
  || input.tool_input.patch
47
58
  || input.tool_input.input
48
59
  )
49
- || input.content || input.patch || '')
60
+ || input.content || input.patch || patchOf(input) || '')
50
61
  }
51
62
 
52
63
  function cwdOf(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",