Buscar/Crea una herramienta de línea de comandos que no pierda datos ni ejecute código sin aprobar

Crea una herramienta de línea de comandos que no pierda datos ni ejecute código sin aprobar

Úsala cuando una IA cree o cambie una CLI que ya tiene usuarios. Mantiene compatibles opciones y configuración, se detiene antes de destruir trabajo, ejecuta los hooks del proyecto solo tras aprobarlos, rastrea cada subproceso y muestra lo local antes que la red.

Para usar estas reglas de IA
## Mantén compatibles el archivo de configuración y las opciones de comando a medida que crece Una vez que una herramienta tiene usuarios reales, hay que equilibrar el diseño limpio con la compatibilidad. Un rediseño no es, por sí solo, motivo suficiente para romper la experiencia de los usuarios. ### Justifica cada ruptura externa Cambiar una interfaz externa requiere una mejora concreta y visible para el usuario, no solo una limpieza interna. Prefiere una advertencia de obsolescencia y una ruta de migración antes que una ruptura silenciosa. ### Define la superficie protegida Decide qué interfaces son contratos (el formato del archivo de configuración, las opciones y argumentos de línea de comandos) y cuáles pueden cambiar libremente (las APIs internas, la ubicación de los logs, el formato de salida legible para humanos). Protege el primer grupo y mantén el segundo flexible para que las refactorizaciones sigan siendo económicas. ### Delimita la ventana de soporte Declara una versión mínima compatible del entorno de ejecución o la cadena de herramientas, y avánzala con una cadencia de mantenimiento predecible en lugar de hacerlo de forma improvisada. ## Usa una sola palabra por concepto en la documentación, la ayuda y los comentarios Elige un solo término por concepto y úsalo de forma idéntica en la documentación, los textos de ayuda y los comentarios del código. Un vocabulario que va cambiando con el tiempo confunde silenciosamente tanto a los usuarios como a quienes mantienen el proyecto. ### Escribe el glosario Mantén una lista breve que fije el significado de cada palabra y los casi sinónimos que deben evitarse. Distingue explícitamente los conceptos que se parecen entre sí, como el checkout original frente a uno secundario, o el destino de una operación frente al espacio de trabajo en el que se ejecuta. ### Nunca sobrecargues una palabra clave Si una palabra ya nombra un concepto, no la reutilices para otro; elige una palabra distinta para el segundo caso, incluso cuando la reutilización suene natural dentro de una misma frase. ## Actualiza la ayuda con cada cambio de comportamiento y compruébala con una prueba Todo cambio de comportamiento debe llevar su cambio de documentación en la misma edición. La prueba a aplicar es: ¿el texto de ayuda sigue describiendo lo que el código realmente hace? ### Mantén una única fuente principal por cada dato Para la documentación generada, edita la fuente de verdad (la ayuda incrustada en el código y las definiciones de comandos) y vuelve a generar las copias renderizadas; nunca edites a mano un archivo generado. Cuando un mismo directorio contiene tanto copias generadas como páginas escritas a mano, confirma qué rol cumple cada archivo antes de editarlo. ### Refuerza la sincronización con una prueba Una prueba de sincronización de documentación que compara la salida renderizada con la fuente detecta el desfase de forma mecánica. Editar el texto de ayuda también cambia las capturas renderizadas, así que vuelve a generarlas y revísalas en el mismo cambio para mantener la prueba en verde. ## Detente con un error en vez de destruir archivos del usuario sin consentimiento Trata los datos del usuario como algo sagrado. Un comando que falla pero preserva el trabajo es mejor que uno que "tiene éxito" destruyéndolo silenciosamente. ### Prefiere el fallo antes que la pérdida silenciosa Si una operación puede destruir archivos no rastreados, cambios sin confirmar u otros datos del usuario, detente con un error en lugar de continuar. ### Exige consentimiento explícito para destruir datos Una acción destructiva, como eliminar datos a la fuerza o sobrescribirlos, solo debe ejecutarse cuando el usuario la haya solicitado explícitamente. Nunca elimines ni sobrescribas como efecto secundario de una operación no relacionada; convierte la limpieza en un paso separado que el usuario elija. ### Favorece la variante que falla y ten en cuenta el intervalo entre la verificación y el uso Cuando existen dos formas de realizar una operación, elige la que falla ante un conflicto en lugar de la que fuerza el resultado. Sé conservador cuando transcurre tiempo entre una verificación de seguridad y la acción que protege: el estado puede cambiar en ese intervalo, así que no fuerces el resultado durante una limpieza. ## Pasa cada subproceso por un único envoltorio que lo registre y mida su tiempo Envía cada comando externo a través de un único envoltorio de ejecución en lugar de llamar directamente a la API de procesos. Una sola ruta ofrece de forma gratuita un registro de depuración y una medición de tiempos consistentes, además de un único lugar donde cambiar el comportamiento. ### Un envoltorio, un emisor Mantén la gramática de rastreo en un único componente y conviértelo en el único que escribe las llamadas de registro subyacentes, de modo que un subproceso quede rastreado a través de ese guardián o no produzca ningún registro en absoluto. ### Los nuevos puntos de creación de procesos deben sumarse voluntariamente Algunas formas de creación de procesos no encajan en el envoltorio y deben construir el registro de rastreo a mano: iniciarlo justo antes de crear el proceso, completarlo si tiene éxito y marcarlo como fallido si hay un error. Convierte el guardián en un valor de uso obligatorio que dispare una aserción si se descarta sin resolver, de modo que una finalización olvidada haga fallar una prueba en lugar de quedar silenciosamente sin rastrear. Los procesos hijos independientes en segundo plano y los asistentes interactivos son las excepciones documentadas que no se rastrean. ### Depura las variables de descubrimiento heredadas Algunas variables de entorno se resuelven antes de que un proceso hijo consulte su directorio de trabajo, por lo que un valor heredado puede sobrescribir silenciosamente el directorio que elegiste. Cualquier punto de creación de procesos que reubique un comando del usuario en un directorio seleccionado por la herramienta debe limpiar esas variables; los procesos hijos que se ejecutan en el propio contexto del usuario conservan el contexto heredado, convertido en absoluto. ## Decide según los códigos de salida y la salida JSON, no según el texto de los mensajes Prefiere los códigos de salida, el estado legible por máquinas y los formatos estructurados antes que analizar mensajes legibles para humanos, que se rompen con los cambios de idioma, versión y redacción. ### Elige el canal estructurado Lee la salida en formato porcelain o JSON y los indicadores de estado numéricos en lugar de la salida predeterminada para humanos. Cuando un comando codifica su significado en el código de salida, ramifica según ese código, no según el texto del mensaje. ### Documenta la fragilidad inevitable Cuando no existe una alternativa estructurada y debes analizar texto, marca la fragilidad directamente en el punto donde se hace el análisis, para que el próximo lector sepa que se trata de un riesgo conocido y no de un contrato estable. ## Muestra primero los datos locales y añade después los datos de red La capacidad de respuesta es una funcionalidad en sí misma: dibuja primero a partir de datos locales y deja que los detalles más lentos lleguen después. ### Nunca dejes que la red bloquee el primer fotograma Accede a la red solo cuando la solicitud del usuario realmente lo requiera. Todo comando debe renderizar su primera salida a partir de datos locales y transmitir progresivamente los detalles derivados de la red; un comando que no puede dibujar su primer fotograma hasta que regrese una llamada remota representa el modo de fallo, que es peor aún en una instalación recién hecha. Una ruta crítica síncrona, como el prompt de una shell, no debe tocar la red en absoluto, ni siquiera de forma progresiva. ### Prefiere contextos delimitados por el usuario para los datos remotos en vivo Transmite la salida del comando línea por línea en lugar de almacenarla en un búfer. Un selector interactivo es el lugar más tolerante para el trabajo de red: se dibuja de inmediato, y si el usuario actúa antes de que una llamada lenta responda, la solicitud simplemente se abandona. Un comando que debe ejecutarse hasta el final no puede terminar hasta que todas las llamadas respondan, así que obtén los datos remotos en vivo dentro del selector, de forma progresiva. ## Detén todos los bucles cuando el usuario interrumpa un comando en ejecución Cuando un proceso hijo muere a causa de una señal (interrupción o terminación), todo bucle en la ruta de ejecución en primer plano debe abortar en lugar de avanzar a la siguiente iteración. ### Lee el canal de señales estructurado Detecta las salidas derivadas de una señal a partir del campo de señal estructurado, nunca inspeccionando si el código de salida es igual o superior a 128 ni analizando el texto del error. Propaga un resultado canónico de salida por interrupción e interrumpe el bucle. ### Verifica antes de cualquier rama que absorba errores Realiza la verificación de interrupción antes de que se ejecute cualquier estrategia de manejo de fallos, de modo que una política de "advertir y continuar" no pueda absorber una interrupción. Todo código nuevo que recorra procesos hijos en un bucle debe realizar esta verificación en cada iteración; de lo contrario, una sola interrupción durante una operación de varios pasos avanzará igualmente por los pasos restantes. ## Ejecuta los hooks y alias definidos por el proyecto solo tras su aprobación Los comandos definidos por el proyecto (hooks de ciclo de vida, alias, cuerpos de ejecución tomados de la configuración del proyecto) son código arbitrario que viaja dentro de un repositorio que el usuario puede acabar de clonar. Solo deben ejecutarse después de que un sistema de aprobación los autorice. ### La puerta de control es todo el límite de seguridad Nunca construyas una ruta de código que ejecute comandos definidos por el proyecto sin pasar por esa puerta de control. Un atajo del tipo "ya validamos la operación, así que ejecutemos también sus hooks" convierte cualquier comando que toque la configuración del proyecto en ejecución remota de código; esa puerta de control es lo único que se interpone entre clonar un repositorio y que un hook ejecute un script traído directamente de internet. ### Selecciona el conjunto aprobado una sola vez, de forma inmutable Condensa el conjunto de comandos en un plan aprobado e inmutable en el punto de decisión, y deja que el ejecutor consuma únicamente ese valor, sin conservar nada a partir de lo cual pueda volver a derivarlo, de modo que volver a seleccionar un comando no aprobado se convierta en un error de compilación y no en una simple revisión. Un contexto que no puede preguntar al usuario consulta el estado de aprobación en modo de solo lectura y ejecuta únicamente el subconjunto ya aprobado. ## No fusiones con la comprobación de cobertura fallando sin permiso del usuario Nunca fusiones un cambio con una puerta de cobertura fallida sin la aprobación explícita del usuario, incluso cuando la plataforma marque esa puerta como "no obligatoria", porque en la práctica sigue siendo bloqueante. ### Conoce cómo se reporta la señal Las herramientas de cobertura pueden publicar sus resultados como ejecuciones de verificación en lugar de estados de commit, y se leen a través de APIs distintas; una consulta de estado combinado puede pasar por alto por completo una ejecución de verificación, y el resultado puede llegar varios minutos después de que termine el trabajo. Consulta la API correcta antes de concluir que un cambio está en verde. ### Corrige el vacío, no lo dejes pasar sin más Ante un fallo de cobertura, investiga y cierra el vacío: agrega pruebas o elimina código sin uso, incluidos los manejadores de casos especiales donde basta con dejar que el flujo caiga en una ruta general. Si crees que se trata de un falso positivo, consulta al usuario antes de fusionar en lugar de decidirlo unilateralmente. Ejecuta la cobertura con los mismos indicadores de funcionalidad que usa el pipeline de CI, para que no puedas descartar un fallo alegando que una funcionalidad estaba desactivada. ## Reutiliza bibliotecas existentes y corrige los avisos en vez de silenciarlos Revisa las dependencias existentes antes de crear una utilidad desde cero. Una biblioteca probada para la normalización de rutas, el escape de shell, la coloración o la detección de variables de plantilla es mejor que una reimplementación improvisada que maneja mal los casos límite. ### No disimules las advertencias Nunca silencies una advertencia suprimiéndola de forma general. Elimina el código muerto, o deja una marca fechada y explicada que la vincule a un trabajo futuro concreto. ### Documenta los sistemas complejos en el propio lugar Un sistema que no es obvio (una máquina de estados, un estado en caché, una coordinación entre módulos, una invalidación sutil) debe llevar una especificación a nivel de módulo que cubra su propósito, las decisiones clave, los contratos y los invariantes; mantenla actualizada. ### Mantén las pruebas fuera del código de biblioteca No agregues a los tipos de la biblioteca métodos de conveniencia que solo sirven para las pruebas; las pruebas deben llamar a la API real o definir sus propios auxiliares. Para los literales de varias líneas, prefiere saltos de línea reales incrustados en lugar de escapes de continuación de línea que eliminan espacios en blanco de forma silenciosa. ## Añade contexto a los errores y nunca los conviertas en cierres inesperados Lleva contexto con cada error. Usa errores explícitos de lógica de negocio para los estados de fallo esperados (un espacio de trabajo sucio, una rama faltante, un estado inválido) y envoltura de contexto para los fallos de E/S y de comandos externos. ### Nunca absorbas un fallo En una función que devuelve un tipo de error, nunca uses llamadas de tipo pánico-ante-ausencia o de estilo "unwrap" que conviertan un fallo recuperable en una caída; propaga el error con el operador correspondiente, lanza un error explícito o devuélvelo. Todo error envuelto debe indicarle al lector qué operación falló y por qué. ## Migra las claves de configuración antiguas en vez de descartarlas en silencio Mantén todo el manejo de compatibilidad de configuración en una sola capa: reescribe los patrones obsoletos en su forma canónica antes de que el analizador los vea. Nunca descartes silenciosamente una clave de configuración antigua, porque eso es un cambio de comportamiento silencioso; en su lugar, migra esa clave. ### Un único predicado para detectar y migrar Haz que una única función idempotente reescriba un patrón obsoleto y, a la vez, informe qué cambió, de modo que la detección y la migración compartan un único predicado y no puedan desincronizarse. Realiza la detección aplicando esas mismas funciones a una copia temporal, de modo que una advertencia se dispare exactamente cuando una actualización real modificaría el archivo; fija esa equivalencia con una prueba. ### Clasifica el momento de aplicación de cada regla Decide, para cada regla, cuándo se aplica una reescritura: en cada carga, solo ante una actualización explícita, de forma silenciosa sin advertencia, o como un valor predeterminado pendiente que una versión posterior adoptará. Deja intacto un valor que no se puede reescribir de forma segura (por estar malformado, o porque la clave de destino ya está ocupada), y deja que el propio error de tipo del analizador transmita el mensaje. ## Nombra los accesores para que el prefijo indique su costo y decide dónde guardar la caché Codifica la intención en los nombres de los accesores, de modo que quien los llame pueda predecir el costo y el modo de fallo solo a partir del prefijo. ### Deja que el prefijo transmita el contrato Adopta un esquema coherente: un sustantivo simple devuelve un valor sin efectos secundarios y puede almacenarse en caché; un nombre con el prefijo "set" escribe estado y puede fallar con un error; un nombre con el prefijo "require" produce un error cuando el valor está ausente; un nombre con el prefijo "fetch" realiza E/S de red; un nombre con el prefijo "load" realiza E/S de archivos. Evita un prefijo "get" redundante cuando un sustantivo simple se lee mejor. ### Decide dónde vive el estado en caché Comparte los valores en caché de solo lectura mediante una caché con conteo de referencias que se comparte al clonar. Elige el nivel según el costo y la frecuencia de uso: los valores baratos y de uso frecuente obtienen un "obtener o crear" en memoria; los valores costosos se persisten en disco; los valores que combinan ambas características mantienen un frente en memoria sobre un fondo persistente. Documenta qué se almacena en caché y qué no, y el invariante para actualizarlo después de una mutación.