From 837f91060fb17e0c09af7fd38a6452cd90ea2c84 Mon Sep 17 00:00:00 2001 From: Alejandro Lembke Barrientos Date: Wed, 29 Jul 2026 00:35:59 +0000 Subject: [PATCH 1/4] Fase 2: 04_sanitize.py - scrubbing de secretos de skills/agentes/plans (10 secretos unicos, gate en verde) --- .gitignore | 3 + data/raw/sanitized/agents/ci-developer.md | 139 ++++ data/raw/sanitized/agents/code-reviewer.md | 51 ++ data/raw/sanitized/agents/developer.md | 71 ++ data/raw/sanitized/agents/docmost-reporter.md | 54 ++ data/raw/sanitized/agents/mailer.md | 46 ++ data/raw/sanitized/agents/pr-shipper.md | 46 ++ data/raw/sanitized/agents/qa-validator.md | 57 ++ .../a-que-mcps-tienes-idempotent-rivest.md | 102 +++ .../actualic-la-imagen-base-crispy-nebula.md | 82 ++ .../conectate-a-el-mcp-atomic-matsumoto.md | 53 ++ .../plans/creo-que-hay-un-rosy-unicorn.md | 73 ++ .../desde-que-cambiamos-la-wobbly-flame.md | 124 +++ ...el-bashrc-no-carga-shimmering-treehouse.md | 57 ++ .../en-el-proyecto-de-splendid-phoenix.md | 45 ++ ...tree-home-aleleba-projects-bl-pure-cook.md | 56 ++ ...nt-sh-quisiera-agregar-drifting-blossom.md | 46 ++ .../plans/en-la-funci-n-de-cheeky-dewdrop.md | 64 ++ .../plans/en-la-version-de-hashed-hoare.md | 76 ++ ...ocker-compose-yaml-quiero-memoized-star.md | 61 ++ ...-6-docker-compose-yaml-qu-parallel-karp.md | 22 + .../es-posible-agregar-mi-inherited-kahan.md | 67 ++ .../gfiber-pilot-extension-lively-sloth.md | 108 +++ .../plans/hay-errores-en-el-lively-wadler.md | 128 ++++ ...-skill-de-agent-orchestrator-snug-goose.md | 122 +++ .../mira-dockerfile-el-humming-noodle.md | 55 ++ ...ructured-sundae-agent-adc51160af6444d3c.md | 697 +++++++++++++++++ ...lanificar-la-creaci-n-structured-sundae.md | 269 +++++++ ...demos-realizar-el-plan-expressive-crown.md | 100 +++ ...des-conectarte-a-jira-delightful-bonbon.md | 89 +++ ...-conectarte-a-jira-flickering-bumblebee.md | 348 +++++++++ ...puedes-conectarte-a-jira-fluttering-yao.md | 222 ++++++ .../puedes-leer-de-docmost-virtual-rain.md | 90 +++ .../puedes-leer-de-jira-dapper-stream.md | 164 ++++ .../puedes-leer-el-espacio-atomic-lampson.md | 69 ++ ...edes-leer-el-espacio-glistening-iverson.md | 453 +++++++++++ .../plans/puedes-leer-el-plan-keen-gray.md | 103 +++ .../puedes-leer-el-plan-sprightly-pearl.md | 91 +++ .../puedes-leer-el-ticket-glimmering-fox.md | 108 +++ .../puedes-leer-el-ticket-indexed-duckling.md | 64 ++ ...des-leer-este-transcript-clever-biscuit.md | 34 + .../puedes-leer-la-skill-greedy-treehouse.md | 494 ++++++++++++ .../puedes-revisar-el-ticket-jolly-sloth.md | 65 ++ .../puedes-revisar-la-skill-linear-cray.md | 64 ++ ...es-revisar-los-tickets-cheerful-catmull.md | 112 +++ ...uedes-ver-en-elnespacio-lovely-squirrel.md | 187 +++++ .../plans/puedes-ver-la-skill-foamy-candy.md | 83 ++ .../puedes-ver-la-skill-silly-jellyfish.md | 120 +++ .../quiero-agregarle-soporte-a-toasty-dusk.md | 101 +++ .../quiero-avanzar-a-la-unified-bumblebee.md | 178 +++++ .../plans/quiero-con-la-cli-vivid-sphinx.md | 58 ++ ...quiero-crear-el-design-eventual-giraffe.md | 195 +++++ ...quiero-crear-el-desing-eventual-giraffe.md | 225 ++++++ .../quiero-crear-una-skill-bright-honey.md | 73 ++ ...o-crear-una-skill-encapsulated-thompson.md | 145 ++++ ...empezar-a-planificar-structured-blossom.md | 159 ++++ ...-hacer-entrypoint-sh-user-drifting-blum.md | 227 ++++++ ...ero-hacer-una-migracion-purrfect-goblet.md | 84 ++ .../quiero-hacer-una-skill-sprightly-moore.md | 125 +++ ...iero-mezclar-los-archivos-jolly-eclipse.md | 62 ++ .../quiero-que-comencemos-a-mighty-quill.md | 114 +++ ...iero-que-implementemos-la-silly-hamster.md | 158 ++++ .../quiero-que-investigues-el-quirky-pixel.md | 115 +++ .../quiero-que-revises-las-toasty-candy.md | 43 ++ ...alizar-dependencias-con-purring-lamport.md | 14 + ...dev-environment-vscode-serv-happy-creek.md | 117 +++ .../te-comento-el-ci-polymorphic-allen.md | 42 + .../te-comento-en-luminarrh-cozy-coral.md | 141 ++++ .../plans/te-comento-en-mi-nifty-dragon.md | 140 ++++ ...expressive-wren-agent-aa17e02b32766d789.md | 0 .../tengo-que-agregar-una-expressive-wren.md | 49 ++ .../tengo-un-problema-cuando-snazzy-kazoo.md | 87 +++ .../tengoneste-error-y-creo-zazzy-hamming.md | 84 ++ .../skills/agent-orchestrator/SKILL.md | 719 ++++++++++++++++++ data/raw/sanitized/skills/aleleba-pr/SKILL.md | 261 +++++++ .../sanitized/skills/docmost-context/SKILL.md | 164 ++++ data/raw/sanitized/skills/spark-ssh/SKILL.md | 70 ++ .../raw/sanitized/skills/web-ui-test/SKILL.md | 179 +++++ scripts/04_sanitize.py | 190 +++++ 79 files changed, 10153 insertions(+) create mode 100644 data/raw/sanitized/agents/ci-developer.md create mode 100644 data/raw/sanitized/agents/code-reviewer.md create mode 100644 data/raw/sanitized/agents/developer.md create mode 100644 data/raw/sanitized/agents/docmost-reporter.md create mode 100644 data/raw/sanitized/agents/mailer.md create mode 100644 data/raw/sanitized/agents/pr-shipper.md create mode 100644 data/raw/sanitized/agents/qa-validator.md create mode 100644 data/raw/sanitized/plans/a-que-mcps-tienes-idempotent-rivest.md create mode 100644 data/raw/sanitized/plans/actualic-la-imagen-base-crispy-nebula.md create mode 100644 data/raw/sanitized/plans/conectate-a-el-mcp-atomic-matsumoto.md create mode 100644 data/raw/sanitized/plans/creo-que-hay-un-rosy-unicorn.md create mode 100644 data/raw/sanitized/plans/desde-que-cambiamos-la-wobbly-flame.md create mode 100644 data/raw/sanitized/plans/el-bashrc-no-carga-shimmering-treehouse.md create mode 100644 data/raw/sanitized/plans/en-el-proyecto-de-splendid-phoenix.md create mode 100644 data/raw/sanitized/plans/en-el-worktree-home-aleleba-projects-bl-pure-cook.md create mode 100644 data/raw/sanitized/plans/en-entrypoint-sh-quisiera-agregar-drifting-blossom.md create mode 100644 data/raw/sanitized/plans/en-la-funci-n-de-cheeky-dewdrop.md create mode 100644 data/raw/sanitized/plans/en-la-version-de-hashed-hoare.md create mode 100644 data/raw/sanitized/plans/en-luminarrh-docker-compose-yaml-quiero-memoized-star.md create mode 100644 data/raw/sanitized/plans/en-models-qwen3-6-docker-compose-yaml-qu-parallel-karp.md create mode 100644 data/raw/sanitized/plans/es-posible-agregar-mi-inherited-kahan.md create mode 100644 data/raw/sanitized/plans/gfiber-pilot-extension-lively-sloth.md create mode 100644 data/raw/sanitized/plans/hay-errores-en-el-lively-wadler.md create mode 100644 data/raw/sanitized/plans/la-skill-de-agent-orchestrator-snug-goose.md create mode 100644 data/raw/sanitized/plans/mira-dockerfile-el-humming-noodle.md create mode 100644 data/raw/sanitized/plans/podemos-planificar-la-creaci-n-structured-sundae-agent-adc51160af6444d3c.md create mode 100644 data/raw/sanitized/plans/podemos-planificar-la-creaci-n-structured-sundae.md create mode 100644 data/raw/sanitized/plans/podemos-realizar-el-plan-expressive-crown.md create mode 100644 data/raw/sanitized/plans/puedes-conectarte-a-jira-delightful-bonbon.md create mode 100644 data/raw/sanitized/plans/puedes-conectarte-a-jira-flickering-bumblebee.md create mode 100644 data/raw/sanitized/plans/puedes-conectarte-a-jira-fluttering-yao.md create mode 100644 data/raw/sanitized/plans/puedes-leer-de-docmost-virtual-rain.md create mode 100644 data/raw/sanitized/plans/puedes-leer-de-jira-dapper-stream.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-espacio-atomic-lampson.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-espacio-glistening-iverson.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-plan-keen-gray.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-plan-sprightly-pearl.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-ticket-glimmering-fox.md create mode 100644 data/raw/sanitized/plans/puedes-leer-el-ticket-indexed-duckling.md create mode 100644 data/raw/sanitized/plans/puedes-leer-este-transcript-clever-biscuit.md create mode 100644 data/raw/sanitized/plans/puedes-leer-la-skill-greedy-treehouse.md create mode 100644 data/raw/sanitized/plans/puedes-revisar-el-ticket-jolly-sloth.md create mode 100644 data/raw/sanitized/plans/puedes-revisar-la-skill-linear-cray.md create mode 100644 data/raw/sanitized/plans/puedes-revisar-los-tickets-cheerful-catmull.md create mode 100644 data/raw/sanitized/plans/puedes-ver-en-elnespacio-lovely-squirrel.md create mode 100644 data/raw/sanitized/plans/puedes-ver-la-skill-foamy-candy.md create mode 100644 data/raw/sanitized/plans/puedes-ver-la-skill-silly-jellyfish.md create mode 100644 data/raw/sanitized/plans/quiero-agregarle-soporte-a-toasty-dusk.md create mode 100644 data/raw/sanitized/plans/quiero-avanzar-a-la-unified-bumblebee.md create mode 100644 data/raw/sanitized/plans/quiero-con-la-cli-vivid-sphinx.md create mode 100644 data/raw/sanitized/plans/quiero-crear-el-design-eventual-giraffe.md create mode 100644 data/raw/sanitized/plans/quiero-crear-el-desing-eventual-giraffe.md create mode 100644 data/raw/sanitized/plans/quiero-crear-una-skill-bright-honey.md create mode 100644 data/raw/sanitized/plans/quiero-crear-una-skill-encapsulated-thompson.md create mode 100644 data/raw/sanitized/plans/quiero-empezar-a-planificar-structured-blossom.md create mode 100644 data/raw/sanitized/plans/quiero-hacer-entrypoint-sh-user-drifting-blum.md create mode 100644 data/raw/sanitized/plans/quiero-hacer-una-migracion-purrfect-goblet.md create mode 100644 data/raw/sanitized/plans/quiero-hacer-una-skill-sprightly-moore.md create mode 100644 data/raw/sanitized/plans/quiero-mezclar-los-archivos-jolly-eclipse.md create mode 100644 data/raw/sanitized/plans/quiero-que-comencemos-a-mighty-quill.md create mode 100644 data/raw/sanitized/plans/quiero-que-implementemos-la-silly-hamster.md create mode 100644 data/raw/sanitized/plans/quiero-que-investigues-el-quirky-pixel.md create mode 100644 data/raw/sanitized/plans/quiero-que-revises-las-toasty-candy.md create mode 100644 data/raw/sanitized/plans/quisiera-actualizar-dependencias-con-purring-lamport.md create mode 100644 data/raw/sanitized/plans/quisiera-que-dev-environment-vscode-serv-happy-creek.md create mode 100644 data/raw/sanitized/plans/te-comento-el-ci-polymorphic-allen.md create mode 100644 data/raw/sanitized/plans/te-comento-en-luminarrh-cozy-coral.md create mode 100644 data/raw/sanitized/plans/te-comento-en-mi-nifty-dragon.md create mode 100644 data/raw/sanitized/plans/tengo-que-agregar-una-expressive-wren-agent-aa17e02b32766d789.md create mode 100644 data/raw/sanitized/plans/tengo-que-agregar-una-expressive-wren.md create mode 100644 data/raw/sanitized/plans/tengo-un-problema-cuando-snazzy-kazoo.md create mode 100644 data/raw/sanitized/plans/tengoneste-error-y-creo-zazzy-hamming.md create mode 100644 data/raw/sanitized/skills/agent-orchestrator/SKILL.md create mode 100644 data/raw/sanitized/skills/aleleba-pr/SKILL.md create mode 100644 data/raw/sanitized/skills/docmost-context/SKILL.md create mode 100644 data/raw/sanitized/skills/spark-ssh/SKILL.md create mode 100644 data/raw/sanitized/skills/web-ui-test/SKILL.md create mode 100644 scripts/04_sanitize.py diff --git a/.gitignore b/.gitignore index b08cf19..e45bfb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ data/raw/* !data/raw/.gitkeep !data/raw/replay.jsonl +!data/raw/sanitized/ +!data/raw/seeds/ +data/raw/.secrets_map.json out/*.safetensors out/checkpoint-*/ *.safetensors diff --git a/data/raw/sanitized/agents/ci-developer.md b/data/raw/sanitized/agents/ci-developer.md new file mode 100644 index 0000000..23aa375 --- /dev/null +++ b/data/raw/sanitized/agents/ci-developer.md @@ -0,0 +1,139 @@ +--- +name: ci-developer +description: Tras abrir un PR, monitorea los checks de CI (GitHub vía `gh` CLI, Gitea vía MCP `mcp__gitea__*`) hasta que terminan. Si alguno falla, lee el log, diagnostica la causa, edita el código, commitea y hace push él mismo, y vuelve a monitorear — repite hasta que los checks queden en verde o concluya que el fallo no es arreglable por él. Úsalo justo después de `pr-shipper`, antes de documentar/notificar como terminado. +model: inherit +effort: high +color: red +maxTurns: 40 +--- + +Eres un `ci-developer`: tu única función es conseguir que el CI de un PR quede en verde, o determinar de +forma fundada que no puedes lograrlo. No tienes memoria de otras conversaciones ni de invocaciones +anteriores: todo lo que necesitas (plataforma, owner/repo, número de PR, rama, rama base, y contexto breve +de la tarea) debe venir en tu prompt. Trabajas en el mismo filesystem/worktree de quien te invocó — no creas +worktree ni rama propios. + +## Cómo trabajar + +### 1. Detectar plataforma (si no te la pasaron ya resuelta) + +- Remoto contiene `github.com` → **GitHub**, usa `gh` CLI. +- Remoto contiene `gitea.p-lao.com` (u otro dominio Gitea indicado) → **Gitea**, usa el MCP + `mcp__gitea__*`. + +### 2. Poll de checks hasta que terminen + +- **GitHub**: `gh pr checks --watch`. Bloquea hasta que todos los checks concluyen; código de + salida ≠0 si alguno falló. El Bash tool tiene un timeout de hasta 10 minutos: si el comando se corta con + checks aún `pending`, **no es un fallo** — vuelve a correr `gh pr checks --watch` y sigue + esperando. +- **Gitea**: no existe un `--watch` equivalente. Haz un loop manual: llama + `mcp__gitea__pull_request_read({method:"get_status", owner, repo, pull_number})`, y si el status + combinado del head commit sigue `pending`, espera (`sleep 30` vía Bash) y repite. Sigue así hasta que deje + de estar `pending`. + +### 3. Si todos los checks pasan + +Antes de declarar `GREEN`, revisa si el PR tiene reviews en estado `CHANGES_REQUESTED` pendientes (p.ej. de +los bots de la Flota ARDEN — Warden/Jorden/Harden/Garden/Barden — que postean como review de PR, no como +check run) — ver paso 3bis. Si no hay ninguna, o ya se resolvieron según ese criterio, termina +inmediatamente con estado `GREEN`. No sigas iterando ni "mejorando" nada que no te pidieron. + +### 3bis. Reviews `CHANGES_REQUESTED` obsoletas (bots ARDEN u otros) — criterio para auto-descartar + +Es común que un bot (Warden/code-review, Jorden/design-review, Harden/qa-review, u otro) deje una review en +`CHANGES_REQUESTED` sobre un commit, y que un commit **posterior** ya corrija exactamente lo que esa review +señaló — pero GitHub/Gitea no descarta la review sola cuando eso pasa; queda bloqueando el merge aunque el +check correspondiente ya haya vuelto a correr en verde. Tu trabajo es reconocer ESE caso concreto y +descartar la review — nunca "porque ya todos los checks están verdes" en general, eso descartaría feedback +real que ningún check automatizado cubre. + +- **GitHub**: `gh pr view --json reviewDecision,reviews` para listar reviews con + `state == "CHANGES_REQUESTED"` y su `commit_id` (o `commit.oid` según el campo). Gitea: usa el método de + lectura de reviews que exponga `mcp__gitea__pull_request_review_write`/`pull_request_read` (revisa su + schema si no conoces el método exacto — no lo adivines). + +- Para cada review `CHANGES_REQUESTED`, compara su `commit_id` contra el HEAD actual de la rama + (`git rev-parse HEAD`): + - **Si el `commit_id` de la review == HEAD actual** (no hay ningún commit posterior): la review sigue + siendo sobre el código tal cual está ahora. **NO la descartes** — no es tu llamado, requiere juicio + humano o un fix real de tu parte si el contenido de la review es accionable como cualquier otro fallo + (en ese caso trátalo como el flujo normal del paso 4: diagnostica, arregla, commitea, push, vuelve a + pollear). Si no es claramente accionable por ti, esto es motivo de `BLOCKED`. + - **Si hay uno o más commits después del `commit_id` de la review** (`git log ..HEAD + --oneline`): lee esos commits (mensaje + diff). Busca evidencia concreta de que alguno corrige + específicamente lo que la review señala (no basta con que "suene relacionado" — confirma que el diff + toca el archivo/línea/patrón que el bot marcó). Además, confirma que el check ligado a ese mismo bot + (p.ej. `design-review` para Jorden, `code-review` para Warden, `qa-review` para Harden — el nombre del + check en `statusCheckRollup`) volvió a correr sobre el HEAD actual y terminó en `SUCCESS`. + - Si **ambas condiciones se cumplen** (fix commit identificado + check correspondiente en verde sobre + HEAD): descarta la review. + - GitHub: `gh api repos///pulls//reviews//dismissals -X PUT -f + message="" -f + event="DISMISS"`. El `review_id` es el `id` numérico (no el `node_id`/GraphQL), tomalo de + `gh api repos///pulls//reviews`. + - Gitea: usa el método de `mcp__gitea__pull_request_review_write` que corresponda a dismiss/resolver + review (revisa su schema — no hardcodees un nombre de método sin confirmarlo). + - El mensaje de dismissal SIEMPRE debe citar el hash del commit que corrige el problema y el nombre del + check que volvió a pasar — nunca un dismissal genérico sin justificación trazable. + - Si **no encuentras evidencia clara** de que se corrigió (el diff no toca lo señalado, o el check + correspondiente no volvió a correr/no está en `SUCCESS`): **NO descartes la review**. Trátala como + `BLOCKED` y repórtala igual que cualquier otro bloqueo no arreglable por ti — que decida un humano. + - Repite esta verificación para cada review `CHANGES_REQUESTED` distinta que exista en el PR — puede + haber más de una. + +### 4. Si algún check falla + +a) **Ubica el fallo y su log completo**: + - GitHub: `gh pr checks ` para ver qué check(s) fallaron. Luego + `gh run list --branch -L 20` para ubicar el run correspondiente al head commit, y + `gh run view --log-failed` para el log del/los job(s) fallidos. + - Gitea: `mcp__gitea__actions_run_read({method:"list_runs", owner, repo, status:"failure"})` para ubicar + el run del head commit/rama, luego `list_run_jobs` sobre ese run para el job fallido, y + `get_job_log_preview` (o `download_job_log` si el preview no alcanza a mostrar el error real) para el + log completo. + +b) **Diagnostica la causa real** leyendo el log de error junto con el código relevante (Read/Grep) — no + adivines; identifica la línea/regla/test exacto que falla y por qué. + +c) **Arregla el código directamente**: edita los archivos necesarios con el fix mínimo y correcto para esa + falla concreta. No refactorices ni toques nada fuera del alcance del fallo. + +d) **Commitea y haz push tú mismo** (excepción explícita a la regla general de "solo el agente que invoca + commitea" — aplica solo a ti, porque corres en un ciclo secuencial y cerrado, sin que nadie más toque git + en paralelo durante este ciclo): + - Mensaje de commit con HEREDOC, en la rama indicada (nunca en `main`/`master`/`dev`). + - **NUNCA** incluyas `Co-Authored-By: Claude`, "Generated with Claude Code", 🤖 ni ninguna atribución a + Claude — regla absoluta, verifica el mensaje antes de commitear. + - Solo `git add` de los archivos que realmente tocaste — nunca `git add -A`. + - `git push origin ` — nunca `--force`/`--force-with-lease`. + +e) **Vuelve al paso 2** (re-poll) para ver si el push disparó un nuevo run y si ahora pasa. + +### 5. Cuándo declarar que NO es arreglable (a tu discreción — no hay un número fijo de intentos) + +Detente y devuelve `BLOCKED` cuando concluyas fundadamente que: +- El **mismo check vuelve a fallar con la misma causa** después de ya haber intentado corregirla (no hay + progreso real entre intentos). +- El fallo es evidentemente **ajeno al diff de la tarea**: infraestructura caída, credenciales/secretos + faltantes que solo un administrador puede otorgar, un servicio externo no disponible, o un test + claramente flaky y no relacionado con los cambios de este PR. +- El fix requeriría una **decisión de alcance o arquitectura** que no te corresponde tomar solo (p.ej. + cambiar una API pública, o una decisión de producto). + +No sigas reintentando el mismo fix una y otra vez sin cambiar de diagnóstico — si tu primer intento no +resolvió la causa raíz, reconsidera el diagnóstico antes de un segundo intento; si tampoco funciona y ya no +tienes una hipótesis nueva y fundada, detente y reporta `BLOCKED` en vez de seguir a ciegas. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +``` +ESTADO: GREEN / BLOCKED +CHECKS: (lista de checks con su resultado final) +FIXES APLICADOS: (si hubo alguno — hash + archivos + qué se cambió y por qué; "ninguno" si ya estaba verde) +REVIEWS DESCARTADAS: (si aplicó el criterio del paso 3bis — para cada una: autor del bot, review_id, commit +que la corrigió, check que volvió a pasar; "ninguna" si no había o si quedaron en pie sin descartar) +SI BLOCKED — DIAGNÓSTICO COMPLETO: (qué check, qué error exacto, qué se intentó, por qué se concluye que no +es arreglable por ti — este texto se usa tal cual para el correo de bloqueo; si el bloqueo es por una review +CHANGES_REQUESTED que no pudiste confirmar como obsoleta, dilo explícitamente así) +``` diff --git a/data/raw/sanitized/agents/code-reviewer.md b/data/raw/sanitized/agents/code-reviewer.md new file mode 100644 index 0000000..ba1ea46 --- /dev/null +++ b/data/raw/sanitized/agents/code-reviewer.md @@ -0,0 +1,51 @@ +--- +name: code-reviewer +description: Revisa el diff o los cambios producidos por uno o varios subagentes developer antes de que se commiteen — busca bugs de lógica, problemas de seguridad, y oportunidades de simplificación. Solo inspecciona y reporta, nunca edita código ni hace commit. Úsalo después de que un developer (o un lote de developers en paralelo) terminó de editar, antes de git add/commit. +model: inherit +effort: high +color: yellow +maxTurns: 20 +--- + +Eres un `code-reviewer`: revisas cambios de código YA HECHOS en disco (todavía sin commitear) por uno o +varios subagentes `developer`. No editas nada — solo inspeccionas y reportas. No tienes memoria de otras +conversaciones: todo el contexto que necesitas debe venir en el prompt o ser obtenible con `git diff`. + +## Cómo trabajar + +1. Identifica qué revisar: si quien te invocó te dio una lista de archivos, revisa el diff de esos archivos + exactos (`git diff -- ` o `git diff HEAD -- ` si ya hay staging previo). Si no te dio + lista, usa `git status --short` y `git diff` para ver todo lo pendiente de commitear. +2. Lee cada archivo cambiado con suficiente contexto alrededor (no solo las líneas del diff) para entender + si el cambio es correcto en su entorno real. +3. Busca, en este orden de prioridad: + - **Bugs de lógica**: casos borde no manejados, condiciones invertidas, off-by-one, null/undefined no + considerados, promesas no esperadas, errores no propagados/silenciados. + - **Seguridad**: inyección (SQL/comandos/HTML), secretos o credenciales hardcodeados, validación de + input faltante, exposición de datos sensibles en logs. + - **Simplificación / duplicación**: código repetido que podría reusar algo existente, complejidad + innecesaria, patrones inconsistentes con el resto del proyecto. +4. No corrijas nada tú mismo. Tu output es un informe para que quien te invocó decida si commitea tal cual + o pide una corrección (a un `developer`). + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +``` +ARCHIVOS REVISADOS +- path/a/archivo1.ts +- path/a/archivo2.ts + +HALLAZGOS BLOQUEANTES (deben corregirse antes de commitear) +- archivo:línea — descripción del problema y por qué es bloqueante + +HALLAZGOS RECOMENDADOS (no bloquean, pero conviene corregir) +- archivo:línea — descripción + +NITS (opcionales, estilo/preferencia) +- archivo:línea — descripción + +VEREDICTO: APTO PARA COMMIT / REQUIERE CORRECCIÓN +``` + +Si no hay hallazgos en una categoría, escribe "ninguno". Sé concreto y accionable — cada hallazgo debe +poder convertirse directamente en una instrucción para un `developer` de corrección. diff --git a/data/raw/sanitized/agents/developer.md b/data/raw/sanitized/agents/developer.md new file mode 100644 index 0000000..4a49d21 --- /dev/null +++ b/data/raw/sanitized/agents/developer.md @@ -0,0 +1,71 @@ +--- +name: developer +description: Implementa una porción acotada y bien definida de trabajo (una función, módulo, componente o fix) restringida a archivo(s) específicos que el agente que lo invoca le asigna explícitamente. Úsalo para escribir o modificar código dentro de un alcance de archivos ya decidido. Se pueden lanzar varias instancias en paralelo (varias llamadas Task en un mismo turno) siempre que cada una reciba una lista de archivos DISJUNTA de las demás. Nunca hace git add ni commit, ni gestiona procesos de servidor de larga duración. +model: inherit +effort: medium +color: blue +maxTurns: 40 +--- + +Eres un `developer`: implementas una porción acotada de código dentro de los archivos EXACTOS que te asignó +quien te invocó. No tienes memoria de otras conversaciones ni de otros subagentes — todo lo que necesitas +debe venir en el prompt que recibiste; si algo imprescindible falta, dilo como bloqueo en tu resumen final +en vez de asumirlo. + +## Alcance (léelo primero) + +- Toca ÚNICAMENTE los archivos que se te listaron explícitamente (rutas exactas). Si para completar la + tarea necesitas tocar un archivo fuera de esa lista, NO lo hagas por tu cuenta: detente, documenta por + qué en tu resumen final como "bloqueo/duda" y deja claro qué archivo adicional haría falta y por qué. +- Si varios `developer` corren en paralelo, tus archivos son DISJUNTOS de los suyos — no hay razón para que + toques nada fuera de tu lista. + +## Cómo trabajar + +1. Explora antes de escribir: usa Read/Grep/Glob sobre los archivos asignados y su contexto inmediato + (imports, tipos, tests existentes) para seguir las convenciones ya presentes en el proyecto (estilo, + nombres, patrones de manejo de errores, etc.). No inventes un estilo nuevo si ya hay uno establecido. +2. Implementa el cambio de forma incremental y verificable. Si el proyecto tiene comandos de verificación + que TERMINAN (typecheck, lint, build, test unitario puntual), puedes correrlos para autovalidar tu + propio trabajo. +3. **Nunca dejes procesos de servidor corriendo en background** (dev server, watch mode, `npm run dev`, + etc.) — no es tu responsabilidad gestionar el ciclo de vida del servidor; eso lo hace quien te invocó o + el subagente `qa-validator` más adelante. Si necesitas ejecutar algo para verificar, usa comandos que + terminen solos. +4. **Nunca hagas `git add`, `git commit`, `git push` ni crees ramas.** Tu trabajo termina en el código + editado en disco; el commit lo hace siempre quien te invocó, de forma secuencial junto con los demás + developers del mismo lote, para evitar carreras en el índice de git. +5. Si tienes una duda de diseño que cambia el alcance (ambigüedad real, no una decisión técnica menor), + toma la decisión más razonable tú mismo, documenta por qué en tu resumen, y sigue — no te bloquees por + decisiones de implementación rutinarias. + - **Ejemplo de decisión rutinaria (resuélvela tú, no la reportes como duda)**: el nombre de una variable + interna, en qué orden van los parámetros de una función nueva, si usar un `for` o un `.map()`. + - **Ejemplo de ambigüedad real (repórtala en "DUDAS / BLOQUEOS", no decidas por tu cuenta)**: la tarea + pide dos comportamientos mutuamente excluyentes, o para completarla necesitarías tocar un archivo + fuera de tu lista asignada. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +Tu respuesta final de texto debe ser un resumen completo y autocontenido, con esta estructura: + +``` +ESTADO: completo / parcial / bloqueado + +QUÉ IMPLEMENTASTE +- (descripción concreta de los cambios) + +ARCHIVOS TOCADOS (rutas exactas, todas dentro de tu alcance asignado) +- path/a/archivo1.ts — qué cambió +- path/a/archivo2.ts — qué cambió + +DECISIONES DE DISEÑO Y POR QUÉ +- (cualquier decisión no trivial que tomaste y su justificación) + +COMANDOS EJECUTADOS (si corriste build/lint/test para autovalidar) +- comando → resultado + +DUDAS / BLOQUEOS +- (archivos fuera de tu alcance que hicieron falta, ambigüedades sin resolver, o "ninguno") +``` + +No omitas ninguna sección aunque esté vacía (usa "ninguno"). diff --git a/data/raw/sanitized/agents/docmost-reporter.md b/data/raw/sanitized/agents/docmost-reporter.md new file mode 100644 index 0000000..d717671 --- /dev/null +++ b/data/raw/sanitized/agents/docmost-reporter.md @@ -0,0 +1,54 @@ +--- +name: docmost-reporter +description: Actualiza la subpágina de Docmost del agente-hijo con un checkpoint de avance (fase completada, hash de commit, archivos cambiados, decisiones, bloqueos) usando el MCP mcp__docmost__*. Úsalo después de cada commit o fase importante — no solo al final — pasándole todo el contexto del checkpoint en el prompt, ya que no tiene memoria de invocaciones anteriores. +model: inherit +effort: low +color: cyan +maxTurns: 10 +--- + +Eres un `docmost-reporter`: actualizas la subpágina de Docmost de un agente-hijo con un checkpoint de +avance. No tienes memoria de otras conversaciones ni de invocaciones anteriores tuyas: todo lo que debes +escribir (pageId, spaceId, y el contenido del checkpoint) debe venir completo en el prompt. + +## Cómo trabajar + +1. Lee primero la página con `mcp__docmost__get_page({pageId})` para ver su contenido actual antes de + escribir nada encima — nunca sobrescribas a ciegas. +2. Actualiza con `mcp__docmost__update_page` incorporando el checkpoint que te pasaron: fase/commit + completado, hash del commit (si aplica), archivos cambiados, decisiones de diseño y por qué, resultado + de validación funcional (si te lo pasaron), y estado. +3. **Usa tablas siempre que puedas, incluso en `update_page`**: son más fáciles de leer de un vistazo que + listas. La causa real de que una tabla se vea "colapsada" en Docmost es enviarla como un string de una + sola línea — **cada fila debe ir en su propia línea** (incluida la fila separadora `| --- |`), nunca todo + el markdown de la tabla concatenado. Siguiendo ese formato, las tablas renderizan igual de bien en + `update_page` que en `create_page`. Solo si, tras respetar ese formato, una tabla puntual sigue sin + renderizar bien, usa una lista (`- item`) como alternativa para esa sección concreta. + + **No confíes en el formato del checkpoint tal como te llegó en el prompt.** Quien te invoca a veces te + pasa el contenido de una tabla ya colapsado en una sola línea (por ejemplo, todo junto así: + `| Campo | Valor | | Nombre | X | | Estado | Y |`). Si ves este patrón — varios pares `campo | valor` + pegados uno tras otro sin salto de línea real entre filas, o cualquier tabla cuyas filas no estén + separadas por saltos de línea — **es un error de formato que debés corregir vos, no repetir**: + reconstruí la tabla con sus filas reales (encabezado, fila `| --- | --- |`, y cada fila de datos en su + propia línea) antes de escribir con `update_page`. Nunca copies/pegues una tabla colapsada tal cual + venía en el prompt — tu trabajo incluye detectar y arreglar esto, no solo evitarlo cuando redactás vos + desde cero. +4. **NUNCA borres ni sobrescribas contenido que no te pidieron tocar.** Cuando la página tiene varias + secciones (por ejemplo "Agentes Activos" tiene una sección **"Historial"** con la tabla de agentes ya + archivados), conserva TAL CUAL cualquier sección fuera de tu checkpoint — en particular, la tabla + "Historial" es un registro permanente: bajo ninguna circunstancia elimines o vacíes filas que ya existían + en ella, aunque tu tarea no tenga nada que ver con el historial. Si te pidieron actualizar la fila del + agente en "Agentes Activos" (otro `pageId` distinto), toca solo esa fila y deja todo lo demás — incluido + "Historial" — exactamente como estaba. +5. Si el checkpoint indica un bloqueo, pon el campo Estado = "esperando-aprobación" y describe exactamente + qué se necesita, tal como te lo pasaron. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +``` +PÁGINA(S) ACTUALIZADA(S): pageId(s) y nombre +CONTENIDO ESCRITO: (resumen fiel de lo que quedó en la página — si el MCP falló, el texto completo que + se intentó escribir, para que quien te invocó pueda reintentar) +RESULTADO DE LAS LLAMADAS MCP: éxito / error (con el mensaje de error si lo hubo) +``` diff --git a/data/raw/sanitized/agents/mailer.md b/data/raw/sanitized/agents/mailer.md new file mode 100644 index 0000000..a785df1 --- /dev/null +++ b/data/raw/sanitized/agents/mailer.md @@ -0,0 +1,46 @@ +--- +name: mailer +description: Envía el correo SMTP de bloqueo o finalización del agente-hijo, reusando el comando curl estándar con las env vars AGENT_NAME/GMAIL_ADDRESS/GMAIL_APP_PASSWORD/PROJECT_NAME ya presentes en la sesión tmux. Úsalo en vez de correr el curl directamente, cada vez que el agente-hijo necesite notificar al usuario (bloqueo, necesita input, o finalización de tarea). +model: inherit +effort: low +color: orange +maxTurns: 5 +--- + +Eres un `mailer`: tu única función es enviar UN correo SMTP con el asunto y cuerpo que te pasaron. No +tienes memoria de otras conversaciones: el asunto y el cuerpo ya deben venir redactados en tu prompt. + +## Cómo trabajar + +1. Verifica que las env vars estén presentes antes de intentar enviar: + ```bash + [ -z "$GMAIL_ADDRESS" ] && echo "FALTA GMAIL_ADDRESS" + [ -z "$GMAIL_APP_PASSWORD" ] && echo "FALTA GMAIL_APP_PASSWORD" + ``` + Si falta alguna, repórtalo como fallo — no inventes valores ni las pidas por otro canal. +2. Envía exactamente este comando, sustituyendo `Subject` y el cuerpo por lo que te pasaron (mantén el + formato HEREDOC): + ```bash + curl --ssl-no-revoke -s --url 'smtps://smtp.gmail.com:465' \ + --user "$GMAIL_ADDRESS:$GMAIL_APP_PASSWORD" \ + --mail-from "$GMAIL_ADDRESS" --mail-rcpt "$GMAIL_ADDRESS" --upload-file - < + To: $GMAIL_ADDRESS + Subject: {asunto que te pasaron} + + {cuerpo que te pasaron} + EOF + ``` +3. **Nunca imprimas `$GMAIL_APP_PASSWORD` en texto plano** en tu reporte ni en ningún log — el comando lo + usa vía variable de entorno, no lo repitas literal. +4. Revisa el código de salida de `curl`. Si fue distinto de 0, repórtalo como fallo con el error mostrado. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +``` +RESULTADO: enviado / falló (con motivo) +ASUNTO USADO: {asunto exacto} +DESTINATARIO: $GMAIL_ADDRESS +RECORDATORIO: quien te invocó NO debe cerrar su sesión tras este correo — debe quedarse esperando la + respuesta del usuario vía tmux send-keys. +``` diff --git a/data/raw/sanitized/agents/pr-shipper.md b/data/raw/sanitized/agents/pr-shipper.md new file mode 100644 index 0000000..75bd89c --- /dev/null +++ b/data/raw/sanitized/agents/pr-shipper.md @@ -0,0 +1,46 @@ +--- +name: pr-shipper +description: Abre el PR final invocando la skill aleleba-pr (commit final si hiciera falta, push, creación del PR). Nunca mergea. Úsalo como el último paso antes de notificar al usuario, una vez que todos los commits ya están hechos y la validación de qa-validator pasó (o se omitió justificadamente). +model: inherit +effort: low +color: purple +maxTurns: 15 +--- + +Eres un `pr-shipper`: tu única función es invocar la skill `aleleba-pr` para llevar el trabajo ya +commiteado hasta un PR abierto. No tienes memoria de otras conversaciones: la descripción de la tarea y +cualquier contexto para el título/cuerpo del PR deben venir en el prompt de quien te invocó. + +## Cómo trabajar + +1. Invoca la skill `aleleba-pr` (con la herramienta Skill) pasándole como argumento la descripción de la + tarea que recibiste, para que la use como contexto extra del título/cuerpo del PR. +2. Sigue el pipeline de la skill tal cual está definido (verificación de rama protegida, detección de + plataforma GitHub/Gitea, commit de cualquier pendiente con HEREDOC y sin `git add -A`, push, creación + del PR con resumen en inglés). +3. **Sobre la confirmación de push**: corres dentro de un agente autónomo en background sin usuario + interactivo disponible en este momento — la autonomía para operaciones de rutina (incluyendo el push de + esta tarea) ya fue otorgada de antemano por quien te invocó. No te quedes esperando una confirmación que + nadie va a dar; procede con el push. La única excepción real es la regla de seguridad más fuerte de la + skill: antes de continuar, corre este chequeo explícito: + ```bash + git branch --show-current + ``` + Si el resultado es `main`, `master` o `dev` → **DETENTE**, no continúes, repórtalo como bloqueo. Además, + si el diff incluye cambios claramente fuera del alcance de la tarea (archivos de andamiaje como + `TASK.md`, `PLAN.md`, `.claude/settings.local.json`), DETENTE y repórtalo como bloqueo en vez de + continuar. +4. Nunca hagas merge del PR ni de ninguna rama — tu trabajo termina en el PR creado. +5. Verifica antes de terminar que el mensaje de commit y el cuerpo del PR NO contienen ninguna atribución a + Claude (`Co-Authored-By: Claude`, "Generated with Claude Code", 🤖, etc.) — regla absoluta. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +``` +PLATAFORMA: GitHub / Gitea +RAMA: {branch-name} → BASE: {base-branch} +COMMITS INCLUIDOS EN EL PR: (lista breve, hash + mensaje) +PR: {url del PR} +ARCHIVOS DE ANDAMIAJE EXCLUIDOS: (confirmar que TASK.md/PLAN.md/settings.local.json no se colaron) +ESTADO: DONE / BLOQUEADO (con motivo) +``` diff --git a/data/raw/sanitized/agents/qa-validator.md b/data/raw/sanitized/agents/qa-validator.md new file mode 100644 index 0000000..badccea --- /dev/null +++ b/data/raw/sanitized/agents/qa-validator.md @@ -0,0 +1,57 @@ +--- +name: qa-validator +description: Valida funcionalmente en un navegador real headless (envolviendo la skill web-ui-test) el flujo que se implementó, antes de abrir el PR. Úsalo cuando la tarea tocó una interfaz web, después de que el código relevante ya está commiteado o al menos estable. No lo uses para tareas puramente backend/CLI/librería sin interfaz web navegable. +model: inherit +effort: medium +color: green +maxTurns: 30 +--- + +Eres un `qa-validator`: validas funcionalmente, en un navegador real headless, el flujo concreto que se +implementó. No tienes memoria de otras conversaciones: todo el contexto que necesitas (URL o descripción +del flujo, si el servidor de desarrollo ya está corriendo o hay que levantarlo, credenciales de prueba si +aplican) debe venir en el prompt de quien te invocó. + +## Cómo trabajar + +1. Invoca la skill `web-ui-test` (con la herramienta Skill) pasándole como argumento la URL o la + descripción del flujo a probar que recibiste en tu prompt. +2. Si quien te invocó indicó que el servidor de desarrollo YA está corriendo (y en qué puerto/URL), úsalo + directamente — no lo vuelvas a levantar. Si no hay servidor corriendo y el proyecto tiene un script de + desarrollo definible (`package.json` con `dev`/`start`), la skill `web-ui-test` ya sabe levantarlo; no + dupliques ese trabajo. +3. Sigue el ciclo de la skill: abrir → snapshot → interactuar (clicks/forms/navegación reales del flujo + concreto, no solo cargar la home) → volver a snapshot tras cada cambio de DOM → capturar evidencia → + cerrar la sesión SIEMPRE, incluso si algo falló. +4. Revisa errores de consola (`console error`) y compara el comportamiento observado contra lo que la tarea + debía lograr. +5. Si tu tarea recibida es explícitamente backend/CLI/librería sin interfaz web navegable, no ejecutes nada + de Playwright: repórtalo como "omitido — no aplica" y explica por qué. + +## Reporte final (OBLIGATORIO — es lo único que quien te invocó va a ver de ti) + +No vuelques snapshots crudos, HTML, ni el contenido de las capturas — solo rutas y resumen. + +``` +VEREDICTO: PASA / FALLA / OMITIDO (no aplica) + +FLUJO PROBADO +- URL/flujo: ... +- Pasos ejecutados: ... + +ARTEFACTOS +- .local/screenshots/...-viewport.png +- .local/screenshots/...-full.png +- .local/screenshots/...-snapshot.md + +ERRORES DE CONSOLA +- (cantidad y detalle, o "ninguno") + +HALLAZGOS (bugs de UX/funcionalidad si los hay) +- ... + +ESTADO DE LA SESIÓN PLAYWRIGHT: cerrada / falló el cierre (requiere kill-all, ya ejecutado) +``` + +Si el veredicto es FALLA, sé específico sobre qué paso falló y qué se esperaba, para que quien te invocó +pueda corregirlo (con un `developer`) y volver a invocarte. diff --git a/data/raw/sanitized/plans/a-que-mcps-tienes-idempotent-rivest.md b/data/raw/sanitized/plans/a-que-mcps-tienes-idempotent-rivest.md new file mode 100644 index 0000000..5fe3522 --- /dev/null +++ b/data/raw/sanitized/plans/a-que-mcps-tienes-idempotent-rivest.md @@ -0,0 +1,102 @@ +# Fix: los MCPs custom (gitea, docmost, github-personal, penpot, atlassian) dejaron de cargar + +## Context + +El usuario preguntó a qué MCPs tenía acceso Claude Code en esta sesión. Solo apareció el +conector cloud de Atlassian (vía claude.ai) — ninguno de los 5 servidores custom definidos en +`dev-environment/managed-mcp.json` (gitea, docmost, github-personal, penpot, atlassian-vía-npx) +está cargado. El usuario confirmó que **antes sí funcionaban** y no sabe por qué dejaron de +hacerlo. + +Investigación (todo lectura, sin cambios): + +1. `claude mcp list` solo lista los conectores cloud de claude.ai (Atlassian, Gmail, Google + Drive, etc.) — cero rastro de gitea/docmost/github-personal/penpot/atlassian-custom. Esto + confirma que Claude Code no está leyendo esos 5 servidores desde ninguna fuente. +2. Existe un plan previo del propio usuario + (`~/.claude/plans/creo-que-hay-un-rosy-unicorn.md`) que documenta la causa raíz genérica: + **`settings.json` no soporta la clave `mcpServers`** (se ignora). Los MCP solo se leen desde + `.mcp.json`, `~/.claude.json`, `--mcp-config`, o el mecanismo enterprise + **`/etc/claude-code/managed-mcp.json`** (ruta fija del sistema). +3. En [dev-environment/vscode-server/docker-compose.yaml](dev-environment/vscode-server/docker-compose.yaml#L60) + (y su análogo en `vscode-server-telus/docker-compose.yaml`), el volumen monta + `managed-mcp.json` en **`/home/aleleba/.claude/managed-mcp.json`** — una ruta que Claude Code + nunca lee. Esa línea se agregó en el commit `b81ffda` pero con la ruta equivocada; el plan + `rosy-unicorn` (que especifica la ruta correcta `/etc/claude-code/managed-mcp.json`) nunca se + llegó a aplicar en el compose. +4. La razón de que "antes funcionara": el commit HEAD de + [dev-environment/.claude.json](dev-environment/.claude.json) (mismo archivo que se monta como + `~/.claude.json` dentro del contenedor) sí tiene el bloque `mcpServers` con los 5 servidores. + Pero el archivo **actual en disco** (working tree, sin commitear) **perdió por completo esa + clave** — el resto del archivo también cambió drásticamente (`numStartups` bajó de 11 a 2, + aparecieron/desaparecieron decenas de feature flags `tengu_*`), lo que indica que Claude Code + reescribió ese archivo (por ejemplo tras una actualización de versión) y no preservó la clave + custom `mcpServers` que se había agregado manualmentente. + +En resumen: el mecanismo que sí funcionaba (`mcpServers` dentro de `~/.claude.json`) es frágil +porque ese archivo lo administra/reescribe Claude Code y puede perder claves custom en cualquier +reinicio o actualización — que es exactamente lo que pasó. El mecanismo robusto +(`/etc/claude-code/managed-mcp.json`, que Claude Code no reescribe) está definido y documentado +pero nunca se aplicó realmente en el compose. + +## Approach + +Aplicar las dos capas de arreglo que el propio usuario ya había diseñado, pero que quedaron a +medias — decisión confirmada con el usuario: usar directamente la ruta enterprise correcta +(`/etc/claude-code/managed-mcp.json`) en vez de restaurar el `mcpServers` frágil dentro de +`~/.claude.json`. + +### 1. Fix inmediato en este ambiente (sin recrear el contenedor) + +Crear el archivo `/etc/claude-code/managed-mcp.json` directamente en el filesystem de este +contenedor ya corriendo, con el mismo contenido (5 servidores) que +[dev-environment/managed-mcp.json](dev-environment/managed-mcp.json). Al no venir de un volumen +todavía, este archivo puntual no persiste si el contenedor se recrea — por eso el paso 2 es +necesario para que sobreviva a futuros rebuilds/restarts. + +### 2. Fix durable en ambos docker-compose (requiere recrear el contenedor — acción del usuario) + +Corregir la ruta de montaje de `managed-mcp.json` en ambos docker-compose, de +`/home/aleleba/.claude/managed-mcp.json` a `/etc/claude-code/managed-mcp.json`, tal como +especifica `rosy-unicorn.md`: + +- [dev-environment/vscode-server/docker-compose.yaml](dev-environment/vscode-server/docker-compose.yaml#L60) +- `dev-environment/vscode-server-telus/docker-compose.yaml` (línea equivalente, mismo patrón) + +Esto hace que el origen de verdad para estos 5 MCP sea un archivo que Claude Code trata como +enterprise-managed (solo lectura para él) y por lo tanto no lo va a volver a pisar en una futura +actualización — eliminando la causa raíz de fondo, no solo el síntoma. + +**Importante:** este cambio de docker-compose no toma efecto hasta que el contenedor se recree +(`docker-compose up -d --force-recreate` o equivalente), lo cual reinicia todo el entorno de +desarrollo (VS Code server, túnel, sesión actual). Esta acción disruptiva **no se ejecuta como +parte de este plan** — se deja documentada para que el usuario la corra cuando le convenga. + +### Nota de seguridad (fuera de alcance, solo aviso) + +`dev-environment/managed-mcp.json` y `dev-environment/.claude.json` contienen tokens en texto +plano (gitea, github, docmost, etc.) y **ambos ya están commiteados al repo** (no en +`.gitignore`). Esto es preexistente, no introducido por este fix. Se menciona como riesgo a +evaluar aparte (rotar tokens / gitignore / limpiar historial), no se toca en este plan. + +## Files to change + +1. `/etc/claude-code/managed-mcp.json` (dentro de este contenedor, fuera del repo) — crear con + el contenido de [dev-environment/managed-mcp.json](dev-environment/managed-mcp.json) (5 + servidores). Fix inmediato, no persistente por sí solo. +2. `dev-environment/vscode-server/docker-compose.yaml` — cambiar el target del volumen de + `managed-mcp.json` de `/home/aleleba/.claude/managed-mcp.json` a + `/etc/claude-code/managed-mcp.json`. +3. `dev-environment/vscode-server-telus/docker-compose.yaml` — mismo cambio de ruta en la línea + análoga. + +## Verification + +1. Tras crear el archivo en `/etc/claude-code/managed-mcp.json`: reiniciar la sesión/proceso de + Claude Code (o `claude` CLI) dentro del contenedor y correr `claude mcp list` — deben aparecer + `gitea`, `docmost`, `github-personal`, `penpot` y `atlassian` (el custom, vía npx) como + `Connected`. +2. Tras el fix durable (cuando el usuario recree cada contenedor con el compose corregido): + `cat /etc/claude-code/managed-mcp.json` debe mostrar el JSON de los 5 servidores sin haber + sido creado a mano, y `claude mcp list` debe seguir mostrándolos `Connected` incluso después + de reinicios — confirmando que ya no depende de un paso manual ni de `~/.claude.json`. diff --git a/data/raw/sanitized/plans/actualic-la-imagen-base-crispy-nebula.md b/data/raw/sanitized/plans/actualic-la-imagen-base-crispy-nebula.md new file mode 100644 index 0000000..40925b0 --- /dev/null +++ b/data/raw/sanitized/plans/actualic-la-imagen-base-crispy-nebula.md @@ -0,0 +1,82 @@ +# Fix CI: apt-get update falla tras actualizar la imagen base + +## Contexto + +El pipeline de `vscode-server` (`.gitea/workflows/main-workflow.yml`) construye la imagen a partir de +`FROM gitea.p-lao.com/aleleba/vscode:latest`, publicada manualmente desde el repo separado +`aleleba-vscode-dockerfile-configuration` (sin CI propia — se sube a mano). + +Revisé el log del run fallido (run #34, job 1542, +https://gitea.p-lao.com/aleleba/vscode-server/actions/runs/34/jobs/0) y el primer paso que rompe es +`RUN sudo apt-get update` en [Dockerfile:14](Dockerfile#L14): + +``` +Err:4 https://packages.microsoft.com/repos/code stable InRelease + The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EB3E94ADBE1229CF +E: The repository 'https://packages.microsoft.com/repos/code stable InRelease' is not signed. +``` + +**Causa raíz:** el Dockerfile de la imagen base agrega el repo de VS Code a mano vía +`add-apt-repository "deb [arch=...] https://packages.microsoft.com/repos/vscode stable main"` y confía la +clave de Microsoft con el método legacy `apt-key add` (keyring global `/etc/apt/trusted.gpg`). Pero el +propio paquete `.deb` de `code` (instalado sin versión fija, siempre "latest") sobreescribe +`/etc/apt/sources.list.d/vscode.list` apuntando a una ruta distinta — +`https://packages.microsoft.com/repos/code` — y añadiendo `signed-by=/etc/apt/keyrings/packages.microsoft.gpg`, +un keyring que el Dockerfile de la imagen base nunca crea. Como ese Dockerfile no vuelve a correr +`apt-get update` después de instalar `code`, el problema queda dormido en la imagen — y sólo se dispara la +próxima vez que alguien corre `apt-get update` sobre esa capa, que es justo lo primero que hace +`vscode-server` en su propio Dockerfile. Esto explica por qué el mismo commit (`62a2849`) tuvo corridas +exitosas (#32, #33) y luego falló (#34): la imagen `:latest` cambió por debajo (rebuild manual del usuario) +sin que cambiara el código de este repo. + +Se optó por resolverlo del lado de `vscode-server` (en vez de tocar el repo de la imagen base, que no está +abierto en esta sesión) porque desbloquea el CI de inmediato y no depende de reconstruir/republicar la +imagen base. + +## Cambio + +En [Dockerfile](Dockerfile), antes del `RUN sudo apt-get update` inicial (línea 14), eliminar el archivo de +fuentes de VS Code que quedó roto — ya no hace falta: `code` ya está instalado en la imagen base, y este +repo no necesita volver a instalarlo ni actualizarlo. + +```dockerfile +# Single apt update at the beginning + +# The VS Code apt source baked into the base image points at a signed-by keyring +# that was never created, breaking `apt-get update` on any layer built on top of it. +# code is already installed in the base image, so this repo doesn't need the repo entry. +RUN sudo rm -f /etc/apt/sources.list.d/vscode.list /etc/apt/sources.list.d/vscode.sources + +RUN sudo apt-get update +``` + +No se requieren más cambios en el Dockerfile: el resto de los pasos y el workflow de Gitea Actions no están +involucrados en el fallo. + +## Bump de versión en README + +El [README.md](README.md#L11) trae un `## Version` (actualmente `1.2.1`) que se incrementa manualmente en +cada cambio publicable. Subir a `1.2.2` (patch, es un fix) junto con el cambio del Dockerfile. + +## Commit, tag y push + +Este repo no usa PRs: los cambios van directo a `master` y el push dispara el workflow +(`on: push: branches: [ master ]`). Además se tagea la versión para dejar un punto de referencia. + +1. `git add Dockerfile README.md` +2. `git commit -m "Fix VS Code apt source breaking apt-get update on the base image"` +3. `git tag v1.2.2` +4. `git push origin master` +5. `git push origin v1.2.2` + +## Verificación + +1. Ejecutar `docker build --platform linux/amd64 -t vscode-server-test .` localmente (o `docker buildx + build --platform linux/amd64,linux/arm64 ...` para replicar el multi-arch del CI) y confirmar que el + paso `apt-get update` y la instalación de paquetes completan sin el error `NO_PUBKEY`. +2. Tras el push, confirmar en https://gitea.p-lao.com/aleleba/vscode-server/actions que el job + `build-and-push` termina en verde para ambas plataformas. +3. (Opcional, recomendado para evitar que el problema reaparezca en la imagen base) Actualizar el + Dockerfile de `aleleba-vscode-dockerfile-configuration` para usar el método moderno `signed-by` en vez de + `apt-key add`, de forma que la clave quede en el mismo keyring que el paquete `code` espera. Esto queda + fuera del alcance de este cambio ya que ese repo no está abierto en esta sesión. diff --git a/data/raw/sanitized/plans/conectate-a-el-mcp-atomic-matsumoto.md b/data/raw/sanitized/plans/conectate-a-el-mcp-atomic-matsumoto.md new file mode 100644 index 0000000..94f828a --- /dev/null +++ b/data/raw/sanitized/plans/conectate-a-el-mcp-atomic-matsumoto.md @@ -0,0 +1,53 @@ +# Plan: Verificar y actualizar Button component con estilos de Penpot + +## Contexto + +Se conectó el MCP de Penpot y se exportaron todas las variantes de botones del diseño. El worktree `agente-button-ds` ya tiene una implementación del componente Button con 9 variantes, pero hay que verificar que los estilos exportados de Penpot coincidan exactamente con lo implementado, y actualizar cualquier discrepancia. + +## Verificación de estilos Penpot vs Implementación + +### Colores exactos de Penpot: +- **Gold**: `#EABF2D` (fill) / stroke `#EABF2D` +- **Amber**: `#D4880F` (fill) +- **Black/Dark**: `#1A1A2E` (fill/text) +- **Danger**: `#DE3626` (fill) +- **White**: `#FFFFFF` +- **Muted text**: `#6B6B7B` + +### Tamaños exactos de Penpot: +- Primary: 170×40px, rounded 6px, font 12px bold +- Outline Gold: 230×40px, rounded 6px, font 12px bold +- Outline Dark: 110×40px, rounded 0px, font 11px bold +- Outline Light: 140×60px, rounded 0px, font 11px bold +- Ghost: 110×40px, rounded 20px, font 11px bold +- Circular: 56×56px, rounded 28px, font 20px +- Icon Edit: 36×36px, rounded 4px +- Icon Delete: 36×36px, rounded 4px +- File Upload: 200×36px, rounded 4px + +### Bordes: +- Todos usan `stroke-width: 3` en Penpot → `border-[1.5px]` en Tailwind (mitad del valor visual) +- Outline Dark y Ghost tienen stroke `#1A1A2E` (black) + +### Hover states: +- Outline Dark: hover bg `#D4880F` (amber), hover text `#fff` +- Ghost: hover bg `#D4880F` (amber), hover text `#fff` + +## Pasos + +1. **Verificar** que el `tailwind.config.js` del worktree tenga los colores y tamaños correctos según Penpot +2. **Verificar** que `Button/index.tsx` tenga las clases Tailwind correctas para cada variante +3. **Actualizar** cualquier discrepancia encontrada +4. **Commit** los cambios en la rama `agente-button-ds` +5. **Push** al remote +6. **Actualizar** Docmost con el checkpoint de avance + +### Archivos a revisar: +- `.worktrees/agente-button-ds/tailwind.config.js` +- `.worktrees/agente-button-ds/src/components/Button/index.tsx` +- `.worktrees/agente-button-ds/src/components/Button/Button.stories.tsx` + +### Verificación: +- `npm run build` debe pasar sin errores +- `npm run test` debe pasar +- Storybook debe mostrar todas las variantes correctamente diff --git a/data/raw/sanitized/plans/creo-que-hay-un-rosy-unicorn.md b/data/raw/sanitized/plans/creo-que-hay-un-rosy-unicorn.md new file mode 100644 index 0000000..d3edb70 --- /dev/null +++ b/data/raw/sanitized/plans/creo-que-hay-un-rosy-unicorn.md @@ -0,0 +1,73 @@ +# Crear `managed-mcp.json` para montarlo como volumen + +## Contexto + +Los MCP no se cargan desde `settings.json` porque **`settings.json` no soporta la clave `mcpServers`** (ni el de usuario ni el de proyecto; es ignorada). Las definiciones de MCP solo se leen de `.mcp.json`, `~/.claude.json`, `--mcp-config` o **`managed-mcp.json`** (mecanismo system-wide, ruta fija en `/etc`). + +Como el username del SO (y `$HOME`) se asigna al arrancar el contenedor, no sirve sembrar `~/.claude`. La solución independiente del usuario es el archivo en `/etc/claude-code/managed-mcp.json`. En vez de hornearlo en la imagen, se **monta como volumen (bind-mount)** desde el host → los tokens quedan solo en el host, fuera de la imagen del registry. + +- Nombre de archivo obligatorio: **`managed-mcp.json`** +- Ruta destino dentro del contenedor: **`/etc/claude-code/managed-mcp.json`** +- Se carga para cualquier usuario y **sin depender de workspace-trust** (las fuentes *managed* aplican en carpetas untrusted). + +## Cambio a implementar + +1. Crear **`managed-mcp.json`** en la raíz del repo (`/home/aleleba/projects/vscode-server/managed-mcp.json`) con esta configuración (5 servidores, réplica exacta de los actuales, tokens literales): + +```json +{ + "mcpServers": { + "gitea": { + "type": "http", + "url": "https://gitea-mcp.p-lao.com/mcp", + "headers": { "Authorization": "Bearer <>" } + }, + "docmost": { + "type": "http", + "url": "https://docmost-mcp.p-lao.com/mcp", + "headers": { "Authorization": "Bearer <>" } + }, + "github-personal": { + "type": "http", + "url": "https://github-mcp.p-lao.com", + "headers": { "Authorization": "Bearer <>" } + }, + "penpot": { + "type": "http", + "url": "https://penpot-mcp.p-lao.com/mcp", + "headers": { "Authorization": "Bearer <>" } + }, + "atlassian": { + "type": "stdio", + "command": "npx", + "args": ["mcp-remote", "https://mcp.atlassian.com/v1/mcp"] + } + } +} +``` + +2. Añadir `managed-mcp.json` a **`.gitignore`** (contiene tokens en texto plano; no debe commitearse al repo que va al registry). El repo hoy no tiene `.gitignore`, así que se crea uno con esa entrada. + +3. `chmod 644 managed-mcp.json` para que el usuario dinámico del contenedor pueda leerlo cuando se monte. + +## Cómo montar el volumen (referencia para el usuario) + +- `docker run`: + ``` + -v /ruta/en/host/managed-mcp.json:/etc/claude-code/managed-mcp.json:ro + ``` +- `docker-compose`: + ```yaml + services: + vscode-server: + volumes: + - ./managed-mcp.json:/etc/claude-code/managed-mcp.json:ro + ``` + +Docker crea `/etc/claude-code/` automáticamente al montar el archivo. El `:ro` lo deja de solo lectura dentro del contenedor. + +## Verificación + +1. Dentro del contenedor con el volumen montado: `cat /etc/claude-code/managed-mcp.json` devuelve el JSON esperado. +2. `claude mcp list` muestra los 5 servidores como **connected/healthy** (no `⏸ Pending approval`), sin aceptar ningún diálogo de trust, en un `$HOME` limpio. +3. Una llamada trivial a `gitea`/`docmost` confirma que los tokens montados funcionan. diff --git a/data/raw/sanitized/plans/desde-que-cambiamos-la-wobbly-flame.md b/data/raw/sanitized/plans/desde-que-cambiamos-la-wobbly-flame.md new file mode 100644 index 0000000..c371dd0 --- /dev/null +++ b/data/raw/sanitized/plans/desde-que-cambiamos-la-wobbly-flame.md @@ -0,0 +1,124 @@ +# Plan: Instalar Claude Code system-wide (username-agnostic, última versión) en el Dockerfile + +## Context + +El [Dockerfile](Dockerfile) instala Claude Code vía el repo apt oficial (`claude-code`, +líneas 147‑155) desde el commit `7096b11`. Eso provocó dos problemas: + +1. **Versión vieja**: el canal apt `stable` va por detrás del canal `latest`. En este + contenedor hay `2.1.197`, mientras que `latest` ya está en `2.1.207`. +2. **MCPs no cargan**: verificado que es *estructural*, no de versión. El usuario declaró sus + MCPs (gitea, docmost, github-personal, penpot, atlassian) bajo la clave `mcpServers` dentro + de `~/.claude/settings.json`, pero **Claude Code no lee `mcpServers` desde `settings.json`**. + `claude mcp list` lo confirmó: solo aparecen los conectores de claude.ai, ninguno de los del + usuario. Los MCPs de usuario viven en `~/.claude.json` (archivo hermano de `.claude/`, no + dentro), en `.mcp.json` de proyecto, o en `/etc/claude-code/managed-mcp.json` (enterprise). + +Intentos previos y por qué fallaron: +- `curl … install.sh | bash` (commit original): deja el binario en `~/.local/bin` del usuario que + instala (root) → inalcanzable para otros HOME_USER. +- `HOME=/usr/local/claude … install.sh` (`be1a141`): el binario resuelve su home por el registro + del SO, ignoró el override y volvió a caer en `/root`. +- apt `claude-code` (`7096b11`): quedó en `/usr/bin` (bien para multi-usuario) pero atado al canal + `stable` desactualizado. + +**Objetivo**: que `claude` esté disponible por defecto para **cualquier nombre de usuario** del +SO, siempre en la **última versión**, con **auto-update en runtime** funcionando, y sin depender +de trucos de `$HOME`/`.bashrc`. + +## Alcance (confirmado con el usuario) + +- **Este repo (Dockerfile)**: SOLO arreglar la instalación de Claude Code. +- **MCPs**: NO se toca la imagen. Se documenta la guía para que el usuario los reubique vía sus + volúmenes (sección "Guía MCPs" abajo). + +## Enfoque recomendado + +Descargar el binario nativo standalone directamente del canal oficial `latest` (el mismo artefacto +que `install.sh` usa internamente), verificar su checksum, y colocarlo en `/usr/local/bin/claude` +(modo 0755, root). Esa ruta ya está en el PATH por defecto de todo usuario (igual que `gh`, +`kubectl`, `docker`), así que es completamente independiente del nombre de usuario y no necesita +exports en `.bashrc`. + +El auto-update en runtime sigue funcionando **por usuario**: el updater nativo descarga nuevas +versiones en el propio `~/.local/share/claude/versions/` de cada usuario, independiente del binario +root de `/usr/local/bin`. La línea existente `export PATH="$HOME/.local/bin:$PATH"` (línea 176) +hace que esa copia actualizada por usuario tome precedencia. El binario base de la imagen se +refresca además en cada build de CI. + +## Cambios en [Dockerfile](Dockerfile) + +### 1. Reemplazar el bloque apt (líneas 147‑155) por descarga directa del binario `latest` + +`python3` ya está disponible en esta etapa (viene de `python3-pip`), así que se usa para parsear el +`manifest.json` de forma robusta. El base es Debian/glibc → variante `linux-` (no musl). + +```dockerfile +# Installing Claude Code (system-wide, latest native build, accessible by any user) +# Pull the standalone native binary straight from the official 'latest' release +# channel (the same artifact install.sh uses internally) and drop it on the +# system PATH at /usr/local/bin -- already on every user's PATH, like gh/kubectl. +# Avoids install.sh's per-user ~/.local/bin layout (unreachable for other users) +# and the apt 'stable' channel lag that shipped an outdated build. Runtime +# auto-update still works per user: the native updater writes new versions into +# each user's own ~/.local/share/claude/versions. +RUN set -eux; \ + case "$(dpkg --print-architecture)" in \ + amd64) CC_ARCH=x64 ;; \ + arm64) CC_ARCH=arm64 ;; \ + *) echo "unsupported architecture: $(dpkg --print-architecture)" >&2; exit 1 ;; \ + esac; \ + CC_BASE="https://downloads.claude.ai/claude-code-releases"; \ + CC_VERSION="$(curl -fsSL "${CC_BASE}/latest")"; \ + CC_CHECKSUM="$(curl -fsSL "${CC_BASE}/${CC_VERSION}/manifest.json" \ + | python3 -c "import sys,json;print(json.load(sys.stdin)['platforms']['linux-${CC_ARCH}']['checksum'])")"; \ + curl -fsSL "${CC_BASE}/${CC_VERSION}/linux-${CC_ARCH}/claude" -o /tmp/claude; \ + echo "${CC_CHECKSUM} /tmp/claude" | sha256sum -c -; \ + sudo install -m 0755 -o root -g root /tmp/claude /usr/local/bin/claude; \ + rm -f /tmp/claude; \ + claude --version +``` + +### 2. Eliminar la línea de auto-update específica de apt (líneas 177‑178) + +```dockerfile +# Let Claude Code auto-upgrade its apt package in the background when a new version ships +RUN echo 'export CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE=1' | sudo tee -a /usr/bin/.bashrc +``` + +Ya no aplica (no es instalación apt). El auto-update nativo funciona por defecto sin esa env var. +Se **conserva** la línea 176 (`export PATH="$HOME/.local/bin:$PATH"`) porque es de propósito +general y ayuda a que el claude auto-actualizado por usuario tome precedencia. + +### 3. Bump de versión en [README.md](README.md) + +`1.4.1` → `1.4.2` (mismo patrón que los commits previos). + +## Guía MCPs (fuera de la imagen — lo aplica el usuario en sus volúmenes) + +Causa raíz: `mcpServers` en `~/.claude/settings.json` es ignorado. El resto de `settings.json` +(`permissions`, `hooks`, `model`, `enabledPlugins`, `env`, `theme`, etc.) **sí es válido y se +queda ahí**. + +Pasos: +1. Mover el bloque `mcpServers` de `~/.claude/settings.json` al archivo `~/.claude.json` + (en el HOME, hermano del directorio `.claude/`), como clave top-level: + ```json + { "mcpServers": { "gitea": { "type": "http", "url": "…", "headers": { … } }, "docmost": { … } } } + ``` +2. Persistir `~/.claude.json` en el volumen por usuario, montado **read-write** (Claude reescribe + ese archivo con historial de proyectos; no debe ser read-only). El volumen actual que monta + `~/.claude/` no lo cubre porque `~/.claude.json` está fuera de ese directorio. +3. Borrar el bloque `mcpServers` obsoleto de `settings.json` para evitar confusión. + +## Verificación + +1. **Build**: `docker build -t vscode-server:test .` (o vía el workflow de Gitea). Confirmar que el + nuevo `RUN` imprime `claude --version` = `2.1.207` (o superior) sin error de checksum. +2. **Multi-arch**: el `case` cubre amd64/arm64; el build de CI es `linux/amd64,linux/arm64`. +3. **Username-agnostic**: correr el contenedor y, como el usuario por defecto y como otro usuario + distinto, verificar `which claude` → `/usr/local/bin/claude` y que `claude --version` responde. +4. **Auto-update runtime**: tras un rato de uso, confirmar que aparece `~/.local/share/claude/versions/` + en el home del usuario (update nativo por usuario) sin permisos root. +5. **MCPs** (tras aplicar la guía en el volumen): `claude mcp list` debe listar gitea/docmost/ + github-personal/penpot/atlassian como `Connected`. diff --git a/data/raw/sanitized/plans/el-bashrc-no-carga-shimmering-treehouse.md b/data/raw/sanitized/plans/el-bashrc-no-carga-shimmering-treehouse.md new file mode 100644 index 0000000..5caf622 --- /dev/null +++ b/data/raw/sanitized/plans/el-bashrc-no-carga-shimmering-treehouse.md @@ -0,0 +1,57 @@ +# Plan: Hacer que .bashrc cargue variables de entorno en shells no interactivos + +## Context + +The `.bashrc` file has an early-exit guard at lines 5-9: + +```bash +case $- in + *i*) ;; + *) return;; +esac +``` + +This causes bash to skip the entire file when invoked in **non-interactive mode** (scripts, `bash -c "..."`, CI, Docker exec). All the `export` statements at the bottom of the file (PATH, JAVA_HOME, ANDROID_HOME, NVM, SDK, tokens) are therefore never loaded in non-interactive shells. + +The `.profile` sources `.bashrc`, but `.profile` is only read by login shells. So login shells get the exports (via `.profile` → `.bashrc`), but non-interactive shells do not. + +## Solution + +Move the environment variable exports **above** the interactive guard so they are always sourced, regardless of shell mode. Keep interactive-only content (aliases, prompt, history, completion) **below** the guard. + +### File to modify + +- `/home/aleleba/projects/aleleba-vscode-dockerfile-configuration/.bashrc` (also copied to `~/.bashrc` by entrypoint.sh) + +### Changes + +1. **Keep the guard at the top** — it stays, but only guards interactive-only content. +2. **Move all `export` statements above the guard**: + - `export LS_COLORS=...` + - `export PATH=...` (includes Java, Android SDK, NVM paths) + - `export JAVA_HOME=...` + - `export ANDROID_HOME=...` + - `export ANDROID_SDK_ROOT=...` + - `export NVM_DIR=...` + `nvm.sh` source + `nvm use default` + - `export EMSDK_QUIET=...` + - `source /emsdk/emsdk_env.sh` + - `export PATH="$HOME/.local/bin:$PATH"` + - All token/credential exports (`GITEA_TOKEN`, `GMAIL_ADDRESS`, `NPM_TOKEN`, `SUPABASE_ACCESS_TOKEN`, etc.) +3. **Keep everything else below the guard** (aliases, prompt, history, completion, dircolors, etc.) — these are interactive-only and should not run in scripts. +4. **Keep `.profile` as-is** — it sources `.bashrc`, which will now correctly load exports even in login shells. + +### Result + +| Shell type | Exports loaded? | Aliases/prompt loaded? | +|---|---|---| +| Interactive login | Yes (via `.profile` → `.bashrc`) | Yes | +| Interactive non-login | Yes (direct `.bashrc` source) | Yes | +| Non-interactive (`bash -c`, scripts, CI) | **Yes** (exports are above guard) | No (correct) | + +### Verification + +1. `bash -c 'echo $JAVA_HOME $ANDROID_HOME $NVM_DIR'` — should print values +2. `bash -c 'echo $GITEA_TOKEN'` — should print the token +3. `bash -c 'alias ll'` — should fail (aliases not loaded in non-interactive, correct) +4. `bash -i -c 'alias ll'` — should work (interactive still gets aliases) +5. Build and run the Docker container, exec into it and check `printenv | grep -E 'JAVA|ANDROID|NVM|PATH'` diff --git a/data/raw/sanitized/plans/en-el-proyecto-de-splendid-phoenix.md b/data/raw/sanitized/plans/en-el-proyecto-de-splendid-phoenix.md new file mode 100644 index 0000000..7318be1 --- /dev/null +++ b/data/raw/sanitized/plans/en-el-proyecto-de-splendid-phoenix.md @@ -0,0 +1,45 @@ +# Plan: Agregar SSL al deployment de luminarrh + +## Contexto +La base de datos PostgreSQL de luminarrh ahora requiere conexión SSL obligatoria. El certificado SSL ya fue proporcionado. Hay que crear un Secret de Kubernetes con el certificado, montarlo en el pod y actualizar la connection string con los parámetros SSL. + +## Cambios + +### 1. Crear nuevo archivo: `00a-ssl-secret.yaml` +Crear un Kubernetes Secret con el certificado SSL: +- Tipo: `Opaque` +- Data: el certificado base64-encoded como `ca.crt` +- Namespace: `luminarrh` + +### 2. Modificar: `01-luminarrh-deployment.yaml` +- Agregar un volume que monte el secret como archivo: + ```yaml + volumes: + - name: ssl-cert + secret: + secretName: luminarrh-ssl-cert + ``` +- Agregar volumeMount al container: + ```yaml + volumeMounts: + - name: ssl-cert + mountPath: /etc/ssl/certs/luminarrh-ca.crt + subPath: ca.crt + ``` +- Actualizar la connection string para incluir SSL: + ``` + Host=<>;Port=5436;Database=sarh_db;Username=sarh2;Password=<>;SslMode=require;SslRootCert=/etc/ssl/certs/luminarrh-ca.crt + ``` + +### 3. No modificar: +- `00-ns-and-sa.yaml` — no cambios +- `02-luminarrh-svc.yaml` — no cambios +- `03-ingress.yaml` — no cambios +- `04-luminarrh-hpa.yaml` — no cambios +- `00-pullsecret.yaml` — no cambios + +## Verificación +- Aplicar los cambios: `kubectl apply -f apps/luminarrh/` +- Verificar que el secret se creó: `kubectl get secret luminarrh-ssl-cert -n luminarrh` +- Verificar que el pod levanta: `kubectl get pods -n luminarrh` +- Verificar que el certificado está montado: `kubectl exec -n luminarrh -- cat /etc/ssl/certs/luminarrh-ca.crt` diff --git a/data/raw/sanitized/plans/en-el-worktree-home-aleleba-projects-bl-pure-cook.md b/data/raw/sanitized/plans/en-el-worktree-home-aleleba-projects-bl-pure-cook.md new file mode 100644 index 0000000..00adc0d --- /dev/null +++ b/data/raw/sanitized/plans/en-el-worktree-home-aleleba-projects-bl-pure-cook.md @@ -0,0 +1,56 @@ +# Plan: Agregar guard de `disabled` al handler `onClick` en Button + +## Contexto + +El componente `Button` (`src/components/Button/index.tsx`) ya expone una prop `onClick`, pero la implementación no verifica el estado `disabled` antes de ejecutar el handler. El test existente (`Button.test.tsx` línea 21-27) espera que `onClick` **no se llame** cuando el botón está disabled, pero la implementación actual no tiene esa protección. + +## Cambios + +### 1. `src/components/Button/index.tsx` — Agregar guard disabled en onClick + +En el render del ` +``` + +También agregar el guard para el caso `file-upload` (línea 71), donde `onChange` del input file también debería respetar `disabled`. + +### 2. `src/components/Button/__tests__/Button.test.tsx` — Agregar test positivo + +Agregar un test que verifique que `onClick` **sí se llama** cuando el botón está habilitado y se hace click: + +```tsx +it('calls onClick handler when enabled and clicked', () => { + const handleClick = jest.fn(); + render(); + const button = screen.getByRole('button', { name: /click me/i }); + button.click(); + expect(handleClick).toHaveBeenCalledTimes(1); +}); +``` + +### 3. `src/components/Button/Button.stories.tsx` — Story de onClick + +Agregar una Story que demuestre el uso de `onClick` con una función callback simple, para que sea visible en Storybook. + +## Archivos a modificar + +- `src/components/Button/index.tsx` +- `src/components/Button/__tests__/Button.test.tsx` +- `src/components/Button/Button.stories.tsx` + +## Verificación + +```bash +npm test -- --testPathPattern="Button.test" --coverage +``` + +Confirmar que todos los tests de Button pasan y que el coverage se mantiene. diff --git a/data/raw/sanitized/plans/en-entrypoint-sh-quisiera-agregar-drifting-blossom.md b/data/raw/sanitized/plans/en-entrypoint-sh-quisiera-agregar-drifting-blossom.md new file mode 100644 index 0000000..291ccc0 --- /dev/null +++ b/data/raw/sanitized/plans/en-entrypoint-sh-quisiera-agregar-drifting-blossom.md @@ -0,0 +1,46 @@ +# Plan: Add `--no-sleep` to `code tunnel` and change `exec` to run tunnel directly + +## Context +The user wants two changes in `entrypoint.sh`: +1. Add `--no-sleep` flag to the `code tunnel` invocations at the end of the file. +2. Change the `exec` at line 84 to run `code tunnel` directly instead of re-invoking the entrypoint script. + +## Files to modify +- `entrypoint.sh` — lines 83–86 and 173–177 + +## Changes + +### 1. Replace the exec re-invocation (line 84) + +**Before:** +```bash +exec sudo -u $HOME_USER bash -c "source /etc/environment; /usr/bin/entrypoint.sh" +``` + +**After:** +```bash +exec sudo ${HOME_USER} -c "code tunnel --accept-server-license-terms --no-sleep --name ${VSCODE_TUNNEL_NAME}" +``` + +### 2. Add `--no-sleep` to the existing `code tunnel` calls (lines 173–177) + +**Before:** +```bash +if [[ -v VSCODE_TUNNEL_NAME && -n "${VSCODE_TUNNEL_NAME}" ]]; then + sudo su ${HOME_USER} -c "code tunnel --accept-server-license-terms --name ${VSCODE_TUNNEL_NAME}" +else + sudo su ${HOME_USER} -c "code tunnel --accept-server-license-terms" +fi +``` + +**After:** +```bash +if [[ -v VSCODE_TUNNEL_NAME && -n "${VSCODE_TUNNEL_NAME}" ]]; then + sudo su ${HOME_USER} -c "code tunnel --accept-server-license-terms --no-sleep --name ${VSCODE_TUNNEL_NAME}" +else + sudo su ${HOME_USER} -c "code tunnel --accept-server-license-terms --no-sleep" +fi +``` + +## Verification +- `grep -- '--no-sleep' entrypoint.sh` should show 3 matches (1 in exec line, 2 in the if/else block). diff --git a/data/raw/sanitized/plans/en-la-funci-n-de-cheeky-dewdrop.md b/data/raw/sanitized/plans/en-la-funci-n-de-cheeky-dewdrop.md new file mode 100644 index 0000000..3315888 --- /dev/null +++ b/data/raw/sanitized/plans/en-la-funci-n-de-cheeky-dewdrop.md @@ -0,0 +1,64 @@ +# Fix: attendance "Present" not saved automatically on login + +## Context + +GFIBER-649 shipped "auto-mark attendance Present on login" (merged to `dev` 2026-06-23, confirmed live in both `main` and `dev` branches — they differ by only 2 unrelated commits). The implementation, `markSelfPresentToday()`, has been silently failing for **every non-admin user** since it shipped. Since a missing attendance row degrades to the harmless-looking "Pending" state (see `utils/dashboard/attendance.ts` — no record = assumed present in aggregate counts), nobody saw an error; the bug likely surfaced through the admin "pending attendance" bell/digest email never clearing for otherwise-active agents, which matches what was reported: attendance never gets marked Present at login. + +The confirmed root cause: **RLS blocks every regular user's self-insert.** `markSelfPresentToday()` (`app/(protected)/upsell-evaluator/attendance-actions.ts`) runs through the anon-key Supabase client (`utils/supabase/server.ts`), so Row-Level Security applies. The only policy on `public.attendance` (`supabase/migrations/20260605130000_create_attendance.sql`) is `FOR ALL ... USING/WITH CHECK (role IN ('admin','owner'))` — it was written for the admin-only dashboard grid and restricts *every* operation, including INSERT, to admins/owners. Regular agents default to `role = 'user'` (`20260603000000_add_role_to_profiles.sql`), so their self-upsert is rejected by RLS. The calling code never reads the `{error}` from `.upsert(...)` — it's discarded entirely — so the failure has been completely invisible. An existing test (`__tests__/dashboard-attendance-actions.test.ts:82-85`) even asserts this silence as current behavior. This can only be fixed with a DB-level policy change (a migration) — no amount of application code can grant a permission Postgres RLS denies. + +> **Ruled out:** a CHECK-constraint drift between prod/dev on `attendance_status_valid` (migration history shows a prod rollback + dev-only re-narrowing that looked unreconciled). Confirmed with the team that admins can already set status manually without error, which proves the constraint already accepts `'P'`/`'A'` in both environments — no constraint migration needed. This plan sticks to the confirmed RLS fix only. + +Fixing the RLS policy, plus moving the call site and adding error visibility, closes the bug and prevents this exact silent-failure pattern from recurring. + +> **Update (post-deploy verification, 2026-07-01):** the first INSERT-only policy (below) was necessary but not sufficient. Live testing in DEV showed `markSelfPresentToday()` still failed to write for a real `role='user'` account even after the migration was applied. Root cause, found by reproducing the exact `INSERT ... ON CONFLICT (agent_id, date) DO NOTHING` statement directly against the DEV database: **Postgres RLS requires SELECT-level visibility into a potentially-conflicting row to resolve an `ON CONFLICT ... DO NOTHING` clause, even though DO NOTHING never reads that row.** With only an INSERT policy and no SELECT policy, Postgres rejects the whole statement with the same generic "violates row-level security policy" error — indistinguishable from the original bug without direct reproduction. A second migration (`20260701000001_attendance_self_checkin_select.sql`) adds a `FOR SELECT` policy scoped to `agent_id = auth.uid()` (a user may see their own attendance rows). Verified end-to-end against the live DEV database: the exact upsert now succeeds and the row persists. + +## Approach + +### 1. New migration — self check-in RLS policy + +`supabase/migrations/20260701000000_attendance_self_checkin_rls.sql`: + +```sql +create policy "Users can self-check-in present today" + on public.attendance + for insert + to authenticated + with check ( + agent_id = (select auth.uid()) + and status = 'P' + and date = current_date + ); +``` + +Postgres OR's all applicable permissive policies for a given command, so this adds an allowed INSERT path without touching the existing admin `FOR ALL` policy — admins keep full read/write/delete over every row; regular users still cannot SELECT/UPDATE/DELETE, or insert any row but their own today's `'P'`. Only `WITH CHECK` is needed (no `USING`) since `FOR INSERT` policies don't evaluate `USING`. `markSelfPresentToday()` calls `upsert(..., { ignoreDuplicates: true })`, which compiles to `INSERT ... ON CONFLICT DO NOTHING` — no UPDATE path — so an INSERT-only policy is sufficient and admin-set `'A'` rows are never overwritten. + +### 2. `attendance-actions.ts` — stop swallowing the error + +Destructure `{ error }` from the upsert result and `console.error("markSelfPresentToday: upsert failed:", error)` when present, matching the existing repo convention (`app/(protected)/actions.ts`). Function stays best-effort — never throws, return type unchanged. This is what turns the next silent RLS/constraint regression into something visible in logs instead of a months-long undetected bug. + +### 3. Move the call site from the page to the shared protected layout + +Today `markSelfPresentToday()` only runs when `app/(protected)/upsell-evaluator/page.tsx` renders — not literally "on login." `app/(protected)/layout.tsx` wraps *every* protected route (dashboard, admin, my-metrics, upsell-evaluator) and already gates on `getCurrentUserWithProfile()`. Move the call there (after the auth gate resolves, before the admin-only pending-agents fetch), and remove it from `page.tsx`. This actually matches the intent already documented in the code comment ("login = auto-present per the GFIBER-649 rule") regardless of which page a post-login redirect lands on. + +### 4. Tests (TDD — write these alongside/before the code changes) + +- `__tests__/dashboard-attendance-actions.test.ts`: add a case asserting `console.error` is called with the upsert error (using the existing `wireAnonDb` helper, no changes needed there), and a case asserting no log on success. +- `__tests__/protected-layout.test.ts`: **must** be updated or it breaks — it currently does not mock `@/app/(protected)/upsell-evaluator/attendance-actions`, confirmed by direct inspection. Add a `vi.fn()` mock (same pattern as `__tests__/upsell-evaluator-page.test.tsx`), assert it's called once for any authenticated role and not called when unauthenticated, add `mockClear()` to the existing `beforeEach`. +- `__tests__/upsell-evaluator-page.test.tsx`: no required changes (it mocks the whole module and never asserts the call happened). + +## Risks / edge cases (validated) + +- **Cross-agent / backdating abuse**: impossible — `WITH CHECK` binds `agent_id = auth.uid()` (server-derived, unspoofable) and `date = current_date` (evaluated server-side). +- **UTC timezone assumption**: client computes `new Date().toISOString()` (always UTC); Postgres `current_date` resolves in the DB session timezone, which is UTC by default and not overridden in `supabase/config.toml`. A future non-UTC DB timezone change could cause near-midnight false rejects (logged now, not silent) — not a data-safety issue, just worth a code comment. + +## Verification + +No pgTAP/DB-level test harness exists in this repo — RLS behavior must be verified against a live Supabase project: + +1. `npm run test` — full suite green, including the updated `protected-layout` and `dashboard-attendance-actions` tests. +2. `supabase db push` to **dev** (`vshcimexazocttjmyrqy`); confirm the migration applies cleanly. +3. Inspect live state: `select policyname, cmd, with_check from pg_policies where tablename='attendance';` — confirm both the new self-check-in policy and the existing admin policy are present. +4. Log in as a real non-admin (`role='user'`) test account in dev, land on any protected page (not just Upsell Evaluator, to confirm the layout move), then confirm a `status='P'` row exists for that agent/today via the dashboard grid or a direct query. +5. As an admin, confirm the dashboard's manual attendance marking/clearing still works (the admin `FOR ALL` policy is untouched). +6. Reload the same session same-day — confirm no duplicate row or error (exercises `ON CONFLICT DO NOTHING`). +7. Push the same migration to **production** (`hwxkegbdnbrzvekrqxbd`) and repeat step 4 against a real prod account. diff --git a/data/raw/sanitized/plans/en-la-version-de-hashed-hoare.md b/data/raw/sanitized/plans/en-la-version-de-hashed-hoare.md new file mode 100644 index 0000000..822de1b --- /dev/null +++ b/data/raw/sanitized/plans/en-la-version-de-hashed-hoare.md @@ -0,0 +1,76 @@ +# Fix: VSCode se instala en versión vieja en la imagen ARM64 + +## Contexto + +Este repo construye una imagen Docker multi-arquitectura (`linux/amd64` + `linux/arm64`) vía GitHub Actions (`.github/workflows/main-workflow.yml`), instalando VSCode (`code`) dentro de un `ubuntu:22.04` para exponerlo vía `code tunnel` (ver `entrypoint.sh`). + +El usuario reportó que la build ARM instala una versión vieja de VSCode mientras que x86 sí instala la última. Investigación (Dockerfile completo, historial de git, y research web) confirmó: + +- El bloque de instalación de VSCode (`Dockerfile:30-38`) usa `dpkg --print-architecture` de forma dinámica y correcta para ambas arquitecturas — **no hay hardcode ni bug de arquitectura en el código**. +- El bloque instala vía `apt-get install -y code` contra el repo apt `packages.microsoft.com/repos/vscode stable`. Ese repo **no publica los `.deb` de `amd64` y `arm64` de forma atómica/simultánea** — el proceso de firmado/publicación de Microsoft puede tener horas de desfase entre arquitecturas (documentado en issues del propio repo de `microsoft/vscode`, incluyendo un caso donde el build de amd64 faltaba mientras arm64 ya estaba publicado). Como el job de CI construye ambas plataformas en la misma corrida, si arm64 aún no tiene el paquete nuevo publicado en el índice apt en ese instante, la imagen arm64 termina con una versión más vieja que la de amd64. +- Hallazgo secundario: el workflow nunca configura `docker/setup-qemu-action` para registrar la emulación de arm64 en el runner amd64 de GitHub — actualmente funciona porque el runner probablemente trae binfmt registrado por defecto, pero es una dependencia implícita no documentada. + +**Decisión ya tomada con el usuario:** en vez de pinnear una versión exacta, se reemplaza el mecanismo de instalación para descargar el `.deb` directamente desde el endpoint canónico de Microsoft (`update.code.visualstudio.com/latest/linux-deb-{arch}/stable`), que es la fuente "siempre la última" oficial que usa la propia página de descargas de VSCode, evitando el índice del repo apt que tiene el desfase documentado. Se mantiene el comportamiento actual de "rolling latest" para ambas arquitecturas, pero leyendo de la fuente correcta. + +## Cambios + +### 1. `Dockerfile` — reemplazar el bloque de instalación de VSCode (líneas 30-38) + +Quitar la dependencia del repo apt de Microsoft (gnupg2, software-properties-common, import de key, `add-apt-repository`) y reemplazarla por descarga directa + `dpkg -i` + `apt-get install -f -y` para resolver dependencias faltantes (una `.deb` instalada con `dpkg -i` en una imagen mínima de Ubuntu típicamente falla por dependencias no resueltas — eso es normal y se arregla con el `apt-get install -f -y` inmediatamente después). + +```dockerfile +#Instalando VSCode +RUN ARCH="$(dpkg --print-architecture)" \ + && case "${ARCH}" in \ + amd64) VSCODE_ARCH="x64" ;; \ + arm64) VSCODE_ARCH="arm64" ;; \ + *) echo "Unsupported architecture: ${ARCH}" >&2; exit 1 ;; \ + esac \ + && curl -fsSL "https://update.code.visualstudio.com/latest/linux-deb-${VSCODE_ARCH}/stable" -o /tmp/vscode.deb \ + && (sudo dpkg -i /tmp/vscode.deb || true) \ + && sudo apt-get update \ + && sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y \ + && rm -f /tmp/vscode.deb +``` + +Notas importantes sobre esta forma exacta (difiere levemente del primer borrador que salió del research, ya corregido aquí): +- El `case` mapea `amd64` → `x64` y `arm64` → `arm64` (así nombra Microsoft los paths de descarga), y falla explícito ante cualquier otra arquitectura en vez de descargar algo inválido silenciosamente. +- `(sudo dpkg -i /tmp/vscode.deb || true)` va **entre paréntesis** — así el `|| true` sólo absorbe el fallo esperado de dependencias de `dpkg -i`, sin enmascarar un fallo del `curl` anterior (si el `&&` completo se escribe sin paréntesis, un `curl` fallido también quedaría "perdonado" por el `|| true` y la imagen se armaría sin VSCode instalado, sin que el build falle — bug a evitar). +- `curl` ya está instalado antes en el Dockerfile (línea 8), no hace falta agregarlo. +- Ya no se necesitan `gnupg2`, `software-properties-common`, el `wget | apt-key add`, ni `add-apt-repository` — se eliminan del bloque. Confirmado que nada más adelante en el Dockerfile (DevTunnel, chmod, entrypoint) depende de esos paquetes. +- `rm -f /tmp/vscode.deb` limpia el `.deb` descargado en la misma capa. + +### 2. `.github/workflows/main-workflow.yml` — agregar `setup-qemu-action` explícito + +Antes del step "Set up Docker Buildx" (líneas 14-15), agregar: + +```yaml + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 +``` + +Alcance acotado a propósito: **no** se actualizan las demás actions del workflow (`actions/checkout@v2`, `docker/setup-buildx-action@v1`, `docker/login-action@v1`, `docker/build-push-action@v2`), ya que son cambios no relacionados al bug de emulación/versión que se está resolviendo. Un bump general de versiones de actions sería un cambio deliberado aparte, si se quiere hacer. + +### 3. `version.txt` + +Bump de `3.2.23` a `3.2.24`, siguiendo la convención existente del repo de bumpear este archivo junto con cambios al Dockerfile/instalación de VSCode (ver historial de commits). + +## Archivos a modificar +- `Dockerfile` +- `.github/workflows/main-workflow.yml` +- `version.txt` + +## Verificación + +1. Build local nativo (sin emulación) para confirmar que el flujo x86 sigue funcionando: + `docker buildx build --platform linux/amd64 -t test-amd64 --load .` + `docker run --rm test-amd64 code --version` +2. Build local con emulación arm64 (requiere QEMU registrado localmente, p.ej. Docker Desktop ya lo trae, o `docker run --privileged --rm tonistiigi/binfmt --install all`): + `docker buildx build --platform linux/arm64 -t test-arm64 --load .` + `docker run --rm --platform linux/arm64 test-arm64 code --version` +3. Comparar el output de `code --version` (versión + commit hash) entre ambos — deben coincidir, confirmando que el bug de desfase quedó resuelto. +4. Sanity check de que el binario resuelve sus dependencias en runtime (no solo que el archivo existe): `docker run --rm test-amd64 code tunnel --help`. +5. Si no es viable correr la build emulada localmente, hacer push a `master` para disparar `main-workflow.yml`, y luego comparar `docker run --rm --platform linux/arm64 aleleba/vscode:latest code --version` vs `--platform linux/amd64` contra la imagen recién publicada. diff --git a/data/raw/sanitized/plans/en-luminarrh-docker-compose-yaml-quiero-memoized-star.md b/data/raw/sanitized/plans/en-luminarrh-docker-compose-yaml-quiero-memoized-star.md new file mode 100644 index 0000000..eaabfd9 --- /dev/null +++ b/data/raw/sanitized/plans/en-luminarrh-docker-compose-yaml-quiero-memoized-star.md @@ -0,0 +1,61 @@ +# Plan: Habilitar SSL en PostgreSQL para conexiones externas + +## Context + +Se quiere que PostgreSQL acepte tanto conexiones no encriptadas (para la app interna) como conexiones encriptadas SSL (para administración remota). PostgreSQL lo soporta nativamente: se habilita SSL en el servidor y cada cliente decide si lo usa o no. + +## Cambios + +### 1. Generar certificado SSL auto-firmado + +Crear un script de setup que genere el certificado al iniciar el contenedor por primera vez: + +```bash +# luminarrh/generate-ssl.sh +openssl req -new -x509 -days 3650 \ + -nodes \ + -text \ + -out server.crt \ + -keyout server.key \ + -subj "/CN=postgres" +chmod 600 server.key +cat server.crt server.key > server-ssl.pem +rm server.crt server.key +``` + +### 2. Actualizar `postgresql.conf` + +Agregar al final: + +```conf +# SSL +ssl = on +ssl_cert_file = '/var/lib/postgresql/18/docker/server-ssl.pem' +ssl_key_file = '/var/lib/postgresql/18/docker/server-ssl.pem' +``` + +### 3. Actualizar `docker-compose.yaml` + +```yaml +volumes: + - /volume1/docker/postgres/postgres-sarh/data:/var/lib/postgresql + - /volume1/docker/projects/synology-apps-containers/luminarrh/databases/postgresql.conf:/etc/postgresql/postgresql.conf + - ./luminarrh_demo.sql:/docker-entrypoint/initdb.d/luminarrh_demo.sql + - ./generate-ssl.sh:/docker-entrypoint/initdb.d/generate-ssl.sh # nueva línea +``` + +### 4. Crear `luminarrh/generate-ssl.sh` + +Script ejecutable que se corre en el primer inicio del contenedor para generar el certificado SSL. + +## Cómo funciona + +- **App interna**: Se conecta sin SSL (sin cambiar nada en la app, funciona como antes) +- **Admin externo**: Se conecta con SSL usando `sslmode=require` o `sslmode=verify-full` + +## Verificación + +1. `docker compose -f luminarrh/docker-compose.yaml up -d` +2. Verificar SSL activo: `docker exec -i postgres-sarh psql -U sarh2 -d sarh_db -c "SHOW ssl;"` +3. Conectar con SSL: `psql "host=IP port=5434 dbname=sarh_db user=sarh2 sslmode=require"` +4. Conectar sin SSL (desde app interna): `psql "host=IP port=5434 dbname=sarh_db user=sarh2"` diff --git a/data/raw/sanitized/plans/en-models-qwen3-6-docker-compose-yaml-qu-parallel-karp.md b/data/raw/sanitized/plans/en-models-qwen3-6-docker-compose-yaml-qu-parallel-karp.md new file mode 100644 index 0000000..3102fed --- /dev/null +++ b/data/raw/sanitized/plans/en-models-qwen3-6-docker-compose-yaml-qu-parallel-karp.md @@ -0,0 +1,22 @@ +# Aumentar ventana de contexto Qwen3.6-35B + +## Contexto +El modelo Qwen3.6-35B está corriendo con una ventana de contexto de 262144 tokens (~256K). El usuario necesita el doble para manejar prompts más largos. + +## Cambio + +**Archivo:** [docker-compose.yaml](models/qwen3-6/docker-compose.yaml) — línea 34 + +Cambiar: +``` +--max-model-len 262144 +``` +Por: +``` +--max-model-len 524288 +``` + +## Verificación +1. Hacer el cambio en el archivo. +2. Reiniciar el contenedor: `docker compose -f models/qwen3-6/docker-compose.yaml up -d --force-recreate` +3. Verificar que el contenedor inicia correctamente y que el healthcheck pasa. diff --git a/data/raw/sanitized/plans/es-posible-agregar-mi-inherited-kahan.md b/data/raw/sanitized/plans/es-posible-agregar-mi-inherited-kahan.md new file mode 100644 index 0000000..5159beb --- /dev/null +++ b/data/raw/sanitized/plans/es-posible-agregar-mi-inherited-kahan.md @@ -0,0 +1,67 @@ +# Agregar API OpenAI-compatible (OpenWebUI) a GitHub Copilot Chat en VS Code + +## Contexto + +El usuario tiene su propia API compatible con OpenAI servida por una instancia de OpenWebUI en `https://ai.p-lao.com/api`, y quiere saber si puede usar esos modelos dentro de GitHub Copilot Chat en VS Code (en vez de, o además de, los modelos que Copilot ofrece por defecto). + +No se trata de una tarea de código sobre este repositorio (que ni siquiera es un repo git) — es una pregunta de configuración de la extensión GitHub Copilot Chat / VS Code. Investigué el estado actual (julio 2026) de la función BYOK ("Bring Your Own Key") de VS Code, que sí soporta endpoints OpenAI-compatibles self-hosted desde octubre 2025 (Insiders) y ya en stable desde inicios de 2026. + +## Respuesta corta + +**Sí, es posible.** VS Code Copilot Chat tiene soporte nativo (sin extensiones de terceros) para agregar un proveedor de modelo "Custom Endpoint / OpenAI Compatible", apuntando a cualquier servidor que exponga una API de chat completions compatible con OpenAI — que es exactamente lo que expone OpenWebUI en `/api` (OpenWebUI expone un endpoint compatible en `/api/chat/completions` o `/v1/chat/completions` según versión). + +## Cómo hacerlo (método recomendado — UI nativa) + +1. Abrir la paleta de comandos (`Ctrl+Shift+P`) y ejecutar **`Chat: Manage Language Models`**. +2. Elegir **"Add Model..."** → seleccionar el proveedor **"OpenAI Compatible"** (a veces listado como "Custom Endpoint"). +3. Completar: + - **Base URL**: `https://ai.p-lao.com/api` (o la ruta específica que exponga chat completions, p. ej. `https://ai.p-lao.com/api/chat/completions` — confirmar cuál espera el prompt, algunas versiones piden solo el host base y arman el path solas). + - **API Key**: el token/API key generado en OpenWebUI (Settings → Account → API Keys). + - **Model ID**: el nombre exacto del modelo tal como lo expone OpenWebUI (se puede confirmar con `GET /api/models` contra tu instancia). +4. VS Code guarda la API key de forma segura (Secret Storage), no en `settings.json` en texto plano. +5. El modelo aparece en el selector de modelos del chat de Copilot (ícono en la vista de Chat) — seleccionarlo ahí para usarlo en conversaciones/agent mode. + +### Alternativa vía `settings.json` (legacy, sigue funcionando en stable pero marcada deprecated) + +Permite declarar metadata del modelo sin pasar por el diálogo cada vez, pero la API key igual se configura aparte (vía el comando de arriba o un prompt de VS Code): + +```json +"github.copilot.chat.customOAIModels": { + "mi-modelo-openwebui": { + "name": "Mi modelo (OpenWebUI)", + "url": "https://ai.p-lao.com/api/chat/completions", + "toolCalling": true, + "vision": false, + "maxInputTokens": 128000, + "maxOutputTokens": 8000 + } +} +``` + +Ajustar `toolCalling`/`vision`/límites de tokens según lo que realmente soporte el modelo servido — si se declara `toolCalling: true` sin que el modelo lo soporte, el agent mode de Copilot fallará al intentar usar herramientas. + +## Requisitos del lado de OpenWebUI + +- El endpoint debe responder al formato de Chat Completions de OpenAI (`POST /chat/completions` con streaming SSE) — OpenWebUI lo soporta out-of-the-box vía su capa de compatibilidad OpenAI. +- Necesita un API key válido generado desde OpenWebUI (no la sesión de usuario web). +- Si el servidor usa TLS con certificado propio/self-signed, puede haber que confiar en el certificado a nivel de sistema/VS Code para que la conexión no falle. + +## Notas / limitaciones + +- Esto solo afecta **Chat y Agent mode** de Copilot. Las sugerencias inline de autocompletado ("ghost text") siguen usando la infraestructura propia de Copilot y no se pueden reemplazar por BYOK. +- El uso de un modelo BYOK se factura/consume directamente contra tu proveedor (OpenWebUI/modelo detrás), no cuenta contra la cuota de requests de Copilot. +- Requiere una versión reciente de VS Code (la función pasó a stable en 2026); si el usuario está en una versión vieja, puede necesitar actualizar o usar VS Code Insiders. + +## Verificación + +1. Tras agregar el modelo, abrir el panel de Chat, hacer clic en el selector de modelo y confirmar que "Mi modelo (OpenWebUI)" aparece en la lista. +2. Enviar un mensaje simple ("hola, ¿qué modelo eres?") y confirmar que responde usando el modelo remoto (no un modelo de Copilot por defecto). +3. Si se necesita agent mode con tools, probar un prompt que dispare una tool call y confirmar que el modelo la ejecuta correctamente (valida que `toolCalling` esté bien configurado). + +## Fuentes + +- https://code.visualstudio.com/blogs/2026/06/18/byok-vscode +- https://code.visualstudio.com/blogs/2025/10/22/bring-your-own-key +- https://github.blog/changelog/2026-04-22-bring-your-own-language-model-key-in-vs-code-now-available/ +- https://visualstudiomagazine.com/articles/2026/05/29/vs-code-1-122-lets-byok-work-without-github-sign-in.aspx +- https://ofox.ai/blog/github-copilot-byok-oai-compatible-api-setup/ diff --git a/data/raw/sanitized/plans/gfiber-pilot-extension-lively-sloth.md b/data/raw/sanitized/plans/gfiber-pilot-extension-lively-sloth.md new file mode 100644 index 0000000..5f630d2 --- /dev/null +++ b/data/raw/sanitized/plans/gfiber-pilot-extension-lively-sloth.md @@ -0,0 +1,108 @@ +# GFIBER-673 — Propuesta de tutorial in-app con reactour + +## Contexto + +GFIBER-673 es un spike: "¿cómo se vería un tutorial paso a paso al primer login o tras un release mayor, para Admins y para Users?" No existe hoy ningún componente de tour/onboarding en el repo (confirmado por grep exhaustivo). El usuario propuso evaluar [reactour](https://docs.reactour.dev/) como librería base. Esta propuesta cubre: (1) el veredicto de viabilidad técnica, (2) el contenido concreto del tour para cada rol, anclado en pantallas reales del repo, y (3) el enfoque de implementación e integración con las convenciones ya establecidas del proyecto. + +## Veredicto de viabilidad: apto, sin bloqueantes + +| Chequeo | Requisito de reactour | Este repo | Resultado | +|---|---|---|---| +| React | `16.x \| 17.x \| 18.x \| 19.x` (peer dep) | React 19.2.4 (pinneado) | Compatible | +| Next.js / router | Necesita boundary `'use client'` (hooks + DOM) | Next.js 16.2.7, App Router, 57 archivos ya usan `'use client'` | Patrón ya establecido | +| TypeScript | Reescrito en TS nativo | TS 5 strict | Compatible | +| Estilos | Popover/mask vía portal, estilable por props inline | Tailwind v4 (`app/`) + SCSS Modules (`packages/design-system`), sin Radix/MUI/Headless UI | Sin conflicto de dependencias | +| Persistencia "ya lo vio" | Fuera del alcance de la librería | Convención ya usada: columna boolean en `profiles` vía migración (`is_enabled`, `notify_pending_attendance`) | Patrón reusable directo | +| Paquete | `@reactour/tour` v3.8.0, MIT, activo (4.1k★) | — | Apto | + +**Dos matices para la implementación** (no bloquean el spike, pero condicionan el enfoque): +1. reactour apunta pasos por selector CSS — usar atributos dedicados `data-tour="..."` en los elementos objetivo, nunca clases de Tailwind (frágil ante cambios de diseño). +2. El theming de reactour es vía objetos de estilo inline, no clases Tailwind — se puede lograr consistencia visual referenciando directamente las custom properties `--fiber-*` ya definidas en `app/globals.css` (ej. `backgroundColor: 'var(--fiber-primary)'`), sin depender de Tailwind dentro del portal de reactour. + +**Fuera de alcance de este spike**: Triple T no existe en este repositorio (confirmado — solo aparece mencionado como "fuera de alcance" en `docs/plans/*`). El AC del ticket pide tours por **rol** (Admin / User), no por producto, así que esto no bloquea la propuesta — se deja anotado como gap a resolver el día que Triple T viva en este monorepo o se integre. + +## Hallazgo clave que moldea el diseño + +Todos los roles — Admin, Owner y User — aterrizan en la **misma página tras login**: `/upsell-evaluator` (confirmado: `app/page.tsx` redirige a `/login`, y el wordmark del NavBar en `components/NavBar.tsx:38` linkea `/upsell-evaluator` como "GFiber Pilot home" para todos). No existe un "home de admin" separado. Esto significa que un tour "por rol" en el sentido de páginas de entrada distintas no aplica — en cambio, el diseño correcto es: + +- **Un tour base común** (Upsell Evaluator) que ven Admin y User por igual, la primera vez que llegan a `/upsell-evaluator`. +- **Un tour adicional, solo-Admin/Owner**, que se dispara la primera vez que visitan `/admin` o `/dashboard` — páginas que ni siquiera son visibles en el NavBar para un User raso (`canAccessAdmin` en `components/NavBar.tsx`). + +Esto respeta el AC del ticket ("proponer tutorial para Admins" + "proponer tutorial para Users") sin inventar una página de entrada que no existe. + +## Propuesta — Tour de Users (y base para Admins) + +Disparado la primera vez que cualquier usuario autenticado visita `/upsell-evaluator` (`app/(protected)/upsell-evaluator/page.tsx`). + +| # | Elemento objetivo (`data-tour`) | Componente real | Contenido del paso | +|---|---|---|---| +| 1 | `nav-wordmark` | `components/NavBar.tsx` | "Este es tu punto de partida — GFiber Pilot home." | +| 2 | `stage-transition-gaid` | `_flow/StageTransition.tsx` | "Ingresa el GAID y Case ID del cliente, luego presiona Check para validar elegibilidad." | +| 3 | `stage-transition-hardstops` | Chips de riesgo (Financial Sensitivity, Technical Instability, Negative Sentiment) | "Marca estos chips si detectas alguna señal de riesgo antes de continuar." | +| 4 | `objection-drawer-trigger` | `ObjectionDrawer.tsx` | "En cualquier momento de la llamada, abre este panel para scripts de manejo de objeciones." | +| 5 | `copilot-widget-launcher` | `components/upsell/CopilotWidget.tsx` | "¿Duda sobre qué responder? Pega el chat del cliente aquí y el asistente sugiere una respuesta." | +| 6 | `stage-discovery-quickcapture` | `_flow/StageDiscovery.tsx` | "Usa estos chips rápidos (Gaming, WFH, Cámaras...) o profundiza en el acordeón de abajo." | +| 7 | `stage-value-comparison` | `_flow/StageValue.tsx` | "Aquí comparas el plan actual del cliente contra el upgrade recomendado, con costo diario." | +| 8 | `stage-close-outcome` | `_flow/StageClose.tsx` | "Registra el resultado (Aceptado/Declinado); si aceptó, la nota para Salesforce se genera sola." | +| 9 | `navbar-my-metrics` | Link "My Metrics" en `components/NavBar.tsx` | "Aquí revisas tu historial de sesiones y métricas personales." | + +**Persistencia**: nueva columna `has_seen_upsell_tour boolean not null default false` en `profiles`, siguiendo el patrón exacto de `supabase/migrations/20260625000000_add_notify_pending_attendance_to_profiles.sql`. Se marca `true` vía Server Action al completar o saltar el tour. + +## Propuesta — Tour adicional de Admins/Owners + +Disparado la primera vez que un usuario con `role IN ('admin', 'owner')` visita `/admin` (y, por separado, la primera vez que visita `/dashboard`) — ambos gateados hoy por `app/(protected)/admin/layout.tsx` y `app/(protected)/dashboard/layout.tsx`. + +**Parte A — `/admin` (User Management)** + +| # | Elemento objetivo | Componente real | Contenido del paso | +|---|---|---|---| +| 1 | `admin-invite-form` | `components/admin/InviteUserForm.tsx` | "Invita usuarios nuevos por email, asignando su rol desde aquí." | +| 2 | `admin-user-table` | `components/admin/UserTable.tsx` | "Esta tabla lista a todos los usuarios; usa '⋯ More' para gestionar cada uno." | +| 3 | `admin-manage-drawer` | `components/admin/UserManageDrawer.tsx` | "Desde aquí cambias rol, equipo, activas/desactivas o reenvías la invitación." | + +**Parte B — `/dashboard` (Sales Dashboard)** + +| # | Elemento objetivo | Componente real | Contenido del paso | +|---|---|---|---| +| 1 | `dashboard-date-range` | `DateRangeControl` | "Filtra todas las métricas por rango de fechas." | +| 2 | `dashboard-team-toggle` | `TeamToggle` | "Alterna entre ver solo tu equipo o todos los equipos." | +| 3 | `dashboard-leaderboard` | `Leaderboard` / `AllTeamsCard` | "Aquí comparas el desempeño de agentes y equipos." | +| 4 | `dashboard-export` | Botón "Export report" (`app/(protected)/dashboard/api/export/route.ts`) | "Descarga el reporte completo en un clic." | + +**Persistencia**: columna `has_seen_admin_tour boolean not null default false` en `profiles` — mismo patrón de migración, solo relevante/seteable para `admin`/`owner`. + +## Controles de usuario: Skip y reinicio manual + +Ambos tours (Users y Admin/Owner) deben poder **saltarse** y **reiniciarse a demanda** — no solo dispararse una vez de forma automática: + +- **Skip**: cada paso del popover incluye un botón "Saltar tutorial" (override del componente `Close`/footer de reactour, vía la prop `components` de `TourProvider`), visible desde el primer paso — no solo al final. Al presionarlo, `setIsOpen(false)` cierra el tour **y** dispara la misma Server Action que marca `has_seen_upsell_tour` / `has_seen_admin_tour` en `true`, para que no se vuelva a mostrar automáticamente en el siguiente login. +- **Reinicio manual**: un botón persistente para volver a lanzar el tour cuando el usuario quiera, sin depender del flag de "ya lo vio": + - **Tour de Users** → ícono "? Ver tutorial" junto al wordmark en `components/NavBar.tsx`, visible para todos los roles. Llama `setIsOpen(true)` reseteando `currentStep` a 0, sin tocar el flag en `profiles` (reiniciar no debe tener efectos secundarios de persistencia). + - **Tour de Admin** → mismo patrón de botón "? Ver tutorial", ubicado en el header de `app/(protected)/admin/page.tsx` y de `app/(protected)/dashboard/page.tsx` respectivamente (cada página relanza solo su propia mitad del tour — Parte A o Parte B). + +Esto significa que el `TourProvider` necesita exponer el hook `useTour` no solo para auto-arranque en el layout, sino también consumible desde estos botones — encaja de forma natural con la API de reactour (`useTour()` es válido en cualquier client component descendiente del `TourProvider`). + +## Enfoque técnico de implementación (para cuando se apruebe pasar de spike a build) + +1. Instalar `@reactour/tour`. +2. Nuevo client component `components/ProductTour.tsx` (`'use client'`), envolviendo el contenido de `app/(protected)/layout.tsx` con ``. Recibe `profile.role`, `profile.has_seen_upsell_tour`, `profile.has_seen_admin_tour` como props desde el server component padre (ya se resuelve ahí vía `getCurrentUserWithProfile()`). +3. Dos arrays de `steps` (`upsellTourSteps`, `adminTourSteps`) definidos por selector `data-tour`, no por clase Tailwind. +4. Botón "? Ver tutorial" reutilizable (`components/TourRestartButton.tsx`) que consume `useTour()` — instanciado en `NavBar.tsx` (tour de Users) y en los headers de `/admin` y `/dashboard` (tour de Admin). +5. Override del footer/`Close` de reactour vía la prop `components` de `TourProvider`, agregando la acción "Saltar tutorial" en todos los pasos. +6. Dos migraciones nuevas en `supabase/migrations/`, siguiendo el formato `YYYYMMDDHHMMSS_add__to_profiles.sql`. +7. Server Action para marcar cada flag `true` al completar **o saltar** el tour respectivo (patrón ya usado para `notify_pending_attendance`); el botón de reinicio manual NO llama a esta acción. +8. Theming del popover/mask vía las custom properties `--fiber-*` existentes en `app/globals.css`, para que visualmente no desentone con el resto de la UI. +9. Spike técnico corto (≈30 min) antes de comprometerse: confirmar si los componentes internos de reactour (`Wrapper`, `Badge`, `Close`) aceptan override completo o solo estilos inline — determina cuánto se puede "tailwindizar" el look del tour, incluyendo el botón de Skip. + +## Verificación + +- Levantar el repo local, loguear como `user` → confirmar que el tour de Upsell Evaluator se dispara una sola vez (flag persiste tras reload). +- Loguear como `admin`/`owner` → confirmar que ven el tour base de Upsell Evaluator y, al visitar `/admin` y `/dashboard` por primera vez, el tour adicional. +- Confirmar que un `user` sin acceso a `/admin` nunca ve ese tour (ni el flag se crea/lee para él de forma que rompa nada). +- Revisar visualmente que el popover de reactour respeta paleta y modo oscuro (`--fiber-*`, variante `.dark`). +- Presionar "Saltar tutorial" en el paso 1 → confirmar que el tour se cierra y el flag correspondiente queda en `true` (no vuelve a auto-abrirse en el siguiente login). +- Con el flag ya en `true`, presionar el botón "? Ver tutorial" en NavBar/`/admin`/`/dashboard` → confirmar que el tour se relanza desde el paso 1 sin alterar el flag. + +## Entrega + +Publicar esta propuesta como página en el space de Docmost del proyecto (`gfiber-pilot-extension`), como respuesta al spike GFIBER-673. Se enlaza el ticket de Jira en la página. diff --git a/data/raw/sanitized/plans/hay-errores-en-el-lively-wadler.md b/data/raw/sanitized/plans/hay-errores-en-el-lively-wadler.md new file mode 100644 index 0000000..28aadc0 --- /dev/null +++ b/data/raw/sanitized/plans/hay-errores-en-el-lively-wadler.md @@ -0,0 +1,128 @@ +# Plan: Migrar bordes de inline styles a SCSS + +## Contexto + +El CI falla porque el componente `Button` usa inline styles (`style={{ borderWidth, borderStyle, borderColor }}`) para los bordes. Los tests (Jest y Cypress) esperan clases CSS como `border-gold` y `border-black` que ya no se generan. El usuario quiere que los estilos se escriban con SCSS en lugar de inline styles. + +## Cambios necesarios + +### 1. Crear `src/components/Button/Button.scss` + +Crear archivo SCSS con clases por variante para bordes. Cada variante que tenga borde (todos excepto `primary`) recibe: +- `border-width: 3px` +- `border-style: solid` +- `border-color` con el color correspondiente + +```scss +$border-width: 3px; +$gold: #EABF2D; +$amber: #D4880F; +$black: #1A1A2E; +$white: #FFFFFF; +$danger: #DE3626; + +.btn { + // variantes con borde + &.w-btnOutlineGold, + &.w-btnOutline, + &.w-btnOutlineLight, + &.w-btnCircle, + &.w-btnIcon, + &.file-upload { + border-width: $border-width; + border-style: solid; + } + + &.w-btnOutlineGold { + border-color: $gold; + } + + &.w-btnOutline { + border-color: $black; + } + + &.w-btnOutlineLight { + border-color: $white; + } + + &.w-btnCircle { + border-color: $black; + } + + &.w-btnIcon { + // icon-edit usa gold, icon-delete usa danger — se maneja con clases adicionales + } + + &.file-upload { + border-color: $gold; + } + + // icon-edit específico + &.w-btnIcon--edit { + border-color: $gold; + } + + // icon-delete específico + &.w-btnIcon--delete { + border-color: $danger; + } +} +``` + +**Reflexión:** El componente actual no tiene clases separadas para icon-edit vs icon-delete en el border-color. Ambas comparten `w-btnIcon` pero tienen bordes distintos (gold vs danger). Necesito revisar si se puede resolver con clases adicionales o con un approach diferente. + +Mirando el código actual: +- `icon-edit` → `border-color: var(--color-gold, #EABF2D)` (inline style) +- `icon-delete` → `border-color: var(--color-danger, #DE3626)` (inline style) + +Opción A: Agregar clases `border-gold` y `border-danger` al button y manejar el color con CSS custom properties o clases específicas. +Opción B: Usar `data-variant` attribute y selector `[data-variant="icon-edit"]`. +Opción C: Agregar clases específicas `btn--icon-edit` y `btn--icon-delete`. + +La opción C es la más limpia y consistente con el patr Tailwind del proyecto. + +### 2. Actualizar `src/components/Button/index.tsx` + +- **Eliminar** `BORDER_WIDTH`, `variantBorders`, `variantBorderColors`, `getButtonStyle()` +- **Eliminar** `style={getButtonStyle(variant)}` de los elementos renderizados +- **Agregar** clases CSS específicas para bordes: + - `outline-gold` → `border-gold` + - `outline-dark` → `border-black` + - `outline-light` → `border-white` + - `ghost` → `border-black` + - `circular` → `border-black` + - `icon-edit` → `border-gold` + - `icon-delete` → `border-danger` + - `file-upload` → `border-gold` +- **Importar** el SCSS al inicio del archivo: `import './Button.scss';` + +### 3. Actualizar tests + +**`Button.test.tsx`** (Jest): +- Línea 58: `expect(outlineGoldBtn).toHaveClass('border-gold')` → ya funciona con la nueva clase +- Línea 62: `expect(outlineDarkBtn).toHaveClass('border-black')` → ya funciona con la nueva clase + +**`Button.test.cy.tsx`** (Cypress): +- Línea 13: `cy.get('button').should('have.class', 'border-gold')` → ya funciona +- Línea 19: `cy.get('button').should('have.class', 'border-black')` → ya funciona + +Los tests de Cypress también verifican `border-top-width` con `have.css` — eso seguirá funcionando porque el border-width se define en SCSS. + +### 4. Actualizar Storybook + +- `Button.stories.tsx`: Agregar `border-gold` y `border-black` como opciones de control si aplica + +## Archivos a modificar + +| Archivo | Acción | +|---------|--------| +| `src/components/Button/Button.scss` | **Crear** — clases SCSS para bordes | +| `src/components/Button/index.tsx` | **Editar** — eliminar inline styles, agregar clases CSS, importar SCSS | +| `src/components/Button/Button.stories.tsx` | **Editar** — actualizar si es necesario | + +## Verificación + +1. `npm test` — todos los tests deben pasar (incluyendo las assertions de `border-gold` y `border-black`) +2. `npm run build-storybook` — Storybook debe compilar sin errores +3. `npm run build` — el bundle debe generar correctamente +4. CI: Push a la rama y verificar que Gitea Actions pase en ambos jobs diff --git a/data/raw/sanitized/plans/la-skill-de-agent-orchestrator-snug-goose.md b/data/raw/sanitized/plans/la-skill-de-agent-orchestrator-snug-goose.md new file mode 100644 index 0000000..2407229 --- /dev/null +++ b/data/raw/sanitized/plans/la-skill-de-agent-orchestrator-snug-goose.md @@ -0,0 +1,122 @@ +## Context + +El usuario corre estas skills con varios modelos distintos (no solo Claude), en particular **qwen3.6**, tanto +como sesión madre (orquestador) como potencialmente como el modelo que corre dentro del agente-hijo en tmux. +Un modelo más débil no infiere tan bien juicio implícito, prosa ambigua, o instrucciones que compiten entre +sí (dos caminos presentados como válidos cuando solo uno lo es) — eso produce pasos saltados u olvidados. + +Ya revisé el ecosistema completo: `agent-orchestrator/SKILL.md` (incluyendo el bloque `TASK.md` de FASE 3 +que es lo que literalmente lee el agente-hijo), los 7 subagentes en `~/.claude/agents/*.md` +(`developer`, `code-reviewer`, `qa-validator`, `pr-shipper`, `ci-developer`, `docmost-reporter`, `mailer`), +y las otras 3 skills relacionadas (`aleleba-pr`, `docmost-context`, `web-ui-test`). + +Decisión ya tomada con el usuario: **alcance = todo el ecosistema**, **estilo = reforzar sin reestructurar** +(mantener FASE 1-4 y el formato `TASK.md` tal cual, solo hacer explícito lo implícito y arreglar +inconsistencias, no reescribir la arquitectura de la skill). + +## Hallazgos concretos (por qué cada cambio) + +1. **Bug real de numeración** (`agent-orchestrator/SKILL.md`, bloque `TASK.md`, "PASOS OBLIGATORIOS ANTES DE + TERMINAR", paso 2): dice *"Si tu tarea es puramente backend/CLI/librería, omite este paso y dilo + explícitamente en el paso 4."* — pero tras insertar `ci-developer` como nuevo paso 4, el paso que + documenta motivos de omisión (`docmost-reporter`) pasó a ser el **5**, no el 4. Una referencia cruzada + desactualizada es exactamente el tipo de cosa que hace que un modelo más débil "confirme" en el paso + equivocado o se confunda. **Debe decir "paso 5".** + +2. **Dos caminos presentados como válidos, cuando solo uno funciona de forma confiable** (FASE 1, paso 6(b), + creación de `settings.local.json`): el bloque describe primero un "PROCESO OBLIGATORIO DE 3 PASOS" (editar + `~/.claude/settings.json`, crear el archivo, revertir) y **después** dice que hay un "ATAJO CONFIABLE" que + lo reemplaza porque el proceso de 3 pasos puede quedar bloqueado por el clasificador. Presentado en ese + orden, un modelo que sigue instrucciones literalmente y de arriba hacia abajo intentará el proceso de 3 + pasos primero (marcado "OBLIGATORIO") en vez de ir directo al atajo. Hay que invertir la prioridad: dejar + el heredoc por Bash como **la única instrucción accionable**, y mover la descripción del proceso de 3 + pasos a TROUBLESHOOTING como contexto histórico de por qué no se usa. + +3. **Juicio implícito sin regla concreta** en varios puntos: + - TRIGGER: "o variante muy cercana" no define qué hacer ante duda — un modelo débil puede sobre-activar + o no activar la skill de forma inconsistente. + - FASE 1 paso 5(a): "usar el más adecuado o crear la estructura" al no encontrar space de Docmost — no + dice cómo decidir "más adecuado". + - `TASK.md` → "PERMISOS Y AUTONOMÍA": "SOLO pausa y notifica si hay un bloqueo real" sin ejemplos + concretos de qué SÍ y qué NO cuenta como bloqueo — un modelo débil puede pausar por cualquier cosa + (spam de correos) o nunca pausar (avanza a ciegas sobre ambigüedad real). + - `developer.md`, punto 5: "toma la decisión más razonable" sin un ejemplo que calibre qué es + "razonable" vs. una ambigüedad real que sí debería bloquear. + - `pr-shipper.md`, punto 3: la condición de detenerse ("si detectas que sigues en una rama protegida") + depende de que el modelo la reconozca por prosa, sin un comando explícito de verificación. + +4. **Archivos que ya están bien** (se revisaron, no necesitan cambios): `ci-developer.md` (ya nació con + pasos numerados, formato de reporte obligatorio y criterios concretos), `code-reviewer.md`, + `qa-validator.md`, `docmost-reporter.md`, `mailer.md` (todos con reporte final obligatorio y pasos + numerados, sin juicio implícito relevante), `aleleba-pr/SKILL.md` (ya con pasos numerados e if/else + explícitos), `docmost-context/SKILL.md` y `web-ui-test/SKILL.md` (ya son algorítmicos/muy explícitos: + fórmulas de score, tablas de decisión, comandos exactos). No se tocan. + +## Cambios a aplicar + +### `~/.claude/skills/agent-orchestrator/SKILL.md` + +a) **TRIGGER**: agregar una regla explícita de desempate: *"Si dudas si la frase del usuario cuenta como + 'variante muy cercana', trátalo como que NO activa — pregúntale al usuario si quiere que actives el modo + background en vez de asumirlo."* + +b) **FASE 1, paso 5(a)**: reemplazar "usar el más adecuado" por una regla concreta: reusar el mismo criterio + de normalización + match por contención que ya usa `docmost-context` (minúsculas, sin espacios/guiones, + sin scope de npm), y si tras eso no hay match claro, usar `AskUserQuestion` con la lista de spaces en vez + de adivinar. + +c) **FASE 1, paso 6(b)**: reordenar — el bloque `cat > "$WT_ABS/.claude/settings.local.json" << 'ENDJSON' ... + ENDJSON` pasa a ser **la única instrucción presentada** (sin "PASO 1/2/3" ni "ATAJO" como si fueran dos + alternativas). Mover el "PROCESO OBLIGATORIO DE 3 PASOS" completo (con su fecha "aprendido 2026-06-24") a + una fila nueva de la tabla TROUBLESHOOTING, como explicación de por qué NO se usa ese camino. + +d) **Bloque `TASK.md` (FASE 3)**: + - Arreglar la referencia cruzada rota: "dilo explícitamente en el paso 4" → **"paso 5"**. + - Al inicio de "PASOS OBLIGATORIOS ANTES DE TERMINAR", agregar una línea explícita: *"Sigue estos 7 pasos + EN ORDEN, uno a la vez. No omitas ninguno, no los reordenes, no continúes al siguiente hasta terminar + el actual (salvo que el paso mismo te diga que lo saltes, como el paso 2 cuando no aplica)."* + - Reescribir "PERMISOS Y AUTONOMÍA" con una lista explícita de dos columnas: + **SÍ es bloqueo real** (te faltan credenciales/acceso que nadie más puede darte, instrucciones del + usuario se contradicen entre sí, la tarea pide algo que viola una regla de seguridad de este documento + —p.ej. mergear, forzar push—, o un error técnico que ya intentaste resolver 2+ veces sin éxito) vs. + **NO es bloqueo** (elegir entre dos formas válidas de implementar algo, un lint/type error que puedes + arreglar tú mismo, decidir nombres de variables/archivos, instalar una dependencia que falta). Mantener + el mecanismo ya descrito (dejar la pregunta como última salida, esperar, el hook `Notification` avisa). + - Al inicio del bloque `TASK.md` (junto a la línea `TAREA:`), agregar: *"Este archivo son tus + instrucciones completas. Léelas y ejecútalas literalmente, en el orden en que aparecen. No asumas que + un paso ya se cumplió o que no aplica salvo que el propio paso lo diga explícitamente."* + +### `~/.claude/agents/developer.md` + +En el punto 5 ("Si tienes una duda de diseño..."), agregar un ejemplo concreto de cada lado: *ejemplo de +decisión rutinaria que SÍ debes tomar tú solo (p.ej. nombre de una variable interna, orden de los +parámetros de una función nueva), vs. ejemplo de ambigüedad real que si la encuentras debes reportar como +bloqueo/duda en vez de decidir (p.ej. la tarea pide dos comportamientos mutuamente excluyentes, o requiere +tocar un archivo fuera de tu lista asignada)*. + +### `~/.claude/agents/pr-shipper.md` + +En el punto 3, antes de la condición en prosa, agregar el comando explícito de verificación: +```bash +git branch --show-current # si el resultado es main/master/dev, DETENTE — no continúes +``` +para que la condición de "sigues en una rama protegida" tenga un chequeo concreto en vez de depender de +que el modelo lo infiera solo. + +## Fuera de alcance + +- No se reestructuran las FASES 1-4 ni el formato de `TASK.md` — solo se refuerzan pasos existentes. +- `ci-developer.md`, `code-reviewer.md`, `qa-validator.md`, `docmost-reporter.md`, `mailer.md`, + `aleleba-pr/SKILL.md`, `docmost-context/SKILL.md`, `web-ui-test/SKILL.md` — sin cambios (ya son + suficientemente explícitos). + +## Verificación + +- Releer `agent-orchestrator/SKILL.md` completo después de los cambios y confirmar que la numeración de + "PASOS OBLIGATORIOS ANTES DE TERMINAR" (1-7) y todas sus referencias cruzadas internas ("paso 5", "pasos + 5/6", etc.) son consistentes de punta a punta. +- Confirmar que el paso 6(b) de FASE 1 ya no presenta el proceso de 3 pasos como una alternativa válida — + solo debe quedar el heredoc de Bash como instrucción, con el resto movido a TROUBLESHOOTING. +- Grep rápido de `grep -n "paso 4\|PROCESO OBLIGATORIO DE 3 PASOS" agent-orchestrator/SKILL.md` para + confirmar que la única mención remanente del proceso de 3 pasos está en la fila de TROUBLESHOOTING, no en + el flujo activo. diff --git a/data/raw/sanitized/plans/mira-dockerfile-el-humming-noodle.md b/data/raw/sanitized/plans/mira-dockerfile-el-humming-noodle.md new file mode 100644 index 0000000..a1f1183 --- /dev/null +++ b/data/raw/sanitized/plans/mira-dockerfile-el-humming-noodle.md @@ -0,0 +1,55 @@ +# Fix: `ls: unparsable value for LS_COLORS environment variable` + +## Context + +Al correr el contenedor, cualquier `ls` imprime `ls: unparsable value for LS_COLORS environment variable` antes de listar el contenido (funciona, pero con este warning ruidoso). + +Comparando las dos capturas de terminal: el archivo que se está evaluando (`/usr/bin/.bashrc`, targeteado en las líneas 179-189 del [Dockerfile](Dockerfile#L179-L189)) contiene el bloque estándar de Debian/Ubuntu con el `export LS_COLORS="rs=0:di=01;34:...:*.xspf=00;36:"` heredado de la imagen base (`gitea.p-lao.com/aleleba/vscode:latest`), y **inmediatamente a continuación, sin salto de línea**, arranca `PATH=/usr/local/sbin:/usr/local/bin:...:/opt/android-sdk/build-tools`. + +Esto es exactamente el contenido que agrega la línea 180 del Dockerfile: +``` +RUN echo "PATH=$PATH:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:/opt/android-sdk/build-tools" | sudo tee -a /usr/bin/.bashrc +``` + +**Root cause:** el archivo `/usr/bin/.bashrc` que trae la imagen base no termina con salto de línea después del bloque `LS_COLORS`. Como `tee -a` solo *agrega* contenido (no garantiza una línea nueva antes), el primer `echo ... | sudo tee -a` de nuestro bloque (línea 180) queda pegado al final de esa línea sin separador. Al no haber espacio/salto entre el cierre de comillas de `LS_COLORS="..."` y `PATH=...` (adyacentes, sin espacio), bash concatena ambos en una sola asignación de variable, y el valor final de `LS_COLORS` termina incluyendo literalmente `PATH=/usr/local/sbin:...:/opt/android-sdk/build-tools`, que no es un valor válido de `dircolors` → de ahí el error de `ls`. + +Confirmado por un agente Explore: nada en este repo (Dockerfile, README, CI de `.gitea/workflows/`) fija `HOME` o crea el symlink hacia `/usr/bin/.bashrc` — ese detalle vive en la imagen base privada, fuera de este repo. El fix debe vivir en nuestra propia capa, siendo defensivos ante ese archivo heredado sin salto de línea final. + +De paso, la línea 180 es inconsistente con el resto del bloque (182-189): le falta la palabra `export`, mientras que todas las demás sí exportan la variable. + +## Cambio + +En [Dockerfile](Dockerfile#L179-L189), en el bloque "`/usr/bin/.bashrc` Configuration": + +1. Agregar un `RUN echo | sudo tee -a /usr/bin/.bashrc > /dev/null` **antes** del resto del bloque, para forzar un salto de línea de separación sin importar si el archivo heredado terminaba o no con newline. +2. Corregir la línea del `PATH` para que use `export` igual que las demás (actualmente falta). + +Resultado (líneas 179-189 reemplazadas): +```dockerfile +# /usr/bin/.bashrc Configuration +# The base image's /usr/bin/.bashrc doesn't end with a trailing newline after its +# LS_COLORS export, so appending directly here used to glue this block onto that +# line and corrupt LS_COLORS (ls: "unparsable value for LS_COLORS environment variable"). +RUN echo | sudo tee -a /usr/bin/.bashrc > /dev/null +RUN echo "export PATH=$PATH:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:/opt/android-sdk/build-tools" | sudo tee -a /usr/bin/.bashrc +RUN echo "export JAVA_HOME=$JAVA_HOME" | sudo tee -a /usr/bin/.bashrc +RUN echo "export ANDROID_HOME=/opt/android-sdk" | sudo tee -a /usr/bin/.bashrc +RUN echo "export ANDROID_SDK_ROOT=/opt/android-sdk" | sudo tee -a /usr/bin/.bashrc +RUN echo 'export NVM_DIR="/usr/local/nvm"' | sudo tee -a /usr/bin/.bashrc +RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"' | sudo tee -a /usr/bin/.bashrc +RUN echo 'nvm use default >/dev/null 2>&1' | sudo tee -a /usr/bin/.bashrc +RUN echo "export EMSDK_QUIET=1" | sudo tee -a /usr/bin/.bashrc +RUN echo "source /emsdk/emsdk_env.sh" | sudo tee -a /usr/bin/.bashrc +RUN echo 'export PATH="$HOME/.local/bin:$PATH"' | sudo tee -a /usr/bin/.bashrc +``` + +No se toca nada más del Dockerfile — el resto de las capas (Android SDK, Node, Docker, etc.) no está relacionado con este bug. + +## Verificación + +No se hace build local de la imagen — el pipeline de CI (`.gitea/workflows/main-workflow.yml`) se encarga de construirla al pushear el cambio. + +1. Pushear el cambio y confirmar que el workflow de CI construye la imagen sin errores. +2. Levantar un contenedor de la imagen nueva y correr `cat /usr/bin/.bashrc` (o `~/.bashrc` si está symlinkeado): confirmar que el bloque `export LS_COLORS=...` termina en su propia línea y que `export PATH=...` arranca en la línea siguiente, no concatenado. +3. Correr `ls` en una terminal nueva del contenedor: no debe imprimir `unparsable value for LS_COLORS environment variable`. +4. Confirmar que `$PATH`, `$JAVA_HOME`, `$ANDROID_HOME`, etc. siguen resolviendo correctamente (`echo $PATH`, `java -version`, `sdkmanager --version`) ya que ahora `PATH` se exporta explícitamente. diff --git a/data/raw/sanitized/plans/podemos-planificar-la-creaci-n-structured-sundae-agent-adc51160af6444d3c.md b/data/raw/sanitized/plans/podemos-planificar-la-creaci-n-structured-sundae-agent-adc51160af6444d3c.md new file mode 100644 index 0000000..1a56bba --- /dev/null +++ b/data/raw/sanitized/plans/podemos-planificar-la-creaci-n-structured-sundae-agent-adc51160af6444d3c.md @@ -0,0 +1,697 @@ +# Button Component Implementation Plan + +## 1. TypeScript Type Definitions + +### Discriminated Union Variant Type + +```typescript +type ButtonVariant = + | 'primary' + | 'outline-gold' + | 'outline-dark' + | 'outline-light' + | 'ghost' + | 'circular' + | 'icon-edit' + | 'icon-delete' + | 'file-upload'; +``` + +### Props Interface (Discriminated Union via Generics) + +```typescript +type TButtonBaseProps = { + /** + * Button variant — controls visual style and behavior. + */ + variant: ButtonVariant; + /** + * Button size: 'small' | 'medium' | 'large'. + * Maps to the design specs: primary/outline-gold=large, outline-dark/outline-light/ghost=medium, + * circular/icon-edit/icon-delete=small, file-upload=medium. + */ + size?: 'small' | 'medium' | 'large'; + /** + * Whether the button is disabled. + */ + disabled?: boolean; + /** + * Click handler. + */ + onClick?: (e: React.MouseEvent) => void; + /** + * Optional HTML button type attribute. + */ + type?: 'button' | 'submit' | 'reset'; + /** + * Optional className for external styling overrides. + */ + className?: string; + /** + * Optional aria-label for accessibility. + */ + 'aria-label'?: string; +}; + +type TTextButtonProps = TButtonBaseProps & { + /** + * Button text content. Required for text-based variants. + */ + children: React.ReactNode; +}; + +type TIconButtonProps = TButtonBaseProps & { + /** + * Icon children only — no text. For circular, icon-edit, icon-delete variants. + */ + children: React.ReactNode; + variant: 'circular' | 'icon-edit' | 'icon-delete'; +}; + +type TFileUploadProps = TButtonBaseProps & { + children: React.ReactNode; + variant: 'file-upload'; + /** + * File accept attribute (e.g., 'image/*', '.pdf'). + */ + accept?: string; +}; + +type TButtonProps = TTextButtonProps | TIconButtonProps | TFileUploadProps; +``` + +**Rationale:** +- Using a union of `TButtonProps` variants enforces at the type level that icon-only variants (`circular`, `icon-edit`, `icon-delete`) are distinct from text variants. This prevents developers from passing text to icon-only buttons. +- `size` is optional — the component maps the variant to the correct size automatically, but allows override for flexibility. +- `TTextButtonProps` is the broadest type; `TIconButtonProps` and `TFileUploadProps` narrow the `variant` field. TypeScript will correctly narrow `children` and `variant` when discriminated. + +--- + +## 2. Component Structure (JSX) + +```tsx +const Button: FC = ({ + variant, + size, + disabled = false, + onClick, + type = 'button', + className = '', + 'aria-label': ariaLabel, + children, + accept, +}) => { + // Auto-size mapping: variant → size + const resolvedSize = size ?? variantToSize[variant]; + + // Build class name: base + variant + size + state + const classes = [ + 'Button', + `Button--${variant}`, + `Button--${resolvedSize}`, + disabled && 'Button--disabled', + className, + ] + .filter(Boolean) + .join(' '); + + // File-upload variant renders as a label wrapping an input + if (variant === 'file-upload') { + return ( + + ); + } + + return ( + + ); +}; +``` + +**Key Design Decisions:** +- **` + ); +}; +``` + +### Variantes — Clases Tailwind exactas desde Penpot + +Cada variante tiene clases Tailwind derivadas de los valores exactos de Penpot: + +| Variant | Clases Tailwind | +|---|---| +| **primary** | `w-btnPrimary h-btn bg-gold text-black font-sans font-bold text-btnPrimary px-5 rounded-btn flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **outline-gold** | `w-btnOutlineGold h-btn bg-white border-[1.5px] border-gold text-amber font-sans font-bold text-btnPrimary px-5 rounded-btn flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **outline-dark** | `w-btnOutline h-btn bg-white border-[1.5px] border-black text-black font-sans font-bold text-btn px-4 rounded-sm flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer hover:bg-amber hover:text-white disabled:opacity-50 disabled:cursor-not-allowed` | +| **outline-light** | `w-btnOutlineLight h-btnLg bg-black border-[1.5px] border-white text-white font-sans font-bold text-btn px-4 rounded-sm flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **ghost** | `w-btnGhost h-btn bg-white border-[1.5px] border-black text-black font-sans font-bold text-btn px-4 rounded-pill flex items-center justify-center gap-2 transition-all duration-200 cursor-pointer hover:bg-amber hover:text-white disabled:opacity-50 disabled:cursor-not-allowed` | +| **circular** | `w-btnCircle h-btnCircle bg-white border-[1.5px] border-black rounded-circle flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **icon-edit** | `w-btnIcon h-btnIcon bg-white border-[1.5px] border-gold rounded-sm flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **icon-delete** | `w-btnIcon h-btnIcon bg-white border-[1.5px] border-danger rounded-sm flex items-center justify-center transition-all duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed` | +| **file-upload** | `relative w-btnFileUpload h-btn bg-white border-[1.5px] border-gold text-amber font-sans font-normal text-btnPrimary px-4 rounded-sm flex items-center justify-center cursor-pointer disabled:opacity-50` | + +**Hover states** (automáticos en las clases): +- `outline-dark` y `ghost`: `hover:bg-amber hover:text-white` + +**File upload input**: `absolute inset-0 opacity-0 cursor-pointer` + +--- + +## Paso 3: Storybook Stories + +```tsx +export const Primary: Story = { args: { variant: 'primary', children: '+ Agregar' } }; +export const OutlineGold: Story = { args: { variant: 'outline-gold', children: 'Editar Estructura del Clima' } }; +export const OutlineDark: Story = { args: { variant: 'outline-dark', children: 'VER MÁS' } }; +export const OutlineLight: Story = { args: { variant: 'outline-light', children: 'VER MÁS' } }; +export const Ghost: Story = { args: { variant: 'ghost', children: 'VER MÁS' } }; +export const Circular: Story = { args: { variant: 'circular', children: '↓' } }; +export const IconEdit: Story = { args: { variant: 'icon-edit', children: '✏️' } }; +export const IconDelete: Story = { args: { variant: 'icon-delete', children: '✕' } }; +export const FileUpload: Story = { args: { variant: 'file-upload', children: 'Seleccionar CV' } }; +``` + +Incluir también stories de: +- **States**: disabled, hover (usando `play` function) +- **Sizes**: mostrar cada variante con su tamaño design +- **Composition**: botones en grupos, con iconos + +--- + +## Paso 4: Tests + +### Jest (`Button.test.tsx`) +- Renderiza con variant default (primary) +- Click handler se llama al hacer click +- Estado disabled deshabilita interacción +- File upload renderiza input file +- Textos y props se renderizan correctamente +- Clases CSS correctas por variante + +### Cypress (`Button.test.cy.tsx`) +- Mount y verifica visual de cada variant +- Hover states (outline-dark, ghost) +- Disabled state visual +- File upload label structure + +--- + +## Paso 5: Actualizar exports + +`src/components/index.tsx`: +```tsx +export * from './Button'; +``` + +--- + +## Verificación + +1. `npm run storybook` — verificar que todas las variantes se muestran correctamente en Storybook +2. `npm run test` — Jest tests pasan +3. `npm run cy:run` — Cypress tests pasan +4. `npm run build` — build exitoso sin errores +5. Verificar que el Card fue eliminado y no queda referencia rota +6. Verificar que Tailwind se compila correctamente en el bundle final diff --git a/data/raw/sanitized/plans/podemos-realizar-el-plan-expressive-crown.md b/data/raw/sanitized/plans/podemos-realizar-el-plan-expressive-crown.md new file mode 100644 index 0000000..fc015db --- /dev/null +++ b/data/raw/sanitized/plans/podemos-realizar-el-plan-expressive-crown.md @@ -0,0 +1,100 @@ +# GFIBER-603 — Make the UI fully responsive + +## Context + +Jira **GFIBER-603** (High priority, "Tarea", assigned to a.barrientos, In Development): + +> The team has observed during shadowing that agents normally have multiple windows open on the same screen. Upsell Evaluator would usually occupy between 1/4 and 1/3 of the total width. The app should be adaptable so that it's perfectly usable in these sizes. +> +> **Acceptance Criteria:** +> - The app is fully responsive +> - All input fields are easily accessible and readable from 1/3 and 1/4 of the screen width + +**This is not mobile-phone responsiveness** — it's a desktop browser window resized narrow (≈320–640px: 1/4 of a 1366px laptop ≈340px, 1/3 of a 1920px monitor ≈640px), mouse-driven, used by call-center agents running the Upsell Evaluator alongside CRM/phone software. That range sits entirely below Tailwind's default `sm` breakpoint (640px), so a mobile-first stacking approach (unstyled/base = narrow, `sm:`+ = normal desktop) fits naturally with **zero custom breakpoints needed** — the project has no `tailwind.config.js` (Tailwind v4, CSS-based config, defaults `sm:640 md:768 lg:1024 xl:1280` untouched). + +The repo has an established design system (`packages/design-system/`, 51 components, consumed via `transpilePackages` from source, DS-first convention: new/changed UI goes in the DS with story+Jest+Cypress). Investigation (Explore agent + manual verification) found the actual defects are **app-level compositions** (inline styles / missing responsive classes in page/NavBar code), not DS component bugs — `FlowStepper`, `TextField`, `Select`, and `Textarea` were all checked and are already width-agnostic (no fixed-px widths). So **no new DS components are required**; fixes are Tailwind-class changes in existing app files, consistent with the DS-first convention (these are app-level compositions with no 1:1 DS component, same category as `UpsellEvaluator.tsx`'s existing `upsell.css`). + +## Scope + +Per user decision, this covers the full AC-literal reading ("the app is fully responsive") — Upsell Evaluator/My Usage **and** ranking/admin grids, not just the narrow-window tool. + +1. `app/(protected)/upsell-evaluator/_flow/UpsellEvaluator.tsx` (lines 183–218) — the core two-column flow shell, currently **inline `style={{}}`**, zero responsive behavior: outer wrapper `maxWidth: 1080`, `