Buscar/Crie uma ferramenta de linha de comando que não perca dados nem rode código sem aprovação

Crie uma ferramenta de linha de comando que não perca dados nem rode código sem aprovação

Use quando uma IA criar ou alterar uma CLI que já tem usuários. Mantém opções e configuração compatíveis, para antes de destruir trabalho, roda hooks do projeto só após aprovação, rastreia cada subprocesso e mostra os resultados locais antes dos dados de rede.

Para usar estas regras de IA
## Mantenha o arquivo de configuração e as opções de comando compatíveis à medida que a ferramenta cresce Quando uma ferramenta já tem usuários reais, é preciso equilibrar um design limpo com a compatibilidade. Um redesenho não é, por si só, motivo para quebrar a experiência das pessoas que a usam. ### Justifique toda quebra externa Alterar uma interface externa exige uma melhoria concreta e perceptível para o usuário, não apenas uma limpeza de código. Prefira emitir um aviso de descontinuação com um caminho de migração claro a quebrar algo silenciosamente. ### Nomeie a superfície protegida Defina quais interfaces são contratos (o formato do arquivo de configuração, as flags e argumentos de linha de comando) e quais podem mudar livremente (APIs internas, locais de log, formatação de saída legível para humanos). Proteja o primeiro grupo e mantenha o segundo flexível, para que as refatorações continuem baratas. ### Delimite a janela de suporte Declare uma versão mínima suportada de runtime ou toolchain e avance essa versão em um ritmo de manutenção previsível, em vez de fazer isso de forma improvisada. ## Use uma só palavra por conceito na documentação, na ajuda e nos comentários Escolha um único termo para cada conceito e use-o de forma idêntica na documentação, nos textos de ajuda e nos comentários de código. Um vocabulário que varia sem critério confunde silenciosamente tanto usuários quanto mantenedores. ### Registre o glossário por escrito Mantenha uma lista curta que fixe o significado de cada palavra e os quase-sinônimos a evitar. Diferencie explicitamente conceitos parecidos, como o checkout original versus um secundário, ou o destino de uma operação versus o workspace em que ela é executada. ### Nunca sobrecarregue uma palavra estrutural Se uma palavra já nomeia um conceito, não a reutilize para outro; escolha uma palavra diferente para o segundo conceito, mesmo quando a reutilização soar natural dentro de uma única frase. ## Atualize a ajuda a cada mudança de comportamento e confira com um teste Toda mudança de comportamento deve levar junto a mudança correspondente na documentação, na mesma edição. O teste a aplicar é: o texto de ajuda ainda descreve o que o código realmente faz? ### Mantenha uma única fonte primária por fato Para documentação gerada, edite a fonte da verdade (a ajuda embutida no código e as definições de comando) e regenere os espelhos renderizados; nunca edite manualmente um arquivo gerado. Quando um mesmo diretório contém tanto espelhos gerados quanto páginas escritas manualmente, confirme qual papel um arquivo desempenha antes de editá-lo. ### Garanta a sincronia com um teste Um teste de sincronia de documentação que compara a saída renderizada com a fonte captura o desalinhamento de forma mecânica. Editar o texto de ajuda também altera os snapshots renderizados, então regenere e revise esses snapshots na mesma mudança para manter o teste passando. ## Pare com um erro em vez de destruir arquivos do usuário sem consentimento Trate os dados do usuário como sagrados. Um comando que falha mas preserva o trabalho é melhor do que um que "tem sucesso" destruindo-o silenciosamente. ### Prefira falhar a perder dados silenciosamente Se uma operação puder destruir arquivos não rastreados, alterações não commitadas ou outros dados do usuário, interrompa com um erro em vez de prosseguir. ### Exija consentimento explícito para destruir Uma ação destrutiva, como forçar a remoção de dados ou sobrescrever algo, só deve ser executada quando o usuário pedir explicitamente por isso. Nunca exclua ou sobrescreva como efeito colateral de uma operação não relacionada; transforme a limpeza em uma etapa separada que o usuário escolhe fazer. ### Prefira a variante que falha e cuide do intervalo entre verificação e uso Quando existem duas formas de realizar uma operação, escolha a que falha em caso de conflito em vez da que força a execução. Seja conservador quando passa tempo entre uma verificação de segurança e a ação que ela protege: o estado pode mudar nesse intervalo, então não force a execução durante a limpeza. ## Passe todo subprocesso por um único wrapper que o registre e meça seu tempo Envie todo comando externo por meio de um único wrapper de execução, em vez de chamar diretamente a API de processos. Um único caminho garante, de graça, logging de depuração e medição de tempo consistentes, além de um único lugar para alterar o comportamento. ### Um wrapper, um emissor Mantenha a gramática de rastreamento em exatamente um componente e faça dele o único responsável por escrever as chamadas de log subjacentes, de modo que um subprocesso ou é rastreado por essa proteção, ou não produz registro algum. ### Novos pontos de criação de processo aderem explicitamente Alguns formatos de criação de processo não se encaixam no wrapper e precisam construir o registro de rastreamento manualmente: inicie-o logo antes de criar o processo, complete-o em caso de sucesso e marque-o como falho em caso de erro. Faça da proteção um valor de uso obrigatório que dispara uma assertion se for descartado sem ser resolvido, de modo que uma conclusão esquecida falhe um teste em vez de passar silenciosamente sem rastreamento. Processos filhos desacoplados em segundo plano e auxiliares interativos são as exceções documentadas que ficam sem rastreamento. ### Limpe variáveis de descoberta herdadas Algumas variáveis de ambiente são resolvidas antes que um processo filho consulte seu diretório de trabalho, então um valor herdado pode sobrescrever silenciosamente o diretório que você escolheu. Todo ponto de criação de processo que realoca um comando do usuário para um diretório escolhido pela ferramenta deve limpar essas variáveis; processos filhos que rodam no próprio contexto do usuário mantêm o contexto herdado, de forma absolutizada. ## Decida por códigos de saída e saída JSON, não pelo texto das mensagens Prefira códigos de saída, status legíveis por máquina e formatos estruturados a analisar mensagens legíveis por humanos, que quebram com mudanças de idioma, versão e redação. ### Escolha o canal estruturado Leia a saída no formato porcelain ou JSON e as flags numéricas de status, em vez da saída padrão voltada para humanos. Quando um comando codifica significado em seu código de saída, baseie a decisão nesse código, não no texto da mensagem. ### Documente a fragilidade inevitável Quando não existe alternativa estruturada e é preciso analisar texto, marque essa fragilidade diretamente no local da análise, para que o próximo leitor saiba que se trata de um risco conhecido, não de um contrato estável. ## Mostre primeiro os dados locais e traga os dados de rede depois Responsividade é um recurso: renderize primeiro a partir de dados locais e deixe que os detalhes mais lentos cheguem depois. ### Nunca deixe a rede bloquear o primeiro frame Acesse a rede apenas quando o pedido do usuário realmente precisar disso. Todo comando deve renderizar sua primeira saída a partir de dados locais e transmitir progressivamente os detalhes que dependem da rede; um comando que não consegue desenhar seu primeiro frame até que uma chamada remota retorne é o modo de falha, que fica ainda pior em uma instalação recém-feita. Um caminho síncrono crítico, como um prompt de shell, não deve acessar a rede de jeito nenhum, nem mesmo de forma progressiva. ### Prefira contextos delimitados pelo usuário para dados remotos ao vivo Transmita a saída do comando linha por linha, em vez de armazená-la em buffer. Um seletor interativo é o lugar mais tolerante para trabalho de rede: ele renderiza imediatamente e, se o usuário agir antes que uma chamada lenta retorne, o pedido é simplesmente abandonado. Um comando que precisa rodar até o fim não pode terminar até que todas as chamadas retornem, então busque dados remotos ao vivo dentro do seletor, de forma progressiva. ## Pare todos os loops quando o usuário interromper um comando em execução Quando um processo filho morre por causa de um sinal (interrupção ou finalização), todo loop no caminho de execução em primeiro plano deve abortar em vez de avançar para a próxima iteração. ### Leia o canal estruturado de sinais Detecte saídas causadas por sinal a partir do campo estruturado de sinal, nunca farejando um código de saída igual ou acima de 128 nem analisando texto de erro. Propague um resultado canônico de saída-por-interrupção e interrompa o loop. ### Verifique antes de qualquer ramo que engole erros Faça a verificação de interrupção antes que qualquer estratégia de tratamento de falhas seja executada, para que uma política de "avisar e continuar" não consiga engolir uma interrupção. Todo código novo que faz loop sobre processos filhos deve realizar essa verificação a cada iteração; caso contrário, uma única interrupção em uma operação de múltiplas etapas acaba avançando pelas etapas restantes. ## Rode hooks e aliases definidos pelo projeto só após aprovação Comandos definidos pelo projeto (hooks de ciclo de vida, aliases, corpos de execução extraídos da configuração do projeto) são código arbitrário distribuído em um repositório que o usuário pode ter acabado de clonar. Eles só devem ser executados depois que um sistema de aprovação os libera. ### O portão é toda a fronteira de segurança Nunca construa um caminho de código que execute comandos definidos pelo projeto sem passar por esse portão. Um atalho do tipo "já validamos a operação, então vamos rodar os hooks dela também" transforma todo comando que toca a configuração do projeto em execução remota de código; o portão é a única coisa que separa o ato de clonar um repositório da execução de um hook que roda um script vindo direto da internet. ### Selecione o conjunto aprovado uma única vez, de forma imutável No ponto de decisão, transforme o conjunto de comandos em um plano aprovado e imutável, e deixe que o executor consuma apenas esse valor, sem reter nada a partir do qual ele possa derivar outra seleção; assim, selecionar novamente um comando não aprovado vira um erro de compilação, não apenas uma verificação de revisão. Um contexto que não pode perguntar ao usuário consulta o estado de aprovação apenas em modo leitura e executa somente o subconjunto já aprovado. ## Não faça merge com a verificação de cobertura falhando sem o OK do usuário Nunca faça merge de uma mudança com o portão de cobertura falhando sem aprovação explícita do usuário, mesmo quando a plataforma marca esse portão como "não obrigatório", porque na prática ele continua bloqueando. ### Saiba como o sinal é reportado Ferramentas de cobertura podem publicar resultados como check runs em vez de commit statuses, que são lidos por APIs diferentes; uma consulta de status combinado pode simplesmente não captar um check run, e o resultado pode chegar minutos depois que o job termina. Consulte a API correta antes de concluir que uma mudança está aprovada. ### Corrija a lacuna, não a deixe passar Diante de uma falha de cobertura, investigue e feche a lacuna: adicione testes ou exclua código não utilizado, incluindo tratadores de casos especiais quando cair para um caminho genérico já for suficiente. Se você acredita que é um falso positivo, pergunte ao usuário antes de fazer o merge, em vez de decidir sozinho. Execute a cobertura com as mesmas feature flags usadas pelo pipeline de CI, para que você não possa descartar uma falha alegando que uma funcionalidade estava desativada. ## Reutilize bibliotecas existentes e corrija avisos em vez de silenciá-los Verifique as dependências existentes antes de criar um utilitário do zero. Uma biblioteca comprovada para normalização de caminhos, escaping de shell, coloração ou detecção de variáveis de template é melhor do que uma reimplementação improvisada que trata mal os casos extremos. ### Não maquie avisos Nunca silencie um aviso simplesmente suprimindo-o por completo. Exclua o código morto, ou deixe um marcador datado e explicado que vincule esse aviso a um trabalho futuro específico. ### Documente sistemas complexos no próprio local Um sistema não óbvio (uma máquina de estados, estado em cache, coordenação entre módulos, invalidação sutil) deve ter uma especificação no nível do módulo cobrindo propósito, decisões-chave, contratos e invariantes; mantenha essa especificação atualizada. ### Mantenha os testes fora do código de biblioteca Não adicione a tipos de biblioteca métodos de conveniência que só servem para testes; os testes devem chamar a API real ou definir seus próprios helpers. Para literais de múltiplas linhas, prefira quebras de linha reais embutidas a escapes de continuação de linha, que removem espaços em branco silenciosamente. ## Adicione contexto aos erros e nunca os transforme em travamentos Carregue contexto em todo erro. Use erros explícitos de lógica de negócio para estados de falha esperados (um workspace sujo, uma branch ausente, um estado inválido) e envolva com contexto as falhas de I/O e de comandos externos. ### Nunca engula uma falha Em uma função que retorna um tipo de erro, nunca use chamadas do tipo panic-on-absent ou unwrap, que transformam uma falha recuperável em um crash; propague usando o operador de erro, levante um erro explícito ou retorne um. Todo erro envolvido em contexto deve informar ao leitor qual operação falhou e por quê. ## Migre chaves antigas de configuração em vez de descartá-las em silêncio Mantenha todo o tratamento de compatibilidade de configuração em uma única camada: reescreva padrões descontinuados para a forma canônica antes que o parser os veja. Nunca descarte silenciosamente uma chave de configuração antiga, porque isso é uma mudança silenciosa de comportamento; migre-a em vez disso. ### Um único predicado para detectar e migrar Deixe que uma única função idempotente reescreva um padrão descontinuado e também relate o que foi alterado, de modo que detecção e migração compartilhem um único predicado e não possam divergir. Execute a detecção aplicando essas mesmas funções a uma cópia temporária, de forma que um aviso dispare exatamente quando uma atualização materializada mudaria o arquivo; fixe essa equivalência com um teste. ### Classifique o momento de cada regra Decida, para cada regra, quando uma reescrita se aplica: em todo carregamento, apenas em uma atualização explícita, silenciosamente sem aviso, ou como um padrão pendente que uma versão futura vai adotar. Deixe intocado um valor que não pode ser reescrito com segurança (malformado, ou com uma chave de destino já ocupada), e deixe que o próprio erro de tipo do parser carregue a mensagem. ## Nomeie acessores para que o prefixo mostre o custo e defina onde fica o cache Codifique a intenção nos nomes dos acessores, para que quem os chama consiga prever o custo e o modo de falha só pelo prefixo. ### Deixe o prefixo carregar o contrato Adote um esquema consistente: um substantivo simples retorna um valor sem efeitos colaterais e pode usar cache; um nome com prefixo set escreve estado e pode gerar erro; um nome com prefixo require gera erro quando o valor está ausente; um nome com prefixo fetch realiza I/O de rede; um nome com prefixo load realiza I/O de arquivo. Evite um prefixo get redundante quando um substantivo simples já ficaria mais claro. ### Decida onde o estado em cache vive Compartilhe valores em cache somente leitura por meio de um cache com contagem de referências que a clonagem compartilha. Escolha o nível pelo custo e pela frequência de uso: valores baratos e muito usados recebem um get-or-create em memória; valores caros persistem em disco; valores que são as duas coisas mantêm uma camada em memória na frente de um armazenamento persistente. Documente o que é e o que não é cacheado, e o invariante para atualizar esse cache depois de uma mutação.