Escrevendo Regras
Regras são funções TypeScript que verificam seu codebase quanto à conformidade com ADRs. Elas ficam em arquivos .rules.ts complementares ao lado dos arquivos markdown dos ADRs e são executadas quando você roda archgate check.
.archgate/adrs/ ARCH-001-command-structure.md # The decision ARCH-001-command-structure.rules.ts # The automated checksConfiguração básica
Seção intitulada “Configuração básica”Todo arquivo de regras exporta por padrão um objeto simples tipado com satisfies RuleSet. Cada chave no objeto rules se torna um ID de regra, e cada regra tem uma description e uma função async check que recebe um objeto de contexto.
/// <reference path="../rules.d.ts" />
export default { rules: { "my-rule-id": { description: "What this rule checks", async check(ctx) { // Your check logic here }, }, },} satisfies RuleSet;Um único arquivo de regras pode definir múltiplas regras:
/// <reference path="../rules.d.ts" />
export default { rules: { "first-rule": { description: "Checks one thing", async check(ctx) { // ... }, }, "second-rule": { description: "Checks another thing", async check(ctx) { // ... }, }, },} satisfies RuleSet;A API de Contexto
Seção intitulada “A API de Contexto”O objeto ctx passado para toda função check fornece capacidades de leitura de arquivos, busca e relatório. Aqui está uma referência detalhada com exemplos.
ctx.scopedFiles
Seção intitulada “ctx.scopedFiles”Um array de caminhos de arquivo que correspondem ao glob files do frontmatter do ADR. Se o ADR não tiver o campo files, isso inclui todos os arquivos do projeto.
for (const file of ctx.scopedFiles) { const content = await ctx.readFile(file); // Check content...}Use ctx.scopedFiles quando sua regra deve se aplicar apenas aos arquivos que o ADR governa. Por exemplo, uma regra de estrutura de comandos com escopo src/commands/**/*.ts receberá apenas arquivos de comandos.
ctx.changedFiles
Seção intitulada “ctx.changedFiles”Um array de caminhos de arquivo que diferem da branch base, incluindo alterações não commitadas da árvore de trabalho (arquivos staged, não staged e não rastreados que não sejam ignorados). Auto-detectado por padrão, ou preenchido via --staged / --base <ref>. Útil para verificação incremental e regras de dependência entre arquivos.
// Verificação incremental -- validar apenas arquivos alteradosconst filesToCheck = ctx.scopedFiles.filter((f) => ctx.changedFiles.includes(f));
// Dependência entre arquivos -- se o arquivo A mudou, o arquivo B também deve mudarif (ctx.changedFiles.includes("config/database.yml")) { if (!ctx.changedFiles.includes("deploy/manifest.yml")) { ctx.report.violation({ message: "config changed but manifest was not bumped", file: "config/database.yml", }); }}ctx.readFile(path)
Seção intitulada “ctx.readFile(path)”Lê o conteúdo de um arquivo como string. O caminho é relativo à raiz do projeto.
const content = await ctx.readFile("src/cli.ts");ctx.readJSON(path)
Seção intitulada “ctx.readJSON(path)”Lê e faz o parse de um arquivo JSON. Retorna unknown — faça cast para o formato esperado.
const pkg = (await ctx.readJSON("package.json")) as { dependencies?: Record<string, string>;};ctx.grep(file, pattern)
Seção intitulada “ctx.grep(file, pattern)”Busca em um único arquivo com uma expressão regular. Retorna um array de objetos GrepMatch, cada um com as propriedades file, line, column e content.
const matches = await ctx.grep(file, /console\.error\(/);for (const match of matches) { ctx.report.violation({ message: "Use logError() instead of console.error()", file: match.file, line: match.line, });}ctx.grepFiles(pattern, fileGlob)
Seção intitulada “ctx.grepFiles(pattern, fileGlob)”Busca em múltiplos arquivos que correspondem a um padrão glob. Retorna um array plano de objetos GrepMatch de todos os arquivos correspondentes. Arquivos ignorados pelo .gitignore são excluídos por padrão. Defina respectGitignore: false no frontmatter do ADR para incluí-los.
const matches = await ctx.grepFiles(/TODO:/, "src/**/*.ts");for (const match of matches) { ctx.report.warning({ message: "TODO comment found", file: match.file, line: match.line, });}ctx.glob(pattern)
Seção intitulada “ctx.glob(pattern)”Encontra arquivos por padrão glob. Retorna um array de caminhos de arquivo relativos à raiz do projeto. Arquivos ignorados pelo .gitignore são excluídos por padrão. Defina respectGitignore: false no frontmatter do ADR para incluí-los.
const testFiles = await ctx.glob("tests/**/*.test.ts");ctx.report
Seção intitulada “ctx.report”A interface de relatório com três métodos de severidade:
ctx.report.violation(detail)— severidade error (código de saída 1, bloqueia CI)ctx.report.warning(detail)— severidade warning (registrado, mas não bloqueia)ctx.report.info(detail)— informacional (registrado para visibilidade)
Cada método aceita um objeto com:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
message | string | Sim | Qual é a violação |
file | string | Não | Caminho do arquivo com a violação |
line | number | Não | Número da linha da violação |
fix | string | Não | Correção sugerida (exibida ao desenvolvedor) |
ctx.report.violation({ message: "Command file must export a register*Command function", file: "src/commands/check.ts", line: 5, fix: "Add: export function registerCheckCommand(program: Command) { ... }",});ctx.projectRoot
Seção intitulada “ctx.projectRoot”O caminho absoluto para o diretório raiz do projeto. Útil quando você precisa construir caminhos absolutos.
Níveis de severidade
Seção intitulada “Níveis de severidade”Cada regra pode definir uma severidade padrão em sua configuração. A severidade determina como as violações são tratadas:
| Severidade | Código de saída | Comportamento |
|---|---|---|
error | 1 | Bloqueia CI, deve ser corrigido |
warning | 0 | Registrado, mas não bloqueia |
info | 0 | Informacional, registrado para visibilidade |
Defina a severidade na definição da regra:
export default { rules: { "my-rule": { description: "...", severity: "warning", async check(ctx) { // Violations from this rule are warnings, not errors ctx.report.violation({ message: "..." }); }, }, },} satisfies RuleSet;Se severity for omitido, o padrão é error.
Você também pode reportar com diferentes severidades dentro da mesma regra usando ctx.report.violation(), ctx.report.warning() e ctx.report.info() diretamente.
Timeout de regras
Seção intitulada “Timeout de regras”Cada regra tem um timeout de execução de 30 segundos. Se uma regra exceder esse limite, ela é tratada como erro. Isso impede que verificações descontroladas bloqueiem o pipeline.
Mantenha as regras rápidas:
- Usando
ctx.grepFiles()ao invés de ler cada arquivo manualmente - Usando
Promise.all()para verificar arquivos em paralelo - Delimitando regras com o campo
filesdo frontmatter para limitar o número de arquivos processados
O campo fix
Seção intitulada “O campo fix”O campo fix é uma string opcional exibida ao desenvolvedor junto com a mensagem de violação. Ele descreve qual ação tomar para resolver o problema. Correções não são aplicadas automaticamente — são orientações.
ctx.report.violation({ message: `Unapproved dependency: "chalk"`, file: "package.json", fix: "Use styleText() from node:util instead of chalk",});Quando exibido, o fix aparece abaixo da mensagem de violação:
ARCH-006/no-unapproved-deps package.json Unapproved dependency: "chalk" Fix: Use styleText() from node:util instead of chalkDicas para escrever regras
Seção intitulada “Dicas para escrever regras”-
Use
Promise.all()para verificações de arquivo em paralelo. Ao verificar múltiplos arquivos independentes, processe-os em paralelo ao invés de sequencialmente.// Good: parallelconst checks = files.map(async (file) => {const content = await ctx.readFile(file);// ...});await Promise.all(checks);// Avoid: sequentialfor (const file of files) {const content = await ctx.readFile(file);// ...} -
Use
ctx.changedFilespara verificação incremental.ctx.changedFilesé auto-preenchido com o diff da branch mais as alterações não commitadas da árvore de trabalho (ou arquivos staged com--staged). Filtrectx.scopedFilescom ele para verificar apenas o que mudou, ou use-o diretamente para regras de dependência entre arquivos. -
Mantenha as regras focadas em uma única preocupação. Uma regra que verifica tanto convenções de nomenclatura quanto padrões de import deve ser dividida em duas regras com IDs separados.
-
Use
ctx.grepFiles()ao invés de iteração manual. Ao buscar um padrão em muitos arquivos,ctx.grepFiles()é mais eficiente do que ler cada arquivo e executar uma regex. -
Forneça mensagens de
fixacionáveis. Um fix como “Não faça isso” não é útil. Diga ao desenvolvedor exatamente o que fazer. -
Filtre arquivos não-aplicáveis cedo. Se sua regra se aplica apenas a certos arquivos dentro do escopo, filtre
ctx.scopedFilesantes de processar:const commandFiles = ctx.scopedFiles.filter((f) => !f.endsWith("index.ts")); -
Trate arquivos ausentes com elegância. Se sua regra lê um arquivo específico como
package.json, envolva a leitura em um try/catch e retorne antecipadamente se o arquivo não existir.
Diretivas de opt-out
Seção intitulada “Diretivas de opt-out”Existem duas formas de lidar com exceções no Archgate: supressão no nível do engine (funciona com qualquer regra automaticamente) e diretivas customizadas no nível da regra (implementadas pelo autor da regra para opt-outs específicos do domínio).
Supressão no nível do engine
Seção intitulada “Supressão no nível do engine”O Archgate suporta comentários inline archgate-ignore que suprimem violações sem modificar a regra em si. O engine analisa esses comentários e filtra as violações correspondentes antes de reportar.
Supressão da próxima linha: suprime a violação na linha imediatamente seguinte:
// archgate-ignore ARCH-006/no-unapproved-deps dep legada, migração planejada para Q3import chalk from "chalk";Supressão no nível do arquivo: suprime todas as violações correspondentes em qualquer lugar do arquivo:
// archgate-ignore-file ARCH-005/test-mirrors-src arquivo gerado, sem teste manualMúltiplas regras: empilhe comentários para suprimir mais de uma regra na mesma linha:
// archgate-ignore ARCH-006/no-unapproved-deps dep legada// archgate-ignore ARCH-003/use-style-text lib de terceiros lida com coresimport chalk from "chalk";Comentários de supressão consecutivos todos visam a primeira linha que não é uma supressão após o bloco.
O formato é ADR-ID/rule-id seguido de um motivo. O motivo é obrigatório: uma supressão sem motivo é ignorada e produz um aviso:
[suppression] Suppression for ARCH-006/no-unapproved-deps is missing a reason src/foo.ts:1Ambos os estilos de comentário // e # são suportados, então as supressões funcionam em TypeScript, JavaScript, YAML, Python, shell scripts e outros tipos de arquivo que suas regras podem analisar.
Diretivas customizadas no nível da regra
Seção intitulada “Diretivas customizadas no nível da regra”Para opt-outs específicos do domínio, autores de regras podem implementar suas próprias diretivas baseadas em comentários dentro da função check. Esse padrão dá à regra controle total sobre a sintaxe, posicionamento e validação da diretiva.
// No seu arquivo .rules.ts:async check(ctx) { const files = await ctx.glob("src/components/**/*Connected.tsx");
for (const file of files) { const content = await ctx.readFile(file);
// Suportar diretiva de opt-out no topo do arquivo if (/^\/\/\s*@no-presentational:/u.test(content.trimStart())) continue;
// ... lógica da regra que pode reportar uma violação ... ctx.report.violation({ message: "Componente presentational ausente", file, fix: 'Adicione "// @no-presentational: <motivo>" no topo do arquivo para fazer opt-out', }); }}O desenvolvedor faz opt-out adicionando a diretiva ao seu arquivo:
// @no-presentational: este componente apenas redireciona, sem UI para renderizarimport { useNavigate } from "react-router";Quando usar qual
Seção intitulada “Quando usar qual”| Abordagem | Melhor para | Quem controla |
|---|---|---|
archgate-ignore | Exceções ad-hoc para qualquer regra | Desenvolvedor usando a regra |
| Diretiva customizada | Opt-outs específicos do domínio com motivos estruturados | Autor da regra |
Use archgate-ignore quando um desenvolvedor precisa suprimir uma violação pontual. Use diretivas customizadas quando o opt-out é um conceito de primeira classe no domínio da sua regra. Por exemplo, marcar um componente como intencionalmente sem par, ou um arquivo como auto-gerado.
Próximos passos
Seção intitulada “Próximos passos”- Padrões Comuns de Regras: padrões prontos para copiar e colar, organizados por categoria: gerenciamento de dependências, restrições de import, estrutura de arquivos, qualidade de código, esquema de banco de dados e limites de arquitetura.
- Referência da API de Regras: referência completa de todos os tipos e funções da API de regras.
- Integração com CI: integre
archgate checkao seu pipeline para aplicar regras em cada PR.