CommunityRedacción y edicióngithub.com

FalkWoldmann/skills

Útil para cualquier reestructuración que preserve el comportamiento de un núcleo de protocolo Rust sans-IO. Funciona con cajas como quinn-proto, str0m o un resolvedor, handshake, framing o núcleo de cliente.

¿Qué es skills?

Este skill está diseñado para refactorizar de manera segura y legible un núcleo de protocolo Rust sans-IO, es decir, una máquina de estados que separa la E/S externa. Permite reestructurar el código sin cambiar su comportamiento, haciendo que los estados, transiciones, salidas y temporizadores sean explícitos y fáciles de seguir. Funciona con plataformas como Cursor, Claude Code y Codex, y se basa en un flujo de trabajo en pasos pequeños: mapear la máquina de estados, aplicar cambios como enumeraciones de fases o funciones de transición, y verificar con pruebas existentes y un arnés diferencial. Es ideal para pedidos como "hacer este resolver.rs más legible" o "desenredar flags en estados", pero no para corregir errores, agregar funciones o ajustar rendimiento.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/FalkWoldmann/skills/tree/HEAD/sansio-readability

Installed? Explore more Redacción y edición skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Máquinas de estado sans-IO legibles

Una biblioteca sans-IO es una máquina de estados con la E/S extraída. Quien la llama le alimenta entradas (handle_*, cada una con now), drena sus salidas (poll_transmit, poll_event, poll_timeout) y posee los sockets y temporizadores. Código como este vale la pena tenerlo solo si la gente puede razonar sobre él: el próximo mantenedor, el autor de un controlador, un revisor que lo verifica contra un RFC. Cada uno de ellos debería poder responder cuatro preguntas rápidamente:

  1. Estados: ¿en qué estados puede estar cada entidad de larga duración (conexión, consulta, flujo, sesión) y qué datos existen en cada uno?
  2. Transiciones: ¿qué entradas lo mueven de un estado a otro y dónde en el código ocurre cada movimiento?
  3. Salidas: ¿qué encola cada movimiento (transmisiones, eventos, escrituras) y en qué orden?
  4. Tiempo: ¿cuándo necesita despertarse y qué regla establece cada plazo?

Tu trabajo es hacer visibles esas respuestas en los tipos y el código mientras la máquina sigue haciendo exactamente lo que hacía. Editas el árbol de trabajo y terminas con un resumen.

<skill> a continuación significa el directorio base de este skill, la carpeta que contiene este SKILL.md.

Reglas básicas

  • El comportamiento permanece idéntico. En una caja sans-IO, el comportamiento cubre cosas que parecen cosméticas:

    • el orden en que se encolan las salidas
    • el orden de iteración de los mapas (un BTreeMap o un Vec con orden de inserción decide quién va primero)
    • qué instante devuelve poll_timeout
    • si se emite una escritura vacía o un evento sin operación
    • con qué error falla o se cierra una entidad
    • la salida de un tipo #[derive(Debug)] cuyos campos cambias, si una instantánea, aserción de registro o mensaje de error lo imprime

    Las pruebas que pasan por la API pública, y cada instantánea, deben pasar sin cambios. Si una necesita edición, ese paso cambió el comportamiento: deshazlo. Las pruebas unitarias dentro de la caja que nombran elementos privados pueden necesitar la misma edición mecánica que el código (una bandera que se convirtió en variante, un ayudante renombrado). Haz solo esa edición, mantén lo que cada aserción verifica y lista la prueba en el resumen.

  • Deja la API pública intacta. Mantén los nombres, firmas, variantes y campos de los elementos pub tal como están, ya que los controladores y las cajas descendentes dependen de ellos (y de semver). pub(crate) y los elementos privados son juego limpio. Pon las mejoras de API en el resumen como sugerencias. Cómo verificar esto está en el paso 5.

  • No gastes rendimiento por legibilidad. Sin nuevos clones, asignaciones o pasadas adicionales sobre los mapas en rutas que se ejecutan por entrada (por paquete, mensaje o consulta). Un mem::replace o mem::take es gratuito; clonar un Vec para esquivar el verificador de préstamos no lo es.

  • Trabaja en pasos pequeños. Cada paso compila y pasa las comprobaciones, así que una comprobación fallida señala un cambio y el diff se puede revisar pieza por pieza.

  • Sigue el proyecto. Antes de editar, lee sus documentos de agente y contribuidor (AGENTS.md, CLAUDE.md, CONTRIBUTING), la configuración de lint y el flujo de trabajo de CI. Coincide con la densidad de comentarios y el modismo circundante. Donde el proyecto mantiene documentos en sintonía con el código (una tabla de ciclo de vida, un comentario que mapea filas a funciones, una tabla de cobertura de especificaciones), actualízalos en el mismo paso.

Tamaño del trabajo según el cambio

La red de seguridad existe para detectar cambios de comportamiento que las pruebas existentes pasarían por alto, así que ajústala al cambio que planeas:

  • Pequeño. Un manejador o algunos ayudantes, sin cambio en qué campos contienen estado. Ejecuta las comprobaciones del proyecto una vez antes y una vez después, y omite todo lo marcado como grande a continuación. En el resumen, mapea solo la entidad que tocaste.
  • Mediano. Varias transiciones o ayudantes de salida en un módulo. Ejecuta las comprobaciones antes de cada paso y al final. Construye un arnés diferencial solo si ninguna prueba existente fija las salidas exactas de las rutas que cambias, y solo sobre esas rutas. Sin fuzzing, sin casos adicionales de pruebas de propiedades, sin benchmarks: las comprobaciones propias del proyecto en su configuración normal son el peso adecuado aquí.
  • Grande. Banderas reemplazadas por un enumerado de fase, muchos escritores de un campo de estado fusionados en transiciones, la mayoría de los manejadores reformados. Usa todo el flujo de trabajo.

Decide el tamaño después del mapa (paso 2). Si el cambio crece a medio camino, sube un tamaño y dilo en el resumen.

Flujo de trabajo

1. Configura una red de seguridad

Haz esto antes de la primera edición, para que cada fallo posterior sea tuyo y no preexistente.

  • Anota la base. Ejecuta git status y anota el commit base: cada comparación posterior es contra él. Si los archivos que planeas tocar ya tienen cambios sin commit, díselo al usuario antes de mezclar tus ediciones con las suyas. Si continúas encima de ellos, usa git stash create como base: los registra como un commit sin tocar el árbol.
  • Fija un comando de comprobación. Usa el propio del proyecto si tiene uno (un flujo de trabajo de CI, un justfile, un xtask, los comandos en sus documentos de agente). De lo contrario, usa la puerta incluida: <skill>/scripts/check.sh <repo> default all. Lista los conjuntos de características que usa CI, por ejemplo default all none tls. La puerta verifica el formateo sin cambiarlo, luego ejecuta clippy y las pruebas para cada conjunto de características, y sale con código distinto de cero si algo falla. Limítalo al paquete que cambias y a los miembros que dependen de él con CHECK_PACKAGES="-p <crate> -p <dependent>", y ejecuta todo el workspace una vez al final. Si CI también ejecuta algo más (una verificación de MSRV, banderas de lint adicionales), ejecuta eso también.
  • Si la línea base no es verde, no la arregles. Registra qué comprobaciones fallan y cómo, díselo al usuario, y a partir de entonces espera los mismos fallos y ninguno nuevo. Formatea solo los archivos que editas. Un fallo que proviene del entorno más que del código (una configuración global de cargo con banderas solo nightly, una cadena de herramientas o herramienta faltante) va bajo Verificación como "no se pudo ejecutar", con la razón. Si el repositorio no tiene CI, toma el piso de la cadena de herramientas de rust-version, la edición y las características del lenguaje en uso, y di qué pudiste y no pudiste ejecutar.
  • Cambios grandes: construye un arnés diferencial cuando las pruebas existentes no fijan las salidas exactas de las rutas que cambiarás. Fijar significa pruebas de traza o instantáneas que afirman la secuencia de salida y los plazos al pie de la letra; afirmaciones sobre el estado final solo no cuentan. El arnés ejecuta la base y el árbol de trabajo lado a lado en las mismas llamadas aleatorias y compara cada salida. <skill>/scripts/difftest_scaffold.py crea el par de cajas y una plantilla fuera del repositorio. Llenar la plantilla es el trabajo real, así que presupónlo: lee references/differential.md primero. Verifica que detecta un mutante de una línea plantado antes de confiar en él; uno es suficiente.
  • Mantén el arnés más barato que la refactorización. Conduce las rutas que reestructuras, no toda la caja. Si cubrir una ruta costaría más que el cambio de ella, informa la brecha bajo Verificación en su lugar y confía en las pruebas existentes de esa ruta. Ejemplos: una ruta detrás de TLS o cifrado, no determinismo que tendrías que normalizar, un par que tendrías que simular.
  • Solo cambios grandes: ejecuta las comprobaciones lentas una vez con más casos. Ejecuta las pruebas de propiedades, dimensionadas para terminar en unos minutos (diez veces el valor predeterminado, o PROPTEST_CASES=10000 cuando los casos son baratos), y de nuevo al final. Ejecuta un objetivo de fuzzing solo si llega al código que cambiarás, limitado a 30–60 s. Ejecuta benchmarks solo si un cambio agregará trabajo en una ruta por entrada; reestructurar el flujo de control no los necesita.
  • Llena las brechas de cobertura que deberían sobrevivir al cambio. Si una ruta que reestructurarás no tiene cobertura y no estás construyendo un arnés, agrega pruebas que fijen sus salidas actuales primero. Si estás construyendo uno, cubre la brecha para la refactorización; agrega una prueba solo para un comportamiento que descubra y que valga la pena mantener (una rareza que mantuviste a propósito). Pon las nuevas pruebas en un archivo o módulo nuevo, como su propio paso, para que las pruebas existentes permanezcan sin cambios. La excepción es la única línea mod que un diseño de prueba de caja única como tests/main.rs necesita.

2. Mapea la máquina

Mapea las entidades sobre las que trata la solicitud, y cualquier entidad que un cambio planificado tocaría. En una caja grande donde la solicitud no dice qué parte importa, elige la entidad que el usuario nombró y di qué dejaste fuera. Escribe el mapa antes de cambiar nada (un archivo temporal está bien). Se convierte en el mapa de estados del resumen, y a menudo en la documentación del módulo.

  • Entidades. Para cada entidad en alcance, lista sus campos y agrupa los que significan algo en la fase en la que están. Las combinaciones de bools y Options que codifican una fase entre ellas son el objetivo principal.
  • Entradas. Cada pub fn que toma datos externos o now, y qué puede desencadenar cada uno.
  • Escritores. Para cada campo de estado, cada lugar que lo asigna (busca .field =, mem::replace, .take(), += 1). Un campo con muchos escritores es una transición repartida por el archivo.
  • Salidas. Cada inserción en una cola de salida, y cada plazo que poll_timeout lee.
  • Invariantes. Qué debe mantenerse siempre: un ID en exactamente un mapa, un plazo establecido siempre que algo espera, una conexión referenciada que existe.
  • Rarezas. Comportamiento que parece accidental: análisis permisivo, precedencia extraña entre dos errores, una respuesta enviada como respuesta a basura, entrada aún aceptada después de un plazo. Lo mantienes (regla 1), pero listalo para el usuario.

Luego escribe el mapa como una tabla: estado, entrada, siguiente estado, salidas. Donde no puedas llenarlo desde el código sin rastrear varias funciones, el código aún no es legible, y ahí es donde va el trabajo.

3. Elige los cambios

references/patterns.md es el catálogo, con ejemplos antes/después y los casos en que cada patrón no vale la pena. Lee su índice, luego las secciones para lo que sea que tu mapa encontró, incluyendo sus párrafos "No vale la pena". Lee el archivo completo para un cambio grande. Clasifica los cambios candidatos por cuánto acorta cada uno la respuesta a una de las cuatro preguntas, contra el tamaño del diff. Los movimientos que suelen rendir más:

  • un enumerado cuyas variantes llevan los datos de cada fase, en lugar de banderas y opciones que codifican la fase (patrón 1)
  • una función nombrada por transición, para que cada campo de estado tenga pocos escritores (patrón 2)
  • manejadores que coinciden con la entrada, deciden, aplican la transición y encolan las salidas, a lo largo de un eje de despacho consistente (patrón 3)
  • un ayudante por tipo de salida, nombrado según su intención (patrón 4)
  • cada regla de plazo escrita una vez, con poll_timeout derivado del estado (patrón 5)
  • el ciclo de vida escrito donde un lector aterriza, e invariantes en los campos (patrón 9)

Si el usuario nombró una función o una parte, cambia solo eso y lo que necesita. Un ayudante que extraigas puede ser reutilizado por sus hermanos naturales, pero no reelabores código fuera de la solicitud solo porque el ayudante también encaja allí. Lista esas mejoras bajo Sugerencias en su lugar: un revisor que preguntó por una función no debería tener que verificar una segunda.

Deja fuera los cambios que solo mueven código, renombres de variables locales para ajustarse a un gusto, y cualquier cosa que haga el código más largo sin facilitar responder una pregunta. Cuando el usuario pida el movimiento en sí (dividir un archivo largo en módulos), ve el patrón 11. El código que ya se lee bien puede quedarse como está: dilo en el resumen en lugar de inventar trabajo.

4. Aplica en pasos

Ordena los pasos para que cada uno sea revisable y ninguno agregue código que un paso posterior elimine:

  1. Adiciones y extracciones puras: ayudantes de salida, predicados nombrados, ayudantes de plazo, pasos protegidos por características movidos a ayudantes, codificación y análisis de cable movidos a funciones (patrones 4, 5, 6, 10).
  2. El cambio central: un enumerado de fase, junto con las transiciones y formas de manejador que fuerza (patrones 1–3, 7, 12). Esto a menudo tiene que aterrizar como un solo paso. Si es así, di por qué.
  3. Nombres (patrón 8).
  4. Documentación (patrón 9).
  5. Reordenamiento de métodos, al final y por sí mismo, si acaso (patrón 11).

Entre ediciones, usa un bucle rápido: clippy y las pruebas del paquete que estás cambiando, en el conjunto de características predeterminado, más el arnés si construiste uno. Al final de cada paso, ejecuta el comando de comprobación del paso 1. Luego haz el paso un commit si el usuario quiere commits (lo pidió, o el flujo de trabajo del proyecto lo espera); de lo contrario, deja el trabajo sin commit y lista los pasos en el resumen. No silencies un lint con andamios que el siguiente paso elimina (una puerta cfg o un #[allow] agregado solo para que un paso sea verde). Reordena los pasos en su lugar.

Abandona un patrón en cualquier punto donde el verificador de préstamos lo fuerce a contorsiones, ya que el código contorsionado se lee peor que con lo que empezaste. Las señales habituales:

  • un método que toma un campo hermano de self (una cola de salida como &mut VecDeque<Transmit>) o su propia clave de mapa como argumento, solo para superar un préstamo
  • un método &self que toma una subestructura solo para poder leer la configuración
  • claves clonadas o una búsqueda adicional

Las soluciones habituales:

  • devuelve la salida y deja que quien llama la encola
  • pon el ayudante en la entidad y pasa el valor de configuración Copy que necesita
  • mantén el código en línea

5. Verifica y resume

Ejecuta el comando de comprobación una última vez, sobre todo el workspace, más cualquier otra cosa que hayas ejecutado al inicio: las comprobaciones lentas y el arnés.

Luego verifica la API pública, si el diff toca un elemento pub, un derive, una implementación de trait, un tipo de campo (los autotraits como Send siguen campos privados) o un cfg en un elemento:

  • Usa cargo semver-checks --baseline-rev <base> una vez por conjunto de características, con banderas explícitas: --default-features, --all-features, --only-explicit-features (ninguna), y --only-explicit-features --features x.
  • Solo detecta cambios disruptivos, así que también confirma que nada nuevo se volvió público: usa cargo public-api diff si está instalado, o lee git diff <base> | grep -E '^\+.*\bpub\b'.
  • Si no hay herramienta instalada, pregunta antes de instalar una, y de lo contrario informa la verificación de API como no ejecutada.
  • Si el diff toca solo cuerpos de función y elementos privados, di que eso

...

[truncado para traducción]

Skills relacionados