Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e56e7a

Browse filesBrowse files
committed
docs: plan to close codejob loop on PR merge via GitHub Action
Propose 'codejob --init-action' (scaffold .github/workflows/codejob.yml) and a 'codejob --ci-publish' mode the action invokes on PR merge. The gate combines merged==true with the presence of docs/PLAN.md so that closing from the web publishes via CI while closing on the PC keeps using the local flow, with no double publish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fwEi4DViv5uveAWLLjsuS
1 parent d7692ea commit 0e56e7a
Copy full SHA for 0e56e7a

1 file changed

+263Lines changed: 263 additions & 0 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎docs/PLAN.md‎

Copy file name to clipboard
+263Lines changed: 263 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
---
2+
PLAN: "feat: codejob action subcommand to close the loop on PR merge from CI"
3+
TAG: v0.5.0
4+
---
5+
6+
# Plan — Cerrar el loop de `codejob` desde el merge (GitHub Action)
7+
8+
## 1. Problema (justificación)
9+
10+
Hoy `codejob` cierra el loop **solo en local**. El flujo típico cuando un agente
11+
(Jules u otro) termina su tarea es:
12+
13+
1. El agente abre un PR.
14+
2. Reviso el PR desde la tablet — todo bien.
15+
3. **Tengo que ir a la PC** y ejecutar `codejob` → descarga el repo, hace fetch y
16+
posiciona el árbol en la rama del PR (fase `review`).
17+
4. Ejecuto **otra vez** `codejob 'mensaje'` → fusiona el PR y publica la nueva
18+
versión (`gopush`: tests, tag, cross-compile, cascade, backup).
19+
20+
El paso 3–4 **obliga a estar físicamente frente al computador** con las
21+
credenciales locales (keyring de Jules, PAT de GitHub, `.env` con la sesión
22+
`CODEJOB`). Cuando la revisión ya se hizo desde el móvil y "todo está bien", ese
23+
viaje a la PC es puro overhead: lo único que falta es *fusionar y publicar*, algo
24+
que no requiere criterio humano adicional.
25+
26+
**Objetivo:** poder **cerrar el PR fusionándolo desde GitHub (móvil/web)** y que
27+
la nueva versión se publique automáticamente, sin abrir la PC. El flujo local
28+
actual se mantiene intacto como alternativa.
29+
30+
## 2. Propuesta
31+
32+
Dos piezas:
33+
34+
### 2.1 Nuevo subcomando: `codejob --init-action`
35+
36+
Genera `.github/workflows/codejob.yml` en el repo **si no existe** (idempotente:
37+
si ya existe, no lo sobreescribe salvo `--force`). El workflow se llama `codejob`.
38+
39+
- Escribe el YAML embebido (`go:embed`) en `.github/workflows/codejob.yml`.
40+
- Verifica/registra los secrets que el publish en CI necesite, reutilizando el
41+
`GitHub.SetSecret` / `ListSecrets` que **ya existen** en `github_secrets.go`.
42+
- No toca nada más; el usuario revisa, commitea y pushea el workflow una sola vez.
43+
44+
### 2.2 GitHub Action `codejob` (publica al fusionar)
45+
46+
El workflow se activa **solo** cuando se cierra un PR **y** ese cierre es un
47+
**merge** **y** se trata de la finalización de un `codejob`. La discriminación es
48+
la clave del diseño (§3).
49+
50+
Al dispararse, corre en CI el equivalente al *close-loop* de `codejob 'mensaje'`
51+
(publish vía `gopush`): tests → tag → cross-compile/release → limpieza. El
52+
mensaje y el tag salen del **frontmatter de `docs/PLAN.md`** (`PLAN:` / `TAG:`),
53+
exactamente la misma fuente de verdad que ya usa el flujo local vía
54+
`CHECK_PLAN.md`.
55+
56+
## 3. Decisión de diseño clave — cómo distinguir "finalizando un codejob"
57+
58+
El requisito "el action solo debe activarse si es un merge y si estamos
59+
finalizando un codejob" necesita una señal **fiable y sin intervención local**.
60+
61+
La señal elegida: **la presencia de `docs/PLAN.md` (con frontmatter válido) en la
62+
rama por defecto tras el merge.**
63+
64+
Por qué funciona y por qué es robusta frente al doble-publish:
65+
66+
| Escenario de cierre | ¿Queda `docs/PLAN.md` en la rama base? | Quién publica |
67+
|---|---|---|
68+
| **Desde tablet/web** (merge en GitHub) | **** — el flujo local nunca corrió, nunca renombró `PLAN.md``CHECK_PLAN.md` | **La Action** |
69+
| **Desde la PC** (`codejob 'msg'`) | **No** — el flujo local ya renombró/borró `PLAN.md` y lo publicó | El flujo local |
70+
71+
Esto hace ambos caminos **mutuamente excluyentes por construcción**: la Action
72+
solo actúa cuando el `PLAN.md` sigue presente, es decir, cuando el loop **no** se
73+
cerró en local. No hace falta coordinación ni locks. (Recordar: en dispatch,
74+
`Send()` commitea y pushea `docs/PLAN.md` con su frontmatter, así que el archivo
75+
está en el historial y viaja a la rama del agente y al merge.)
76+
77+
- **Trigger YAML:** `pull_request: types: [closed]` sobre la rama por defecto.
78+
No se usa filtro `paths:` porque el PR del agente puede **no** modificar
79+
`PLAN.md` (si el agente no lo tocó, el diff no lo incluye y `paths` no
80+
dispararía). El filtrado real se hace en el *guard* del job.
81+
- **Guard del job:** `if: github.event.pull_request.merged == true` (descarta PRs
82+
cerrados sin merge) **más** un paso que verifica que `docs/PLAN.md` existe y
83+
tiene frontmatter válido; si no, el job termina en *no-op* (exit 0).
84+
- **Anti-re-trigger:** tras publicar, la Action **borra `docs/PLAN.md`** y commitea
85+
la limpieza. Ese commit es un `push` directo, no un `pull_request`, así que no
86+
vuelve a disparar el workflow. Y el gate queda cerrado para futuros eventos.
87+
88+
## 4. Diagramas
89+
90+
### 4.1 Flujo actual (el cuello de botella)
91+
92+
```mermaid
93+
flowchart TD
94+
A[Escribo docs/PLAN.md] --> B[codejob: dispatch al agente]
95+
B --> C[Agente trabaja y abre PR]
96+
C --> D[Reviso el PR desde la tablet - OK]
97+
D --> E{Cerrar el loop}
98+
E --> F[IR A LA PC]
99+
F --> G[codejob: fetch + checkout rama PR<br/>fase review]
100+
G --> H[codejob 'mensaje':<br/>merge PR + gopush publica]
101+
H --> I[Nueva versión + tag publicados]
102+
103+
style F fill:#ffdddd,stroke:#c00,stroke-width:2px
104+
style E fill:#fff3cd,stroke:#d90
105+
```
106+
107+
### 4.2 Flujo propuesto (cerrar desde el móvil)
108+
109+
```mermaid
110+
flowchart TD
111+
subgraph SETUP["Una sola vez por repo"]
112+
S1[codejob --init-action] --> S2[.github/workflows/codejob.yml<br/>+ secrets] --> S3[commit + push del workflow]
113+
end
114+
115+
A[Escribo docs/PLAN.md] --> B[codejob: dispatch al agente]
116+
B --> C[Agente trabaja y abre PR]
117+
C --> D[Reviso el PR desde la tablet - OK]
118+
D --> E[Merge del PR en GitHub<br/>desde el movil/web]
119+
120+
E --> GA{Action codejob}
121+
GA -->|PR merged == true?| GA2
122+
GA2{docs/PLAN.md presente<br/>con frontmatter?}
123+
GA2 -->|No: cerrado en local| NOOP[no-op, exit 0]
124+
GA2 -->|Si: finalizando codejob| P[CI: setup Go<br/>tests + gopush<br/>tag + release]
125+
P --> CLEAN[Borra docs/PLAN.md + commit limpieza]
126+
CLEAN --> I[Nueva version + tag publicados]
127+
128+
ALT[Alternativa: sigo pudiendo<br/>correr codejob 'msg' en la PC] -.-> I
129+
130+
style E fill:#d4edda,stroke:#28a745,stroke-width:2px
131+
style GA fill:#cce5ff,stroke:#004085
132+
```
133+
134+
## 5. Alcance de la implementación
135+
136+
### 5.1 Archivos nuevos
137+
138+
- `codejob_action.go` — lógica del subcomando:
139+
- `InitCodejobAction(force bool) error`: crea `.github/workflows/` si falta,
140+
escribe el YAML embebido si no existe (o con `--force`), y devuelve mensajes
141+
claros (creado / ya existe / sobreescrito).
142+
- Plantilla del workflow embebida con `//go:embed templates/codejob_action.yml`.
143+
- `templates/codejob_action.yml` — el workflow (ver §5.3).
144+
- `codejob_action_test.go` (en `test/`) — cobertura (ver §6).
145+
146+
### 5.2 Archivos modificados
147+
148+
- `cli.go` — parsear el flag `--init-action` (y `--force`) en `ParseCodeJobArgs`,
149+
devolviendo un nuevo booleano `isInitAction`.
150+
- `cmd/codejob/main.go` — atender `--init-action` antes del flujo normal
151+
(igual que ya hace con `--reset-gh-token`): llamar a `InitCodejobAction`,
152+
imprimir resultado y salir.
153+
- `cmd/codejob/main.go` `showHelp()` — documentar el subcomando.
154+
- `docs/CODEJOB.md` y `docs/diagrams/CODEJOB_FLOW.md` — documentar el modo CI y
155+
actualizar la tabla de uso.
156+
157+
### 5.3 Contenido del workflow (`.github/workflows/codejob.yml`)
158+
159+
Diseño técnico (basado en `docs/codejob/RUNNER_BEST_PRACTICES.md` — se usa
160+
`ubuntu-latest` porque hay compilación Go cross-platform, caso C de ese doc):
161+
162+
```yaml
163+
name: codejob
164+
on:
165+
pull_request:
166+
types: [closed]
167+
permissions:
168+
contents: write # crear tag, release, push de limpieza
169+
jobs:
170+
publish:
171+
# Solo merges reales (descarta PR cerrados sin fusionar)
172+
if: github.event.pull_request.merged == true
173+
runs-on: ubuntu-latest
174+
steps:
175+
- uses: actions/checkout@v4
176+
with:
177+
ref: ${{ github.event.pull_request.base.ref }}
178+
fetch-depth: 0 # historial completo para tags/gopush
179+
- name: Gate — ¿estamos finalizando un codejob?
180+
id: gate
181+
run: |
182+
# PLAN.md presente con frontmatter => cierre desde web (no se cerró en local)
183+
if [ -f docs/PLAN.md ] && head -1 docs/PLAN.md | grep -q '^---'; then
184+
echo "run=true" >> "$GITHUB_OUTPUT"
185+
else
186+
echo "run=false" >> "$GITHUB_OUTPUT"
187+
echo "No hay codejob pendiente; no-op."
188+
fi
189+
- uses: actions/setup-go@v5
190+
if: steps.gate.outputs.run == 'true'
191+
with: { go-version: 'stable' }
192+
- name: Publicar (close-loop en CI)
193+
if: steps.gate.outputs.run == 'true'
194+
env:
195+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
196+
run: |
197+
go install github.com/tinywasm/devflow/cmd/codejob@latest
198+
codejob --ci-publish # nuevo modo: lee frontmatter, publica, limpia PLAN.md
199+
```
200+
201+
> El detalle exacto de los steps (cache de módulos, versión pinneada de la
202+
> herramienta en vez de `@latest`, publicación a repo público separado cuando el
203+
> origin es privado) se afina en implementación. Lo esencial es el par
204+
> **trigger `closed` + guard `merged` + gate `PLAN.md`**.
205+
206+
### 5.4 Nuevo modo `codejob --ci-publish`
207+
208+
Ruta de ejecución **no interactiva** pensada para el runner, que reutiliza la
209+
maquinaria existente sin depender del estado local (`.env`/keyring/PAT):
210+
211+
- No hay sesión `CODEJOB` en `.env` (el runner es efímero): se salta el manejo de
212+
fases; el PR **ya está fusionado** por GitHub.
213+
- Lee `docs/PLAN.md` con `ReadPlanMeta` → `message` y `tag` (`PLAN:`/`TAG:`).
214+
- Borra `docs/PLAN.md` (equivale al borrado de `CHECK_PLAN.md` del flujo local).
215+
- Llama al `Publisher.Publish(message, tag, ...)` completo (deps + tag + release),
216+
usando `GITHUB_TOKEN` para push/tag/release.
217+
- **No** despacha planes encadenados en CI (requiere API key de Jules); si tras el
218+
merge hay un `PLAN.md` nuevo distinto, se deja como *follow-up* opcional (§7).
219+
220+
Esto mantiene una única fuente de verdad para el mensaje/tag (frontmatter) y
221+
reaprovecha `gopush`/`gorelease` sin duplicar lógica.
222+
223+
## 6. Pruebas (test map)
224+
225+
| Comportamiento | Test |
226+
|---|---|
227+
| `InitCodejobAction` crea el workflow si no existe | `TestInitCodejobAction_CreatesWhenAbsent` |
228+
| Es idempotente (no sobreescribe sin `--force`) | `TestInitCodejobAction_NoOverwrite` |
229+
| `--force` sí sobreescribe | `TestInitCodejobAction_ForceOverwrites` |
230+
| El YAML embebido es válido y contiene el guard `merged` + gate `PLAN.md` | `TestCodejobActionTemplate_Contract` |
231+
| `ParseCodeJobArgs` detecta `--init-action` / `--force` | `TestParseCodeJobArgs_InitAction` |
232+
| `--ci-publish` con `PLAN.md` válido publica con msg/tag del frontmatter | `TestCIPublish_UsesFrontmatter` |
233+
| `--ci-publish` sin `PLAN.md` es no-op | `TestCIPublish_NoopWhenNoPlan` |
234+
235+
## 7. Riesgos y decisiones abiertas (para tu aprobación)
236+
237+
1. **Señal de discriminación.** Propongo *presencia de `docs/PLAN.md`* por ser
238+
cero-fricción. Alternativa más explícita: una **label `codejob`** en el PR
239+
(más autodocumentada, pero requiere que algo la ponga sin la PC — habría que
240+
añadir el etiquetado al abrir/detectar el PR). ¿Prefieres archivo o label?
241+
2. **Repos privados con distribución pública.** `gorelease` ya soporta publicar a
242+
un repo público derivado cuando el origin es privado; en CI eso exige un **PAT
243+
con acceso al repo público** como secret (`SetSecret` ya existe). Si el repo es
244+
público, basta `GITHUB_TOKEN`. ¿Incluyo el registro del secret en
245+
`--init-action`?
246+
3. **Planes encadenados en CI.** El re-dispatch automático necesita la API key de
247+
Jules como secret. Lo dejo **fuera** del alcance inicial (queda solo-local),
248+
salvo que lo quieras dentro.
249+
4. **Cascade/backup en CI.** El backup asíncrono y el cascade a módulos
250+
dependientes asumen entorno local; en CI conviene `--no-cascade` y omitir
251+
backup. Propongo publicar **solo el módulo** en CI y dejar el cascade al flujo
252+
local. ¿De acuerdo?
253+
254+
## 8. Resumen
255+
256+
Añadir `codejob --init-action` (scaffolding one-shot del workflow) y un modo
257+
`codejob --ci-publish` que la Action invoca al fusionar un PR de codejob. El
258+
disparo es preciso — merge real **y** `docs/PLAN.md` aún presente — lo que separa
259+
limpiamente el cierre-desde-web (publica la Action) del cierre-en-PC (publica el
260+
flujo local), sin doble publicación. Resultado: cerrar el PR desde el móvil
261+
publica la versión, sin tocar la PC.
262+
</content>
263+
</invoke>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.