@ingeniomaps/cauce 0.81.0 → 0.83.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 (43) hide show
  1. package/CHANGELOG.md +248 -0
  2. package/automatization/hooks/README.md +10 -6
  3. package/automatization/hooks/guard-ops-config-shell.sh +3 -0
  4. package/automatization/hooks/guard-ops-config.sh +3 -0
  5. package/automatization/runners/antigravity/rules/cauce.md +7 -2
  6. package/automatization/runners/claude/CLAUDE.md +1 -4
  7. package/automatization/runners/codex/AGENTS.md +7 -3
  8. package/automatization/runners/gemini/GEMINI.md +1 -4
  9. package/automatization/shared/inbox.js +47 -0
  10. package/automatization/workflows/agent-eval.js +1 -1
  11. package/automatization/workflows/autobuild.js +56 -18
  12. package/automatization/workflows/flow.js +45 -8
  13. package/automatization/workflows/onboard.js +32 -3
  14. package/engine/automation/index.js +19 -4
  15. package/engine/automation/rules.js +122 -0
  16. package/engine/automation/runners.js +3 -1
  17. package/engine/cli/instance.js +35 -6
  18. package/engine/cli/planning.js +16 -2
  19. package/engine/config/validate.js +53 -3
  20. package/engine/core/onboarding.js +37 -7
  21. package/engine/core/ownership.js +64 -1
  22. package/engine/core/scan.js +23 -9
  23. package/engine/hooks/approval.js +50 -15
  24. package/engine/hooks/chat.js +122 -11
  25. package/engine/hooks/input.js +1 -11
  26. package/engine/hooks/ops-config.js +110 -0
  27. package/engine/hooks/push.js +194 -0
  28. package/engine/hooks/run.js +17 -2
  29. package/engine/hooks/secrets-shell.js +13 -2
  30. package/engine/hooks/self-approval.js +93 -13
  31. package/engine/hooks/shell.js +13 -11
  32. package/engine/integrations/registry.js +13 -1
  33. package/engine/planning/inbox.js +36 -0
  34. package/engine/planning/parser.js +22 -9
  35. package/engine/planning/recurring.js +13 -2
  36. package/engine/schemas/ops-config.schema.json +21 -0
  37. package/package.json +1 -1
  38. package/template/AGENTS.md +13 -5
  39. package/template/gitignore +5 -0
  40. package/template/planning/INBOX.md +2 -1
  41. package/template/planning/RECURRING.md +6 -5
  42. package/template/planning/delivery/teamwork.md +1 -1
  43. package/template/planning/rules/system/commits.md +3 -1
@@ -405,18 +405,31 @@ function readWips(dir) {
405
405
  // La plantilla no traía ningún ejemplo, así que quien escribía viñetas planas veía cero ítems sobre un
406
406
  // archivo con doce y nada se lo decía. `skipped` es lo que vuelve visible esa diferencia.
407
407
  function readInbox(dir) {
408
- const result = { deuda: 0, ideas: 0, propuestas: 0, lecciones: 0, skipped: 0 }
408
+ const { heads, skipped } = inboxSections(dir)
409
+ return { ...Object.fromEntries(Object.entries(heads).map(([key, names]) => [key, names.length])), skipped }
410
+ }
411
+
412
+ // Los nombres en negrita de cada sección, que es lo que un recorrido necesita para no volver a escribir
413
+ // lo que ya está: el nombre y no la entrada, porque pasar el archivo entero a cada tarea cuesta lo que
414
+ // el INBOX pesa (caso 101).
415
+ function inboxHeads(dir) {
416
+ return inboxSections(dir).heads
417
+ }
418
+
419
+ function inboxSections(dir) {
420
+ const heads = { deuda: [], ideas: [], propuestas: [], lecciones: [] }
421
+ let skipped = 0
409
422
  for (const part of read(path.join(dir, 'INBOX.md')).split(/^##\s+/m)) {
410
423
  const title = part.split('\n')[0]
411
424
  const bullets = (part.match(/^[-*]\s+(?:\[[ xX]\]\s+)?/gm) || []).length
412
- const count = (part.match(/^[-*]\s+(?:\[[ xX]\]\s+)?\*\*/gm) || []).length
413
- if (/Deuda|Ideas|Visi[oó]n|Propuestas|Lecciones/i.test(title)) result.skipped += bullets - count
414
- if (/Deuda/i.test(title)) result.deuda = count
415
- if (/Ideas|Visi[oó]n/i.test(title)) result.ideas = count
416
- if (/Propuestas/i.test(title)) result.propuestas = count
417
- if (/Lecciones/i.test(title)) result.lecciones = count
425
+ const names = [...part.matchAll(/^[-*]\s+(?:\[[ xX]\]\s+)?\*\*([^*\n]*)/gm)].map((hit) => hit[1].trim())
426
+ if (/Deuda|Ideas|Visi[oó]n|Propuestas|Lecciones/i.test(title)) skipped += bullets - names.length
427
+ if (/Deuda/i.test(title)) heads.deuda = names
428
+ if (/Ideas|Visi[oó]n/i.test(title)) heads.ideas = names
429
+ if (/Propuestas/i.test(title)) heads.propuestas = names
430
+ if (/Lecciones/i.test(title)) heads.lecciones = names
418
431
  }
419
- return result
432
+ return { heads, skipped }
420
433
  }
421
434
 
422
435
  module.exports = {
@@ -424,5 +437,5 @@ module.exports = {
424
437
  TASK_LINE, TASK_LINE_ANY_LANE,
425
438
  read, section, withoutComments, frontmatter, readEpics, readBacklog, readDone, readWip, readWips, wipName,
426
439
  acceptanceConditions, tableRows, taskFromLine,
427
- readInbox, readHumanActions,
440
+ readInbox, inboxHeads, readHumanActions,
428
441
  }
@@ -19,6 +19,10 @@ const CADENCES = { mensual: 1, trimestral: 3, semestral: 6, anual: 12 }
19
19
 
20
20
  const ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
21
21
  const DATE = /^\d{4}-\d{2}-\d{2}$/
22
+ // La fila que el molde trae activa escribe su `Desde` como marcador, porque la fecha depende del día en
23
+ // que se crea la instancia y la reemplazan `init` y `upgrade`. Sin resolver sólo existe en el molde
24
+ // mismo —el que `npm run check` valida en el toolkit—, y ahí no vence: `status` la descarta por fecha.
25
+ const PLACEHOLDER = /^\{\{[A-Z_]+\}\}$/
22
26
  // `- **qué** AAAA-MM-DD — razón`. El nombre en negrita adelante es la misma convención del INBOX, y por
23
27
  // el mismo motivo: es con lo que se cita la fila desde otro lado.
24
28
  const POSTPONEMENT = /^-\s+\*\*([^*]+)\*\*\s+(\S+)\s+[—-]\s+(.+)$/
@@ -77,7 +81,7 @@ function validate({ exists, rows, postponements }) {
77
81
  if (!CADENCES[row.cadence]) {
78
82
  errors.push(`${at}: cadencia "${row.cadence}" fuera de ${Object.keys(CADENCES).join(' | ')}`)
79
83
  }
80
- if (!DATE.test(row.since)) errors.push(`${at}: Desde debe ser AAAA-MM-DD`)
84
+ if (!DATE.test(row.since) && !PLACEHOLDER.test(row.since)) errors.push(`${at}: Desde debe ser AAAA-MM-DD`)
81
85
  // Se juzga la línea armada y no la celda suelta: lo que se promueve es esa línea, y quien la va a
82
86
  // leer es el mismo lector de BACKLOG. Una celda que pasa acá y una línea que BACKLOG rechaza es el
83
87
  // error que aparece un mes después, con la tarea ya pegada.
@@ -145,4 +149,11 @@ function warnings(state) {
145
149
  return lines
146
150
  }
147
151
 
148
- module.exports = { FILE, read, validate, status, warnings, taskLine }
152
+ // Los marcadores de fecha del molde, resueltos para una instancia que nace hoy. `Desde` es la primera
153
+ // fecha de vencimiento y no el día en que se declara la fila, así que va un período después: con la
154
+ // fecha de hoy la fila nacía «vence hoy» (caso 106). Trimestral es la cadencia de la fila del molde.
155
+ function sinceValues(today) {
156
+ return { '{{INBOX_SINCE}}': addMonths(today, CADENCES.trimestral) }
157
+ }
158
+
159
+ module.exports = { FILE, read, validate, status, warnings, taskLine, sinceValues }
@@ -83,6 +83,15 @@
83
83
  },
84
84
  "allowPush": {
85
85
  "type": "boolean"
86
+ },
87
+ "pushToLiveBranches": {
88
+ "type": "array",
89
+ "description": "Las ramas vivas —main, master y la rama por defecto de cada remoto— en las que se puede publicar. Sin nombrarla acá, ni allowPush ni una orden en el chat llegan a una rama viva. Nombres exactos, sin patrones.",
90
+ "items": {
91
+ "type": "string",
92
+ "pattern": "^[^\\s*?\\[]+$"
93
+ },
94
+ "uniqueItems": true
86
95
  }
87
96
  },
88
97
  "additionalProperties": false
@@ -102,6 +111,18 @@
102
111
  "minItems": 1
103
112
  }
104
113
  }
114
+ },
115
+ "inbox": {
116
+ "type": "object",
117
+ "description": "El aviso de tamaño del INBOX. `check` advierte —nunca falla— cuando `planning/INBOX.md` pasa de este número de líneas: el INBOX es de la persona, y lo que el aviso pide es recorrerlo, no dejar de escribir.",
118
+ "additionalProperties": false,
119
+ "properties": {
120
+ "warnLines": {
121
+ "type": "integer",
122
+ "minimum": 1,
123
+ "description": "Líneas a partir de las cuales `check` avisa. Por defecto 300."
124
+ }
125
+ }
105
126
  }
106
127
  },
107
128
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.81.0",
3
+ "version": "0.83.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "keywords": [
6
6
  "planning",
@@ -2,7 +2,9 @@
2
2
 
3
3
  Este archivo gobierna el qué y el cuándo. `planning/PROTOCOL.md` gobierna el flujo y
4
4
  `planning/rules/` el cómo: sus reglas rigen cada tarea y se leen antes de empezar, no cuando algo sale
5
- mal. Los tres los mantiene Cauce y valen para cualquier proyecto. Lo que este proyecto tiene de propio
5
+ mal. Este archivo, el protocolo y `planning/rules/system/` los mantiene Cauce y valen para cualquier
6
+ proyecto; las reglas propias viven junto a `system/`, y donde una de ellas sobrescribe, contradice o
7
+ restringe una del sistema, rige la del proyecto. Lo que este proyecto tiene de propio
6
8
  —su mapa, sus integraciones, hasta dónde llega la autonomía acá— vive en `organization/workspace.md`.
7
9
 
8
10
  ## Qué sabe este proyecto y no este archivo
@@ -107,7 +109,9 @@ te frena es el peor para elegir bien.
107
109
  cuando trabaja dentro de un recorrido; lo que vos pedís directo no se frena. Nombrá lo que querés que
108
110
  toque —«borrá la prueba de altas», «reescribí la migración 004»— y pasa sin preguntarte de nuevo. Si tu
109
111
  pedido no lo nombraba y algo se frena, el agente te dice qué y por qué: contestá «dale» y pasa exactamente
110
- eso. Y `plan-first` no te pide un plan cuando el cambio lo pediste vos: el plan es para el trabajo que va
112
+ eso. **Con los gates de un commit se pregunta cada vez**, como con publicar: gobernanza, los gates del
113
+ stack y los lockfiles no heredan lo que autorizaste en un mensaje anterior, porque cada commit es otra
114
+ operación. Y `plan-first` no te pide un plan cuando el cambio lo pediste vos: el plan es para el trabajo que va
111
115
  por tareas. Funciona en Claude Code, Codex y Gemini, que le avisan a Cauce cuando mandás un mensaje; en
112
116
  Antigravity, y cuando nadie está en el chat —CI, un recorrido, un subagente—, queda el archivo de abajo.
113
117
 
@@ -250,12 +254,16 @@ aprobación que pide BR-OPS-002 — `context` la nombra para que la vea una pers
250
254
  `BACKLOG.md` es esa persona.
251
255
 
252
256
  Publicar es lo único de todo eso que este proyecto puede habilitar, y `runner.allowPush` en
253
- `ops.config.json` es la autorización que R10 pide. Reescribir historia publicada no entra en el trato:
254
- un `push --force` se frena con la llave prendida o apagada.
257
+ `ops.config.json` es la autorización que R10 pide para las ramas de trabajo. La rama viva —`main`,
258
+ `master` o la rama por defecto del remoto— no la alcanza si el proyecto no la nombra en
259
+ `runner.pushToLiveBranches`, y un subagente no publica con ningún permiso. Sin la llave, pasa el push
260
+ que la persona pide en el chat nombrando el remoto y la rama, o el que ella aprueba contestando «dale».
261
+ Reescribir historia publicada no entra en el trato: un `push --force` se frena con la llave prendida o
262
+ apagada.
255
263
 
256
264
  Eso rige sin que nadie escriba nada. Lo que este proyecto amplíe o restrinja va en
257
265
  `organization/workspace.md`, con su razón; ninguna de esas prohibiciones se amplía ahí, y la
258
- publicación tampoco se decide ahí: la decide `allowPush`.
266
+ publicación tampoco se decide ahí: la deciden `allowPush` y `pushToLiveBranches`.
259
267
 
260
268
  ## Definición de terminado
261
269
 
@@ -9,6 +9,11 @@ node_modules/
9
9
  # máquina. Committearlo sería historia que nadie lee y un conflicto por commit.
10
10
  planning/.verify-log
11
11
 
12
+ # El rastro de los push que pasaron por una aprobación. Local por lo mismo que el de arriba, y encima
13
+ # sólo crece: lo que contesta —quién autorizó qué rama y cuándo— se pregunta en la máquina donde corrió
14
+ # la sesión que publicó.
15
+ planning/.push-log
16
+
12
17
  # El plan de cada runner. Es de la máquina que lo corre —existe para recuperar una ejecución
13
18
  # interrumpida, y nadie más puede retomarla— y cambia en cada paso, así que compartirlo es un conflicto
14
19
  # por commit a cambio de nada. Lo que el equipo sí necesita saber vive en `planning/claims/`.
@@ -14,7 +14,8 @@ Lo que separa a las cuatro secciones es el sujeto del ítem, no su tamaño ni su
14
14
 
15
15
  - **Deuda** — un costo que ya estamos cargando en código nuestro, conocido y no bloqueante.
16
16
  - **Ideas** — una pregunta abierta, sin respuesta propuesta.
17
- - **Propuestas** — un cambio concreto del producto, con su evidencia y su fix propuesto.
17
+ - **Propuestas** — un cambio concreto del producto y su fix propuesto. La evidencia no se copia acá: se
18
+ cita dónde vive —el `done/` de la tarea, el informe—.
18
19
  - **Lecciones** — sobre cómo trabajamos; es lo que alimenta reglas y propuestas de cargo.
19
20
 
20
21
  Ideas y Propuestas se separan por si hay una respuesta propuesta. Deuda y Propuestas, por si el costo
@@ -16,7 +16,8 @@ vencimiento se calcula cuando alguien corre el CLI. Promover sigue siendo un act
16
16
  - **Cada** — vocabulario cerrado: `mensual`, `trimestral`, `semestral`, `anual`. No hay expresiones de
17
17
  cron, y esa ausencia es el enunciado: si hiciera falta una, lo que estarías declarando es otra cosa.
18
18
  Tampoco hay `semanal`, porque el slug de cada vuelta tiene grano de mes.
19
- - **Desde** — `AAAA-MM-DD`. Ancla la primera vuelta y después no se toca.
19
+ - **Desde** — `AAAA-MM-DD`. Ancla la primera vuelta y después no se toca. Es la primera fecha en que
20
+ vence, no el día en que se escribe la fila: con la fecha de hoy nace vencida.
20
21
  - **Tarea y aceptación** — lo que se va a promover, con su aceptación observable escrita una sola vez y
21
22
  con calma. Improvisada en cada vuelta, la misma recurrencia termina significando cosas distintas sin
22
23
  que nadie lo decida.
@@ -47,12 +48,12 @@ slug son un error de `check`, y ese error llega un mes tarde.
47
48
 
48
49
  | Qué | Cada | Desde | Tarea y aceptación observable |
49
50
  |---|---|---|---|
51
+ | inbox | trimestral | {{INBOX_SINCE}} | Recorrer el INBOX entero. _Aceptación: ninguna viñeta queda sin decisión de promover, dejar o borrar._ (service: planning) |
50
52
 
51
53
  <!--
52
- | deps | mensual | 2026-09-01 | Actualizar dependencias. _Aceptación: `npm outdated` no deja una versión mayor sin decisión escrita y la puerta queda verde._ |
53
- | accesos | trimestral | 2026-07-01 | Revisar quién tiene acceso a producción. _Aceptación: cada cuenta activa figura en `organization/`, y las demás están dadas de baja._ |
54
- | costos | mensual | 2026-09-01 | Revisar el gasto de infraestructura del mes. _Aceptación: cada línea que subió más de 20% tiene una razón escrita._ |
55
- | inbox | trimestral | 2026-08-01 | Recorrer el INBOX entero. _Aceptación: ninguna viñeta queda sin decisión de promover, dejar o borrar._ |
54
+ | deps | mensual | 2026-09-01 | Actualizar dependencias. _Aceptación: `npm outdated` no deja una versión mayor sin decisión escrita y la puerta queda verde._ (service: .) |
55
+ | accesos | trimestral | 2026-07-01 | Revisar quién tiene acceso a producción. _Aceptación: cada cuenta activa figura en `organization/`, y las demás están dadas de baja._ (service: organization) |
56
+ | costos | mensual | 2026-09-01 | Revisar el gasto de infraestructura del mes. _Aceptación: cada línea que subió más de 20% tiene una razón escrita._ (service: .) |
56
57
  -->
57
58
 
58
59
  ## Postergaciones
@@ -11,7 +11,7 @@ choques entre dos personas —o entre dos agentes— salen de tratarlas igual.
11
11
  |---|---|---|---|
12
12
  | **Compartido** | `roadmap/`, `BACKLOG.md`, `INBOX.md`, `HUMAN_ACTIONS.md`, `done/`, reglas y ADR | cualquiera, en actos humanos | baja |
13
13
  | **Coordinación** | `claims/`, un archivo por tarea tomada | uno por tarea | dos veces por tarea |
14
- | **Local** | `wip/<runner>.md`, `.verify-log`, el árbol de trabajo | vos | continua |
14
+ | **Local** | `wip/<runner>.md`, `.verify-log`, `.push-log`, el árbol de trabajo | vos | continua |
15
15
 
16
16
  La regla que los separa: **un archivo con más de un escritor tiene que cambiar poco; uno que cambia mucho
17
17
  tiene que tener un solo escritor.** Cuando uno viola las dos a la vez, el equipo se pisa en cada commit.
@@ -65,7 +65,9 @@ dice qué había que hacer, no qué se apoyaba en lo que había.
65
65
 
66
66
  Push, PR, merge, tags, deploy y rollback requieren la autorización configurada para el proyecto.
67
67
 
68
- De esos seis, el motor comprueba uno: el push, contra `runner.allowPush`. Reescribir historia publicada
68
+ De esos seis, el motor comprueba uno: el push, contra `runner.allowPush` —que no llega a la rama viva
69
+ sin `runner.pushToLiveBranches`, ni a un subagente— o contra la orden que la persona da en el chat
70
+ nombrando el remoto y la rama. Reescribir historia publicada
69
71
  no entra en esa autorización y se frena siempre, igual que `--amend`. Los otros cinco no tienen una
70
72
  forma reconocible en un comando —un deploy es `kubectl`, `terraform`, un script o un botón— y los
71
73
  sostiene esta regla y el review, no un guard.