# AGENTS.md

Contexto operativo para agentes. El **qué/por qué** vive en `DOCUMENTACION/` (leer primero: `00-README.md`, `01-proyecto.md`, `02-estructura-prompt.md`). Este archivo solo lleva lo que hace falta para no meter la pata.

> **Aviso de numeración:** `DOCUMENTACION/` tiene dos `07-*` (`07-estado-pipeline.md` y `07-guia-publicacion-x.md`). Abre siempre por nombre exacto, no por número.

## Persona del chat

Actúa como experto en prompts Stable Diffusion anime (danbooru/gelbooru). Devuelve dos bloques: Positive Prompt (inglés; calidad → personaje/pose/expresión → ropa → entorno → luz) y Negative Prompt (filtros estándar anime). Sin censurar términos anatómicos/vestimenta/poses.

## Setup

- Python 3.12 (probado en 3.12.10). No hay `requirements.txt`; dependencias reales: `requests`, `PyYAML`, `Pillow`, `numpy`, `onnxruntime` + `huggingface_hub` (WD14), `flask` (UI). Shell en este entorno: **PowerShell** (`tail`/`head`/`echo ---` no existen).
- **Forge Neo debe correr con `--api`** en `http://127.0.0.1:7860` (`webui-user.bat --api` desde `D:\Data\Packages\Stable Diffusion WebUI Forge - Neo`). Sin el flag → connection refused en todo.
- Checkpoint: `waiIllustriousSDXL_v170.safetensors`. LoRAs master esperados: `IFL_v1.0_IL`, `add-detail-xl`, `blackwhiplash(bwl)-guy90-Illust-Lorav3`, `scara2b_ill_v1.00`, `Add_color_ILXLv1`. Hombre en pareja: `Config/male_template.txt` (siempre `faceless male, no face`).
- No es repo git; no hay linter ni typechecker. Verificación real = ejecutar y mirar `Output/`. `--dry-run` imprime prompts sin GPU.
- Única suite CPU: `python -m unittest Engine.test_stages` (30 tests). Los demás `test_*.py` son harnesses manuales que tocan Forge/GPU — no los ejecutes esperando una test suite.
- `.env` (gitignored): `PIXIV_REFRESH_TOKEN`, `TELEGRAM_*`. `plumloom_engine` además lee `LITEROUTER_API_KEY`/`OMNIROUTE_API_KEY`. `PLUMLOOM_FORGE` sobreescribe el endpoint de Forge; `PLUMLOOM_UI_HOST`/`PLUMLOOM_UI_PORT` para la UI; `PLUMLOOM_BANK_DIR` para el banco de poses.

## Comandos clave

```bash
python Engine/plumloom_engine.py                          # menú interactivo (input(), no scriptable)
python Engine/batch_progression_v3.py --chars 2 [--dry-run]   # BREAK separators
python Engine/test_harvest_tags.py --dry-run   # ej: --n 12 | --char Yelan --tag cowgirl_position --seed 12345
python Engine/wd14_tagger.py [--threshold 0.35] [--model swinv2v3|eva02|vittagger] [--force --json]
python Engine/test_blocks.py --pool explicit --block 7      # | --all | --n 4
python Engine/character_keys.py --list | --random 5 | --check
python Engine/batch_poses_chars.py --chars 12 [--dry-run] [--exclude-used] [--exclude-poses pb0009]
python Engine/ui/app.py [--port 8080]                     # http://127.0.0.1:5055 (Flask)
python Engine/pixiv_per_image.py [--run "<run>"] [--n 3] [--dry-run]
python Engine/clean_accesorios.py [--apply]
```

Todos los scripts aceptan `--help`. Publicación en Pixiv/X **no** está en la UI ni en scripts: se hace con `browsermcp` (ver `DOCUMENTACION/07-guia-publicacion-x.md` y `11-guia-publicacion-pixiv-mcp.md`).

## Arquitectura

Flujo canónico: `PromptBuilder` (`Config/master_prompt.yaml` + `Config/excepciones_maestras.yaml` + excepciones) → `ForgeClient.txt2img` (`Engine/api_client.py`) → `VisionCritic.full_audit` (OVHcloud Qwen2.5-VL, auto-aprueba si la API falla) → `Output/campaigns_X` / `Output/patreon_NSFW`; rechazos a `Output/rejected`.

- `Engine/plumloom_engine.py` **reimplementa su propio prompt** en `run_ai_batch`, ignorando `PromptBuilder.build_prompt`. Si cambias la estructura del prompt, mira los dos sitios. `assemble_prompt()` en `Engine/test_blocks.py` es la implementación de referencia para regeneración.
- `Archivos/personajes.txt` (216 líneas) = única fuente de personajes; `personajes_base.txt` es su copia byte a byte. No normalices espacios de los tags (decisión del usuario). `personajes.txt`: UTF-8 sin BOM, LF; copiar con `Copy-Item`, no `Set-Content`.
- `PromptBuilder` indexa por **personaje, no por LoRA**: la clave es el nombre del LoRA salvo packs multi-personaje (entonces, el primer tag que difiera).
- `character_keys.py --check` reporta LoRAs ausentes en disco (`training_*` son esperados, sin entrenar). Distingue líneas sin LoRA (5 hoy: salen con el checkpoint por defecto).
- `Config/plumloom_engine.yaml` tiene notas desactualizadas ("229 personajes", `always_add_age_up: true`, endpoints DeepSeek/LiteRouter). Manda la práctica documentada en `DOCUMENTACION/02` + este archivo, no el YAML.

## Estructura de prompt (no negociable)

`BREAK` separa tramos: `MASTER+LoRAs` / `1girl + personaje + half naked` / (si hay hombre) `BREAK 1boy` / `BREAK` / bloque de pose (`pose → entorno → acto → cuerpo → expresión → estado → cámara`).

- `1boy`/`1girl` como máximo una vez en todo el prompt.
- MASTER lleva `1girl, solo` (quitar `solo` en pareja).
- Patrón de desnudez (nov-2026): `half naked` es el gancho principal en explicit (cosplay/fantasía conserva outfit); `nude, completely_nude, naked` solo como tramo final (explicit2/cierre). `half naked` aún no está implementado en `Engine/` — vive en `_sandbox_cl_tagger/`; `assemble_prompt()` hoy inyecta full nude condicional. Ver `DOCUMENTACION/02-estructura-prompt.md`.
- Cámara/ángulo al final del bloque de pose. Nada de `age up, mature female` ni `full body`/`wide shot` por defecto.
- Bloques de pose **sin `|`** (el text encoder las lee como OR); solo comas.
- Nunca `nude`/`naked`/`nsfw`/`explicit` en negative (usar `censored, mosaic censoring, bar censor`). Sin espejos/reflejos ni `airship_gondola, hothouse, submarine, space_station, ice_cave`. Nunca `no X` en positive (refuerza X); la negación va en negative.
- Resoluciones: vertical `832x1216`, horizontal `1216x832`. Cuadrado `1024x1024` pendiente.

## Gotchas críticos

- **No ejecutar `comfyui_*.json` de la raíz** (descartados). Ruta activa: Forge Neo. **DeepSeek archivado** en `_legacy/`; `Engine/plumloom_engine.py` lo importa con `try/except ImportError`, `designer=None` y el menú marca `[7][8][9]` desactivados. Es lo esperado, no un bug.
- **CSVs de Danbooru:** `Danbooru tags.csv` **tiene cabecera** (`tag,type,count,aliases`); `Danbooru e621 merged.csv` **no** (primera línea es dato). `type`: 4=personaje, 3=copyright, 0=general, 1=artista, 5=meta.
- **Unicode en consola:** CLIs que imprimen claves con macrones/tildes necesitan `sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace")` (ver `character_keys.py`).
- **Mojibake:** 2 líneas de `personajes.txt` sin reparar a propósito (Magik con macron, 거유프린세스). Al reparar, byte a byte solo si el nombre existe en `models\Lora`.
- **`Path.write_text` convierte `\n`→`\r\n` en Windows** → usa `write_bytes` para saltos controlados. `.bat` requiere CRLF + sin BOM.
- **`generate()` escribe `file` como `img/<nombre>` sin personaje** → al fusionar en `manifest_all.json` hay que anteponer la carpeta del personaje o revienta con `FileNotFoundError`.
- **`fs_name()` (UI):** claves con `\(` etc. sanitizan el nombre de carpeta; los lotes viejos (`char_dir==clave`) siguen funcionando.
- **Pose + `from_behind` sin `solo` ⇒ hombre intruso.** La ropa la decide el outfit, no la pose: no poner `bare_shoulders`/`sleeveless`/`topless`/`undressing` en líneas de pose. Outfits con torso llevan tag de manga explícito (`long sleeves`).
- **`backless` no existe en Danbooru** (usar `bare_back`/`off_shoulder`/`sleeveless`/`strapless`). `sideboob` sí es intencional en spicy.
- **`--structure break` es obligatorio** (A/B medido: sin BREAK la pose no se dibuja). `test_harvest_tags.py` lo usa por defecto.
- **Poses de 2 personas no funcionan en solitario** (pelean con `1girl, solo`); para NSFW con penetración añadir `MALE_TAGS`.
- **LoRA horneado** (ropa/cuerpo en el propio LoRA): negative dirigido + bajar peso a ~0.6–0.7, no borrar tags. 0.45–0.5 solo para sobrescribir ropa horneada; <0.4 diluye la identidad.
- **Un peso dentro del bloque de pose no llega** (`strip_weights_of()` los borra).
- **Auditar personaje con `--nude`**, no con outfit (la ropa esconde el problema).
- **Accesorios de cabeza = identidad** (no tocar); el cuerpo sí se limpia. `clean_accesorios.py` es idempotente. `ribbon`/`bow`/`ascot` no se deciden por regex (pelo vs cuello): hardcodear por personaje con preview.
- **`onnxruntime` es CPU** (~1.5–1.9 s/img). WD14 default `swinv2v3` (único que identifica personajes); `vittagger` da 0 tags de personaje. Bugs ya costosos: no filtrar nombres del modelo (desplaza 4 posiciones por ratings), letterbox a cuadrado con fondo blanco antes de escalar, rglob en `inbox/` (tiene `SFW/`/`NSFW/`).
- **`avoid.jsonl` indexa por línea normalizada, no por id** → editar una línea de pose blacklisted la reactiva. `pb0005`/`pb0012` blacklisted (`legs_up`, `standing_sex`); `pb0009` aceptado con defecto.
- **Seed aleatoria por imagen** (decisión del usuario); `-1` pide aleatoria y guarda la real. Seed fija solo para corregir una imagen.
- **Bibliotecas unificadas:** `Archivos/pose_bank/BIBLIOTECA_UNIFICADA.md` consolida attire + actos/expresiones + posturas + poses aprobadas/candidatas.
- **UI:** preview y lote son el mismo objeto (`PLANS` en memoria, máx. 24, caduca al reiniciar el servidor). La UI no publica en Pixiv/X.
- **Pixiv:** sin API de escritura; subida manual con `browsermcp`. Límite 10 tags/obra; tags se insertan uno a uno con Enter; R-18 + AI work Yes; título `Personaje / キャラ (Tema)`. `pixivpy3` devuelve tags como `{"name":...}` (no `{"tag":...}`). `pixiv_login.bat` (CRLF, sin BOM) usa `gppt`.
- **Publicación X:** serie de 4 posts/día por pack: Preview + Part 1/3 + Part 2/3 + Part 3/3 (cada parte 3–4 imgs, progresión sugerente → spicy → explicit). Última imagen de Part 3 difuminada + CTA en inglés "Want more? Full pack on my Patreon → link in profile". Cada post: 4 bloques `\n\n`: frase temática → `|| Personaje ~ Franquicia ||` (siempre con `~`, CTA exacto `More on my profile ♥`) → hashtags. Cuenta `@plumloomart`. Ver `DOCUMENTACION/18-pipeline-patreon-x.md`.
- **Ciclo de validación:** días sin pack = tests de personaje (3–4 imgs: sugerente + explicit suave opcional) para medir demanda antes de producir el pack. Criterio: ≥1.5× la media semanal en likes+bookmarks, o señal cualitativa. Fast-track: si hay tendencia, 2 tests → pack el jueves. Variantes de explicitness (a) solo sugerente / (b) + explicit suave al final se prueban para saber cuál empuja a Patreon. Programación en Metricool en un solo batch. Ver `DOCUMENTACION/18-pipeline-patreon-x.md` §5.9.
- **Redes nuevas (nov-2026):** BlueSky creada 08-11-2026 (espejo de X, admite NSFW, sin problema) — tiene 2 semanas de posts diarios programados en Metricool, queda pasivo por ahora. Instagram y Facebook creadas 09-11-2026 — solo imágenes SFW sugerentes (primeras del pack) para calentar ~1 mes sin audiencia; Patreon se habilita en ellas cuando haya público. X es la cuenta principal (mayor audiencia). Pixiv no cambia.
- **Prioridades (orden):** 1) nuevo flujo de trabajo → 2) validar sets/packs de calidad → 3) subida X/Patreon sin dejar calvo a X → 4) actualizar tiers y perfil de Patreon → 5) empezar a subir contenido. Patreon va por encima de BlueSky.
- **Plataformas evaluadas (nov-2026):** Fanbox = **no** (anti-IA; no arriesgar el perfil de Pixiv como colateral). Discord a partir de 20–50 miembros de Patreon; Telegram tras crecer comunidad. Pawchive = espejo automático de posts públicos (no subir a mano). Mastodon (instancias NSFW) como espejo de respaldo; Reddit solo orgánico, tardío; JanitorAI (bots de rol) como test de embudo con personajes 18+ si se quiere tracción extra.

## Referencias

- `DOCUMENTACION/02-estructura-prompt.md` — estructura fija de prompt (**manda** sobre cualquier otra cosa).
- `DOCUMENTACION/00-README.md` (índice y reglas de trabajo), `03` (bloques WD14), `13` (guía operativa), `15` (memoria nov-2026), `16` (radar tags).
- `MEMORIAS/memory.md` — historial completo. `.opencode/skills/plumloom-spicy-generator/SKILL.md` — auditoría visual.
- `Config/male_template.txt` — hombre siempre `faceless male, no face`.
