@ingeniomaps/cauce 0.31.0 → 0.33.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 +37 -0
- package/automatization/workflows/onboard.js +13 -6
- package/engine/cli/ops.js +26 -14
- package/engine/core/scan.js +32 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,43 @@ 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.33.0] - 2026-08-19
|
|
18
|
+
|
|
19
|
+
### Corregido
|
|
20
|
+
|
|
21
|
+
- **Con varias raíces declaradas, ningún servicio tenía nombre.** Cada repositorio es la raíz de su
|
|
22
|
+
propio escaneo, así que su proyecto principal volvía como `.`: tres servicios llamados igual y nada que
|
|
23
|
+
los distinga. Una credencial no se podía atribuir a un servicio porque ningún servicio era nombrable, y
|
|
24
|
+
una corrida real terminó pidiéndole a una persona que declarara variables que su propio repositorio ya
|
|
25
|
+
declaraba. Ahora cada candidato lleva el nombre de su raíz, y donde hay una sola el proyecto de arriba
|
|
26
|
+
se nombra por su carpeta en vez de aparecer como `.` a secas.
|
|
27
|
+
|
|
28
|
+
- **El arranque podía negar lo que el inventario le entregó.** Se le dice explícitamente que un nombre
|
|
29
|
+
que el inventario trae está declarado, así que pedir que se declare de nuevo contradice al repositorio.
|
|
30
|
+
La mitad de la corrección anterior que tocaba el recorrido —la que le hace copiar los nombres de
|
|
31
|
+
variable por servicio— no había llegado a publicarse.
|
|
32
|
+
|
|
33
|
+
## [0.32.0] - 2026-08-19
|
|
34
|
+
|
|
35
|
+
### Corregido
|
|
36
|
+
|
|
37
|
+
- **Las credenciales de un multirepo no existían para el arranque.** `/onboard` leía un solo
|
|
38
|
+
`.env.example`, el de la raíz del workspace: en un monolito eso es todo, entre repositorios hermanos no
|
|
39
|
+
es nada. Una corrida real sobre tres repos dejó filas que decían «la credencial del proveedor» sin
|
|
40
|
+
nombrar una sola variable, y un externo que sólo ese archivo mencionaba —un certificado de organismo
|
|
41
|
+
fiscal— no apareció en ningún lado.
|
|
42
|
+
|
|
43
|
+
Ahora `ops scan` reporta, por servicio, los nombres que declara su ejemplo y de qué archivo salieron, y
|
|
44
|
+
el arranque los usa para nombrar cada fila. **Sólo los nombres**: el valor es de una persona, y el
|
|
45
|
+
ejemplo es la mitad pública del par. Se leen `.env.example`, `.env.sample`, `.env.template` y
|
|
46
|
+
`.env.dist`; un ejemplo de más de cuarenta variables es un archivo generado y no un contrato, así que
|
|
47
|
+
se corta ahí y lo dice.
|
|
48
|
+
|
|
49
|
+
- **`ops scan <ruta>` dejaba afuera al proyecto de la raíz.** Un monolito que declara sus comandos en el
|
|
50
|
+
nivel de arriba volvía listando todo menos a sí mismo, mientras que desde adentro de la instancia el
|
|
51
|
+
mismo árbol sí lo listaba: dos respuestas distintas sobre el mismo directorio. Los dos caminos comparten
|
|
52
|
+
una sola función y el proyecto de la raíz va primero.
|
|
53
|
+
|
|
17
54
|
## [0.31.0] - 2026-08-18
|
|
18
55
|
|
|
19
56
|
### Añadido
|
|
@@ -85,6 +85,10 @@ const SCAN = {
|
|
|
85
85
|
required: ['kind', 'command', 'source'], properties: {
|
|
86
86
|
kind: { type: 'string' }, command: { type: 'string' }, source: { type: 'string' },
|
|
87
87
|
} } },
|
|
88
|
+
// Los nombres de variable que ese servicio espera, copiados del inventario. En un multirepo cada
|
|
89
|
+
// repositorio trae su propio ejemplo, y sin esto las credenciales de tres repos no existían para
|
|
90
|
+
// el arranque: las filas terminaban diciendo «la credencial del proveedor» sin nombrarla.
|
|
91
|
+
env: { type: 'array', items: { type: 'string' } },
|
|
88
92
|
} } },
|
|
89
93
|
externals: { type: 'array', items: { type: 'string' } },
|
|
90
94
|
secrets: { type: 'array', items: { type: 'string' } },
|
|
@@ -110,11 +114,12 @@ const state = await agent(
|
|
|
110
114
|
`else and open no file other than .env.example at the workspace root.\n` +
|
|
111
115
|
`1. "node tools/ops.js onboard --json": the instance state, the workspace inventory, the opening ` +
|
|
112
116
|
`question and the dimensions still uncovered. Copy fresh, opening, followUps, the "need" of each ` +
|
|
113
|
-
`dimension, and every service with its path, its runtimes
|
|
114
|
-
`file each command came from. Add nothing it did not print.\n` +
|
|
117
|
+
`dimension, and every service with its path, its runtimes, its declared commands keeping the source ` +
|
|
118
|
+
`file each command came from, and the variable names its "env" carries. Add nothing it did not print.\n` +
|
|
115
119
|
`2. "node tools/ops.js check planning".\n` +
|
|
116
|
-
`
|
|
117
|
-
`the
|
|
120
|
+
`The inventory already names every credential each service expects: never open a .env file to look for ` +
|
|
121
|
+
`more. Report those names in secrets and the services they point at in externals. A name the inventory ` +
|
|
122
|
+
`carries is declared, and saying otherwise is a claim the repository contradicts.`,
|
|
118
123
|
{ schema: SCAN, label: 'inventario' },
|
|
119
124
|
)
|
|
120
125
|
if (!state) return stop('scan-unavailable', 'no se pudo leer el estado del workspace')
|
|
@@ -167,8 +172,10 @@ const drafted = await agent(
|
|
|
167
172
|
'completa.\n'}` +
|
|
168
173
|
`4. ${HUMAN}: una fila por cada cosa que necesita a una persona, con la tarea, el estado pendiente, el ` +
|
|
169
174
|
`origen "onboard" y la acción concreta que la desbloquea. Como mínimo, una por cada credencial que el ` +
|
|
170
|
-
`
|
|
171
|
-
`
|
|
175
|
+
`inventario nombra, diciendo la variable y el servicio que la espera. El nombre ya está declarado, así ` +
|
|
176
|
+
`que no pidas declararlo de nuevo: lo que falta es dónde se carga el valor y quién lo hace, y ningún ` +
|
|
177
|
+
`valor se propone acá. Además, una por cada sistema externo o MCP a conectar, y una por la autoridad ` +
|
|
178
|
+
`del runner, que hoy declara runner.allowPush=false.\n` +
|
|
172
179
|
`5. Las preguntas que queden abiertas, en la sección Ideas de ${INBOX}, sin promover.\n` +
|
|
173
180
|
`Devolvé en files cada archivo que tocaste y en assumptions cada supuesto que dejaste marcado.`,
|
|
174
181
|
{ schema: WRITTEN, label: 'contexto' },
|
package/engine/cli/ops.js
CHANGED
|
@@ -353,29 +353,41 @@ function workspaceRoots(root) {
|
|
|
353
353
|
|
|
354
354
|
// Qué hay en las raíces declaradas, antes de que nadie razone sobre ello. La raíz ops se saltea: no es
|
|
355
355
|
// un servicio del proyecto, y su `package.json` sólo declara el motor.
|
|
356
|
+
// Los candidatos de una raíz, con el proyecto que vive en ella misma primero: un monolito declara sus
|
|
357
|
+
// comandos en el nivel de arriba, y dejarlo afuera hacía desaparecer justo al proyecto principal.
|
|
358
|
+
function candidates(workspace, skip = '') {
|
|
359
|
+
const result = SC.scan(workspace, skip)
|
|
360
|
+
const found = result.rootManifests.length
|
|
361
|
+
? [{ path: '.', root: workspace, runtimes: ['raíz'], commands: result.rootCommands, env: result.rootEnv }]
|
|
362
|
+
: []
|
|
363
|
+
return [...found, ...result.services.map((service) => ({ ...service, root: workspace }))]
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Con varias raíces, cada repositorio es la raíz de su propio escaneo y su candidato principal se llama
|
|
367
|
+
// `.`: tres servicios con el mismo nombre y nada que los distinga. El prefijo los vuelve nombrables, que
|
|
368
|
+
// es la única forma de que una credencial pueda atribuirse a un servicio en vez de quedar suelta.
|
|
356
369
|
function inventory(root) {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
for (const service of result.services) found.push({ ...service, root: workspace })
|
|
364
|
-
}
|
|
365
|
-
return found
|
|
370
|
+
const roots = workspaceRoots(root)
|
|
371
|
+
if (roots.length === 1) return candidates(roots[0], root)
|
|
372
|
+
return roots.flatMap((workspace) => candidates(workspace, root).map((service) => ({
|
|
373
|
+
...service,
|
|
374
|
+
path: service.path === '.' ? path.basename(workspace) : `${path.basename(workspace)}/${service.path}`,
|
|
375
|
+
})))
|
|
366
376
|
}
|
|
367
377
|
|
|
368
378
|
function scan(target, cli) {
|
|
369
379
|
const root = path.resolve(target || '.')
|
|
370
|
-
const result = target
|
|
371
|
-
? { root: path.resolve(target), services: SC.scan(path.resolve(target)).services }
|
|
372
|
-
: { root, services: inventory(root) }
|
|
380
|
+
const result = { root, services: target ? candidates(root) : inventory(root) }
|
|
373
381
|
if (cli.has('--json')) return console.log(JSON.stringify(result, null, 2))
|
|
374
382
|
// Un monorepo de sesenta paquetes no se lee en pantalla. Se recorta, y se dice cuánto: un corte que no
|
|
375
383
|
// se anuncia hace pasar lo listado por todo lo que hay.
|
|
376
384
|
for (const service of result.services.slice(0, LISTA)) {
|
|
377
|
-
|
|
378
|
-
|
|
385
|
+
// El proyecto que vive en la raíz se nombra por su carpeta: `.` a secas no dice de cuál se habla.
|
|
386
|
+
const nombre = service.path === '.' ? `. (${path.basename(service.root || result.root)})` : service.path
|
|
387
|
+
const espera = service.env ? `\n espera ${service.env.names.join(', ')} (${service.env.file})` : ''
|
|
388
|
+
console.log(
|
|
389
|
+
`${nombre} [${(service.runtimes || []).join(', ')}]${comandos(service.commands)}${espera}`,
|
|
390
|
+
)
|
|
379
391
|
}
|
|
380
392
|
if (result.services.length > LISTA) {
|
|
381
393
|
console.log(`… y ${result.services.length - LISTA} más, todos en --json`)
|
package/engine/core/scan.js
CHANGED
|
@@ -98,6 +98,35 @@ function manifestsOf(dir) {
|
|
|
98
98
|
return MANIFESTS.filter((entry) => fs.existsSync(path.join(dir, entry.file)))
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Qué credenciales espera un servicio, por su nombre y nada más. El ejemplo es público y versionado —el
|
|
102
|
+
// `.env` de verdad no se toca nunca—, y los nombres son justo lo que hace falta para dejar una fila que
|
|
103
|
+
// diga qué cargar; el valor no le corresponde a nadie más que a una persona.
|
|
104
|
+
//
|
|
105
|
+
// Se lee por servicio y no sólo en la raíz porque en un multirepo cada repositorio trae el suyo: leyendo
|
|
106
|
+
// una sola raíz, las credenciales de tres repos se volvían invisibles y las acciones humanas terminaban
|
|
107
|
+
// diciendo «la credencial del proveedor» en vez de nombrarla.
|
|
108
|
+
const ENV_EXAMPLES = ['.env.example', '.env.sample', '.env.template', '.env.dist']
|
|
109
|
+
|
|
110
|
+
// Un ejemplo con cientos de variables es un archivo generado, no un contrato: se corta y se dice.
|
|
111
|
+
const ENV_MAX = 40
|
|
112
|
+
|
|
113
|
+
function expectedEnv(dir) {
|
|
114
|
+
for (const name of ENV_EXAMPLES) {
|
|
115
|
+
const file = path.join(dir, name)
|
|
116
|
+
if (!fs.existsSync(file)) continue
|
|
117
|
+
let text = ''
|
|
118
|
+
try { text = fs.readFileSync(file, 'utf8') } catch { return null }
|
|
119
|
+
const names = text.split('\n')
|
|
120
|
+
.map((line) => line.trim())
|
|
121
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
122
|
+
.map((line) => line.replace(/^export\s+/, '').split('=')[0].trim())
|
|
123
|
+
.filter((key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
124
|
+
if (!names.length) return null
|
|
125
|
+
return { file: name, names: names.slice(0, ENV_MAX), truncated: Math.max(0, names.length - ENV_MAX) }
|
|
126
|
+
}
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
// Servicios candidatos bajo `root`, sin entrar en `skip` —típicamente la raíz ops, que no es un
|
|
102
131
|
// servicio del proyecto—. El resultado es una lista, no un veredicto: decidir cuál es el producto y
|
|
103
132
|
// cuál quedó muerto sigue siendo trabajo de una persona o de un cargo.
|
|
@@ -113,6 +142,7 @@ function services(root, skip = '') {
|
|
|
113
142
|
runtimes: manifests.map((entry) => entry.runtime),
|
|
114
143
|
manifests: manifests.map((entry) => entry.file),
|
|
115
144
|
commands: commandsOf(dir),
|
|
145
|
+
env: expectedEnv(dir),
|
|
116
146
|
})
|
|
117
147
|
}
|
|
118
148
|
if (depth >= DEPTH) return
|
|
@@ -136,8 +166,9 @@ function scan(root, skip = '') {
|
|
|
136
166
|
root: path.resolve(root),
|
|
137
167
|
rootManifests: manifestsOf(root).map((entry) => entry.file),
|
|
138
168
|
rootCommands: commandsOf(root),
|
|
169
|
+
rootEnv: expectedEnv(root),
|
|
139
170
|
services: services(root, skip),
|
|
140
171
|
}
|
|
141
172
|
}
|
|
142
173
|
|
|
143
|
-
module.exports = { scan, services, IGNORED, DEPTH }
|
|
174
|
+
module.exports = { scan, services, expectedEnv, IGNORED, DEPTH, ENV_MAX }
|