@ingeniomaps/cauce 0.12.0 → 0.13.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.
@@ -5,6 +5,9 @@ name: Agent learning proposals
5
5
 
6
6
  on:
7
7
  schedule:
8
+ # Semanal la investigación, mensual la consolidación. La propuesta sólo vale si antes hubo
9
+ # informes: consolidar sin ellos produce un andamiaje vacío que nadie puede aprobar.
10
+ - cron: "17 13 * * 1"
8
11
  - cron: "17 13 1 * *"
9
12
  workflow_dispatch:
10
13
  inputs:
@@ -61,7 +64,97 @@ jobs:
61
64
  fs.appendFileSync(process.env.GITHUB_OUTPUT, `agents=${JSON.stringify(list)}\n`)
62
65
  ' "$all"
63
66
 
67
+ # La investigación necesita un modelo, así que este job sólo corre si el repositorio declara una
68
+ # credencial. Sin ella se saltea entero en vez de abrir un PR por cargo con un informe vacío, que
69
+ # es ruido con forma de trabajo. `learn` solo arma el andamiaje; llenarlo es lo que hace un agente.
70
+ research:
71
+ if: github.event.schedule != '17 13 1 * *'
72
+ needs: discover
73
+ runs-on: ubuntu-latest
74
+ strategy:
75
+ fail-fast: false
76
+ max-parallel: 3
77
+ matrix:
78
+ agent: ${{ fromJSON(needs.discover.outputs.agents) }}
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ - uses: actions/setup-node@v4
82
+ with:
83
+ node-version: "24"
84
+ - name: Check credential
85
+ id: creds
86
+ env:
87
+ KEY: ${{ secrets.ANTHROPIC_API_KEY }}
88
+ run: |
89
+ if [ -n "$KEY" ]; then
90
+ echo "ready=true" >> "$GITHUB_OUTPUT"
91
+ else
92
+ echo "ready=false" >> "$GITHUB_OUTPUT"
93
+ echo "Sin ANTHROPIC_API_KEY: la investigación semanal queda para correr a mano." >&2
94
+ fi
95
+ - name: Resolve CLI
96
+ if: steps.creds.outputs.ready == 'true'
97
+ id: cli
98
+ run: |
99
+ for candidate in tools/ops.js engine/cli/ops.js; do
100
+ if [ -f "$candidate" ]; then
101
+ echo "path=$candidate" >> "$GITHUB_OUTPUT"
102
+ exit 0
103
+ fi
104
+ done
105
+ echo "No se encontró el CLI de Cauce." >&2
106
+ exit 1
107
+ - name: Prepare report
108
+ if: steps.creds.outputs.ready == 'true'
109
+ env:
110
+ AGENT: ${{ matrix.agent }}
111
+ OPS: ${{ steps.cli.outputs.path }}
112
+ run: node "$OPS" learn "$AGENT"
113
+ # El prompt no lo escribe este archivo: lo declara el propio cargo en su
114
+ # `learning/CODEX_AUTOMATION.md`. Así un cargo nuevo trae su investigación sin tocar el cron.
115
+ - name: Research
116
+ if: steps.creds.outputs.ready == 'true'
117
+ env:
118
+ AGENT: ${{ matrix.agent }}
119
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
120
+ run: |
121
+ npm install -g @anthropic-ai/claude-code
122
+ claude -p "Leé el learning/CODEX_AUTOMATION.md del cargo $AGENT y ejecutá exactamente lo que declara. El informe de esta semana ya está creado y vacío: completalo. Cada afirmación necesita enlace, fecha y versión; lo que no puedas corroborar va a Preguntas abiertas en vez de afirmarse. El único archivo que escribís es ese informe."
123
+ - name: Detect changes
124
+ if: steps.creds.outputs.ready == 'true'
125
+ id: changes
126
+ run: |
127
+ if git diff --quiet; then
128
+ echo "changed=false" >> "$GITHUB_OUTPUT"
129
+ else
130
+ echo "changed=true" >> "$GITHUB_OUTPUT"
131
+ fi
132
+ - name: Open research pull request
133
+ if: steps.creds.outputs.ready == 'true' && steps.changes.outputs.changed == 'true'
134
+ env:
135
+ AGENT: ${{ matrix.agent }}
136
+ GH_TOKEN: ${{ github.token }}
137
+ run: |
138
+ stamp="$(date -u +%Y-%m-%d)"
139
+ branch="automation/$AGENT-research-$stamp"
140
+ report="$(git diff --name-only -- "agents/*/$AGENT/learning/reports/$stamp.md")"
141
+ if [ -z "$report" ]; then
142
+ echo "El agente cambió archivos fuera de su informe; no se abre PR." >&2
143
+ git diff --name-only >&2
144
+ exit 1
145
+ fi
146
+ git config user.name "github-actions[bot]"
147
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
148
+ git switch -c "$branch"
149
+ git add "$report"
150
+ git commit -m "docs($AGENT): weekly research $stamp"
151
+ git push origin "$branch"
152
+ gh pr create --base "$GITHUB_REF_NAME" --head "$branch" \
153
+ --title "$AGENT weekly research — $stamp" \
154
+ --body "Investigación semanal de la profesión. No modifica SKILL.md ni propone cambios: alimenta la propuesta mensual."
155
+
64
156
  propose:
157
+ if: github.event.schedule == '17 13 1 * *' || github.event_name == 'workflow_dispatch'
65
158
  needs: discover
66
159
  runs-on: ubuntu-latest
67
160
  strategy:
package/CHANGELOG.md CHANGED
@@ -8,6 +8,32 @@ esa operación sea confiable en vez de sólo cómoda: acá se lee qué cambió a
8
8
  un cambio en el protocolo, en las reglas del sistema o en un guard es visible para el usuario y sube
9
9
  minor aunque no toque una sola línea de código.
10
10
 
11
+ ## [0.13.0] - 2026-08-16
12
+
13
+ ### Añadido
14
+
15
+ - **El ciclo de aprendizaje se cierra solo hasta la firma, y sigue solo después de ella.** Faltaban
16
+ las dos mitades entre «recomendación» y «contrato actualizado`»:
17
+ - `/agent-propose <cargo>` escribe el cambio concreto —el texto exacto, archivo por archivo— y lo
18
+ contrasta contra los casos vigentes. Antes la propuesta llegaba con «Cambio propuesto: por
19
+ definir», y nadie firma una intención.
20
+ - `/agent-promote <cargo>` aplica una propuesta **ya firmada**, registra en `HISTORY.md` y manda a
21
+ correr los casos. Dos candados: se niega si «Aprobación humana» no tiene responsable —un agente no
22
+ se autoriza a sí mismo— y exige verificar, porque aplicar sin correr los casos deja un contrato
23
+ cambiado sin saber si se sostiene.
24
+ - Aplica **prosa, no un parche**, a propósito: un parche envejece si alguien toca el archivo mientras
25
+ la propuesta espera firma. El costo es que aplicar exige criterio, y por eso toda desviación se
26
+ escribe en la propia propuesta: quien firmó tiene derecho a saber qué se aplicó de lo que firmó.
27
+ - Investigación **semanal** en el cron, además de la consolidación mensual. Corre sólo si el
28
+ repositorio declara `ANTHROPIC_API_KEY`; sin ella se saltea entero en vez de abrir un PR por cargo
29
+ con un informe vacío. El prompt no vive en el cron: lo declara cada cargo en su
30
+ `learning/CODEX_AUTOMATION.md`, así que un cargo nuevo trae su investigación sin tocar el workflow.
31
+
32
+ ### Nota
33
+
34
+ El cron es de Cauce, no de las instancias: una empresa no lo recibe. Su ciclo de aprendizaje es por
35
+ comando, y activarlo en su propio repositorio es decisión suya.
36
+
11
37
  ## [0.12.0] - 2026-08-16
12
38
 
13
39
  ### Cambiado
@@ -32,6 +32,14 @@
32
32
  {
33
33
  "source": "../../workflows/agent-eval.js",
34
34
  "target": ".claude/workflows/agent-eval.js"
35
+ },
36
+ {
37
+ "source": "../../workflows/agent-propose.js",
38
+ "target": ".claude/workflows/agent-propose.js"
39
+ },
40
+ {
41
+ "source": "../../workflows/agent-promote.js",
42
+ "target": ".claude/workflows/agent-promote.js"
35
43
  }
36
44
  ],
37
45
  "capabilities": {
@@ -0,0 +1,140 @@
1
+ // Aplica una propuesta ya firmada y comprueba que el cargo siga aguantando sus casos.
2
+ //
3
+ // Es el único recorrido que modifica un cargo, y por eso tiene dos candados que no se pueden saltar:
4
+ //
5
+ // 1. Se niega si «Aprobación humana» no está firmada con responsable. Un agente no se autoriza a sí
6
+ // mismo, y `automatic_apply: false` sigue siendo cierto: esto no corre solo, lo corre alguien
7
+ // después de firmar.
8
+ // 2. Corre los casos adversariales al terminar. Aplicar sin verificar deja un contrato cambiado y
9
+ // nadie sabiendo si todavía se sostiene, que es peor que no haber aplicado.
10
+ //
11
+ // Aplica **prosa**, no un parche, y es a propósito: la propuesta dice «agregar dos viñetas después de
12
+ // la última existente». Un parche envejece si alguien toca el archivo mientras la propuesta espera
13
+ // firma; la prosa sobrevive a eso, a cambio de exigir criterio al aplicar. Ese criterio queda visible:
14
+ // toda desviación se escribe en la propia propuesta.
15
+ export const meta = {
16
+ name: 'agent-promote',
17
+ description: 'Aplica una propuesta firmada, registra el cambio y vuelve a correr los casos',
18
+ whenToUse: 'Después de firmar «Aprobación humana» en una propuesta mensual.',
19
+ phases: [
20
+ { title: 'Firma', detail: 'Sin aprobación humana no se toca nada' },
21
+ { title: 'Aplicar', detail: 'El cambio, archivo por archivo' },
22
+ { title: 'Registrar', detail: 'Historial del cargo' },
23
+ { title: 'Verificar', detail: 'Los casos adversariales contra el contrato nuevo' },
24
+ ],
25
+ }
26
+
27
+ const ROOT = '{{OPS_DIR}}'.replace(/\/+$/, '') || '.'
28
+ const AGENT = String((typeof args === 'string' ? args : (args || {}).agent) || '').trim()
29
+ const PERIOD = String((args || {}).period || '').trim()
30
+
31
+ const FIRMA = {
32
+ type: 'object', additionalProperties: false, required: ['dir', 'proposal', 'approved'],
33
+ properties: {
34
+ dir: { type: 'string' },
35
+ proposal: { type: 'string' },
36
+ approved: { type: 'boolean' },
37
+ signedBy: { type: 'string' },
38
+ state: { type: 'string' },
39
+ hasChange: { type: 'boolean' },
40
+ },
41
+ }
42
+
43
+ const APLICADO = {
44
+ type: 'object', additionalProperties: false, required: ['applied', 'files'],
45
+ properties: {
46
+ applied: { type: 'boolean' },
47
+ files: { type: 'array', items: { type: 'string' } },
48
+ deviations: { type: 'string' },
49
+ newCase: { type: 'string' },
50
+ },
51
+ }
52
+
53
+ function finish(result) {
54
+ log(`Fin: ${JSON.stringify(result)}`)
55
+ return result
56
+ }
57
+
58
+ const stop = (reason, detail = '') => {
59
+ log(`Checkpoint: ${reason}${detail ? ` — ${detail}` : ''}`)
60
+ return finish({ stopped: true, reason, detail })
61
+ }
62
+
63
+ if (!AGENT) return stop('sin-cargo', 'pasá el slug del cargo')
64
+
65
+ phase('Firma')
66
+
67
+ const firma = await agent(
68
+ `From ${ROOT}, run "node tools/ops.js agents list --json" and take the path it printed for ${AGENT}. ` +
69
+ `Set dir to "${ROOT}/<path>": that command prints paths relative to ${ROOT}.\n\n` +
70
+ `Find the newest <dir>/learning/proposals/AAAA-MM.md` +
71
+ `${PERIOD ? `, preferring ${PERIOD}.md` : ''} and set proposal to its full path. Read **only** its ` +
72
+ `"Aprobación humana" and "Cambio propuesto" sections and report, without interpreting in anyone's ` +
73
+ `favour:\n` +
74
+ `- approved: true only if the state says it is approved AND a named person is recorded. "pendiente", ` +
75
+ `"por definir" or an empty responsible means false.\n` +
76
+ `- signedBy: the person recorded, or empty.\n` +
77
+ `- state: the literal state line.\n` +
78
+ `- hasChange: true only if "Cambio propuesto" carries a concrete change; "por definir" means false.`,
79
+ { schema: FIRMA, label: 'firma' },
80
+ )
81
+ if (!firma) return stop('sin-propuesta', `no se pudo leer una propuesta de ${AGENT}`)
82
+ if (!firma.hasChange) {
83
+ return stop('propuesta-vacia', `${firma.proposal} no tiene cambio concreto: corré /agent-propose primero`)
84
+ }
85
+ if (!firma.approved) {
86
+ return stop('sin-firma', `${firma.proposal} no está aprobada (${firma.state || 'sin estado'}). ` +
87
+ 'Firmá «Aprobación humana» con un responsable y repetí: nadie se autoriza a sí mismo')
88
+ }
89
+ log(`Aprobada por ${firma.signedBy}`)
90
+
91
+ phase('Aplicar')
92
+
93
+ const aplicado = await agent(
94
+ `La propuesta ${firma.proposal} está aprobada por ${firma.signedBy}. Aplicá su sección «Cambio ` +
95
+ `propuesto» al cargo ${AGENT} en ${firma.dir}, archivo por archivo, tal como la propuesta lo ` +
96
+ `describe.\n\n` +
97
+ `Reglas al aplicar:\n` +
98
+ `- Preferí agregar sobre reescribir. Si la propuesta dice «agregar después de X», agregá; no ` +
99
+ `reordenes ni reformules lo que ya estaba.\n` +
100
+ `- Si la propuesta pide crear un caso adversarial nuevo, crealo con el enunciado que da y con ` +
101
+ `**cuatro** comportamientos esperados, como todos los del catálogo. Si el enunciado trae más, ` +
102
+ `fusioná los que se solapen sin perder ninguna idea.\n` +
103
+ `- Si algo de la propuesta ya no aplica —el archivo cambió, la sección no existe— **no lo fuerces**: ` +
104
+ `dejalo sin aplicar y reportalo como desviación.\n` +
105
+ `- Toda desviación, por chica que sea, se escribe al final de «Aprobación humana» en la propia ` +
106
+ `propuesta, explicando qué se hizo distinto y por qué. Quien firmó tiene derecho a saber qué se ` +
107
+ `aplicó de lo que firmó.\n\n` +
108
+ `No toques otros cargos. No hagas commit ni push. Devolvé qué archivos modificaste, el caso nuevo si ` +
109
+ `lo creaste, y las desviaciones —o cadena vacía si no hubo—.`,
110
+ { schema: APLICADO, label: `aplica:${AGENT}` },
111
+ )
112
+ if (!aplicado || !aplicado.applied) {
113
+ return stop('no-aplicada', 'el cambio no se pudo aplicar; la propuesta queda como estaba')
114
+ }
115
+ log(`Aplicado en: ${(aplicado.files || []).join(', ')}`)
116
+
117
+ phase('Registrar')
118
+
119
+ await agent(
120
+ `Agregá una fila a ${firma.dir}/learning/HISTORY.md con la fecha de hoy —obtenela con "date +%F"—, ` +
121
+ `la propuesta ${firma.proposal}, decisión "Aprobada", quién firmó (${firma.signedBy}) y qué se ` +
122
+ `aplicó: ${(aplicado.files || []).join(', ')}` +
123
+ `${aplicado.newCase ? ` más el caso ${aplicado.newCase}` : ''}` +
124
+ `${aplicado.deviations ? `. Desviaciones: ${aplicado.deviations}` : ''}.\n\n` +
125
+ `Respetá el formato de tabla que ya tiene el archivo. No toques ninguna otra cosa, no hagas commit.`,
126
+ { label: 'historial' },
127
+ )
128
+
129
+ phase('Verificar')
130
+
131
+ log('El contrato cambió: los casos valen sólo si se vuelven a correr contra la versión nueva.')
132
+ return finish({
133
+ agent: AGENT,
134
+ proposal: firma.proposal,
135
+ signedBy: firma.signedBy,
136
+ files: aplicado.files || [],
137
+ newCase: aplicado.newCase || '',
138
+ deviations: aplicado.deviations || '',
139
+ next: `corré /agent-eval ${AGENT}: el registro anterior quedó viejo y no vale para el contrato nuevo`,
140
+ })
@@ -0,0 +1,117 @@
1
+ // Convierte la recomendación de los informes en el cambio concreto que una persona puede aprobar.
2
+ //
3
+ // `learn --proposal` consolida qué recomendaron los informes de la semana, pero deja «Cambio
4
+ // propuesto: por definir». Eso no es aprobable: nadie firma una intención. Este recorrido escribe el
5
+ // texto exacto que habría que agregar, archivo por archivo, y lo contrasta contra los casos
6
+ // adversariales vigentes antes de proponerlo.
7
+ //
8
+ // Nunca aplica nada, y no es una formalidad: la sección «Aprobación humana» la firma una persona, y
9
+ // hasta que esté firmada `/agent-promote` se niega a tocar el cargo.
10
+ export const meta = {
11
+ name: 'agent-propose',
12
+ description: 'Escribe el cambio concreto de una propuesta mensual y lo contrasta con los casos',
13
+ whenToUse: 'Después de que el ciclo consolide una propuesta, para dejarla en estado aprobable.',
14
+ phases: [
15
+ { title: 'Contexto', detail: 'Contrato, fuentes, conductas y casos vigentes' },
16
+ { title: 'Proponer', detail: 'El cambio exacto, sus riesgos y su evaluación' },
17
+ ],
18
+ }
19
+
20
+ // El prefijo lo completa `automation install`. El runtime de workflows no expone `process`.
21
+ const ROOT = '{{OPS_DIR}}'.replace(/\/+$/, '') || '.'
22
+ const AGENT = String((typeof args === 'string' ? args : (args || {}).agent) || '').trim()
23
+ const PERIOD = String((args || {}).period || '').trim()
24
+
25
+ const CONTEXT = {
26
+ type: 'object', additionalProperties: false, required: ['dir', 'proposal', 'cases'],
27
+ properties: {
28
+ dir: { type: 'string' },
29
+ proposal: { type: 'string' },
30
+ cases: { type: 'integer' },
31
+ hasChange: { type: 'boolean' },
32
+ },
33
+ }
34
+
35
+ const RESULT = {
36
+ type: 'object', additionalProperties: false, required: ['completed', 'files'],
37
+ properties: {
38
+ completed: { type: 'boolean' },
39
+ files: { type: 'array', items: { type: 'string' } },
40
+ newCase: { type: 'string' },
41
+ risks: { type: 'integer' },
42
+ summary: { type: 'string' },
43
+ },
44
+ }
45
+
46
+ function finish(result) {
47
+ log(`Fin: ${JSON.stringify(result)}`)
48
+ return result
49
+ }
50
+
51
+ const stop = (reason, detail = '') => {
52
+ log(`Checkpoint: ${reason}${detail ? ` — ${detail}` : ''}`)
53
+ return finish({ stopped: true, reason, detail })
54
+ }
55
+
56
+ if (!AGENT) return stop('sin-cargo', 'pasá el slug del cargo')
57
+
58
+ phase('Contexto')
59
+
60
+ const contexto = await agent(
61
+ `From ${ROOT}, run "node tools/ops.js agents list --json" and take the path it printed for ${AGENT}. ` +
62
+ `Set dir to "${ROOT}/<path>": that command prints paths relative to ${ROOT} and the next agent runs ` +
63
+ `from elsewhere, so the prefix is not optional.\n\n` +
64
+ `Then find the newest file under <dir>/learning/proposals/ named AAAA-MM.md` +
65
+ `${PERIOD ? `, preferring ${PERIOD}.md` : ''}. Set proposal to its full path. Read it and set ` +
66
+ `hasChange to true only if its "Cambio propuesto" section already carries a concrete change —if it ` +
67
+ `still says "por definir", it is false.\n\n` +
68
+ `Finally run "node tools/ops.js evaluate ${AGENT} --cases" and set cases to how many it listed. ` +
69
+ `Report only what the commands printed.`,
70
+ { schema: CONTEXT, label: 'contexto' },
71
+ )
72
+ if (!contexto) return stop('sin-contexto', `no se pudo ubicar el cargo ${AGENT}`)
73
+ if (contexto.hasChange) {
74
+ return stop('ya-propuesta', `${contexto.proposal} ya tiene un cambio concreto; revisalo o borralo antes`)
75
+ }
76
+ log(`${contexto.proposal} · ${contexto.cases} caso(s) vigentes`)
77
+
78
+ phase('Proponer')
79
+
80
+ const propuesta = await agent(
81
+ `Sos quien mantiene el cargo ${AGENT}. La propuesta ${contexto.proposal} consolidó lo que ` +
82
+ `recomendaron los informes semanales, pero le falta lo único que una persona puede aprobar: el ` +
83
+ `cambio concreto.\n\n` +
84
+ `Leé los informes citados en su sección «Hallazgos», el SKILL.md del cargo, ` +
85
+ `${contexto.dir}/learning/sources.yaml, ${contexto.dir}/evaluations/expected-behaviors.yaml y sus ` +
86
+ `casos en ${contexto.dir}/evaluations/cases/. Después completá **sólo** estas tres secciones de la ` +
87
+ `propuesta, sin tocar ninguna otra ni el frontmatter:\n\n` +
88
+ `**Cambio propuesto**: el texto exacto a agregar o reemplazar, archivo por archivo, citando la ` +
89
+ `sección o la línea que se toca. Que se pueda aplicar leyéndolo, sin volver al informe. Preferí ` +
90
+ `agregar sobre reescribir: el núcleo del contrato no está en discusión. Si una fuente no se pudo ` +
91
+ `corroborar, dejalo escrito en el propio texto en vez de afirmarla.\n\n` +
92
+ `**Riesgos y regresiones**: qué caso adversarial existente podría empezar a fallar y qué parte del ` +
93
+ `contrato podría contradecirse. Si dos reglas quedan en tensión aparente, resolvela en el texto ` +
94
+ `propuesto, no la dejes para quien aplique.\n\n` +
95
+ `**Evaluación**: contrastá el cambio contra los ${contexto.cases} casos vigentes, uno por uno, y ` +
96
+ `decí si cada uno sigue pasando. Si una conducta prohibida nueva no tiene caso que la distinga de ` +
97
+ `las que ya están, escribí el enunciado del caso que haría falta —con **cuatro** comportamientos ` +
98
+ `esperados, como todos los del catálogo— y **no crees el archivo**: cambiar el denominador de la ` +
99
+ `evaluación es parte de lo que se aprueba.\n\n` +
100
+ `No modifiques SKILL.md, sources.yaml, expected-behaviors.yaml ni los casos: esto es una propuesta, ` +
101
+ `no su aplicación. No toques «Aprobación humana»: esa la firma una persona. No hagas commit ni push. ` +
102
+ `El único archivo que escribís es la propuesta.\n\n` +
103
+ `Devolvé qué archivos tocaría el cambio, el enunciado del caso nuevo si lo hay, cuántos riesgos ` +
104
+ `identificaste y un resumen en una frase.`,
105
+ { schema: RESULT, label: `propone:${AGENT}` },
106
+ )
107
+ if (!propuesta) return stop('sin-propuesta', 'el agente no devolvió resultado')
108
+
109
+ log(`Tocaría: ${(propuesta.files || []).join(', ')}`)
110
+ return finish({
111
+ agent: AGENT,
112
+ proposal: contexto.proposal,
113
+ files: propuesta.files || [],
114
+ risks: propuesta.risks || 0,
115
+ newCase: propuesta.newCase || '',
116
+ next: 'firmá «Aprobación humana» y corré /agent-promote',
117
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingeniomaps/cauce",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Sistema portable de planificación y ejecución verificable para cualquier proyecto",
5
5
  "type": "commonjs",
6
6
  "bin": {