@ingeniomaps/cauce 0.2.0 → 0.3.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 +49 -0
- package/README.md +9 -2
- package/agents/roles/system/finops-engineer/SKILL.md +81 -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 +33 -0
- package/agents/roles/system/finops-engineer/references/operating-model.md +70 -0
- package/agents/roles/system/growth-marketer/SKILL.md +77 -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 +33 -0
- package/agents/roles/system/growth-marketer/references/operating-model.md +76 -0
- package/automatization/workflows/autobuild.js +73 -11
- package/automatization/workflows/team.js +59 -6
- package/engine/cli/ops.js +10 -2
- package/engine/teams/registry.js +11 -0
- 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/planning/reports/README.md +11 -0
- package/agents/coordinators/.gitkeep +0 -1
- package/agents/specialists/.gitkeep +0 -1
- package/agents/workflows/.gitkeep +0 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: data-analyst
|
|
3
|
+
period: YYYY-MM
|
|
4
|
+
status: proposed
|
|
5
|
+
automatic_apply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Propuesta mensual — YYYY-MM
|
|
9
|
+
|
|
10
|
+
## Hallazgos
|
|
11
|
+
## Evidencia
|
|
12
|
+
## Cambio propuesto
|
|
13
|
+
## Riesgos y regresiones
|
|
14
|
+
## Evaluación
|
|
15
|
+
## Aprobación humana
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
agent: data-analyst
|
|
3
|
+
date: YYYY-MM-DD
|
|
4
|
+
status: draft
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Investigación profesional semanal — YYYY-MM-DD
|
|
8
|
+
|
|
9
|
+
## Fuentes consultadas
|
|
10
|
+
## Hallazgos
|
|
11
|
+
## Evidencia
|
|
12
|
+
## Posibles prácticas obsoletas
|
|
13
|
+
## Recomendación
|
|
14
|
+
## Preguntas abiertas
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
rules:
|
|
3
|
+
require_primary_source: true
|
|
4
|
+
require_version_applicability: true
|
|
5
|
+
require_corroboration_for_major_change: true
|
|
6
|
+
reject_unsourced_claims: true
|
|
7
|
+
automatic_apply: false
|
|
8
|
+
|
|
9
|
+
sources:
|
|
10
|
+
- name: Company funnel definitions cohorts spend and decisions
|
|
11
|
+
url: local://growth-context
|
|
12
|
+
tier: company-primary
|
|
13
|
+
topics: [funnel, cohorts, spend, decisions]
|
|
14
|
+
- name: Official advertising and analytics platform documentation
|
|
15
|
+
url: local://growth-stack-docs
|
|
16
|
+
tier: platform-primary
|
|
17
|
+
topics: [attribution, tracking, audiences, reporting]
|
|
18
|
+
- name: NIST Statistical Methods Handbook
|
|
19
|
+
url: https://www.itl.nist.gov/div898/handbook/
|
|
20
|
+
tier: primary-method
|
|
21
|
+
topics: [experiments, uncertainty, sampling, inference]
|
|
22
|
+
- name: ICC Advertising and Marketing Communications Code
|
|
23
|
+
url: https://iccwbo.org/business-codes/advertising-and-marketing-communications-code/
|
|
24
|
+
tier: professional-primary
|
|
25
|
+
topics: [claims, honesty, substantiation, disclosure]
|
|
26
|
+
- name: EDPB Guidelines on targeting and consent
|
|
27
|
+
url: https://www.edpb.europa.eu/our-work-tools/general-guidance/guidelines-recommendations-best-practices_en
|
|
28
|
+
tier: regulator-primary
|
|
29
|
+
topics: [privacy, consent, targeting, transfers]
|
|
30
|
+
- name: FTC Truth in Advertising
|
|
31
|
+
url: https://www.ftc.gov/business-guidance/advertising-marketing
|
|
32
|
+
tier: regulator-primary
|
|
33
|
+
topics: [claims, endorsements, substantiation, disclosure]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Modelo operativo de Growth Marketing
|
|
2
|
+
|
|
3
|
+
## Contrato de embudo
|
|
4
|
+
|
|
5
|
+
Antes de proponer inversión, cada etapa queda definida con la misma precisión que una métrica:
|
|
6
|
+
|
|
7
|
+
- **Población**: quién entra y quién queda excluido, con la regla explícita.
|
|
8
|
+
- **Etapas**: impresión, clic, visita, registro, activación y retención temprana, cada una con su
|
|
9
|
+
evento de origen y su ventana.
|
|
10
|
+
- **Denominador**: contra qué se divide cada tasa. La mayoría de las discusiones de conversión son
|
|
11
|
+
desacuerdos sobre el denominador.
|
|
12
|
+
- **Ventana de atribución**: declarada y constante entre comparaciones. Cambiarla a mitad de un análisis
|
|
13
|
+
invalida la serie.
|
|
14
|
+
|
|
15
|
+
Una tasa sin población, ventana y denominador no es una métrica: es una opinión con decimales.
|
|
16
|
+
|
|
17
|
+
## Economía unitaria
|
|
18
|
+
|
|
19
|
+
Toda propuesta de inversión reporta, con supuestos visibles:
|
|
20
|
+
|
|
21
|
+
- costo por resultado en la etapa que se está comprando;
|
|
22
|
+
- costo de adquisición hasta el usuario activado, no hasta el registro;
|
|
23
|
+
- margen de contribución y período de recuperación;
|
|
24
|
+
- qué pasa si la conversión cae un tercio: si el caso sólo cierra en el escenario optimista, no cierra.
|
|
25
|
+
|
|
26
|
+
El valor de vida se estima con cohortes observadas, no proyectando indefinidamente la retención del
|
|
27
|
+
mejor mes.
|
|
28
|
+
|
|
29
|
+
## Diseño de experimentos
|
|
30
|
+
|
|
31
|
+
Antes de lanzar se escriben: hipótesis falsable, unidad de asignación, métrica primaria, métricas
|
|
32
|
+
guardia, tamaño mínimo, duración y regla de decisión. Después de lanzar no se cambian.
|
|
33
|
+
|
|
34
|
+
- Una sola métrica primaria. Varias métricas primarias es no tener ninguna.
|
|
35
|
+
- Las métricas guardia protegen lo que el experimento podría romper: calidad del registro, carga de
|
|
36
|
+
soporte, tasa de reembolso, retención.
|
|
37
|
+
- Mirar resultados antes de tiempo y cortar al ver un número favorable produce ganadores falsos.
|
|
38
|
+
- Un resultado sin efecto también es un resultado: se registra, con el tamaño que se podría haber
|
|
39
|
+
detectado.
|
|
40
|
+
|
|
41
|
+
## Atribución
|
|
42
|
+
|
|
43
|
+
Ningún modelo de atribución es verdad. Se elige uno, se declara, y se lo trata como una lente:
|
|
44
|
+
|
|
45
|
+
- Las cifras que reporta una plataforma publicitaria son de parte interesada y suelen sobreatribuir.
|
|
46
|
+
- Se reconcilian contra datos propios; cuando la diferencia es grande, se reporta la diferencia en vez
|
|
47
|
+
de elegir el número conveniente.
|
|
48
|
+
- Para decisiones grandes, contrastar con una prueba de incrementalidad —geo, holdout— antes que
|
|
49
|
+
discutir modelos.
|
|
50
|
+
|
|
51
|
+
## Contenido y promesa
|
|
52
|
+
|
|
53
|
+
Lo que se afirma en un anuncio o una landing es un compromiso del producto:
|
|
54
|
+
|
|
55
|
+
- La promesa la valida quien es dueño del producto; el mensaje no crea capacidades.
|
|
56
|
+
- Afirmaciones sobre resultados, seguridad, cumplimiento o comparaciones con competidores pasan por
|
|
57
|
+
legal antes de publicarse.
|
|
58
|
+
- Un embudo que convierte porque promete de más traslada el costo a soporte y a la retención.
|
|
59
|
+
|
|
60
|
+
## Privacidad
|
|
61
|
+
|
|
62
|
+
- Minimizar datos personales: recolectar lo necesario para la decisión, no lo que sea posible.
|
|
63
|
+
- Verificar base legal antes de construir audiencias, sincronizar listas o activar remarketing.
|
|
64
|
+
- Coordinar con el especialista de privacidad cualquier transferencia a una plataforma externa.
|
|
65
|
+
|
|
66
|
+
## Control de calidad
|
|
67
|
+
|
|
68
|
+
Antes de entregar, verificar que la propuesta declara baseline, denominadores, ventana, regla de
|
|
69
|
+
decisión, supuestos e incertidumbre; que el gasto tiene autorización registrada; que las métricas
|
|
70
|
+
guardia existen; y que un resultado negativo también deja un aprendizaje utilizable.
|
|
71
|
+
|
|
72
|
+
## Fundamento externo
|
|
73
|
+
|
|
74
|
+
Los métodos de experimentación y medición se apoyan en fuentes primarias de estadística y en la
|
|
75
|
+
documentación oficial de cada plataforma, con su versión y fecha. Un caso de éxito publicado por un
|
|
76
|
+
proveedor es material comercial, no evidencia.
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Descubre proyecto, servicios y límites desde ops.config.json; no codifica rutas ni proveedores.
|
|
3
3
|
export const meta = {
|
|
4
4
|
name: 'autobuild',
|
|
5
|
-
description: 'Triage → Pick → Ready → Plan → Build → Review → Verify → QA → Commit → Done',
|
|
5
|
+
description: 'Triage → Pick → Cast → Ready → Plan → Build → Review → Verify → QA → Commit → Done',
|
|
6
6
|
whenToUse: 'Ejecutar un hito aprobado con recuperación por WIP y checkpoint humano entre hitos.',
|
|
7
7
|
phases: [
|
|
8
|
-
'Triage', 'Pick', 'Ready', 'Decompose', 'Plan', 'Critique', 'Build', 'Review',
|
|
8
|
+
'Triage', 'Pick', 'Cast', 'Ready', 'Decompose', 'Plan', 'Critique', 'Build', 'Review',
|
|
9
9
|
'Verify', 'QA', 'Commit', 'Done', 'Closing',
|
|
10
10
|
].map((title) => ({ title, detail: `Fase ${title} del protocolo agnóstico` })),
|
|
11
11
|
}
|
|
@@ -81,6 +81,31 @@ const COMMIT = {
|
|
|
81
81
|
branch: { type: 'string' }, leftovers: { type: 'array', items: { type: 'string' } }, reason: { type: 'string' },
|
|
82
82
|
},
|
|
83
83
|
}
|
|
84
|
+
// Dueño por defecto de cada fase. Es determinista: no hace falta preguntarle a un modelo quién
|
|
85
|
+
// revisa la arquitectura o quién decide si la evidencia de calidad alcanza.
|
|
86
|
+
const OWNERS = {
|
|
87
|
+
ready: 'product-manager',
|
|
88
|
+
plan: 'software-architect',
|
|
89
|
+
review: 'software-architect',
|
|
90
|
+
verify: 'qa-engineer',
|
|
91
|
+
qa: 'qa-engineer',
|
|
92
|
+
commit: 'release-manager',
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const CAST_RULES = 'Elegís quién trabaja, no qué se hace. No inventes slugs: usá sólo los que ' +
|
|
96
|
+
'devuelve el CLI. Un cargo se suma por riesgo, plataforma o alcance, nunca por rutina.'
|
|
97
|
+
|
|
98
|
+
const CAST = {
|
|
99
|
+
type: 'object', additionalProperties: false, required: ['build'],
|
|
100
|
+
properties: {
|
|
101
|
+
build: { type: 'string' },
|
|
102
|
+
review: { type: 'array', items: { type: 'string' } },
|
|
103
|
+
verify: { type: 'array', items: { type: 'string' } },
|
|
104
|
+
qa: { type: 'array', items: { type: 'string' } },
|
|
105
|
+
reason: { type: 'string' },
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
const CONTRACT = {
|
|
85
110
|
type: 'object', additionalProperties: false,
|
|
86
111
|
required: ['project', 'workspaceRoots', 'maxTaskHours', 'commitPerTask', 'humanCheckpoint', 'contracts'],
|
|
@@ -164,11 +189,41 @@ while (safety++ < 50) {
|
|
|
164
189
|
const direct = planning.lane === 'directo'
|
|
165
190
|
const lite = planning.lane === 'lite'
|
|
166
191
|
|
|
192
|
+
// Quién ejecuta cada fase. Los dueños por defecto son fijos y no gastan una llamada; los
|
|
193
|
+
// condicionales entran por riesgo, plataforma y alcance —nunca por rutina—, que es la misma
|
|
194
|
+
// regla que ya usa el team. En `directo` no hay condicionales: el lane baja ceremonia.
|
|
195
|
+
const cast = { ...OWNERS, build: '' }
|
|
196
|
+
if (!direct) {
|
|
197
|
+
phase('Cast')
|
|
198
|
+
const chosen = await read(
|
|
199
|
+
`${CAST_RULES}\n\nTarea ${task.id} en ${task.service}. Aceptación: ${task.acceptance}.\n` +
|
|
200
|
+
`Run "node tools/ops.js agents list ${ROOT} --json" and choose only from the slugs it lists.\n` +
|
|
201
|
+
`Elegí el cargo que implementa según la plataforma del servicio${lite ? '.' : ', y los cargos ' +
|
|
202
|
+
'condicionales que la superficie realmente justifica: seguridad si toca autenticación, permisos, ' +
|
|
203
|
+
'criptografía o datos sensibles; privacidad si toca datos personales; sre si toca disponibilidad, ' +
|
|
204
|
+
'límites o despliegue; ux si cambia una superficie que usa una persona; y el que corresponda a ' +
|
|
205
|
+
'datos o modelos si los toca. Dejá vacío lo que no aplique: sumar un cargo que no aporta es ruido ' +
|
|
206
|
+
'que diluye la revisión.'}`,
|
|
207
|
+
{ schema: CAST, label: 'cast' },
|
|
208
|
+
)
|
|
209
|
+
if (chosen) {
|
|
210
|
+
cast.build = chosen.build || ''
|
|
211
|
+
for (const key of ['review', 'verify', 'qa']) {
|
|
212
|
+
cast[key] = [OWNERS[key], ...(lite ? [] : chosen[key] || [])].filter(Boolean).join(', ')
|
|
213
|
+
}
|
|
214
|
+
log(`Cargos: build=${cast.build || '(sin asignar)'} · review=${cast.review} · qa=${cast.qa}`)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const asRole = (slugs) => (slugs
|
|
218
|
+
? `Actuá como ${slugs}, respetando el contrato de cada uno en su SKILL.md bajo agents/ y sus ` +
|
|
219
|
+
`límites: un cargo que no puede decidir solo, no decide solo.\n\n`
|
|
220
|
+
: '')
|
|
221
|
+
|
|
167
222
|
if (!planning.wipActive) {
|
|
168
223
|
phase('Ready')
|
|
169
224
|
const ready = await read(
|
|
170
|
-
|
|
171
|
-
|
|
225
|
+
`${asRole(OWNERS.ready)}Check concrete acceptance, dependencies and unresolved decisions for ${task.id}: ` +
|
|
226
|
+
`${task.acceptance}. Clarify wording only; never expand scope.`, { schema: READY },
|
|
172
227
|
)
|
|
173
228
|
if (!ready.ready) {
|
|
174
229
|
await write(`Register ${task.id} in ${HUMAN} with reason and an exact human action: ${ready.reason}.`)
|
|
@@ -192,7 +247,8 @@ while (safety++ < 50) {
|
|
|
192
247
|
|
|
193
248
|
phase('Plan')
|
|
194
249
|
let plan = await run(
|
|
195
|
-
|
|
250
|
+
`${asRole(OWNERS.plan)}Inspect real code, repository instructions, neighbouring conventions, epic context ` +
|
|
251
|
+
`and git status for ${task.id}. ` +
|
|
196
252
|
`Produce the smallest plan satisfying ${task.acceptance}. Planning files cannot be implementation files.`, { schema: PLAN },
|
|
197
253
|
)
|
|
198
254
|
if (!direct && !lite) {
|
|
@@ -209,13 +265,16 @@ while (safety++ < 50) {
|
|
|
209
265
|
}
|
|
210
266
|
await write(
|
|
211
267
|
`Persist active WIP before code: task=${task.id}, hito=${JSON.stringify(task.hito)}, phase=Build, service=${task.service}, ` +
|
|
212
|
-
`acceptance=${JSON.stringify(task.acceptance)}, unchecked steps=${JSON.stringify(plan.steps)}.
|
|
268
|
+
`acceptance=${JSON.stringify(task.acceptance)}, unchecked steps=${JSON.stringify(plan.steps)}. ` +
|
|
269
|
+
`Registrá además el reparto de cargos ${JSON.stringify(cast)} en las decisiones del WIP, para que ` +
|
|
270
|
+
`después se pueda auditar quién revisó qué. Follow the WIP contract exactly.`,
|
|
213
271
|
)
|
|
214
272
|
}
|
|
215
273
|
|
|
216
274
|
phase('Build')
|
|
217
275
|
const build = await run(
|
|
218
|
-
|
|
276
|
+
`${asRole(cast.build)}Implement only ${task.id} inside ${task.service}. Resume at the first pending WIP ` +
|
|
277
|
+
`step; verify completed steps on disk ` +
|
|
219
278
|
`and tick each successful step. Use RED/GREEN for behavior. Acceptance: ${task.acceptance}.`, {
|
|
220
279
|
schema: { type: 'object', required: ['completed', 'summary'], properties: {
|
|
221
280
|
completed: { type: 'boolean' }, summary: { type: 'string' }, blockers: { type: 'array', items: { type: 'string' } },
|
|
@@ -227,7 +286,8 @@ while (safety++ < 50) {
|
|
|
227
286
|
if (!direct) {
|
|
228
287
|
phase('Review')
|
|
229
288
|
let review = await run(
|
|
230
|
-
|
|
289
|
+
`${asRole(cast.review)}Review the actual diff for acceptance, regressions, security, architecture, ` +
|
|
290
|
+
`generated code, migrations and accidental scope. Cada cargo revisa su dominio, no el ajeno.`,
|
|
231
291
|
{ schema: DECISION },
|
|
232
292
|
)
|
|
233
293
|
if (!review.approved) {
|
|
@@ -239,14 +299,15 @@ while (safety++ < 50) {
|
|
|
239
299
|
|
|
240
300
|
phase('Verify')
|
|
241
301
|
const verified = await run(
|
|
242
|
-
|
|
302
|
+
`${asRole(cast.verify)}Discover and run the real gates for ${task.service}: repository instructions first, ` +
|
|
303
|
+
`then applicable test, lint, ` +
|
|
243
304
|
`typecheck and build. Read actual exit codes. passed=true needs commands and no task-caused regression.`, { schema: VERIFY },
|
|
244
305
|
)
|
|
245
306
|
if (!verified.passed || !verified.commands.length) return stop('verify-failed', verified.details)
|
|
246
307
|
|
|
247
308
|
phase('QA')
|
|
248
309
|
const qa = await run(
|
|
249
|
-
`${direct || lite ? 'Perform the cheapest real acceptance check' : 'Exercise real consumer-visible behavior'} for ` +
|
|
310
|
+
`${asRole(cast.qa)}${direct || lite ? 'Perform the cheapest real acceptance check' : 'Exercise real consumer-visible behavior'} for ` +
|
|
250
311
|
`${task.id}. Unit tests alone are not QA. Start only minimum runtime and tear it down. Acceptance: ${task.acceptance}.`,
|
|
251
312
|
{ schema: QA },
|
|
252
313
|
)
|
|
@@ -254,7 +315,8 @@ while (safety++ < 50) {
|
|
|
254
315
|
|
|
255
316
|
phase('Commit')
|
|
256
317
|
const commit = contract.commitPerTask ? await run(
|
|
257
|
-
|
|
318
|
+
`${asRole(OWNERS.commit)}Find the git repo owning ${task.service}, inspect status/diff, stage explicit task ` +
|
|
319
|
+
`files, create one Conventional ` +
|
|
258
320
|
`Commit with footer "Task: ${task.id}", then verify log/status. Never amend or push; report unrelated leftovers.`,
|
|
259
321
|
{ schema: COMMIT },
|
|
260
322
|
) : { committed: true, reason: 'runner.commitPerTask is disabled' }
|
|
@@ -20,14 +20,27 @@ const P = `${ROOT}/planning`
|
|
|
20
20
|
const ROADMAP = `${P}/roadmap`
|
|
21
21
|
const HUMAN = `${P}/HUMAN_ACTIONS.md`
|
|
22
22
|
const INBOX = `${P}/INBOX.md`
|
|
23
|
+
const REPORTS = `${P}/reports`
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// Tres formas de invocarlo, porque escribir JSON en un slash command no es razonable:
|
|
26
|
+
//
|
|
27
|
+
// /team quiero cobrar con tarjeta equipo por defecto
|
|
28
|
+
// /team feasibility-review: quiero cobrar con tarjeta equipo elegido por prefijo
|
|
29
|
+
// /team {"team": "acme-soporte", "intent": "..."} argumentos estructurados
|
|
30
|
+
//
|
|
31
|
+
// El prefijo se toma como candidato y se confirma más abajo contra los equipos que existen: si no es
|
|
32
|
+
// uno, el texto completo era la intención y nadie tuvo que aprenderse una sintaxis.
|
|
33
|
+
const input = typeof args === 'string' ? { intent: args } : (args || {})
|
|
34
|
+
const raw = String(input.intent || process.env.OPS_INTENT || '').trim()
|
|
35
|
+
const prefix = raw.match(/^([a-z][a-z0-9-]*)\s*:\s*(.+)$/s)
|
|
36
|
+
const CANDIDATE = String(input.team || process.env.OPS_TEAM || (prefix ? prefix[1] : '') || 'product-development')
|
|
37
|
+
const INTENT = (input.team || !prefix ? raw : prefix[2]).trim()
|
|
26
38
|
|
|
27
39
|
const MANIFEST = {
|
|
28
40
|
type: 'object', additionalProperties: false, required: ['name', 'purpose', 'stages', 'guardrails'],
|
|
29
41
|
properties: {
|
|
30
42
|
name: { type: 'string' }, purpose: { type: 'string' },
|
|
43
|
+
outcome: { type: 'string', enum: ['epic', 'report'] },
|
|
31
44
|
entryAgent: { type: 'string' }, facilitator: { type: 'string' },
|
|
32
45
|
guardrails: { type: 'array', items: { type: 'string' } },
|
|
33
46
|
owners: { type: 'array', items: { type: 'object', additionalProperties: false, properties: {
|
|
@@ -35,6 +48,7 @@ const MANIFEST = {
|
|
|
35
48
|
} } },
|
|
36
49
|
stages: { type: 'array', items: { type: 'object', additionalProperties: false, properties: {
|
|
37
50
|
id: { type: 'string' }, agent: { type: 'string' }, exitGate: { type: 'string' },
|
|
51
|
+
phase: { type: 'string', enum: ['discovery', 'delivery'] },
|
|
38
52
|
produces: { type: 'array', items: { type: 'string' } },
|
|
39
53
|
} } },
|
|
40
54
|
},
|
|
@@ -75,10 +89,27 @@ const BASE = `Nunca inventes clientes, métricas, restricciones ni decisiones. N
|
|
|
75
89
|
`haría falta averiguar. No promuevas trabajo al BACKLOG, no escribas en sistemas externos y no ` +
|
|
76
90
|
`declares validado nada sin evidencia observable.`
|
|
77
91
|
|
|
92
|
+
const resolved = await agent(
|
|
93
|
+
`${BASE}\n\nRun "node tools/ops.js team list" from ${ROOT}. Report whether "${CANDIDATE}" is one of ` +
|
|
94
|
+
`the slugs it printed, and the full list. Do not guess: report only what the command printed.`,
|
|
95
|
+
{ schema: { type: 'object', required: ['exists', 'teams'], properties: {
|
|
96
|
+
exists: { type: 'boolean' }, teams: { type: 'array', items: { type: 'string' } },
|
|
97
|
+
} }, label: 'team-resolve' },
|
|
98
|
+
)
|
|
99
|
+
if (!resolved) return stop('teams-unavailable', 'no se pudo listar los equipos')
|
|
100
|
+
if (!resolved.exists && (input.team || process.env.OPS_TEAM)) {
|
|
101
|
+
return stop('equipo-inexistente', `${CANDIDATE} no existe. Disponibles: ${resolved.teams.join(', ')}`)
|
|
102
|
+
}
|
|
103
|
+
// El prefijo era parte de la intención, no un equipo: se recompone y sigue con el equipo por defecto.
|
|
104
|
+
const TEAM = resolved.exists ? CANDIDATE : 'product-development'
|
|
105
|
+
const GOAL = resolved.exists ? INTENT : raw
|
|
106
|
+
if (!resolved.exists && prefix) log(`"${CANDIDATE}" no es un equipo: se toma el texto completo como intención.`)
|
|
107
|
+
|
|
78
108
|
const contract = await agent(
|
|
79
109
|
`${BASE}\n\nRun "node tools/ops.js team show ${TEAM} --json" from ${ROOT} and report only what it ` +
|
|
80
|
-
`printed: name, purpose, entryAgent, facilitator, guardrails, the stages with id,
|
|
81
|
-
`
|
|
110
|
+
`printed: name, purpose, outcome, entryAgent, facilitator, guardrails, the stages with id, phase, ` +
|
|
111
|
+
`agent, ` +
|
|
112
|
+
`produces and exitGate, and decisionOwners flattened into owners as domain/agent pairs. Then read ` +
|
|
82
113
|
`${ROOT}/organization/ and report nothing from it: it is context for later stages, not output.`,
|
|
83
114
|
{ schema: MANIFEST, label: 'team-contract' },
|
|
84
115
|
)
|
|
@@ -88,13 +119,17 @@ const owners = (contract.owners || []).map((owner) => `${owner.domain}=${owner.a
|
|
|
88
119
|
const RULES = `${BASE}\n\nEquipo ${contract.name}: ${contract.purpose}\n` +
|
|
89
120
|
`Guardrails: ${contract.guardrails.join(' ')}\n` +
|
|
90
121
|
`${owners ? `Dueños de decisión: ${owners}. Ningún otro cargo resuelve en su dominio.\n` : ''}` +
|
|
91
|
-
`Contexto de la empresa en ${ROOT}/organization/. Intención a evaluar: ${
|
|
122
|
+
`Contexto de la empresa en ${ROOT}/organization/. Intención a evaluar: ${GOAL}`
|
|
92
123
|
|
|
93
124
|
phase('Stages')
|
|
94
125
|
|
|
95
126
|
const handoffs = []
|
|
96
127
|
const blocked = []
|
|
97
|
-
|
|
128
|
+
// Sólo descubrimiento: este recorrido propone trabajo, no lo ejecuta. Las etapas de entrega las
|
|
129
|
+
// corre `autobuild`, y sólo después de que una persona promueva la épica al BACKLOG.
|
|
130
|
+
const discovery = contract.stages.filter((stage) => stage.phase === 'discovery')
|
|
131
|
+
if (!discovery.length) return stop('sin-descubrimiento', `${TEAM} no declara etapas de discovery`)
|
|
132
|
+
for (const stage of discovery) {
|
|
98
133
|
const previous = handoffs.length
|
|
99
134
|
? `Handoffs previos:\n${handoffs.map((entry) => `- ${entry.id}: ${entry.findings}`).join('\n')}`
|
|
100
135
|
: 'Sos la primera etapa: no hay handoff previo.'
|
|
@@ -129,6 +164,24 @@ if (blocked.length) {
|
|
|
129
164
|
|
|
130
165
|
phase('Draft')
|
|
131
166
|
|
|
167
|
+
// Un recorrido que registra lo aprendido no propone trabajo: deja el informe y las tareas de
|
|
168
|
+
// seguimiento en el INBOX, donde una persona decide si alguna merece convertirse en épica.
|
|
169
|
+
if (contract.outcome === 'report') {
|
|
170
|
+
const report = await agent(
|
|
171
|
+
`${RULES}\n\nHandoffs completos:\n${JSON.stringify(handoffs)}\n\nEscribí el informe en ${REPORTS} como ` +
|
|
172
|
+
`<AAAA-MM-DD>-<slug>.md: qué pasó, qué se sabe con evidencia, qué se supone, qué se decidió y qué ` +
|
|
173
|
+
`queda abierto. Separá causa de síntoma y no atribuyas responsabilidad a personas. Registrá cada ` +
|
|
174
|
+
`seguimiento en la sección Lecciones de ${INBOX}, sin promoverlo, y toda acción que requiera una ` +
|
|
175
|
+
`persona en ${HUMAN}.`,
|
|
176
|
+
{ schema: { type: 'object', required: ['file', 'followUps'], properties: {
|
|
177
|
+
file: { type: 'string' }, followUps: { type: 'integer' }, summary: { type: 'string' },
|
|
178
|
+
} }, label: 'report-write' },
|
|
179
|
+
)
|
|
180
|
+
if (!report) return stop('report-unavailable', 'el informe no devolvió resultado')
|
|
181
|
+
log(`Informe en ${report.file}. ${report.followUps} seguimiento(s) en el INBOX, sin promover.`)
|
|
182
|
+
return finish({ team: TEAM, stages: handoffs.length, report: report.file, promoted: false })
|
|
183
|
+
}
|
|
184
|
+
|
|
132
185
|
const epic = await agent(
|
|
133
186
|
`${RULES}\n\nHandoffs completos:\n${JSON.stringify(handoffs)}\n\nComo product-manager, decidí si la ` +
|
|
134
187
|
`intención es viable con la evidencia reunida. Si lo es, redactá la épica: título, slug en ` +
|
package/engine/cli/ops.js
CHANGED
|
@@ -506,8 +506,11 @@ function upgrade(dir) {
|
|
|
506
506
|
if (changed.length && !force) {
|
|
507
507
|
for (const file of changed) console.error(`✗ ${file}`)
|
|
508
508
|
fail(
|
|
509
|
-
`\n${changed.length} archivo(s) del runtime fueron editados y se perderían.\n` +
|
|
510
|
-
'
|
|
509
|
+
`\n${changed.length} archivo(s) del runtime fueron editados y se perderían.\n\n` +
|
|
510
|
+
'El runtime es del toolkit: en vez de editarlo, agregá lo tuyo al lado con otro nombre —un\n' +
|
|
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.',
|
|
511
514
|
)
|
|
512
515
|
}
|
|
513
516
|
|
|
@@ -532,6 +535,9 @@ function upgrade(dir) {
|
|
|
532
535
|
F.atomicWriteJson(path.join(root, 'ops.config.json'), config)
|
|
533
536
|
|
|
534
537
|
console.log(`✓ Cauce ${from || '(previa)'} → ${to}`)
|
|
538
|
+
// Descartar con --force es legítimo; hacerlo sin dejar rastro no. Queda en la salida del comando,
|
|
539
|
+
// que es la evidencia que el protocolo pide para cualquier cambio.
|
|
540
|
+
for (const file of changed) console.log(`− descartado tu cambio en ${file}`)
|
|
535
541
|
printChangelog(from, to)
|
|
536
542
|
console.log(` ${system.length} ruta(s) del sistema y ${O.RUNTIME_PATHS.length} del runtime actualizadas`)
|
|
537
543
|
for (const override of overrides) {
|
|
@@ -547,8 +553,10 @@ function agents(action, dir) {
|
|
|
547
553
|
const root = path.resolve(dir || '.')
|
|
548
554
|
const roles = AG.list(root)
|
|
549
555
|
if (process.argv.includes('--json')) {
|
|
556
|
+
// `path` viene resuelto: quien consuma esto no debería reconstruir dónde ganó la precedencia.
|
|
550
557
|
return console.log(JSON.stringify(roles.map((role) => ({
|
|
551
558
|
slug: role.slug, type: role.type, system: role.system,
|
|
559
|
+
path: path.relative(root, role.dir).split(path.sep).join('/'),
|
|
552
560
|
}))))
|
|
553
561
|
}
|
|
554
562
|
for (const role of roles) console.log(`${role.slug}${role.system ? '' : ' (propio)'}`)
|
package/engine/teams/registry.js
CHANGED
|
@@ -5,6 +5,10 @@ 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.
|
|
@@ -40,6 +44,7 @@ function validate(root, slug) {
|
|
|
40
44
|
for (const field of ['name', 'purpose', 'entryAgent', 'facilitator']) {
|
|
41
45
|
if (typeof manifest[field] !== 'string' || !manifest[field].trim()) errors.push(`falta ${field}`)
|
|
42
46
|
}
|
|
47
|
+
if (!OUTCOMES.includes(manifest.outcome)) errors.push(`outcome debe ser ${OUTCOMES.join(' o ')}`)
|
|
43
48
|
if (!Array.isArray(manifest.stages) || !manifest.stages.length) errors.push('stages debe contener etapas')
|
|
44
49
|
if (!Array.isArray(manifest.guardrails) || !manifest.guardrails.length) errors.push('guardrails debe contener controles')
|
|
45
50
|
if (!Array.isArray(manifest.completion) || !manifest.completion.length) errors.push('completion debe contener criterios')
|
|
@@ -58,6 +63,9 @@ function validate(root, slug) {
|
|
|
58
63
|
agents.add(stage.agent)
|
|
59
64
|
if (!Array.isArray(stage.produces) || !stage.produces.length) errors.push(`${stage.id}: falta produces`)
|
|
60
65
|
if (typeof stage.exitGate !== 'string' || !stage.exitGate.trim()) errors.push(`${stage.id}: falta exitGate`)
|
|
66
|
+
// Descubrimiento propone, entrega ejecuta. Sin la distinción, un recorrido de descubrimiento
|
|
67
|
+
// terminaría construyendo antes de que exista la épica y antes de la promoción humana.
|
|
68
|
+
if (!PHASES.includes(stage.phase)) errors.push(`${stage.id}: phase debe ser ${PHASES.join(' o ')}`)
|
|
61
69
|
}
|
|
62
70
|
for (const agent of agents) {
|
|
63
71
|
if (!SLUG.test(agent || '')) errors.push(`slug de agente inválido: ${agent || '(vacío)'}`)
|
|
@@ -66,6 +74,9 @@ function validate(root, slug) {
|
|
|
66
74
|
if (problem) errors.push(problem)
|
|
67
75
|
}
|
|
68
76
|
}
|
|
77
|
+
if (!(manifest.stages || []).some((stage) => stage.phase === 'discovery')) {
|
|
78
|
+
errors.push('falta al menos una etapa de discovery: un equipo sin descubrimiento no propone nada')
|
|
79
|
+
}
|
|
69
80
|
// Junto al team.json que ganó la resolución, no en una ruta fija: el team puede venir de system/.
|
|
70
81
|
const workflow = path.join(path.dirname(teamFile(root, slug)), 'WORKFLOW.md')
|
|
71
82
|
if (!fs.existsSync(workflow)) errors.push('falta WORKFLOW.md')
|
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.
|