@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +9 -2
  3. package/agents/roles/system/finops-engineer/SKILL.md +81 -0
  4. package/agents/roles/system/finops-engineer/agents/openai.yaml +4 -0
  5. package/agents/roles/system/finops-engineer/evaluations/cases/01-apagar-en-produccion.md +10 -0
  6. package/agents/roles/system/finops-engineer/evaluations/cases/02-ahorro-estimado-como-realizado.md +10 -0
  7. package/agents/roles/system/finops-engineer/evaluations/cases/03-salto-por-regresion.md +10 -0
  8. package/agents/roles/system/finops-engineer/evaluations/cases/04-optimizar-contra-la-fiabilidad.md +10 -0
  9. package/agents/roles/system/finops-engineer/evaluations/cases/05-costos-por-cliente.md +10 -0
  10. package/agents/roles/system/finops-engineer/evaluations/cases/06-adversarial-calculadora-del-proveedor.md +11 -0
  11. package/agents/roles/system/finops-engineer/evaluations/expected-behaviors.yaml +21 -0
  12. package/agents/roles/system/finops-engineer/learning/CODEX_AUTOMATION.md +18 -0
  13. package/agents/roles/system/finops-engineer/learning/HISTORY.md +4 -0
  14. package/agents/roles/system/finops-engineer/learning/proposals/_template.md +15 -0
  15. package/agents/roles/system/finops-engineer/learning/reports/_template.md +14 -0
  16. package/agents/roles/system/finops-engineer/learning/sources.yaml +33 -0
  17. package/agents/roles/system/finops-engineer/references/operating-model.md +70 -0
  18. package/agents/roles/system/growth-marketer/SKILL.md +77 -0
  19. package/agents/roles/system/growth-marketer/agents/openai.yaml +4 -0
  20. package/agents/roles/system/growth-marketer/evaluations/cases/01-gastar-sin-baseline.md +10 -0
  21. package/agents/roles/system/growth-marketer/evaluations/cases/02-metrica-de-plataforma.md +10 -0
  22. package/agents/roles/system/growth-marketer/evaluations/cases/03-cortar-experimento.md +10 -0
  23. package/agents/roles/system/growth-marketer/evaluations/cases/04-promesa-que-el-producto-no-sostiene.md +10 -0
  24. package/agents/roles/system/growth-marketer/evaluations/cases/05-audiencia-sin-base-legal.md +10 -0
  25. package/agents/roles/system/growth-marketer/evaluations/cases/06-adversarial-caso-de-exito.md +11 -0
  26. package/agents/roles/system/growth-marketer/evaluations/expected-behaviors.yaml +21 -0
  27. package/agents/roles/system/growth-marketer/learning/CODEX_AUTOMATION.md +18 -0
  28. package/agents/roles/system/growth-marketer/learning/HISTORY.md +4 -0
  29. package/agents/roles/system/growth-marketer/learning/proposals/_template.md +15 -0
  30. package/agents/roles/system/growth-marketer/learning/reports/_template.md +14 -0
  31. package/agents/roles/system/growth-marketer/learning/sources.yaml +33 -0
  32. package/agents/roles/system/growth-marketer/references/operating-model.md +76 -0
  33. package/automatization/workflows/autobuild.js +73 -11
  34. package/automatization/workflows/team.js +59 -6
  35. package/engine/cli/ops.js +10 -2
  36. package/engine/teams/registry.js +11 -0
  37. package/package.json +1 -1
  38. package/teams/000-template.md +110 -0
  39. package/teams/README.md +60 -0
  40. package/teams/system/feasibility-review/WORKFLOW.md +60 -0
  41. package/teams/system/feasibility-review/team.json +73 -0
  42. package/teams/system/incident-review/WORKFLOW.md +65 -0
  43. package/teams/system/incident-review/team.json +72 -0
  44. package/teams/system/product-development/team.json +71 -17
  45. package/template/AGENTS.md +11 -0
  46. package/template/README.md +1 -1
  47. package/template/planning/reports/README.md +11 -0
  48. package/agents/coordinators/.gitkeep +0 -1
  49. package/agents/specialists/.gitkeep +0 -1
  50. 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
- `As product manager, check concrete acceptance, dependencies and unresolved decisions for ${task.id}: ${task.acceptance}. ` +
171
- `Clarify wording only; never expand scope.`, { schema: READY },
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
- `Inspect real code, repository instructions, neighbouring conventions, epic context and git status for ${task.id}. ` +
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)}. Follow the WIP contract exactly.`,
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
- `Implement only ${task.id} inside ${task.service}. Resume at the first pending WIP step; verify completed steps on disk ` +
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
- `Review the actual diff for acceptance, regressions, security, architecture, generated code, migrations and accidental scope.`,
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
- `Discover and run the real gates for ${task.service}: repository instructions first, then applicable test, lint, ` +
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
- `Find the git repo owning ${task.service}, inspect status/diff, stage explicit task files, create one Conventional ` +
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
- const TEAM = String(args && args.team || process.env.OPS_TEAM || 'product-development')
25
- const INTENT = String(args && args.intent || process.env.OPS_INTENT || '').trim()
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, agent, produces ` +
81
- `and exitGate, and decisionOwners flattened into owners as domain/agent pairs. Then read ` +
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: ${INTENT}`
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
- for (const stage of contract.stages) {
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
- 'Movelos junto a system/ como regla propia, o repetí con --force para descartarlos.',
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)'}`)
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.2.0",
3
+ "version": "0.3.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.