@ingeniomaps/cauce 0.9.1 → 0.10.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
@@ -8,6 +8,34 @@ esa operación sea confiable en vez de sólo cómoda: acá se lee qué cambió a
8
8
  un cambio en el protocolo, en las reglas del sistema o en un guard es visible para el usuario y sube
9
9
  minor aunque no toque una sola línea de código.
10
10
 
11
+ ## [0.10.0] - 2026-08-15
12
+
13
+ ### Cambiado
14
+
15
+ - **El motor llega siempre como dependencia. Se retiró el modo copia.** `--engine copy|dependency` ya
16
+ no existe: `init` declara `@ingeniomaps/cauce` en el `package.json` del repo ops, creándolo si hace
17
+ falta.
18
+
19
+ La copia en `.ops/` existía para no exigirle npm a un repo de Go, Python o Rust. Dejó de tener
20
+ sentido cuando el repo ops pasó a ser un **sidecar**, hermano de los repos de producto: declarar npm
21
+ ahí no le impone un stack a ninguno. Y Node hace falta igual —el motor, los guards y los workflows
22
+ son JavaScript—, así que la copia sólo ahorraba un `package.json` de seis líneas a cambio de 5 MB y
23
+ 763 archivos en la historia de la empresa, y de no tener cómo enterarse de que salió una versión
24
+ nueva: sin npm no hay `npm outdated`.
25
+ - Los tres resolutores en cascada —`tools/ops.js`, `run-hook.sh` y el motor— pasan de tres caminos a
26
+ dos. Menos superficie donde esconder un caso raro.
27
+ - Una instancia que arrastra `.ops/` **no se toca**: `upgrade` avisa que Cauce ya no lo distribuye y
28
+ dice qué correr. Borrarlo por su cuenta la dejaría sin motor.
29
+ - El `$schema` de `ops.config.json` ya no depende del modo.
30
+
31
+ ## [0.9.2] - 2026-08-15
32
+
33
+ ### Corregido
34
+
35
+ - Una instancia ya no recibe un `.github/workflows/` vacío. `init` lo copiaba salteando los dos
36
+ únicos archivos que existen —`ci.yml` valida el toolkit y el ciclo de aprendizaje dejó de
37
+ distribuirse en 0.4.0—, así que creaba dos directorios y no ponía nada adentro.
38
+
11
39
  ## [0.9.1] - 2026-08-15
12
40
 
13
41
  ### Corregido
package/README.md CHANGED
@@ -35,11 +35,12 @@ node engine/cli/ops.js team show product-development
35
35
  El destino debe estar vacío o no existir. En modo embebido normalmente ya es un repo: `--force` permite
36
36
  completar archivos faltantes, pero nunca sobrescribe archivos existentes.
37
37
 
38
- El motor llega de dos formas y `init` elige sola: si el destino tiene `package.json`, lo declara como
39
- dependencia y el lockfile fija la versión; si no —un repo Go, Python o Rust—, copia el runtime en
40
- `.ops/engine`, porque exigir npm para leer un planning sería imponer un stack. `--engine copy|dependency`
41
- fuerza cualquiera de las dos. En ambos casos el proyecto invoca `node tools/ops.js`, que resuelve el motor
42
- donde esté.
38
+ El motor llega como dependencia y el lockfile fija la versión. `init` declara `@ingeniomaps/cauce` en el
39
+ `package.json` del repo ops —creándolo si no existey el proyecto invoca `node tools/ops.js`, que resuelve
40
+ el motor sin que nadie tenga que saber dónde está.
41
+
42
+ Declarar npm ahí no le impone un stack a nadie: el repo ops es un sidecar, hermano de los repos de
43
+ producto, y Node hace falta igual —el motor, los guards y los workflows son JavaScript—.
43
44
 
44
45
  Dentro de un proyecto generado, el CLI autocontenido se invoca con `node tools/ops.js`. En la tabla siguiente,
45
46
  `ops` representa cualquiera de esas dos formas según el contexto. El binario `cauce` también queda
@@ -169,8 +170,8 @@ y actualizar no exige resolver conflictos: se reemplaza `system/` entero y nada
169
170
 
170
171
  ### Dónde vive cada cosa del catálogo
171
172
 
172
- Los cargos que trae Cauce **no se copian al proyecto**: se resuelven desde la dependencia, o desde
173
- `.ops/agents/` cuando el repo no usa npm. Evolucionan como profesión, y esa evolución es la misma para
173
+ Los cargos que trae Cauce **no se copian al proyecto**: se resuelven desde la dependencia. Evolucionan
174
+ como profesión, y esa evolución es la misma para
174
175
  todas las empresas: investigarla una vez y bien es mejor que repetirla en cada instalación.
175
176
 
176
177
  | Qué | Dónde | Quién lo mantiene |
@@ -232,7 +233,7 @@ promoción y validación no se reimplementan. Consulta [integrations/README.md](
232
233
 
233
234
  El toolkit no guarda contexto real de ninguna empresa. `template/organization/` es el molde que cada proyecto
234
235
  recibe como `organization/`. De igual forma, `planning/` pertenece a la instancia generada: conserva su
235
- intención, estado y evidencia, mientras el motor reusable permanece en `.ops/engine/`.
236
+ intención, estado y evidencia, mientras el motor reusable permanece en la dependencia.
236
237
 
237
238
  ## Hooks y runners
238
239
 
@@ -8,7 +8,7 @@ implementa y conecta ese proceso con runners concretos: no redefine producto, pr
8
8
 
9
9
  | Ruta | Responsabilidad | Fuente canónica |
10
10
  |---|---|---|
11
- | `hooks/` | Entradas ejecutables estables para los guards. | La lógica vive una sola vez en `../.ops/engine/hooks/run.js` en una instancia o `../engine/hooks/run.js` en el toolkit. |
11
+ | `hooks/` | Entradas ejecutables estables para los guards. | La lógica vive una sola vez en `engine/hooks/run.js`, que llega con la dependencia. |
12
12
  | `workflows/` | Recorridos portables de build e integraciones. | Las fases y gates pertenecen a `../planning/PROTOCOL.md`. |
13
13
  | `runners/` | Wiring, capacidades e instalación por herramienta. | Cada adaptador declara su contrato en `manifest.json`. |
14
14
 
@@ -25,7 +25,7 @@ o automatization/hooks/guard-<nombre>.sh (guard suelto)
25
25
  ↓ nombre del grupo o del guard
26
26
  automatization/hooks/run-hook.sh
27
27
  ↓ localiza el runtime
28
- .ops/engine/hooks/run.js (proyecto instalado)
28
+ node_modules/@ingeniomaps/cauce/engine/hooks/run.js (instancia)
29
29
  o engine/hooks/run.js (repositorio del toolkit)
30
30
  ```
31
31
 
@@ -8,12 +8,11 @@ ops_root=$(CDPATH= cd -- "$hook_dir/../.." && pwd)
8
8
  # abre en la carpeta de la compañía y la raíz ops es un hermano, que ninguna búsqueda hacia arriba
9
9
  # encuentra: sin esto el guard no halla `ops.config.json` y deja pasar todo en silencio.
10
10
  export OPS_ROOT="$ops_root"
11
- # Mismo orden que tools/ops.js: primero la dependencia npm, después la copia local, y por último
12
- # el propio repositorio del toolkit. Un guard que no encuentra su motor bloquea, nunca permite.
11
+ # Mismo orden que tools/ops.js: la dependencia npm y, por último, el propio repositorio del toolkit.
12
+ # Un guard que no encuentra su motor bloquea, nunca permite.
13
13
  runner=""
14
14
  for candidate in \
15
15
  "$ops_root/node_modules/@ingeniomaps/cauce/engine/hooks/run.js" \
16
- "$ops_root/.ops/engine/hooks/run.js" \
17
16
  "$ops_root/engine/hooks/run.js"
18
17
  do
19
18
  if [ -f "$candidate" ]; then runner="$candidate"; break; fi
@@ -21,7 +20,7 @@ done
21
20
 
22
21
  if [ -z "$runner" ]; then
23
22
  echo "BLOQUEADO [$hook_name]: no se encontró el motor de hooks de Cauce." >&2
24
- echo " Buscado en node_modules/@ingeniomaps/cauce, .ops/engine y engine/ bajo $ops_root" >&2
23
+ echo " Buscado en node_modules/@ingeniomaps/cauce y engine/ bajo $ops_root" >&2
25
24
  exit 2
26
25
  fi
27
26
 
@@ -44,10 +44,9 @@ function findRoot(input) {
44
44
  }
45
45
 
46
46
  function runtimeAt(root) {
47
- // Mismo orden que tools/ops.js: dependencia npm, copia local, repositorio del toolkit.
47
+ // Mismo orden que tools/ops.js: dependencia npm y, por último, el repositorio del toolkit.
48
48
  const candidates = [
49
49
  path.join(root, 'node_modules', '@ingeniomaps', 'cauce', 'engine', 'hooks', 'run.js'),
50
- path.join(root, '.ops', 'engine', 'hooks', 'run.js'),
51
50
  path.join(root, 'engine', 'hooks', 'run.js'),
52
51
  ]
53
52
  // El bridge corre antes de poder cargar el motor, así que repite la cascada de
@@ -17,8 +17,7 @@ Antes de cerrar, corré `node {{OPS_DIR}}tools/ops.js check {{OPS_DIR}}planning`
17
17
 
18
18
  ## Los cargos
19
19
 
20
- El catálogo está en `{{OPS_DIR}}node_modules/@ingeniomaps/cauce/agents/` —o en
21
- `{{OPS_DIR}}.ops/agents/` si este repo no usa npm— y los propios de la empresa en
20
+ El catálogo está en `{{OPS_DIR}}node_modules/@ingeniomaps/cauce/agents/` y los propios de la empresa en
22
21
  `{{OPS_DIR}}agents/`, que mandan sobre los del sistema con el mismo nombre.
23
22
 
24
23
  Cada cargo tiene un `SKILL.md` con su contrato: cuándo actúa, qué decide, qué no le corresponde y cuál es
@@ -29,7 +29,7 @@ function runnerManifest(root, name) {
29
29
  }
30
30
  const packaged = packagedAutomation(root)
31
31
  if (!packaged) {
32
- throw new Error('no encuentro automatization/: instalá la dependencia o restaurá .ops/')
32
+ throw new Error('no encuentro automatization/: corré "npm install" en la raíz del repo ops')
33
33
  }
34
34
  const file = path.join(packaged, 'runners', name, 'manifest.json')
35
35
  try { return JSON.parse(fs.readFileSync(file, 'utf8')) } catch (error) {
@@ -261,14 +261,14 @@ function check(root) {
261
261
  }
262
262
  // El motor puede venir de la dependencia npm, de la copia local o del propio repositorio.
263
263
  if (!O.engineAt(root, path.join('hooks', 'run.js'))) {
264
- errors.push('falta engine/hooks/run.js: instalá la dependencia o restaurá .ops/engine')
264
+ errors.push('falta engine/hooks/run.js: corré "npm install" en la raíz del repo ops')
265
265
  }
266
266
  const workflows = ['autobuild.js', 'team.js', path.join('integrations', 'sync.js')]
267
267
  workflows.push(path.join('integrations', 'promote.js'))
268
268
  const packaged = packagedAutomation(root)
269
269
  for (const name of workflows) {
270
270
  if (!packaged || !fs.existsSync(path.join(packaged, 'workflows', name))) {
271
- errors.push(`falta automatization/workflows/${name}: instalá la dependencia o restaurá .ops/`)
271
+ errors.push(`falta automatization/workflows/${name}: corré "npm install" en la raíz del repo ops`)
272
272
  }
273
273
  }
274
274
  for (const { file, edited } of staleHooks(root)) {
package/engine/cli/ops.js CHANGED
@@ -26,7 +26,7 @@ function fail(message, code = 1) {
26
26
 
27
27
  function usage() {
28
28
  console.log(`Uso:
29
- ops init <destino> [--name <nombre>] [--mode embedded|sidecar] [--engine copy|dependency] [--force]
29
+ ops init <destino> [--name <nombre>] [--mode embedded|sidecar] [--force]
30
30
  ops check <planning-dir> [--json]
31
31
  ops tree <planning-dir> [--no-color] [--json]
32
32
  ops context <planning-dir> [--json]
@@ -56,7 +56,7 @@ function usage() {
56
56
  }
57
57
 
58
58
  // Banderas que consumen el argumento siguiente: su valor no es un posicional.
59
- const VALUED_FLAGS = new Set(['--name', '--mode', '--engine', '--fixture'])
59
+ const VALUED_FLAGS = new Set(['--name', '--mode', '--fixture'])
60
60
 
61
61
  // Los posicionales del comando, salteando banderas y sus valores. Leer `process.argv` crudo hacía
62
62
  // que `agents list --json` tomara `--json` como la raíz: el catálogo salía vacío, sin error, y quien
@@ -157,13 +157,9 @@ function init(target) {
157
157
  '{{PLANNING_DIR}}': 'planning',
158
158
  '{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
159
159
  }, process.argv.includes('--force'), providerNames())
160
- // `ci.yml` valida el toolkit con `npm run ci`; una instancia no tiene ese script ni sus pruebas.
161
- copyTemplate(path.join(PROJECT_ROOT, '.github', 'workflows'), path.join(root, '.github', 'workflows'), {
162
- '{{PROJECT_NAME}}': name,
163
- '{{MODE}}': mode,
164
- '{{PLANNING_DIR}}': 'planning',
165
- '{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
166
- }, process.argv.includes('--force'), ['ci.yml', 'agent-learning.yml'])
160
+ // No se copia `.github/`: `ci.yml` valida el toolkit con `npm run ci` —que una instancia no tiene y
161
+ // el ciclo de aprendizaje dejó de distribuirse en 0.4.0. Copiar salteando los dos únicos archivos
162
+ // que existen dejaba `.github/workflows/` vacío en cada instancia.
167
163
  const preserve = process.argv.includes('--force')
168
164
  copyRuntime(
169
165
  path.join(PROJECT_ROOT, 'automatization', 'hooks'),
@@ -172,29 +168,13 @@ function init(target) {
172
168
  root,
173
169
  )
174
170
  const version = require(path.join(PROJECT_ROOT, 'package.json')).version
175
- // Un repo con npm recibe el motor como dependencia versionada; uno de Go, Python o Rust recibe
176
- // la copia, porque exigirle un package.json para correr su planning sería imponerle un stack.
177
- const manifest = path.join(root, 'package.json')
178
- const engineMode = option('--engine', fs.existsSync(manifest) ? 'dependency' : 'copy')
179
- if (!['copy', 'dependency'].includes(engineMode)) fail('--engine debe ser copy o dependency.', 2)
180
- if (engineMode === 'dependency') declareEngine(manifest, version)
181
- else {
182
- const engine = path.join(root, '.ops', 'engine')
183
- copyRuntime(path.join(PROJECT_ROOT, 'engine'), engine, preserve, root)
184
- fs.chmodSync(path.join(engine, 'cli', 'ops.js'), 0o755)
185
- // Sin npm no hay de dónde leer el catálogo en tiempo de ejecución: viaja junto al motor, igual
186
- // que los adaptadores de runner y los workflows que instala `automation install`.
187
- copyRuntime(path.join(PROJECT_ROOT, 'agents'), path.join(root, '.ops', 'agents'), preserve, root)
188
- copyRuntime(path.join(PROJECT_ROOT, 'teams'), path.join(root, '.ops', 'teams'), preserve, root)
189
- for (const name of ['runners', 'workflows']) {
190
- copyRuntime(
191
- path.join(PROJECT_ROOT, 'automatization', name),
192
- path.join(root, '.ops', 'automatization', name),
193
- preserve,
194
- root,
195
- )
196
- }
197
- }
171
+ // El motor siempre llega como dependencia. La alternativa era vendorizarlo en `.ops/`, y no valía:
172
+ // Node hace falta igual en los dos casos —el motor, los guards y los workflows son JavaScript—, así
173
+ // que la copia sólo ahorraba este `package.json` de seis líneas a cambio de 5 MB en la historia de
174
+ // la empresa y de no tener cómo enterarse de que salió una versión nueva.
175
+ //
176
+ // El repo ops es un sidecar: declarar npm acá no convierte en Node al servicio de Go de al lado.
177
+ declareEngine(path.join(root, 'package.json'), version)
198
178
  let entregado = {}
199
179
  for (const relative of O.trackedPaths()) {
200
180
  const dir = path.join(root, relative)
@@ -205,13 +185,10 @@ function init(target) {
205
185
  const configFile = path.join(root, 'ops.config.json')
206
186
  const config = JSON.parse(fs.readFileSync(configFile, 'utf8'))
207
187
  config.cauceVersion = version
208
- // El esquema vive donde quedó el motor; la plantilla no puede saberlo de antemano.
209
- config.$schema = engineMode === 'dependency'
210
- ? 'node_modules/@ingeniomaps/cauce/engine/schemas/ops-config.schema.json'
211
- : '.ops/engine/schemas/ops-config.schema.json'
188
+ config.$schema = 'node_modules/@ingeniomaps/cauce/engine/schemas/ops-config.schema.json'
212
189
  F.atomicWriteJson(configFile, config)
213
190
  console.log(`\n✓ ${name}: sistema ops creado en ${root}`)
214
- if (engineMode === 'dependency') console.log(' siguiente: npm install (el motor viene de la dependencia)')
191
+ console.log(' siguiente: npm install (el motor viene de la dependencia)')
215
192
  console.log(` siguiente: node ${path.join(root, 'tools', 'ops.js')} check ${path.join(root, 'planning')}`)
216
193
  }
217
194
 
@@ -569,9 +546,6 @@ function upgrade(dir) {
569
546
  const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
570
547
  if (!fs.existsSync(origin)) continue
571
548
  const target = path.join(root, relative)
572
- // `.ops/` es el paquete vendorizado de una instancia sin npm. Si no existe, esta instancia lo
573
- // toma de la dependencia y crearlo sería duplicar lo que el lockfile ya versiona.
574
- if (relative.startsWith('.ops/') && !fs.existsSync(target)) continue
575
549
  if (fs.statSync(origin).isDirectory()) overlayTree(origin, target, root)
576
550
  else {
577
551
  F.assertNoSymlinkPath(root, target)
@@ -616,6 +590,12 @@ function upgrade(dir) {
616
590
  console.log(`= conservado ${override.collection}/${override.project}: sobrescribe ${override.system}`)
617
591
  }
618
592
  console.log(' planning, organization y todo lo propio quedaron intactos')
593
+ // No se borra: sin la dependencia declarada, quitarle `.ops/` la dejaría sin motor. Se avisa y
594
+ // decide una persona.
595
+ if (fs.existsSync(path.join(root, '.ops', 'engine'))) {
596
+ console.log('\n⚠ esta instancia tiene el motor vendorizado en .ops/, que Cauce ya no distribuye.')
597
+ console.log(' Corré "npm install" para tenerlo como dependencia y después borrá .ops/ a mano.')
598
+ }
619
599
  // El wiring del runner no se actualiza solo: vive fuera de la instancia y lo escribe otro comando.
620
600
  // Sin este recordatorio, una mejora en un workflow o en el catálogo se queda en el paquete.
621
601
  const runners = Object.keys(M.readRunners(root))
@@ -51,11 +51,6 @@ const SYSTEM_COLLECTIONS = [
51
51
  // Se reemplazan enteras. Mientras no tengan su propio `system/`, una edición local se detecta y
52
52
  // se reporta antes de pisarla, nunca después.
53
53
  const RUNTIME_PATHS = [
54
- '.ops/engine',
55
- '.ops/agents',
56
- '.ops/teams',
57
- '.ops/automatization/runners',
58
- '.ops/automatization/workflows',
59
54
  'automatization/hooks',
60
55
  ]
61
56
 
@@ -76,8 +71,6 @@ const TEMPLATE_FILES = new Set(['AGENTS.md', 'Makefile'])
76
71
 
77
72
  function sourceOf(relative) {
78
73
  if (TEMPLATE_FILES.has(relative)) return path.join('template', relative)
79
- // `.ops/` es el paquete vendorizado: cada ruta de ahí adentro se llama igual en el origen.
80
- if (relative.startsWith('.ops/')) return relative.slice('.ops/'.length)
81
74
  // El runtime sale de la raíz del paquete aunque su prefijo sea de plantilla: `automatization/`
82
75
  // le entrega documentos a la instancia, pero los guards que ejecuta son los del toolkit.
83
76
  if (RUNTIME_PATHS.includes(relative)) return relative
@@ -87,13 +80,16 @@ function sourceOf(relative) {
87
80
  return relative
88
81
  }
89
82
 
90
- // Dónde puede estar el motor, en orden de preferencia. Lo mismo que resuelven `tools/ops.js`,
91
- // el wrapper de hooks y el bridge de Antigravity: declararlo una vez evita que un consumidor
92
- // quede afuera cuando aparece una forma nueva de instalarlo.
83
+ // Dónde puede estar el motor. Lo mismo que resuelven `tools/ops.js`, el wrapper de hooks y el bridge
84
+ // de Antigravity: declararlo una vez evita que un consumidor quede afuera.
85
+ //
86
+ // Dos caminos, no tres: el motor llega por npm, y la tercera entrada es el propio repositorio del
87
+ // toolkit corriendo sobre sí mismo. La copia vendorizada en `.ops/` se retiró en 0.10.0 — ahorraba un
88
+ // `package.json` a cambio de 5 MB en la historia de la empresa y de no poder enterarse de una versión
89
+ // nueva, y Node hace falta igual en los dos casos.
93
90
  function engineCandidates(root) {
94
91
  return [
95
92
  path.join(root, 'node_modules', '@ingeniomaps', 'cauce', 'engine'),
96
- path.join(root, '.ops', 'engine'),
97
93
  path.join(root, 'engine'),
98
94
  ]
99
95
  }
@@ -109,7 +105,6 @@ function engineAt(root, relative = '') {
109
105
  function packagePath(root, relative) {
110
106
  const candidates = [
111
107
  path.join(root, 'node_modules', '@ingeniomaps', 'cauce', relative),
112
- path.join(root, '.ops', relative),
113
108
  path.join(root, relative),
114
109
  ]
115
110
  return candidates.find((candidate) => fs.existsSync(candidate)) || ''
@@ -121,7 +116,6 @@ function packagePath(root, relative) {
121
116
  function packageDir(root, name) {
122
117
  const candidates = [
123
118
  path.join(root, 'node_modules', '@ingeniomaps', 'cauce', name),
124
- path.join(root, '.ops', name),
125
119
  path.join(root, name),
126
120
  ]
127
121
  return candidates.find((dir) => fs.existsSync(path.join(dir, 'system'))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -8,7 +8,7 @@ implementa y conecta ese proceso con runners concretos: no redefine producto, pr
8
8
 
9
9
  | Ruta | Responsabilidad |
10
10
  |---|---|
11
- | `hooks/` | Entradas estables; la lógica vive en `.ops/engine/hooks/run.js`. |
11
+ | `hooks/` | Entradas estables; la lógica vive en el motor, que llega con la dependencia. |
12
12
  | `workflows/` | Recorridos portables; las fases pertenecen a `planning/PROTOCOL.md`. |
13
13
  | `runners/` | Adaptadores que declaran su contrato en `manifest.json`. |
14
14
 
@@ -1,7 +1,7 @@
1
1
  # integrations/ — reglas para sistemas externos
2
2
 
3
3
  `config.json` registra proveedores y cada subcarpeta contiene configuración no secreta, staging y
4
- propuestas. La conexión y normalización viven en `.ops/engine/integrations/`; no implementes clientes de API
4
+ propuestas. La conexión y normalización viven en el motor; no implementes clientes de API
5
5
  dentro de esta carpeta.
6
6
 
7
7
  Toda integración se considera de solo lectura. `writeback-plan` solo calcula intención y nunca autoriza ni
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": ".ops/engine/schemas/ops-config.schema.json",
2
+ "$schema": "node_modules/@ingeniomaps/cauce/engine/schemas/ops-config.schema.json",
3
3
  "project": "{{PROJECT_NAME}}",
4
4
  "mode": "{{MODE}}",
5
5
  "planningDir": "{{PLANNING_DIR}}",
@@ -1,7 +1,7 @@
1
1
  # OPS-002 — Distribuir un runtime autocontenido y neutral al runner
2
2
 
3
3
  **Estado:** Aceptado
4
- **Fecha:** 2026-08-14
4
+ **Fecha:** 2026-08-14 · **Revisado:** 2026-08-15
5
5
 
6
6
  > Decide cómo se instala y ejecuta Cauce; no elige qué runner debe usar cada proyecto.
7
7
 
@@ -12,10 +12,16 @@ herramienta o de rutas hacia este repositorio central, una actualización o sesi
12
12
 
13
13
  ## Decisión
14
14
 
15
- **Cada instancia recibe una copia autocontenida del motor en `.ops/engine/`.** El protocolo y los guards son
15
+ **El motor llega como dependencia versionada y el lockfile fija cuál corre.** El protocolo y los guards son
16
16
  neutrales; cada runner se conecta mediante un adaptador declarado. La instalación preserva archivos existentes,
17
17
  no activa runners silenciosamente y rechaza destinos atravesados por symlinks inseguros.
18
18
 
19
+ Hasta el 2026-08-15 cada instancia recibía además una copia del motor en `.ops/engine/`, para no exigirle npm
20
+ a un repo de Go, Python o Rust. Se retiró: el repo ops es un **sidecar**, hermano de los repos de producto, así
21
+ que declarar npm ahí no le impone un stack a ninguno. Y Node hace falta igual —el motor, los guards y los
22
+ workflows son JavaScript—, de modo que la copia sólo ahorraba un `package.json` a cambio de 5 MB en la historia
23
+ de la empresa y de no tener cómo enterarse de que salió una versión nueva.
24
+
19
25
  ## Alternativas consideradas
20
26
 
21
27
  - **Depender del toolkit central:** impide operar de forma aislada y hace frágiles las rutas.
@@ -26,8 +32,8 @@ no activa runners silenciosamente y rechaza destinos atravesados por symlinks in
26
32
 
27
33
  **Ganamos:** portabilidad, comportamiento uniforme y actualizaciones revisables.
28
34
 
29
- **Costos que aceptamos:** cada instancia conserva una copia que debe actualizarse explícitamente sin sobrescribir
30
- personalizaciones.
35
+ **Costos que aceptamos:** el repo ops necesita Node y un `package.json`. Una sesión sin acceso a npm puede
36
+ seguir operando con lo instalado, pero no puede actualizarse.
31
37
 
32
38
  ## Estado de implementación
33
39
 
@@ -2,8 +2,7 @@
2
2
  'use strict'
3
3
 
4
4
  // Punto de entrada estable del proyecto: nadie —ni una persona ni un agente— necesita saber de
5
- // dónde sale el motor. Se prefiere la dependencia npm, que se actualiza con el lockfile; si el
6
- // repositorio no usa npm (Go, Python, Rust), se usa la copia de `.ops/engine`.
5
+ // dónde sale el motor. Viene de la dependencia npm, que el lockfile versiona.
7
6
 
8
7
  const path = require('path')
9
8
 
@@ -16,7 +15,6 @@ process.env.OPS_ROOT = process.env.OPS_ROOT || root
16
15
 
17
16
  const candidates = [
18
17
  () => require.resolve('@ingeniomaps/cauce/engine/cli/ops.js', { paths: [root] }),
19
- () => require.resolve(path.join(root, '.ops', 'engine', 'cli', 'ops.js')),
20
18
  ]
21
19
 
22
20
  let engine = ''
@@ -26,7 +24,7 @@ for (const candidate of candidates) {
26
24
 
27
25
  if (!engine) {
28
26
  console.error('No se encontró el motor de Cauce.')
29
- console.error(' Instalá la dependencia con "npm install" o restaurá .ops/engine con "cauce upgrade".')
27
+ console.error(' Instalá la dependencia con "npm install" desde la raíz del repo ops.')
30
28
  process.exit(2)
31
29
  }
32
30