@ingeniomaps/cauce 0.2.0 → 0.4.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 +79 -0
- package/README.md +25 -3
- package/agents/roles/system/ai-governance-lead/SKILL.md +2 -0
- package/agents/roles/system/ai-governance-lead/learning/sources.yaml +2 -4
- package/agents/roles/system/ai-product-manager/SKILL.md +2 -0
- package/agents/roles/system/ai-product-manager/learning/sources.yaml +2 -2
- package/agents/roles/system/analytics-engineer/SKILL.md +2 -0
- package/agents/roles/system/analytics-engineer/learning/sources.yaml +2 -1
- package/agents/roles/system/backend-engineer/SKILL.md +2 -0
- package/agents/roles/system/backend-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/business-operations-manager/SKILL.md +2 -0
- package/agents/roles/system/business-operations-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/business-strategist/SKILL.md +2 -0
- package/agents/roles/system/business-strategist/learning/sources.yaml +2 -8
- package/agents/roles/system/cloud-architect/SKILL.md +2 -0
- package/agents/roles/system/cloud-architect/learning/sources.yaml +2 -2
- package/agents/roles/system/community-manager/SKILL.md +2 -0
- package/agents/roles/system/community-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/content-specialist/SKILL.md +2 -0
- package/agents/roles/system/content-specialist/learning/sources.yaml +2 -8
- package/agents/roles/system/customer-success-manager/SKILL.md +2 -0
- package/agents/roles/system/customer-success-manager/learning/sources.yaml +2 -8
- package/agents/roles/system/customer-support-specialist/SKILL.md +2 -0
- package/agents/roles/system/customer-support-specialist/learning/sources.yaml +2 -8
- package/agents/roles/system/data-analyst/SKILL.md +2 -0
- package/agents/roles/system/data-analyst/learning/sources.yaml +2 -8
- package/agents/roles/system/data-engineer/SKILL.md +2 -0
- package/agents/roles/system/data-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/data-scientist/SKILL.md +2 -0
- package/agents/roles/system/data-scientist/learning/sources.yaml +2 -4
- package/agents/roles/system/database-administrator/SKILL.md +2 -0
- package/agents/roles/system/database-administrator/learning/sources.yaml +2 -2
- package/agents/roles/system/developer-relations-engineer/SKILL.md +2 -0
- package/agents/roles/system/developer-relations-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/devops-engineer/SKILL.md +2 -0
- package/agents/roles/system/devops-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/engineering-manager/SKILL.md +2 -0
- package/agents/roles/system/engineering-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/financial-controller/SKILL.md +2 -0
- package/agents/roles/system/financial-controller/learning/sources.yaml +2 -8
- package/agents/roles/system/finops-engineer/SKILL.md +83 -0
- package/agents/roles/system/finops-engineer/agents/openai.yaml +4 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/01-apagar-en-produccion.md +10 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/02-ahorro-estimado-como-realizado.md +10 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/03-salto-por-regresion.md +10 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/04-optimizar-contra-la-fiabilidad.md +10 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/05-costos-por-cliente.md +10 -0
- package/agents/roles/system/finops-engineer/evaluations/cases/06-adversarial-calculadora-del-proveedor.md +11 -0
- package/agents/roles/system/finops-engineer/evaluations/expected-behaviors.yaml +21 -0
- package/agents/roles/system/finops-engineer/learning/CODEX_AUTOMATION.md +18 -0
- package/agents/roles/system/finops-engineer/learning/HISTORY.md +4 -0
- package/agents/roles/system/finops-engineer/learning/proposals/_template.md +15 -0
- package/agents/roles/system/finops-engineer/learning/reports/_template.md +14 -0
- package/agents/roles/system/finops-engineer/learning/sources.yaml +27 -0
- package/agents/roles/system/finops-engineer/references/operating-model.md +70 -0
- package/agents/roles/system/frontend-engineer/SKILL.md +2 -0
- package/agents/roles/system/frontend-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/growth-marketer/SKILL.md +79 -0
- package/agents/roles/system/growth-marketer/agents/openai.yaml +4 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/01-gastar-sin-baseline.md +10 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/02-metrica-de-plataforma.md +10 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/03-cortar-experimento.md +10 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/04-promesa-que-el-producto-no-sostiene.md +10 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/05-audiencia-sin-base-legal.md +10 -0
- package/agents/roles/system/growth-marketer/evaluations/cases/06-adversarial-caso-de-exito.md +11 -0
- package/agents/roles/system/growth-marketer/evaluations/expected-behaviors.yaml +21 -0
- package/agents/roles/system/growth-marketer/learning/CODEX_AUTOMATION.md +18 -0
- package/agents/roles/system/growth-marketer/learning/HISTORY.md +4 -0
- package/agents/roles/system/growth-marketer/learning/proposals/_template.md +15 -0
- package/agents/roles/system/growth-marketer/learning/reports/_template.md +14 -0
- package/agents/roles/system/growth-marketer/learning/sources.yaml +27 -0
- package/agents/roles/system/growth-marketer/references/operating-model.md +76 -0
- package/agents/roles/system/implementation-manager/SKILL.md +2 -0
- package/agents/roles/system/implementation-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/legal-counsel/SKILL.md +2 -0
- package/agents/roles/system/legal-counsel/learning/sources.yaml +2 -8
- package/agents/roles/system/machine-learning-engineer/SKILL.md +2 -0
- package/agents/roles/system/machine-learning-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/mlops-engineer/SKILL.md +2 -0
- package/agents/roles/system/mlops-engineer/learning/sources.yaml +2 -2
- package/agents/roles/system/mobile-engineer/SKILL.md +2 -0
- package/agents/roles/system/mobile-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/partnerships-manager/SKILL.md +2 -0
- package/agents/roles/system/partnerships-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/people-operations-manager/SKILL.md +2 -0
- package/agents/roles/system/people-operations-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/privacy-compliance-specialist/SKILL.md +2 -0
- package/agents/roles/system/privacy-compliance-specialist/learning/sources.yaml +2 -8
- package/agents/roles/system/procurement-manager/SKILL.md +2 -0
- package/agents/roles/system/procurement-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/product-manager/SKILL.md +2 -0
- package/agents/roles/system/product-manager/learning/sources.yaml +2 -0
- package/agents/roles/system/product-marketing-manager/SKILL.md +2 -0
- package/agents/roles/system/product-marketing-manager/learning/sources.yaml +2 -8
- package/agents/roles/system/project-manager/SKILL.md +2 -0
- package/agents/roles/system/project-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/qa-engineer/SKILL.md +2 -0
- package/agents/roles/system/qa-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/release-manager/SKILL.md +2 -0
- package/agents/roles/system/release-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/revenue-operations-manager/SKILL.md +2 -0
- package/agents/roles/system/revenue-operations-manager/learning/sources.yaml +2 -4
- package/agents/roles/system/sales-representative/SKILL.md +2 -0
- package/agents/roles/system/sales-representative/learning/sources.yaml +2 -8
- package/agents/roles/system/security-engineer/SKILL.md +2 -0
- package/agents/roles/system/security-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/site-reliability-engineer/SKILL.md +2 -0
- package/agents/roles/system/site-reliability-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/software-architect/SKILL.md +2 -0
- package/agents/roles/system/software-architect/learning/sources.yaml +2 -4
- package/agents/roles/system/solutions-engineer/SKILL.md +2 -0
- package/agents/roles/system/solutions-engineer/learning/sources.yaml +2 -4
- package/agents/roles/system/technical-program-manager/SKILL.md +2 -0
- package/agents/roles/system/technical-program-manager/learning/sources.yaml +2 -1
- package/agents/roles/system/technical-writer/SKILL.md +2 -0
- package/agents/roles/system/technical-writer/learning/sources.yaml +2 -4
- package/agents/roles/system/ui-designer/SKILL.md +2 -0
- package/agents/roles/system/ui-designer/learning/sources.yaml +2 -0
- package/agents/roles/system/user-researcher/SKILL.md +2 -0
- package/agents/roles/system/user-researcher/learning/sources.yaml +2 -0
- package/agents/roles/system/ux-designer/SKILL.md +2 -0
- package/agents/roles/system/ux-designer/learning/sources.yaml +2 -0
- package/automatization/workflows/autobuild.js +73 -11
- package/automatization/workflows/team.js +59 -6
- package/engine/agents/catalog.js +45 -20
- package/engine/agents/learning.js +19 -2
- package/engine/cli/ops.js +72 -18
- package/engine/core/manifest.js +54 -0
- package/engine/core/ownership.js +68 -21
- package/engine/teams/registry.js +23 -3
- package/package.json +1 -1
- package/teams/000-template.md +110 -0
- package/teams/README.md +60 -0
- package/teams/system/feasibility-review/WORKFLOW.md +60 -0
- package/teams/system/feasibility-review/team.json +73 -0
- package/teams/system/incident-review/WORKFLOW.md +65 -0
- package/teams/system/incident-review/team.json +72 -0
- package/teams/system/product-development/team.json +71 -17
- package/template/AGENTS.md +11 -0
- package/template/README.md +1 -1
- package/template/organization/roles/README.md +37 -0
- package/template/planning/reports/README.md +11 -0
- package/agents/coordinators/.gitkeep +0 -1
- package/agents/specialists/.gitkeep +0 -1
- package/agents/workflows/.gitkeep +0 -1
package/engine/cli/ops.js
CHANGED
|
@@ -12,6 +12,7 @@ const A = require('../automation')
|
|
|
12
12
|
const F = require('../core/files')
|
|
13
13
|
const O = require('../core/ownership')
|
|
14
14
|
const CL = require('../core/changelog')
|
|
15
|
+
const M = require('../core/manifest')
|
|
15
16
|
const C = require('../config/validate')
|
|
16
17
|
const T = require('../teams/registry')
|
|
17
18
|
const AG = require('../agents/catalog')
|
|
@@ -125,25 +126,13 @@ function init(target) {
|
|
|
125
126
|
'{{PLANNING_DIR}}': 'planning',
|
|
126
127
|
'{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
|
|
127
128
|
}, process.argv.includes('--force'))
|
|
128
|
-
copyTemplate(path.join(PROJECT_ROOT, 'agents'), path.join(root, 'agents'), {
|
|
129
|
-
'{{PROJECT_NAME}}': name,
|
|
130
|
-
'{{MODE}}': mode,
|
|
131
|
-
'{{PLANNING_DIR}}': 'planning',
|
|
132
|
-
'{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
|
|
133
|
-
}, process.argv.includes('--force'))
|
|
134
|
-
copyTemplate(path.join(PROJECT_ROOT, 'teams'), path.join(root, 'teams'), {
|
|
135
|
-
'{{PROJECT_NAME}}': name,
|
|
136
|
-
'{{MODE}}': mode,
|
|
137
|
-
'{{PLANNING_DIR}}': 'planning',
|
|
138
|
-
'{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
|
|
139
|
-
}, process.argv.includes('--force'))
|
|
140
129
|
// `ci.yml` valida el toolkit con `npm run ci`; una instancia no tiene ese script ni sus pruebas.
|
|
141
130
|
copyTemplate(path.join(PROJECT_ROOT, '.github', 'workflows'), path.join(root, '.github', 'workflows'), {
|
|
142
131
|
'{{PROJECT_NAME}}': name,
|
|
143
132
|
'{{MODE}}': mode,
|
|
144
133
|
'{{PLANNING_DIR}}': 'planning',
|
|
145
134
|
'{{WORKSPACE_PATH}}': mode === 'embedded' ? '.' : '..',
|
|
146
|
-
}, process.argv.includes('--force'), ['ci.yml'])
|
|
135
|
+
}, process.argv.includes('--force'), ['ci.yml', 'agent-learning.yml'])
|
|
147
136
|
const preserve = process.argv.includes('--force')
|
|
148
137
|
copyRuntime(
|
|
149
138
|
path.join(PROJECT_ROOT, 'automatization', 'hooks'),
|
|
@@ -176,11 +165,24 @@ function init(target) {
|
|
|
176
165
|
const engine = path.join(root, '.ops', 'engine')
|
|
177
166
|
copyRuntime(path.join(PROJECT_ROOT, 'engine'), engine, preserve, root)
|
|
178
167
|
fs.chmodSync(path.join(engine, 'cli', 'ops.js'), 0o755)
|
|
168
|
+
// Sin npm no hay de dónde leer el catálogo en tiempo de ejecución: viaja junto al motor.
|
|
169
|
+
copyRuntime(path.join(PROJECT_ROOT, 'agents'), path.join(root, '.ops', 'agents'), preserve, root)
|
|
170
|
+
copyRuntime(path.join(PROJECT_ROOT, 'teams'), path.join(root, '.ops', 'teams'), preserve, root)
|
|
171
|
+
}
|
|
172
|
+
let entregado = {}
|
|
173
|
+
for (const relative of O.trackedPaths()) {
|
|
174
|
+
const dir = path.join(root, relative)
|
|
175
|
+
if (fs.existsSync(dir)) entregado = { ...entregado, ...M.record(root, relative, O.treeFiles(dir)) }
|
|
179
176
|
}
|
|
177
|
+
M.write(root, entregado)
|
|
180
178
|
// La instancia recuerda de qué versión salió: sin esto no hay actualización posible.
|
|
181
179
|
const configFile = path.join(root, 'ops.config.json')
|
|
182
180
|
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'))
|
|
183
181
|
config.cauceVersion = version
|
|
182
|
+
// El esquema vive donde quedó el motor; la plantilla no puede saberlo de antemano.
|
|
183
|
+
config.$schema = engineMode === 'dependency'
|
|
184
|
+
? 'node_modules/@ingeniomaps/cauce/engine/schemas/ops-config.schema.json'
|
|
185
|
+
: '.ops/engine/schemas/ops-config.schema.json'
|
|
184
186
|
F.atomicWriteJson(configFile, config)
|
|
185
187
|
console.log(`\n✓ ${name}: sistema ops creado en ${root}`)
|
|
186
188
|
if (engineMode === 'dependency') console.log(' siguiente: npm install (el motor viene de la dependencia)')
|
|
@@ -492,7 +494,7 @@ function upgrade(dir) {
|
|
|
492
494
|
const from = instanceVersion(root)
|
|
493
495
|
const to = require(path.join(PROJECT_ROOT, 'package.json')).version
|
|
494
496
|
const system = O.systemPaths(root)
|
|
495
|
-
const changed = O.localChanges(root
|
|
497
|
+
const changed = O.localChanges(root)
|
|
496
498
|
const overrides = O.overrides(root)
|
|
497
499
|
|
|
498
500
|
if (dry) {
|
|
@@ -503,11 +505,37 @@ function upgrade(dir) {
|
|
|
503
505
|
process.exit(1)
|
|
504
506
|
}
|
|
505
507
|
|
|
508
|
+
// Antes de retirar nada, comprobar que no se lleve puesto aprendizaje acumulado.
|
|
509
|
+
const rescatar = O.retiredWithLearning(root)
|
|
510
|
+
if (rescatar.length && !force) {
|
|
511
|
+
for (const file of rescatar) console.error(`✗ ${file}`)
|
|
512
|
+
fail(
|
|
513
|
+
`\n${rescatar.length} archivo(s) de aprendizaje quedaron en una ruta que Cauce ya no mantiene.\n\n` +
|
|
514
|
+
'Movelos a un cargo propio en agents/roles/<slug>/learning/ y repetí, o descartalos con --force.',
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
|
|
506
518
|
if (changed.length && !force) {
|
|
507
519
|
for (const file of changed) console.error(`✗ ${file}`)
|
|
520
|
+
const reglas = changed.filter((file) => file.includes('/system/'))
|
|
521
|
+
const runtime = changed.filter((file) => !file.includes('/system/'))
|
|
522
|
+
const guia = []
|
|
523
|
+
if (reglas.length) {
|
|
524
|
+
guia.push(
|
|
525
|
+
'Las reglas y decisiones bajo system/ son del toolkit. Para cambiar una, escribí la tuya al\n'
|
|
526
|
+
+ 'lado con el mismo ID: el proyecto manda y `check` lo reporta como override explícito.',
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
if (runtime.length) {
|
|
530
|
+
guia.push(
|
|
531
|
+
'El runtime es del toolkit: en vez de editarlo, agregá lo tuyo al lado con otro nombre —un\n'
|
|
532
|
+
+ 'guard propio sobrevive a cada actualización— y registralo en la configuración de tu runner,\n'
|
|
533
|
+
+ 'que sí es del proyecto. Para desactivar un guard alcanza con quitarlo de esa configuración.',
|
|
534
|
+
)
|
|
535
|
+
}
|
|
508
536
|
fail(
|
|
509
|
-
`\n${changed.length} archivo(s)
|
|
510
|
-
'
|
|
537
|
+
`\n${changed.length} archivo(s) que mantiene Cauce fueron editados y se perderían.\n\n` +
|
|
538
|
+
`${guia.join('\n\n')}\n\nSi el cambio ya no te sirve, repetí con --force para descartarlo.`,
|
|
511
539
|
)
|
|
512
540
|
}
|
|
513
541
|
|
|
@@ -515,8 +543,9 @@ function upgrade(dir) {
|
|
|
515
543
|
const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
|
|
516
544
|
if (!fs.existsSync(origin)) continue
|
|
517
545
|
const target = path.join(root, relative)
|
|
518
|
-
//
|
|
519
|
-
|
|
546
|
+
// `.ops/` es el paquete vendorizado de una instancia sin npm. Si no existe, esta instancia lo
|
|
547
|
+
// toma de la dependencia y crearlo sería duplicar lo que el lockfile ya versiona.
|
|
548
|
+
if (relative.startsWith('.ops/') && !fs.existsSync(target)) continue
|
|
520
549
|
const skip = O.TEMPLATE_OWNED
|
|
521
550
|
.filter((owned) => owned.startsWith(`${relative}/`))
|
|
522
551
|
.map((owned) => path.basename(owned))
|
|
@@ -527,11 +556,34 @@ function upgrade(dir) {
|
|
|
527
556
|
}
|
|
528
557
|
}
|
|
529
558
|
|
|
559
|
+
// Retirar lo que el toolkit ya no distribuye, después de haber actualizado lo que sí.
|
|
560
|
+
const retirado = []
|
|
561
|
+
for (const relative of O.RETIRED) {
|
|
562
|
+
const target = path.join(root, relative)
|
|
563
|
+
if (!fs.existsSync(target)) continue
|
|
564
|
+
F.assertNoSymlinkPath(root, target)
|
|
565
|
+
fs.rmSync(target, { recursive: true, force: true })
|
|
566
|
+
retirado.push(relative)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Dejar registrado lo que se entregó, para poder distinguir después una edición local de una
|
|
570
|
+
// mejora del toolkit.
|
|
571
|
+
let registro = M.read(root)
|
|
572
|
+
for (const relative of O.trackedPaths()) {
|
|
573
|
+
const dir = path.join(root, relative)
|
|
574
|
+
if (fs.existsSync(dir)) registro = { ...registro, ...M.record(root, relative, O.treeFiles(dir)) }
|
|
575
|
+
}
|
|
576
|
+
M.write(root, registro)
|
|
577
|
+
|
|
530
578
|
const config = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8'))
|
|
531
579
|
config.cauceVersion = to
|
|
532
580
|
F.atomicWriteJson(path.join(root, 'ops.config.json'), config)
|
|
533
581
|
|
|
534
582
|
console.log(`✓ Cauce ${from || '(previa)'} → ${to}`)
|
|
583
|
+
// Descartar con --force es legítimo; hacerlo sin dejar rastro no. Queda en la salida del comando,
|
|
584
|
+
// que es la evidencia que el protocolo pide para cualquier cambio.
|
|
585
|
+
for (const file of changed) console.log(`− descartado tu cambio en ${file}`)
|
|
586
|
+
for (const relative of retirado) console.log(`− retirado ${relative}: Cauce ya no lo distribuye`)
|
|
535
587
|
printChangelog(from, to)
|
|
536
588
|
console.log(` ${system.length} ruta(s) del sistema y ${O.RUNTIME_PATHS.length} del runtime actualizadas`)
|
|
537
589
|
for (const override of overrides) {
|
|
@@ -547,8 +599,10 @@ function agents(action, dir) {
|
|
|
547
599
|
const root = path.resolve(dir || '.')
|
|
548
600
|
const roles = AG.list(root)
|
|
549
601
|
if (process.argv.includes('--json')) {
|
|
602
|
+
// `path` viene resuelto: quien consuma esto no debería reconstruir dónde ganó la precedencia.
|
|
550
603
|
return console.log(JSON.stringify(roles.map((role) => ({
|
|
551
604
|
slug: role.slug, type: role.type, system: role.system,
|
|
605
|
+
path: path.relative(root, role.dir).split(path.sep).join('/'),
|
|
552
606
|
}))))
|
|
553
607
|
}
|
|
554
608
|
for (const role of roles) console.log(`${role.slug}${role.system ? '' : ' (propio)'}`)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Qué entregó Cauce y con qué contenido. Sin este registro no se puede distinguir un archivo que
|
|
4
|
+
// la empresa editó de uno que cambió río arriba: los dos se ven igual comparando la instancia
|
|
5
|
+
// contra el paquete, y `upgrade` terminaría negándose ante cualquier mejora del toolkit.
|
|
6
|
+
//
|
|
7
|
+
// Se guarda junto a la configuración porque es estado de la instalación, no del producto, y se
|
|
8
|
+
// commitea: todo el equipo tiene que ver lo mismo.
|
|
9
|
+
|
|
10
|
+
const crypto = require('node:crypto')
|
|
11
|
+
const fs = require('node:fs')
|
|
12
|
+
const path = require('node:path')
|
|
13
|
+
|
|
14
|
+
const FILE = path.join('.cauce', 'manifest.json')
|
|
15
|
+
|
|
16
|
+
function digest(file) {
|
|
17
|
+
try {
|
|
18
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex').slice(0, 16)
|
|
19
|
+
} catch { return '' }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function read(root) {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(fs.readFileSync(path.join(root, FILE), 'utf8'))
|
|
25
|
+
return data && typeof data.files === 'object' ? data.files : {}
|
|
26
|
+
} catch { return {} }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function write(root, files) {
|
|
30
|
+
const target = path.join(root, FILE)
|
|
31
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
32
|
+
const ordered = Object.fromEntries(Object.entries(files).sort(([left], [right]) => left.localeCompare(right)))
|
|
33
|
+
fs.writeFileSync(target, `${JSON.stringify({ version: 1, files: ordered }, null, 2)}\n`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Registra lo entregado en una ruta, relativo a la raíz de la instancia.
|
|
37
|
+
function record(root, relative, files) {
|
|
38
|
+
const current = read(root)
|
|
39
|
+
for (const file of files) current[`${relative}/${file}`] = digest(path.join(root, relative, file))
|
|
40
|
+
return current
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Archivos que la empresa modificó después de recibirlos. Un archivo sin registro previo no
|
|
44
|
+
// cuenta: llegó con una versión anterior a este mecanismo, o lo agregó el proyecto.
|
|
45
|
+
function edited(root, relative, files) {
|
|
46
|
+
const recorded = read(root)
|
|
47
|
+
return files.filter((file) => {
|
|
48
|
+
const key = `${relative}/${file}`
|
|
49
|
+
if (!recorded[key]) return false
|
|
50
|
+
return recorded[key] !== digest(path.join(root, relative, file))
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { FILE, digest, edited, read, record, write }
|
package/engine/core/ownership.js
CHANGED
|
@@ -22,6 +22,7 @@ const SYSTEM_FILES = [
|
|
|
22
22
|
'planning/rules/README.md',
|
|
23
23
|
'planning/roadmap/README.md',
|
|
24
24
|
'planning/roadmap/epic-000-template.md',
|
|
25
|
+
'organization/roles/README.md',
|
|
25
26
|
]
|
|
26
27
|
|
|
27
28
|
// Colecciones mixtas: el toolkit posee `<dir>/system/`, el proyecto todo lo demás del directorio.
|
|
@@ -29,7 +30,6 @@ const SYSTEM_COLLECTIONS = [
|
|
|
29
30
|
'planning/adr',
|
|
30
31
|
'planning/business-rules',
|
|
31
32
|
'planning/rules',
|
|
32
|
-
'teams',
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
// Copias del runtime: el proyecto las recibe para poder ejecutar sin red, pero no las escribe.
|
|
@@ -37,6 +37,8 @@ const SYSTEM_COLLECTIONS = [
|
|
|
37
37
|
// se reporta antes de pisarla, nunca después.
|
|
38
38
|
const RUNTIME_PATHS = [
|
|
39
39
|
'.ops/engine',
|
|
40
|
+
'.ops/agents',
|
|
41
|
+
'.ops/teams',
|
|
40
42
|
'automatization/hooks',
|
|
41
43
|
'automatization/runners',
|
|
42
44
|
'automatization/workflows',
|
|
@@ -48,9 +50,15 @@ const TEMPLATE_OWNED = ['automatization/runners/README.md']
|
|
|
48
50
|
|
|
49
51
|
// Dentro del paquete, lo que una instancia recibe en su raíz vive bajo `template/`; el catálogo,
|
|
50
52
|
// los equipos y la automatización están en la raíz del paquete, y el motor en `engine/`.
|
|
53
|
+
const TEMPLATE_PREFIXES = ['planning/', 'organization/', 'integrations/']
|
|
54
|
+
|
|
51
55
|
function sourceOf(relative) {
|
|
52
56
|
if (relative === '.ops/engine') return 'engine'
|
|
53
|
-
if (relative.
|
|
57
|
+
if (relative === '.ops/agents') return 'agents'
|
|
58
|
+
if (relative === '.ops/teams') return 'teams'
|
|
59
|
+
if (TEMPLATE_PREFIXES.some((prefix) => relative.startsWith(prefix))) {
|
|
60
|
+
return path.join('template', relative)
|
|
61
|
+
}
|
|
54
62
|
return relative
|
|
55
63
|
}
|
|
56
64
|
|
|
@@ -71,6 +79,19 @@ function engineAt(root, relative = '') {
|
|
|
71
79
|
.find((candidate) => fs.existsSync(candidate)) || ''
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
// Definiciones que consume el motor —cargos y equipos— y que por eso viajan con el paquete en vez
|
|
83
|
+
// de copiarse. Se reconocen por contener `system/`, que es el espacio del toolkit y no algo que un
|
|
84
|
+
// proyecto deba crear. Una sola implementación para las dos, o divergen.
|
|
85
|
+
function packageDir(root, name) {
|
|
86
|
+
const candidates = [
|
|
87
|
+
path.join(root, 'node_modules', '@ingeniomaps', 'cauce', name),
|
|
88
|
+
path.join(root, '.ops', name),
|
|
89
|
+
path.join(root, name),
|
|
90
|
+
]
|
|
91
|
+
return candidates.find((dir) => fs.existsSync(path.join(dir, 'system'))
|
|
92
|
+
|| fs.existsSync(path.join(dir, 'roles', 'system'))) || ''
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
// Identidad de una entrada dentro de una colección, para detectar que el proyecto sobrescribe
|
|
75
96
|
// algo del sistema. Las reglas y decisiones se identifican por su ID; el resto, por su nombre.
|
|
76
97
|
const ID_PATTERN = /^(?:BR-)?[A-Z][A-Z0-9]*-\d{3}/
|
|
@@ -106,12 +127,35 @@ function overrides(root) {
|
|
|
106
127
|
return found
|
|
107
128
|
}
|
|
108
129
|
|
|
130
|
+
// Rutas que el toolkit dejó de materializar. Sin esto una instancia arrastra para siempre lo que
|
|
131
|
+
// alguna versión suya copió: `upgrade` agrega y reemplaza, pero nunca quitaba nada.
|
|
132
|
+
const RETIRED = [
|
|
133
|
+
'agents/roles/system',
|
|
134
|
+
'teams/system',
|
|
135
|
+
'.github/workflows/agent-learning.yml',
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
// Aprendizaje que quedó dentro de una ruta retirada. Es lo único ahí que no se puede reponer, así
|
|
139
|
+
// que se detecta antes de borrar nada: perderlo en silencio sería peor que dejar el directorio.
|
|
140
|
+
function retiredWithLearning(root) {
|
|
141
|
+
const found = []
|
|
142
|
+
for (const relative of RETIRED) {
|
|
143
|
+
const dir = path.join(root, relative)
|
|
144
|
+
if (!fs.existsSync(dir)) continue
|
|
145
|
+
for (const file of treeFiles(dir)) {
|
|
146
|
+
if (!/(^|\/)learning\/(reports|proposals)\//.test(file)) continue
|
|
147
|
+
if (path.basename(file).startsWith('_')) continue
|
|
148
|
+
found.push(`${relative}/${file}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return found
|
|
152
|
+
}
|
|
153
|
+
|
|
109
154
|
// Rutas que `upgrade` reemplaza. Todo lo que no aparezca acá pertenece al proyecto.
|
|
155
|
+
// Se listan aunque todavía no existan en la instancia: así un archivo nuevo del sistema llega en
|
|
156
|
+
// vez de esperar a que alguien lo cree a mano.
|
|
110
157
|
function systemPaths(root) {
|
|
111
|
-
const paths = []
|
|
112
|
-
for (const file of SYSTEM_FILES) {
|
|
113
|
-
if (fs.existsSync(path.join(root, file))) paths.push(file)
|
|
114
|
-
}
|
|
158
|
+
const paths = [...SYSTEM_FILES]
|
|
115
159
|
for (const collection of SYSTEM_COLLECTIONS) {
|
|
116
160
|
const dir = path.join(root, collection, 'system')
|
|
117
161
|
if (fs.existsSync(dir)) paths.push(`${collection}/system`)
|
|
@@ -132,28 +176,31 @@ function treeFiles(dir, prefix = '') {
|
|
|
132
176
|
return found.sort()
|
|
133
177
|
}
|
|
134
178
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
// Rutas que Cauce mantiene y que por eso conviene registrar y vigilar.
|
|
180
|
+
function trackedPaths() {
|
|
181
|
+
return [...RUNTIME_PATHS, ...SYSTEM_COLLECTIONS.map((collection) => `${collection}/system`)]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Archivos que la empresa editó después de recibirlos. Se compara contra lo que Cauce entregó,
|
|
185
|
+
// no contra el paquete: si se comparara contra el paquete, cualquier mejora del toolkit se vería
|
|
186
|
+
// idéntica a una edición local y bloquearía la actualización.
|
|
187
|
+
function localChanges(root) {
|
|
188
|
+
const manifest = require('./manifest')
|
|
138
189
|
const changed = []
|
|
139
|
-
for (const target of
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
for (const file of treeFiles(from)) {
|
|
144
|
-
if (TEMPLATE_OWNED.includes(`${target}/${file}`)) continue
|
|
145
|
-
const mine = path.join(to, file)
|
|
146
|
-
if (!fs.existsSync(mine)) continue
|
|
147
|
-
if (fs.readFileSync(mine, 'utf8') !== fs.readFileSync(path.join(from, file), 'utf8')) {
|
|
148
|
-
changed.push(`${target}/${file}`)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
190
|
+
for (const target of trackedPaths()) {
|
|
191
|
+
const dir = path.join(root, target)
|
|
192
|
+
if (!fs.existsSync(dir)) continue
|
|
193
|
+
for (const file of manifest.edited(root, target, treeFiles(dir))) changed.push(`${target}/${file}`)
|
|
151
194
|
}
|
|
152
195
|
return changed
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
module.exports = {
|
|
199
|
+
RETIRED,
|
|
200
|
+
trackedPaths,
|
|
201
|
+
packageDir,
|
|
156
202
|
RUNTIME_PATHS,
|
|
203
|
+
retiredWithLearning,
|
|
157
204
|
engineAt,
|
|
158
205
|
engineCandidates,
|
|
159
206
|
TEMPLATE_OWNED,
|
package/engine/teams/registry.js
CHANGED
|
@@ -5,14 +5,23 @@ const path = require('path')
|
|
|
5
5
|
const catalog = require('../agents/catalog')
|
|
6
6
|
|
|
7
7
|
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
8
|
+
const PHASES = ['discovery', 'delivery']
|
|
9
|
+
// Qué deja el recorrido: una épica candidata que alguien puede promover, o un informe que registra
|
|
10
|
+
// lo aprendido. Sin declararlo, el workflow sólo sabría terminar de una forma.
|
|
11
|
+
const OUTCOMES = ['epic', 'report']
|
|
8
12
|
|
|
9
13
|
// El proyecto manda sobre el sistema: un team propio con el mismo slug reemplaza al de `system/`,
|
|
10
14
|
// que se sigue actualizando debajo sin que nadie tenga que forkearlo.
|
|
15
|
+
function systemTeams(root) {
|
|
16
|
+
return require('../core/ownership').packageDir(root, 'teams')
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
function teamFile(root, slug) {
|
|
12
20
|
if (!SLUG.test(slug || '')) throw new Error(`team inválido: ${slug || '(vacío)'}`)
|
|
13
21
|
const own = path.join(root, 'teams', slug, 'team.json')
|
|
14
22
|
if (fs.existsSync(own)) return own
|
|
15
|
-
|
|
23
|
+
const system = systemTeams(root)
|
|
24
|
+
return system ? path.join(system, 'system', slug, 'team.json') : own
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
// Devuelve el motivo real: "no existe" y "ambiguo" piden acciones distintas de quien lo lea.
|
|
@@ -40,6 +49,7 @@ function validate(root, slug) {
|
|
|
40
49
|
for (const field of ['name', 'purpose', 'entryAgent', 'facilitator']) {
|
|
41
50
|
if (typeof manifest[field] !== 'string' || !manifest[field].trim()) errors.push(`falta ${field}`)
|
|
42
51
|
}
|
|
52
|
+
if (!OUTCOMES.includes(manifest.outcome)) errors.push(`outcome debe ser ${OUTCOMES.join(' o ')}`)
|
|
43
53
|
if (!Array.isArray(manifest.stages) || !manifest.stages.length) errors.push('stages debe contener etapas')
|
|
44
54
|
if (!Array.isArray(manifest.guardrails) || !manifest.guardrails.length) errors.push('guardrails debe contener controles')
|
|
45
55
|
if (!Array.isArray(manifest.completion) || !manifest.completion.length) errors.push('completion debe contener criterios')
|
|
@@ -58,6 +68,9 @@ function validate(root, slug) {
|
|
|
58
68
|
agents.add(stage.agent)
|
|
59
69
|
if (!Array.isArray(stage.produces) || !stage.produces.length) errors.push(`${stage.id}: falta produces`)
|
|
60
70
|
if (typeof stage.exitGate !== 'string' || !stage.exitGate.trim()) errors.push(`${stage.id}: falta exitGate`)
|
|
71
|
+
// Descubrimiento propone, entrega ejecuta. Sin la distinción, un recorrido de descubrimiento
|
|
72
|
+
// terminaría construyendo antes de que exista la épica y antes de la promoción humana.
|
|
73
|
+
if (!PHASES.includes(stage.phase)) errors.push(`${stage.id}: phase debe ser ${PHASES.join(' o ')}`)
|
|
61
74
|
}
|
|
62
75
|
for (const agent of agents) {
|
|
63
76
|
if (!SLUG.test(agent || '')) errors.push(`slug de agente inválido: ${agent || '(vacío)'}`)
|
|
@@ -66,6 +79,9 @@ function validate(root, slug) {
|
|
|
66
79
|
if (problem) errors.push(problem)
|
|
67
80
|
}
|
|
68
81
|
}
|
|
82
|
+
if (!(manifest.stages || []).some((stage) => stage.phase === 'discovery')) {
|
|
83
|
+
errors.push('falta al menos una etapa de discovery: un equipo sin descubrimiento no propone nada')
|
|
84
|
+
}
|
|
69
85
|
// Junto al team.json que ganó la resolución, no en una ruta fija: el team puede venir de system/.
|
|
70
86
|
const workflow = path.join(path.dirname(teamFile(root, slug)), 'WORKFLOW.md')
|
|
71
87
|
if (!fs.existsSync(workflow)) errors.push('falta WORKFLOW.md')
|
|
@@ -82,9 +98,13 @@ function slugsIn(dir) {
|
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
// Un slug aparece una sola vez aunque exista en los dos niveles: el del proyecto ya ganó.
|
|
101
|
+
// Los propios de la empresa y los que trae Cauce, sin duplicar un slug que ya ganó el proyecto.
|
|
85
102
|
function list(root) {
|
|
86
|
-
const
|
|
87
|
-
const slugs = new Set([
|
|
103
|
+
const system = systemTeams(root)
|
|
104
|
+
const slugs = new Set([
|
|
105
|
+
...slugsIn(path.join(root, 'teams')),
|
|
106
|
+
...(system ? slugsIn(path.join(system, 'system')) : []),
|
|
107
|
+
])
|
|
88
108
|
return [...slugs].sort()
|
|
89
109
|
}
|
|
90
110
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Plantilla de equipo
|
|
2
|
+
|
|
3
|
+
Un equipo compone varios cargos en etapas, con un dueño por dominio de decisión y un gate de salida por
|
|
4
|
+
etapa. Vive en `teams/<slug>/` y necesita dos archivos: `team.json` y `WORKFLOW.md`.
|
|
5
|
+
|
|
6
|
+
Los equipos que trae Cauce están en `teams/system/`. Un equipo propio con el mismo slug **reemplaza** al
|
|
7
|
+
del sistema; con otro slug, convive. Nunca editar dentro de `system/`: se pierde al actualizar.
|
|
8
|
+
|
|
9
|
+
Validar siempre con `node tools/ops.js team check <slug>` antes de usarlo.
|
|
10
|
+
|
|
11
|
+
## `team.json`
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"schemaVersion": 1,
|
|
16
|
+
"slug": "acme-soporte",
|
|
17
|
+
"name": "Soporte de Acme",
|
|
18
|
+
"purpose": "Una frase: qué convierte este equipo, de qué entrada a qué salida.",
|
|
19
|
+
"outcome": "report",
|
|
20
|
+
|
|
21
|
+
"entryAgent": "customer-support-specialist",
|
|
22
|
+
"facilitator": "project-manager",
|
|
23
|
+
|
|
24
|
+
"decisionOwners": {
|
|
25
|
+
"customer_impact": "customer-support-specialist",
|
|
26
|
+
"technical_design": "software-architect"
|
|
27
|
+
},
|
|
28
|
+
"conditionalAgents": ["security-engineer", "privacy-compliance-specialist"],
|
|
29
|
+
|
|
30
|
+
"stages": [
|
|
31
|
+
{
|
|
32
|
+
"id": "triage",
|
|
33
|
+
"phase": "discovery",
|
|
34
|
+
"agent": "customer-support-specialist",
|
|
35
|
+
"dependsOn": [],
|
|
36
|
+
"produces": ["impacto-y-alcance"],
|
|
37
|
+
"exitGate": "Qué falla, a cuántos afecta y desde cuándo está explícito y verificado."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "diagnose",
|
|
41
|
+
"phase": "discovery",
|
|
42
|
+
"agent": "software-architect",
|
|
43
|
+
"dependsOn": ["triage"],
|
|
44
|
+
"produces": ["causa-probable", "opciones"],
|
|
45
|
+
"exitGate": "Hay una causa sostenida por evidencia, o la pregunta concreta que la resolvería."
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
"guardrails": [
|
|
50
|
+
"Cada agente conserva los límites y autorizaciones de su SKILL.md.",
|
|
51
|
+
"Ningún handoff convierte una propuesta en aprobación ni en ejecución."
|
|
52
|
+
],
|
|
53
|
+
"completion": [
|
|
54
|
+
"El resultado distingue lo que se sabe de lo que se supone.",
|
|
55
|
+
"Lo que quedó pendiente tiene responsable y acción concreta."
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Qué exige el validador
|
|
61
|
+
|
|
62
|
+
| Campo | Regla |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `schemaVersion` | debe ser `1` |
|
|
65
|
+
| `slug` | igual al nombre del directorio, en kebab-case |
|
|
66
|
+
| `name`, `purpose`, `entryAgent`, `facilitator` | string no vacío |
|
|
67
|
+
| `outcome` | `epic` o `report` |
|
|
68
|
+
| `stages` | al menos una, con `id` único en kebab-case |
|
|
69
|
+
| `stages[].phase` | `discovery` o `delivery`, y al menos una `discovery` |
|
|
70
|
+
| `stages[].dependsOn` | sólo etapas **anteriores**; no se permiten ciclos ni adelantos |
|
|
71
|
+
| `stages[].produces` | al menos un artefacto nombrado |
|
|
72
|
+
| `stages[].exitGate` | una condición verificable, no un deseo |
|
|
73
|
+
| agentes citados | deben existir en `agents/` y no ser ambiguos |
|
|
74
|
+
| `guardrails`, `completion` | al menos un elemento cada uno |
|
|
75
|
+
| `WORKFLOW.md` | debe existir junto al `team.json` |
|
|
76
|
+
|
|
77
|
+
## `outcome`: qué deja el recorrido
|
|
78
|
+
|
|
79
|
+
- **`epic`** — propone trabajo. Escribe una épica candidata en `planning/roadmap/` con criterios
|
|
80
|
+
observables, y para. Es lo que corresponde cuando la pregunta es *qué construimos*.
|
|
81
|
+
- **`report`** — registra lo aprendido. Escribe un informe en `planning/reports/<fecha>-<slug>.md`,
|
|
82
|
+
deja los seguimientos en la sección Lecciones de `planning/INBOX.md` **sin promover** y las acciones
|
|
83
|
+
que requieren una persona en `planning/HUMAN_ACTIONS.md`. Es lo que corresponde a una revisión.
|
|
84
|
+
|
|
85
|
+
Ninguno de los dos promueve al BACKLOG. La diferencia no es cuánta autoridad tienen —ninguno tiene—,
|
|
86
|
+
sino qué artefacto dejan para que una persona decida.
|
|
87
|
+
|
|
88
|
+
## `discovery` y `delivery`
|
|
89
|
+
|
|
90
|
+
**Descubrimiento propone; entrega ejecuta.** `/team` recorre únicamente las etapas `discovery` y termina
|
|
91
|
+
proponiendo una épica. Las etapas `delivery` las corre `autobuild`, y sólo después de que una persona
|
|
92
|
+
promueva esa épica al BACKLOG.
|
|
93
|
+
|
|
94
|
+
Marcar una etapa de construcción como `discovery` haría que se escriba código antes de la aprobación
|
|
95
|
+
humana, que es justamente lo que el protocolo impide.
|
|
96
|
+
|
|
97
|
+
## Cómo escribir un exit gate
|
|
98
|
+
|
|
99
|
+
Un gate se cumple o no se cumple sin discusión. Comparar:
|
|
100
|
+
|
|
101
|
+
- ❌ "El problema está bien entendido."
|
|
102
|
+
- ✅ "Problema, usuario, outcome, baseline y decisión requerida están explícitos."
|
|
103
|
+
|
|
104
|
+
Si para saber si un gate se cumplió hace falta interpretar, el recorrido va a avanzar siempre.
|
|
105
|
+
|
|
106
|
+
## `WORKFLOW.md`
|
|
107
|
+
|
|
108
|
+
Acompaña al manifiesto y explica lo que el JSON no puede: cuándo activarlo, cómo se ve un handoff, con
|
|
109
|
+
qué criterio entran los agentes condicionales y qué **no** hace el equipo. Sin ese archivo, `team check`
|
|
110
|
+
falla.
|
package/teams/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Equipos
|
|
2
|
+
|
|
3
|
+
Un equipo compone varios cargos de `agents/` en un recorrido con etapas, dueños de decisión y gates de
|
|
4
|
+
salida. Es lo que convierte una intención en una épica candidata —o en la razón por la que todavía no
|
|
5
|
+
lo es— sin que un solo agente decida por todos.
|
|
6
|
+
|
|
7
|
+
## Los que trae Cauce
|
|
8
|
+
|
|
9
|
+
| Equipo | Descubrimiento | Deja | Para qué |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `system/feasibility-review` | 3 etapas | épica | ¿vale el esfuerzo? Con la evidencia que ya existe. |
|
|
12
|
+
| `system/product-development` | 5 etapas | épica | ¿qué construimos y cómo? Produce evidencia nueva. |
|
|
13
|
+
| `system/incident-review` | 4 etapas | informe | ¿qué pasó y qué aprendemos? Después de contener. |
|
|
14
|
+
|
|
15
|
+
Son tres **formas**, no tres dominios. Un equipo de seguridad o uno de crecimiento tienen la misma forma
|
|
16
|
+
con otros cargos: eso lo escribe cada empresa, porque cómo decide es suyo.
|
|
17
|
+
|
|
18
|
+
## Escribir uno propio
|
|
19
|
+
|
|
20
|
+
Copiar la estructura de [000-template.md](000-template.md) a `teams/<slug>/`, con su `team.json` y su
|
|
21
|
+
`WORKFLOW.md`, y validar:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node tools/ops.js team list
|
|
25
|
+
node tools/ops.js team check <slug>
|
|
26
|
+
node tools/ops.js team show <slug>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Un equipo propio con el mismo slug que uno de `system/` lo reemplaza; con otro slug, convive. **Nunca
|
|
30
|
+
editar dentro de `system/`**: se reemplaza completo en cada actualización.
|
|
31
|
+
|
|
32
|
+
## Invocar un equipo
|
|
33
|
+
|
|
34
|
+
Tres formas, de la más simple a la más explícita:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
/team quiero cobrar con tarjeta guardada
|
|
38
|
+
/team incident-review: se cayó el checkout el martes a las 14:00
|
|
39
|
+
/team {"team": "acme-soporte", "intent": "qué se quiere evaluar"}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Sin prefijo corre `product-development`. El prefijo se confirma contra los equipos que existen: si no
|
|
43
|
+
es uno, el texto completo se toma como intención, así que escribir `nota: revisar esto` no dispara un
|
|
44
|
+
equipo llamado `nota`. Con el equipo pasado explícitamente, un slug inexistente falla en vez de caer al
|
|
45
|
+
por defecto en silencio.
|
|
46
|
+
|
|
47
|
+
## Corto o largo
|
|
48
|
+
|
|
49
|
+
Un recorrido largo cuesta más y aporta más certeza. La elección es la misma que con los lanes de una
|
|
50
|
+
tarea: usar el corto cuando la evidencia ya existe y la pregunta es de esfuerzo, y el largo cuando hay
|
|
51
|
+
que averiguar algo antes de comprometerse.
|
|
52
|
+
|
|
53
|
+
Un recorrido corto que termina en "hay que investigar" no falló: acotó el problema por el costo de tres
|
|
54
|
+
etapas en lugar de cinco.
|
|
55
|
+
|
|
56
|
+
## Lo que un equipo nunca hace
|
|
57
|
+
|
|
58
|
+
Ningún recorrido promueve trabajo al BACKLOG. Escribe la épica candidata en `planning/roadmap/` y para;
|
|
59
|
+
la promoción es la firma humana que autoriza ejecución. Un gate que no se cumple se convierte en una
|
|
60
|
+
acción concreta en `planning/HUMAN_ACTIONS.md`, no en un gate más blando.
|