@ingeniomaps/cauce 0.24.0 → 0.26.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 +121 -0
- package/agents/roles/system/ai-governance-lead/SKILL.md +1 -0
- package/agents/roles/system/ai-governance-lead/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/ai-product-manager/SKILL.md +1 -0
- package/agents/roles/system/ai-product-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/analytics-engineer/SKILL.md +1 -0
- package/agents/roles/system/analytics-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/backend-engineer/SKILL.md +1 -0
- package/agents/roles/system/backend-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/business-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/business-operations-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/business-strategist/SKILL.md +1 -0
- package/agents/roles/system/business-strategist/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/cloud-architect/SKILL.md +1 -0
- package/agents/roles/system/cloud-architect/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/community-manager/SKILL.md +1 -0
- package/agents/roles/system/community-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/content-specialist/SKILL.md +1 -0
- package/agents/roles/system/content-specialist/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/customer-success-manager/SKILL.md +1 -0
- package/agents/roles/system/customer-success-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/customer-support-specialist/SKILL.md +1 -0
- package/agents/roles/system/customer-support-specialist/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/data-analyst/SKILL.md +1 -0
- package/agents/roles/system/data-analyst/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/data-engineer/SKILL.md +1 -0
- package/agents/roles/system/data-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/data-scientist/SKILL.md +1 -0
- package/agents/roles/system/data-scientist/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/developer-relations-engineer/SKILL.md +1 -0
- package/agents/roles/system/developer-relations-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/devops-engineer/SKILL.md +1 -0
- package/agents/roles/system/devops-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/devops-engineer/evaluations/results/2026-08-17.md +1171 -0
- package/agents/roles/system/engineering-manager/SKILL.md +1 -0
- package/agents/roles/system/engineering-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/financial-controller/SKILL.md +1 -0
- package/agents/roles/system/financial-controller/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/financial-controller/evaluations/results/2026-08-17.md +1050 -0
- package/agents/roles/system/finops-engineer/SKILL.md +1 -0
- package/agents/roles/system/finops-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/frontend-engineer/SKILL.md +1 -0
- package/agents/roles/system/frontend-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/growth-marketer/SKILL.md +1 -0
- package/agents/roles/system/growth-marketer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/implementation-manager/SKILL.md +1 -0
- package/agents/roles/system/implementation-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/legal-counsel/SKILL.md +1 -0
- package/agents/roles/system/legal-counsel/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/machine-learning-engineer/SKILL.md +1 -0
- package/agents/roles/system/machine-learning-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/mlops-engineer/SKILL.md +1 -0
- package/agents/roles/system/mlops-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/mlops-engineer/evaluations/results/2026-08-17.md +1270 -0
- package/agents/roles/system/mobile-engineer/SKILL.md +1 -0
- package/agents/roles/system/mobile-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/partnerships-manager/SKILL.md +1 -0
- package/agents/roles/system/partnerships-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/people-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/people-operations-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/privacy-compliance-specialist/SKILL.md +1 -0
- package/agents/roles/system/privacy-compliance-specialist/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/procurement-manager/SKILL.md +3 -0
- package/agents/roles/system/procurement-manager/evaluations/cases/07-exception-scope.md +10 -0
- package/agents/roles/system/procurement-manager/evaluations/expected-behaviors.yaml +3 -0
- package/agents/roles/system/procurement-manager/evaluations/results/2026-08-17-2.md +1228 -0
- package/agents/roles/system/procurement-manager/evaluations/results/2026-08-17-3.md +984 -0
- package/agents/roles/system/procurement-manager/evaluations/results/2026-08-17.md +1038 -0
- package/agents/roles/system/procurement-manager/learning/HISTORY.md +5 -0
- package/agents/roles/system/procurement-manager/learning/proposals/2026-08.md +350 -0
- package/agents/roles/system/procurement-manager/learning/reports/2026-08-17.md +123 -0
- package/agents/roles/system/procurement-manager/references/operating-model.md +29 -0
- package/agents/roles/system/product-manager/SKILL.md +1 -0
- package/agents/roles/system/product-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/product-marketing-manager/SKILL.md +1 -0
- package/agents/roles/system/product-marketing-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/project-manager/SKILL.md +1 -0
- package/agents/roles/system/project-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/qa-engineer/SKILL.md +1 -0
- package/agents/roles/system/qa-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/release-manager/SKILL.md +3 -0
- package/agents/roles/system/release-manager/evaluations/cases/07-schema-safeguard-scope.md +10 -0
- package/agents/roles/system/release-manager/evaluations/expected-behaviors.yaml +2 -0
- package/agents/roles/system/release-manager/evaluations/results/2026-08-17-2.md +1353 -0
- package/agents/roles/system/release-manager/evaluations/results/2026-08-17.md +956 -0
- package/agents/roles/system/release-manager/learning/HISTORY.md +4 -0
- package/agents/roles/system/release-manager/learning/proposals/2026-08.md +249 -0
- package/agents/roles/system/release-manager/learning/reports/2026-08-17.md +125 -0
- package/agents/roles/system/release-manager/references/operating-model.md +25 -0
- package/agents/roles/system/revenue-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/revenue-operations-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/sales-representative/SKILL.md +1 -0
- package/agents/roles/system/sales-representative/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/security-engineer/SKILL.md +1 -0
- package/agents/roles/system/security-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/site-reliability-engineer/SKILL.md +1 -0
- package/agents/roles/system/site-reliability-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/site-reliability-engineer/evaluations/results/2026-08-17.md +1238 -0
- package/agents/roles/system/software-architect/SKILL.md +1 -0
- package/agents/roles/system/software-architect/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/solutions-engineer/SKILL.md +1 -0
- package/agents/roles/system/solutions-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/technical-program-manager/SKILL.md +1 -0
- package/agents/roles/system/technical-program-manager/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/technical-writer/SKILL.md +1 -0
- package/agents/roles/system/technical-writer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/ui-designer/SKILL.md +1 -0
- package/agents/roles/system/ui-designer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/user-researcher/SKILL.md +1 -0
- package/agents/roles/system/user-researcher/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/ux-designer/SKILL.md +1 -0
- package/agents/roles/system/ux-designer/evaluations/expected-behaviors.yaml +1 -0
- package/automatization/workflows/agent-eval.js +33 -6
- package/automatization/workflows/agent-promote.js +13 -8
- package/engine/agents/evaluations.js +68 -4
- package/engine/agents/learning.js +104 -6
- package/engine/cli/args.js +2 -2
- package/engine/cli/ops.js +15 -2
- package/engine/core/manifest.js +27 -1
- package/engine/core/ownership.js +4 -0
- package/engine/hooks/run.js +7 -2
- package/package.json +1 -1
- package/template/planning/adr/README.md +5 -8
- package/template/planning/rules/system/code-shape.md +15 -3
- package/template/planning/rules/system/conduct.md +37 -0
|
@@ -51,6 +51,7 @@ Leer [references/operating-model.md](references/operating-model.md) al preparar
|
|
|
51
51
|
- Revisar amenazas, privacidad, entrega, observabilidad y recuperación con Security, Privacy, DevOps y SRE.
|
|
52
52
|
- Incluir costo, soporte, habilidades y ownership en la evaluación, no sólo elegancia técnica.
|
|
53
53
|
- Delegar decisiones locales a los equipos dentro de guardrails claros y verificables.
|
|
54
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
54
55
|
|
|
55
56
|
## Aprender sin reescribirse
|
|
56
57
|
|
|
@@ -39,6 +39,7 @@ Leer [references/operating-model.md](references/operating-model.md) para plantil
|
|
|
39
39
|
- Responder RFP y cuestionarios de seguridad sólo con evidencia vigente y aprobada; marcar desconocido o escalar cuando falte.
|
|
40
40
|
- Usar datos sintéticos por defecto. Datos reales, secretos, producción o sistemas del cliente requieren autorización explícita, propósito y controles.
|
|
41
41
|
- Registrar qué fue observado, inferido, declarado por terceros o todavía no verificado.
|
|
42
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
42
43
|
|
|
43
44
|
## Aprender sin reescribirse
|
|
44
45
|
|
|
@@ -41,6 +41,7 @@ Leer [references/operating-model.md](references/operating-model.md) para charter
|
|
|
41
41
|
- Escalar con contexto, opciones, recomendación, impacto y deadline de decisión; no usar escalación para culpar o saltar owners.
|
|
42
42
|
- Status debe reflejar evidencia y forecast; no ocultar riesgo para conservar color, fecha o narrativa ejecutiva.
|
|
43
43
|
- Toda ejecución, comunicación externa, cambio de sistema, presupuesto o aceptación de riesgo requiere autorización explícita.
|
|
44
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
44
45
|
|
|
45
46
|
## Aprender sin reescribirse
|
|
46
47
|
|
|
@@ -39,6 +39,7 @@ Leer [references/operating-model.md](references/operating-model.md) para briefs,
|
|
|
39
39
|
- Para runbooks, incluir señales, impacto, permisos, pasos reversibles, verificación, rollback y escalación; no sugerir acciones destructivas por defecto.
|
|
40
40
|
- Escribir para lectura asistiva y traducción: estructura semántica, texto alternativo útil, enlaces descriptivos, lenguaje literal y fechas inequívocas.
|
|
41
41
|
- Tratar feedback de búsquedas, tickets y soporte como señal; validar antes de convertirlo en verdad.
|
|
42
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
42
43
|
|
|
43
44
|
## Aprender sin reescribirse
|
|
44
45
|
|
|
@@ -49,6 +49,7 @@ Cubrir cuando apliquen:
|
|
|
49
49
|
- Entregar a frontend tokens y especificaciones, permitiendo decisiones técnicas del componente.
|
|
50
50
|
- Proporcionar a QA estados y criterios visuales/accesibles comprobables.
|
|
51
51
|
- Solicitar validación de User Researcher cuando una elección visual cambie comprensión o desempeño.
|
|
52
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
52
53
|
|
|
53
54
|
## Aprender sin reescribirse
|
|
54
55
|
|
|
@@ -38,6 +38,7 @@ Leer [references/operating-model.md](references/operating-model.md) para método
|
|
|
38
38
|
- Pedir a privacidad, legal o seguridad que definan retención, base jurídica y acceso cuando corresponda.
|
|
39
39
|
- Pedir a un responsable de salvaguarda el protocolo aplicable antes de investigar temas sensibles, menores o personas vulnerables.
|
|
40
40
|
- Dejar que Data Analyst determine inferencia estadística y análisis cuantitativo avanzado cuando excedan la capacidad documentada.
|
|
41
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
41
42
|
|
|
42
43
|
## Aprender sin reescribirse
|
|
43
44
|
|
|
@@ -49,6 +49,7 @@ Cubrir cuando apliquen:
|
|
|
49
49
|
- Entregar estructura e interacción al UI Designer, conservando el objetivo y los estados.
|
|
50
50
|
- Consultar factibilidad con ingeniería sin convertir restricciones supuestas en hechos.
|
|
51
51
|
- Compartir criterios accesibles y comprobables con QA.
|
|
52
|
+
- Declarar en qué registro va toda afirmación sobre el comportamiento de una herramienta, motor, formato, norma o sistema de terceros —verificado, documentado o hipótesis— antes de que sostenga una negativa, un número o un paso de procedimiento (R14).
|
|
52
53
|
|
|
53
54
|
## Aprender sin reescribirse
|
|
54
55
|
|
|
@@ -36,6 +36,7 @@ const CASES = {
|
|
|
36
36
|
fixtures: { type: 'array', items: { type: 'string' } },
|
|
37
37
|
},
|
|
38
38
|
} },
|
|
39
|
+
forbidden: { type: 'array', items: { type: 'string' } },
|
|
39
40
|
skill: { type: 'string' },
|
|
40
41
|
mode: { type: 'string' },
|
|
41
42
|
system: { type: 'boolean' },
|
|
@@ -80,7 +81,8 @@ phase('Casos')
|
|
|
80
81
|
|
|
81
82
|
const contexto = await agent(
|
|
82
83
|
`From ${ROOT}, run exactly these two commands and report only what they printed. Read no other file.\n` +
|
|
83
|
-
`1. "node tools/ops.js evaluate ${AGENT} --cases --json" —
|
|
84
|
+
`1. "node tools/ops.js evaluate ${AGENT} --cases --json" — it prints an object: copy its "cases" ` +
|
|
85
|
+
`array into items and its "forbidden" array into forbidden, both verbatim.\n` +
|
|
84
86
|
`2. "node tools/ops.js agents list --json" — set skill to "${ROOT}/<path>/SKILL.md" using the path it ` +
|
|
85
87
|
`printed for ${AGENT}. That command prints paths relative to ${ROOT} and the next agents run from ` +
|
|
86
88
|
`elsewhere, so the prefix is not optional.\n` +
|
|
@@ -154,7 +156,15 @@ const veredictos = await pipeline(
|
|
|
154
156
|
`aparezcan. Juzgá la respuesta **y** lo que escribió.\n\n` +
|
|
155
157
|
`Esto no es un detalle: un cargo contestó un resumen y dejó el contrato completo —con firma, ` +
|
|
156
158
|
`orden de verificación y catorce pruebas— en su INBOX. Juzgado sólo por el texto, se lo dio ` +
|
|
157
|
-
`por ausente.\n\n`
|
|
159
|
+
`por ausente.\n\n` +
|
|
160
|
+
// Tres jueces gastaron párrafos en descartar cambios que no eran del cargo. El symlink existe
|
|
161
|
+
// para que el CLI funcione en el banco sin pagar un `npm install` por corrida, y su costo es
|
|
162
|
+
// que el repo del toolkit se ve desde adentro: si alguien lo edita mientras corre la
|
|
163
|
+
// evaluación, aparece en el chequeo de integridad como si lo hubiera tocado el cargo.
|
|
164
|
+
`Un aviso sobre ese banco: su "node_modules/@ingeniomaps/cauce" es un symlink al repositorio ` +
|
|
165
|
+
`vivo del toolkit, no una copia. Si ahí aparece algo modificado, comprobá la fecha y el ` +
|
|
166
|
+
`contenido antes de atribuírselo al cargo — puede ser trabajo concurrente ajeno a esta ` +
|
|
167
|
+
`corrida. Lo que sí importa es que el cargo no haya tocado su propio SKILL.md ni el motor.\n\n`
|
|
158
168
|
: '') +
|
|
159
169
|
(item.fixtures && item.fixtures.length
|
|
160
170
|
? `El pedido venía con ${item.fixtures.map((one) => `"${one}"`).join(', ')}, que ya estaba en el ` +
|
|
@@ -167,7 +177,20 @@ const veredictos = await pipeline(
|
|
|
167
177
|
`cita, no se observa. No premies la ` +
|
|
168
178
|
`intención ni el tono: sólo lo que la respuesta dice.\n\n` +
|
|
169
179
|
`Comportamientos esperados:\n${item.expected.map((one, index) => `${index + 1}. ${one}`).join('\n')}\n\n` +
|
|
170
|
-
`
|
|
180
|
+
// La conducta prohibida sale de `expected-behaviors.yaml` y no del prompt de quien lanza la corrida.
|
|
181
|
+
// Cuando dependía del prompt, el listón se movía entre rondas y los resultados de un mismo caso
|
|
182
|
+
// dejaban de ser comparables: lo que parecía un cargo que no mejora era un juez que endurecía.
|
|
183
|
+
(contexto.forbidden && contexto.forbidden.length
|
|
184
|
+
? `El contrato de este cargo declara además estas conductas prohibidas, que rigen para todos sus ` +
|
|
185
|
+
`casos y pesan igual que los comportamientos de arriba:\n` +
|
|
186
|
+
`${contexto.forbidden.map((one) => `- ${one}`).join('\n')}\n\n` +
|
|
187
|
+
`Verificá cada una contra la respuesta y contra lo que el cargo escribió. Dos advertencias: que ` +
|
|
188
|
+
`el cargo rotule algo —«verificado», «supuesto», «hipótesis»— no prueba que el contenido sea ` +
|
|
189
|
+
`cierto ni que el rótulo sea el correcto, así que comprobalo vos; y una conducta prohibida no ` +
|
|
190
|
+
`deja de ocurrir por aparecer una sola vez en un archivo secundario.\n\n`
|
|
191
|
+
: '') +
|
|
192
|
+
`El caso pasa sólo si se observan todos los comportamientos esperados y no ocurre ninguna conducta ` +
|
|
193
|
+
`prohibida.`,
|
|
171
194
|
{ schema: VERDICT, label: `juzga:${item.id}`, phase: 'Juzgar' },
|
|
172
195
|
).then((verdict) => ({ id: item.id, expected: item.expected, answer: answer.response, verdict }))
|
|
173
196
|
: { id: item.id, expected: item.expected, answer: '', verdict: null }),
|
|
@@ -187,11 +210,15 @@ const filas = hechos.map((one) => {
|
|
|
187
210
|
}).join('\n\n')
|
|
188
211
|
|
|
189
212
|
await agent(
|
|
190
|
-
`Escribí el registro junto al cargo
|
|
191
|
-
`
|
|
213
|
+
`Escribí el registro junto al cargo. Desde ${ROOT}, corré ` +
|
|
214
|
+
`"node tools/ops.js evaluate ${AGENT} --record" y escribí en la ruta que imprima, relativa a ` +
|
|
215
|
+
`${ROOT}. Creá el directorio si no existe.\n\n` +
|
|
216
|
+
`Preguntale la ruta al motor en vez de componerla: aplicar una propuesta cambia el contrato y pide ` +
|
|
217
|
+
`volver a correr los casos el mismo día, y cuando el nombre salía de la fecha la segunda corrida ` +
|
|
218
|
+
`escribía encima de la primera —que es la línea base que la propuesta cita como evidencia—.\n\n` +
|
|
192
219
|
`Ahí y no en el banco de trabajo. El banco se borra en la próxima corrida —es donde el cargo ` +
|
|
193
220
|
`trabajó, no donde vive—, mientras que el veredicto pertenece al contrato que lo rindió y viaja ` +
|
|
194
|
-
`con él. La fecha es la de hoy en formato AAAA-MM-DD; obtenela con "date +%F".\n\n` +
|
|
221
|
+
`con él. La fecha del frontmatter es la de hoy en formato AAAA-MM-DD; obtenela con "date +%F".\n\n` +
|
|
195
222
|
`El archivo lleva este frontmatter y después el contenido tal cual te lo paso, sin reescribirlo ni ` +
|
|
196
223
|
`resumirlo:\n\n---\nagent: ${AGENT}\ndate: <fecha>\npassed: ${pasan.length}\ntotal: ${hechos.length}\n---\n\n` +
|
|
197
224
|
`# Casos adversariales — <fecha>\n\n${filas}\n\n` +
|
|
@@ -69,8 +69,11 @@ phase('Firma')
|
|
|
69
69
|
const firma = await agent(
|
|
70
70
|
`From ${ROOT}, run "node tools/ops.js agents list --json" and take the path it printed for ${AGENT}. ` +
|
|
71
71
|
`Set dir to "${ROOT}/<path>": that command prints paths relative to ${ROOT}.\n\n` +
|
|
72
|
-
`Find the newest <dir>/learning/proposals
|
|
73
|
-
|
|
72
|
+
`Find the newest proposal under <dir>/learning/proposals/. They are named AAAA-MM.md, and a ` +
|
|
73
|
+
`correction to an already applied one is AAAA-MM-rN.md — the revision is newer than the plain name ` +
|
|
74
|
+
`for the same month, so sorting file names is not enough` +
|
|
75
|
+
`${PERIOD ? `. Prefer the newest one for ${PERIOD}` : ''}. Set proposal to its full path. Read ` +
|
|
76
|
+
`**only** its ` +
|
|
74
77
|
`frontmatter and its "Aprobación humana" and "Cambio propuesto" sections and report, without ` +
|
|
75
78
|
`interpreting in anyone's favour:\n` +
|
|
76
79
|
`- approved: true only if the state says it is approved AND a named person is recorded. "pendiente", ` +
|
|
@@ -93,15 +96,17 @@ if (!firma.approved) {
|
|
|
93
96
|
// después, y el estado en prosa pasa a decir «aprobada y aplicada», que también lee como aprobada.
|
|
94
97
|
// Como el cambio es aditivo por diseño, reaplicar no falla — duplica cada viñeta y cada fuente.
|
|
95
98
|
if ((firma.status || '').toLowerCase() === 'applied') {
|
|
96
|
-
return stop('ya-aplicada', `${firma.proposal} ya está aplicada.
|
|
97
|
-
|
|
99
|
+
return stop('ya-aplicada', `${firma.proposal} ya está aplicada. Si la evaluación posterior mostró que ` +
|
|
100
|
+
`el cambio quedó mal calibrado, abrí una revisión con "node tools/ops.js learn ${AGENT} --proposal": ` +
|
|
101
|
+
'la aplicada queda sellada donde está y la corrección va con su propia firma')
|
|
98
102
|
}
|
|
99
103
|
log(`Aprobada por ${firma.signedBy}`)
|
|
100
104
|
|
|
101
|
-
// El período sale del nombre del archivo, que el motor ya garantiza
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
// El período sale del nombre del archivo, que el motor ya garantiza: pedírselo otra vez al modelo sería
|
|
106
|
+
// preguntar dos veces lo mismo y arriesgar dos respuestas. Se toma el nombre entero y no sólo el mes,
|
|
107
|
+
// porque con una revisión abierta hay dos archivos del mismo período y hay que sellar el que se aplicó.
|
|
108
|
+
const PERIODO = (firma.proposal.match(/(\d{4}-\d{2}(?:-r\d+)?)\.md$/) || [])[1] || ''
|
|
109
|
+
if (!PERIODO) return stop('propuesta-sin-periodo', `${firma.proposal} no se llama AAAA-MM.md ni AAAA-MM-rN.md`)
|
|
105
110
|
|
|
106
111
|
phase('Aplicar')
|
|
107
112
|
|
|
@@ -77,22 +77,86 @@ function list(root, agent) {
|
|
|
77
77
|
})
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Lo que el cargo no debe hacer, declarado por cargo en `expected-behaviors.yaml`.
|
|
81
|
+
//
|
|
82
|
+
// Se parsea a mano porque el archivo es una lista de escalares y el repositorio no tiene dependencias:
|
|
83
|
+
// traer un parser de YAML para leer dos listas sería pagar una dependencia por un `split`.
|
|
84
|
+
//
|
|
85
|
+
// Existe porque durante todo el catálogo esas listas no fueron criterio de nada. Los casos declaran
|
|
86
|
+
// cuatro comportamientos a observar y el juez decidía sólo con esos; los `forbidden` los leía un agente
|
|
87
|
+
// al proponer cambios, nunca al evaluar. La conducta prohibida entraba únicamente si quien lanzaba la
|
|
88
|
+
// corrida se acordaba de escribirla en el prompt, y ahí el criterio cambiaba entre corridas: tres rondas
|
|
89
|
+
// del mismo caso se midieron con tres listones distintos y dejaron de ser comparables.
|
|
90
|
+
function behaviors(root, agent) {
|
|
91
|
+
const file = path.join(catalog.resolve(root, agent), 'evaluations', 'expected-behaviors.yaml')
|
|
92
|
+
let text
|
|
93
|
+
try { text = fs.readFileSync(file, 'utf8') } catch { return { required: [], forbidden: [] } }
|
|
94
|
+
const found = { required: [], forbidden: [] }
|
|
95
|
+
let key = ''
|
|
96
|
+
for (const line of text.split('\n')) {
|
|
97
|
+
const heading = line.match(/^(\w+):\s*$/)
|
|
98
|
+
if (heading) { key = heading[1]; continue }
|
|
99
|
+
if (/^\S/.test(line)) { key = ''; continue }
|
|
100
|
+
const bullet = line.match(/^\s+-\s+(.*)$/)
|
|
101
|
+
if (bullet && found[key]) found[key].push(bullet[1].trim())
|
|
102
|
+
}
|
|
103
|
+
return found
|
|
104
|
+
}
|
|
105
|
+
|
|
80
106
|
function resultsDir(root, agent) {
|
|
81
107
|
return path.join(catalog.resolve(root, agent), ...RESULTS)
|
|
82
108
|
}
|
|
83
109
|
|
|
110
|
+
// Un registro por corrida, y puede haber más de una en el mismo día: aplicar una propuesta cambia el
|
|
111
|
+
// contrato, y el recorrido que la aplica pide volver a correr los casos ahí mismo. Con sólo
|
|
112
|
+
// `AAAA-MM-DD.md` la segunda corrida escribía encima de la primera sin avisar, y la primera es
|
|
113
|
+
// justamente la línea base que la propuesta cita como evidencia: se perdía el término de comparación
|
|
114
|
+
// en el momento exacto en que empezaba a hacer falta.
|
|
115
|
+
const RESULT_NAME = /^(\d{4}-\d{2}-\d{2})(?:-(\d+))?\.md$/
|
|
116
|
+
|
|
117
|
+
// Ordena por fecha y después por corrida, con el nombre sin sufijo como la primera. No alcanza con
|
|
118
|
+
// ordenar los nombres: `-` va antes que `.` en ASCII, así que `2026-08-17-2.md` quedaría *delante* de
|
|
119
|
+
// `2026-08-17.md` y la más nueva se leería como la más vieja.
|
|
120
|
+
function resultOrder(name) {
|
|
121
|
+
const [, date, run] = name.match(RESULT_NAME)
|
|
122
|
+
return `${date}-${String(Number(run || 1)).padStart(4, '0')}`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resultNames(dir) {
|
|
126
|
+
return caseFiles(dir)
|
|
127
|
+
.filter((name) => RESULT_NAME.test(name))
|
|
128
|
+
.sort((one, other) => resultOrder(one).localeCompare(resultOrder(other)))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// El nombre que le toca a la corrida de hoy: el primero libre. Lo decide el motor y no el prompt del
|
|
132
|
+
// recorrido, porque «escribí en <fecha>.md» es una instrucción que no puede saber qué ya existe.
|
|
133
|
+
function nextResult(root, agent, date) {
|
|
134
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) throw new Error(`fecha inválida: ${date}`)
|
|
135
|
+
const dir = resultsDir(root, agent)
|
|
136
|
+
const taken = new Set(resultNames(dir))
|
|
137
|
+
if (!taken.has(`${date}.md`)) return `${date}.md`
|
|
138
|
+
let run = 2
|
|
139
|
+
while (taken.has(`${date}-${run}.md`)) run += 1
|
|
140
|
+
return `${date}-${run}.md`
|
|
141
|
+
}
|
|
142
|
+
|
|
84
143
|
// El último resultado registrado, para que `evaluate` pueda decir si el cargo se corrió alguna vez y
|
|
85
144
|
// cómo le fue. No es un error no tenerlo: correrlo cuesta, y exigirlo en CI sería exigir red.
|
|
86
145
|
function latest(root, agent) {
|
|
87
146
|
const dir = resultsDir(root, agent)
|
|
88
|
-
const names =
|
|
147
|
+
const names = resultNames(dir)
|
|
89
148
|
if (!names.length) return null
|
|
90
|
-
const
|
|
149
|
+
const name = names[names.length - 1]
|
|
150
|
+
const file = path.join(dir, name)
|
|
91
151
|
const text = fs.readFileSync(file, 'utf8')
|
|
92
152
|
const verdicts = [...text.matchAll(/^-\s*Veredicto:\s*(pasa|no pasa)\s*$/gim)].map((hit) => hit[1].toLowerCase())
|
|
153
|
+
const [, date, run] = name.match(RESULT_NAME)
|
|
93
154
|
return {
|
|
94
155
|
file,
|
|
95
|
-
|
|
156
|
+
// La fecha es la del día, sin el sufijo de corrida: quien lee «no pasaron en 2026-08-17» busca un
|
|
157
|
+
// día, no un nombre de archivo. Cuál de las corridas fue va aparte, y sólo cuando hubo más de una.
|
|
158
|
+
date,
|
|
159
|
+
run: Number(run || 1),
|
|
96
160
|
total: verdicts.length,
|
|
97
161
|
passed: verdicts.filter((verdict) => verdict === 'pasa').length,
|
|
98
162
|
}
|
|
@@ -126,4 +190,4 @@ function validate(root, agent) {
|
|
|
126
190
|
return { errors, warnings, cases: total, last }
|
|
127
191
|
}
|
|
128
192
|
|
|
129
|
-
module.exports = { fixtures, list, latest, parseCase, validate, resultsDir }
|
|
193
|
+
module.exports = { behaviors, fixtures, list, latest, nextResult, parseCase, validate, resultsDir }
|
|
@@ -33,14 +33,30 @@ function assertWritable(root, agent) {
|
|
|
33
33
|
function isoDate(now = new Date()) { return now.toISOString().slice(0, 10) }
|
|
34
34
|
function month(now = new Date()) { return now.toISOString().slice(0, 7) }
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// Una propuesta por período, y sus revisiones. La revisión existe porque aplicar no es el final del
|
|
37
|
+
// ciclo: la evaluación posterior es la que dice si el cambio sirvió, y cuando dice que no, el sello
|
|
38
|
+
// —que está para que nadie reaplique lo mismo y duplique cada viñeta— dejaba al cargo con un contrato
|
|
39
|
+
// que se sabe mal calibrado y sin camino para corregirlo hasta el mes siguiente. La corrección es un
|
|
40
|
+
// cambio distinto: documento propio, firma propia, y la aplicada queda sellada donde está.
|
|
41
|
+
const PROPOSAL_NAME = /^(\d{4}-\d{2})(?:-r(\d+))?\.md$/
|
|
42
|
+
|
|
43
|
+
// Mismo cuidado que con los registros de evaluación: `-` va antes que `.` en ASCII, así que ordenar
|
|
44
|
+
// nombres pondría `2026-08-r2.md` delante de `2026-08.md` y la revisión se leería como la más vieja.
|
|
45
|
+
function proposalOrder(name) {
|
|
46
|
+
const [, period, revision] = name.match(PROPOSAL_NAME)
|
|
47
|
+
return `${period}-${String(Number(revision || 1)).padStart(4, '0')}`
|
|
48
|
+
}
|
|
37
49
|
|
|
38
50
|
// El tope de la línea de índice. No es estético: son 47 líneas que se leen de un vistazo, y una que
|
|
39
51
|
// se envuelve rompe la columna que hace posible el vistazo.
|
|
40
52
|
const SUMMARY_MAX = 120
|
|
41
53
|
|
|
42
54
|
function proposalFiles(dir) {
|
|
43
|
-
try {
|
|
55
|
+
try {
|
|
56
|
+
return fs.readdirSync(dir)
|
|
57
|
+
.filter((name) => PROPOSAL_NAME.test(name))
|
|
58
|
+
.sort((one, other) => proposalOrder(one).localeCompare(proposalOrder(other)))
|
|
59
|
+
} catch { return [] }
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
function proposalState(text) {
|
|
@@ -58,8 +74,14 @@ function seal(root, agent, period = '') {
|
|
|
58
74
|
const dir = path.join(assertWritable(root, agent), 'learning', 'proposals')
|
|
59
75
|
const names = proposalFiles(dir)
|
|
60
76
|
if (!names.length) throw new Error(`${agent} no tiene propuestas en learning/proposals/.`)
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
// `--period 2026-08` nombra el período, no un archivo: con revisiones abiertas la que se sella es la
|
|
78
|
+
// vigente de ese período. Resolverlo siempre a `2026-08.md` habría devuelto «ya estaba aplicada» y
|
|
79
|
+
// dejado la revisión sin sellar, que es justo el estado en que `agent-promote` la vuelve a aplicar.
|
|
80
|
+
// Una revisión concreta se puede nombrar entera —`--period 2026-08-r2`— y entonces manda esa.
|
|
81
|
+
const name = period
|
|
82
|
+
? (/^\d{4}-\d{2}$/.test(period) ? lastOfPeriod(dir, period) : `${period}.md`)
|
|
83
|
+
: names[names.length - 1]
|
|
84
|
+
if (!name || !names.includes(name)) throw new Error(`${agent} no tiene la propuesta ${period || name}.`)
|
|
63
85
|
const file = path.join(dir, name)
|
|
64
86
|
const text = fs.readFileSync(file, 'utf8')
|
|
65
87
|
const state = proposalState(text)
|
|
@@ -109,12 +131,88 @@ propuesta consolidada. -->
|
|
|
109
131
|
return { file, created: true }
|
|
110
132
|
}
|
|
111
133
|
|
|
134
|
+
// Abre la revisión siguiente. No vuelve a consolidar los informes semanales: ya se consolidaron en la
|
|
135
|
+
// propuesta que ésta corrige, y repetirlos haría que el mismo hallazgo entre dos veces al contrato. El
|
|
136
|
+
// insumo de una revisión es otro —qué mostró la evaluación posterior a aplicar—, y por eso el molde
|
|
137
|
+
// pregunta eso y no otra cosa.
|
|
138
|
+
function reviseProposal(root, agent, dir, previo, now) {
|
|
139
|
+
const period = month(now)
|
|
140
|
+
const anterior = previo.match(PROPOSAL_NAME)
|
|
141
|
+
const revision = Number(anterior[2] || 1) + 1
|
|
142
|
+
const file = path.join(dir, `${period}-r${revision}.md`)
|
|
143
|
+
fs.writeFileSync(file, `---
|
|
144
|
+
agent: ${agent}
|
|
145
|
+
period: ${period}
|
|
146
|
+
revision: ${revision}
|
|
147
|
+
corrects: ${previo}
|
|
148
|
+
status: proposed
|
|
149
|
+
automatic_apply: false
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
# Propuesta mensual — ${period}, revisión ${revision}
|
|
153
|
+
|
|
154
|
+
Corrige \`${previo}\`, que ya está aplicada y queda sellada donde está. Esta revisión no la reemplaza ni
|
|
155
|
+
la reabre: es un cambio distinto, con su propia firma.
|
|
156
|
+
|
|
157
|
+
## Hallazgos
|
|
158
|
+
|
|
159
|
+
Qué mostró la evaluación posterior a aplicar \`${previo}\`. No repitas acá los hallazgos de esa propuesta
|
|
160
|
+
—ya entraron al contrato—: lo que va es lo que se supo después, con el registro de evaluación que lo
|
|
161
|
+
sostiene.
|
|
162
|
+
|
|
163
|
+
## Evidencia
|
|
164
|
+
|
|
165
|
+
El registro de la corrida que lo destapó, y la cita del veredicto. Si el cambio anterior falló por estar
|
|
166
|
+
mal calibrado, va también la línea del contrato que quedó floja y la del caso que la contradice.
|
|
167
|
+
|
|
168
|
+
## Cambio propuesto
|
|
169
|
+
|
|
170
|
+
Una revisión suele **no** ser aditiva: reemplaza texto que la propuesta anterior agregó. Decilo
|
|
171
|
+
explícitamente y decí por qué la aditividad no aplica acá — vale para lo que ya rindió sus casos, no para
|
|
172
|
+
un texto que acaba de fallar su primera medición.
|
|
173
|
+
|
|
174
|
+
## Riesgos y regresiones
|
|
175
|
+
|
|
176
|
+
Qué casos pasaban con el texto anterior y podrían dejar de pasar con éste. Nombralos por su id: son los
|
|
177
|
+
que hay que volver a correr.
|
|
178
|
+
|
|
179
|
+
## Evaluación
|
|
180
|
+
|
|
181
|
+
Cómo se comprueba que esta vez sí. Nombrá el caso que tiene que cambiar de veredicto y por qué razón, no
|
|
182
|
+
sólo que pase.
|
|
183
|
+
|
|
184
|
+
## Aprobación humana
|
|
185
|
+
|
|
186
|
+
- Estado: pendiente
|
|
187
|
+
- Responsable: por definir
|
|
188
|
+
- Fecha: por definir
|
|
189
|
+
`)
|
|
190
|
+
return { file, created: true, reports: 0, corrects: previo }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// La última propuesta del período, si la hay: es contra ella que se decide si abrir una revisión.
|
|
194
|
+
function lastOfPeriod(dir, period) {
|
|
195
|
+
const names = proposalFiles(dir).filter((name) => name.match(PROPOSAL_NAME)[1] === period)
|
|
196
|
+
return names.length ? names[names.length - 1] : ''
|
|
197
|
+
}
|
|
198
|
+
|
|
112
199
|
function prepareProposal(root, agent, now = new Date()) {
|
|
113
200
|
const target = assertWritable(root, agent)
|
|
114
201
|
const proposalDir = path.join(target, 'learning', 'proposals')
|
|
115
|
-
const file = path.join(proposalDir, `${month(now)}.md`)
|
|
116
202
|
fs.mkdirSync(proposalDir, { recursive: true })
|
|
117
|
-
|
|
203
|
+
|
|
204
|
+
// Una sola propuesta pendiente por período. Si la última todavía no se aplicó, abrir otra partiría
|
|
205
|
+
// la firma en dos documentos que dicen cosas distintas sobre el mismo contrato.
|
|
206
|
+
const previo = lastOfPeriod(proposalDir, month(now))
|
|
207
|
+
if (previo) {
|
|
208
|
+
const anterior = path.join(proposalDir, previo)
|
|
209
|
+
if (proposalState(fs.readFileSync(anterior, 'utf8')) !== 'applied') {
|
|
210
|
+
return { file: anterior, created: false, reports: 0 }
|
|
211
|
+
}
|
|
212
|
+
return reviseProposal(root, agent, proposalDir, previo, now)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const file = path.join(proposalDir, `${month(now)}.md`)
|
|
118
216
|
let names = []
|
|
119
217
|
try { names = fs.readdirSync(path.join(target, 'learning', 'reports')) } catch { /* vacío */ }
|
|
120
218
|
const reports = names.filter((name) => name.startsWith(month(now)) && /^\d{4}-\d{2}-\d{2}\.md$/.test(name)).sort()
|
package/engine/cli/args.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// requerido y no puede exportar nada sin dispararse.
|
|
6
6
|
|
|
7
7
|
// Banderas que consumen el argumento siguiente: su valor no es un posicional.
|
|
8
|
-
const VALUED_FLAGS = new Set(['--name', '--mode', '--fixture', '--period'])
|
|
8
|
+
const VALUED_FLAGS = new Set(['--name', '--mode', '--fixture', '--period', '--record'])
|
|
9
9
|
|
|
10
10
|
// Qué acepta cada comando, y a la vez qué comandos existen. Una bandera desconocida se rechaza en vez
|
|
11
11
|
// de ignorarse: `check --jsonn` imprimía la salida humana con código 0, así que quien esperaba JSON
|
|
@@ -21,7 +21,7 @@ const FLAGS = {
|
|
|
21
21
|
integration: ['--fixture'],
|
|
22
22
|
automation: ['--force'],
|
|
23
23
|
learn: ['--proposal', '--applied', '--period'],
|
|
24
|
-
evaluate: ['--cases', '--json', '--bench', '--force'],
|
|
24
|
+
evaluate: ['--cases', '--json', '--bench', '--force', '--record'],
|
|
25
25
|
team: ['--json'],
|
|
26
26
|
}
|
|
27
27
|
|
package/engine/cli/ops.js
CHANGED
|
@@ -51,7 +51,7 @@ function usage() {
|
|
|
51
51
|
ops automation doctor <ops-root> claude|codex|gemini|antigravity
|
|
52
52
|
ops automation install <ops-root> claude|codex|gemini|antigravity
|
|
53
53
|
ops learn <agent> [--proposal] [--applied [--period <AAAA-MM>]]
|
|
54
|
-
ops evaluate <agent> [--cases [--json]] [--bench [caso]]
|
|
54
|
+
ops evaluate <agent> [--cases [--json]] [--bench [caso]] [--record [AAAA-MM-DD]]
|
|
55
55
|
ops agents list [ops-root] [--own|--system] [--json]
|
|
56
56
|
ops agents fork <cargo> [ops-root]
|
|
57
57
|
ops team list
|
|
@@ -154,6 +154,7 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
154
154
|
const dir = path.join(root, relative)
|
|
155
155
|
if (fs.existsSync(dir)) entregado = M.record(root, relative, O.treeFiles(dir), entregado)
|
|
156
156
|
}
|
|
157
|
+
entregado = M.recordPaths(root, O.SYSTEM_FILES, entregado)
|
|
157
158
|
M.write(root, entregado)
|
|
158
159
|
// La instancia recuerda de qué versión salió: sin esto no hay actualización posible.
|
|
159
160
|
const configFile = path.join(root, 'ops.config.json')
|
|
@@ -628,6 +629,7 @@ function upgrade(dir, cli) {
|
|
|
628
629
|
const dir = path.join(root, relative)
|
|
629
630
|
if (fs.existsSync(dir)) registro = M.record(root, relative, O.treeFiles(dir), registro)
|
|
630
631
|
}
|
|
632
|
+
registro = M.recordPaths(root, O.SYSTEM_FILES, registro)
|
|
631
633
|
// El registro de forks se poda igual que el de archivos: un cargo devuelto al catálogo deja su
|
|
632
634
|
// entrada, y una entrada sin copia sólo puede producir avisos sobre algo que no está.
|
|
633
635
|
const vivos = Object.fromEntries(Object.entries(M.readForks(root)).filter(
|
|
@@ -976,11 +978,22 @@ function evaluate(agent, caso, cli) {
|
|
|
976
978
|
// máquina, no de persona.
|
|
977
979
|
if (cli.has('--cases')) {
|
|
978
980
|
const cases = EV.list(root, agent)
|
|
981
|
+
const prohibido = EV.behaviors(root, agent).forbidden
|
|
982
|
+
// La salida legible no lleva la conducta prohibida: `agent-propose` cuenta sus líneas para saber
|
|
983
|
+
// cuántos casos hay, y una línea de más se contaría como un caso.
|
|
979
984
|
if (!cli.has('--json')) {
|
|
980
985
|
for (const item of cases) console.log(`${item.id} ${item.expected.length} comportamiento(s)`)
|
|
981
986
|
return
|
|
982
987
|
}
|
|
983
|
-
|
|
988
|
+
// La conducta prohibida viaja junto a los casos y no dentro de cada uno: rige para los seis, y
|
|
989
|
+
// repetirla por caso invitaría a que alguien la editara en uno solo.
|
|
990
|
+
return console.log(JSON.stringify({ cases, forbidden: prohibido }))
|
|
991
|
+
}
|
|
992
|
+
// Dónde escribir el registro de esta corrida. Lo pregunta el recorrido en vez de componer el
|
|
993
|
+
// nombre, que es lo que hacía que la segunda corrida de un día borrara a la primera.
|
|
994
|
+
if (cli.has('--record')) {
|
|
995
|
+
const dia = cli.value('--record') || new Date().toISOString().slice(0, 10)
|
|
996
|
+
return console.log(path.relative(root, path.join(EV.resultsDir(root, agent), EV.nextResult(root, agent, dia))))
|
|
984
997
|
}
|
|
985
998
|
const result = L.evaluate(root, agent)
|
|
986
999
|
const runs = EV.validate(root, agent)
|
package/engine/core/manifest.js
CHANGED
|
@@ -82,6 +82,31 @@ function prune(root, files) {
|
|
|
82
82
|
)
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Los archivos que el toolkit posee de a uno —`AGENTS.md`, `Makefile`, los README del sistema— se
|
|
86
|
+
// registran por su ruta exacta y no por directorio: no son una colección, y agruparlos por carpeta
|
|
87
|
+
// produciría claves como `./AGENTS.md` que después no casan con nada.
|
|
88
|
+
//
|
|
89
|
+
// Sin este registro, `upgrade` los reemplazaba sin comparar: una edición local desaparecía sin que
|
|
90
|
+
// nada lo dijera. Comparar contra el paquete no era alternativa —es lo que este mecanismo existe para
|
|
91
|
+
// evitar—, porque ahí toda mejora del toolkit se ve idéntica a una edición de la empresa.
|
|
92
|
+
function recordPaths(root, paths, into = {}) {
|
|
93
|
+
const record = { ...into }
|
|
94
|
+
for (const relative of paths) {
|
|
95
|
+
const file = path.join(root, relative)
|
|
96
|
+
if (fs.existsSync(file)) record[relative] = digest(file)
|
|
97
|
+
}
|
|
98
|
+
return record
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function editedPaths(root, paths) {
|
|
102
|
+
const recorded = read(root)
|
|
103
|
+
return paths.filter((relative) => {
|
|
104
|
+
const file = path.join(root, relative)
|
|
105
|
+
if (!recorded[relative] || !fs.existsSync(file)) return false
|
|
106
|
+
return recorded[relative] !== digest(file)
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
85
110
|
// Archivos que la empresa modificó después de recibirlos. Un archivo sin registro previo no
|
|
86
111
|
// cuenta: llegó con una versión anterior a este mecanismo, o lo agregó el proyecto.
|
|
87
112
|
function edited(root, relative, files) {
|
|
@@ -94,5 +119,6 @@ function edited(root, relative, files) {
|
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
module.exports = {
|
|
97
|
-
digest, digestText, edited, prune, read, readForks, readRunners,
|
|
122
|
+
digest, digestText, edited, editedPaths, prune, read, readForks, readRunners,
|
|
123
|
+
record, recordPaths, write,
|
|
98
124
|
}
|
package/engine/core/ownership.js
CHANGED
|
@@ -228,6 +228,10 @@ function localChanges(root) {
|
|
|
228
228
|
if (!fs.existsSync(dir)) continue
|
|
229
229
|
for (const file of manifest.edited(root, target, treeFiles(dir))) changed.push(`${target}/${file}`)
|
|
230
230
|
}
|
|
231
|
+
// Los archivos sueltos del sistema entran por la misma puerta. Quedaban afuera, así que `upgrade`
|
|
232
|
+
// los reemplazaba en silencio: un cargo escribió el índice de ADR que el propio README le pedía
|
|
233
|
+
// actualizar, comprobó que se perdería, y prefirió no dejar una entrada condenada a desaparecer.
|
|
234
|
+
changed.push(...manifest.editedPaths(root, SYSTEM_FILES))
|
|
231
235
|
return changed
|
|
232
236
|
}
|
|
233
237
|
|
package/engine/hooks/run.js
CHANGED
|
@@ -185,9 +185,14 @@ function workspaceBoundary(input) {
|
|
|
185
185
|
|
|
186
186
|
function migrations(input) {
|
|
187
187
|
if (process.env.OPS_MIGRATIONS_OVERRIDE === '1') return
|
|
188
|
+
// Cada rama cierra su propio límite. Cuando el `\b` estaba al final del grupo se aplicaba a las tres, y
|
|
189
|
+
// la de `delete` termina a propósito en `;`: después de un punto y coma no hay límite de palabra, así que
|
|
190
|
+
// `DELETE FROM pedidos;` —la forma que tiene en cualquier migración— pasaba y sólo frenaba la variante sin
|
|
191
|
+
// punto y coma. `drop column` y `drop constraint` faltaban: pierden datos y garantías igual que `drop table`.
|
|
188
192
|
const destructiveSql = new RegExp(
|
|
189
|
-
String.raw`\
|
|
190
|
-
String.raw
|
|
193
|
+
String.raw`\bdrop\s+(?:table|database|schema|column|constraint)\b` +
|
|
194
|
+
String.raw`|\btruncate\b` +
|
|
195
|
+
String.raw`|\bdelete\s+from\s+\S+\s*(?:;|$)`,
|
|
191
196
|
'i',
|
|
192
197
|
)
|
|
193
198
|
if (destructiveSql.test(contentOf(input))) {
|
package/package.json
CHANGED
|
@@ -25,8 +25,11 @@ de una, crea una ADR propia que documente la excepción; no modifica silenciosam
|
|
|
25
25
|
1. Copiar `000-template.md` como `NNN-slug-corto.md`.
|
|
26
26
|
2. Documentar contexto, fuentes, supuestos, decisión, alternativas y costos.
|
|
27
27
|
3. Declarar por separado lo construido y lo pendiente.
|
|
28
|
-
4.
|
|
29
|
-
|
|
28
|
+
4. Al reemplazarla, enlazar la nueva ADR sin borrar el historial.
|
|
29
|
+
|
|
30
|
+
No hay índice que mantener: las decisiones del proyecto son los archivos `NNN-*.md` de este
|
|
31
|
+
directorio, y su estado vive en cada uno. Una tabla que los repitiera envejecería aparte —y este
|
|
32
|
+
archivo lo mantiene Cauce, así que una fila agregada acá se perdería en el próximo `upgrade`.
|
|
30
33
|
|
|
31
34
|
## Decisiones del sistema
|
|
32
35
|
|
|
@@ -34,9 +37,3 @@ de una, crea una ADR propia que documente la excepción; no modifica silenciosam
|
|
|
34
37
|
- [OPS-002](system/OPS-002-runtime-autocontenido-y-neutral-al-runner.md): runtime portable y neutral.
|
|
35
38
|
- [OPS-003](system/OPS-003-integraciones-seguras-por-staging.md): integraciones mediante staging seguro.
|
|
36
39
|
- [OPS-004](system/OPS-004-promocion-humana-y-evidencia-verificable.md): promoción controlada y verificable.
|
|
37
|
-
|
|
38
|
-
## Decisiones del proyecto
|
|
39
|
-
|
|
40
|
-
| ADR | Decisión | Fecha | Estado |
|
|
41
|
-
|---|---|---|---|
|
|
42
|
-
| _(ninguna todavía)_ | | | |
|