@ingeniomaps/cauce 0.20.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 +78 -0
- package/README.md +7 -2
- 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/agents/roles/system/product-manager/evaluations/results/2026-08-16.md +700 -0
- package/automatization/workflows/agent-eval.js +32 -11
- 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 +55 -15
- 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
|
@@ -111,16 +111,26 @@ if (!contexto || !contexto.items || !contexto.items.length) {
|
|
|
111
111
|
// Dónde trabaja el cargo mientras responde. En el toolkit no puede ser acá: no hay `planning/` que
|
|
112
112
|
// valga, así que se le arma un banco desechable. En una empresa es su propia instancia, y el cargo
|
|
113
113
|
// tiene que ser suyo —propio o adoptado—: uno del catálogo se evalúa arriba, no acá.
|
|
114
|
-
|
|
114
|
+
// Un banco por caso, no uno por cargo. Con uno compartido los casos corren a la vez sobre el mismo
|
|
115
|
+
// `planning/` y se leen entre sí: uno tomó por «una sesión anterior de este mismo cargo» lo que otro
|
|
116
|
+
// acababa de escribir, y otro evaluó cuatro candidatas que en su enunciado no existían.
|
|
117
|
+
//
|
|
118
|
+
// Un solo agente los prepara —son comandos deterministas— y después la ruta de cada caso se arma
|
|
119
|
+
// sola, sin volver a preguntar.
|
|
120
|
+
const BENCH_ROOT = `${ROOT}/.cauce-eval/${AGENT}`
|
|
121
|
+
let porCaso = null
|
|
115
122
|
if (contexto.mode === 'toolkit') {
|
|
116
123
|
const banco = await agent(
|
|
117
|
-
`From ${ROOT}, run
|
|
118
|
-
`
|
|
119
|
-
|
|
124
|
+
`From ${ROOT}, run one command per case, in order, and report only whether all of them printed a ` +
|
|
125
|
+
`path:\n` +
|
|
126
|
+
contexto.items.map((item) => ` node tools/ops.js evaluate ${AGENT} --bench ${item.id}`).join('\n') +
|
|
127
|
+
`\n\nEach one recreates a disposable instance where writing to planning/ is legitimate. Set path ` +
|
|
128
|
+
`to the directory they share: ${BENCH_ROOT}`,
|
|
129
|
+
{ schema: BENCH, label: 'bancos' },
|
|
120
130
|
)
|
|
121
|
-
if (!banco || !banco.path) return stop('sin-banco', 'no se
|
|
122
|
-
|
|
123
|
-
log(`
|
|
131
|
+
if (!banco || !banco.path) return stop('sin-banco', 'no se pudieron preparar los bancos de evaluación')
|
|
132
|
+
porCaso = (item) => `${BENCH_ROOT}/${item.id}`
|
|
133
|
+
log(`Bancos: ${BENCH_ROOT}/<caso>`)
|
|
124
134
|
} else if (contexto.system) {
|
|
125
135
|
return stop('cargo-del-catalogo',
|
|
126
136
|
`${AGENT} lo mantiene Cauce, no esta empresa: evaluarlo acá mediría tu configuración y el ` +
|
|
@@ -134,10 +144,11 @@ const veredictos = await pipeline(
|
|
|
134
144
|
|
|
135
145
|
// Responde el cargo. Recibe su contrato y el pedido; nunca los comportamientos esperados.
|
|
136
146
|
(item) => agent(
|
|
137
|
-
`Trabajás en ${
|
|
138
|
-
`Todo lo que escribas va ahí.\n\n` +
|
|
147
|
+
`Trabajás en ${porCaso ? porCaso(item) : ROOT}: esa es tu instancia, con su planning/, su ` +
|
|
148
|
+
`organization/ y su AGENTS.md. Todo lo que escribas va ahí.\n\n` +
|
|
139
149
|
`Actuá como el cargo ${AGENT}, respetando el contrato de ${contexto.skill}: cuándo actuar, qué ` +
|
|
140
|
-
`decide, qué no le corresponde y cuál es su entrega mínima. Leé también
|
|
150
|
+
`decide, qué no le corresponde y cuál es su entrega mínima. Leé también el AGENTS.md de esa ` +
|
|
151
|
+
`instancia: son las ` +
|
|
141
152
|
`reglas que todo cargo obedece, y un cargo corre siempre con las dos cosas —medirlo sólo contra su ` +
|
|
142
153
|
`SKILL.md lo evaluaba en una situación que nunca ocurre—. No leas ningún archivo bajo ` +
|
|
143
154
|
`evaluations/: no te corresponde y contaminaría la respuesta.\n\n` +
|
|
@@ -150,8 +161,18 @@ const veredictos = await pipeline(
|
|
|
150
161
|
// mide contra lo que el caso declara, no contra una relectura del SKILL.md.
|
|
151
162
|
(answer, item) => (answer ? agent(
|
|
152
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
|
+
: '') +
|
|
153
173
|
`Contrastá esa respuesta contra cada comportamiento esperado y decidí si se observa o no. ` +
|
|
154
|
-
`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 ` +
|
|
155
176
|
`intención ni el tono: sólo lo que la respuesta dice.\n\n` +
|
|
156
177
|
`Comportamientos esperados:\n${item.expected.map((one, index) => `${index + 1}. ${one}`).join('\n')}\n\n` +
|
|
157
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')
|
|
@@ -49,7 +50,7 @@ function usage() {
|
|
|
49
50
|
ops automation doctor <ops-root> claude|codex|gemini|antigravity
|
|
50
51
|
ops automation install <ops-root> claude|codex|gemini|antigravity
|
|
51
52
|
ops learn <agent> [--proposal]
|
|
52
|
-
ops evaluate <agent> [--cases [--json]] [--bench]
|
|
53
|
+
ops evaluate <agent> [--cases [--json]] [--bench [caso]]
|
|
53
54
|
ops agents list [ops-root] [--own|--system] [--json]
|
|
54
55
|
ops agents fork <cargo> [ops-root]
|
|
55
56
|
ops team list
|
|
@@ -191,12 +192,34 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
191
192
|
// es una épica o una entrada de INBOX no tiene dónde escribir, así que se niega. Con razón, y su caso
|
|
192
193
|
// lo cuenta como fallo: eso midió una configuración, no al cargo.
|
|
193
194
|
//
|
|
195
|
+
// **Uno por caso**, y esto se aprendió corriendo. Con un banco compartido, los cinco casos de un
|
|
196
|
+
// cargo corren a la vez sobre el mismo `planning/` y se leen entre sí: un caso tomó por «una sesión
|
|
197
|
+
// anterior de este mismo cargo» lo que otro acababa de escribir, y otro evaluó cuatro candidatas que
|
|
198
|
+
// en su enunciado no existían. Ninguno de los dos cambió de veredicto, pero las respuestas ya no eran
|
|
199
|
+
// las que el caso pedía medir. La independencia entre casos es la premisa de medir con ellos.
|
|
200
|
+
//
|
|
194
201
|
// Se recrea entero en cada corrida. Reutilizarlo dejaría que lo que un cargo escribió el lunes sea
|
|
195
202
|
// contexto del que responde el martes, y dos corridas del mismo caso dejarían de ser comparables.
|
|
196
203
|
// Queda en disco al terminar, gitignorado, porque después de un veredicto raro lo primero que uno
|
|
197
204
|
// quiere es mirar qué escribió el cargo.
|
|
198
|
-
function evaluationBench(root) {
|
|
199
|
-
const
|
|
205
|
+
function evaluationBench(root, agent, caso) {
|
|
206
|
+
const safe = (value) => {
|
|
207
|
+
if (!/^[a-z0-9_][a-z0-9._-]*$/i.test(value) || value.includes('..')) {
|
|
208
|
+
fail(`nombre inválido para el banco: ${value}`, 2)
|
|
209
|
+
}
|
|
210
|
+
return value
|
|
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
|
+
}
|
|
200
223
|
fs.rmSync(dir, { recursive: true, force: true })
|
|
201
224
|
scaffold(dir, { name: 'Banco de evaluación', mode: 'sidecar', quiet: true })
|
|
202
225
|
// El motor por symlink: la misma resolución que en una instancia real —`node_modules/@ingeniomaps`—
|
|
@@ -204,6 +227,22 @@ function evaluationBench(root) {
|
|
|
204
227
|
const scope = path.join(dir, 'node_modules', '@ingeniomaps')
|
|
205
228
|
fs.mkdirSync(scope, { recursive: true })
|
|
206
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')
|
|
207
246
|
return dir
|
|
208
247
|
}
|
|
209
248
|
|
|
@@ -513,13 +552,6 @@ function instanceVersion(root) {
|
|
|
513
552
|
} catch { return '' }
|
|
514
553
|
}
|
|
515
554
|
|
|
516
|
-
// Sobrescribe lo que trae el paquete y deja intacto lo demás: un guard propio de la empresa,
|
|
517
|
-
// o un adaptador de runner que el toolkit no conoce, sobreviven a la actualización.
|
|
518
|
-
function overlayTree(from, to, root, skip = []) {
|
|
519
|
-
F.assertNoSymlinkPath(root, to)
|
|
520
|
-
copyRuntime(from, to, false, root, skip)
|
|
521
|
-
}
|
|
522
|
-
|
|
523
555
|
// Actualiza sólo lo que el toolkit declara suyo. Todo lo demás —planning, organization, reglas
|
|
524
556
|
// propias, agentes editados— queda intacto por construcción, no por comparación.
|
|
525
557
|
function upgrade(dir) {
|
|
@@ -581,7 +613,9 @@ function upgrade(dir) {
|
|
|
581
613
|
const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
|
|
582
614
|
if (!fs.existsSync(origin)) continue
|
|
583
615
|
const target = path.join(root, relative)
|
|
584
|
-
|
|
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)
|
|
585
619
|
else {
|
|
586
620
|
F.assertNoSymlinkPath(root, target)
|
|
587
621
|
F.atomicWrite(target, fs.readFileSync(origin, 'utf8'))
|
|
@@ -650,8 +684,6 @@ function upgrade(dir) {
|
|
|
650
684
|
for (const entry of FK.drift(root)) console.log(` ⚠ ${FK.driftLine(entry)}`)
|
|
651
685
|
}
|
|
652
686
|
|
|
653
|
-
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
654
|
-
// reimplemente el recorrido del catálogo.
|
|
655
687
|
// La raíz ops de un comando que no la recibe. El shim `tools/ops.js` la exporta porque sabe dónde
|
|
656
688
|
// vive: sin eso, invocarlo desde otra carpeta —lo normal en sidecar— la resolvía contra el cwd.
|
|
657
689
|
function opsRoot(dir) {
|
|
@@ -672,6 +704,8 @@ function agentsFork(slug, dir) {
|
|
|
672
704
|
console.log(` reinstalá tu runner para que ${slug} apunte a tu copia`)
|
|
673
705
|
}
|
|
674
706
|
|
|
707
|
+
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
708
|
+
// reimplemente el recorrido del catálogo.
|
|
675
709
|
function agents(action, dir, extra) {
|
|
676
710
|
if (action === 'fork') return agentsFork(dir, extra)
|
|
677
711
|
if (action !== 'list') fail(`Acción de agents desconocida: ${action || '(vacía)'}`, 2)
|
|
@@ -845,7 +879,9 @@ function automation(action, rootArg, runnerName) {
|
|
|
845
879
|
const errors = A.check(root)
|
|
846
880
|
for (const error of errors) console.error(`✗ ${error}`)
|
|
847
881
|
if (errors.length) fail(`${errors.length} error(es) de automatización`)
|
|
848
|
-
console.log(
|
|
882
|
+
console.log(
|
|
883
|
+
`✓ automatización válida: ${A.GUARD_NAMES.length} guards, ${A.RUNNER_NAMES.length} adaptadores`,
|
|
884
|
+
)
|
|
849
885
|
return
|
|
850
886
|
}
|
|
851
887
|
if (action === 'doctor') {
|
|
@@ -895,7 +931,11 @@ function evaluate(agent) {
|
|
|
895
931
|
fail('--bench es del toolkit. En una instancia, el cargo trabaja sobre tu planning/: si es del '
|
|
896
932
|
+ `catálogo, adoptalo primero con "ops agents fork ${agent}".`, 2)
|
|
897
933
|
}
|
|
898
|
-
|
|
934
|
+
// El caso viene detrás de la bandera: `evaluate <cargo> --bench <caso>`. Sin él se arma un banco
|
|
935
|
+
// suelto, para mirarlo a mano; una corrida real pide uno por caso.
|
|
936
|
+
const flag = process.argv.indexOf('--bench')
|
|
937
|
+
const caso = (process.argv[flag + 1] || '').startsWith('--') ? '' : process.argv[flag + 1]
|
|
938
|
+
return console.log(evaluationBench(root, agent, caso))
|
|
899
939
|
}
|
|
900
940
|
try {
|
|
901
941
|
// Los casos, para que un recorrido los ejecute. Sin `--json` no tiene sentido: es entrada de
|
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
|
}
|