Escribir artefactos de texto/markdown largos en bloques y verificarlos con un script — nunca generar el fichero completo en una sola llamada.
Cuándo usar
- Generar cualquier fichero de texto/markdown largo (>~15KB, >~100 intervenciones/líneas): transcripciones limpias, informes, documentación, migraciones de contenido.
- Cuando una sola generación del fichero completo arriesga: timeout del proxy/gateway (ej. HTTP 524 de Cloudflare con LLMs lentos, origin >120s), truncación silenciosa, o respuestas que mueren a mitad y dejan el fichero sin escribir.
- Cuando el fichero debe ser fiel a una fuente estructurada (raw → clean, borrador → final) y necesita verificación automática.
Reglas (jul 2026, preferencia explícita del usuario — mandatorias)
- Nunca escribir el fichero completo de una vez. Siempre
write_filecon el primer bloque + appends sucesivos. - Tamaño por bloque: ~100 intervenciones / ~10-15KB máx por llamada. Si un bloque va a ser más largo, partirlo.
- Append con heredoc citado (sin interpolación):
cat >> "ruta/fichero.md" << 'EOF' ...bloque de contenido... EOF- El delimitador SIEMPRE entre comillas (
'EOF'): sin ellas,$, backticks y\se interpretan y corrompen el contenido. - Tras cada append, comprobar el tamaño acumulado:
wc -c ruta/fichero.md. - El heredoc equivale a un patch pero sin re-leer el fichero — más rápido y sin riesgo de anchors no únicos.
- El delimitador SIEMPRE entre comillas (
- Nunca uses un segundo
write_filesobre la misma ruta para «añadir una parte»: REEMPLAZA el fichero entero (ver Pitfalls). El único mecanismo de append es el heredoc citado del punto 3. Tras el último bloque, verificar en disco y estructuralmente — nunca fiarse del estado reportado:- El fichero existe y tiene tamaño razonable (
ls -la,wc -c). - Si el formato es
Nombre [MM:SS]: texto(tl;dv y similares):python3 scripts/verify_turns.py <raw> <clean>(script del skill). - La última línea/timestamp del fichero coincide con la fuente.
- El fichero existe y tiene tamaño razonable (
Pitfalls
-
Duplicar contenido al fusionar fragmentos: al fusionar un fragmento de continuación en el turno padre (mismo hablante, timestamps adyacentes, ej.
[50:40]+[50:45]), es fácil escribir por error el contenido de un turno posterior en el timestamp intermedio, duplicándolo. El verificador detecta texto duplicado entre turnos. -
Fusiones intencionadas ≠ pérdida: eliminar muletillas y fusionar continuaciones reduce el nº de turnos vs raw (normal). El verificador distingue: timestamps del raw ausentes = revisar si son merges intencionados; turnos de más que el raw = error real.
-
No fusionar turnos del mismo hablante si otro hablante interrumpió entre medias (aunque el interjector sea eliminable, ej. "Vale.", "Sí."): el raw los marca como turnos con timestamps distintos y el clean debe conservarlos separados. Validado jul 2026: fusionar [25:50]+[26:03] de un mismo hablante separados por un "Vale." de otro obligó a deshacerlo con patches para restaurar la fidelidad.
-
Timestamps duplicados en el raw (mismo [MM:SS] en varios turnos, hablantes solapados, ej. [09:47], [20:00], [30:52]) son legítimos: comparar SIEMPRE timestamps únicos (
sort -u), nunca el recuento bruto de ocurrencias — nunca coincide con el clean aunque esté perfecto. -
Heredoc sin comillas corrompe contenido con
$/backticks (ej. texto contl;dv,$PATH,console.log). -
Un solo bloque gigante = riesgo de 524/truncación. Dividir ANTES, no cuando ya ha fallado.
-
Tras un 524, verificar en disco antes de relanzar: el agente puede reportar "completed" aunque la última llamada muriera y el fichero no exista o esté incompleto.
-
BSD grep en macOS no soporta
\S,\d,\wen-E(POSIX ERE):grep -cE '^\S+ 'devuelve 0 matches SIN error (silencio peligroso). Para conteos/validaciones usar ERE puro:grep -oE '\[[0-9]{2}:[0-9]{2}\]'o[[:space:]]en vez de\s. -
Nunca recomponer un fichero con
split()+join()sobre el literal de una etiqueta o cabecera que quieras conservar:s.split('<section class="page', 1)+ rejoin BORRA los tags reales y deja fragmentos huérfanos (">) que solo se detectan al grepear el fichero. Síntoma típico: el fichero "pesa" y el contenido parece estar, perogrep -c '<section'devuelve 0. Para renumerar o transformar repeticiones usare.subcon contador sobre el texto COMPLETO (nunca split/rejoin), y tras cada write verifica los conteos estructurales:s.count('<etiqueta') == s.count('</etiqueta>') == n_esperado. Un write aparentemente exitoso (bytes escritos,verified: true) puede haber dejado el fichero sin sus etiquetas — la verificación estructural es la única prueba. -
Partir un documento y escribir la «segunda parte» con
write_fileBORRA la primera:write_fileSIEMPRE reemplaza el fichero completo — no existe modo append. Si divides un documento largo en partes y haceswrite_filede la parte 2 sobre la misma ruta, el fichero queda SOLO con la parte 2 y la parte 1 desaparece sin ningún error (verified: true, bytes escritos, lint OK). Detección inmediata:grep -n '^## ' ficherotras CADA escritura — si un documento que iba por la sección 0 empieza ahora por la 6, has pisado el head. Recuperación sin rehacerlo todo: conservar el head recién escrito y la cola en/tmp, y recomponer concat /tmp/head.md /tmp/tail_backup.md > fichero.md, volviendo a comprobar la lista de secciones. Corolario: al partir un documento, escribe la PRIMERA parte conwrite_filey TODAS las siguientes con heredoc citado. -
write_file+ appends sucesivos sobre un fichero que ya tiene un writer previo: si el mismo fichero se reescribe varias veces en la misma sesión (p.ej. renumera → reescribe → renumera), lee SIEMPRE el estado real de disco antes de la nueva transformación y comprueba los conteos tras escribir; encadenar transformaciones sobre un estado supuesto multiplica el riesgo de corrupción silenciosa. -
Heredoc y backticks en el comando: si el bloque a escribir contiene backticks (código, comandos, markdown), un heredoc sin citar o un
python3 -cembebido rompen el shell (unexpected EOF while looking for matching). Escribir el contenido conwrite_filea un.pyy ejecutarlo conpython3 /tmp/<script>.pyevita la clase entera de problemas de quoting y las aprobaciones repetidas de pipe-a-intérprete. -
patchborra líneas de contexto no reproducidas: enpatch mode=replace, cualquier línea delold_stringausente delnew_stringse ELIMINA del fichero. Si usas la línea siguiente como ancla de unicidad (ej. la línea> **Para quién:**tras una cabecera que quieres mantener) y no la repites ennew_string, la borras y fusionas el texto con la línea anterior. Regla:old_string= SOLO el texto a sustituir (sin líneas que quieras conservar); tras cada patch, leer el diff devuelto buscando borrados inesperados antes de lanzar el siguiente patch (visto en edición de handoff 8-ago-2026: cabecera fusionada, restaurada con un patch de fix). -
Markdown que tiene que RENDERIZAR: línea en blanco antes de toda lista. Python-Markdown (y la mayoría de conversores) absorbe
- itemcomo texto del párrafo anterior si no hay blanco entre medias: los ítems se ven como prosa con guiones, las casillas- [ ]NO se renderizan (quedan como[ ]literal) y bloques enteros —gates, criterios de aceptación— salen colapsados en una sola línea. En documentos generados campo a campo (una línea por dato, sin blancos) pasa SIEMPRE, y no se nota hasta que alguien convierte el markdown. Normalizar antes de convertir: insertar línea en blanco cuando una línea es ítem (-/*/1.) y la anterior no lo es ni es tabla/cita/código/encabezado. -
Bloques de metadatos como líneas consecutivas
**Clave:** valorse unen en un párrafo ilegible. Markdown los trata como un solo párrafo: si el documento va a renderizarse (HTML/PDF), escríbelos como tabla de dos columnas (| | |+ filas| **Clave** | valor |) — y si van a portada, la tabla se extrae y se pinta ahí. -
Los diagramas se verifican por su FUENTE y por el DOM, nunca con visión: al pedirle a un modelo de visión que describa un diagrama renderizado inventa defectos que no existen (reportó ids de nodo duplicados y etiquetas de clúster cruzadas en un grafo que estaba limpio). Ground truth: (a) parsear el bloque del propio fuente — etiquetas de
subgraph, ids declarados vs. los referenciados en los-->, ids duplicados; (b) contar en el DOM renderizado.node,.edgePathy.cluster, y errores de consola. La visión se usa solo para «¿se lee bien / hay cajas cortadas?», nunca para datos concretos que se puedan contar. Para renderizar y comprobar un diagrama basta un HTML con<pre class="mermaid">, el import del módulo ESM desde CDN y ~7s de espera antes de contar.
Scripts
scripts/verify_turns.py— Verifica un fichero "limpio" vs su raw en formatoNombre [MM:SS]: texto: turnos por hablante (raw vs clean), timestamps del raw ausentes en el clean, orden cronológico de los timestamps, coincidencia del último timestamp, y contenido duplicado entre turnos. Exit 0 = OK (con avisos de revisión manual), 1 = errores. Uso:python3 scripts/verify_turns.py <raw> [<clean>](si solo se pasa el raw, derivaclean/<base>_clean.md).
Relación con otros skills
- Para pares de documentos emparejados (PRD + roadmap, plan + tareas) existe un verificador listo:
scripts/verify_prd_roadmap.py— paridad fichas de tarea ↔ tabla de dependencias, DAG sin ciclos, cobertura de todos los requisitos del PRD, integridad de los nodos mermaid y aviso si un documento empieza por la sección equivocada (head pisado, ver Pitfalls). Uso:python3 scripts/verify_prd_roadmap.py [PRD.md] [ROADMAP.md]; exit 1 si hay fallos.- La cobertura se comprueba por ID, no «a ojo»: cada requisito del PRD (
**F1.,**F2., …) tiene que aparecer por su ID en alguna ficha de tarea o fila de la tabla. Leer 29 requisitos contra 39 fichas parece exhaustivo y aun así deja cinco sin cubrir en silencio — el grep de IDs es lo que lo detecta. Un requisito sin tarea es un hueco de alcance, no un olvido cosmético. - Paridad en los dos sentidos: ficha sin fila en la tabla de dependencias Y fila sin ficha son ambos errores. Y IDs únicos: una pasada de renumeración duplica con facilidad (
T2.10avsT2.1). - El verificador no decide si la cobertura es CORRECTA: puede que un requisito esté cubierto por una tarea con otro nombre, o que la referencia al ID se haya añadido al texto sin que la tarea exista de verdad (pasar el verificador no es haberlo resuelto — comprobar que la tarea hace lo que el requisito pide).
- La cobertura se comprueba por ID, no «a ojo»: cada requisito del PRD (
- El pipeline de transcripciones (skill
clean-transcriptions-auto, del usuario) aplica estas reglas con tamaños concretos; si está adoptado por el curator, las reglas viven allí. Este skill es la técnica genérica reutilizable en cualquier generación de ficheros largos.