diff --git a/.github/workflows/harness-quality.yml b/.github/workflows/harness-quality.yml index 12dd3df..975f92a 100644 --- a/.github/workflows/harness-quality.yml +++ b/.github/workflows/harness-quality.yml @@ -74,6 +74,9 @@ jobs: - name: Reject whitespace errors run: git diff --check + - name: Check sibling-harness parity manifest + run: python scripts/parity_check.py + generated-profiles: strategy: fail-fast: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 3433ffc..05d4a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and ### Added +- Documentation parity with the sibling `claude-python-engineering-harness`: + `docs/EVALUATION.md` (reproducible evaluation guide and acceptance criteria), + `docs/ENTERPRISE_ROLLOUT.md` (ownership, distribution, MCP rollout, and metrics), and pt-BR + translations `docs/LOOPS.pt-BR.md`, `docs/UPGRADING.pt-BR.md`, and + `docs/ENTERPRISE_ROLLOUT.pt-BR.md`, under the language policy now documented in + `CONTRIBUTING.md`. +- `scripts/parity_check.py` with `parity-manifest.json` and `parity-exceptions.json`: a + deterministic check, shared with the sibling `claude-python-engineering-harness`, that fails CI + when a parity-required artifact is missing and is not declared as an intentional divergence. + Wired into CI. - Phase 0-1 (report-only) Evidence-Gated Engineering Loop foundation, see `docs/LOOPS.md`: - `.loop/**` and `scripts/loop_*` are now denylisted for agent writes in `protect_sensitive_files.py` (template and plugin), so an agent cannot silently build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f875b44..d61c19e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,23 @@ evaluation order requires it. Prefer changing the canonical template first, then port the equivalent plugin or profile change. Regression tests verify duplicated security scripts remain identical. +## Language policy + +English is the canonical language for all documentation. The following documents must also ship a +`.pt-BR.md` sibling, updated in the same change: `docs/LOOPS.md`, `docs/UPGRADING.md`, and +`docs/ENTERPRISE_ROLLOUT.md`. Other documents may be translated opportunistically, but a stale +translation is worse than none: if you cannot update the pair, say so in the pull request. The +sibling `claude-python-engineering-harness` follows the same policy so both harnesses keep the +same language matrix. + +## Sibling-harness parity + +This harness and `claude-python-engineering-harness` share a parity manifest +(`parity-manifest.json`, byte-identical in both repositories) checked in CI by +`scripts/parity_check.py`. When adding or removing a parity-relevant artifact, update the manifest +in both repositories in the same change, or declare an intentional divergence with a reason in +this repository's `parity-exceptions.json`. + ## Pull requests - Keep changes focused and link the relevant issue when one exists. diff --git a/README.md b/README.md index b560781..de911ac 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,11 @@ uv run python scripts/quality_gate.py ``` See `VALIDATION.md` for the latest verification record and `SOURCES.md` for official OpenAI -references used by this port. See `docs/VERSIONING.md` for artifact lifecycles, -`docs/UPGRADING.md` for non-destructive upgrades, `CHANGELOG.md` for release history, and +references used by this port. See `docs/EVALUATION.md` for the reproducible evaluation guide and +acceptance criteria, `docs/VERSIONING.md` for artifact lifecycles, `docs/UPGRADING.md` for +non-destructive upgrades (também em português: `docs/UPGRADING.pt-BR.md`), +`docs/ENTERPRISE_ROLLOUT.md` for rollout guidance, `CHANGELOG.md` for release history, and `SECURITY.md` for private vulnerability reporting. Community participation is covered by -`CONTRIBUTING.md`, `SUPPORT.md`, and `CODE_OF_CONDUCT.md`. +`CONTRIBUTING.md`, `SUPPORT.md`, and `CODE_OF_CONDUCT.md`. Structural parity with the sibling +`claude-python-engineering-harness` is checked in CI by `scripts/parity_check.py` against +`parity-manifest.json`. diff --git a/docs/ENTERPRISE_ROLLOUT.md b/docs/ENTERPRISE_ROLLOUT.md new file mode 100644 index 0000000..25a504f --- /dev/null +++ b/docs/ENTERPRISE_ROLLOUT.md @@ -0,0 +1,86 @@ +# Enterprise rollout + +*[Português](ENTERPRISE_ROLLOUT.pt-BR.md)* + +Use the repository scaffold for project-specific policy and the plugin marketplace for reusable +capabilities. + +## Recommended ownership + +- Platform engineering owns the plugin, hook runtime, approved configuration, and CI baseline. +- Security owns blocked paths, dangerous command patterns, secret patterns, and exception + governance. +- Architecture owns dependency contracts and the standard ADR template. +- Each product team owns its `AGENTS.md`, project configuration, data inventory, and acceptance + criteria. + +## Distribution model + +1. Publish this repository in a controlled internal Git host. +2. Add it as an internal marketplace + (`codex plugin marketplace add `). +3. Validate and version the plugin before promotion. +4. Pin or approve versions through your configuration-management baseline where available. +5. Roll out first to a pilot group and inspect denials, false positives, latency, and developer + overrides. +6. Promote only after the generated project and plugin pass the validation checklist. + +## Separation of concerns + +- Keep project facts and commands in the repository (`AGENTS.md`, `.codex/config.toml`). +- Keep reusable procedures and skills in the plugin. +- Keep mandatory controls in hooks, sandboxing, CI, identity, network, and repository protection. +- Require explicit trust of `.codex/config.toml` and `.codex/hooks.json`; review new or changed + hooks with `/hooks` before trusting them. +- Do not place credentials in plugin settings or MCP configuration. +- Treat MCP servers and external integrations as data egress paths that require an explicit + threat model. + +## MCP rollout model + +Treat MCP as an integration platform, not a developer convenience toggle. Recommended progression: + +1. Inventory the external system, owner, data classes, operations, authentication, and retention. +2. Pilot with a read-only identity and non-production data. +3. Review the server implementation, release process, dependency pinning, prompt-injection + exposure, and network destinations. +4. Publish approved project servers through `[mcp_servers.*]` in `.codex/config.toml`; the + project validator enforces TLS, environment-name credential indirection, exact + ephemeral-runner versions, direct STDIO commands, and bounded timeouts. +5. Keep credentials per user through environment indirection or a credential helper. Never place + secrets in shared configuration. +6. Monitor server and tool use through the platform's native OpenTelemetry export without + collecting full inputs or outputs. +7. Reapprove integrations periodically and revoke unused servers, scopes, and credentials. + +For strict regulated environments, prefer a fixed approved server set or disable MCP entirely +until each integration has an approved threat model. + +## Change governance + +Every harness release should include: + +- semantic version; +- release notes and migration notes; +- test evidence for allowed and denied hook cases; +- plugin validation evidence; +- compatibility statement for the supported Codex and Python versions; +- rollback instructions; +- named owner and exception process. + +## Metrics + +Track adoption and control effectiveness without collecting source code or prompts: + +- repositories and developers on each harness version; +- hook denials by category, from each project's local `.codex/logs/hooks-audit.jsonl` (written by + `log_event` in `.codex/hooks/_common.py`; one JSON line per deny/block decision with timestamp, + hook name, category, decision, and tool name - never command text, file contents, or matched + values). Aggregate this file centrally through your existing log pipeline; it is not collected + automatically; +- false-positive and override rates; +- quality-gate duration and failure category; +- time to remediate secrets and vulnerable dependencies; +- percentage of projects with current lock files and documented data handling; +- MCP servers by owner, version, scope, and review expiry; +- OpenTelemetry-derived agent and tool-use metrics, kept to metadata. diff --git a/docs/ENTERPRISE_ROLLOUT.pt-BR.md b/docs/ENTERPRISE_ROLLOUT.pt-BR.md new file mode 100644 index 0000000..d860028 --- /dev/null +++ b/docs/ENTERPRISE_ROLLOUT.pt-BR.md @@ -0,0 +1,89 @@ +# Rollout corporativo + +*[English](ENTERPRISE_ROLLOUT.md)* + +Use o scaffold do repositório para políticas específicas do projeto e o marketplace de plugins +para capacidades reutilizáveis. + +## Ownership recomendado + +- Engenharia de plataforma é dona do plugin, do runtime de hooks, da configuração aprovada e da + baseline de CI. +- Segurança é dona dos caminhos bloqueados, padrões de comandos perigosos, padrões de secrets e + da governança de exceções. +- Arquitetura é dona dos contratos de dependência e do template padrão de ADR. +- Cada time de produto é dono do seu `AGENTS.md`, configuração do projeto, inventário de dados e + critérios de aceite. + +## Modelo de distribuição + +1. Publique este repositório em um Git host interno controlado. +2. Adicione-o como marketplace interno + (`codex plugin marketplace add `). +3. Valide e versione o plugin antes da promoção. +4. Fixe ou aprove versões pela sua baseline de gestão de configuração, quando disponível. +5. Faça o rollout primeiro para um grupo piloto e inspecione negações, falsos positivos, latência + e overrides de desenvolvedores. +6. Promova apenas depois que o projeto gerado e o plugin passarem no checklist de validação. + +## Separação de responsabilidades + +- Mantenha fatos e comandos do projeto no repositório (`AGENTS.md`, `.codex/config.toml`). +- Mantenha procedimentos e skills reutilizáveis no plugin. +- Mantenha controles obrigatórios em hooks, sandboxing, CI, identidade, rede e proteção de + repositório. +- Exija trust explícito de `.codex/config.toml` e `.codex/hooks.json`; revise hooks novos ou + alterados com `/hooks` antes de confiar neles. +- Não coloque credenciais em configurações de plugin ou de MCP. +- Trate servidores MCP e integrações externas como caminhos de saída de dados que exigem um + threat model explícito. + +## Modelo de rollout de MCP + +Trate MCP como uma plataforma de integração, não como uma conveniência de desenvolvedor. +Progressão recomendada: + +1. Inventarie o sistema externo, dono, classes de dados, operações, autenticação e retenção. +2. Pilote com uma identidade somente-leitura e dados não produtivos. +3. Revise a implementação do servidor, processo de release, pinning de dependências, exposição a + prompt injection e destinos de rede. +4. Publique servidores aprovados do projeto via `[mcp_servers.*]` em `.codex/config.toml`; o + validador do projeto exige TLS, indireção de credenciais por nome de variável de ambiente, + versões exatas de runners efêmeros, comandos STDIO diretos e timeouts limitados. +5. Mantenha credenciais por usuário via indireção de ambiente ou credential helper. Nunca coloque + secrets em configuração compartilhada. +6. Monitore uso de servidores e ferramentas pelo export nativo de OpenTelemetry da plataforma, + sem coletar entradas ou saídas completas. +7. Reaprove integrações periodicamente e revogue servidores, escopos e credenciais sem uso. + +Para ambientes regulados estritos, prefira um conjunto fixo de servidores aprovados ou desabilite +MCP inteiramente até que cada integração tenha um threat model aprovado. + +## Governança de mudanças + +Cada release do harness deve incluir: + +- versão semântica; +- release notes e notas de migração; +- evidência de teste para casos permitidos e negados dos hooks; +- evidência de validação do plugin; +- declaração de compatibilidade com as versões suportadas de Codex e Python; +- instruções de rollback; +- dono nomeado e processo de exceção. + +## Métricas + +Acompanhe adoção e efetividade dos controles sem coletar código-fonte ou prompts: + +- repositórios e desenvolvedores em cada versão do harness; +- negações de hooks por categoria, a partir do `.codex/logs/hooks-audit.jsonl` local de cada + projeto (escrito por `log_event` em `.codex/hooks/_common.py`; uma linha JSON por decisão de + negação/bloqueio com timestamp, nome do hook, categoria, decisão e nome da ferramenta - nunca + texto de comando, conteúdo de arquivo ou valores identificados). Agregue esse arquivo + centralmente pela sua pipeline de logs existente; ele não é coletado automaticamente; +- taxas de falso positivo e de override; +- duração do quality gate e categoria de falha; +- tempo para remediar secrets e dependências vulneráveis; +- percentual de projetos com lock files atualizados e tratamento de dados documentado; +- servidores MCP por dono, versão, escopo e expiração de revisão; +- métricas de uso de agente e ferramentas derivadas de OpenTelemetry, restritas a metadados. diff --git a/docs/EVALUATION.md b/docs/EVALUATION.md new file mode 100644 index 0000000..c4263ff --- /dev/null +++ b/docs/EVALUATION.md @@ -0,0 +1,85 @@ +# Evaluation guide + +This guide makes the harness claims reproducible. It separates repository checks from checks run in +freshly generated projects and records the commands, expected artifacts, and acceptance criteria. +It expands the README's five-minute evaluation. + +## Five-minute evaluation + +From a clean checkout with Python, Git, and uv installed: + +```bash +python bootstrap.py --name demo-service --package demo_service \ + --target ../demo-service --profile service --git-init --lock +cd ../demo-service +uv sync --frozen --all-groups +uv run python scripts/quality_gate.py +``` + +The generated project should contain: + +```text +demo-service/ +├── .agents/skills/ # invocable project skills +├── .codex/ # config.toml, hooks.json, and hook scripts +├── .github/workflows/ # CI invoking the project-owned quality gate +├── docs/ # architecture, privacy, MCP, and observability guidance +├── scripts/ # deterministic quality and policy validators +├── src/demo_service/ # Clean Architecture package roots +├── tests/ # starter regression tests +├── .harness.json # generation metadata and content hashes +├── AGENTS.md +├── Dockerfile +└── pyproject.toml +``` + +Trust the repository before expecting `.codex/config.toml` and `.codex/hooks.json` to load, and +review new hooks with `/hooks` in Codex. The evaluation needs no credentials. + +## Profile comparison + +| Capability | service | library | workspace | +|---|---|---|---| +| Installable root package | Yes | Yes | No | +| Runtime container | Yes | No | No | +| Strict Mypy and tests | Yes | Yes | Per member | +| Clean Architecture boundaries | Yes | Yes | Per configured root | +| Observability starter (OpenTelemetry) | Optional | No | Per member | +| Intended use | Deployable backend | Reusable package | Multi-package repository | + +## Acceptance criteria + +The repository is ready for release when all of the following are true at the release commit: + +1. The repository-owned gate (`uv run python scripts/quality_gate.py`) passes, including + regression tests, Ruff, format, Mypy, Bandit, pip-audit, and the governance gate. +2. Fresh `service`, `library`, and `workspace` projects render for every supported Python version. +3. Each generated project passes frozen dependency sync and its own `scripts/quality_gate.py`. +4. The service image builds and runs as a non-root user; non-service profiles emit no Dockerfile. +5. Merge, dry-run, check, conflict numbering, file-mode preservation, and symlink-confinement + regressions pass. +6. Hook and MCP tests cover malformed input, secret scanning, sensitive paths, trust + classification, and prohibited literal credentials. +7. `VALIDATION.md` records the date, Python version, release commit, commands, and observed + results. + +## Recording results + +Do not update pass counts by hand without running the corresponding command. For a release, +capture: + +```bash +git rev-parse HEAD +python3 --version +uv run python scripts/quality_gate.py +git diff --check +``` + +Then run the generated-profile matrix in CI or reproduce each supported profile and Python version +locally. Link the successful workflow run from `VALIDATION.md`; do not treat a previous run from a +different commit as evidence for the release. + +Dependabot checks pinned GitHub Actions and container references that appear directly in workflow +and Docker files. The Python base-image digests in `bootstrap.py` are template data and require a +manual refresh for every supported Python minor. Review upstream release notes, verify each digest +with `docker buildx imagetools inspect`, and require the complete CI matrix before merging updates. diff --git a/docs/LOOPS.pt-BR.md b/docs/LOOPS.pt-BR.md new file mode 100644 index 0000000..392fe16 --- /dev/null +++ b/docs/LOOPS.pt-BR.md @@ -0,0 +1,125 @@ +# Evidence-Gated Engineering Loops + +*[English](LOOPS.md)* + +Os schemas compartilhados de contrato, evidência, veredito e resultado do +builder deste harness vivem em um repositório separado, +[`engineering-loop-schemas`](https://github.com/brunovicco/engineering-loop-schemas), +para que tanto este harness quanto seu irmão +(`claude-python-engineering-harness`) validem contratos de loop contra uma +única fonte canônica, em vez de manterem cópias divergentes. Esse +repositório também é o embrião da futura camada de loop do harness +unificado ("alicerce"). + +## Estado atual: Fase 1, somente relatório (report-only) + +A autonomia de loop neste harness é atualmente **`report`** e nada além +disso. Concretamente, a partir desta integração: + +- Nenhum agente neste repositório, ou em um projeto gerado a partir dele, + pode promover uma mudança candidata, executar um loop de ponta a ponta + ou certificar o próprio trabalho. +- `loop_runner.py`, `loop_gate.py`, `loop_state.py`, um avaliador + (evaluator) ou qualquer tipo de máquina de estados não existem. Construí-los + está explicitamente fora do escopo desta fase. +- `.loop/**` e `scripts/loop_*` estão na denylist de escrita por agentes em + `protect_sensitive_files.py` (veja `.codex/hooks/protect_sensitive_files.py` + em um projeto gerado). Espera-se que apenas um humano, editando fora de + uma chamada de ferramenta do agente, coloque um contrato real em + `.loop/contracts/`. +- A verificação `loop-contracts` do `scripts/quality_gate.py` valida + qualquer contrato encontrado em `.loop/contracts/**` contra os schemas + acima. Sem contratos presentes -- o estado esperado hoje -- ela é um + no-op documentado. +- O workflow de CI de autoavaliação (veja `.github/workflows/` do + repositório), que renderiza cada perfil e reporta os resultados dos + gates, é ele próprio somente-relatório: nunca modifica código do + repositório, e sua etapa opcional de interpretação por agente fica + desabilitada por padrão atrás de uma flag, sem credenciais no repositório. + +## O modelo de três níveis + +Toda execução de loop pertence a um dos três níveis de escrutínio, +espelhando o README do `engineering-loop-schemas`: + +1. **Nível do agente (agent-level)** -- uma única tentativa do builder + contra um contrato. Sua saída é um documento `builder-result`: o + próprio relato do builder sobre o que tentou, explicitamente marcado + como não-autoritativo. +2. **Nível de conclusão (completion-level)** -- uma execução completa: + tentativa(s) do builder, coleta mecânica de `evidence` (comandos com + hash, saída com hash, `baseline_sha`/`candidate_sha` exatos), e um + `verdict` derivado ao avaliar essa evidência contra os + `acceptance.hard_gates` do contrato. +3. **Nível operacional (operational-level)** -- a saúde do próprio loop ao + longo de muitas execuções: consumo de budget, taxa de escalonamento, e + divergência entre o que um contrato declara e o que de fato acontece. + +## Princípios não negociáveis + +- Um builder nunca certifica o próprio resultado. Apenas um `verdict` + derivado mecanicamente pode fazê-lo. +- Um hard gate é default-FAIL e precisa se reduzir a um comando com código + de saída. Os hard gates referenciáveis pelo contrato + (`acceptance.hard_gates`) são exatamente as verificações + nomeadas que o `quality_gate.py` deste harness já implementa: `lock`, + `lint`, `format`, `typing`, `tests`, `security`, `dependencies`, + `architecture`, `mcp`, `governance`. + Verificações obrigatórias de infraestrutura, incluindo `loop-schema-vendor` e + `loop-contracts`, protegem proveniência, integridade e validação contratual em todas as + execuções; elas não são comandos arbitrários selecionáveis pelo builder. +- A evidência é vinculada a commits exatos (`baseline_sha`, + `candidate_sha`) e a um ambiente com hash (`uv_lock_sha256`), de modo que + um veredito sempre possa ser rastreado até exatamente o que rodou contra + exatamente qual código. +- Os hooks (`protect_sensitive_files.py`, `validate_bash.py`, + `guard_mcp.py`) são defesa em profundidade, não orquestração. Eles + impedem que um agente exceda silenciosamente o escopo declarado desta + fase; eles não executam, agendam ou promovem nada. + +## Estados finais + +Toda execução concluída se resolve em exatamente um estado final +(`verdict.final_state`): + +| Estado | Significado | +| --- | --- | +| `SUCCEEDED` | Todos os hard gates passaram; candidato é promovível, pendente de revisão humana. | +| `NO_OP` | O builder determinou corretamente que não havia nada a fazer. | +| `NO_PROGRESS` | O builder produziu um candidato, mas ele não melhora em relação ao baseline. | +| `VERIFY_FAILED` | Um ou mais hard gates falharam contra o candidato. | +| `POLICY_BLOCKED` | O candidato tocou `scope.denylist` ou uma entrada de `actions.denied`. | +| `BUDGET_EXCEEDED` | `budgets` (tokens, custo, tempo de parede ou contagem de comandos) foi excedido. | +| `ESCALATED` | A execução não conseguiu resolver PASS/FAIL e precisa de uma decisão humana. | +| `INFRA_FAILED` | A execução falhou por razões não relacionadas ao candidato (ferramental, rede, ambiente). | + +## Vendoring + +`template/scripts/_vendor_loop_schemas/` é um bundle determinístico renderizado a partir do +`engineering-loop-schemas v0.1.2`, fixado no commit completo +`0459d61b7b1d4e7b46709e6d3895770553e6fab0`. Seu `manifest.json` registra repositório de origem, +versão, commit, tamanhos, hashes SHA-256 e a adaptação de import declarada. + +O bundle não é uma cópia byte a byte. Durante a renderização, o import de pacote em +`validate_contract.py` muda de `loop_schemas` para `_vendor_loop_schemas`; isso isola o pacote +vendorizado e evita colisão com o namespace protegido `scripts/loop_*`. A adaptação está explícita +no manifesto e coberta por testes de renderização determinística, integridade e adulteração. O +gate `loop-schema-vendor` verifica o bundle offline em cada quality gate de projeto gerado. +Corrija o repositório canônico de schemas e renderize uma nova versão, em vez de editar o bundle +manualmente. + +`validate_contract.py` é somente-stdlib. Ler um contrato YAML requer que o +PyYAML seja importável no ambiente em que `scripts/quality_gate.py` +executa; contratos JSON sempre funcionam sem dependência extra. O PyYAML +deliberadamente **não** foi adicionado como dependência do harness por +esta integração (o núcleo compartilhado está em congelamento de features -- +veja `CONTRIBUTING.md`); se um humano quiser validar contratos YAML +localmente, deve adicionar o PyYAML ao próprio projeto. + +## Fora do escopo desta integração + +Conforme o plano aprovado, esta fase **não** adiciona um executor de loop, +um executor de gates, uma máquina de estados, um avaliador, ou qualquer +autonomia acima de `report`. Também não cria o futuro harness unificado +nem uma CLI `alicerce`. Isso é trabalho subsequente, uma vez que o Sprint 0 +e esta fundação sejam revisados. diff --git a/docs/UPGRADING.pt-BR.md b/docs/UPGRADING.pt-BR.md new file mode 100644 index 0000000..940f4e7 --- /dev/null +++ b/docs/UPGRADING.pt-BR.md @@ -0,0 +1,42 @@ +# Atualizando projetos gerados + +*[English](UPGRADING.md)* + +Projetos gerados são snapshots e nunca são modificados automaticamente. + +## Fluxo recomendado + +1. Leia o `CHANGELOG.md` e confirme a versão-alvo do harness. +2. Faça commit ou preserve de outra forma o estado atual do projeto gerado. +3. Rode uma prévia a partir do checkout do novo harness: + + ```bash + python /caminho/para/harness/bootstrap.py --target . --dry-run --merge + ``` + +4. Aplique a atualização não destrutiva: + + ```bash + python /caminho/para/harness/bootstrap.py --target . --merge + ``` + +5. Revise cada arquivo de conflito `*.harness-new`. O harness preserva arquivos customizados + localmente e nunca trata a cópia de conflito como substituição automática. +6. Rode o gate do projeto gerado e a verificação de metadados: + + ```bash + uv sync --all-groups + uv run python scripts/quality_gate.py + python /caminho/para/harness/bootstrap.py --target . --check + ``` + +7. Revise e confie (trust) nos hooks do Codex alterados antes de usá-los. + +## Atualizações do plugin + +Releases do plugin são independentes das releases do gerador. Atualize a instalação do +marketplace, reinicie o Codex quando necessário, revise hooks e permissões alterados e valide o +plugin antes de adotá-lo. Uma atualização de plugin não reescreve projetos gerados. + +Não copie hashes do manifesto, não apague arquivos de conflito sem revisão e não substitua +arquivos customizados do projeto apenas para fazer o `--check` passar. diff --git a/parity-exceptions.json b/parity-exceptions.json new file mode 100644 index 0000000..48c7088 --- /dev/null +++ b/parity-exceptions.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "exceptions": [ + { + "id": "profile-library-ci-overlay", + "reason": "This harness ships a profiles/library CI overlay because its template workflow differs per profile; the Claude sibling's library profile inherits the template workflow unchanged. Documented in the sibling's docs/ARCHITECTURE.md." + }, + { + "id": "observability-adapter", + "reason": "LLM observability starter is an OpenTelemetry adapter (--extra observability) because Codex exposes native OTel export; the Claude sibling ships Langfuse metadata-only tracing instead. Documented in the sibling's docs/ARCHITECTURE.md." + } + ] +} diff --git a/parity-manifest.json b/parity-manifest.json new file mode 100644 index 0000000..43e71db --- /dev/null +++ b/parity-manifest.json @@ -0,0 +1,49 @@ +{ + "version": 1, + "description": "Family-independent artifacts both Python engineering harnesses must ship. Family-specific artifacts (hooks, plugin, and marketplace manifests) are derived at runtime by scripts/parity_check.py. Keep this file byte-identical in codex-python-engineering-harness and claude-python-engineering-harness; update both repositories in the same change. Intentional divergences are declared per-repository in parity-exceptions.json.", + "artifacts": [ + {"id": "readme", "path": "README.md"}, + {"id": "changelog", "path": "CHANGELOG.md"}, + {"id": "license", "path": "LICENSE"}, + {"id": "contributing", "path": "CONTRIBUTING.md"}, + {"id": "security-policy", "path": "SECURITY.md"}, + {"id": "support-policy", "path": "SUPPORT.md"}, + {"id": "code-of-conduct", "path": "CODE_OF_CONDUCT.md"}, + {"id": "validation-record", "path": "VALIDATION.md"}, + {"id": "sources", "path": "SOURCES.md"}, + {"id": "doc-architecture", "path": "docs/ARCHITECTURE.md"}, + {"id": "doc-versioning", "path": "docs/VERSIONING.md"}, + {"id": "doc-loops", "path": "docs/LOOPS.md"}, + {"id": "doc-loops-pt-br", "path": "docs/LOOPS.pt-BR.md"}, + {"id": "doc-upgrading", "path": "docs/UPGRADING.md"}, + {"id": "doc-upgrading-pt-br", "path": "docs/UPGRADING.pt-BR.md"}, + {"id": "doc-evaluation", "path": "docs/EVALUATION.md"}, + {"id": "doc-enterprise-rollout", "path": "docs/ENTERPRISE_ROLLOUT.md"}, + {"id": "doc-enterprise-rollout-pt-br", "path": "docs/ENTERPRISE_ROLLOUT.pt-BR.md"}, + {"id": "root-pyproject", "path": "pyproject.toml"}, + {"id": "root-lockfile", "path": "uv.lock"}, + {"id": "bootstrap", "path": "bootstrap.py"}, + {"id": "repo-quality-gate", "path": "scripts/quality_gate.py"}, + {"id": "loop-self-evaluation", "path": "scripts/loop_self_evaluation.py"}, + {"id": "parity-script", "path": "scripts/parity_check.py"}, + {"id": "parity-exceptions", "path": "parity-exceptions.json"}, + {"id": "tests-harness", "path": "tests/test_harness.py"}, + {"id": "tests-loop-vendor", "path": "tests/test_loop_schema_vendor.py"}, + {"id": "ci-harness-quality", "path": ".github/workflows/harness-quality.yml"}, + {"id": "ci-loop-self-evaluation", "path": ".github/workflows/loop-self-evaluation.yml"}, + {"id": "governance-readme", "path": "governance/README.md"}, + {"id": "governance-catalog", "path": "governance/catalog/controls.json"}, + {"id": "governance-overlay-dora", "path": "governance/overlays/dora.json"}, + {"id": "governance-overlay-iso-42001", "path": "governance/overlays/iso-iec-42001.json"}, + {"id": "governance-overlay-800-53", "path": "governance/overlays/nist-sp-800-53.json"}, + {"id": "template-pyproject", "path": "template/pyproject.toml"}, + {"id": "template-quality-gate", "path": "template/scripts/quality_gate.py"}, + {"id": "template-validate-architecture", "path": "template/scripts/validate_architecture.py"}, + {"id": "template-validate-mcp", "path": "template/scripts/validate_mcp_config.py"}, + {"id": "template-governance-gate", "path": "template/scripts/governance_gate.py"}, + {"id": "template-loop-contracts", "path": "template/scripts/validate_loop_contracts.py"}, + {"id": "template-loop-vendor-manifest", "path": "template/scripts/_vendor_loop_schemas/manifest.json"}, + {"id": "profile-library-pyproject", "path": "profiles/library/pyproject.toml"}, + {"id": "profile-workspace-pyproject", "path": "profiles/workspace/pyproject.toml"} + ] +} diff --git a/scripts/parity_check.py b/scripts/parity_check.py new file mode 100644 index 0000000..fc1de41 --- /dev/null +++ b/scripts/parity_check.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Verify that parity-required artifacts exist or are declared intentional divergences. + +The two sibling Python engineering harnesses must not drift silently. +``parity-manifest.json`` lists the family-independent artifacts both repositories are +expected to ship; family-specific artifacts (hooks, plugin, and marketplace manifests) +are derived at runtime from the detected family so the manifest stays byte-identical in +both repositories. ``parity-exceptions.json`` declares intentional divergences with a +reason. This script fails when a required artifact is missing for the local family and +is not covered by a declared exception. + +Both harnesses carry byte-identical copies of this script and of the manifest; the +exceptions file is repository-specific. Update the manifest in both repositories in the +same change. +""" + +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MANIFEST = ROOT / "parity-manifest.json" +EXCEPTIONS = ROOT / "parity-exceptions.json" + + +def family_artifacts(family: str) -> dict[str, str]: + """Return family-specific artifact paths, built without family-name literals.""" + dot = "." + family + if family == "codex": + return { + "template-hooks": f"template/{dot}/hooks/validate_bash.py", + "plugin-manifest": f"plugins/python-engineering-harness/{dot}-plugin/plugin.json", + "marketplace-manifest": ".agents/plugins/marketplace.json", + } + return { + "template-hooks": f"template/{dot}/hooks/validate_bash.py", + "plugin-manifest": f"plugin/python-engineering-harness/{dot}-plugin/plugin.json", + "marketplace-manifest": f"{dot}-plugin/marketplace.json", + } + + +def detect_family() -> str: + """Detect which harness family this repository belongs to.""" + for candidate in ("claude", "codex"): + if (ROOT / family_artifacts(candidate)["marketplace-manifest"]).is_file(): + return candidate + raise SystemExit("parity: cannot detect harness family (no marketplace manifest found)") + + +def load_json(path: Path) -> dict: + """Load one JSON document or exit with a readable error.""" + try: + return json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError: + raise SystemExit(f"parity: missing {path.name}") from None + except json.JSONDecodeError as error: + raise SystemExit(f"parity: invalid JSON in {path.name}: {error}") from None + + +def main() -> int: + """Check every required artifact for the local family.""" + family = detect_family() + manifest = load_json(MANIFEST) + exceptions = load_json(EXCEPTIONS) + + declared: dict[str, str] = {} + for entry in exceptions.get("exceptions", []): + identifier, reason = entry.get("id"), entry.get("reason") + if not identifier or not reason: + print(f"parity: exception without id or reason: {entry}", file=sys.stderr) + return 1 + declared[identifier] = reason + + required: dict[str, str] = {} + for artifact in manifest.get("artifacts", []): + identifier, relative = artifact.get("id"), artifact.get("path") + if not identifier or not relative: + print(f"parity: manifest artifact without id or path: {artifact}", file=sys.stderr) + return 1 + required[identifier] = relative + required.update(family_artifacts(family)) + + missing: list[str] = [] + checked = 0 + for identifier, relative in required.items(): + if identifier in declared: + continue + checked += 1 + if not (ROOT / relative).exists(): + missing.append(f"{identifier}: {relative}") + + if missing: + print( + f"parity: {len(missing)} required artifact(s) missing for family '{family}':", + file=sys.stderr, + ) + for item in missing: + print(f"- {item}", file=sys.stderr) + print( + "Add the artifact or declare an exception with a reason in parity-exceptions.json.", + file=sys.stderr, + ) + return 1 + + print( + f"parity: {checked} artifacts present for family '{family}' " + f"({len(declared)} declared exceptions)." + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())