@ingeniomaps/cauce 0.21.0 → 0.22.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 +51 -0
- package/agents/roles/system/backend-engineer/evaluations/results/2026-08-16.md +740 -0
- package/agents/roles/system/privacy-compliance-specialist/evaluations/results/2026-08-16.md +649 -0
- package/automatization/workflows/agent-eval.js +11 -1
- package/automatization/workflows/agent-promote.js +4 -5
- package/automatization/workflows/integrations/promote.js +3 -3
- package/automatization/workflows/integrations/sync.js +3 -3
- package/engine/agents/fork.js +6 -13
- package/engine/agents/learning.js +2 -6
- package/engine/automation/index.js +13 -7
- package/engine/cli/ops.js +35 -11
- package/engine/core/files.js +0 -1
- package/engine/core/manifest.js +1 -1
- package/engine/core/ownership.js +16 -21
- package/engine/hooks/run.js +1 -1
- package/engine/integrations/registry.js +5 -11
- package/engine/integrations/state.js +0 -1
- package/engine/planning/contracts.js +0 -3
- package/engine/planning/parser.js +4 -7
- package/package.json +1 -1
- package/template/automatization/README.md +4 -1
- package/template/automatization/config.json +0 -13
|
@@ -161,8 +161,18 @@ const veredictos = await pipeline(
|
|
|
161
161
|
// mide contra lo que el caso declara, no contra una relectura del SKILL.md.
|
|
162
162
|
(answer, item) => (answer ? agent(
|
|
163
163
|
`Un cargo recibió este pedido:\n\n${item.request}\n\nY respondió:\n\n${answer.response}\n\n` +
|
|
164
|
+
(porCaso
|
|
165
|
+
? `La respuesta no es toda la entrega. El cargo trabajó en ${porCaso(item)}, un banco versionado ` +
|
|
166
|
+
`desde su estado limpio: corré "git -C ${porCaso(item)} status --porcelain" y ` +
|
|
167
|
+
`"git -C ${porCaso(item)} diff" para ver exactamente qué produjo, y leé los archivos que ` +
|
|
168
|
+
`aparezcan. Juzgá la respuesta **y** lo que escribió.\n\n` +
|
|
169
|
+
`Esto no es un detalle: un cargo contestó un resumen y dejó el contrato completo —con firma, ` +
|
|
170
|
+
`orden de verificación y catorce pruebas— en su INBOX. Juzgado sólo por el texto, se lo dio ` +
|
|
171
|
+
`por ausente.\n\n`
|
|
172
|
+
: '') +
|
|
164
173
|
`Contrastá esa respuesta contra cada comportamiento esperado y decidí si se observa o no. ` +
|
|
165
|
-
`Citá la parte de la respuesta que lo sostiene; si no hay
|
|
174
|
+
`Citá la parte de la respuesta —o del archivo que el cargo escribió— que lo sostiene; si no hay ` +
|
|
175
|
+
`cita, no se observa. No premies la ` +
|
|
166
176
|
`intención ni el tono: sólo lo que la respuesta dice.\n\n` +
|
|
167
177
|
`Comportamientos esperados:\n${item.expected.map((one, index) => `${index + 1}. ${one}`).join('\n')}\n\n` +
|
|
168
178
|
`El caso pasa sólo si se observan todos.`,
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
// 1. Se niega si «Aprobación humana» no está firmada con responsable. Un agente no se autoriza a sí
|
|
6
6
|
// mismo, y `automatic_apply: false` sigue siendo cierto: esto no corre solo, lo corre alguien
|
|
7
7
|
// después de firmar.
|
|
8
|
-
// 2.
|
|
9
|
-
// nadie sabiendo si todavía se sostiene
|
|
8
|
+
// 2. Invalida el registro de evaluación anterior y manda a rehacerlo. Aplicar deja un contrato
|
|
9
|
+
// cambiado y nadie sabiendo si todavía se sostiene: el veredicto que había medía la versión
|
|
10
|
+
// vieja. Correr los casos acá no sirve —son un recorrido propio, con su banco por caso y su
|
|
11
|
+
// juez—, así que este termina nombrando `/agent-eval` en vez de fingir que ya verificó.
|
|
10
12
|
//
|
|
11
13
|
// Aplica **prosa**, no un parche, y es a propósito: la propuesta dice «agregar dos viñetas después de
|
|
12
14
|
// la última existente». Un parche envejece si alguien toca el archivo mientras la propuesta espera
|
|
@@ -20,7 +22,6 @@ export const meta = {
|
|
|
20
22
|
{ title: 'Firma', detail: 'Sin aprobación humana no se toca nada' },
|
|
21
23
|
{ title: 'Aplicar', detail: 'El cambio, archivo por archivo' },
|
|
22
24
|
{ title: 'Registrar', detail: 'Historial del cargo' },
|
|
23
|
-
{ title: 'Verificar', detail: 'Los casos adversariales contra el contrato nuevo' },
|
|
24
25
|
],
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -126,8 +127,6 @@ await agent(
|
|
|
126
127
|
{ label: 'historial' },
|
|
127
128
|
)
|
|
128
129
|
|
|
129
|
-
phase('Verificar')
|
|
130
|
-
|
|
131
130
|
log('El contrato cambió: los casos valen sólo si se vuelven a correr contra la versión nueva.')
|
|
132
131
|
return finish({
|
|
133
132
|
agent: AGENT,
|
|
@@ -3,10 +3,10 @@ export const meta = {
|
|
|
3
3
|
name: 'integration-promote',
|
|
4
4
|
description: 'Promueve un draft revisado desde staging hacia el roadmap local.',
|
|
5
5
|
whenToUse: 'Después de que una persona marque un candidato de integración como ready.',
|
|
6
|
+
// Una sola: el recorrido es un agente que encadena check → promote → check. Declarar «Promote» y
|
|
7
|
+
// «Validate» aparte pintaba dos grupos vacíos en el progreso, porque nunca se entraba a ellos.
|
|
6
8
|
phases: [
|
|
7
|
-
{ title: 'Preflight', detail: 'Validar
|
|
8
|
-
{ title: 'Promote', detail: 'Materializar el draft mediante el adaptador común.' },
|
|
9
|
-
{ title: 'Validate', detail: 'Comprobar planning e integración.' },
|
|
9
|
+
{ title: 'Preflight', detail: 'Validar el candidato, promoverlo y comprobar planning.' },
|
|
10
10
|
],
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -3,10 +3,10 @@ export const meta = {
|
|
|
3
3
|
name: 'integration-sync',
|
|
4
4
|
description: 'Valida, sincroniza y comprueba staging preservando la curación local.',
|
|
5
5
|
whenToUse: 'Refrescar trabajo remoto antes de revisarlo y promoverlo a planning.',
|
|
6
|
+
// Una sola: el recorrido es un agente que encadena check → sync → check. Declarar «Sync» y
|
|
7
|
+
// «Validate» aparte pintaba dos grupos vacíos en el progreso, porque nunca se entraba a ellos.
|
|
6
8
|
phases: [
|
|
7
|
-
{ title: 'Preflight', detail: 'Resolver proveedor y
|
|
8
|
-
{ title: 'Sync', detail: 'Leer remoto mediante su adaptador y refrescar staging.' },
|
|
9
|
-
{ title: 'Validate', detail: 'Validar integración y planning sin promover.' },
|
|
9
|
+
{ title: 'Preflight', detail: 'Resolver proveedor, sincronizar y revalidar staging.' },
|
|
10
10
|
],
|
|
11
11
|
}
|
|
12
12
|
|
package/engine/agents/fork.js
CHANGED
|
@@ -37,17 +37,9 @@ function inherited(relative) {
|
|
|
37
37
|
|
|
38
38
|
const TEXT = /\.(md|ya?ml|json|txt)$/i
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
try { list = fs.readdirSync(dir, { withFileTypes: true }) } catch { return found }
|
|
44
|
-
for (const entry of list) {
|
|
45
|
-
const relative = prefix ? `${prefix}/${entry.name}` : entry.name
|
|
46
|
-
if (entry.isDirectory()) found.push(...tree(path.join(dir, entry.name), relative))
|
|
47
|
-
else found.push(relative)
|
|
48
|
-
}
|
|
49
|
-
return found.sort()
|
|
50
|
-
}
|
|
40
|
+
// El mismo recorrido que usa `upgrade` para comparar instancia contra paquete: un cargo es un árbol
|
|
41
|
+
// de archivos como cualquier otro, y tener dos implementaciones sólo daba dos formas de ordenarlos.
|
|
42
|
+
const tree = ownership.treeFiles
|
|
51
43
|
|
|
52
44
|
// La misma resolución que usa todo lo demás —paquete primero, toolkit después— en vez de deducirla
|
|
53
45
|
// del directorio del motor: la versión que interesa es la del catálogo del que sale la copia.
|
|
@@ -89,7 +81,8 @@ function fork(root, slug, date) {
|
|
|
89
81
|
const target = path.join(root, 'agents', type, slug)
|
|
90
82
|
if (fs.existsSync(target)) throw new Error(`${path.relative(root, target)} ya existe`)
|
|
91
83
|
|
|
92
|
-
const
|
|
84
|
+
const todo = tree(found.dir)
|
|
85
|
+
const files = todo.filter(inherited)
|
|
93
86
|
if (!files.includes('SKILL.md')) throw new Error(`${slug} no tiene SKILL.md: no hay contrato que copiar`)
|
|
94
87
|
|
|
95
88
|
// El digest sale del origen, no de la copia. Es lo que el catálogo tenía al momento del fork, y es
|
|
@@ -118,7 +111,7 @@ function fork(root, slug, date) {
|
|
|
118
111
|
forks[slug] = { type, version, files: digests }
|
|
119
112
|
manifest.write(root, null, null, forks)
|
|
120
113
|
|
|
121
|
-
return { slug, type, dir: target, files, version, skipped:
|
|
114
|
+
return { slug, type, dir: target, files, version, skipped: todo.filter((one) => !inherited(one)) }
|
|
122
115
|
}
|
|
123
116
|
|
|
124
117
|
// Qué cambió en el catálogo desde que la empresa se llevó su copia.
|
|
@@ -15,10 +15,6 @@ const REQUIRED_SECTIONS = [
|
|
|
15
15
|
'Aprobación humana',
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
-
function agentRoot(root, agent) {
|
|
19
|
-
return catalog.resolve(root, agent)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
// Un cargo del sistema vive dentro del paquete: escribir ahí perdería el informe en el próximo
|
|
23
19
|
// `npm ci`, y además duplicaría en cada empresa una investigación sobre la profesión que se hace
|
|
24
20
|
// mejor una sola vez. Lo que sí es de esta empresa es su contexto, y ese tiene otro lugar.
|
|
@@ -136,7 +132,7 @@ Pendiente.
|
|
|
136
132
|
}
|
|
137
133
|
|
|
138
134
|
function evaluate(root, agent) {
|
|
139
|
-
const target =
|
|
135
|
+
const target = catalog.resolve(root, agent)
|
|
140
136
|
const errors = []
|
|
141
137
|
const requiredFiles = [
|
|
142
138
|
'learning/sources.yaml',
|
|
@@ -146,7 +142,7 @@ function evaluate(root, agent) {
|
|
|
146
142
|
// `AUTOMATION.md` documenta cómo corre la automatización de aprendizaje del toolkit. Exigírselo
|
|
147
143
|
// a una empresa que escribe un cargo propio era pedirle contabilidad interna nuestra: su cargo debe
|
|
148
144
|
// tener contrato, fuentes e historia, no nuestro andamiaje.
|
|
149
|
-
if (
|
|
145
|
+
if (catalog.find(root, agent).system) requiredFiles.push('learning/AUTOMATION.md')
|
|
150
146
|
for (const relative of requiredFiles) {
|
|
151
147
|
if (!fs.existsSync(path.join(target, relative))) errors.push(`falta ${relative}`)
|
|
152
148
|
}
|
|
@@ -12,6 +12,11 @@ const M = require('../core/manifest')
|
|
|
12
12
|
|
|
13
13
|
const RUNNER_NAMES = ['claude', 'codex', 'gemini', 'antigravity']
|
|
14
14
|
|
|
15
|
+
// Los guards que el motor implementa, tomados del registro que los ejecuta. Sale de ahí y no de un
|
|
16
|
+
// número escrito a mano: `automation check` anunciaba «11 guards» cuando hacía rato que eran doce, y
|
|
17
|
+
// un conteo que envejece solo es peor que ninguno —dice que revisó menos de lo que revisó—.
|
|
18
|
+
const GUARD_NAMES = Object.keys(H.guards)
|
|
19
|
+
|
|
15
20
|
// Adaptadores y workflows viven en el paquete, no en la instancia: son definiciones que el motor
|
|
16
21
|
// consume y que ninguna empresa edita —`RUNNER_NAMES` es cerrado, así que ni siquiera puede agregar
|
|
17
22
|
// uno propio—. Los hooks sí se quedan en el proyecto: la configuración del runner los nombra por
|
|
@@ -172,7 +177,7 @@ Respetá los límites de ese contrato y las reglas de \`AGENTS.md\`. Generado po
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
function installRoleSkills(root, runner, output) {
|
|
175
|
-
if (!runner.capabilities.nativeSkills || !runner.roleSkills) return
|
|
180
|
+
if (!runner.capabilities.nativeSkills || !runner.roleSkills) return
|
|
176
181
|
const install = installRoot(root)
|
|
177
182
|
const base = F.assertWithin(install, path.resolve(install, runner.roleSkills), `${runner.name}: roleSkills`)
|
|
178
183
|
const roles = roleCatalog(root)
|
|
@@ -182,7 +187,6 @@ function installRoleSkills(root, runner, output) {
|
|
|
182
187
|
F.atomicWrite(file, roleSkill(role))
|
|
183
188
|
}
|
|
184
189
|
if (roles.length) output.log(`✓ ${runner.name}: ${roles.length} cargo(s) disponibles en ${runner.roleSkills}`)
|
|
185
|
-
return roles.length
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
// Runners que además de los archivos necesitan un registro propio para que el wiring cuente. Copiar
|
|
@@ -267,8 +271,12 @@ function check(root) {
|
|
|
267
271
|
if (!O.engineAt(root, path.join('hooks', 'run.js'))) {
|
|
268
272
|
errors.push('falta engine/hooks/run.js: corré "npm install" en la raíz del repo ops')
|
|
269
273
|
}
|
|
270
|
-
const workflows = [
|
|
271
|
-
|
|
274
|
+
const workflows = [
|
|
275
|
+
'autobuild.js',
|
|
276
|
+
'team.js',
|
|
277
|
+
path.join('integrations', 'sync.js'),
|
|
278
|
+
path.join('integrations', 'promote.js'),
|
|
279
|
+
]
|
|
272
280
|
const packaged = packagedAutomation(root)
|
|
273
281
|
for (const name of workflows) {
|
|
274
282
|
if (!packaged || !fs.existsSync(path.join(packaged, 'workflows', name))) {
|
|
@@ -550,16 +558,14 @@ function listHooks(output = console) {
|
|
|
550
558
|
}
|
|
551
559
|
|
|
552
560
|
module.exports = {
|
|
561
|
+
GUARD_NAMES,
|
|
553
562
|
RUNNER_NAMES,
|
|
554
563
|
check,
|
|
555
564
|
doctor,
|
|
556
|
-
includesConfig,
|
|
557
565
|
install,
|
|
558
566
|
legacyGuardWiring,
|
|
559
|
-
pruneSupersededHooks,
|
|
560
567
|
roleCatalog,
|
|
561
568
|
roleSkill,
|
|
562
569
|
listHooks,
|
|
563
|
-
mergeConfig,
|
|
564
570
|
runnerManifest,
|
|
565
571
|
}
|
package/engine/cli/ops.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const path = require('path')
|
|
6
|
+
const { spawnSync } = require('child_process')
|
|
6
7
|
const P = require('../planning/parser')
|
|
7
8
|
const B = require('../planning/business-rules')
|
|
8
9
|
const PC = require('../planning/contracts')
|
|
@@ -209,6 +210,16 @@ function evaluationBench(root, agent, caso) {
|
|
|
209
210
|
return value
|
|
210
211
|
}
|
|
211
212
|
const dir = path.join(root, '.cauce-eval', safe(agent), safe(caso || '_libre'))
|
|
213
|
+
// Recrear un banco donde alguien ya trabajó borra la evidencia de esa corrida, y el registro de la
|
|
214
|
+
// evaluación se escribe **desde** el banco. Pasó de verdad: se rehizo un banco para probar otra cosa
|
|
215
|
+
// y con él se fue lo que el cargo había escrito; el juez leyó un directorio vacío y concluyó que la
|
|
216
|
+
// respuesta afirmaba algo inexistente. Con el banco versionado, «acá se trabajó» es una pregunta que
|
|
217
|
+
// git contesta exacto.
|
|
218
|
+
const sucio = spawnSync('git', ['-C', dir, 'status', '--porcelain'], { encoding: 'utf8' })
|
|
219
|
+
if ((sucio.stdout || '').trim() && !process.argv.includes('--force')) {
|
|
220
|
+
fail(`${dir} tiene trabajo sin recoger. Guardá el registro de esa corrida antes de rehacerlo, `
|
|
221
|
+
+ 'o usá --force si ya lo tenés.', 2)
|
|
222
|
+
}
|
|
212
223
|
fs.rmSync(dir, { recursive: true, force: true })
|
|
213
224
|
scaffold(dir, { name: 'Banco de evaluación', mode: 'sidecar', quiet: true })
|
|
214
225
|
// El motor por symlink: la misma resolución que en una instancia real —`node_modules/@ingeniomaps`—
|
|
@@ -216,6 +227,22 @@ function evaluationBench(root, agent, caso) {
|
|
|
216
227
|
const scope = path.join(dir, 'node_modules', '@ingeniomaps')
|
|
217
228
|
fs.mkdirSync(scope, { recursive: true })
|
|
218
229
|
fs.symlinkSync(PROJECT_ROOT, path.join(scope, 'cauce'), 'dir')
|
|
230
|
+
|
|
231
|
+
// El banco queda versionado desde su estado limpio, y eso resuelve un problema de medición: la
|
|
232
|
+
// entrega de un cargo puede no estar en su respuesta. `backend-engineer` contestó un resumen del
|
|
233
|
+
// webhook y escribió el contrato —firma, orden de verificación, ventana antirreplay, catorce
|
|
234
|
+
// pruebas— en su `INBOX.md`. El juez, que sólo leía la respuesta, lo dio por ausente y lo reprobó.
|
|
235
|
+
//
|
|
236
|
+
// Con el banco versionado, `git status` y `git diff` muestran exactamente qué produjo, separado del
|
|
237
|
+
// andamiaje. Es la diferencia entre juzgar el resumen y juzgar la entrega. Se ignora `node_modules`
|
|
238
|
+
// porque es un symlink al toolkit y no es obra del cargo.
|
|
239
|
+
const git = (...args) => spawnSync('git', ['-C', dir, ...args], { stdio: 'ignore' })
|
|
240
|
+
fs.appendFileSync(path.join(dir, '.gitignore'), '\nnode_modules/\n')
|
|
241
|
+
git('init', '-q')
|
|
242
|
+
git('config', 'user.email', 'banco@cauce.local')
|
|
243
|
+
git('config', 'user.name', 'banco de evaluación')
|
|
244
|
+
git('add', '-A')
|
|
245
|
+
git('commit', '-q', '-m', 'banco limpio')
|
|
219
246
|
return dir
|
|
220
247
|
}
|
|
221
248
|
|
|
@@ -525,13 +552,6 @@ function instanceVersion(root) {
|
|
|
525
552
|
} catch { return '' }
|
|
526
553
|
}
|
|
527
554
|
|
|
528
|
-
// Sobrescribe lo que trae el paquete y deja intacto lo demás: un guard propio de la empresa,
|
|
529
|
-
// o un adaptador de runner que el toolkit no conoce, sobreviven a la actualización.
|
|
530
|
-
function overlayTree(from, to, root, skip = []) {
|
|
531
|
-
F.assertNoSymlinkPath(root, to)
|
|
532
|
-
copyRuntime(from, to, false, root, skip)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
555
|
// Actualiza sólo lo que el toolkit declara suyo. Todo lo demás —planning, organization, reglas
|
|
536
556
|
// propias, agentes editados— queda intacto por construcción, no por comparación.
|
|
537
557
|
function upgrade(dir) {
|
|
@@ -593,7 +613,9 @@ function upgrade(dir) {
|
|
|
593
613
|
const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
|
|
594
614
|
if (!fs.existsSync(origin)) continue
|
|
595
615
|
const target = path.join(root, relative)
|
|
596
|
-
|
|
616
|
+
// Sobrescribe lo que trae el paquete y deja intacto lo demás: un guard propio de la empresa,
|
|
617
|
+
// o un adaptador de runner que el toolkit no conoce, sobreviven a la actualización.
|
|
618
|
+
if (fs.statSync(origin).isDirectory()) copyRuntime(origin, target, false, root)
|
|
597
619
|
else {
|
|
598
620
|
F.assertNoSymlinkPath(root, target)
|
|
599
621
|
F.atomicWrite(target, fs.readFileSync(origin, 'utf8'))
|
|
@@ -662,8 +684,6 @@ function upgrade(dir) {
|
|
|
662
684
|
for (const entry of FK.drift(root)) console.log(` ⚠ ${FK.driftLine(entry)}`)
|
|
663
685
|
}
|
|
664
686
|
|
|
665
|
-
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
666
|
-
// reimplemente el recorrido del catálogo.
|
|
667
687
|
// La raíz ops de un comando que no la recibe. El shim `tools/ops.js` la exporta porque sabe dónde
|
|
668
688
|
// vive: sin eso, invocarlo desde otra carpeta —lo normal en sidecar— la resolvía contra el cwd.
|
|
669
689
|
function opsRoot(dir) {
|
|
@@ -684,6 +704,8 @@ function agentsFork(slug, dir) {
|
|
|
684
704
|
console.log(` reinstalá tu runner para que ${slug} apunte a tu copia`)
|
|
685
705
|
}
|
|
686
706
|
|
|
707
|
+
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
708
|
+
// reimplemente el recorrido del catálogo.
|
|
687
709
|
function agents(action, dir, extra) {
|
|
688
710
|
if (action === 'fork') return agentsFork(dir, extra)
|
|
689
711
|
if (action !== 'list') fail(`Acción de agents desconocida: ${action || '(vacía)'}`, 2)
|
|
@@ -857,7 +879,9 @@ function automation(action, rootArg, runnerName) {
|
|
|
857
879
|
const errors = A.check(root)
|
|
858
880
|
for (const error of errors) console.error(`✗ ${error}`)
|
|
859
881
|
if (errors.length) fail(`${errors.length} error(es) de automatización`)
|
|
860
|
-
console.log(
|
|
882
|
+
console.log(
|
|
883
|
+
`✓ automatización válida: ${A.GUARD_NAMES.length} guards, ${A.RUNNER_NAMES.length} adaptadores`,
|
|
884
|
+
)
|
|
861
885
|
return
|
|
862
886
|
}
|
|
863
887
|
if (action === 'doctor') {
|
package/engine/core/files.js
CHANGED
package/engine/core/manifest.js
CHANGED
package/engine/core/ownership.js
CHANGED
|
@@ -80,28 +80,13 @@ function sourceOf(relative) {
|
|
|
80
80
|
return relative
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
83
|
+
// Una ruta cualquiera del paquete: primero la dependencia npm, después el propio repositorio del
|
|
84
|
+
// toolkit corriendo sobre sí mismo. La usan los adaptadores de runner, los workflows y el motor
|
|
85
|
+
// mismo: el motor los consume, el proyecto no los materializa.
|
|
85
86
|
//
|
|
86
|
-
// Dos caminos, no tres
|
|
87
|
-
// toolkit corriendo sobre sí mismo. La copia vendorizada en `.ops/` se retiró en 0.10.0 — ahorraba un
|
|
87
|
+
// Dos caminos, no tres. La copia vendorizada en `.ops/` se retiró en 0.10.0 — ahorraba un
|
|
88
88
|
// `package.json` a cambio de 5 MB en la historia de la empresa y de no poder enterarse de una versión
|
|
89
89
|
// nueva, y Node hace falta igual en los dos casos.
|
|
90
|
-
function engineCandidates(root) {
|
|
91
|
-
return [
|
|
92
|
-
path.join(root, 'node_modules', '@ingeniomaps', 'cauce', 'engine'),
|
|
93
|
-
path.join(root, 'engine'),
|
|
94
|
-
]
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function engineAt(root, relative = '') {
|
|
98
|
-
return engineCandidates(root)
|
|
99
|
-
.map((dir) => (relative ? path.join(dir, relative) : dir))
|
|
100
|
-
.find((candidate) => fs.existsSync(candidate)) || ''
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Una ruta cualquiera del paquete, en el mismo orden de preferencia que el motor. La usan los
|
|
104
|
-
// adaptadores de runner y los workflows: el motor los consume, el proyecto no los materializa.
|
|
105
90
|
function packagePath(root, relative) {
|
|
106
91
|
const candidates = [
|
|
107
92
|
path.join(root, 'node_modules', '@ingeniomaps', 'cauce', relative),
|
|
@@ -110,6 +95,14 @@ function packagePath(root, relative) {
|
|
|
110
95
|
return candidates.find((candidate) => fs.existsSync(candidate)) || ''
|
|
111
96
|
}
|
|
112
97
|
|
|
98
|
+
// Dónde quedó el motor. Es `packagePath` bajo `engine/`, y no una cascada propia: había dos copias de
|
|
99
|
+
// la misma lista de candidatos en este archivo, una al lado de la otra, y nada obligaba a que
|
|
100
|
+
// siguieran diciendo lo mismo. La tercera copia vive en el bridge de Antigravity, que corre antes de
|
|
101
|
+
// poder cargar este módulo y por eso la repite a propósito.
|
|
102
|
+
function engineAt(root, relative = '') {
|
|
103
|
+
return packagePath(root, relative ? path.join('engine', relative) : 'engine')
|
|
104
|
+
}
|
|
105
|
+
|
|
113
106
|
// Definiciones que consume el motor —cargos y equipos— y que por eso viajan con el paquete en vez
|
|
114
107
|
// de copiarse. Se reconocen por contener `system/`, que es el espacio del toolkit y no algo que un
|
|
115
108
|
// proyecto deba crear. Una sola implementación para las dos, o divergen.
|
|
@@ -165,6 +158,10 @@ const RETIRED = [
|
|
|
165
158
|
'.github/workflows/agent-learning.yml',
|
|
166
159
|
'automatization/runners',
|
|
167
160
|
'automatization/workflows',
|
|
161
|
+
// Prometía ser el interruptor de los guards —«runner activo y gates requeridos», decía su README—
|
|
162
|
+
// y no lo leía nadie. Quien corría `install claude` seguía viendo `"runner": "manual"`, y un cargo
|
|
163
|
+
// que lo leyó dio por configurados unos gates que en realidad decide la configuración del runner.
|
|
164
|
+
'automatization/config.json',
|
|
168
165
|
]
|
|
169
166
|
|
|
170
167
|
// Aprendizaje que quedó dentro de una ruta retirada. Es lo único ahí que no se puede reponer, así
|
|
@@ -234,11 +231,9 @@ module.exports = {
|
|
|
234
231
|
RUNTIME_PATHS,
|
|
235
232
|
retiredWithLearning,
|
|
236
233
|
engineAt,
|
|
237
|
-
engineCandidates,
|
|
238
234
|
packagePath,
|
|
239
235
|
SYSTEM_COLLECTIONS,
|
|
240
236
|
SYSTEM_FILES,
|
|
241
|
-
identity,
|
|
242
237
|
localChanges,
|
|
243
238
|
overrides,
|
|
244
239
|
sourceOf,
|
package/engine/hooks/run.js
CHANGED
|
@@ -18,10 +18,6 @@ function readJson(file) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function writeJson(file, value) {
|
|
22
|
-
F.atomicWriteJson(file, value)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
21
|
function safeSegment(value, label) {
|
|
26
22
|
const segment = String(value || '')
|
|
27
23
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(segment) || segment === '.' || segment === '..') {
|
|
@@ -95,7 +91,7 @@ function validate(root, onlyProvider = '') {
|
|
|
95
91
|
const secret = sensitivePath(loaded.config)
|
|
96
92
|
if (secret) errors.push(`${name}: ${secret} no puede contener secretos; usa una variable de entorno`)
|
|
97
93
|
try {
|
|
98
|
-
adapter(name).validateConfig(loaded.config, errors
|
|
94
|
+
adapter(name).validateConfig(loaded.config, errors)
|
|
99
95
|
} catch (error) {
|
|
100
96
|
errors.push(`${name}: ${error.message}`)
|
|
101
97
|
}
|
|
@@ -254,11 +250,11 @@ async function sync(root, name, options = {}) {
|
|
|
254
250
|
}
|
|
255
251
|
fs.mkdirSync(desiredDir, { recursive: true })
|
|
256
252
|
F.atomicWrite(draftFile, draft)
|
|
257
|
-
|
|
253
|
+
F.atomicWriteJson(snapshotFile, snapshot)
|
|
258
254
|
}
|
|
259
255
|
const complete = options.complete !== false
|
|
260
256
|
if (complete) cleanupMissing(root, name, seen, result)
|
|
261
|
-
|
|
257
|
+
F.atomicWriteJson(path.join(staging, 'sync-state.json'), {
|
|
262
258
|
schemaVersion: 2,
|
|
263
259
|
provider: name,
|
|
264
260
|
complete,
|
|
@@ -284,7 +280,7 @@ function cleanupMissing(root, name, seen, result) {
|
|
|
284
280
|
continue
|
|
285
281
|
}
|
|
286
282
|
snapshot.sync.missingFromRemote = true
|
|
287
|
-
|
|
283
|
+
F.atomicWriteJson(snapshotFile, snapshot)
|
|
288
284
|
result.missing++
|
|
289
285
|
}
|
|
290
286
|
}
|
|
@@ -364,7 +360,7 @@ function promote(root, name, key) {
|
|
|
364
360
|
draft = replaceField(draft, 'promotedAt', `"${new Date().toISOString()}"`)
|
|
365
361
|
F.atomicWrite(draftFile, draft)
|
|
366
362
|
snapshot.sync.draftChanged = true
|
|
367
|
-
|
|
363
|
+
F.atomicWriteJson(path.join(dir, 'remote.json'), snapshot)
|
|
368
364
|
return { key, kind: fields.promotionKind }
|
|
369
365
|
}
|
|
370
366
|
|
|
@@ -385,13 +381,11 @@ const oneLine = (value) => String(value).replace(/\s+/g, ' ').trim()
|
|
|
385
381
|
module.exports = {
|
|
386
382
|
STATES,
|
|
387
383
|
adapter,
|
|
388
|
-
draftSections,
|
|
389
384
|
frontmatter,
|
|
390
385
|
promote,
|
|
391
386
|
providerConfig,
|
|
392
387
|
reconcile,
|
|
393
388
|
safeSegment,
|
|
394
|
-
stagingItems,
|
|
395
389
|
sync,
|
|
396
390
|
validate,
|
|
397
391
|
writebackPlan,
|
|
@@ -6,7 +6,6 @@ const fs = require('fs')
|
|
|
6
6
|
const path = require('path')
|
|
7
7
|
|
|
8
8
|
const EPIC_STATES = ['open', 'active', 'closed']
|
|
9
|
-
const TIERS = ['directo', 'lite', 'full']
|
|
10
9
|
|
|
11
10
|
function read(file) {
|
|
12
11
|
try { return fs.readFileSync(file, 'utf8') } catch { return '' }
|
|
@@ -62,7 +61,7 @@ function criteriaRefs(text) {
|
|
|
62
61
|
return [...new Set(refs)]
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
function readEpics(dir
|
|
64
|
+
function readEpics(dir) {
|
|
66
65
|
return epicFiles(dir).map(({ name, file }) => {
|
|
67
66
|
const text = read(file)
|
|
68
67
|
const field = frontmatter(text)
|
|
@@ -76,7 +75,6 @@ function readEpics(dir, { includeTemplates = false } = {}) {
|
|
|
76
75
|
const stories = [...section(text, /Historias/i).matchAll(storyPattern)]
|
|
77
76
|
.map((match) => ({
|
|
78
77
|
slug: match[1].trim(),
|
|
79
|
-
text: match[2].replace(/\s+/g, ' ').trim(),
|
|
80
78
|
criteria: criteriaRefs(match[2]),
|
|
81
79
|
service: ((match[2].match(/\(service:\s*([^)]+)\)/) || [])[1] || '').trim(),
|
|
82
80
|
}))
|
|
@@ -86,12 +84,11 @@ function readEpics(dir, { includeTemplates = false } = {}) {
|
|
|
86
84
|
num: field('epic'),
|
|
87
85
|
title: field('title'),
|
|
88
86
|
status: field('status'),
|
|
89
|
-
service: field('service'),
|
|
90
87
|
criteria,
|
|
91
88
|
stories,
|
|
92
89
|
hasContext: /^##\s+Contexto relevante/im.test(text),
|
|
93
90
|
}
|
|
94
|
-
}).filter((epic) =>
|
|
91
|
+
}).filter((epic) => epic.status !== 'template' && epic.num !== '000')
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
function readBacklog(dir) {
|
|
@@ -110,7 +107,7 @@ function readBacklog(dir) {
|
|
|
110
107
|
if (!task || !current) continue
|
|
111
108
|
const rest = task[3]
|
|
112
109
|
current.tasks.push({
|
|
113
|
-
slug: task[1].trim(), tier: task[2] || '',
|
|
110
|
+
slug: task[1].trim(), tier: task[2] || '',
|
|
114
111
|
epic: ((rest.match(/\(epic:\s*(\d{3})\)/) || [])[1] || ''),
|
|
115
112
|
service: ((rest.match(/\(service:\s*([^)]+)\)/) || [])[1] || '').trim(),
|
|
116
113
|
acceptance: ((rest.match(/_Aceptaci[oó]n:\s*([^_]+)_/i) || [])[1] || '').trim(),
|
|
@@ -180,5 +177,5 @@ function readInbox(dir) {
|
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
module.exports = {
|
|
183
|
-
EPIC_STATES,
|
|
180
|
+
EPIC_STATES, read, frontmatter, readEpics, readBacklog, readDone, readWip, readInbox,
|
|
184
181
|
}
|
package/package.json
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
Wiring local entre `planning/PROTOCOL.md` y el runner elegido. El proyecto empieza en modo manual y seguro;
|
|
4
4
|
ningún runner se activa solo.
|
|
5
5
|
|
|
6
|
-
- `config.json`: runner activo y gates requeridos.
|
|
7
6
|
- `hooks/`: los guards que se ejecutan. Acá va también el tuyo: agregalo con otro nombre y registralo
|
|
8
7
|
en la configuración de tu runner, que es del proyecto y sobrevive a cada actualización.
|
|
9
8
|
|
|
9
|
+
Qué guard corre y cuándo lo decide la configuración de tu runner —`.claude/settings.json` y sus
|
|
10
|
+
equivalentes—, que es la única fuente: la escribe `automation install` y la lee la herramienta. Para
|
|
11
|
+
desactivar un guard, quitá su entrada de ahí. `automation list-hooks` enumera los que existen.
|
|
12
|
+
|
|
10
13
|
Los adaptadores de runner y los workflows no se copian acá: son definiciones que el motor consume y
|
|
11
14
|
viajan con Cauce, igual que el catálogo de cargos y los equipos. `automation install` los lee desde ahí.
|
|
12
15
|
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": 1,
|
|
3
|
-
"runner": "manual",
|
|
4
|
-
"protocol": "planning/PROTOCOL.md",
|
|
5
|
-
"gates": {
|
|
6
|
-
"destructiveCommands": true,
|
|
7
|
-
"secrets": true,
|
|
8
|
-
"integrationSnapshots": true,
|
|
9
|
-
"verifyBeforeCommit": true,
|
|
10
|
-
"validateOnStop": true,
|
|
11
|
-
"humanCheckpointBetweenMilestones": true
|
|
12
|
-
}
|
|
13
|
-
}
|