@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.
Files changed (145) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +25 -3
  3. package/agents/roles/system/ai-governance-lead/SKILL.md +2 -0
  4. package/agents/roles/system/ai-governance-lead/learning/sources.yaml +2 -4
  5. package/agents/roles/system/ai-product-manager/SKILL.md +2 -0
  6. package/agents/roles/system/ai-product-manager/learning/sources.yaml +2 -2
  7. package/agents/roles/system/analytics-engineer/SKILL.md +2 -0
  8. package/agents/roles/system/analytics-engineer/learning/sources.yaml +2 -1
  9. package/agents/roles/system/backend-engineer/SKILL.md +2 -0
  10. package/agents/roles/system/backend-engineer/learning/sources.yaml +2 -4
  11. package/agents/roles/system/business-operations-manager/SKILL.md +2 -0
  12. package/agents/roles/system/business-operations-manager/learning/sources.yaml +2 -4
  13. package/agents/roles/system/business-strategist/SKILL.md +2 -0
  14. package/agents/roles/system/business-strategist/learning/sources.yaml +2 -8
  15. package/agents/roles/system/cloud-architect/SKILL.md +2 -0
  16. package/agents/roles/system/cloud-architect/learning/sources.yaml +2 -2
  17. package/agents/roles/system/community-manager/SKILL.md +2 -0
  18. package/agents/roles/system/community-manager/learning/sources.yaml +2 -4
  19. package/agents/roles/system/content-specialist/SKILL.md +2 -0
  20. package/agents/roles/system/content-specialist/learning/sources.yaml +2 -8
  21. package/agents/roles/system/customer-success-manager/SKILL.md +2 -0
  22. package/agents/roles/system/customer-success-manager/learning/sources.yaml +2 -8
  23. package/agents/roles/system/customer-support-specialist/SKILL.md +2 -0
  24. package/agents/roles/system/customer-support-specialist/learning/sources.yaml +2 -8
  25. package/agents/roles/system/data-analyst/SKILL.md +2 -0
  26. package/agents/roles/system/data-analyst/learning/sources.yaml +2 -8
  27. package/agents/roles/system/data-engineer/SKILL.md +2 -0
  28. package/agents/roles/system/data-engineer/learning/sources.yaml +2 -4
  29. package/agents/roles/system/data-scientist/SKILL.md +2 -0
  30. package/agents/roles/system/data-scientist/learning/sources.yaml +2 -4
  31. package/agents/roles/system/database-administrator/SKILL.md +2 -0
  32. package/agents/roles/system/database-administrator/learning/sources.yaml +2 -2
  33. package/agents/roles/system/developer-relations-engineer/SKILL.md +2 -0
  34. package/agents/roles/system/developer-relations-engineer/learning/sources.yaml +2 -4
  35. package/agents/roles/system/devops-engineer/SKILL.md +2 -0
  36. package/agents/roles/system/devops-engineer/learning/sources.yaml +2 -4
  37. package/agents/roles/system/engineering-manager/SKILL.md +2 -0
  38. package/agents/roles/system/engineering-manager/learning/sources.yaml +2 -4
  39. package/agents/roles/system/financial-controller/SKILL.md +2 -0
  40. package/agents/roles/system/financial-controller/learning/sources.yaml +2 -8
  41. package/agents/roles/system/finops-engineer/SKILL.md +83 -0
  42. package/agents/roles/system/finops-engineer/agents/openai.yaml +4 -0
  43. package/agents/roles/system/finops-engineer/evaluations/cases/01-apagar-en-produccion.md +10 -0
  44. package/agents/roles/system/finops-engineer/evaluations/cases/02-ahorro-estimado-como-realizado.md +10 -0
  45. package/agents/roles/system/finops-engineer/evaluations/cases/03-salto-por-regresion.md +10 -0
  46. package/agents/roles/system/finops-engineer/evaluations/cases/04-optimizar-contra-la-fiabilidad.md +10 -0
  47. package/agents/roles/system/finops-engineer/evaluations/cases/05-costos-por-cliente.md +10 -0
  48. package/agents/roles/system/finops-engineer/evaluations/cases/06-adversarial-calculadora-del-proveedor.md +11 -0
  49. package/agents/roles/system/finops-engineer/evaluations/expected-behaviors.yaml +21 -0
  50. package/agents/roles/system/finops-engineer/learning/CODEX_AUTOMATION.md +18 -0
  51. package/agents/roles/system/finops-engineer/learning/HISTORY.md +4 -0
  52. package/agents/roles/system/finops-engineer/learning/proposals/_template.md +15 -0
  53. package/agents/roles/system/finops-engineer/learning/reports/_template.md +14 -0
  54. package/agents/roles/system/finops-engineer/learning/sources.yaml +27 -0
  55. package/agents/roles/system/finops-engineer/references/operating-model.md +70 -0
  56. package/agents/roles/system/frontend-engineer/SKILL.md +2 -0
  57. package/agents/roles/system/frontend-engineer/learning/sources.yaml +2 -4
  58. package/agents/roles/system/growth-marketer/SKILL.md +79 -0
  59. package/agents/roles/system/growth-marketer/agents/openai.yaml +4 -0
  60. package/agents/roles/system/growth-marketer/evaluations/cases/01-gastar-sin-baseline.md +10 -0
  61. package/agents/roles/system/growth-marketer/evaluations/cases/02-metrica-de-plataforma.md +10 -0
  62. package/agents/roles/system/growth-marketer/evaluations/cases/03-cortar-experimento.md +10 -0
  63. package/agents/roles/system/growth-marketer/evaluations/cases/04-promesa-que-el-producto-no-sostiene.md +10 -0
  64. package/agents/roles/system/growth-marketer/evaluations/cases/05-audiencia-sin-base-legal.md +10 -0
  65. package/agents/roles/system/growth-marketer/evaluations/cases/06-adversarial-caso-de-exito.md +11 -0
  66. package/agents/roles/system/growth-marketer/evaluations/expected-behaviors.yaml +21 -0
  67. package/agents/roles/system/growth-marketer/learning/CODEX_AUTOMATION.md +18 -0
  68. package/agents/roles/system/growth-marketer/learning/HISTORY.md +4 -0
  69. package/agents/roles/system/growth-marketer/learning/proposals/_template.md +15 -0
  70. package/agents/roles/system/growth-marketer/learning/reports/_template.md +14 -0
  71. package/agents/roles/system/growth-marketer/learning/sources.yaml +27 -0
  72. package/agents/roles/system/growth-marketer/references/operating-model.md +76 -0
  73. package/agents/roles/system/implementation-manager/SKILL.md +2 -0
  74. package/agents/roles/system/implementation-manager/learning/sources.yaml +2 -4
  75. package/agents/roles/system/legal-counsel/SKILL.md +2 -0
  76. package/agents/roles/system/legal-counsel/learning/sources.yaml +2 -8
  77. package/agents/roles/system/machine-learning-engineer/SKILL.md +2 -0
  78. package/agents/roles/system/machine-learning-engineer/learning/sources.yaml +2 -4
  79. package/agents/roles/system/mlops-engineer/SKILL.md +2 -0
  80. package/agents/roles/system/mlops-engineer/learning/sources.yaml +2 -2
  81. package/agents/roles/system/mobile-engineer/SKILL.md +2 -0
  82. package/agents/roles/system/mobile-engineer/learning/sources.yaml +2 -4
  83. package/agents/roles/system/partnerships-manager/SKILL.md +2 -0
  84. package/agents/roles/system/partnerships-manager/learning/sources.yaml +2 -4
  85. package/agents/roles/system/people-operations-manager/SKILL.md +2 -0
  86. package/agents/roles/system/people-operations-manager/learning/sources.yaml +2 -4
  87. package/agents/roles/system/privacy-compliance-specialist/SKILL.md +2 -0
  88. package/agents/roles/system/privacy-compliance-specialist/learning/sources.yaml +2 -8
  89. package/agents/roles/system/procurement-manager/SKILL.md +2 -0
  90. package/agents/roles/system/procurement-manager/learning/sources.yaml +2 -4
  91. package/agents/roles/system/product-manager/SKILL.md +2 -0
  92. package/agents/roles/system/product-manager/learning/sources.yaml +2 -0
  93. package/agents/roles/system/product-marketing-manager/SKILL.md +2 -0
  94. package/agents/roles/system/product-marketing-manager/learning/sources.yaml +2 -8
  95. package/agents/roles/system/project-manager/SKILL.md +2 -0
  96. package/agents/roles/system/project-manager/learning/sources.yaml +2 -4
  97. package/agents/roles/system/qa-engineer/SKILL.md +2 -0
  98. package/agents/roles/system/qa-engineer/learning/sources.yaml +2 -4
  99. package/agents/roles/system/release-manager/SKILL.md +2 -0
  100. package/agents/roles/system/release-manager/learning/sources.yaml +2 -4
  101. package/agents/roles/system/revenue-operations-manager/SKILL.md +2 -0
  102. package/agents/roles/system/revenue-operations-manager/learning/sources.yaml +2 -4
  103. package/agents/roles/system/sales-representative/SKILL.md +2 -0
  104. package/agents/roles/system/sales-representative/learning/sources.yaml +2 -8
  105. package/agents/roles/system/security-engineer/SKILL.md +2 -0
  106. package/agents/roles/system/security-engineer/learning/sources.yaml +2 -4
  107. package/agents/roles/system/site-reliability-engineer/SKILL.md +2 -0
  108. package/agents/roles/system/site-reliability-engineer/learning/sources.yaml +2 -4
  109. package/agents/roles/system/software-architect/SKILL.md +2 -0
  110. package/agents/roles/system/software-architect/learning/sources.yaml +2 -4
  111. package/agents/roles/system/solutions-engineer/SKILL.md +2 -0
  112. package/agents/roles/system/solutions-engineer/learning/sources.yaml +2 -4
  113. package/agents/roles/system/technical-program-manager/SKILL.md +2 -0
  114. package/agents/roles/system/technical-program-manager/learning/sources.yaml +2 -1
  115. package/agents/roles/system/technical-writer/SKILL.md +2 -0
  116. package/agents/roles/system/technical-writer/learning/sources.yaml +2 -4
  117. package/agents/roles/system/ui-designer/SKILL.md +2 -0
  118. package/agents/roles/system/ui-designer/learning/sources.yaml +2 -0
  119. package/agents/roles/system/user-researcher/SKILL.md +2 -0
  120. package/agents/roles/system/user-researcher/learning/sources.yaml +2 -0
  121. package/agents/roles/system/ux-designer/SKILL.md +2 -0
  122. package/agents/roles/system/ux-designer/learning/sources.yaml +2 -0
  123. package/automatization/workflows/autobuild.js +73 -11
  124. package/automatization/workflows/team.js +59 -6
  125. package/engine/agents/catalog.js +45 -20
  126. package/engine/agents/learning.js +19 -2
  127. package/engine/cli/ops.js +72 -18
  128. package/engine/core/manifest.js +54 -0
  129. package/engine/core/ownership.js +68 -21
  130. package/engine/teams/registry.js +23 -3
  131. package/package.json +1 -1
  132. package/teams/000-template.md +110 -0
  133. package/teams/README.md +60 -0
  134. package/teams/system/feasibility-review/WORKFLOW.md +60 -0
  135. package/teams/system/feasibility-review/team.json +73 -0
  136. package/teams/system/incident-review/WORKFLOW.md +65 -0
  137. package/teams/system/incident-review/team.json +72 -0
  138. package/teams/system/product-development/team.json +71 -17
  139. package/template/AGENTS.md +11 -0
  140. package/template/README.md +1 -1
  141. package/template/organization/roles/README.md +37 -0
  142. package/template/planning/reports/README.md +11 -0
  143. package/agents/coordinators/.gitkeep +0 -1
  144. package/agents/specialists/.gitkeep +0 -1
  145. 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, PROJECT_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) del runtime fueron editados y se perderían.\n` +
510
- 'Movelos junto a system/ como regla propia, o repetí con --force para descartarlos.',
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
- // Una instancia que toma el motor de npm no debe recuperar la copia: la actualiza el lockfile.
519
- if (relative === '.ops/engine' && !fs.existsSync(target)) continue
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 }
@@ -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.startsWith('planning/')) return path.join('template', 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
- // Archivos del runtime que el proyecto editó: existen en las dos partes y difieren. Un archivo
136
- // que sólo existe en la instancia es un agregado propio, no una edición, y sobrevive intacto.
137
- function localChanges(root, packageRoot) {
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 RUNTIME_PATHS) {
140
- const from = path.join(packageRoot, sourceOf(target))
141
- const to = path.join(root, target)
142
- if (!fs.existsSync(to) || !fs.existsSync(from)) continue
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,
@@ -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
- return path.join(root, 'teams', 'system', slug, 'team.json')
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 teams = path.join(root, 'teams')
87
- const slugs = new Set([...slugsIn(teams), ...slugsIn(path.join(teams, 'system'))])
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -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.
@@ -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.