feat(ci): revisor de PRs con varios modelos por OpenRouter #2

Merged
julio.caicedo merged 7 commits from feat/revisor into main 2026-10-15 21:31:08 +00:00

Plantilla reusable _revision.yaml más el programa cmd/reviewer: varios modelos de OpenRouter revisan el diff de un PR contra los criterios de aceptación del propio repositorio, y un verificador descarta todo hallazgo sin cita textual del plan.

Comenta, nunca frena.

Incluye go.yaml, que corre gofmt, go vet y go test -race sobre el código de este repositorio. Hasta ahora nada lo probaba: todas las demás plantillas empiezan con _ y solo arrancan por workflow_call.

Para usar la plantilla hace falta, a nivel de organización:

  • secreto OPENROUTER_API_KEY
  • variable REVISOR_MODELS (ids de OpenRouter separados por coma)

Sin REVISOR_MODELS el programa no arranca: no hay modelos por defecto a propósito.

Seguridad: el revisor se compila siempre desde este repositorio y del PR se leen únicamente datos, en dos checkouts separados. Nada controlado por el PR se interpola en un run:.

Plantilla reusable `_revision.yaml` más el programa `cmd/reviewer`: varios modelos de OpenRouter revisan el diff de un PR contra los criterios de aceptación del propio repositorio, y un verificador descarta todo hallazgo sin cita textual del plan. Comenta, nunca frena. Incluye `go.yaml`, que corre gofmt, `go vet` y `go test -race` sobre el código de este repositorio. Hasta ahora nada lo probaba: todas las demás plantillas empiezan con `_` y solo arrancan por `workflow_call`. Para usar la plantilla hace falta, a nivel de organización: - secreto `OPENROUTER_API_KEY` - variable `REVISOR_MODELS` (ids de OpenRouter separados por coma) Sin `REVISOR_MODELS` el programa no arranca: no hay modelos por defecto a propósito. Seguridad: el revisor se compila siempre desde este repositorio y del PR se leen únicamente datos, en dos checkouts separados. Nada controlado por el PR se interpola en un `run:`.
feat: plantilla de revisión de PRs con varios modelos
Some checks failed
_analytics.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_build-docker.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_deploy-k8s.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_go-analytics.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_go-build.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_go-deploy.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_go-test.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_revision.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
_test.yaml / feat: plantilla de revisión de PRs con varios modelos (push) Failing after 0s
0e148dfbd5
Revisa un pull request contra los criterios de aceptación del propio
repositorio. Cada hallazgo tiene que citar una frase textual del plan; la
cita se verifica contra el documento y la que no aparece se descarta. Tres
modelos que leen mal el mismo párrafo no hacen verdad, el documento sí.

Nada es de un repositorio en particular: el dominio, los tres archivos del
plan y las frases que no se publican son entradas de la plantilla. Lo único
que el repositorio que la use tiene que aceptar es la convención de cómo se
escriben las fichas y los requisitos, y que el plan viva en el árbol: un
criterio que vive fuera del repositorio no se le puede exigir a un PR,
porque no se sabe en qué versión estaba cuando se escribió el código.

Primer corte: deja el borrador en el log y no escribe en el pull request.

Seguridad: el revisor se compila desde este repositorio, nunca desde el PR;
del PR se leen solo datos, con os.Root para que un symlink no salga de docs/;
y todo lo que se imprime va saneado, porque el log del runner obedece las
líneas que empiezan por "::" o "##[".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
fix: confinar la lectura a una raíz que fija la plantilla (revisor)
Some checks failed
_analytics.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_build-docker.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_deploy-k8s.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_go-analytics.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_go-build.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_go-deploy.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_go-test.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_revision.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
_test.yaml / fix: confinar la lectura a una raíz que fija la plantilla (revisor) (push) Failing after 0s
4e62c8436a
Al volver la plantilla genérica, el directorio del plan y los tres nombres
de archivo pasaron a ser entradas del repositorio que se revisa — y la raíz
de lectura se abría en el directorio que llegaba por entrada. Con
-docs "docs/../.." la raíz quedaba fuera del árbol del pull request y el
programa leía cualquier archivo del runner, que después termina dentro del
prompt que se le manda a un tercero.

Ahora la raíz la fija la plantilla con -raiz ../pr, que no es una entrada, y
todo lo configurable se resuelve adentro: os.Root no deja salir ni con "..",
ni con una ruta absoluta, ni siguiendo un symlink. Las cuatro rutas de escape
están en la prueba, además del uso legítimo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
style(ci): nombres de jobs y pasos en verbo más objeto concreto
Some checks failed
_analytics.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_build-docker.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_deploy-k8s.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_go-analytics.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_go-build.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_go-deploy.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_go-test.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_revision.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
_test.yaml / style(ci): nombres de jobs y pasos en verbo más objeto concreto (push) Failing after 0s
8ce48e4ef5
Los nombres que se ven en la pestaña de Actions eran lo primero que lee
alguien que llega a un run rojo, y varios no decían qué había pasado:
"¿Hay dependencias que cachear?" era una pregunta, "gofmt" y "QEMU" eran
la herramienta sin la acción, y "Analizar", "Verificar", "Migrar" y
"Resumen" se repetían en jobs distintos sin decir sobre qué.

Ahora todos siguen la misma forma: verbo en infinitivo más el objeto
concreto, con la herramienta nombrada cuando es el punto del paso
("Analizar las dependencias con govulncheck"). Los jobs de
_go-analytics pasan a decir qué buscan y entre paréntesis con qué
("Secretos en el historial (gitleaks)"), que es lo que hace falta
cuando el run falla y hay cuatro jobs en la lista.

Solo cambian nombres visibles, los comentarios de cabecera y una línea
del README. Los identificadores de los jobs quedan iguales, así que ni
los "needs:" ni las referencias de los repos que llaman a estas
plantillas se mueven.

El juego genérico multi-lenguaje (_test, _analytics, _build-docker,
_deploy-k8s) queda como está: sus nombres ya eran formales y están en
inglés, y traducirlos es otra decisión.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
style(ci): nombres de jobs y pasos en inglés y cortos
Some checks failed
_analytics.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_build-docker.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_deploy-k8s.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_go-analytics.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_go-build.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_go-deploy.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_go-test.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_revision.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
_test.yaml / style(ci): nombres de jobs y pasos en inglés y cortos (push) Failing after 0s
af9d699dc2
El commit anterior los dejó en español y descriptivos, y descriptivo se
volvió largo: "Esperar a que PostgreSQL acepte conexiones" ocupa más de
lo que la pestaña de Actions muestra sin cortar, y la pestaña es el
único lugar donde se leen.

Quedan en inglés, de una a cuatro palabras, con la misma forma en los
cinco archivos: "Wait for Postgres", "Coverage gate", "Run govulncheck",
"Rollback", "Summary". El juego genérico multi-lenguaje entra en el
mismo criterio, que ya estaba en inglés pero con tres variantes de lo
mismo ("Checkout Code", "Checkout code", "Checkout") y con "Setup Go"
donde la acción oficial se llama "Set up Go".

El borrador del revisor pasa a review-draft.md, que es el nombre con el
que se descarga.

Los identificadores de los jobs siguen sin moverse, así que los
"needs:" y las referencias de los repos que llaman a estas plantillas
quedan igual. Los comentarios y el README siguen en español: son prosa,
no nombres.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer)
Some checks failed
_analytics.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_build-docker.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_deploy-k8s.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_go-analytics.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_go-build.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_go-deploy.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_go-test.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_revision.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
_test.yaml / refactor: el revisor pasa a inglés, salvo lo que se publica (reviewer) (push) Failing after 0s
ac98bfd96b
El código estaba en español y el repositorio de plantillas lo van a
leer repos que no son Balanzio. Pasa a inglés: identificadores,
comentarios, nombres de flags, mensajes de error y el nombre del
paquete, que queda cmd/reviewer.

Lo que NO pasa a inglés, y es la mitad del trabajo:

Los comentarios que el bot publica. El prompt del modelo, los
encabezados del material y cada título y etiqueta del borrador quedan
en español carácter por carácter: "## Con cita verificada", "**Cita
del plan:**", "## Modelos que no respondieron". El equipo lee la
revisión, no el código.

Los tags JSON. El prompt le pide al modelo
{"hallazgos":[{"archivo":…,"linea":…,"criterio":…,"cita":…,"que":…}]},
así que los campos de Go son File, Line, Criterion, Quote y What pero
los tags siguen diciendo archivo, linea, criterio, cita y que.
Traducir el tag deja los cinco campos en cero con el JSON bien
formado, y ninguna de las 15 pruebas lo detecta: todas construyen los
structs en Go y no pasan por el Unmarshal. Se verificó aparte, metiendo
el JSON de ejemplo que está dentro de `rules` por ExtractFindings.

Los literales que parsean el plan. "· Requisitos:", los prefijos RF- y
RNF-, la regla de D más dígitos, los encabezados con — y ·, la fila
| D69 |, el prefijo de código de bloque y los marcadores :: y ##[ del
saneado. Eso lee Markdown en español: es formato de datos, no idioma.
Se comparó contra la versión anterior y son idénticos.

La línea "Tarjeta: <nombre>" de la descripción del PR y el comando
/revisar también se quedan: son la interfaz que el equipo escribe.

El orden de safe() sobrevive intacto, que era lo otro frágil: los
caracteres de control se reemplazan por espacio ANTES de partir los
pares, nunca al revés.

Los flags cambian, así que esta plantilla y el repo que la llama se
actualizan en el mismo paso. REVISOR_MODELOS pasa a REVISOR_MODELS:
la variable todavía no existe en Forgejo, así que el cambio no rompe
nada hoy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
ci: correr vet y las pruebas del código Go de este repositorio
Some checks failed
Go / Test (pull_request) Successful in 34s
_analytics.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_build-docker.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_deploy-k8s.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_go-analytics.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_go-build.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_go-deploy.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_go-test.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_revision.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_test.yaml / ci: correr vet y las pruebas del código Go de este repositorio (push) Failing after 0s
_analytics.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_build-docker.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_deploy-k8s.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_go-analytics.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_go-build.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_go-deploy.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_go-test.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_revision.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
_test.yaml / ci: correr vet y las pruebas del código Go de este repositorio (pull_request) Failing after 0s
a89fbf0850
Todas las plantillas de acá empiezan con "_" y solo arrancan por
workflow_call, o sea que corren sobre el código del repositorio que las
llama. Ninguna se dispara sola. Con el revisor entró código Go con
pruebas a un repositorio donde nada las corría: el próximo que lo toque
lo puede romper y el pull request pasa en verde.

Entra go.yaml, sin "_" porque sí se dispara, con gofmt, go vet y
go test -race en pull_request y push a main. No llama a _go-test.yaml a
propósito: esa levanta Postgres, corre e2e con compose y exige 90% de
cobertura excluyendo /cmd/, y todo el código de este repositorio vive
en /cmd/, así que la cobertura daría cero y el job fallaría siempre.

Al correr los tres pasos a mano antes de subirlos salieron dos cosas:

examples/Dockerfile.go era un Dockerfile con extensión .go. Cualquier
herramienta de Go corrida en la raíz intenta parsearlo y muere en el
"#" de la primera línea, así que go vet ./... y go test ./... fallaban
los dos. Pasa a examples/go.Dockerfile, que es la convención, y el
README apunta al nombre nuevo. Nadie lo había notado porque hasta ahora
no había código Go en este repositorio.

Y el paso de gofmt ocultaba eso mismo: gofmt -l manda los errores de
parseo a stderr y sale con 0, así que mirar solo su salida estándar
deja pasar en verde un archivo que gofmt no pudo leer. Ahora se juntan
las dos salidas. Verificado metiendo un .go inválido a propósito: lo
detecta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
fix(ci): sacar el bloque secrets del workflow_call del revisor
Some checks failed
_analytics.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_build-docker.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_analytics.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_build-docker.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_deploy-k8s.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_go-analytics.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_go-build.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_go-deploy.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_go-test.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_test.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (pull_request) Failing after 0s
_analytics.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_build-docker.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_deploy-k8s.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_deploy-k8s.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_go-analytics.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_go-build.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_go-deploy.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_go-test.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
_test.yaml / fix(ci): sacar el bloque secrets del workflow_call del revisor (push) Failing after 0s
Go / Test (pull_request) Successful in 34s
_go-analytics.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_go-build.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_go-deploy.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_go-test.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
_test.yaml / Merge pull request 'feat(ci): revisor de PRs con varios modelos por OpenRouter' (#2) from feat/revisor into main (pull_request) Failing after 0s
409d055b66
Forgejo 15.0.9 no soporta "secrets" dentro de workflow_call. Al leer el
archivo como workflow propio del repositorio lo rechaza entero:

  invalid value on key "workflow_call": workflow_call only supports keys
  "inputs" and "outputs", but key "secrets" was found

El secreto llega igual: el llamador lo pasa en jobs.<id>.secrets y Forgejo
lo entrega sin que la plantilla lo declare. Es como ya funcionan en verde
los pipelines de balanzio/api con REGISTRY_USER, REGISTRY_PASSWORD,
SONARQUBE_TOKEN y KUBECONFIG.

Las otras ocho plantillas tienen el mismo bloque y el mismo fallo, que es
anterior a esta rama. Se sacan aparte, una vez verificado acá: tocan el
pipeline que ya corre en produccion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012oVizRo2WB4FeZVRnHr9Ye
julio.caicedo deleted branch feat/revisor 2026-10-15 21:31:09 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
startcodex/ci!2
No description provided.