[ADD] ci: Forgejo Actions CI and Claude bots ported from ecocentral

Same system as ecocentral-18 (see its docs/CI-CD.md):

- ci.yml: lint (pre-commit), test (tools/ci/run_tests.py, OCA-style
  two-phase install) and review (PR bot, after lint and test).
- issue-bot.yml: the issue bot answers issues or opens a PR on bot/issue-N.
- pr-bot.yml: review when WIP is removed or the i18n label is added.
- .gitea/issue-bot.md: context for the bots; this repo is shared by
  several clients.

The bot scripts are copies of ecocentral's. Only the defaults differ (repo,
token and state files, i18n languages es,eu,ca,gl) along with the [I18N]
commit message and the references to the runner's sources.txt.

The jobs run on a second forgejo-runner registration (CriptoMart org) on
ecocentral's runner machine, sharing its image, mounts and cache. The
runner files (Dockerfile, update.sh, sources.txt) live in ecocentral.
Specific to this repo:

- ODOO_SRC_SKIP: the runner's /odoo-src/18.0 holds a clone of addons-cm,
  so test skips it and checks the PR checkout instead.
- BOT_BASE_BRANCH "18.0": the repo's default branch is 16.0.
- test PostgreSQL on port 54330: ecocentral's runs on 54329 and a test job
  from each repo can run at the same time.

The lint job fails until the repo is cleaned up with
pre-commit run --all-files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-10-04 14:14:22 +02:00
parent fad510f69e
commit d9d9e8be2d
Signed by: snt
GPG key ID: A9FD34930EADBE71
13 changed files with 2154 additions and 0 deletions

157
tools/issue_bot/README.md Normal file
View file

@ -0,0 +1,157 @@
# Bot de incidencias
Atiende con Claude las incidencias de `CriptoMart/addons-cm` en Forgejo: responde
las preguntas funcionales o de configuración y abre un PR cuando hace falta código.
- [bot.py](bot.py): orquestador. Habla con la API de Forgejo, prepara un clon
limpio, lanza `claude -p` y publica el resultado.
- [prompt.md](prompt.md): instrucciones del bot (criterios y tono), comunes a
todos los repos.
- [../../.gitea/issue-bot.md](../../.gitea/issue-bot.md): qué es este repo
(compartido entre clientes) y sus convenciones. Se añade a las instrucciones;
es lo único que cambia de un repo a otro.
- [../../.gitea/workflows/issue-bot.yml](../../.gitea/workflows/issue-bot.yml):
disparo por Actions al abrir o comentar una incidencia.
## Qué hace con cada incidencia
Actúa cuando el último mensaje de una incidencia abierta es de una persona:
| Decisión | Acción |
| ------------ | ------------------------------------------------------------ |
| `answer` | Comenta la respuesta |
| `needs_info` | Comenta pidiendo los datos que faltan |
| `code` | Commit en `bot/issue-N`, push, PR contra `18.0` y comentario |
| `none` | Nada |
No actúa sobre PR, incidencias cerradas ni las que llevan la etiqueta `no-bot`, y
deja de responder tras `BOT_MAX_REPLIES` respuestas en la misma incidencia. Nunca
fusiona ni cierra nada.
Claude trabaja sin el token de Forgejo y sin herramientas web: solo lee el repo y
las fuentes de Odoo, edita ficheros y pasa pre-commit. Un cambio que toque
`.gitea/`, `tools/ci/`, `tools/issue_bot/` o `tools/pr_bot/` se rechaza.
Los PR que abre los revisa después el [bot de revisión](../pr_bot/README.md),
que comparte con este la imagen del runner, la cuenta y los secretos.
## Puesta en marcha
En Forgejo, una vez:
1. Dar a la cuenta del bot (`claudia_eco`) permiso de escritura en código,
incidencias y PR: como colaborador con rol de escritura o desde un equipo
propio, no desde el equipo de las personas del cliente.
2. Proteger la rama `18.0` exigiendo aprobación, para que solo se fusione tras
revisión.
3. Crear la etiqueta `no-bot`.
### Con Actions
Los jobs corren en la máquina del runner de ecocentral, con su imagen (Claude
Code, pre-commit, dependencias de Odoo 18 y Chromium) y sus montajes: las
fuentes de Odoo/OCA en `/odoo-src` y la caché `issue-bot-cache`. Cómo se monta
y se mantiene (`Dockerfile`, `update.sh`, `sources.txt`, `runner-config.yml`,
cron semanal) está documentado en el `tools/issue_bot/README.md` de ecocentral,
y sus ficheros viven allí: aquí no hay copia, para no tener dos listas de
fuentes para una misma máquina.
Un runner de Forgejo solo atiende el ámbito en el que se registra, y este repo
(organización `CriptoMart`) y el de ecocentral (`Ecocentral`) están en
organizaciones distintas. Por eso en esa máquina hay un segundo registro,
hecho en la organización `CriptoMart`, que comparte imagen, montajes y caché
con el de ecocentral:
1. Con el usuario `runner`, una carpeta propia para el segundo daemon, con una
copia del `runner-config.yml` de ecocentral (misma etiqueta, `network: host`,
montajes, `valid_volumes`, `force_pull: false`, `capacity: 1`):
```bash
mkdir ~/runner-criptomart && cd ~/runner-criptomart
cp ~/<carpeta del runner de ecocentral>/runner-config.yml .
forgejo-runner register --config runner-config.yml \
--instance http://localhost:3000 --token <token> \
--name criptomart --labels issue-bot:docker://eco-issue-bot:latest
```
El token sale de los ajustes de Actions de la organización `CriptoMart`
(Ajustes → Actions → Runners → Crear nuevo runner).
2. Un servicio aparte para este daemon (copia de la unidad de systemd del de
ecocentral con `WorkingDirectory=` en `~/runner-criptomart` y
`forgejo-runner daemon --config runner-config.yml`), y arrancarlo.
3. Activar Actions en el repo.
4. Secretos del repo: `BOT_FORGEJO_TOKEN` (token de `claudia_eco`) y uno de
`ANTHROPIC_API_KEY` o `CLAUDE_CODE_OAUTH_TOKEN` (lo genera
`claude setup-token`). Son secretos por repo: no se heredan de ecocentral.
Cada daemon tiene su `capacity: 1`, así que un job de cada repo puede correr a
la vez. Por eso el PostgreSQL del job `test` de aquí escucha en el `54330` y no
en el `54329` de ecocentral. Los dos comparten la caché `issue-bot-cache`
(entornos de pre-commit y los `.pot` del bot de revisión, que van por commit).
### Tests del CI
En cada PR contra `18.0`, y en cada push a esa rama, el job `test` de
[ci.yml](../../.gitea/workflows/ci.yml) lanza
[../ci/run_tests.py](../ci/run_tests.py) con el esquema del CI de la OCA: crea
una base de datos con las dependencias de los addons y después instala los
addons con `--test-enable`, de modo que solo corren los tests del repo.
PostgreSQL es un servicio del job, en el puerto 54330 de `127.0.0.1`.
Odoo arranca desde las fuentes montadas en `/odoo-src/18.0`, así que:
- Todo repo del que dependa un addon de aquí tiene que estar en la parte
`18.0` del `sources.txt` del runner (en ecocentral). Si falta, el job falla
diciendo qué módulo no encuentra, en lugar de dejar el addon sin probar.
- Una dependencia Python nueva (`external_dependencies`) pide añadirla al
`Dockerfile` del runner (en ecocentral).
- Ambas cosas llegan al runner al ejecutar `update.sh`, no al fusionar.
`/odoo-src/18.0` trae también un clon de este repo, porque ecocentral depende
de él. El job lo salta (`ODOO_SRC_SKIP: addons-cm` en `ci.yml`) y prueba el
checkout del PR.
Los tests corren contra la OCB y la OCA de la última actualización semanal, no
contra commits fijados.
### Sin Actions (poll)
En una máquina con `claude` autenticado, `pre-commit` y el token en
`~/.config/gitea-addons-cm.token`:
```bash
*/5 * * * * flock -n /tmp/addons-cm-issue-bot.lock python3 /ruta/al/repo/tools/issue_bot/bot.py poll
```
El primer `poll` solo anota la hora: el histórico no se atiende. Para pasar el
bot por incidencias ya abiertas:
```bash
python3 tools/issue_bot/bot.py run 12 15 --dry-run # enseña lo que publicaría
python3 tools/issue_bot/bot.py run 12 15
```
## Configuración
| Variable | Por defecto |
| ------------------------------------------- | --------------------------------------------- |
| `BOT_FORGEJO_URL` | `https://git.criptomart.net` |
| `BOT_FORGEJO_REPO` | `CriptoMart/addons-cm` |
| `BOT_FORGEJO_TOKEN` / `..._TOKEN_FILE` | `~/.config/gitea-addons-cm.token` |
| `BOT_REPO_DIR` (clon propio del bot) | `~/.cache/eco-issue-bot/addons-cm` |
| `BOT_BASE_BRANCH` (`18.0` en los workflows) | rama por defecto del repo (`16.0`) |
| `BOT_ODOO_SRC` (carpetas separadas por `:`) | `/media/snt/data/odoo` |
| `BOT_MODEL` (pide Claude Code ≥ 2.1.280) | `claude-opus-5-5` |
| `BOT_MAX_BUDGET_USD` (por incidencia) | `5` |
| `BOT_TIMEOUT` (segundos) | `2400` |
| `BOT_MAX_REPLIES` | `8` |
| `BOT_STATE_FILE` | `~/.local/state/eco-issue-bot/addons-cm/since` |
`bot.py`, `prompt.md` y los ficheros de [../pr_bot/](../pr_bot/) son copia de
los de ecocentral; solo cambian los valores por defecto de esta tabla, el de
`BOT_I18N_LANGS`, las referencias al `sources.txt` del runner y el mensaje
del commit de traducciones del bot de revisión (`[I18N] …`). Un arreglo en
uno de los dos repos se copia tal cual al otro.