From 5e5ebbdbb718564d73a98c12f9df88d60118c9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ramos?= Date: Sun, 11 Oct 2026 15:37:59 -0300 Subject: [PATCH 1/4] Document project in English --- AGENTS.md | 164 +++++---- CHANGELOG.md | 14 +- README.md | 259 +++++++++++-- docs/README.md | 106 +++--- docs/implementation-plan.md | 366 +++++++------------ docs/quality.md | 56 +-- docs/release-checklist.md | 25 +- specs/README.md | 44 +-- specs/architecture.md | 76 ++-- specs/configuration.md | 90 +++-- specs/decisions/0001-rust-and-tree-sitter.md | 26 +- specs/github-action.md | 82 +++-- specs/requirements.md | 196 +++++----- src/report.rs | 6 +- 14 files changed, 778 insertions(+), 732 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fcb6f6a..6084bef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,81 +1,83 @@ -# AGENTS.md - -## Objetivo do repositório - -Este repositório contém o `risk-gate`, um software escrito em Rust e executado -como etapa de GitHub Actions. A finalidade do software é analisar o risco de um -Pull Request, aplicar gates de validação compatíveis com o risco encontrado e -publicar um relatório auditável no PR. - -O produto não é um linter genérico nem um serviço externo. A configuração é -versionada junto com o projeto analisado no arquivo `.risk-gate.yaml`. - -## Regras obrigatórias para agentes - -1. Implemente o produto em Rust. Não introduza uma implementação paralela em - Python, Node.js ou Elixir. Ferramentas auxiliares podem usar outra linguagem - somente quando isso for explicitamente necessário e permanecerem fora do - runtime principal. -2. Preserve uma arquitetura de biblioteca + CLI. A biblioteca deve concentrar - parsing, descoberta de linguagens, AST, mapeamento, cálculo de risco, - regras e relatório; a CLI deve adaptar isso ao GitHub Actions. -3. Toda decisão de risco deve ser determinística, explicável e testável sem - acesso à rede. O acesso ao GitHub deve ser uma borda substituível. -4. O arquivo `.risk-gate.yaml` é parte do contrato público. Mudanças - incompatíveis exigem atualização da especificação, dos exemplos e dos - testes de contrato. -5. Nunca trate uma validação não executada, interrompida, com comando ausente - ou com saída inconclusiva como sucesso. -6. Não publique, aprove, faça merge ou altere configurações do GitHub fora do - escopo explícito da execução. O comentário do relatório deve ser idempotente - e atualizado por uma marca estável. -7. Não esconda falhas por meio de `instantMerge`. Essa opção somente impede que - o gate bloqueie o merge; ela não converte validações falhas em sucesso nem - altera o relatório. -8. Comandos de validação são configuração não confiável do repositório. Execute - com diretório de trabalho, ambiente e timeout explícitos, registre o resultado - e evite interpolação insegura de valores vindos de eventos do GitHub. -9. Separe sempre os estados `SUCCESS`, `PARTIAL` e `FAILED`. Um comentário, - uma árvore AST parcial ou uma permissão ausente não prova sucesso completo. -10. Antes de declarar uma integração concluída, prove o caminho real: evento do - PR, leitura da configuração, diff, análise, execução dos gates, comentário - e código de saída da Action. - -## Escopo inicial de linguagens - -O modelo de AST deve ser independente de linguagem. A primeira entrega deve -suportar Python, JavaScript, TypeScript e Elixir, usando parsers concretos -atrás de uma interface comum. A árvore deve cobrir todo o projeto incluído no -escopo da execução, não apenas arquivos alterados. Arquivos sem parser devem -ser reportados como não analisados; nunca devem desaparecer silenciosamente. - -## Modelo de risco - -Os níveis ordenados, do menor para o maior, são: - -`minimal < low < normal < high < danger` - -O risco do PR é o maior risco aplicável aos módulos alcançados por arquivos -alterados no diff. Alterações sem correspondência no `riskMap` usam o risco -`normal` por padrão, para que uma área nova não passe despercebida. O fallback -deve ser configurável somente quando isso estiver especificado e validado. - -## Fluxo esperado - -`configuração -> árvore AST completa -> módulos/path map -> diff do PR -> risco -máximo -> gateRules -> validações -> comentário no PR -> exit code` - -Cada etapa deve expor dados suficientes para diagnóstico e testes. Consulte os -documentos em `specs/` antes de alterar contratos e `docs/` para exemplos de -operação. - -## Qualidade mínima - -Antes de concluir uma mudança, execute os checks Rust disponíveis (`cargo fmt ---check`, `cargo clippy --all-targets --all-features -- -D warnings` e `cargo -test`, quando o projeto já os tiver). Adicione testes unitários para regras de -risco e testes de integração para configuração, diff, gates e relatório. - -Toda alteração deve atualizar a documentação afetada. Não invente uma -capacidade do GitHub Actions que não esteja coberta por teste ou marcada como -limitação. +# Agent Instructions + +## Repository purpose + +This repository contains risk-gate, a Rust application executed as a GitHub +Actions step. It analyzes Pull Request risk, applies validation gates that +match the detected risk, and publishes an auditable PR report. + +The product is not a generic linter or an external service. Configuration is +versioned with the analyzed project in .risk-gate.yaml. + +## Mandatory rules for agents + +1. Implement the product in Rust. Do not introduce a parallel implementation + in Python, Node.js, or Elixir. Helper tools may use another language only + when explicitly necessary and kept outside the product runtime. +2. Preserve the library plus CLI architecture. The library owns parsing, + language discovery, AST construction, mapping, risk calculation, gates, and + reporting; the CLI adapts those capabilities to GitHub Actions. +3. Every risk decision must be deterministic, explainable, and testable without + network access. GitHub access must remain replaceable at the boundary. +4. .risk-gate.yaml is part of the public contract. Incompatible changes require + updates to the specification, examples, and tests. +5. Never treat a validation that was not executed, was interrupted, has a + missing command, or produced inconclusive output as success. +6. Do not publish, approve, merge, or change GitHub settings outside the + explicit scope of the task. Reports must be idempotent and updated through + a stable marker. +7. Do not hide failures through instantMerge. This option only prevents the + gate from blocking the merge; it does not turn failed validations into + success or alter the report. +8. Validation commands are untrusted repository configuration. Run them with + explicit working directory, environment, and timeout; record the result and + avoid unsafe interpolation of GitHub event values. +9. Always distinguish SUCCESS, PARTIAL, and FAILED. A comment, partial AST, or + missing permission does not prove complete success. +10. Before declaring an integration complete, prove the real path: PR event, + configuration loading, diff, analysis, gate execution, comment, and Action + exit code. +11. All documentation, comments, CLI messages, reports, examples, and other + user-facing project text must be written in English. Preserve exact + identifiers, YAML keys, API fields, exit codes, and code literals. + +## AST and language model + +The AST model must be language-independent. The first supported languages are +Python, JavaScript, TypeScript, and Elixir behind a common interface. The tree +must cover the complete project included in the execution scope, not only +changed files. Files without a parser must be reported as unanalyzed and must +never disappear silently. + +## Risk model + +The ordered levels, from lowest to highest, are: + +minimal < low < normal < high < danger + +PR risk is the highest risk applicable to modules reached by changed files. +Changes that do not match riskMap use normal by default so that new areas are +not silently overlooked. A fallback may be configurable only when it is +specified and validated. + +The execution flow is: + +configuration -> complete AST -> module/path map -> PR diff -> maximum risk -> gateRules -> validations -> PR comment -> exit code + +Every stage must expose enough data for diagnosis and testing. Read specs/ +before changing contracts and docs/ for operational examples. + +## Minimum quality + +Before completing a change, run the available Rust checks: + +~~~bash +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets --all-features +~~~ + +Add unit tests for risk rules and integration tests for configuration, diff, +gates, and reporting. Every change must update affected documentation. Do not +invent GitHub Actions capabilities that are not covered by tests or explicitly +marked as limitations. diff --git a/CHANGELOG.md b/CHANGELOG.md index a89b57c..ef6fba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## Unreleased +## 0.1.0 -- Implementa análise AST para Python, JavaScript, TypeScript e Elixir. -- Adiciona classificação por `riskMap`, gates por risco, validações e aprovações - de equipes/usuários do GitHub. -- Publica relatório idempotente no PR e aplica limites de arquivos, nós AST e - tamanho de comentário. -- Automatiza cobertura mínima de 95%, mutação e checksum do binário de release. +- Implements AST analysis for Python, JavaScript, TypeScript, and Elixir. +- Adds riskMap classification, risk-based gates, validations, and GitHub team + or user approvals. +- Publishes an idempotent PR report and applies file, AST-node, and comment + size limits. +- Automates coverage above 95%, mutation testing, and release binary checksums. diff --git a/README.md b/README.md index 3755350..df1861a 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,237 @@ # Risk Gate -Rust CLI and GitHub Action for classifying Pull Request risk from a versioned -`.risk-gate.yaml`, a complete project AST and the files changed by the PR. +Risk Gate is a Rust CLI and GitHub Action for estimating Pull Request merge +risk. It combines a repository-owned risk map, a complete project AST, the PR +diff, a dependency graph, configurable validations, approval groups, and an +idempotent report comment. -The first parser matrix supports Python, JavaScript, TypeScript and Elixir. -The result is one of `minimal`, `low`, `normal`, `high` or `danger`, followed -by configured approvals and validations and an idempotent PR report. +It does not replace technical review. It makes likely impact explicit and +applies a policy proportional to the affected area's criticality. -The report also traces import/require relationships to show cascade impact. A -rule may set `newCodePolicy: ignore` when isolated new files should not inherit -the path risk; connected code always remains in the risk calculation. +## How the analysis works -## Local analysis +For a PR execution, Risk Gate: -Para criar uma configuração inicial permissiva, com comentários explicando -as opções disponíveis: +1. Loads and validates .risk-gate.yaml. +2. Queries GitHub for changed, renamed, and removed PR files. +3. Walks the repository and builds the complete supported AST. +4. Extracts modules and references from Python, JavaScript, TypeScript, and + Elixir. +5. Maps changed files and modules to riskMap rules. +6. Traverses the dependency graph for direct and transitive impact. +7. Selects the highest applicable risk. +8. Runs validations and checks configured approvals for that risk. +9. Creates or updates one PR comment with evidence and a Mermaid cascade graph. -```bash +The graph can raise the risk of a change outside a critical path when the +change depends on, or is depended on by, critical code. A new file can use +newCodePolicy: ignore when it is isolated; any direct or transitive connection +keeps it in the risk calculation. + +## AST and language support + +Risk Gate uses Tree-sitter grammars selected by file extension: + +- .py: Python; +- .js, .jsx, .mjs, .cjs: JavaScript; +- .ts, .tsx: TypeScript; +- .ex, .exs: Elixir. + +Each file becomes a common ParsedFile/AstNode model containing language, path, +node kind, byte range, optional name, child nodes, references, and parse +errors. The current implementation rebuilds the AST for the current checkout; +it does not yet calculate a structural AST diff. Changed paths come from Git +or the GitHub API and are mapped onto the current AST. + +Python imports and relative imports are resolved to likely .py and +__init__.py paths. JavaScript and TypeScript imports, exports, and require +calls are resolved to likely .js, .ts, and index paths. Elixir alias, import, +require, and use forms are normalized to paths such as +lib/my_app/accounts/user.ex. + +The analysis is structural rather than complete semantic analysis. Dynamic +imports, reflection, runtime-generated paths, complex TypeScript aliases, +bundler-only resolution, configuration-driven calls, advanced Elixir +metaprogramming, and dependencies outside the repository may not be detected. + +## GitHub Action + +The consumer repository needs .risk-gate.yaml and a workflow that can read the +PR and write its report comment: + +~~~yaml +name: Risk Gate + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: read + issues: write + +jobs: + risk-gate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: CodeArq-tech/software-risk-gate@v0.1.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} +~~~ + +The Action downloads the Linux x86_64 release binary and verifies its +SHA-256 checksum. The version input selects another published release: + +~~~yaml +- uses: CodeArq-tech/software-risk-gate@v0.1.0 + with: + config: .risk-gate.yaml + token: ${{ secrets.GITHUB_TOKEN }} + version: v0.1.0 +~~~ + +Action outputs are risk, status, merge_blocked, and report_url. + +## .risk-gate.yaml + +The configuration has an explicit version and rejects unknown keys: + +~~~yaml +version: 1 + +riskMap: + - path: src/payment/ + risk: danger + description: "Payment processing and financial authorization" + newCodePolicy: include + - path: tests/ + risk: minimal + description: "Automated tests" + - path: infra/database/ + risk: danger + description: "Database migrations and access" + - path: src/instructions/ + risk: normal + description: "Runtime instructions" + newCodePolicy: ignore + +gateRules: + minimal: + instantMerge: true + normal: + instantMerge: false + high: + requireApprovalGroup: architects + danger: + validations: [e2e, mutationTesting] + +validations: + e2e: + command: "./scripts/test-e2e.sh" + timeoutSeconds: 1800 + mutationTesting: + command: "cargo mutants --baseline run" + timeoutSeconds: 3600 + +approvalGroups: + architects: + team: platform/architects + users: [alice, bob] + +limits: + maxFiles: 10000 + maxAstNodes: 1000000 + maxCommentBytes: 60000 +~~~ + +Risk levels are ordered: + +minimal < low < normal < high < danger + +Paths are repository-relative. path: ./ matches the entire repository. More +specific rules may coexist with broad rules; the highest applicable risk wins. +Descriptions are included in the PR report. + +newCodePolicy include keeps a rule's risk for isolated new code. ignore reduces +isolated new code to minimal only when there is no graph path to any mapped +area. Connected code keeps its applicable risk and its chain appears in the +report. + +gateRules may define instantMerge, requireApprovalGroup, and validations. +instantMerge: true reports failures without blocking mergeability; it does not +turn technical failure into SUCCESS. With instantMerge: false, every selected +validation must return zero and required approval must be present. + +validations maps names to shell commands and optional timeouts. +approvalGroups maps names to GitHub teams or explicit users. limits bounds +files, AST nodes, and published comment size. + +## Initialize configuration + +Create a valid permissive configuration with: + +~~~bash cargo run -- init -``` +~~~ + +The generated file uses path: ./, minimal, newCodePolicy: include, and +instantMerge: true for every level. Comments explain the available values. + +Options: + +~~~bash +risk-gate init --root /path/to/repository +risk-gate init --output config/risk-gate.yaml +risk-gate init --force +~~~ + +## CLI modes + +Offline local Git range: + +~~~bash +cargo run -- --config .risk-gate.yaml --root . + --base "$BASE_SHA" --head "$HEAD_SHA" +~~~ + +Remote read-only policy preview: -O comando cria `.risk-gate.yaml`, usa `path: ./` com risco `minimal` e deixa -`instantMerge: true` em todos os níveis. Use `--force` para substituir um -arquivo existente; depois ajuste o mapa, gates, validações e aprovações ao -risco real do repositório. +~~~bash +cargo run -- --dry-run --config .risk-gate.yaml --root . + --repository owner/repository --pull-number 123 --token "$GITHUB_TOKEN" +~~~ -```bash -cargo run -- --config .risk-gate.yaml --root . --base "$BASE_SHA" --head "$HEAD_SHA" -``` +dry-run returns JSON with risk, evidence, changed files, and the policy the +Action would execute. It does not run validations, check approvals, or publish +comments. -Para consultar um PR existente sem efeitos colaterais, use: +AST and dependency analysis without configuration: - cargo run -- --dry-run --config .risk-gate.yaml --root . \ - --repository CodeArq-tech/software-risk-gate \ - --pull-number 123 --token "$GITHUB_TOKEN" +~~~bash +cargo run -- --ast-impact --root /path/to/repository + --repository owner/repository --pull-number 123 --token "$GITHUB_TOKEN" +~~~ -O dry-run consulta os arquivos do PR e retorna JSON com o risco, a análise de -impacto e a política de gateRules selecionada pela Action. Ele não executa -validações, não consulta aprovações e não publica comentários. +ast-impact returns changed files, languages, modules, references, and ordered +dependency chains. It does not calculate risk, execute gates, or publish +comments. -Quando o repositório ainda não possui configuração, use o modo ast-impact -para analisar somente AST e dependências: +## Development and quality - cargo run -- --ast-impact --root /path/to/condominio \ - --repository Lar21/condominio --pull-number 31554 \ - --token "$GITHUB_TOKEN" +~~~bash +cargo fmt --all -- --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets --all-features +cargo llvm-cov --workspace --all-features --fail-under-lines 95.01 +cargo mutants --baseline run --copy-target true --minimum-test-timeout 60 --no-shuffle -j 4 +bash scripts/check-mutation-score.sh +~~~ -Esse modo não exige .risk-gate.yaml e retorna os arquivos alterados, módulos -identificados, referências e as cadeias de módulos afetados. +Pull Request CI requires formatting, Clippy, all tests, line coverage strictly +above 95%, mutation score strictly above 95%, and a branch rebased on the +current master. -For the public configuration contract, see -[`specs/configuration.md`](specs/configuration.md). For implementation order, -see [`docs/implementation-plan.md`](docs/implementation-plan.md). Quality gates -and the 95% coverage/mutation requirements are in -[`docs/quality.md`](docs/quality.md). +See specs/configuration.md for the normative configuration contract, +docs/implementation-plan.md for implementation work, and docs/quality.md for +quality policy. diff --git a/docs/README.md b/docs/README.md index e2acbed..98bceae 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,98 +1,76 @@ -# Risk Gate +# Risk Gate Documentation -O Risk Gate é uma etapa Rust para GitHub Actions que classifica o risco de um -Pull Request a partir do código alterado, da AST completa do projeto e de um -mapa de risco versionado no próprio repositório. +Risk Gate is a Rust step for GitHub Actions that classifies Pull Request risk +from changed code, the complete project AST, and a versioned repository risk +map. -## Início rápido +## Quick start -1. Crie `.risk-gate.yaml` na raiz seguindo [`specs/configuration.md`](../specs/configuration.md). -2. Configure a Action com permissão para ler o PR e atualizar comentários. -3. Defina comandos de validação reproduzíveis no repositório. -4. Execute localmente em modo offline para conferir o risco e o relatório. -5. Use a execução no PR para validar o caminho GitHub completo. +1. Run risk-gate init to create .risk-gate.yaml. +2. Review the generated permissive configuration. +3. Configure the Action with permission to read the PR and update comments. +4. Define reproducible validation commands in the repository. +5. Run the local offline mode before opening a PR. +6. Validate the complete GitHub flow on a test PR. -Para começar com um arquivo válido e permissivo, execute: +Minimal example: -```bash -cargo run -- init -``` - -O comando aceita `--root`, `--output` e `--force`. O arquivo gerado documenta -os valores válidos na própria linha de configuração; seu `riskMap` inicial -usa `path: ./`, `minimal` e `newCodePolicy: include`, enquanto todos os -`instantMerge` começam como `true`. - -Exemplo mínimo: - -```yaml +~~~yaml version: 1 riskMap: - path: src/payment/ risk: danger - description: "Pagamento" + description: "Payment processing" - path: tests/ risk: minimal - description: "Testes" + description: "Automated tests" gateRules: danger: validations: [e2e] validations: e2e: command: "./scripts/e2e.sh" -``` - -O risco final é o maior nível encontrado entre os arquivos do diff. O -comentário do PR explica quais paths e descrições causaram o resultado. Leia as -especificações para a semântica normativa e os limites da primeira versão. +~~~ -Quando `newCodePolicy: ignore` for usado, o comentário também informa se o -arquivo novo ficou isolado ou se encontrou uma cadeia de imports/requires até -uma área de risco. Código conectado não é ignorado. +The final risk is the highest level found among files in the diff. The PR +comment explains which paths and descriptions caused the result. When +newCodePolicy is ignore, the report states whether a new file was isolated or +connected to a risk area. -## Permissões do workflow +## Workflow permissions -Use o token padrão com estas permissões mínimas no job que executa a Action: +Use the default token with these minimum job permissions: -```yaml +~~~yaml permissions: contents: read pull-requests: read issues: write -``` +~~~ -`pull-requests: read` permite consultar arquivos e reviews. A API de comentários -usa `issues: write`; sem ela a análise retorna falha de publicação, nunca uma -aprovação implícita. Para equipes privadas, o token também precisa ter acesso -à organização correspondente. +pull-requests: read allows file and review queries. Comment creation and +updates use issues: write. Without it, publication fails; it must never be +treated as implicit approval. -O modo local não chama GitHub e é adequado para validar a configuração e o -relatório antes de abrir um PR: +## Local modes -```bash -cargo run -- --config .risk-gate.yaml --root . --base "$BASE_SHA" --head "$HEAD_SHA" -``` - -Para analisar um PR remoto sem efeitos colaterais, use: +Offline local analysis: - cargo run -- --dry-run --config .risk-gate.yaml --root . \ - --repository CodeArq-tech/software-risk-gate \ - --pull-number 123 --token "$GITHUB_TOKEN" - -Esse comando retorna JSON com risk, assessment e policy. O modo é somente -leitura: não executa validações, não consulta reviews e não publica comentário. +~~~bash +cargo run -- --config .risk-gate.yaml --root . --base "$BASE_SHA" --head "$HEAD_SHA" +~~~ -Quando não houver .risk-gate.yaml, use o modo ast-impact: +Remote read-only policy preview: - cargo run -- --ast-impact --root /path/to/condominio \ - --repository Lar21/condominio --pull-number 31554 \ - --token "$GITHUB_TOKEN" +~~~bash +cargo run -- --dry-run --config .risk-gate.yaml --root . + --repository owner/repository --pull-number 123 --token "$GITHUB_TOKEN" +~~~ -Ele retorna JSON com os arquivos alterados, módulos encontrados pela AST, -referências e as cadeias de dependência afetadas, sem risco ou gates. +AST and dependency analysis without configuration: -Para implementar o produto, siga o [plano de implementação](implementation-plan.md), -que organiza as tarefas por dependência e critério de aceite. +~~~bash +cargo run -- --ast-impact --root /path/to/repository + --repository owner/repository --pull-number 123 --token "$GITHUB_TOKEN" +~~~ -Os comandos e critérios de cobertura/mutation testing estão em -[quality.md](quality.md). +The implementation plan is in implementation-plan.md. Quality commands and +coverage/mutation requirements are in quality.md. diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index e74031f..61a33ea 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -1,227 +1,139 @@ -# Plano de implementação do Risk Gate - -Este documento transforma as especificações em tarefas executáveis. A ordem -abaixo reduz retrabalho: primeiro são estabilizados os contratos e o domínio; -depois entram os parsers, GitHub e distribuição. - -## Estado da implementação - -As fases de domínio, configuração, AST, diff, gates, relatório, CLI e Action -estão implementadas no código atual. A execução local validada cobre 39 testes, -95,82% de linhas no workspace e 97,01% de mutação. As caixas abaixo continuam -como backlog rastreável do plano original; a prova final de publicação em um -repositório GitHub real depende de credenciais, repository e PR externos. - -## Critério de conclusão do produto - -O produto estará pronto para a primeira versão quando conseguir, em uma -execução real de GitHub Actions: - -1. ler e validar `.risk-gate.yaml`; -2. montar a AST completa de um projeto com Python, JavaScript, TypeScript e - Elixir; -3. mapear paths e módulos alterados no diff do PR; -4. calcular e explicar o maior risco; -5. executar gates, validações e aprovação configurados; -6. publicar ou atualizar um único comentário no PR; -7. emitir outputs e exit code coerentes com `instantMerge`; -8. preservar evidências de erros, análise parcial e limitações. - -## Fase 0 — Preparação e decisões técnicas - -- [ ] **T-001 — Criar o esqueleto Rust.** Inicializar `Cargo.toml`, crate de - biblioteca, binário CLI, `src/` e estrutura de testes. - - Aceite: `cargo test` e `cargo fmt --check` executam em um checkout limpo. -- [ ] **T-002 — Definir toolchain e qualidade.** Fixar toolchain Rust, adicionar - `rustfmt`, `clippy`, dependabot/atualização equivalente e comandos locais - documentados. - - Aceite: CI reproduz os mesmos comandos localmente e na Action. -- [ ] **T-003 — Registrar decisões de dependências.** Escolher e documentar - parser(es) AST, cliente HTTP GitHub, YAML, logging e executor de processos. - Avaliar suporte de licença, tamanho do binário, manutenção e cross-compile. - - Aceite: ADR curto em `specs/decisions/` com alternativas rejeitadas. -- [ ] **T-004 — Definir modelo de domínio.** Criar tipos para `RiskLevel`, - `AnalysisStatus`, paths, módulos, diff, avaliação, gate, validação, - aprovação e relatório. - - Aceite: a ordenação `minimal < low < normal < high < danger` é testada e - não depende de strings espalhadas pelo código. - -## Fase 1 — Configuração e contrato público - -- [ ] **T-010 — Implementar carregamento do YAML.** Ler o arquivo na raiz ou no - caminho explicitamente informado e produzir erros com localização no YAML. -- [ ] **T-011 — Implementar validação estrutural.** Validar `version`, - `riskMap`, `gateRules`, `validations` e `approvalGroups`, rejeitando chaves - desconhecidas e referências inexistentes. -- [ ] **T-012 — Implementar normalização de paths.** Normalizar separadores, - prefixos, diretórios e caminhos relativos sem permitir que `..` escape da - raiz. -- [ ] **T-013 — Implementar compatibilidade do formato.** Decidir se o atalho - `riskMap: [{ "src/payment/": danger }]` será aceito além do formato canônico - `path/risk`; se aceito, normalizar ambos para o mesmo modelo e testar. -- [ ] **T-014 — Criar testes de contrato.** Cobrir configuração válida, arquivo - ausente, YAML inválido, risco inválido, regra ambígua, timeout inválido, - validação referenciada mas inexistente e grupo não definido. -- [ ] **T-015 — Atualizar exemplos.** Manter exemplo mínimo e exemplo completo - em `docs/`, sempre sincronizados com `specs/configuration.md`. - -## Fase 2 — Descoberta do projeto e AST - -- [ ] **T-020 — Implementar walker do projeto.** Percorrer a raiz com exclusões - explícitas para `.git`, artefatos, dependências vendorizadas e paths definidos - como ignorados; contabilizar arquivos examinados. -- [ ] **T-021 — Definir interface de parser.** Criar um trait/adaptador que - converta cada linguagem para a AST comum, preservando arquivo, tipo, nome, - intervalo e filhos. -- [ ] **T-022 — Implementar parser Python.** Cobrir módulos, funções, classes, - imports e erros de sintaxe. -- [ ] **T-023 — Implementar parser JavaScript.** Cobrir módulos, funções, - classes, imports/exports e erros de sintaxe. -- [ ] **T-024 — Implementar parser TypeScript.** Cobrir declarações de tipos, - interfaces, módulos, funções, classes, imports/exports e erros de sintaxe. -- [ ] **T-025 — Implementar parser Elixir.** Cobrir módulos, funções, aliases, - imports e macros de forma compatível com a AST comum. -- [ ] **T-026 — Tratar arquivos sem suporte.** Registrar linguagem desconhecida, - parser ausente e erro de parsing como evidência; não descartá-los. -- [ ] **T-027 — Testar AST com fixtures.** Criar fixtures pequenas e projetos - mistos, verificando contagem, nomes qualificados, ranges e nós aninhados. -- [ ] **T-028 — Testar AST completa.** Provar que arquivos não alterados também - são analisados e que o resultado não depende da ordem do filesystem. - -## Fase 3 — Diff, módulos e cálculo de risco - -- [ ] **T-030 — Implementar `DiffProvider` local.** Obter arquivos adicionados, - modificados, removidos e renomeados entre base e head do PR. -- [ ] **T-031 — Implementar `DiffProvider` GitHub.** Usar a API apenas quando - necessário, paginar resultados e preservar SHA/base/head usados na análise. -- [ ] **T-032 — Implementar matching do `riskMap`.** Casar paths relativos e - módulos da AST, mantendo todas as regras aplicáveis e suas descrições. -- [ ] **T-033 — Implementar risco máximo.** Aplicar a precedência de - especificidade e depois o maior `RiskLevel`; usar `normal` para arquivo - alterado sem correspondência. -- [ ] **T-034 — Tratar renomeações e remoções.** Avaliar o path antigo e o novo - quando disponíveis; remoções sem AST devem continuar sendo risco analisável. -- [ ] **T-035 — Definir PR sem alterações.** Retornar erro explícito para diff - vazio, sem classificá-lo como `minimal`. -- [ ] **T-036 — Testar matriz de risco.** Cobrir múltiplas regras, empate, - subpaths, arquivo novo, arquivo removido, rename, path desconhecido e projeto - misto. - -## Fase 4 — Motor de gates e execução de validações - -- [ ] **T-040 — Implementar seleção de `gateRules`.** Selecionar a regra exata - do risco final e produzir um plano determinístico de execução. -- [ ] **T-041 — Implementar executor seguro.** Executar comandos configurados - com diretório, ambiente, timeout, captura de stdout/stderr e limite de saída. - Não interpolar dados do evento no comando. -- [ ] **T-042 — Implementar estados de validação.** Diferenciar sucesso, exit - code diferente de zero, timeout, comando inexistente, interrupção e execução - não iniciada. -- [ ] **T-043 — Implementar aprovação por grupo.** Resolver `team: org/team` e - `users: [...]`, consultar reviews do PR, validar estado/aprovador e registrar - aprovações faltantes. -- [ ] **T-044 — Implementar `instantMerge`.** Quando verdadeiro, manter todas - as falhas no relatório, mas não bloquear o merge nem retornar falha de - mergeability. Não converter o status técnico para `SUCCESS`. -- [ ] **T-045 — Definir exit codes.** Documentar códigos para configuração, - análise, validação, aprovação, GitHub e erro interno; manter códigos estáveis. -- [ ] **T-046 — Testar gates sem rede.** Usar executores e GitHub fakes para - provar que toda validação exigida precisa ter sucesso quando o gate deve - bloquear. - -## Fase 5 — Relatório e integração GitHub - -- [ ] **T-050 — Criar modelo do relatório.** Incluir risco, evidências, paths, - módulos, descrições, gates, validações, aprovações, contagens AST e - limitações. -- [ ] **T-051 — Implementar renderer Markdown seguro.** Escapar conteúdo vindo - do código, configuração, comandos e GitHub; manter formato legível e estável. -- [ ] **T-052 — Implementar comentário idempotente.** Procurar - ``, atualizar o comentário existente ou criar um - novo, sem duplicar mensagens. -- [ ] **T-053 — Implementar outputs da Action.** Publicar `risk`, `status`, - `merge_blocked` e identificador/URL do comentário. -- [ ] **T-054 — Tratar falhas de permissão.** Mostrar autenticação, escopo e - endpoint que falharam; não transformar erro de comentário ou diff em risco - baixo. -- [ ] **T-055 — Testar relatório.** Usar snapshots para cenários de sucesso, - risco alto, validação falha, análise parcial, aprovação ausente e - `instantMerge`. - -## Fase 6 — GitHub Action e execução real - -- [ ] **T-060 — Criar `action.yml`.** Declarar inputs, outputs, permissões, - runtime e versão do binário. -- [ ] **T-061 — Criar workflow de CI do projeto.** Rodar fmt, clippy, testes, - segurança de dependências e build em Linux. -- [ ] **T-062 — Criar workflow de validação do próprio Risk Gate.** Executar a - Action contra fixtures/projetos de teste com PRs controlados. -- [ ] **T-063 — Definir empacotamento.** Escolher binário pré-compilado, - container ou compilação no runner; cobrir Linux x86_64 e arquitetura adotada - pelo projeto. -- [ ] **T-064 — Validar permissões mínimas.** Testar token com leitura do PR e - atualização de comentário, registrando a configuração recomendada. -- [ ] **T-065 — Fazer teste E2E GitHub.** Abrir/usar PR de teste, verificar - comentário atualizado, outputs, status da Action e comportamento de bloqueio. - Registrar o SHA testado e separar resultado local de resultado remoto. - -## Fase 7 — Segurança, performance e observabilidade - -- [ ] **T-070 — Auditar execução de comandos.** Revisar shell, ambiente, timeout, - tamanho de saída, sinais, diretório de trabalho e exposição de tokens. -- [ ] **T-071 — Auditar Markdown e API.** Escapar conteúdo, validar URLs/IDs e - evitar log de secrets, cabeçalhos ou payloads sensíveis. -- [ ] **T-072 — Limitar recursos.** Definir limites para arquivos, nós AST, - concorrência, memória, duração total e tamanho do comentário. -- [ ] **T-073 — Medir performance.** Criar benchmark de projeto pequeno, médio e - grande; medir walker, parse, matching e publicação separadamente. -- [ ] **T-074 — Implementar logs estruturados.** Permitir modo normal e debug, - com correlação por execução e sem vazar credenciais. -- [ ] **T-075 — Testar falhas e interrupções.** Cobrir cancelamento, timeout, - rede indisponível, API paginada, parser quebrado e disco sem espaço. - -## Fase 8 — Documentação, release e manutenção - -- [ ] **T-080 — Documentar instalação e uso local.** Incluir modo offline, - exemplos, variáveis de ambiente e interpretação dos exit codes. -- [ ] **T-081 — Documentar permissões GitHub.** Explicar `permissions`, token, - equipes, usuários e limitações de forks. -- [ ] **T-082 — Criar changelog e versionamento.** Versionar contrato do YAML e - indicar mudanças incompatíveis. -- [ ] **T-083 — Automatizar release.** Gerar artefato verificável, checksum, - tag e atualização controlada da Action. -- [ ] **T-084 — Criar checklist de release.** Exigir fmt, clippy, testes, - fixtures dos quatro parsers, teste E2E, inspeção de permissões e revisão do - relatório antes de publicar. -- [ ] **T-085 — Documentar limitações conhecidas.** Registrar sintaxes não - cobertas, parsing parcial, limites de monorepo e diferenças entre GitHub - Enterprise e GitHub.com. - -## Ordem recomendada de execução - -As tarefas podem ser divididas entre agentes, mas respeitando estas -dependências: - -```text -T-001..004 - ├── T-010..015 - ├── T-020..028 - └── T-030..036 (depende dos modelos e configuração) - └── T-040..046 - ├── T-050..055 - └── T-060..065 - └── T-070..085 -``` - -Cada agente deve entregar código, testes e documentação da sua tarefa. Antes -de integrar uma fase, o agente responsável deve executar os critérios de aceite -da fase e registrar limitações em vez de marcar uma etapa parcialmente -executada como concluída. - -## Backlog explicitamente posterior - -Não bloquear a primeira versão com estes itens: mais linguagens, regras de -risco por linha/símbolo, herança entre `gateRules`, dashboard externo, -comentários em múltiplos idiomas, cache distribuído e análise semântica que -exija executar o projeto. Esses itens só entram depois que o contrato inicial, -os quatro parsers e a execução real no GitHub estiverem comprovados. +# Risk Gate Implementation Plan + +This plan turns the specifications into executable work. It is retained as a +traceable backlog even though the first implementation already covers the core +domain, configuration, AST, diff, gates, report, CLI, and Action. + +## Product completion criteria + +A first release is complete when a real GitHub Actions execution can: + +1. load and validate .risk-gate.yaml; +2. build a complete AST for Python, JavaScript, TypeScript, and Elixir; +3. map changed paths and modules from the PR diff; +4. calculate and explain the highest risk; +5. execute configured gates, validations, and approvals; +6. create or update one idempotent PR comment; +7. emit outputs and exit codes consistent with instantMerge; +8. preserve parsing errors, partial analysis, and limitations; +9. include a Mermaid graph of direct and transitive impact. + +## Phase 0 — Foundation + +- T-001: establish the Rust workspace, library, binary, and test layout. +- T-002: define fmt, Clippy, test, coverage, mutation, and dependency checks. +- T-003: record dependency decisions and cross-compilation constraints. +- T-004: define domain types for risk, status, paths, modules, diff, + assessment, gate, validation, approval, and report. + +Acceptance: a clean checkout runs fmt and the complete test suite. + +## Phase 1 — Configuration + +- T-010: load YAML from the repository root or an explicit path. +- T-011: validate version, risk levels, unknown keys, references, and limits. +- T-012: normalize relative paths without allowing .. to escape the repository. +- T-013: support canonical and shorthand riskMap entries. +- T-014: add contract tests for valid and invalid configurations. +- T-015: keep examples synchronized with specs/configuration.md. +- T-016: implement risk-gate init with commented permissive defaults. + +Acceptance: invalid configuration fails explicitly and generated configuration +loads successfully. + +## Phase 2 — Project discovery and AST + +- T-020: walk the project with explicit exclusions for .git, build artifacts, + vendored dependencies, and node_modules. +- T-021: define the common parser adapter and preserve file, type, name, range, + children, language, and parse errors. +- T-022: implement Python parsing and import extraction. +- T-023: implement JavaScript parsing and import/export/require extraction. +- T-024: implement TypeScript parsing and type/module extraction. +- T-025: implement Elixir parsing and alias/import/require/use extraction. +- T-026: report unsupported files and parser errors instead of dropping them. +- T-027: add fixtures for all four parsers. +- T-028: prove that complete AST results are stable regardless of filesystem + traversal order. + +Acceptance: the complete scoped project is parsed with bounded resources. + +## Phase 3 — Diff, modules, and risk + +- T-030: implement a local Git diff provider for added, modified, deleted, and + renamed files. +- T-031: implement a paginated GitHub diff provider. +- T-032: map changed paths and discovered modules to every applicable rule. +- T-033: apply specificity and maximum RiskLevel precedence. +- T-034: evaluate old and new paths for renames and preserve deleted-file risk. +- T-035: fail explicitly on an empty PR diff. +- T-036: test multiple rules, ties, subpaths, unknown paths, new files, removed + files, renames, and isolated code. +- T-037: build a bidirectional dependency graph and transitive impact paths. +- T-038: render impact order as Mermaid in the report. + +Acceptance: the assessment explains both direct rule matches and graph impact. + +## Phase 4 — Gates and validations + +- T-040: select the exact gateRules entry for final risk. +- T-041: execute commands with explicit directory, environment, timeout, output + capture, and output limits. +- T-042: distinguish success, non-zero exit, timeout, missing command, + interruption, and not-started states. +- T-043: resolve GitHub team and explicit-user approval groups. +- T-044: implement instantMerge without hiding technical failures. +- T-045: document stable exit codes. +- T-046: test gates with fake executors and GitHub adapters. + +Acceptance: every required validation succeeds before a blocking gate succeeds. + +## Phase 5 — Reporting + +- T-050: include risk, evidence, paths, modules, descriptions, graph impact, + Mermaid, gates, validations, approvals, AST counts, and limitations. +- T-051: safely escape code, configuration, commands, and GitHub content. +- T-052: update the comment marked instead of + creating duplicates. +- T-053: publish risk, status, merge_blocked, and report_url outputs. +- T-054: expose authentication and permission failures without downgrading risk. +- T-055: test success, high risk, failed validation, partial analysis, missing + approval, and large reports. + +## Phase 6 — Action and release + +- T-060: define Action inputs, outputs, permissions, runtime, and binary version. +- T-061: run fmt, Clippy, tests, coverage, mutation, and dependency checks in CI. +- T-062: validate the Action against controlled PR fixtures. +- T-063: publish checksummed Linux, macOS, and Windows binaries concurrently. +- T-064: validate minimum GitHub permissions. +- T-065: protect master with required PR review, rebase, and quality checks. + +Acceptance: a release is usable by another repository without compiling Rust. + +## Phase 7 — Security, performance, and observability + +- T-070: audit shell execution, environment, timeouts, output, signals, and + token exposure. +- T-071: audit Markdown and API escaping, URLs, IDs, headers, and payloads. +- T-072: bound files, AST nodes, concurrency, memory, duration, and comment size. +- T-073: benchmark small, medium, and large projects. +- T-074: record execution correlation without leaking credentials. +- T-075: test cancellation, timeout, unavailable network, pagination, broken + parser, and full disk. + +## Phase 8 — Documentation and maintenance + +- T-080: document installation, local modes, environment variables, and exit + codes. +- T-081: document GitHub permissions, tokens, teams, users, forks, and private + Action access. +- T-082: maintain the changelog and identify incompatible changes. +- T-083: automate release artifacts, checksums, tags, and Action updates. +- T-084: run a release checklist with parser fixtures and an E2E PR. +- T-085: document known syntax, monorepo, parser, and GitHub limitations. + +Agents may split these tasks while respecting their dependencies. Every agent +must deliver code, tests, and English documentation for its task. Before a +phase is integrated, its acceptance criteria must be run and limitations must +be recorded rather than marking partial work complete. diff --git a/docs/quality.md b/docs/quality.md index 1937636..e568290 100644 --- a/docs/quality.md +++ b/docs/quality.md @@ -1,53 +1,25 @@ -# Qualidade e evidências +# Quality Gates -## Comandos locais +The repository CI must run: -O ambiente deve ter Rust stable com `rustfmt`, `clippy` e `llvm-tools-preview`. -Comandos mínimos: - -```bash +~~~bash cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings cargo test --all-targets --all-features -cargo llvm-cov --workspace --all-features --fail-under-lines 95 +cargo llvm-cov --workspace --all-features --fail-under-lines 95.01 cargo mutants --baseline run --copy-target true --minimum-test-timeout 60 --no-shuffle -j 4 bash scripts/check-mutation-score.sh -cargo bench --bench risk_analysis -``` - -O mutation score é calculado pelo relatório do `cargo-mutants` como: - -```text -killed / (killed + missed + timeout) -``` - -`unviable` deve ser investigado e não pode ser contado como mutante morto. -Sobreviventes (`missed`) exigem teste de regressão ou uma justificativa -documentada de mutante equivalente. A CI deve falhar em 95% ou abaixo. - -Para diagnóstico opcional, defina `RISK_GATE_LOG=json`. Eventos estruturados -serão enviados para stderr; tokens, comandos de validação e respostas da API -não são incluídos. - -Na validação atual, o score foi `96,38%` (`266/276`), acima da meta de 95%. -O relatório registrou sete sobreviventes e três timeouts; os sobreviventes -restantes estão concentrados em uma condição de parsing de referências, no -ramo de conversão de nós Tree-sitter, em limites de truncamento, na condição -de auto-referência do grafo e no wrapper opcional de telemetria. Eles ficam -registrados pelo artefato do `cargo-mutants`; a CI considera timeouts como -mutantes não aprovados e falha em 95% ou abaixo. +~~~ -## Escopo da cobertura +Coverage must be strictly above 95%. Mutation score is calculated from caught, +missed, and timed-out mutants, excluding unviable mutants, and must also be +strictly above 95%. -A cobertura de linhas é medida com `--workspace`, incluindo a biblioteca, CLI e -testes de integração. A meta de 95% não pode ser atingida excluindo o runtime -principal; código deliberadamente não testável deve ser extraído para uma -fronteira pequena e substituível, com teste de contrato na borda. +The current local validation reached 95.95% line coverage and a 96.38% +mutation score, with 266 caught mutants out of 276 scored mutants. -## Ambiente macOS sem componentes Rust +## Failure policy -Se o `cargo` padrão não estiver no `PATH`, use o toolchain Rust instalado ou -configure o `PATH` para o diretório que contém `cargo`, `rustc` e os componentes -de qualidade. A CI usa `dtolnay/rust-toolchain@stable` com `rustfmt`, `clippy` e -`llvm-tools-preview`, portanto a ausência local não é evidência de que um gate -passou. +The CI must fail when formatting, Clippy, tests, coverage, or mutation score +fails. A successful process with incomplete evidence must not be reported as a +successful quality result. diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 9db78e4..77821a2 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -1,14 +1,13 @@ -# Checklist de release +# Release Checklist -- [ ] Atualizar a versão em `Cargo.toml`, `action.yml` e no changelog. -- [ ] Executar `cargo fmt --all -- --check`. -- [ ] Executar clippy e todos os testes. -- [ ] Confirmar cobertura de linhas > 95% no workspace. -- [ ] Confirmar mutation score > 95%. -- [ ] Construir o binário Linux com `--release --locked`. -- [ ] Gerar `risk-gate-x86_64-unknown-linux-gnu.sha256`. -- [ ] Publicar binário e checksum na mesma tag `vX.Y.Z`. -- [ ] Validar a Action em um PR de teste com permissões de leitura do PR e - escrita de comentários. -- [ ] Confirmar que outputs `risk`, `status`, `merge_blocked` e `report_url` - estão disponíveis para jobs seguintes. +- [ ] Update the version in Cargo.toml, action.yml, and the changelog. +- [ ] Run cargo fmt, Clippy, and all tests. +- [ ] Confirm line coverage is above 95% in the workspace. +- [ ] Confirm mutation score is above 95%. +- [ ] Build the Linux binary with --release --locked. +- [ ] Generate a SHA-256 checksum for every release asset. +- [ ] Publish binaries and checksums under the same vX.Y.Z tag. +- [ ] Validate the Action in a test PR with PR-read and comment-write + permissions. +- [ ] Confirm risk, status, merge_blocked, and report_url outputs are available + to downstream jobs. diff --git a/specs/README.md b/specs/README.md index 157fc2f..8989b8e 100644 --- a/specs/README.md +++ b/specs/README.md @@ -1,27 +1,29 @@ -# Especificações do Risk Gate +# Risk Gate Specifications -Esta pasta define os contratos do produto. Os documentos são normativos para -implementação e testes; exemplos em `docs/` são ilustrativos, mas devem seguir -estes contratos. +This directory defines product contracts. These documents are normative for +implementation and tests; examples in docs/ are illustrative but must follow +the same contracts. -- [Requisitos e fluxo](requirements.md) -- [Contrato de configuração](configuration.md) -- [Arquitetura](architecture.md) -- [GitHub Action e estados](github-action.md) +## Documents -O trabalho de implementação está decomposto em tarefas no -[`docs/implementation-plan.md`](../docs/implementation-plan.md). +- configuration.md: the .risk-gate.yaml contract; +- requirements.md: functional requirements and acceptance rules; +- architecture.md: component boundaries and operational constraints; +- github-action.md: Action inputs, outputs, permissions, and CLI modes; +- decisions/: architecture decision records. -## Termos +Implementation work is tracked in docs/implementation-plan.md. -- **arquivo alterado**: caminho retornado pelo diff do Pull Request; -- **módulo**: unidade semântica descoberta na AST, associada a um arquivo e a - um caminho lógico; -- **path rule**: entrada do `riskMap` que relaciona um prefixo de caminho a um - nível e, opcionalmente, a uma descrição; -- **gate**: comportamento aplicado após o risco máximo ser calculado; -- **validação**: comando nomeado que precisa terminar com sucesso quando for - exigido. +## Normative vocabulary -As palavras “deve” e “não pode” são obrigatórias. “Pode” indica extensão -compatível, sem alterar o comportamento definido. +- changed file: a path returned by the Pull Request diff; +- module: a semantic unit discovered in the AST and associated with a file and + logical path; +- path rule: a riskMap entry that maps a path prefix to a level and optional + description; +- gate: behavior applied after maximum risk is calculated; +- validation: a named command that must succeed when selected; +- approval group: a GitHub team or explicit user set required by a gate. + +The words must and must not are mandatory requirements. May describes a +compatible extension that does not change defined behavior. diff --git a/specs/architecture.md b/specs/architecture.md index b7224b8..ab9386a 100644 --- a/specs/architecture.md +++ b/specs/architecture.md @@ -1,43 +1,43 @@ -# Arquitetura de referência +# Reference Architecture -O binário deve ser pequeno e adequado a uma Action, mas a lógica deve ser -testável como biblioteca. +The binary must be small and suitable for an Action, while its logic must be +testable as a library. -```text +~~~text CLI - ├─ input: workspace, event/PR, token e opções + ├─ input: workspace, event/PR, token, and options ├─ ConfigLoader - ├─ ProjectWalker -> LanguageParser -> CommonAst ├─ DiffProvider - ├─ RiskMapper -> RiskAssessment - ├─ GateEngine -> ValidationRunner / ApprovalChecker - └─ ReportRenderer -> GitHubCommenter + ActionOutputs -``` - -## Fronteiras - -- `ConfigLoader`: lê e valida `.risk-gate.yaml`; não chama GitHub. -- `LanguageParser`: um adaptador por linguagem; retorna erros estruturados. -- `DiffProvider`: abstrai `git diff` local e API/evento do GitHub. -- `RiskMapper`: recebe AST, paths e diff; não executa comandos. -- `ValidationRunner`: executa apenas validações selecionadas e produz - resultados; não decide risco. -- `ApprovalChecker` e `GitHubCommenter`: usam uma interface GitHub mockável. -- `GateEngine`: combina risco, aprovações e validações em um resultado - determinístico. - -O domínio não deve depender de variáveis de ambiente do GitHub. A adaptação de -`GITHUB_EVENT_PATH`, `GITHUB_REPOSITORY`, `GITHUB_TOKEN` e outputs deve ficar na -CLI. - -## Segurança e operação - -O token deve receber somente as permissões documentadas pela Action. Saídas de -comandos e conteúdo do PR devem ser escapados antes de virar Markdown. Comandos -devem rodar sem `shell -c` quando possível; se o shell for necessário para -suportar comandos configuráveis, o executor precisa registrar a decisão, -aplicar timeout e não concatenar dados do evento. - -A árvore completa pode ser grande. O sistema pode cachear parsers e índices, -mas o cache não pode alterar o resultado nem ocultar erro de parsing. O -relatório deve informar contagens de arquivos, nós e parsers. + ├─ ProjectWalker + LanguageParsers + ├─ RiskMapper + DependencyGraph + ├─ GateEngine + ValidationRunner + ├─ GithubAdapter + └─ ReportRenderer + outputs +~~~ + +## Component boundaries + +- ConfigLoader reads and validates .risk-gate.yaml and does not call GitHub. +- LanguageParser has one adapter per language and returns structured errors. +- RiskMapper receives ASTs, paths, and diff data and does not execute commands. +- ValidationRunner executes only selected validations and produces results; it + does not decide risk. +- ApprovalChecker and GitHubCommenter use a mockable GitHub interface. +- GateEngine combines risk, approvals, and validations into a deterministic + result. + +The domain must not depend on GitHub environment variables. Adaptation of +GITHUB_EVENT_PATH, GITHUB_REPOSITORY, GITHUB_TOKEN, and outputs belongs in the +CLI and Action boundary. + +## Security and operations + +The token must receive only the permissions documented by the Action. Command +output and PR content must be escaped before becoming Markdown. Commands should +avoid shell -c when possible; when a shell is required for configurable +commands, the executor must record the decision, apply a timeout, and never +concatenate event data. + +The complete tree can be large. The system may cache parsers and indexes, but +the cache must not change results or hide parsing errors. Reports must include +file, node, and parser counts. diff --git a/specs/configuration.md b/specs/configuration.md index 3c91492..ccada6c 100644 --- a/specs/configuration.md +++ b/specs/configuration.md @@ -1,24 +1,24 @@ -# Contrato de `.risk-gate.yaml` +# .risk-gate.yaml Contract -O formato é YAML. Chaves desconhecidas devem ser rejeitadas na versão inicial, -para evitar falsa sensação de configuração aplicada. +The format is YAML. Unknown keys must be rejected in the initial version to +avoid creating a false impression that a setting was applied. -```yaml +~~~yaml version: 1 riskMap: - path: src/payment/ risk: danger - description: "Processamento de pagamentos e autorização financeira" + description: "Payment processing and financial authorization" - path: tests/ risk: minimal - description: "Testes automatizados" + description: "Automated tests" - path: infra/database/ risk: danger - description: "Migrações, acesso e infraestrutura do banco" + description: "Database migrations, access, and infrastructure" - path: src/instructions/ risk: normal - description: "Instruções usadas pelo runtime" + description: "Instructions used by the runtime" newCodePolicy: include gateRules: @@ -47,53 +47,51 @@ limits: maxFiles: 10000 maxAstNodes: 1000000 maxCommentBytes: 60000 -``` +~~~ -## Forma mínima +## Minimal form -O comando `risk-gate init` gera uma forma mínima permissiva para edição. A -regra `path: ./` representa a raiz inteira do repositório; ela pode ser -substituída por regras mais específicas. A configuração gerada mantém -`instantMerge: true` em todos os níveis e não define validações nem grupos de -aprovação. +The risk-gate init command generates a permissive form for editing. The path: +./ rule represents the entire repository and can be replaced with more +specific rules. The generated configuration keeps instantMerge: true for every +level and defines no validations or approval groups. -`riskMap` é obrigatório e não pode ser vazio. Cada item contém `path`, `risk` -e pode conter `description` e `newCodePolicy`. Os níveis válidos são exatamente -`minimal`, `low`, `normal`, `high` e `danger`. +riskMap is required and cannot be empty. Each item contains path and risk and +may contain description and newCodePolicy. Valid levels are exactly minimal, +low, normal, high, and danger. -`newCodePolicy` controla código novo em uma regra: `include` (padrão) mantém o -risco da regra mesmo quando o arquivo novo é isolado; `ignore` reduz para -`minimal` somente quando não existe ligação de dependência com outro arquivo -que pertença a qualquer regra. Se houver uma ligação direta ou transitiva, o -risco da regra é aplicado e a cadeia aparece no relatório do PR. +newCodePolicy controls new code under a rule: include, the default, preserves +the rule risk even when a new file is isolated; ignore reduces it to minimal +only when there is no dependency link to another file belonging to any rule. +When a direct or transitive link exists, the rule risk applies and the chain +appears in the PR report. -`gateRules` pode conter qualquer nível. Os campos válidos de uma regra são: +gateRules may contain any risk level. Valid fields are: -- `instantMerge: boolean`; -- `requireApprovalGroup: string`; -- `validations: [string, ...]`. +- instantMerge: boolean; +- requireApprovalGroup: string; +- validations: a list of names. -`validations` é um mapa de nomes para objetos com `command` e opcionalmente -`timeoutSeconds`. Por compatibilidade futura, a implementação pode aceitar uma -string como atalho, mas deve normalizá-la para o mesmo modelo e documentar o -atalho em testes. +validations maps names to objects with command and optional timeoutSeconds. +For compatibility, a string shorthand may also be accepted and normalized to +the same model. -`approvalGroups` resolve nomes locais para uma equipe (`team: org/team`) ou -logins explícitos (`users: [alice, bob]`). `requireApprovalGroup` sem uma -entrada correspondente deve falhar na validação da configuração. +approvalGroups resolves local names to a team, such as org/team, or explicit +logins, such as users: [alice, bob]. A requireApprovalGroup entry without a +matching group must fail configuration validation. -`limits` é opcional e possui valores seguros por padrão. `maxFiles` limita os -arquivos percorridos, `maxAstNodes` limita a árvore agregada e -`maxCommentBytes` limita o comentário publicado no PR. Todos devem ser -inteiros positivos; exceder um limite falha a análise de forma explícita. +limits is optional and has safe defaults. maxFiles limits traversed files, +maxAstNodes limits the aggregate tree, and maxCommentBytes limits the PR +comment. All values must be positive integers; exceeding a limit must fail the +analysis explicitly. -## Semântica de paths +## Path semantics -Paths são relativos à raiz, normalizados e comparados com separadores `/`. -Regras mais específicas vencem regras menos específicas quando apontam para o -mesmo arquivo; depois disso, o risco máximo entre todas as regras aplicáveis é -usado. Assim, uma área `danger` nunca é rebaixada por uma regra de diretório -mais ampla. +Paths are relative to the repository root, normalized, and compared with / +separators. More specific rules win when they point to the same file; the +maximum risk among all applicable rules is then used. A danger area can never +be downgraded by a broader directory rule. -Exemplo: `src/payment/checkout.py` casa com `src/` e `src/payment/`; o maior -nível aplicável é usado e ambas as descrições podem ser incluídas no relatório. +For example, src/payment/checkout.py matches src/ and src/payment/; the +highest applicable level is used and both descriptions may be included in the +report. diff --git a/specs/decisions/0001-rust-and-tree-sitter.md b/specs/decisions/0001-rust-and-tree-sitter.md index 9e187ec..3d758de 100644 --- a/specs/decisions/0001-rust-and-tree-sitter.md +++ b/specs/decisions/0001-rust-and-tree-sitter.md @@ -1,19 +1,17 @@ -# ADR-0001 — Rust e parsers Tree-sitter +# ADR-0001: Rust Runtime and Tree-sitter -## Decisão +## Decision -O runtime será Rust. A primeira matriz de linguagens usará as crates oficiais -de gramática Tree-sitter, atrás de uma interface própria que converte os nós -para a AST comum do Risk Gate. +The runtime will be Rust. The first language matrix will use official +Tree-sitter grammar crates behind a project-owned interface that converts +language nodes into the common Risk Gate AST. -## Motivo +## Rationale -Isso mantém um único binário adequado para GitHub Actions, permite parsing -incremental no futuro e separa a gramática de cada linguagem do domínio de -risco. O domínio não depende diretamente de tipos específicos de uma gramática. +This keeps one binary suitable for GitHub Actions, allows incremental parsing +in the future, and separates each language grammar from the risk domain. The +domain does not depend directly on grammar-specific types. -## Limites - -A existência de uma árvore sintática não significa análise semântica completa. -Erros de sintaxe, linguagem sem suporte e nós sem nome devem ser reportados com -estado e evidência no relatório. +The existence of a syntax tree does not mean complete semantic analysis. +Syntax errors, unsupported languages, and unnamed nodes must be reported with +state and evidence in the report. diff --git a/specs/github-action.md b/specs/github-action.md index 2d3b74a..f180416 100644 --- a/specs/github-action.md +++ b/specs/github-action.md @@ -1,51 +1,57 @@ -# Contrato da GitHub Action +# GitHub Action Contract -## Entradas +The Action must accept, at minimum, a configuration file path, a diff mode +(local or GitHub), and a default timeout. The token and PR context must come +from official GitHub Actions inputs or environment variables and must never be +written to the report. -A Action deve aceitar, no mínimo, caminho do arquivo de configuração, modo de -obter o diff (`local` ou `github`) e timeout padrão. O token e o contexto do PR -devem vir das entradas/ambiente oficial do GitHub Actions, sem serem gravados -no relatório. +## Outputs -## Saídas +The Action must publish outputs consumable by later jobs: -Devem ser publicados outputs consumíveis por jobs posteriores: +- risk: final risk level; +- status: technical analysis status; +- merge_blocked: whether configured gates block mergeability; +- report_url or a comment identifier, when available. -- `risk`: nível final; -- `status`: `SUCCESS`, `PARTIAL` ou `FAILED`; -- `merge_blocked`: `true` ou `false`; -- `report_url` ou identificador do comentário, quando disponível. +The process may exit with code zero only when configuration is valid, analysis +is non-fatal, all required validations succeed, required approvals are +satisfied or instantMerge is true, and the comment is published, except in +offline test mode. instantMerge changes only merge_blocked and the blocking +exit code; it does not turn a technical FAILED status into a correct analysis. -O processo deve sair com código zero somente quando todas as condições abaixo -forem verdadeiras: configuração válida, análise não fatal, todas as -validações exigidas com sucesso, aprovações exigidas satisfeitas ou -`instantMerge: true`, e comentário publicado (exceto em modo offline de -teste). `instantMerge` altera apenas `merge_blocked`/exit code do bloqueio; não -transforma `FAILED` técnico em análise correta. +Authentication, permission, comment, or diff failures must be visible and must +not be presented as minimal risk. -Falha de autenticação, permissão, comentário ou obtenção do diff deve ser -visível e não pode ser apresentada como risco `minimal`. +## Expected permissions -## Permissões esperadas +Action documentation must declare the permissions required to read Pull +Requests and publish or update comments. When a permission is unavailable, the +system must explain it in the report without attempting to bypass repository +policy. -A documentação da Action deve declarar as permissões necessárias para ler -Pull Requests e publicar/atualizar comentários. O sistema deve explicar no -relatório quando a permissão não está disponível, sem tentar contornar a -política do repositório. +The initial Action downloads the Linux binary matching the version supplied in +version and runs it with the event context. Releases must publish the asset +risk-gate-x86_64-unknown-linux-gnu before another repository uses that version. -O manifesto da primeira Action baixa o binário Linux correspondente à versão -informada em `version` e executa-o com o contexto do evento. Releases devem -publicar o asset `risk-gate-x86_64-unknown-linux-gnu` antes que essa versão seja -usada por outros repositórios. +## CLI dry-run mode -## CLI em modo dry-run +The binary must accept --dry-run with --repository, --pull-number, and --token. +This mode queries PR files, runs configuration, AST, diff, and graph analysis, +and returns JSON containing risk, assessment, and the selected policy. -O binário também deve aceitar --dry-run com --repository, --pull-number e ---token. Esse modo consulta os arquivos do PR, executa a análise de -configuração, AST, diff e grafo, e retorna JSON contendo risk, assessment, -policy e mergeBlockedIfUnsuccessful. +Dry-run must not publish or update comments, query reviews or team members, or +execute commands declared in validations. Configuration or query errors return +a non-zero code; a valid analysis returns zero even when the planned policy +would block the merge. -O dry-run não pode publicar ou atualizar comentário, consultar reviews ou -membros de equipe, nem executar os comandos declarados em validations. Um erro -de configuração ou consulta retorna código não-zero; uma análise válida retorna -código zero mesmo quando a política planejada seria bloqueadora. +## CLI AST-impact mode + +The binary must accept --ast-impact with --repository, --pull-number, --token, +and --root without requiring .risk-gate.yaml. It must query PR files, build +ASTs from files available in the checkout, identify modules and references, and +return dependency chains connecting changed files to other modules. + +The JSON result must include changed files, language, modules, references, +partial errors, and ordered impacts. This mode does not calculate risk, select +gateRules, execute validations, or publish comments. diff --git a/specs/requirements.md b/specs/requirements.md index 6e5804d..f0bfd1c 100644 --- a/specs/requirements.md +++ b/specs/requirements.md @@ -1,133 +1,131 @@ -# Requisitos funcionais +# Functional Requirements -## RF-01 — Configuração no repositório +## FR-01 — Repository configuration -Em cada execução, o programa deve procurar `.risk-gate.yaml` na raiz do -checkout. A ausência do arquivo, YAML inválido, nível desconhecido, regra -duplicada ambígua ou comando sem nome deve resultar em `FAILED` e exit code -não zero. +Every execution must look for .risk-gate.yaml at the checkout root. A missing +file, invalid YAML, unknown level, ambiguous duplicate rule, or unnamed command +must result in FAILED and a non-zero exit code. -## RF-02 — AST completa e independente de linguagem +## FR-02 — Complete language-independent AST -O analisador deve percorrer o projeto inteiro dentro do escopo configurado e -montar uma representação comum de AST. O nó comum deve conservar, no mínimo, -linguagem, caminho, tipo do nó, nome qualificado quando disponível, intervalo -de origem e filhos. +The analyzer must traverse the entire project within the configured scope and +build a common AST representation. Each common node must preserve, at minimum, +language, path, node type, qualified name when available, byte range, and +children. -A primeira matriz de suporte é Python, JavaScript, TypeScript e Elixir. Parser -ausente ou erro de parsing deve aparecer no relatório com caminho e motivo. -Uma árvore parcial não pode ser apresentada como análise completa. +The first support matrix is Python, JavaScript, TypeScript, and Elixir. A +missing parser or parse error must appear in the report with path and reason. +A partial tree must never be presented as complete analysis. -## RF-03 — Mapeamento de módulos +## FR-03 — Module mapping -Cada entrada de `riskMap` deve ser comparada ao caminho normalizado do arquivo e -dos módulos descobertos. O mapeamento precisa permitir identificar quais -módulos vivem sob cada path rule, inclusive quando a alteração é em um arquivo -de teste, configuração ou infraestrutura sem módulo AST correspondente. +Each riskMap entry must be compared with normalized file paths and discovered +modules. Mapping must identify modules under each path rule, including changes +to tests, configuration, or infrastructure without a corresponding AST module. -O caminho é relativo à raiz do repositório, usa `/` e não começa com `./`. -Uma regra de diretório termina em `/`; uma regra de arquivo pode ser exata. +Paths are relative to the repository root, use /, and are normalized. A +directory rule ends with /; a file rule can be exact. The root rule ./ matches +the entire repository. -## RF-04 — Risco do Pull Request +## FR-04 — Pull Request risk -O diff deve ser obtido dos commits do PR, incluindo arquivos adicionados, -modificados, renomeados e removidos. Para cada arquivo alterado, o sistema -calcula o maior risco das regras aplicáveis. O risco do PR é o máximo entre -esses resultados. +The diff must come from PR commits and include added, modified, renamed, and +removed files. For each changed file, the system calculates the maximum risk +of applicable rules. PR risk is the maximum across changed files. -Se nenhum arquivo alterado casar com o mapa, o risco é `normal` (fallback -seguro). PR sem arquivos alterados é inválido para análise e deve falhar. +If no changed file matches the map, risk is normal as a safe fallback. A PR +with no changed files is invalid for analysis and must fail. -### RF-04.1 — Impacto em cascata e código novo isolado +### FR-04.1 — Cascade impact and isolated new code -O analisador deve construir um grafo de referências entre os arquivos -suportados. Arestas podem representar imports, requires, aliases ou usos -equivalentes identificados pelo parser. A busca de impacto considera a cadeia -transitiva em ambas as direções: código novo que usa uma área crítica e código -existente que passa a usar código novo são evidências de impacto. +The analyzer must build a reference graph between files and modules identified +by the parser. Impact search considers transitive chains in both directions: +new code using a critical area and existing code that starts using new code are +both impact evidence. -Uma regra pode declarar `newCodePolicy: ignore`. Essa exceção só se aplica a -arquivo novo sem caminho no grafo até qualquer regra de risco. Código novo -conectado conserva o risco aplicável. O relatório deve mostrar os arquivos e a -cadeia que causaram o risco; código isolado ignorado também deve ser explícito. +A rule may declare newCodePolicy: ignore. This exception applies only to a new +file with no graph path to any risk rule. Connected new code keeps its +applicable risk. The report must show the files and chain that caused risk; +ignored isolated code must also be explicit. -## RF-05 — Gate rules +The report must include a Mermaid diagram showing the changed module, affected +modules, and traversal order. -Depois do risco máximo, o sistema deve selecionar `gateRules[risco]`. Uma regra -de nível superior não herda automaticamente a regra de níveis inferiores. A -configuração pode usar `extends` no futuro, mas isso precisa ser especificado -antes de ser implementado. +## FR-05 — Risk gates -`instantMerge: true` significa que o resultado do gate não bloqueia o merge e a -Action termina com sucesso para fins de mergeability. O relatório ainda deve -mostrar validações falhas ou incompletas, e a execução nunca pode omitir seus -resultados. +After maximum risk is calculated, the system selects gateRules for that exact +level. A higher-level rule does not automatically inherit lower-level rules. -Sem `instantMerge`, qualquer validação exigida que não termine com exit code 0, -ou que não possa ser executada, faz o gate falhar. +instantMerge: true means the gate result does not block the merge and the +Action exits successfully for mergeability. The report must still show failed +or incomplete validations, and technical status must not be changed to +SUCCESS. -## RF-06 — Aprovação por grupo +Without instantMerge, any required validation that does not exit zero or cannot +be executed must fail the gate. -`requireApprovalGroup` identifica um grupo lógico de aprovação no GitHub. O -adaptador GitHub deve resolver o grupo para usuários/equipe configurados, -verificar aprovações válidas no PR e relatar quem aprovou e o que faltou. -Permissão insuficiente para verificar aprovação é `FAILED`, não aprovação -implícita. A sintaxe de resolução do grupo deve ser documentada pelo -adaptador; a primeira versão deve aceitar `org/team` e uma lista de logins. +## FR-06 — Group approval -## RF-07 — Validações +requireApprovalGroup identifies a logical GitHub approval group. The GitHub +adapter must resolve the group to configured users or a team, verify valid PR +approvals, and report who approved and what is missing. -`validations` referencia comandos nomeados na seção `validations`. Cada comando -selecionado deve ser executado uma vez, com timeout e saída capturados. Todas -as validações exigidas devem retornar sucesso para o Risk Gate retornar -sucesso. O resultado deve incluir nome, comando identificado, duração, exit -code, estado e trecho de saída seguro para log. +Insufficient permission to verify approval is FAILED, not implicit approval. +The first version must accept org/team and a list of logins. -## RF-08 — Relatório no PR +## FR-07 — Configured validations -Toda análise deve gerar um comentário idempotente no PR contendo: +validations references named commands in the validations section. Each selected +command must run once with timeout and output captured. All required +validations must succeed for Risk Gate to return success. The result must +include name, identified command, duration, exit code, state, and a safe output +excerpt. -1. risco final e nível ordenado; -2. arquivos e módulos que determinaram o maior risco; -3. descrição configurada nas regras correspondentes; -4. gates selecionados, aprovações e validações; -5. limitações, parsers ausentes e estados `PARTIAL`/`FAILED`; -6. identificador e versão do risk-gate. +## FR-08 — Idempotent report -O comentário deve ser atualizado quando já existir a marca -``; não deve criar uma nova mensagem a cada execução. +Every analysis must generate an idempotent PR comment containing: -## RF-10 — Dry-run remoto +1. final risk and ordered level; +2. files and modules that determined maximum risk; +3. path descriptions; +4. graph impact and Mermaid cascade; +5. limitations, missing parsers, and PARTIAL or FAILED states; +6. validation and approval results; +7. Risk Gate identifier and version. -O CLI deve aceitar --dry-run --repository owner/name --pull-number N --token -TOKEN para analisar um PR remoto sem alterar o GitHub. O resultado deve ser -JSON e informar o risco, o assessment de impacto e a política selecionada em -gateRules, incluindo validações, timeout, grupo de aprovação, instantMerge e -se uma falha bloquearia o merge. +When the stable marker already exists, the comment +must be updated instead of creating another message. -O dry-run não executa validações, não consulta aprovações e não cria nem -atualiza comentários. Uma análise válida retorna sucesso independentemente de -a política planejada ser bloqueadora; falhas de configuração, API, AST ou diff -retornam erro. +## FR-09 — Remote dry-run -## RF-11 — Impacto AST sem configuração +The CLI must accept --dry-run --repository owner/name --pull-number N --token +TOKEN to analyze a remote PR without changing GitHub. The result must be JSON +and include risk, impact assessment, and the policy selected from gateRules, +including validations, timeout, approval group, instantMerge, and whether a +failure would block the merge. -O CLI deve aceitar --ast-impact com --repository, --pull-number, --token e ---root sem exigir .risk-gate.yaml. O modo deve consultar os arquivos do PR, -montar a AST dos arquivos disponíveis no checkout, identificar módulos e -referências e retornar as cadeias de dependência que conectam cada arquivo -alterado a outros módulos. +Dry-run does not execute validations, query approvals, or create/update +comments. A valid analysis returns success regardless of whether the planned +policy is blocking; configuration, API, AST, or diff failures return an error. -O resultado deve ser JSON e informar arquivos alterados, linguagem, módulos, -referências, erros parciais e impactos ordenados. O modo não calcula risco, -não seleciona gateRules, não executa validações e não publica comentários. +## FR-10 — AST impact without configuration -## RF-12 — Inicialização da configuração +The CLI must accept --ast-impact with --repository, --pull-number, --token, +and --root without requiring .risk-gate.yaml. It must query PR files, build +ASTs from the available checkout, identify modules and references, and return +dependency chains connecting each changed file to other modules. -O CLI deve aceitar o comando `init` e criar `.risk-gate.yaml` no diretório do -repositório. A configuração inicial deve ser válida, conter comentários sobre -os valores permitidos e usar uma política permissiva: `path: ./`, risco -`minimal`, `newCodePolicy: include`, nenhum comando de validação ou aprovação e -`instantMerge: true` para todos os níveis. O comando deve aceitar `--root`, -`--output` e `--force`; sem `--force`, não pode sobrescrever um arquivo -existente. +The JSON result must include changed files, language, modules, references, +partial errors, and ordered impacts. It does not calculate risk, select +gateRules, execute validations, or publish comments. + +## FR-11 — Configuration initialization + +The CLI must accept the init command and create .risk-gate.yaml in the +repository directory. The initial configuration must be valid, contain +comments describing allowed values, and use a permissive policy: path ./, risk +minimal, newCodePolicy include, no validation or approval commands, and +instantMerge true for every level. + +The command must accept --root, --output, and --force. Without --force it must +not overwrite an existing file. diff --git a/src/report.rs b/src/report.rs index b427873..5bea2a3 100644 --- a/src/report.rs +++ b/src/report.rs @@ -468,7 +468,7 @@ mod tests { assessment: &assessment, gate: None, parsed_files: &[], - version: "á", + version: "release 🚀", }, 20, ); @@ -495,8 +495,8 @@ mod tests { 3, ); assert!(tiny.len() <= 3); - assert_eq!(utf8_prefix_len("éclair", 2), 2); - assert_eq!(utf8_prefix_len("éclair", 6), 6); + assert_eq!(utf8_prefix_len("release 🚀", 9), 8); + assert_eq!(utf8_prefix_len("release 🚀", 12), 12); assert_eq!(utf8_prefix_len("abc", 10), 3); } } From 23e8a3972aaad26e3947ebb7d08df95d4e860a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ramos?= Date: Sun, 11 Oct 2026 15:41:32 -0300 Subject: [PATCH 2/4] Isolate CLI config test from GitHub environment --- tests/cli.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cli.rs b/tests/cli.rs index 9802381..b30b284 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -52,6 +52,12 @@ fn cli_loads_configuration_without_diff_context() { binary() .arg("--config") .arg(directory.path().join("risk.yaml")) + .env_remove("GITHUB_BASE_SHA") + .env_remove("GITHUB_SHA") + .env_remove("GITHUB_EVENT_PATH") + .env_remove("GITHUB_TOKEN") + .env_remove("GITHUB_REPOSITORY") + .env_remove("GITHUB_API_URL") .assert() .success() .stdout(predicate::str::contains("loaded 1 risk rules")); From 11e63eb69fcb6168dbc7270c088807a552ee924a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ramos?= Date: Sun, 11 Oct 2026 16:02:03 -0300 Subject: [PATCH 3/4] Improve mutation test coverage --- src/ast.rs | 19 +++++++++++++++++++ src/report.rs | 14 ++++++++++++++ src/risk.rs | 31 +++++++++++++++++++++++++++++++ src/telemetry.rs | 15 +++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/src/ast.rs b/src/ast.rs index 0ecb01e..9d00b80 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -370,6 +370,24 @@ mod tests { assert!(parsed.root.is_some()); } + #[test] + fn preserves_elixir_alias_names_in_the_ast() { + let directory = tempdir().unwrap(); + fs::write( + directory.path().join("alias.ex"), + "defmodule App do\n alias MyApp.Payment\nend\n", + ) + .unwrap(); + let parsed = parse_file(directory.path(), Path::new("alias.ex")).unwrap(); + + fn has_name(node: &AstNode, expected: &str) -> bool { + node.name.as_deref() == Some(expected) + || node.children.iter().any(|child| has_name(child, expected)) + } + + assert!(has_name(parsed.root.as_ref().unwrap(), "MyApp.Payment")); + } + #[test] fn extracts_dependency_references_from_supported_import_forms() { let directory = tempdir().unwrap(); @@ -415,6 +433,7 @@ mod tests { assert!(references.contains(&"alpha.beta".to_owned())); assert!(references.contains(&"gamma.delta".to_owned())); assert!(!references.contains(&"as".to_owned())); + assert!(!references.contains(&"run".to_owned())); for kind in ["alias", "import", "require", "use", "call"] { let mut values = Vec::new(); diff --git a/src/report.rs b/src/report.rs index 5bea2a3..3c21a90 100644 --- a/src/report.rs +++ b/src/report.rs @@ -454,6 +454,14 @@ mod tests { let isolated_mermaid = render_impact_mermaid(&isolated_impact_assessment); assert!(isolated_mermaid.contains("impact_0[\"src/only.py\"]")); assert!(!isolated_mermaid.contains("-->")); + + let isolated_report = render_markdown(&ReportInput { + assessment: &isolated_impact_assessment, + gate: None, + parsed_files: &[], + version: "test", + }); + assert!(!isolated_report.contains("chain:")); } #[test] @@ -498,5 +506,11 @@ mod tests { assert_eq!(utf8_prefix_len("release 🚀", 9), 8); assert_eq!(utf8_prefix_len("release 🚀", 12), 12); assert_eq!(utf8_prefix_len("abc", 10), 3); + + let suffix = "\n\n_Report truncated by configured resource limit._\n"; + assert_eq!( + truncate_utf8("abcdefghij".repeat(10), suffix.len() + 4), + format!("abcd{suffix}") + ); } } diff --git a/src/risk.rs b/src/risk.rs index 2659f32..122bd6d 100644 --- a/src/risk.rs +++ b/src/risk.rs @@ -613,6 +613,37 @@ mod tests { to: "src/b.py".into() }] ); + assert!( + !dependency_graph(&files) + .iter() + .any(|edge| edge.from == edge.to) + ); + } + + #[test] + fn graph_paths_follow_edges_in_both_directions_without_revisiting_nodes() { + let graph = vec![ + DependencyEdge { + from: "src/a.py".into(), + to: "src/b.py".into(), + }, + DependencyEdge { + from: "src/b.py".into(), + to: "src/c.py".into(), + }, + ]; + let paths = graph_paths("src/b.py", &graph); + assert!( + paths + .iter() + .any(|(path, chain)| { path == "src/a.py" && chain == &["src/b.py", "src/a.py"] }) + ); + assert!( + paths + .iter() + .any(|(path, chain)| { path == "src/c.py" && chain == &["src/b.py", "src/c.py"] }) + ); + assert_eq!(paths.len(), 2); } #[test] diff --git a/src/telemetry.rs b/src/telemetry.rs index 9e264ab..b004e0a 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -25,12 +25,27 @@ fn render(name: &str, fields: &[(&str, String)]) -> Option { #[cfg(test)] mod tests { use super::*; + use std::sync::Mutex; + + static LOG_ENV_LOCK: Mutex<()> = Mutex::new(()); #[test] fn disabled_logging_does_not_require_output_configuration() { + let _guard = LOG_ENV_LOCK.lock().unwrap(); + unsafe { std::env::remove_var("RISK_GATE_LOG") }; assert!(event("test", &[("value", "secret".into())]).is_none()); } + #[test] + fn enabled_logging_returns_and_emits_json() { + let _guard = LOG_ENV_LOCK.lock().unwrap(); + unsafe { std::env::set_var("RISK_GATE_LOG", "json") }; + let result = event("test", &[("value", "secret".into())]); + unsafe { std::env::remove_var("RISK_GATE_LOG") }; + assert!(result.is_some()); + assert!(result.unwrap().contains(r#""event":"test""#)); + } + #[test] fn renders_json_without_secret_filtering_or_shell_interpretation() { let line = render("test", &[("value", "a\"b".into())]).unwrap(); From 9de5c51bce6589a380ac58e73ad530fe0d39d24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ramos?= Date: Sun, 11 Oct 2026 16:13:44 -0300 Subject: [PATCH 4/4] Run mutation testing manually --- .github/workflows/ci.yml | 3 --- .github/workflows/mutation.yml | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/mutation.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16ea7c6..34bb48d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ jobs: with: components: rustfmt, clippy, llvm-tools-preview - uses: taiki-e/install-action@cargo-llvm-cov - - uses: taiki-e/install-action@cargo-mutants - name: Require branch rebased on master run: | git fetch --no-tags origin master @@ -32,5 +31,3 @@ jobs: run: cargo clippy --all-targets --all-features -- -D warnings - run: cargo test --all-targets --all-features - run: cargo llvm-cov --workspace --all-features --fail-under-lines 95.01 - - run: cargo mutants --baseline run --copy-target true --minimum-test-timeout 60 --no-shuffle -j 4 - - run: bash scripts/check-mutation-score.sh diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..391268c --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,19 @@ +name: Mutation testing + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + mutation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-mutants + - run: cargo mutants --baseline run --copy-target true --minimum-test-timeout 60 --no-shuffle -j 4 + - run: bash scripts/check-mutation-score.sh