@ingeniomaps/cauce 0.3.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 +30 -0
- package/README.md +16 -1
- 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 +2 -0
- package/agents/roles/system/finops-engineer/learning/sources.yaml +2 -8
- 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 +2 -0
- package/agents/roles/system/growth-marketer/learning/sources.yaml +2 -8
- 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/engine/agents/catalog.js +45 -20
- package/engine/agents/learning.js +19 -2
- package/engine/cli/ops.js +67 -21
- package/engine/core/manifest.js +54 -0
- package/engine/core/ownership.js +68 -21
- package/engine/teams/registry.js +12 -3
- package/package.json +1 -1
- package/template/organization/roles/README.md +37 -0
|
@@ -11,6 +11,8 @@ Actuar como responsable del lenguaje visual de la interfaz y su aplicación sist
|
|
|
11
11
|
|
|
12
12
|
1. Localizar la raíz operativa del proyecto.
|
|
13
13
|
2. Leer `AGENTS.md`, `ops.config.json` y `organization/README.md` si existen.
|
|
14
|
+
Leer también `organization/roles/ui-designer.md` si existe: son las restricciones reales de
|
|
15
|
+
esta empresa para este cargo.
|
|
14
16
|
3. Leer `organization/company.md`, `organization/product.md` y cualquier guía de marca o design system autorizado.
|
|
15
17
|
4. Leer el flujo y criterios entregados por UX Designer, además de evidencia relevante y restricciones confirmadas.
|
|
16
18
|
5. Inspeccionar componentes, tokens y convenciones existentes antes de crear otros. Separar hechos, restricciones y supuestos. No inventar marca, usuarios, métricas ni evidencia observable.
|
|
@@ -5,6 +5,8 @@ rules:
|
|
|
5
5
|
reject_unsourced_claims: true
|
|
6
6
|
automatic_apply: false
|
|
7
7
|
|
|
8
|
+
# El contexto de la empresa no es una fuente de la profesión: vive en
|
|
9
|
+
# organization/roles/ui-designer.md dentro de cada instalación.
|
|
8
10
|
sources:
|
|
9
11
|
- name: W3C Web Accessibility Initiative
|
|
10
12
|
url: https://www.w3.org/WAI/
|
|
@@ -11,6 +11,8 @@ Actuar como responsable de producir evidencia confiable sobre usuarios. Explicar
|
|
|
11
11
|
|
|
12
12
|
1. Localizar la raíz operativa del proyecto.
|
|
13
13
|
2. Leer `AGENTS.md`, `ops.config.json` y `organization/README.md` si existen.
|
|
14
|
+
Leer también `organization/roles/user-researcher.md` si existe: son las restricciones reales de
|
|
15
|
+
esta empresa para este cargo.
|
|
14
16
|
3. Leer `organization/company.md` y `organization/product.md`. Consultar políticas de privacidad, seguridad, accesibilidad y salvaguarda cuando apliquen.
|
|
15
17
|
4. Leer sólo el estado de `planning/` necesario para comprender la decisión que la investigación debe informar.
|
|
16
18
|
5. Clasificar cada entrada como dato original, observación, interpretación, inferencia, supuesto o pregunta.
|
|
@@ -5,6 +5,8 @@ rules:
|
|
|
5
5
|
reject_unsourced_claims: true
|
|
6
6
|
automatic_apply: false
|
|
7
7
|
|
|
8
|
+
# El contexto de la empresa no es una fuente de la profesión: vive en
|
|
9
|
+
# organization/roles/user-researcher.md dentro de cada instalación.
|
|
8
10
|
sources:
|
|
9
11
|
- name: GOV.UK User Research
|
|
10
12
|
url: https://www.gov.uk/service-manual/user-research
|
|
@@ -11,6 +11,8 @@ Actuar como responsable de la estructura y comportamiento de la experiencia. Red
|
|
|
11
11
|
|
|
12
12
|
1. Localizar la raíz operativa del proyecto.
|
|
13
13
|
2. Leer `AGENTS.md`, `ops.config.json` y `organization/README.md` si existen.
|
|
14
|
+
Leer también `organization/roles/ux-designer.md` si existe: son las restricciones reales de
|
|
15
|
+
esta empresa para este cargo.
|
|
14
16
|
3. Leer `organization/company.md`, `organization/product.md` y las reglas de diseño o accesibilidad disponibles.
|
|
15
17
|
4. Consultar evidencia del User Researcher y decisiones aprobadas del Product Manager. Leer el estado de `planning/` necesario.
|
|
16
18
|
5. Separar hechos, evidencia, restricciones, supuestos y decisiones pendientes. No inventar investigación, usuarios, métricas ni evidencia observable.
|
|
@@ -5,6 +5,8 @@ rules:
|
|
|
5
5
|
reject_unsourced_claims: true
|
|
6
6
|
automatic_apply: false
|
|
7
7
|
|
|
8
|
+
# El contexto de la empresa no es una fuente de la profesión: vive en
|
|
9
|
+
# organization/roles/ux-designer.md dentro de cada instalación.
|
|
8
10
|
sources:
|
|
9
11
|
- name: W3C Web Accessibility Initiative
|
|
10
12
|
url: https://www.w3.org/WAI/
|
package/engine/agents/catalog.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
// Resolución única del catálogo de cargos. La usan el ciclo de aprendizaje, la validación de
|
|
4
|
-
// equipos y la instalación en runners
|
|
4
|
+
// equipos y la instalación en runners.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// El catálogo se reparte en dos lugares según de quién es cada cosa:
|
|
7
7
|
//
|
|
8
|
-
// agents/<tipo>/system/<slug>/
|
|
9
|
-
// agents/<tipo>/<slug>/
|
|
8
|
+
// <paquete>/agents/<tipo>/system/<slug>/ viene con Cauce, se actualiza con la dependencia
|
|
9
|
+
// <proyecto>/agents/<tipo>/<slug>/ es de la empresa y manda sobre el anterior
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
11
|
+
// Los cargos del sistema no se copian al proyecto: evolucionan como profesión y esa evolución es
|
|
12
|
+
// la misma para todos. Lo que sí es de cada empresa —su contexto— vive en `organization/roles/`.
|
|
13
|
+
//
|
|
14
|
+
// Un slug repetido entre tipos distintos sigue siendo ambiguo: ahí no hay ninguna regla que diga
|
|
15
|
+
// cuál gana, y elegir en silencio sería peor que fallar.
|
|
13
16
|
|
|
14
17
|
const fs = require('node:fs')
|
|
15
18
|
const path = require('node:path')
|
|
@@ -24,44 +27,66 @@ function directories(dir) {
|
|
|
24
27
|
} catch { return [] }
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
// Dónde está el catálogo que trae Cauce. Se reconoce por tener `roles/system/`, que es el espacio
|
|
31
|
+
// del sistema y no algo que un proyecto deba crear.
|
|
32
|
+
function systemCatalog(root) {
|
|
33
|
+
return require('../core/ownership').packageDir(root, 'agents')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function projectCatalog(root) {
|
|
37
|
+
return path.join(root, 'agents')
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
function types(root) {
|
|
28
|
-
|
|
41
|
+
const own = directories(projectCatalog(root))
|
|
42
|
+
const system = directories(systemCatalog(root) || path.join(root, '\0'))
|
|
43
|
+
return [...new Set([...own, ...system])]
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
// Todos los cargos visibles: los
|
|
46
|
+
// Todos los cargos visibles: los de la empresa ocultan a los del sistema con el mismo slug.
|
|
32
47
|
function list(root) {
|
|
33
|
-
const
|
|
48
|
+
const system = systemCatalog(root)
|
|
34
49
|
const found = new Map()
|
|
35
50
|
for (const type of types(root)) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
// El sistema primero, para que el proyecto lo sobrescriba al pasar por encima.
|
|
52
|
+
const sources = [
|
|
53
|
+
system ? [path.join(system, type, 'system'), true] : null,
|
|
54
|
+
[path.join(projectCatalog(root), type), false],
|
|
55
|
+
].filter(Boolean)
|
|
56
|
+
for (const [source, fromSystem] of sources) {
|
|
39
57
|
for (const slug of directories(source)) {
|
|
40
58
|
if (slug === 'system' || !SLUG.test(slug)) continue
|
|
41
59
|
const dir = path.join(source, slug)
|
|
42
60
|
if (!fs.existsSync(path.join(dir, 'SKILL.md'))) continue
|
|
43
|
-
found.set(slug, { slug, type, dir, system })
|
|
61
|
+
found.set(slug, { slug, type, dir, system: fromSystem })
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
64
|
}
|
|
47
65
|
return [...found.values()].sort((left, right) => left.slug.localeCompare(right.slug))
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
function
|
|
68
|
+
function find(root, slug) {
|
|
51
69
|
if (!SLUG.test(slug || '')) throw new Error(`agente inválido: ${slug || '(vacío)'}`)
|
|
70
|
+
const system = systemCatalog(root)
|
|
52
71
|
const matches = []
|
|
53
72
|
for (const type of types(root)) {
|
|
54
|
-
const own = path.join(root,
|
|
55
|
-
const
|
|
73
|
+
const own = path.join(projectCatalog(root), type, slug)
|
|
74
|
+
const shipped = system ? path.join(system, type, 'system', slug) : ''
|
|
56
75
|
// Dentro de un tipo la precedencia está definida; entre tipos no, y por eso se acumulan.
|
|
57
|
-
if (fs.existsSync(path.join(own, 'SKILL.md'))) matches.push(own)
|
|
58
|
-
else if (fs.existsSync(path.join(
|
|
76
|
+
if (fs.existsSync(path.join(own, 'SKILL.md'))) matches.push({ dir: own, system: false })
|
|
77
|
+
else if (shipped && fs.existsSync(path.join(shipped, 'SKILL.md'))) {
|
|
78
|
+
matches.push({ dir: shipped, system: true })
|
|
79
|
+
}
|
|
59
80
|
}
|
|
60
81
|
if (!matches.length) throw new Error(`no existe agents/<tipo>/${slug}/SKILL.md`)
|
|
61
82
|
if (matches.length > 1) {
|
|
62
|
-
throw new Error(`agente ambiguo ${slug}: ${matches.map((
|
|
83
|
+
throw new Error(`agente ambiguo ${slug}: ${matches.map((entry) => entry.dir).join(', ')}`)
|
|
63
84
|
}
|
|
64
85
|
return matches[0]
|
|
65
86
|
}
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
function resolve(root, slug) {
|
|
89
|
+
return find(root, slug).dir
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { find, list, projectCatalog, resolve, systemCatalog, types }
|
|
@@ -19,11 +19,28 @@ function agentRoot(root, agent) {
|
|
|
19
19
|
return catalog.resolve(root, agent)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Un cargo del sistema vive dentro del paquete: escribir ahí perdería el informe en el próximo
|
|
23
|
+
// `npm ci`, y además duplicaría en cada empresa una investigación sobre la profesión que se hace
|
|
24
|
+
// mejor una sola vez. Lo que sí es de esta empresa es su contexto, y ese tiene otro lugar.
|
|
25
|
+
function assertWritable(root, agent) {
|
|
26
|
+
const found = catalog.find(root, agent)
|
|
27
|
+
// Lo que decide no es si el cargo es del sistema, sino si vive dentro de este repositorio. En el
|
|
28
|
+
// toolkit los cargos del sistema son propios y se aprenden acá; en una instancia vienen del
|
|
29
|
+
// paquete, y escribir ahí se pierde en el próximo `npm ci` o en el próximo upgrade.
|
|
30
|
+
const own = path.resolve(catalog.projectCatalog(root))
|
|
31
|
+
if (path.resolve(found.dir).startsWith(`${own}${path.sep}`)) return found.dir
|
|
32
|
+
throw new Error(
|
|
33
|
+
`${agent} es un cargo que trae Cauce y su aprendizaje se hace en el toolkit, no acá.\n` +
|
|
34
|
+
` Lo que este cargo debe saber de esta empresa va en organization/roles/${agent}.md.\n` +
|
|
35
|
+
` Para tener una versión propia del cargo, escribila en agents/roles/${agent}/.`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
function isoDate(now = new Date()) { return now.toISOString().slice(0, 10) }
|
|
23
40
|
function month(now = new Date()) { return now.toISOString().slice(0, 7) }
|
|
24
41
|
|
|
25
42
|
function prepareReport(root, agent, now = new Date()) {
|
|
26
|
-
const reports = path.join(
|
|
43
|
+
const reports = path.join(assertWritable(root, agent), 'learning', 'reports')
|
|
27
44
|
const file = path.join(reports, `${isoDate(now)}.md`)
|
|
28
45
|
fs.mkdirSync(reports, { recursive: true })
|
|
29
46
|
if (fs.existsSync(file)) return { file, created: false }
|
|
@@ -51,7 +68,7 @@ status: draft
|
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
function prepareProposal(root, agent, now = new Date()) {
|
|
54
|
-
const target =
|
|
71
|
+
const target = assertWritable(root, agent)
|
|
55
72
|
const proposalDir = path.join(target, 'learning', 'proposals')
|
|
56
73
|
const file = path.join(proposalDir, `${month(now)}.md`)
|
|
57
74
|
fs.mkdirSync(proposalDir, { recursive: true })
|
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,14 +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
|
-
'
|
|
511
|
-
'guard propio sobrevive a cada actualización— y registralo en la configuración de tu runner,\n' +
|
|
512
|
-
'que sí es del proyecto. Para desactivar un guard alcanza con quitarlo de esa configuración.\n' +
|
|
513
|
-
'Si el cambio ya no te sirve, repetí con --force para descartarlo.',
|
|
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.`,
|
|
514
539
|
)
|
|
515
540
|
}
|
|
516
541
|
|
|
@@ -518,8 +543,9 @@ function upgrade(dir) {
|
|
|
518
543
|
const origin = path.join(PROJECT_ROOT, O.sourceOf(relative))
|
|
519
544
|
if (!fs.existsSync(origin)) continue
|
|
520
545
|
const target = path.join(root, relative)
|
|
521
|
-
//
|
|
522
|
-
|
|
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
|
|
523
549
|
const skip = O.TEMPLATE_OWNED
|
|
524
550
|
.filter((owned) => owned.startsWith(`${relative}/`))
|
|
525
551
|
.map((owned) => path.basename(owned))
|
|
@@ -530,6 +556,25 @@ function upgrade(dir) {
|
|
|
530
556
|
}
|
|
531
557
|
}
|
|
532
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
|
+
|
|
533
578
|
const config = JSON.parse(fs.readFileSync(path.join(root, 'ops.config.json'), 'utf8'))
|
|
534
579
|
config.cauceVersion = to
|
|
535
580
|
F.atomicWriteJson(path.join(root, 'ops.config.json'), config)
|
|
@@ -538,6 +583,7 @@ function upgrade(dir) {
|
|
|
538
583
|
// Descartar con --force es legítimo; hacerlo sin dejar rastro no. Queda en la salida del comando,
|
|
539
584
|
// que es la evidencia que el protocolo pide para cualquier cambio.
|
|
540
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`)
|
|
541
587
|
printChangelog(from, to)
|
|
542
588
|
console.log(` ${system.length} ruta(s) del sistema y ${O.RUNTIME_PATHS.length} del runtime actualizadas`)
|
|
543
589
|
for (const override of overrides) {
|
|
@@ -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
|
@@ -12,11 +12,16 @@ const OUTCOMES = ['epic', 'report']
|
|
|
12
12
|
|
|
13
13
|
// El proyecto manda sobre el sistema: un team propio con el mismo slug reemplaza al de `system/`,
|
|
14
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
|
+
|
|
15
19
|
function teamFile(root, slug) {
|
|
16
20
|
if (!SLUG.test(slug || '')) throw new Error(`team inválido: ${slug || '(vacío)'}`)
|
|
17
21
|
const own = path.join(root, 'teams', slug, 'team.json')
|
|
18
22
|
if (fs.existsSync(own)) return own
|
|
19
|
-
|
|
23
|
+
const system = systemTeams(root)
|
|
24
|
+
return system ? path.join(system, 'system', slug, 'team.json') : own
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
// Devuelve el motivo real: "no existe" y "ambiguo" piden acciones distintas de quien lo lea.
|
|
@@ -93,9 +98,13 @@ function slugsIn(dir) {
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
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.
|
|
96
102
|
function list(root) {
|
|
97
|
-
const
|
|
98
|
-
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
|
+
])
|
|
99
108
|
return [...slugs].sort()
|
|
100
109
|
}
|
|
101
110
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Contexto por cargo
|
|
2
|
+
|
|
3
|
+
Un cargo del catálogo sabe de su profesión, no de esta empresa. Acá va lo que necesita saber para
|
|
4
|
+
trabajar en **este** contexto: `organization/roles/<slug>.md`.
|
|
5
|
+
|
|
6
|
+
```markdown
|
|
7
|
+
# QA Engineer en Aparatejo
|
|
8
|
+
|
|
9
|
+
No hay ambiente de staging: la verificación corre contra el entorno local y contra producción con
|
|
10
|
+
datos de prueba. No hay equipo de soporte, así que un defecto que llegue a producción lo paga el
|
|
11
|
+
tiempo de las cuatro personas del equipo.
|
|
12
|
+
|
|
13
|
+
Cada imagen generada cuesta plata real: una prueba que dispare generación tiene que declarar cuántas.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Qué va acá y qué no
|
|
17
|
+
|
|
18
|
+
**Acá**: restricciones reales de esta empresa, herramientas que usa, quién decide qué, límites de
|
|
19
|
+
presupuesto, deudas conocidas, lo que ya se intentó y no funcionó.
|
|
20
|
+
|
|
21
|
+
**Acá no**: cómo se hace la profesión. Que exista una técnica nueva de testing o que cambie una norma
|
|
22
|
+
le pasa a todas las empresas por igual: eso vive en el catálogo y llega actualizando Cauce.
|
|
23
|
+
|
|
24
|
+
## Por qué está separado
|
|
25
|
+
|
|
26
|
+
Los cargos que trae Cauce viven en el paquete y se actualizan con él. Si el contexto de tu empresa
|
|
27
|
+
estuviera escrito dentro de un cargo del catálogo, cada actualización te obligaría a elegir entre
|
|
28
|
+
perder tu contexto o congelar el cargo.
|
|
29
|
+
|
|
30
|
+
Separados, las dos cosas mejoran sin pisarse: la profesión la mantiene el toolkit, el contexto lo
|
|
31
|
+
mantenés vos.
|
|
32
|
+
|
|
33
|
+
## Si necesitás cambiar el cargo, no sólo darle contexto
|
|
34
|
+
|
|
35
|
+
Escribí tu propia versión en `agents/roles/<slug>/`, con el mismo slug: reemplaza a la del catálogo.
|
|
36
|
+
Tené en cuenta que a partir de ahí ese cargo deja de recibir mejoras, así que conviene sólo cuando el
|
|
37
|
+
contrato en sí no te sirve —no cuando lo que falta es contexto—.
|