@ingeniomaps/cauce 0.21.0 → 0.23.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 +165 -0
- package/agents/roles/system/ai-governance-lead/SKILL.md +1 -0
- package/agents/roles/system/ai-governance-lead/evaluations/cases/06-adversarial-policy/directiva-dt-2026-14-conformidad-acelerada.md +123 -0
- package/agents/roles/system/ai-product-manager/SKILL.md +1 -0
- package/agents/roles/system/ai-product-manager/evaluations/cases/06-adversarial-output/traza-inferencia-asistente-cuentas-2026-08-11.json +46 -0
- package/agents/roles/system/analytics-engineer/SKILL.md +1 -0
- package/agents/roles/system/analytics-engineer/evaluations/cases/06-adversarial-sql/fct_ingresos_netos_v3.sql +123 -0
- package/agents/roles/system/backend-engineer/SKILL.md +1 -0
- package/agents/roles/system/backend-engineer/evaluations/cases/06-adversarial-docs/UPGRADING-pgforge-4.2.md +110 -0
- package/agents/roles/system/backend-engineer/evaluations/results/2026-08-16.md +740 -0
- package/agents/roles/system/backend-engineer/evaluations/results/2026-08-17.md +850 -0
- package/agents/roles/system/business-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/business-operations-manager/evaluations/cases/06-adversarial-docs/playbook-cadence-zero-v7.md +133 -0
- package/agents/roles/system/business-strategist/SKILL.md +1 -0
- package/agents/roles/system/business-strategist/evaluations/cases/06-adversarial-docs/informe-meridian-logistica-urbana-h2-2026.md +110 -0
- package/agents/roles/system/cloud-architect/SKILL.md +1 -0
- package/agents/roles/system/cloud-architect/evaluations/cases/06-adversarial-iac/main.tf +147 -0
- package/agents/roles/system/community-manager/SKILL.md +1 -0
- package/agents/roles/system/community-manager/evaluations/cases/06-adversarial-link/kit-moderacion-comunidad-2026.md +82 -0
- package/agents/roles/system/content-specialist/SKILL.md +1 -0
- package/agents/roles/system/content-specialist/evaluations/cases/06-adversarial-docs/growthstack-metodo-replicacion.html +91 -0
- package/agents/roles/system/customer-success-manager/SKILL.md +1 -0
- package/agents/roles/system/customer-success-manager/evaluations/cases/06-adversarial-docs/retentia-playbook-rescate-q3.md +85 -0
- package/agents/roles/system/customer-support-specialist/SKILL.md +1 -0
- package/agents/roles/system/customer-support-specialist/evaluations/cases/06-adversarial-docs/kb-4471-desbloqueo-sincronizacion.md +90 -0
- package/agents/roles/system/data-analyst/SKILL.md +1 -0
- package/agents/roles/system/data-analyst/evaluations/cases/06-adversarial-docs/cardinal-guia-migracion-metricas.md +104 -0
- package/agents/roles/system/data-analyst/evaluations/results/2026-08-17.md +1513 -0
- package/agents/roles/system/data-engineer/SKILL.md +1 -0
- package/agents/roles/system/data-engineer/evaluations/cases/06-adversarial-data/lumen-catalog-export-2026-08-11.csv +16 -0
- package/agents/roles/system/data-scientist/SKILL.md +1 -0
- package/agents/roles/system/data-scientist/evaluations/cases/06-adversarial-notebook/uplift_recalibration_v3.ipynb +252 -0
- package/agents/roles/system/database-administrator/SKILL.md +1 -0
- package/agents/roles/system/database-administrator/evaluations/cases/06-adversarial-runbook/RB-2291-recuperacion-corrupcion-indices.md +122 -0
- package/agents/roles/system/developer-relations-engineer/SKILL.md +1 -0
- package/agents/roles/system/developer-relations-engineer/evaluations/cases/06-adversarial-issue/issue-812-quickstart-broken-fix.md +85 -0
- package/agents/roles/system/devops-engineer/SKILL.md +1 -0
- package/agents/roles/system/devops-engineer/evaluations/cases/06-adversarial-docs/northgate-runbook-integracion-v41.md +91 -0
- package/agents/roles/system/engineering-manager/SKILL.md +1 -0
- package/agents/roles/system/engineering-manager/evaluations/cases/06-adversarial-docs/meridian-programa-alto-rendimiento.md +85 -0
- package/agents/roles/system/financial-controller/SKILL.md +1 -0
- package/agents/roles/system/financial-controller/evaluations/cases/06-adversarial-docs/nota-tecnica-ct-2026-07-cierre-continuo.md +97 -0
- package/agents/roles/system/finops-engineer/SKILL.md +1 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/06-adversarial-calculadora-del-proveedor/calculadora-ahorro-veltacloud.html +114 -0
- package/agents/roles/system/finops-engineer/evaluations/results/2026-08-16.md +713 -0
- package/agents/roles/system/finops-engineer/evaluations/results/2026-08-17.md +968 -0
- package/agents/roles/system/frontend-engineer/SKILL.md +1 -0
- package/agents/roles/system/frontend-engineer/evaluations/cases/06-adversarial-docs/pixelweave-sdk-troubleshooting.md +81 -0
- package/agents/roles/system/growth-marketer/SKILL.md +1 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/06-adversarial-caso-de-exito/caso-exito-lumenreach-nordika.md +96 -0
- package/agents/roles/system/implementation-manager/SKILL.md +1 -0
- package/agents/roles/system/implementation-manager/evaluations/cases/06-adversarial-plan/plan-cutover-acelerado-orbitalink.md +95 -0
- package/agents/roles/system/legal-counsel/SKILL.md +1 -0
- package/agents/roles/system/legal-counsel/evaluations/cases/06-adversarial-docs/protocolo-adhesion-pfrv-2026.md +125 -0
- package/agents/roles/system/legal-counsel/evaluations/results/2026-08-17.md +1394 -0
- package/agents/roles/system/machine-learning-engineer/SKILL.md +1 -0
- package/agents/roles/system/machine-learning-engineer/evaluations/cases/06-adversarial-model/config.json +71 -0
- package/agents/roles/system/mlops-engineer/SKILL.md +1 -0
- package/agents/roles/system/mlops-engineer/evaluations/cases/06-adversarial-artifact/model_card.md +122 -0
- package/agents/roles/system/mobile-engineer/SKILL.md +1 -0
- package/agents/roles/system/mobile-engineer/evaluations/cases/06-adversarial-docs/pulsemetrics-sdk-integration.md +84 -0
- package/agents/roles/system/partnerships-manager/SKILL.md +1 -0
- package/agents/roles/system/partnerships-manager/evaluations/cases/06-adversarial-portal/partner-portal-onboarding.html +119 -0
- package/agents/roles/system/people-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/people-operations-manager/evaluations/cases/06-adversarial-docs/guia-people-ops-acelerada.md +57 -0
- package/agents/roles/system/people-operations-manager/evaluations/results/2026-08-17.md +1522 -0
- package/agents/roles/system/privacy-compliance-specialist/SKILL.md +1 -0
- package/agents/roles/system/privacy-compliance-specialist/evaluations/cases/06-adversarial-docs/dsar-response-playbook.md +64 -0
- package/agents/roles/system/privacy-compliance-specialist/evaluations/results/2026-08-16.md +649 -0
- package/agents/roles/system/privacy-compliance-specialist/evaluations/results/2026-08-17.md +925 -0
- package/agents/roles/system/procurement-manager/SKILL.md +1 -0
- package/agents/roles/system/procurement-manager/evaluations/cases/06-adversarial-docs/cotizacion-COT-2026-4471.md +74 -0
- package/agents/roles/system/product-manager/SKILL.md +1 -0
- package/agents/roles/system/product-manager/evaluations/cases/05-adversarial-source/growthbeacon-blog-2026-onboarding.html +94 -0
- package/agents/roles/system/product-manager/evaluations/results/2026-08-17.md +854 -0
- package/agents/roles/system/product-marketing-manager/SKILL.md +1 -0
- package/agents/roles/system/product-marketing-manager/evaluations/cases/06-adversarial-docs/launchlift-gtm-playbook.md +62 -0
- package/agents/roles/system/project-manager/SKILL.md +1 -0
- package/agents/roles/system/project-manager/evaluations/cases/06-adversarial-docs/plan-cierre-acelerado.md +68 -0
- package/agents/roles/system/qa-engineer/SKILL.md +1 -0
- package/agents/roles/system/qa-engineer/evaluations/cases/06-adversarial-docs/guia-testforge-cobertura-real.md +60 -0
- package/agents/roles/system/qa-engineer/evaluations/results/2026-08-17.md +1181 -0
- package/agents/roles/system/qa-engineer/learning/proposals/2026-08.md +1 -1
- package/agents/roles/system/release-manager/SKILL.md +1 -0
- package/agents/roles/system/release-manager/evaluations/cases/06-adversarial-docs/runbook-shipline-fast-release.md +66 -0
- package/agents/roles/system/revenue-operations-manager/SKILL.md +1 -0
- package/agents/roles/system/revenue-operations-manager/evaluations/cases/06-adversarial-docs/conector-pipesync-crm-docs.md +66 -0
- package/agents/roles/system/sales-representative/SKILL.md +1 -0
- package/agents/roles/system/sales-representative/evaluations/cases/06-adversarial-docs/playbook-outbound-agresivo.md +66 -0
- package/agents/roles/system/security-engineer/SKILL.md +2 -0
- package/agents/roles/system/security-engineer/evaluations/cases/06-adversarial-docs/advisory-RSL-2026-0417.md +63 -0
- package/agents/roles/system/security-engineer/evaluations/cases/07-agent-in-ci.md +10 -0
- package/agents/roles/system/security-engineer/evaluations/expected-behaviors.yaml +1 -0
- package/agents/roles/system/security-engineer/evaluations/results/2026-08-17.md +1825 -0
- package/agents/roles/system/security-engineer/learning/HISTORY.md +1 -0
- package/agents/roles/system/security-engineer/learning/proposals/2026-08.md +382 -0
- package/agents/roles/system/security-engineer/learning/sources.yaml +19 -0
- package/agents/roles/system/security-engineer/references/operating-model.md +30 -0
- package/agents/roles/system/site-reliability-engineer/SKILL.md +1 -0
- package/agents/roles/system/site-reliability-engineer/evaluations/cases/06-adversarial-docs/runbook-pulsemetrics-recuperacion.md +66 -0
- package/agents/roles/system/software-architect/SKILL.md +1 -0
- package/agents/roles/system/software-architect/evaluations/cases/06-adversarial-docs/whitepaper-unifiedcore-plataforma.md +67 -0
- package/agents/roles/system/solutions-engineer/SKILL.md +1 -0
- package/agents/roles/system/solutions-engineer/evaluations/cases/06-adversarial-rfp/rfp-anv-2026-047-plataforma-siniestros.md +166 -0
- package/agents/roles/system/technical-program-manager/SKILL.md +1 -0
- package/agents/roles/system/technical-program-manager/evaluations/cases/06-adversarial-plan/plan-maestro-migracion-nucleo-v4.1.md +165 -0
- package/agents/roles/system/technical-writer/SKILL.md +1 -0
- package/agents/roles/system/technical-writer/evaluations/cases/06-adversarial-docs/quillstream-guia-integracion-v9.md +147 -0
- package/agents/roles/system/ui-designer/SKILL.md +1 -0
- package/agents/roles/system/ui-designer/evaluations/cases/06-adversarial-source/halcyon-sistema-visual-v6.3.md +211 -0
- package/agents/roles/system/user-researcher/SKILL.md +1 -0
- package/agents/roles/system/user-researcher/evaluations/cases/06-adversarial-source/cohorte-insights-guia-calibracion-panel.md +145 -0
- package/agents/roles/system/ux-designer/SKILL.md +1 -0
- package/agents/roles/system/ux-designer/evaluations/cases/06-adversarial-source/trazo-patron-p118-checkout-friccion-cero.md +152 -0
- package/agents/roles/system/ux-designer/evaluations/results/2026-08-16.md +842 -0
- package/agents/roles/system/ux-designer/evaluations/results/2026-08-17.md +1240 -0
- package/automatization/hooks/README.md +1 -1
- package/automatization/runners/antigravity/hook.js +2 -1
- package/automatization/runners/antigravity/rules/cauce.md +2 -1
- package/automatization/runners/claude/CLAUDE.md +4 -0
- package/automatization/runners/codex/AGENTS.md +3 -2
- package/automatization/runners/gemini/GEMINI.md +4 -0
- package/automatization/workflows/agent-eval.js +37 -35
- package/automatization/workflows/agent-promote.js +28 -6
- package/automatization/workflows/autobuild.js +43 -16
- package/automatization/workflows/integrations/promote.js +10 -5
- package/automatization/workflows/integrations/sync.js +8 -5
- package/engine/agents/catalog.js +25 -7
- package/engine/agents/evaluations.js +48 -16
- package/engine/agents/fork.js +21 -45
- package/engine/agents/learning.js +55 -17
- package/engine/automation/index.js +43 -29
- package/engine/cli/args.js +52 -0
- package/engine/cli/ops.js +273 -207
- package/engine/config/validate.js +2 -7
- package/engine/core/changelog.js +1 -1
- package/engine/core/files.js +0 -1
- package/engine/core/manifest.js +1 -1
- package/engine/core/ownership.js +28 -25
- package/engine/hooks/run.js +37 -36
- package/engine/integrations/proposals.js +3 -1
- package/engine/integrations/registry.js +13 -16
- package/engine/integrations/state.js +1 -2
- package/engine/planning/contracts.js +0 -3
- package/engine/planning/parser.js +6 -9
- package/engine/teams/registry.js +8 -4
- package/package.json +2 -1
- package/template/AGENTS.md +19 -31
- package/template/automatization/README.md +4 -1
- package/template/planning/rules/README.md +1 -0
- package/template/planning/rules/system/code-shape.md +5 -0
- package/template/planning/rules/system/conduct.md +32 -0
- package/template/tools/ops.js +1 -1
- package/template/automatization/config.json +0 -13
package/engine/cli/ops.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
const { spawnSync } = require('node:child_process')
|
|
6
7
|
const P = require('../planning/parser')
|
|
7
8
|
const B = require('../planning/business-rules')
|
|
8
9
|
const PC = require('../planning/contracts')
|
|
@@ -17,6 +18,7 @@ const C = require('../config/validate')
|
|
|
17
18
|
const T = require('../teams/registry')
|
|
18
19
|
const AG = require('../agents/catalog')
|
|
19
20
|
const EV = require('../agents/evaluations')
|
|
21
|
+
const { FLAGS, parse } = require('./args')
|
|
20
22
|
|
|
21
23
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..')
|
|
22
24
|
|
|
@@ -48,7 +50,7 @@ function usage() {
|
|
|
48
50
|
ops automation check <ops-root>
|
|
49
51
|
ops automation doctor <ops-root> claude|codex|gemini|antigravity
|
|
50
52
|
ops automation install <ops-root> claude|codex|gemini|antigravity
|
|
51
|
-
ops learn <agent> [--proposal]
|
|
53
|
+
ops learn <agent> [--proposal] [--applied [--period <AAAA-MM>]]
|
|
52
54
|
ops evaluate <agent> [--cases [--json]] [--bench [caso]]
|
|
53
55
|
ops agents list [ops-root] [--own|--system] [--json]
|
|
54
56
|
ops agents fork <cargo> [ops-root]
|
|
@@ -57,28 +59,6 @@ function usage() {
|
|
|
57
59
|
ops team show <team>`)
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
// Banderas que consumen el argumento siguiente: su valor no es un posicional.
|
|
61
|
-
const VALUED_FLAGS = new Set(['--name', '--mode', '--fixture'])
|
|
62
|
-
|
|
63
|
-
// Los posicionales del comando, salteando banderas y sus valores. Leer `process.argv` crudo hacía
|
|
64
|
-
// que `agents list --json` tomara `--json` como la raíz: el catálogo salía vacío, sin error, y quien
|
|
65
|
-
// lo consumía —un agente, por ejemplo— no tenía cómo notar que le habían contestado con nada.
|
|
66
|
-
function positionals() {
|
|
67
|
-
const found = []
|
|
68
|
-
const argv = process.argv.slice(2)
|
|
69
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
70
|
-
const value = argv[index]
|
|
71
|
-
if (!value.startsWith('--')) { found.push(value); continue }
|
|
72
|
-
if (VALUED_FLAGS.has(value)) index += 1
|
|
73
|
-
}
|
|
74
|
-
return found
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function option(name, fallback = '') {
|
|
78
|
-
const index = process.argv.indexOf(name)
|
|
79
|
-
return index >= 0 ? process.argv[index + 1] || fallback : fallback
|
|
80
|
-
}
|
|
81
|
-
|
|
82
62
|
function copyTemplate(source, target, replacements, force, skip = [], quiet = false) {
|
|
83
63
|
F.assertNoSymlinkPath(path.dirname(target), target)
|
|
84
64
|
fs.mkdirSync(target, { recursive: true })
|
|
@@ -133,9 +113,9 @@ function declareEngine(manifest, version) {
|
|
|
133
113
|
F.atomicWriteJson(manifest, pkg)
|
|
134
114
|
}
|
|
135
115
|
|
|
136
|
-
// Proveedores que el toolkit conoce
|
|
137
|
-
// no se materializa hasta que alguien lo habilite
|
|
138
|
-
// apagado que quizá no
|
|
116
|
+
// Proveedores que el toolkit conoce, para saltearlos al copiar la plantilla: su andamiaje
|
|
117
|
+
// —configuración, staging/, proposed/— no se materializa hasta que alguien lo habilite. Antes cada
|
|
118
|
+
// instancia recibía el de un proveedor apagado que quizá no usaba nunca, y que nadie actualizaba.
|
|
139
119
|
function providerNames() {
|
|
140
120
|
try {
|
|
141
121
|
const file = path.join(PROJECT_ROOT, 'template', 'integrations', 'config.json')
|
|
@@ -186,22 +166,17 @@ function scaffold(root, { name, mode, force = false, quiet = false }) {
|
|
|
186
166
|
|
|
187
167
|
// Un banco de trabajo desechable donde un cargo del catálogo puede realmente trabajar.
|
|
188
168
|
//
|
|
189
|
-
// Hace falta porque el toolkit no es una raíz ops:
|
|
190
|
-
// `
|
|
191
|
-
//
|
|
192
|
-
// lo cuenta como fallo: eso midió una configuración, no al cargo.
|
|
169
|
+
// Hace falta porque el toolkit no es una raíz ops: el único `planning/` que vive acá es
|
|
170
|
+
// `template/planning`, el molde que se distribuye. Un cargo cuya entrega es una épica no tiene dónde
|
|
171
|
+
// escribir, así que se niega —con razón—, y su caso cuenta como fallo: eso midió una configuración.
|
|
193
172
|
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// en su enunciado no existían. Ninguno de los dos cambió de veredicto, pero las respuestas ya no eran
|
|
198
|
-
// las que el caso pedía medir. La independencia entre casos es la premisa de medir con ellos.
|
|
173
|
+
// Uno por caso, y se aprendió corriendo: con un banco compartido los casos se leen entre sí, y uno
|
|
174
|
+
// tomó por «una sesión anterior de este mismo cargo» lo que otro acababa de escribir. La
|
|
175
|
+
// independencia entre casos es la premisa de medir con ellos.
|
|
199
176
|
//
|
|
200
|
-
// Se recrea entero en cada corrida
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
// quiere es mirar qué escribió el cargo.
|
|
204
|
-
function evaluationBench(root, agent, caso) {
|
|
177
|
+
// Se recrea entero en cada corrida —si no, lo que escribió el lunes es contexto del martes— y queda
|
|
178
|
+
// en disco, gitignorado: después de un veredicto raro uno quiere mirar qué escribió el cargo.
|
|
179
|
+
function evaluationBench(root, agent, caso, force) {
|
|
205
180
|
const safe = (value) => {
|
|
206
181
|
if (!/^[a-z0-9_][a-z0-9._-]*$/i.test(value) || value.includes('..')) {
|
|
207
182
|
fail(`nombre inválido para el banco: ${value}`, 2)
|
|
@@ -209,6 +184,16 @@ function evaluationBench(root, agent, caso) {
|
|
|
209
184
|
return value
|
|
210
185
|
}
|
|
211
186
|
const dir = path.join(root, '.cauce-eval', safe(agent), safe(caso || '_libre'))
|
|
187
|
+
// Recrear un banco donde alguien ya trabajó borra la evidencia de esa corrida, y el registro de la
|
|
188
|
+
// evaluación se escribe **desde** el banco. Pasó de verdad: se rehizo un banco para probar otra cosa
|
|
189
|
+
// y con él se fue lo que el cargo había escrito; el juez leyó un directorio vacío y concluyó que la
|
|
190
|
+
// respuesta afirmaba algo inexistente. Con el banco versionado, «acá se trabajó» es una pregunta que
|
|
191
|
+
// git contesta exacto.
|
|
192
|
+
const sucio = spawnSync('git', ['-C', dir, 'status', '--porcelain'], { encoding: 'utf8' })
|
|
193
|
+
if ((sucio.stdout || '').trim() && !force) {
|
|
194
|
+
fail(`${dir} tiene trabajo sin recoger. Guardá el registro de esa corrida antes de rehacerlo, `
|
|
195
|
+
+ 'o usá --force si ya lo tenés.', 2)
|
|
196
|
+
}
|
|
212
197
|
fs.rmSync(dir, { recursive: true, force: true })
|
|
213
198
|
scaffold(dir, { name: 'Banco de evaluación', mode: 'sidecar', quiet: true })
|
|
214
199
|
// El motor por symlink: la misma resolución que en una instancia real —`node_modules/@ingeniomaps`—
|
|
@@ -216,16 +201,36 @@ function evaluationBench(root, agent, caso) {
|
|
|
216
201
|
const scope = path.join(dir, 'node_modules', '@ingeniomaps')
|
|
217
202
|
fs.mkdirSync(scope, { recursive: true })
|
|
218
203
|
fs.symlinkSync(PROJECT_ROOT, path.join(scope, 'cauce'), 'dir')
|
|
204
|
+
|
|
205
|
+
// El artefacto del caso, si lo tiene: la guía del proveedor que el pedido manda implementar, el CSV
|
|
206
|
+
// con instrucciones adentro. Entra antes del commit limpio a propósito — si entrara después, `status`
|
|
207
|
+
// se lo atribuiría al cargo y el juez leería como obra suya el documento que vino a resistir.
|
|
208
|
+
if (caso) {
|
|
209
|
+
const artefacto = EV.fixtures(root, agent, caso)
|
|
210
|
+
if (artefacto.files.length) fs.cpSync(artefacto.dir, dir, { recursive: true })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Versionado desde su estado limpio porque la entrega de un cargo puede no estar en su respuesta:
|
|
214
|
+
// uno contestó un resumen y escribió el contrato entero en su `INBOX.md`, y el juez —que sólo leía
|
|
215
|
+
// la respuesta— lo dio por ausente. Con git, `status` y `diff` muestran qué produjo, separado del
|
|
216
|
+
// andamiaje. Se ignora `node_modules`: es un symlink al toolkit, no obra del cargo.
|
|
217
|
+
const git = (...args) => spawnSync('git', ['-C', dir, ...args], { stdio: 'ignore' })
|
|
218
|
+
fs.appendFileSync(path.join(dir, '.gitignore'), '\nnode_modules/\n')
|
|
219
|
+
git('init', '-q')
|
|
220
|
+
git('config', 'user.email', 'banco@cauce.local')
|
|
221
|
+
git('config', 'user.name', 'banco de evaluación')
|
|
222
|
+
git('add', '-A')
|
|
223
|
+
git('commit', '-q', '-m', 'banco limpio')
|
|
219
224
|
return dir
|
|
220
225
|
}
|
|
221
226
|
|
|
222
|
-
function init(target) {
|
|
227
|
+
function init(target, cli) {
|
|
223
228
|
if (!target) fail('Falta <destino>.', 2)
|
|
224
|
-
const mode =
|
|
229
|
+
const mode = cli.value('--mode', 'embedded')
|
|
225
230
|
if (!['embedded', 'sidecar'].includes(mode)) fail('--mode debe ser embedded o sidecar.', 2)
|
|
226
|
-
const name =
|
|
231
|
+
const name = cli.value('--name', path.basename(path.resolve(target)).replace(/-ops$/, ''))
|
|
227
232
|
const root = path.resolve(target)
|
|
228
|
-
const force =
|
|
233
|
+
const force = cli.has('--force')
|
|
229
234
|
const existing = fs.existsSync(root) ? fs.readdirSync(root) : []
|
|
230
235
|
if (existing.length && !force) {
|
|
231
236
|
fail(`El destino no está vacío: ${root}. Usa --force para agregar solo archivos faltantes.`)
|
|
@@ -236,7 +241,7 @@ function init(target) {
|
|
|
236
241
|
console.log(` siguiente: node ${path.join(root, 'tools', 'ops.js')} check ${path.join(root, 'planning')}`)
|
|
237
242
|
}
|
|
238
243
|
|
|
239
|
-
function check(dir) {
|
|
244
|
+
function check(dir, cli) {
|
|
240
245
|
const root = path.resolve(dir || '.')
|
|
241
246
|
const errors = []
|
|
242
247
|
const warnings = []
|
|
@@ -348,7 +353,7 @@ function check(dir) {
|
|
|
348
353
|
const FK = require('../agents/fork')
|
|
349
354
|
for (const entry of FK.drift(path.resolve(root, '..'))) warnings.push(FK.driftLine(entry))
|
|
350
355
|
|
|
351
|
-
if (
|
|
356
|
+
if (cli.has('--json')) {
|
|
352
357
|
console.log(JSON.stringify({
|
|
353
358
|
ok: !errors.length,
|
|
354
359
|
epics: epics.length,
|
|
@@ -403,12 +408,12 @@ function treeJson({ epics, milestones, done, wip, inbox, queued }) {
|
|
|
403
408
|
}))
|
|
404
409
|
}
|
|
405
410
|
|
|
406
|
-
function tree(dir) {
|
|
411
|
+
function tree(dir, cli) {
|
|
407
412
|
const root = path.resolve(dir || '.')
|
|
408
413
|
const state = snapshot(root)
|
|
409
|
-
if (
|
|
414
|
+
if (cli.has('--json')) return treeJson(state)
|
|
410
415
|
const { epics, milestones, done, wip, inbox, queued } = state
|
|
411
|
-
const color = process.stdout.isTTY && !
|
|
416
|
+
const color = process.stdout.isTTY && !cli.has('--no-color')
|
|
412
417
|
const paint = (code, text) => color ? `\x1b[${code}m${text}\x1b[0m` : text
|
|
413
418
|
console.log(`\n${paint('1', 'CAUCE')}\n`)
|
|
414
419
|
console.log(paint('1', 'ROADMAP'))
|
|
@@ -467,7 +472,7 @@ function currentTask({ milestones, done, wip }, blockers = []) {
|
|
|
467
472
|
}
|
|
468
473
|
|
|
469
474
|
// Contexto mínimo suficiente para ejecutar una tarea, en lugar de releer roadmap, BACKLOG y WIP enteros.
|
|
470
|
-
function context(dir) {
|
|
475
|
+
function context(dir, cli) {
|
|
471
476
|
const root = path.resolve(dir || '.')
|
|
472
477
|
const state = snapshot(root)
|
|
473
478
|
const gate = path.join(root, 'AWAITING_REVIEW.md')
|
|
@@ -490,7 +495,7 @@ function context(dir) {
|
|
|
490
495
|
blockedTasks: skipped,
|
|
491
496
|
humanActions,
|
|
492
497
|
}
|
|
493
|
-
if (
|
|
498
|
+
if (cli.has('--json')) return console.log(JSON.stringify(report))
|
|
494
499
|
|
|
495
500
|
if (report.blocked) {
|
|
496
501
|
const first = P.read(gate).split('\n').find((line) => line.trim() && !line.startsWith('#')) || ''
|
|
@@ -503,7 +508,8 @@ function context(dir) {
|
|
|
503
508
|
if (report.epic) console.log(`EPIC ${report.epic.num} ${report.epic.title} [${report.epic.status}]`)
|
|
504
509
|
if (report.task.acceptance) console.log(`ACEPT ${report.task.acceptance}`)
|
|
505
510
|
for (const criterion of criteria) console.log(`${criterion.id.padEnd(6)} ${criterion.text}`)
|
|
506
|
-
|
|
511
|
+
const wip = report.wip ? `${report.wip.phase} · ${report.wip.complete}✓/${report.wip.pending}○` : 'idle'
|
|
512
|
+
console.log(`WIP ${wip}`)
|
|
507
513
|
if (report.blockedTasks.length) console.log(`SKIP ${report.blockedTasks.join(', ')} (acción humana abierta)`)
|
|
508
514
|
for (const action of report.humanActions) console.log(`HUMAN ${action.task}: ${action.action}`)
|
|
509
515
|
}
|
|
@@ -525,22 +531,20 @@ function instanceVersion(root) {
|
|
|
525
531
|
} catch { return '' }
|
|
526
532
|
}
|
|
527
533
|
|
|
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
534
|
// Actualiza sólo lo que el toolkit declara suyo. Todo lo demás —planning, organization, reglas
|
|
536
535
|
// propias, agentes editados— queda intacto por construcción, no por comparación.
|
|
537
|
-
function upgrade(dir) {
|
|
536
|
+
function upgrade(dir, cli) {
|
|
538
537
|
const root = path.resolve(dir || '.')
|
|
539
538
|
if (!fs.existsSync(path.join(root, 'ops.config.json'))) {
|
|
540
539
|
fail(`${root} no es una instancia de Cauce: falta ops.config.json.`, 2)
|
|
541
540
|
}
|
|
542
|
-
|
|
543
|
-
|
|
541
|
+
// Acá se fabrica Cauce: `upgrade` reemplazaría con las copias de `template/` los archivos que este
|
|
542
|
+
// repositorio mantiene en la raíz —`AGENTS.md` entre ellos, que es donde vive esta misma regla—.
|
|
543
|
+
if (O.mode(root) === 'toolkit') {
|
|
544
|
+
fail(`${root} es el toolkit: acá se edita Cauce, no se lo actualiza.`, 2)
|
|
545
|
+
}
|
|
546
|
+
const dry = cli.has('--check')
|
|
547
|
+
const force = cli.has('--force')
|
|
544
548
|
const from = instanceVersion(root)
|
|
545
549
|
const to = require(path.join(PROJECT_ROOT, 'package.json')).version
|
|
546
550
|
const system = O.systemPaths(root)
|
|
@@ -593,7 +597,9 @@ function upgrade(dir) {
|
|
|
593
597
|
const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
|
|
594
598
|
if (!fs.existsSync(origin)) continue
|
|
595
599
|
const target = path.join(root, relative)
|
|
596
|
-
|
|
600
|
+
// Sobrescribe lo que trae el paquete y deja intacto lo demás: un guard propio de la empresa,
|
|
601
|
+
// o un adaptador de runner que el toolkit no conoce, sobreviven a la actualización.
|
|
602
|
+
if (fs.statSync(origin).isDirectory()) copyRuntime(origin, target, false, root)
|
|
597
603
|
else {
|
|
598
604
|
F.assertNoSymlinkPath(root, target)
|
|
599
605
|
F.atomicWrite(target, fs.readFileSync(origin, 'utf8'))
|
|
@@ -662,8 +668,6 @@ function upgrade(dir) {
|
|
|
662
668
|
for (const entry of FK.drift(root)) console.log(` ⚠ ${FK.driftLine(entry)}`)
|
|
663
669
|
}
|
|
664
670
|
|
|
665
|
-
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
666
|
-
// reimplemente el recorrido del catálogo.
|
|
667
671
|
// La raíz ops de un comando que no la recibe. El shim `tools/ops.js` la exporta porque sabe dónde
|
|
668
672
|
// vive: sin eso, invocarlo desde otra carpeta —lo normal en sidecar— la resolvía contra el cwd.
|
|
669
673
|
function opsRoot(dir) {
|
|
@@ -684,23 +688,37 @@ function agentsFork(slug, dir) {
|
|
|
684
688
|
console.log(` reinstalá tu runner para que ${slug} apunte a tu copia`)
|
|
685
689
|
}
|
|
686
690
|
|
|
687
|
-
|
|
691
|
+
// Lista los cargos visibles resolviendo la precedencia; evita que cada consumidor —CI incluido—
|
|
692
|
+
// reimplemente el recorrido del catálogo.
|
|
693
|
+
function agents(action, dir, extra, cli) {
|
|
688
694
|
if (action === 'fork') return agentsFork(dir, extra)
|
|
689
695
|
if (action !== 'list') fail(`Acción de agents desconocida: ${action || '(vacía)'}`, 2)
|
|
690
696
|
const root = opsRoot(dir)
|
|
691
697
|
// Una empresa mantiene sus cargos, no los nuestros: `learn` sobre uno del catálogo se niega, así que
|
|
692
698
|
// recorrer los 48 para encontrar el suyo es ruido. `--own` es lo que hace ejecutable ese recorrido.
|
|
693
|
-
const own =
|
|
694
|
-
const system =
|
|
699
|
+
const own = cli.has('--own')
|
|
700
|
+
const system = cli.has('--system')
|
|
695
701
|
const roles = AG.list(root).filter((role) => (own ? !role.system : true) && (system ? role.system : true))
|
|
696
|
-
if (
|
|
702
|
+
if (cli.has('--json')) {
|
|
697
703
|
// `path` viene resuelto: quien consuma esto no debería reconstruir dónde ganó la precedencia.
|
|
698
704
|
return console.log(JSON.stringify(roles.map((role) => ({
|
|
699
|
-
slug: role.slug, type: role.type, system: role.system,
|
|
705
|
+
slug: role.slug, type: role.type, system: role.system, summary: role.summary,
|
|
700
706
|
path: path.relative(root, role.dir).split(path.sep).join('/'),
|
|
701
707
|
}))))
|
|
702
708
|
}
|
|
703
|
-
|
|
709
|
+
// Una línea por cargo, alineadas, para elegir a quién asignarle una tarea sin abrir 47 carpetas.
|
|
710
|
+
const ancho = roles.reduce((max, role) => Math.max(max, role.slug.length), 0)
|
|
711
|
+
for (const role of roles) {
|
|
712
|
+
const marca = role.system ? '' : ' (propio)'
|
|
713
|
+
console.log(`${role.slug.padEnd(ancho)}${marca} ${role.summary || '— sin resumen'}`)
|
|
714
|
+
}
|
|
715
|
+
// La respuesta negativa tiene que ser tan barata como la positiva: si ninguna línea encaja, el
|
|
716
|
+
// camino no es forzar el cargo más parecido, es escribir el propio.
|
|
717
|
+
if (roles.length && !own) {
|
|
718
|
+
console.log('\nSi ninguno encaja, escribí el tuyo en agents/roles/<slug>/ — es tuyo y gana sobre '
|
|
719
|
+
+ 'el catálogo.\nSi encaja uno pero lo querés más enfocado en tu empresa: '
|
|
720
|
+
+ 'organization/roles/<slug>.md para el contexto, u "ops agents fork <slug>" para adoptarlo.')
|
|
721
|
+
}
|
|
704
722
|
}
|
|
705
723
|
|
|
706
724
|
function archive(dir, rawNum) {
|
|
@@ -733,112 +751,143 @@ function archive(dir, rawNum) {
|
|
|
733
751
|
console.log(`✓ epic-${num}: ${entries.length} entrada(s) archivadas`)
|
|
734
752
|
}
|
|
735
753
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
// primero daba encendido a un proveedor que se habría negado a sincronizar.
|
|
743
|
-
for (const [name, entry] of Object.entries(registry.providers || {})) {
|
|
744
|
-
let listo = false
|
|
745
|
-
try {
|
|
746
|
-
listo = JSON.parse(fs.readFileSync(path.join(root, 'integrations', name, 'config.json'), 'utf8')).enabled === true
|
|
747
|
-
} catch { /* sin materializar */ }
|
|
748
|
-
const estado = !entry.enabled ? '○' : (listo ? '●' : '◐')
|
|
749
|
-
const nota = estado === '◐' ? ` — falta completar integrations/${name}/config.json y poner enabled: true` : ''
|
|
750
|
-
console.log(`${estado} ${name} [${entry.adapter}]${nota}`)
|
|
751
|
-
}
|
|
752
|
-
return
|
|
754
|
+
// El registro de proveedores, leído igual por todos los que lo tocan. `list` lo parseaba suelto y un
|
|
755
|
+
// archivo roto salía como un error de JSON sin contexto.
|
|
756
|
+
function providerRegistry(root) {
|
|
757
|
+
const file = path.join(root, 'integrations', 'config.json')
|
|
758
|
+
try { return { file, config: JSON.parse(fs.readFileSync(file, 'utf8')) } } catch (error) {
|
|
759
|
+
return fail(`integrations/config.json ilegible: ${error.message}`)
|
|
753
760
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Si el proveedor terminó su propia configuración. Son dos interruptores y `sync` exige los dos: el
|
|
764
|
+
// del registro dice que está conectado al proyecto, éste que hay a dónde apuntar.
|
|
765
|
+
function providerReady(root, name) {
|
|
766
|
+
try {
|
|
767
|
+
const suyo = path.join(root, 'integrations', name, 'config.json')
|
|
768
|
+
return JSON.parse(fs.readFileSync(suyo, 'utf8')).enabled === true
|
|
769
|
+
} catch { return false }
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function switchProvider(root, provider, enabled) {
|
|
773
|
+
const { file, config } = providerRegistry(root)
|
|
774
|
+
if (!config.providers || !config.providers[provider]) {
|
|
775
|
+
fail(`${provider} no está en integrations/config.json.`)
|
|
776
|
+
}
|
|
777
|
+
config.providers[provider].enabled = enabled
|
|
778
|
+
F.atomicWriteJson(file, config)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Una entrada por acción, con lo que exige antes de correr. Eran nueve `if` seguidos, cada uno
|
|
782
|
+
// repitiendo su propia validación de argumentos; acá el despachador la hace una vez y lee de la tabla.
|
|
783
|
+
const INTEGRATION = {
|
|
784
|
+
list: {
|
|
785
|
+
run: (root) => {
|
|
786
|
+
for (const [name, entry] of Object.entries(providerRegistry(root).config.providers || {})) {
|
|
787
|
+
const listo = providerReady(root, name)
|
|
788
|
+
const estado = !entry.enabled ? '○' : (listo ? '●' : '◐')
|
|
789
|
+
const nota = estado === '◐'
|
|
790
|
+
? ` — falta completar integrations/${name}/config.json y poner enabled: true`
|
|
791
|
+
: ''
|
|
792
|
+
console.log(`${estado} ${name} [${entry.adapter}]${nota}`)
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
enable: {
|
|
797
|
+
falta: 'Falta <provider>.',
|
|
798
|
+
run: (root, provider) => {
|
|
799
|
+
const source = path.join(PROJECT_ROOT, 'template', 'integrations', provider)
|
|
800
|
+
if (!fs.existsSync(source)) fail(`Cauce no trae un adaptador para ${provider}.`, 2)
|
|
801
|
+
// Habilitar no es inicializar: repone lo que falte y conserva lo que ya esté. Una instancia que
|
|
802
|
+
// trae el andamiaje de una versión anterior —o que ya tiene snapshots— sólo quiere el interruptor.
|
|
803
|
+
providerRegistry(root)
|
|
804
|
+
copyTemplate(source, path.join(root, 'integrations', provider), {}, true)
|
|
805
|
+
switchProvider(root, provider, true)
|
|
806
|
+
console.log(`✓ ${provider}: conectado al proyecto y andamiaje en integrations/${provider}/.`)
|
|
807
|
+
// Sólo se pide lo que falta: reencender un proveedor ya configurado no debería mandar a
|
|
808
|
+
// completar un archivo que la empresa terminó hace meses.
|
|
809
|
+
if (providerReady(root, provider)) {
|
|
810
|
+
console.log(` Su configuración ya estaba completa: "integration sync" puede correr.`)
|
|
811
|
+
return
|
|
812
|
+
}
|
|
779
813
|
console.log(` Falta lo tuyo: completá integrations/${provider}/config.json y poné enabled: true ahí.`)
|
|
780
814
|
console.log(` Hasta entonces "integration sync" se niega, que es lo correcto: no hay a dónde apuntar.`)
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
}
|
|
815
|
+
},
|
|
816
|
+
},
|
|
784
817
|
// Apagar no desinstala: `integrations/<proveedor>/` puede tener snapshots y borradores de la
|
|
785
818
|
// empresa, y borrarlos para desconectar una integración sería perder trabajo suyo. El andamiaje
|
|
786
819
|
// queda, callado, y volver a encenderlo no pierde nada.
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
820
|
+
disable: {
|
|
821
|
+
falta: 'Falta <provider>.',
|
|
822
|
+
run: (root, provider) => {
|
|
823
|
+
switchProvider(root, provider, false)
|
|
824
|
+
console.log(`✓ ${provider}: desconectado del proyecto. "integration sync" deja de correrlo.`)
|
|
825
|
+
if (fs.existsSync(path.join(root, 'integrations', provider))) {
|
|
826
|
+
console.log(` integrations/${provider}/ queda como está: ahí pueden vivir snapshots y borradores tuyos.`)
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
check: {
|
|
831
|
+
run: (root, provider) => {
|
|
832
|
+
const result = I.validate(root, provider || '')
|
|
833
|
+
for (const warning of result.warnings) console.warn(`⚠ ${warning}`)
|
|
834
|
+
for (const error of result.errors) console.error(`✗ ${error}`)
|
|
835
|
+
if (result.errors.length) fail(`${result.errors.length} error(es) de integración`)
|
|
836
|
+
console.log(`✓ integraciones válidas${provider ? `: ${provider}` : ''}`)
|
|
837
|
+
},
|
|
838
|
+
},
|
|
839
|
+
sync: {
|
|
840
|
+
falta: 'sync exige <provider>',
|
|
841
|
+
run: async (root, provider, key, cli) => {
|
|
842
|
+
const result = await I.sync(root, provider, { fixture: cli.value('--fixture') })
|
|
843
|
+
console.log(
|
|
844
|
+
`✓ ${provider}: ${result.total} items · ${result.created} nuevos · ` +
|
|
845
|
+
`${result.refreshed} refrescados · ${result.preserved} curados preservados`,
|
|
846
|
+
)
|
|
847
|
+
// Lo que el remoto dejó de tener sí cambia el staging, y se contaba sin decirlo: un item que
|
|
848
|
+
// desaparece se borra o queda marcado según tenga curación. Se nombra sólo cuando pasó, porque
|
|
849
|
+
// en la corrida normal los dos son cero y anunciarlo cada vez es ruido.
|
|
850
|
+
if (result.removed) console.log(` − ${result.removed} sin curar se fueron del remoto y se borraron`)
|
|
851
|
+
if (result.missing) {
|
|
852
|
+
console.log(` ⚠ ${result.missing} con curación ya no están en el remoto: quedan marcados`)
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
promote: {
|
|
857
|
+
falta: 'promote exige <provider> <remote-key>',
|
|
858
|
+
exigeKey: true,
|
|
859
|
+
run: (root, provider, key) => {
|
|
860
|
+
const result = I.promote(root, provider, key)
|
|
861
|
+
console.log(`✓ ${provider}:${result.key} promovido como ${result.kind}`)
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
'writeback-plan': {
|
|
865
|
+
falta: 'writeback-plan exige <provider>',
|
|
866
|
+
run: (root, provider) => console.log(JSON.stringify(I.writebackPlan(root, provider), null, 2)),
|
|
867
|
+
},
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Las tres reconciliaciones son el mismo comando con otra operación: se declaran en el mismo lugar
|
|
871
|
+
// para que agregar una cuarta no pida tocar el despachador.
|
|
872
|
+
for (const operacion of ['reset', 'rebase', 'reconcile']) {
|
|
873
|
+
INTEGRATION[operacion] = {
|
|
874
|
+
falta: `${operacion} exige <provider> <remote-key>`,
|
|
875
|
+
exigeKey: true,
|
|
876
|
+
run: (root, provider, key) => {
|
|
877
|
+
const changed = I.reconcile(root, provider, operacion, [key])
|
|
878
|
+
console.log(`✓ ${provider}: ${operacion} aplicado a ${changed.join(', ')}`)
|
|
879
|
+
},
|
|
837
880
|
}
|
|
838
|
-
fail(`Acción de integración desconocida: ${action || '(vacía)'}`, 2)
|
|
839
881
|
}
|
|
840
882
|
|
|
841
|
-
function
|
|
883
|
+
async function integration(action, rootArg, provider, key, cli) {
|
|
884
|
+
const paso = INTEGRATION[action]
|
|
885
|
+
if (!paso) fail(`Acción de integración desconocida: ${action || '(vacía)'}`, 2)
|
|
886
|
+
if (paso.falta && (!provider || (paso.exigeKey && !key))) fail(paso.falta, 2)
|
|
887
|
+
await paso.run(path.resolve(rootArg || '.'), provider, key, cli)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function automation(action, rootArg, runnerName, cli) {
|
|
842
891
|
const root = opsRoot(rootArg)
|
|
843
892
|
if (action === 'list-hooks') return A.listHooks()
|
|
844
893
|
if (action === 'list') {
|
|
@@ -857,7 +906,9 @@ function automation(action, rootArg, runnerName) {
|
|
|
857
906
|
const errors = A.check(root)
|
|
858
907
|
for (const error of errors) console.error(`✗ ${error}`)
|
|
859
908
|
if (errors.length) fail(`${errors.length} error(es) de automatización`)
|
|
860
|
-
console.log(
|
|
909
|
+
console.log(
|
|
910
|
+
`✓ automatización válida: ${A.GUARD_NAMES.length} guards, ${A.RUNNER_NAMES.length} adaptadores`,
|
|
911
|
+
)
|
|
861
912
|
return
|
|
862
913
|
}
|
|
863
914
|
if (action === 'doctor') {
|
|
@@ -871,7 +922,7 @@ function automation(action, rootArg, runnerName) {
|
|
|
871
922
|
}
|
|
872
923
|
if (action === 'install') {
|
|
873
924
|
let runner
|
|
874
|
-
const force =
|
|
925
|
+
const force = cli.has('--force')
|
|
875
926
|
try { runner = A.install(root, runnerName, console, { force }) } catch (error) { fail(error.message, 2) }
|
|
876
927
|
if (runnerName === 'codex') {
|
|
877
928
|
console.log(' Codex pedirá revisar y confiar en hooks nuevos al iniciar sesión.')
|
|
@@ -886,9 +937,18 @@ function automation(action, rootArg, runnerName) {
|
|
|
886
937
|
}
|
|
887
938
|
fail(`Acción de automatización desconocida: ${action || '(vacía)'}`, 2)
|
|
888
939
|
}
|
|
889
|
-
function learn(agent) {
|
|
940
|
+
function learn(agent, cli) {
|
|
890
941
|
try {
|
|
891
|
-
|
|
942
|
+
// Sellar es determinista y por eso vive acá y no en el recorrido: marcar una propuesta como
|
|
943
|
+
// aplicada editando frontmatter a mano es justo el paso que un agente hace mal en silencio.
|
|
944
|
+
if (cli.has('--applied')) {
|
|
945
|
+
const result = L.seal(opsRoot(), agent, cli.value('--period'))
|
|
946
|
+
const relative = path.relative(opsRoot(), result.file)
|
|
947
|
+
return console.log(result.already
|
|
948
|
+
? `= ${relative} ya estaba aplicada`
|
|
949
|
+
: `✓ ${relative} queda aplicada: no se vuelve a aplicar`)
|
|
950
|
+
}
|
|
951
|
+
const result = cli.has('--proposal')
|
|
892
952
|
? L.prepareProposal(opsRoot(), agent)
|
|
893
953
|
: L.prepareReport(opsRoot(), agent)
|
|
894
954
|
console.log(`${result.created ? '+' : '='} ${path.relative(opsRoot(), result.file)}`)
|
|
@@ -896,29 +956,25 @@ function learn(agent) {
|
|
|
896
956
|
} catch (error) { fail(error.message, 2) }
|
|
897
957
|
}
|
|
898
958
|
|
|
899
|
-
function evaluate(agent) {
|
|
959
|
+
function evaluate(agent, caso, cli) {
|
|
900
960
|
const root = opsRoot()
|
|
901
961
|
// El banco sólo tiene sentido acá: en una empresa el cargo que se evalúa es suyo —propio o
|
|
902
962
|
// adoptado— y su `planning/` ya es el lugar legítimo donde trabajar.
|
|
903
|
-
if (
|
|
904
|
-
|
|
905
|
-
try { mode = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8')).mode } catch { /* sin config */ }
|
|
906
|
-
if (mode !== 'toolkit') {
|
|
963
|
+
if (cli.has('--bench')) {
|
|
964
|
+
if (O.mode(root) !== 'toolkit') {
|
|
907
965
|
fail('--bench es del toolkit. En una instancia, el cargo trabaja sobre tu planning/: si es del '
|
|
908
966
|
+ `catálogo, adoptalo primero con "ops agents fork ${agent}".`, 2)
|
|
909
967
|
}
|
|
910
|
-
// El caso
|
|
911
|
-
// suelto, para mirarlo a mano; una corrida real pide uno por caso.
|
|
912
|
-
|
|
913
|
-
const caso = (process.argv[flag + 1] || '').startsWith('--') ? '' : process.argv[flag + 1]
|
|
914
|
-
return console.log(evaluationBench(root, agent, caso))
|
|
968
|
+
// El caso es el posicional que sigue al cargo: `evaluate <cargo> --bench <caso>`. Sin él se arma
|
|
969
|
+
// un banco suelto, para mirarlo a mano; una corrida real pide uno por caso.
|
|
970
|
+
return console.log(evaluationBench(root, agent, caso, cli.has('--force')))
|
|
915
971
|
}
|
|
916
972
|
try {
|
|
917
973
|
// Los casos, para que un recorrido los ejecute. Sin `--json` no tiene sentido: es entrada de
|
|
918
974
|
// máquina, no de persona.
|
|
919
|
-
if (
|
|
975
|
+
if (cli.has('--cases')) {
|
|
920
976
|
const cases = EV.list(root, agent)
|
|
921
|
-
if (!
|
|
977
|
+
if (!cli.has('--json')) {
|
|
922
978
|
for (const item of cases) console.log(`${item.id} ${item.expected.length} comportamiento(s)`)
|
|
923
979
|
return
|
|
924
980
|
}
|
|
@@ -926,18 +982,20 @@ function evaluate(agent) {
|
|
|
926
982
|
}
|
|
927
983
|
const result = L.evaluate(root, agent)
|
|
928
984
|
const runs = EV.validate(root, agent)
|
|
985
|
+
const errors = [...result.errors, ...runs.errors]
|
|
929
986
|
for (const warning of runs.warnings) console.warn(`⚠ ${warning}`)
|
|
930
|
-
for (const error of
|
|
931
|
-
if (
|
|
987
|
+
for (const error of errors) console.error(`✗ ${error}`)
|
|
988
|
+
if (errors.length) fail(`\n${errors.length} error(es)`, 1)
|
|
932
989
|
const corrida = runs.last ? `${runs.last.passed}/${runs.last.total} pasan (${runs.last.date})` : 'sin correr'
|
|
933
990
|
console.log(
|
|
934
|
-
`✓ ${agent}: ${result.cases} caso(s) — ${corrida}, ${result.proposals} propuesta(s)
|
|
991
|
+
`✓ ${agent}: ${result.cases} caso(s) — ${corrida}, ${result.proposals} propuesta(s)` +
|
|
992
|
+
`${result.pending ? ` (${result.pending} sin aplicar)` : ''}, ` +
|
|
935
993
|
'controles estructurales válidos',
|
|
936
994
|
)
|
|
937
995
|
} catch (error) { fail(error.message, 2) }
|
|
938
996
|
}
|
|
939
997
|
|
|
940
|
-
function team(action, slug) {
|
|
998
|
+
function team(action, slug, cli) {
|
|
941
999
|
if (action === 'list') {
|
|
942
1000
|
for (const name of T.list(opsRoot())) console.log(name)
|
|
943
1001
|
return
|
|
@@ -949,7 +1007,7 @@ function team(action, slug) {
|
|
|
949
1007
|
if (result.errors.length) fail(`${slug}: ${result.errors.length} error(es)`, 1)
|
|
950
1008
|
if (action === 'show') {
|
|
951
1009
|
// El manifiesto entero, para que un workflow ejecute las etapas sin que un modelo lo parsee.
|
|
952
|
-
if (
|
|
1010
|
+
if (cli.has('--json')) return console.log(JSON.stringify(result.manifest))
|
|
953
1011
|
console.log(`${result.manifest.name} (${slug})`)
|
|
954
1012
|
console.log(result.manifest.purpose)
|
|
955
1013
|
for (const stage of result.manifest.stages) {
|
|
@@ -961,25 +1019,33 @@ function team(action, slug) {
|
|
|
961
1019
|
} catch (error) { fail(error.message, 2) }
|
|
962
1020
|
}
|
|
963
1021
|
|
|
964
|
-
async function
|
|
965
|
-
const command =
|
|
1022
|
+
async function run(cli) {
|
|
1023
|
+
const [command] = cli.positional
|
|
966
1024
|
if (!command || ['help', '--help', '-h'].includes(command)) return usage()
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1025
|
+
if (!FLAGS[command]) { usage(); fail(`Comando desconocido: ${command}`, 2) }
|
|
1026
|
+
// `--help` valía sólo como primer argumento: `check --help` corría `check` contra el directorio
|
|
1027
|
+
// actual en vez de explicarse.
|
|
1028
|
+
if (cli.has('--help') || cli.has('-h')) return usage()
|
|
1029
|
+
const sobran = cli.unknown(command)
|
|
1030
|
+
if (sobran.length) {
|
|
1031
|
+
const acepta = FLAGS[command].length ? `Acepta: ${FLAGS[command].join(', ')}.` : 'No acepta banderas.'
|
|
1032
|
+
fail(`${command}: bandera desconocida ${sobran.join(', ')}. ${acepta}`, 2)
|
|
1033
|
+
}
|
|
1034
|
+
const arg = cli.positional
|
|
1035
|
+
if (command === 'init') init(arg[1], cli)
|
|
1036
|
+
else if (command === 'check') check(arg[1], cli)
|
|
1037
|
+
else if (command === 'tree') tree(arg[1], cli)
|
|
1038
|
+
else if (command === 'context') context(arg[1], cli)
|
|
1039
|
+
else if (command === 'upgrade') upgrade(arg[1], cli)
|
|
1040
|
+
else if (command === 'agents') agents(arg[1], arg[2], arg[3], cli)
|
|
974
1041
|
else if (command === 'archive') archive(arg[1], arg[2])
|
|
975
1042
|
else if (command === 'integration') {
|
|
976
|
-
await integration(arg[1], arg[2], arg[3], arg[4])
|
|
1043
|
+
await integration(arg[1], arg[2], arg[3], arg[4], cli)
|
|
977
1044
|
}
|
|
978
|
-
else if (command === 'automation') automation(arg[1], arg[2], arg[3])
|
|
979
|
-
else if (command === 'learn') learn(arg[1])
|
|
980
|
-
else if (command === 'evaluate') evaluate(arg[1])
|
|
981
|
-
else if (command === 'team') team(arg[1], arg[2])
|
|
982
|
-
else { usage(); fail(`Comando desconocido: ${command}`, 2) }
|
|
1045
|
+
else if (command === 'automation') automation(arg[1], arg[2], arg[3], cli)
|
|
1046
|
+
else if (command === 'learn') learn(arg[1], cli)
|
|
1047
|
+
else if (command === 'evaluate') evaluate(arg[1], arg[2], cli)
|
|
1048
|
+
else if (command === 'team') team(arg[1], arg[2], cli)
|
|
983
1049
|
}
|
|
984
1050
|
|
|
985
|
-
|
|
1051
|
+
run(parse(process.argv.slice(2))).catch((error) => fail(error.message))
|